diff --git a/.appveyor.yml b/.appveyor.yml index 86d231930..a77033ec1 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -6,81 +6,50 @@ init: # Uncomment previous line to get RDP access during the build. environment: - X64_EXT: -x64 EXECUTABLE: python.exe - PIP_DIR: Scripts - VENV: NO TEST_OPTIONS: DEPLOY: YES matrix: - - PYTHON: C:/Python38 - - PYTHON: C:/Python38-x64 - - PYTHON: C:/Python35 - - PYTHON: C:/Python35-x64 - - PYTHON: C:/msys64/mingw32 - EXECUTABLE: bin/python3 - PIP_DIR: bin - TEST_OPTIONS: --processes=0 - DEPLOY: NO - - PYTHON: C:/vp/pypy3 - EXECUTABLE: bin/pypy.exe - PIP_DIR: bin - VENV: YES + - PYTHON: C:/Python39 + ARCHITECTURE: x86 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 + - PYTHON: C:/Python36-x64 + ARCHITECTURE: x64 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 install: - curl -fsSL -o pillow-depends.zip https://github.com/python-pillow/pillow-depends/archive/master.zip - 7z x pillow-depends.zip -oc:\ - mv c:\pillow-depends-master c:\pillow-depends -- xcopy c:\pillow-depends\*.zip c:\pillow\winbuild\ -- xcopy c:\pillow-depends\*.tar.gz c:\pillow\winbuild\ -- xcopy /s c:\pillow-depends\test_images\* c:\pillow\tests\images +- xcopy /S /Y c:\pillow-depends\test_images\* c:\pillow\tests\images +- 7z x ..\pillow-depends\nasm-2.14.02-win64.zip -oc:\ +- ..\pillow-depends\gs9533w32.exe /S +- path c:\nasm-2.14.02;C:\Program Files (x86)\gs\gs9.53.3\bin;%PATH% - cd c:\pillow\winbuild\ - ps: | - if ($env:PYTHON -eq "c:/vp/pypy3") - { - c:\pillow\winbuild\appveyor_install_pypy3.cmd - } -- ps: | - if ($env:PYTHON -eq "c:/msys64/mingw32") - { - c:\msys64\usr\bin\bash -l -c c:\\pillow\\winbuild\\appveyor_install_msys2_deps.sh - } - else - { - c:\python37\python.exe c:\pillow\winbuild\build_dep.py - c:\pillow\winbuild\build_deps.cmd + c:\python37\python.exe c:\pillow\winbuild\build_prepare.py -v --depends=C:\pillow-depends\ + c:\pillow\winbuild\build\build_dep_all.cmd $host.SetShouldExit(0) - } -- curl -fsSL -o gs950.exe https://github.com/ArtifexSoftware/ghostpdl-downloads/releases/download/gs950/gs950w32.exe -- gs950.exe /S -- path %path%;C:\Program Files (x86)\gs\gs9.50\bin +- path C:\pillow\winbuild\build\bin;%PATH% +- '%PYTHON%\%EXECUTABLE% -m pip install -U "setuptools>=49.3.2"' build_script: - ps: | - if ($env:PYTHON -eq "c:/msys64/mingw32") - { - c:\msys64\usr\bin\bash -l -c c:\\pillow\\winbuild\\appveyor_build_msys2.sh - Write-Host "through install" + c:\pillow\winbuild\build\build_pillow.cmd install $host.SetShouldExit(0) - } - else - { - & $env:PYTHON/$env:EXECUTABLE c:\pillow\winbuild\build.py - $host.SetShouldExit(0) - } - cd c:\pillow - '%PYTHON%\%EXECUTABLE% selftest.py --installed' test_script: - cd c:\pillow -- '%PYTHON%\%PIP_DIR%\pip.exe install pytest pytest-cov' +- '%PYTHON%\%EXECUTABLE% -m pip install pytest pytest-cov' - c:\"Program Files (x86)"\"Windows Kits"\10\Debuggers\x86\gflags.exe /p /enable %PYTHON%\%EXECUTABLE% - '%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: -- pip install codecov +- python -m pip install codecov - codecov --file coverage.xml --name %PYTHON% --flags AppVeyor matrix: @@ -97,9 +66,9 @@ artifacts: before_deploy: - cd c:\pillow - - '%PYTHON%\%PIP_DIR%\pip.exe install wheel' + - '%PYTHON%\%EXECUTABLE% -m pip install wheel' - cd c:\pillow\winbuild\ - - '%PYTHON%\%EXECUTABLE% c:\pillow\winbuild\build.py --wheel' + - c:\pillow\winbuild\build\build_pillow.cmd bdist_wheel - cd c:\pillow - ps: Get-ChildItem .\dist\*.* | % { Push-AppveyorArtifact $_.FullName -FileName $_.Name } diff --git a/.ci/after_success.sh b/.ci/after_success.sh index dcf276daa..ff91b481e 100755 --- a/.ci/after_success.sh +++ b/.ci/after_success.sh @@ -7,13 +7,3 @@ if [[ $MATRIX_DOCKER ]]; then else coverage xml fi - -if [[ $TRAVIS ]]; then - codecov --flags TravisCI -fi - -if [ "$TRAVIS_PYTHON_VERSION" == "3.8" ]; then - # Coverage and quality reports on just the latest diff. - depends/diffcover-install.sh - depends/diffcover-run.sh -fi diff --git a/.ci/install.sh b/.ci/install.sh index 8e819631a..9372d0c51 100755 --- a/.ci/install.sh +++ b/.ci/install.sh @@ -1,32 +1,50 @@ #!/bin/bash +aptget_update() +{ + if [ ! -z $1 ]; then + echo "" + echo "Retrying apt-get update..." + echo "" + fi + output=`sudo apt-get update 2>&1` + echo "$output" + if [[ $output == *[WE]:\ * ]]; then + return 1 + fi +} +aptget_update || aptget_update retry || aptget_update retry + set -e -sudo apt-get update sudo apt-get -qq install libfreetype6-dev liblcms2-dev python3-tk\ ghostscript libffi-dev libjpeg-turbo-progs libopenjp2-7-dev\ cmake imagemagick libharfbuzz-dev libfribidi-dev -PYTHONOPTIMIZE=0 pip install cffi -pip install coverage -pip install olefile -pip install -U pytest -pip install -U pytest-cov -pip install pyroma -pip install test-image-results -pip install numpy -if [[ $TRAVIS_PYTHON_VERSION == 3.* ]]; then +python3 -m pip install --upgrade pip +PYTHONOPTIMIZE=0 python3 -m pip install cffi +python3 -m pip install coverage +python3 -m pip install olefile +python3 -m pip install -U pytest +python3 -m pip install -U pytest-cov +python3 -m pip install pyroma +python3 -m pip install test-image-results +# TODO Remove condition when numpy supports 3.10 +if ! [ "$GHA_PYTHON_VERSION" == "3.10-dev" ]; then python3 -m pip install numpy ; fi + +# TODO Remove when 3.8 / 3.9 includes setuptools 49.3.2+: +if [ "$GHA_PYTHON_VERSION" == "3.8" ]; then python3 -m pip install -U "setuptools>=49.3.2" ; fi +if [ "$GHA_PYTHON_VERSION" == "3.9" ]; then python3 -m pip install -U "setuptools>=49.3.2" ; fi + +# PyQt5 doesn't support PyPy3 +# Wheel doesn't yet support 3.10 +if [[ $GHA_PYTHON_VERSION == 3.* && $GHA_PYTHON_VERSION != "3.10-dev" ]]; then # arm64, ppc64le, s390x CPUs: # "ERROR: Could not find a version that satisfies the requirement pyqt5" - if [[ $TRAVIS_CPU_ARCH == "amd64" ]]; then - sudo apt-get -qq install pyqt5-dev-tools - pip install pyqt5!=5.14.1 - fi + sudo apt-get -qq install libxcb-xinerama0 pyqt5-dev-tools + python3 -m pip install pyqt5 fi -# docs only on Python 3.8 -if [ "$TRAVIS_PYTHON_VERSION" == "3.8" ]; then pip install -r requirements.txt ; fi - # webp pushd depends && ./install_webp.sh && popd diff --git a/.ci/test.sh b/.ci/test.sh index 516581ff0..5a19ec9b4 100755 --- a/.ci/test.sh +++ b/.ci/test.sh @@ -2,9 +2,4 @@ set -e -python -m pytest -v -x -W always --cov PIL --cov Tests --cov-report term Tests - -# Docs -if [ "$TRAVIS_PYTHON_VERSION" == "3.8" ] && [ "$TRAVIS_CPU_ARCH" == "amd64" ]; then - make doccheck -fi +python -bb -m pytest -v -x -W always --cov PIL --cov Tests --cov-report term Tests diff --git a/.clang-format b/.clang-format new file mode 100644 index 000000000..be32e6d1a --- /dev/null +++ b/.clang-format @@ -0,0 +1,20 @@ +# A clang-format style that approximates Python's PEP 7 +# Useful for IDE integration +BasedOnStyle: Google +AlwaysBreakAfterReturnType: All +AllowShortIfStatementsOnASingleLine: false +AlignAfterOpenBracket: AlwaysBreak +BinPackArguments: false +BinPackParameters: false +BreakBeforeBraces: Attach +ColumnLimit: 88 +DerivePointerAlignment: false +IndentWidth: 4 +Language: Cpp +PointerAlignment: Right +ReflowComments: true +SortIncludes: false +SpaceBeforeParens: ControlStatements +SpacesInParentheses: false +TabWidth: 4 +UseTab: Never diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 3d27b5d88..563fcda6a 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -9,7 +9,7 @@ Please send a pull request to the master branch. Please include [documentation]( - Fork the Pillow repository. - Create a branch from master. - Develop bug fixes, features, tests, etc. -- Run the test suite. You can enable [Travis CI](https://travis-ci.org/profile/) and [AppVeyor](https://ci.appveyor.com/projects/new) on your repo to catch test failures prior to the pull request, and [Codecov](https://codecov.io/gh) to see if the changed code is covered by tests. +- Run the test suite. You can enable GitHub Actions (https://github.com/MY-USERNAME/Pillow/actions) and [AppVeyor](https://ci.appveyor.com/projects/new) on your repo to catch test failures prior to the pull request, and [Codecov](https://codecov.io/gh) to see if the changed code is covered by tests. - Create a pull request to pull the changes from your branch to the Pillow master. ### Guidelines @@ -17,7 +17,7 @@ Please send a pull request to the master branch. Please include [documentation]( - Separate code commits from reformatting commits. - Provide tests for any newly added code. - Follow PEP 8. -- When committing only documentation changes please include [ci skip] in the commit message to avoid running tests on Travis-CI and AppVeyor. +- When committing only documentation changes please include `[ci skip]` in the commit message to avoid running tests on AppVeyor. ## Reporting Issues diff --git a/.github/codecov-upstream.yml b/.github/codecov-upstream.yml deleted file mode 100644 index 09a786fda..000000000 --- a/.github/codecov-upstream.yml +++ /dev/null @@ -1,18 +0,0 @@ -# Documentation: https://docs.codecov.io/docs/codecov-yaml - -codecov: - # Avoid "Missing base report" due to committing CHANGES.rst with "[CI skip]" - # https://github.com/codecov/support/issues/363 - # https://docs.codecov.io/docs/comparing-commits - allow_coverage_offsets: true - - token: 6dafc396-e7f5-4221-a38a-8b07a49fbdae - -comment: false - -# Matches 'omit:' in .coveragerc -ignore: - - "Tests/32bit_segfault_check.py" - - "Tests/bench_cffi_access.py" - - "Tests/check_*.py" - - "Tests/createfontdatachunk.py" diff --git a/.github/mergify.yml b/.github/mergify.yml new file mode 100644 index 000000000..4b8b113d3 --- /dev/null +++ b/.github/mergify.yml @@ -0,0 +1,13 @@ +pull_request_rules: + - name: Automatic merge + conditions: + - "#approved-reviews-by>=1" + - label=automerge + - status-success=Lint + - status-success=Test Successful + - status-success=Docker Test Successful + - status-success=Windows Test Successful + - status-success=continuous-integration/appveyor/pr + actions: + merge: + method: merge diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml new file mode 100644 index 000000000..4d855469a --- /dev/null +++ b/.github/release-drafter.yml @@ -0,0 +1,26 @@ +name-template: "$NEXT_MINOR_VERSION" +tag-template: "$NEXT_MINOR_VERSION" +change-template: '- $TITLE #$NUMBER [@$AUTHOR]' + +categories: + - title: "Dependencies" + label: "Dependency" + - title: "Deprecations" + label: "Deprecation" + - title: "Documentation" + label: "Documentation" + - title: "Removals" + label: "Removal" + - title: "Testing" + label: "Testing" + +exclude-labels: + - "changelog: skip" + +template: | + + https://pillow.readthedocs.io/en/stable/releasenotes/$NEXT_MINOR_VERSION.html + + ## Changes + + $CHANGES diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 576b88414..3c658293e 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -6,27 +6,43 @@ jobs: build: runs-on: ubuntu-latest - strategy: - matrix: - python-version: ["3.8"] - name: Python ${{ matrix.python-version }} + name: Lint steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v1 + - name: pip cache + uses: actions/cache@v2 with: - python-version: ${{ matrix.python-version }} + path: ~/.cache/pip + key: lint-pip-${{ hashFiles('**/setup.py') }} + restore-keys: | + lint-pip- + + - name: pre-commit cache + uses: actions/cache@v2 + 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@v2 + with: + python-version: 3.8 - name: Build system information run: python .github/workflows/system-info.py - name: Install dependencies run: | - python -m pip install --upgrade pip - python -m pip install --upgrade tox + python -m pip install -U pip + python -m pip install -U tox - name: Lint run: tox -e lint + env: + PRE_COMMIT_COLOR: always + diff --git a/.github/workflows/macos-install.sh b/.github/workflows/macos-install.sh index 6cd9dadf3..afcb9a5a7 100755 --- a/.github/workflows/macos-install.sh +++ b/.github/workflows/macos-install.sh @@ -2,16 +2,23 @@ set -e -brew install libtiff libjpeg openjpeg libimagequant webp little-cms2 freetype +brew install libtiff libjpeg openjpeg libimagequant webp little-cms2 freetype openblas libraqm -PYTHONOPTIMIZE=0 pip install cffi -pip install coverage -pip install olefile -pip install -U pytest -pip install -U pytest-cov -pip install pyroma -pip install test-image-results -pip install numpy +PYTHONOPTIMIZE=0 python3 -m pip install cffi +python3 -m pip install coverage +python3 -m pip install olefile +python3 -m pip install -U pytest +python3 -m pip install -U pytest-cov +python3 -m pip install pyroma +python3 -m pip install test-image-results + +echo -e "[openblas]\nlibraries = openblas\nlibrary_dirs = /usr/local/opt/openblas/lib" >> ~/.numpy-site.cfg +# TODO Remove condition when numpy supports 3.10 +if ! [ "$GHA_PYTHON_VERSION" == "3.10-dev" ]; then python3 -m pip install numpy ; fi + +# TODO Remove when 3.8 / 3.9 includes setuptools 49.3.2+: +if [ "$GHA_PYTHON_VERSION" == "3.8" ]; then python3 -m pip install -U "setuptools>=49.3.2" ; fi +if [ "$GHA_PYTHON_VERSION" == "3.9" ]; then python3 -m pip install -U "setuptools>=49.3.2" ; fi # extra test images pushd depends && ./install_extra_test_images.sh && popd diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml new file mode 100644 index 000000000..52456597b --- /dev/null +++ b/.github/workflows/release-drafter.yml @@ -0,0 +1,17 @@ +name: Release drafter + +on: + push: + # branches to consider in the event; optional, defaults to all + branches: + - master + +jobs: + update_release_draft: + if: github.repository == 'python-pillow/Pillow' + runs-on: ubuntu-latest + steps: + # Drafts your next release notes as pull requests are merged into "master" + - uses: release-drafter/release-drafter@v5 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/test-docker.yml b/.github/workflows/test-docker.yml index 363702c9e..eb173c359 100644 --- a/.github/workflows/test-docker.yml +++ b/.github/workflows/test-docker.yml @@ -7,32 +7,47 @@ jobs: runs-on: ubuntu-latest strategy: + fail-fast: false matrix: docker: [ + # Run slower jobs first to give them a headstart and reduce waiting time + ubuntu-20.04-focal-arm64v8, + ubuntu-20.04-focal-ppc64le, + ubuntu-20.04-focal-s390x, + # Then run the remainder alpine, + amazon-2-amd64, arch, - ubuntu-16.04-xenial-amd64, - ubuntu-18.04-bionic-amd64, - debian-9-stretch-x86, - debian-10-buster-x86, - centos-6-amd64, centos-7-amd64, centos-8-amd64, - amazon-1-amd64, - amazon-2-amd64, - fedora-30-amd64, - fedora-31-amd64, + debian-10-buster-x86, + fedora-32-amd64, + fedora-33-amd64, + ubuntu-18.04-bionic-amd64, + ubuntu-20.04-focal-amd64, ] dockerTag: [master] + include: + - docker: "ubuntu-20.04-focal-arm64v8" + qemu-arch: "aarch64" + - docker: "ubuntu-20.04-focal-ppc64le" + qemu-arch: "ppc64le" + - docker: "ubuntu-20.04-focal-s390x" + qemu-arch: "s390x" name: ${{ matrix.docker }} steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 - name: Build system information run: python .github/workflows/system-info.py + - name: Set up QEMU + if: "matrix.qemu-arch" + run: | + docker run --rm --privileged aptman/qus -s -- -p ${{ matrix.qemu-arch }} + - name: Docker pull run: | docker pull pythonpillow/${{ matrix.docker }}:${{ matrix.dockerTag }} @@ -45,7 +60,6 @@ jobs: sudo chown -R runner $GITHUB_WORKSPACE - name: After success - if: success() run: | PATH="$PATH:~/.local/bin" docker start pillow_container @@ -57,14 +71,16 @@ jobs: env: MATRIX_DOCKER: ${{ matrix.docker }} - - name: Prepare coverage token - if: success() && github.repository == 'python-pillow/Pillow' - run: cp .github/codecov-upstream.yml codecov.yml - - name: Upload coverage - if: success() uses: codecov/codecov-action@v1 with: - token: ${{ secrets.CODECOV_TOKEN }} flags: GHA_Docker name: ${{ matrix.docker }} + + success: + needs: build + runs-on: ubuntu-latest + name: Docker Test Successful + steps: + - name: Success + run: echo Docker Test Successful diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index 9853d425e..db1675135 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -4,12 +4,11 @@ on: [push, pull_request] jobs: build: - runs-on: windows-2019 strategy: fail-fast: false matrix: - python-version: ["3.5", "3.6", "3.7", "3.8", "pypy3"] + python-version: ["pypy-3.6", "pypy-3.7", "3.6", "3.7", "3.8", "3.9", "3.10-dev"] architecture: ["x86", "x64"] include: - architecture: "x86" @@ -20,22 +19,26 @@ jobs: platform-msbuild: "x64" exclude: # PyPy does not support 64-bit on Windows - - python-version: "pypy3" + - python-version: "pypy-3.6" + architecture: "x64" + - python-version: "pypy-3.7" architecture: "x64" timeout-minutes: 30 name: Python ${{ matrix.python-version }} ${{ matrix.architecture }} steps: - - uses: actions/checkout@v1 + - name: Checkout Pillow + uses: actions/checkout@v2 - - uses: actions/checkout@v1 + - name: Checkout cached dependencies + uses: actions/checkout@v2 with: repository: python-pillow/pillow-depends - ref: master + path: winbuild\depends - - name: Cache - uses: actions/cache@v1 + - name: Cache pip + uses: actions/cache@v2 with: path: ~\AppData\Local\pip\Cache key: @@ -46,296 +49,125 @@ jobs: # sets env: pythonLocation - name: Set up Python - uses: actions/setup-python@v1 + uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} architecture: ${{ matrix.architecture }} - - name: Build system information + - name: Print build system information run: python .github/workflows/system-info.py - - name: pip install wheel pytest pytest-cov + - name: python -m pip install wheel pytest pytest-cov + run: python -m pip install wheel pytest pytest-cov + + # TODO Remove when 3.8 / 3.9 includes setuptools 49.3.2+: + - name: Upgrade setuptools + if: "contains(matrix.python-version, '3.8') || contains(matrix.python-version, '3.9')" + run: python -m pip install -U "setuptools>=49.3.2" + + - name: Install dependencies + id: install run: | - "%pythonLocation%\python.exe" -m pip install wheel pytest pytest-cov - shell: cmd + 7z x winbuild\depends\nasm-2.14.02-win64.zip "-o$env:RUNNER_WORKSPACE\" + echo "$env:RUNNER_WORKSPACE\nasm-2.14.02" >> $env:GITHUB_PATH - - name: Fetch dependencies - run: | - 7z x ..\pillow-depends\nasm-2.14.02-win64.zip "-o$env:RUNNER_WORKSPACE\" - Write-Host "`#`#[add-path]$env:RUNNER_WORKSPACE\nasm-2.14.02" - Write-Host "::add-path::$env:RUNNER_WORKSPACE\nasm-2.14.02" + winbuild\depends\gs9533w32.exe /S + echo "C:\Program Files (x86)\gs\gs9.53.3\bin" >> $env:GITHUB_PATH - ..\pillow-depends\gs950w32.exe /S - Write-Host "`#`#[add-path]C:\Program Files (x86)\gs\gs9.50\bin" - Write-Host "::add-path::C:\Program Files (x86)\gs\gs9.50\bin" + xcopy /S /Y winbuild\depends\test_images\* Tests\images\ - $env:PYTHON=$env:pythonLocation - xcopy ..\pillow-depends\*.zip $env:GITHUB_WORKSPACE\winbuild\ - xcopy ..\pillow-depends\*.tar.gz $env:GITHUB_WORKSPACE\winbuild\ - xcopy /s ..\pillow-depends\test_images\* $env:GITHUB_WORKSPACE\tests\images\ - cd $env:GITHUB_WORKSPACE/winbuild/ - python.exe $env:GITHUB_WORKSPACE\winbuild\build_dep.py - env: - EXECUTABLE: bin\python.exe + # 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 "::set-output name=vs::$a" } shell: pwsh - - name: Build dependencies / libjpeg - if: false - run: | - REM FIXME uses /MT not /MD, see makefile.vc and win32.mak for more info + - name: Cache build + id: build-cache + uses: actions/cache@v2 + with: + path: winbuild\build + key: + ${{ hashFiles('winbuild\build_prepare.py') }}-${{ hashFiles('.github\workflows\test-windows.yml') }}-${{ env.pythonLocation }}-${{ steps.install.outputs.vs }} - set INCLUDE=C:\Program Files (x86)\Microsoft SDKs\Windows\V7.1A\Include - set INCLIB=%GITHUB_WORKSPACE%\winbuild\depends\msvcr10-x32 - set BUILD=%GITHUB_WORKSPACE%\winbuild\build - cd /D %BUILD%\jpeg-9d - call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" ${{ matrix.platform-vcvars }} - echo on - nmake -nologo -f makefile.vc setup-vc6 - nmake -nologo -f makefile.vc clean - nmake -nologo -f makefile.vc nodebug=1 libjpeg.lib cjpeg.exe djpeg.exe - copy /Y /B j*.h %INCLIB% - copy /Y /B *.lib %INCLIB% - copy /Y /B *.exe %INCLIB% - shell: cmd + - name: Prepare build + if: steps.build-cache.outputs.cache-hit != 'true' + run: | + & python.exe winbuild\build_prepare.py -v --python=$env:pythonLocation --srcdir + shell: pwsh - name: Build dependencies / libjpeg-turbo - run: | - set INCLUDE=C:\Program Files (x86)\Microsoft SDKs\Windows\V7.1A\Include - set INCLIB=%GITHUB_WORKSPACE%\winbuild\depends\msvcr10-x32 - set BUILD=%GITHUB_WORKSPACE%\winbuild\build - cd /D %BUILD%\libjpeg-turbo-2.0.3 - call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" ${{ matrix.platform-vcvars }} - echo on - set CMAKE=cmake.exe -DCMAKE_VERBOSE_MAKEFILE=ON -DCMAKE_RULE_MESSAGES:BOOL=OFF - set CMAKE=%CMAKE% -DENABLE_SHARED:BOOL=OFF -DWITH_JPEG8:BOOL=TRUE -DWITH_CRT_DLL:BOOL=TRUE -DCMAKE_BUILD_TYPE=Release - %CMAKE% -G "NMake Makefiles" . - nmake -nologo -f Makefile clean - nmake -nologo -f Makefile jpeg-static cjpeg-static djpeg-static - copy /Y /B j*.h %INCLIB% - copy /Y /B jpeg-static.lib %INCLIB%\libjpeg.lib - copy /Y /B cjpeg-static.exe %INCLIB%\cjpeg.exe - copy /Y /B djpeg-static.exe %INCLIB%\djpeg.exe - shell: cmd + if: steps.build-cache.outputs.cache-hit != 'true' + run: "& winbuild\\build\\build_dep_libjpeg.cmd" - name: Build dependencies / zlib - run: | - set INCLUDE=C:\Program Files (x86)\Microsoft SDKs\Windows\V7.1A\Include - set INCLIB=%GITHUB_WORKSPACE%\winbuild\depends\msvcr10-x32 - set BUILD=%GITHUB_WORKSPACE%\winbuild\build - cd /D %BUILD%\zlib-1.2.11 - call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" ${{ matrix.platform-vcvars }} - echo on - nmake -nologo -f win32\Makefile.msc clean - nmake -nologo -f win32\Makefile.msc zlib.lib - copy /Y /B z*.h %INCLIB% - copy /Y /B *.lib %INCLIB% - copy /Y /B zlib.lib %INCLIB%\z.lib - shell: cmd + if: steps.build-cache.outputs.cache-hit != 'true' + run: "& winbuild\\build\\build_dep_zlib.cmd" - - name: Build dependencies / LibTIFF - run: | - set INCLUDE=C:\Program Files (x86)\Microsoft SDKs\Windows\V7.1A\Include - set INCLIB=%GITHUB_WORKSPACE%\winbuild\depends\msvcr10-x32 - set BUILD=%GITHUB_WORKSPACE%\winbuild\build - cd /D %BUILD%\tiff-4.1.0 - call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" ${{ matrix.platform-vcvars }} - echo on - copy %GITHUB_WORKSPACE%\winbuild\tiff.opt nmake.opt - nmake -nologo -f makefile.vc clean - nmake -nologo -f makefile.vc lib - copy /Y /B libtiff\tiff*.h %INCLIB% - copy /Y /B libtiff\*.dll %INCLIB% - copy /Y /B libtiff\*.lib %INCLIB% - shell: cmd + - name: Build dependencies / LibTiff + if: steps.build-cache.outputs.cache-hit != 'true' + run: "& winbuild\\build\\build_dep_libtiff.cmd" - name: Build dependencies / WebP - run: | - set INCLUDE=C:\Program Files (x86)\Microsoft SDKs\Windows\V7.1A\Include - set INCLIB=%GITHUB_WORKSPACE%\winbuild\depends\msvcr10-x32 - set BUILD=%GITHUB_WORKSPACE%\winbuild\build - cd /D %BUILD%\libwebp-1.1.0 - call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" ${{ matrix.platform-vcvars }} - echo on - rmdir /S /Q output\release-static - nmake -nologo -f Makefile.vc CFG=release-static OBJDIR=output ARCH=${{ matrix.architecture }} all - mkdir %INCLIB%\webp - copy /Y /B src\webp\*.h %INCLIB%\webp - copy /Y /B output\release-static\${{ matrix.architecture }}\lib\* %INCLIB% - shell: cmd + if: steps.build-cache.outputs.cache-hit != 'true' + run: "& winbuild\\build\\build_dep_libwebp.cmd" + + # for FreeType CBDT font support + - name: Build dependencies / libpng + if: steps.build-cache.outputs.cache-hit != 'true' + run: "& winbuild\\build\\build_dep_libpng.cmd" - name: Build dependencies / FreeType - run: | - REM Toolkit v100 not available; missing VCTargetsPath; Clean fails - - set INCLUDE=C:\Program Files (x86)\Microsoft SDKs\Windows\V7.1A\Include - set INCLIB=%GITHUB_WORKSPACE%\winbuild\depends\msvcr10-x32 - set BUILD=%GITHUB_WORKSPACE%\winbuild\build - cd /D %BUILD%\freetype-2.10.1 - call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" ${{ matrix.platform-vcvars }} - echo on - rmdir /S /Q objs - set DefaultPlatformToolset=v142 - set VCTargetsPath=C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Microsoft\VC\v160\ - set MSBUILD="C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Current\Bin\MSBuild.exe" - powershell -Command "(gc builds\windows\vc2010\freetype.vcxproj) -replace 'MultiThreaded<', 'MultiThreadedDLL<' | Out-File -encoding ASCII builds\windows\vc2010\freetype.vcxproj" - %MSBUILD% builds\windows\vc2010\freetype.sln /t:Build /p:Configuration="Release Static" /p:Platform=${{ matrix.platform-msbuild }} /m - xcopy /Y /E /Q include %INCLIB% - copy /Y /B "objs\${{ matrix.platform-msbuild }}\Release Static\freetype.lib" %INCLIB% - shell: cmd + if: steps.build-cache.outputs.cache-hit != 'true' + run: "& winbuild\\build\\build_dep_freetype.cmd" - name: Build dependencies / LCMS2 - run: | - set INCLUDE=C:\Program Files (x86)\Microsoft SDKs\Windows\V7.1A\Include - set INCLIB=%GITHUB_WORKSPACE%\winbuild\depends\msvcr10-x32 - set BUILD=%GITHUB_WORKSPACE%\winbuild\build - cd /D %BUILD%\lcms2-2.8 - call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" ${{ matrix.platform-vcvars }} - echo on - rmdir /S /Q Lib - rmdir /S /Q Projects\VC2015\Release - set VCTargetsPath=C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Microsoft\VC\v160\ - set MSBUILD="C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Current\Bin\MSBuild.exe" - powershell %GITHUB_WORKSPACE%\winbuild\lcms2_patch.ps1 - %MSBUILD% Projects\VC2015\lcms2.sln /t:Clean;lcms2_static /p:Configuration="Release" /p:Platform=${{ matrix.platform-msbuild }} /m - xcopy /Y /E /Q include %INCLIB% - copy /Y /B Lib\MS\*.lib %INCLIB% - shell: cmd + if: steps.build-cache.outputs.cache-hit != 'true' + run: "& winbuild\\build\\build_dep_lcms2.cmd" - name: Build dependencies / OpenJPEG - run: | - set INCLUDE=C:\Program Files (x86)\Microsoft SDKs\Windows\V7.1A\Include - set INCLIB=%GITHUB_WORKSPACE%\winbuild\depends\msvcr10-x32 - set BUILD=%GITHUB_WORKSPACE%\winbuild\build - cd /D %BUILD%\openjpeg-2.3.1msvcr10-x32 - call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" ${{ matrix.platform-vcvars }} - echo on - set CMAKE=cmake.exe -DCMAKE_VERBOSE_MAKEFILE=ON -DCMAKE_RULE_MESSAGES:BOOL=OFF - set CMAKE=%CMAKE% -DBUILD_THIRDPARTY:BOOL=OFF -DBUILD_SHARED_LIBS:BOOL=OFF - set CMAKE=%CMAKE% -DCMAKE_BUILD_TYPE=Release - %CMAKE% -G "NMake Makefiles" . - nmake -nologo -f Makefile clean - nmake -nologo -f Makefile - mkdir %INCLIB%\openjpeg-2.3.1 - copy /Y /B src\lib\openjp2\*.h %INCLIB%\openjpeg-2.3.1 - copy /Y /B bin\*.lib %INCLIB% - shell: cmd + if: steps.build-cache.outputs.cache-hit != 'true' + run: "& winbuild\\build\\build_dep_openjpeg.cmd" - # GPL licensed; skip if building wheels + # GPL licensed - name: Build dependencies / libimagequant - if: "github.event_name != 'push' || contains(matrix.python-version, 'pypy')" - run: | - set INCLUDE=C:\Program Files (x86)\Microsoft SDKs\Windows\V7.1A\Include - set INCLIB=%GITHUB_WORKSPACE%\winbuild\depends\msvcr10-x32 - set BUILD=%GITHUB_WORKSPACE%\winbuild\build - rem e5d454b: Merge tag '2.12.6' into msvc - cd /D %BUILD%\libimagequant-e5d454bc7f5eb63ee50c84a83a7fa5ac94f68ec4 - call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" ${{ matrix.platform-vcvars }} - echo on - echo (gc CMakeLists.txt) -replace 'add_library', "add_compile_options(-openmp-)`r`nadd_library" ^| Out-File -encoding ASCII CMakeLists.txt > patch.ps1 - echo (gc CMakeLists.txt) -replace ' SHARED', ' STATIC' ^| Out-File -encoding ASCII CMakeLists.txt >> patch.ps1 - powershell .\patch.ps1 - set CMAKE=cmake.exe -DCMAKE_VERBOSE_MAKEFILE=ON -DCMAKE_RULE_MESSAGES:BOOL=OFF - set CMAKE=%CMAKE% -DCMAKE_BUILD_TYPE=Release - %CMAKE% -G "NMake Makefiles" . - nmake -nologo -f Makefile clean - nmake -nologo -f Makefile - copy /Y /B *.h %INCLIB% - copy /Y /B *.lib %INCLIB% - shell: cmd + if: steps.build-cache.outputs.cache-hit != 'true' + run: "& winbuild\\build\\build_dep_libimagequant.cmd" - # for Raqm + # Raqm dependencies - name: Build dependencies / HarfBuzz - run: | - set INCLUDE=C:\Program Files (x86)\Microsoft SDKs\Windows\V7.1A\Include - set INCLIB=%GITHUB_WORKSPACE%\winbuild\depends\msvcr10-x32 - set BUILD=%GITHUB_WORKSPACE%\winbuild\build - set INCLUDE=%INCLUDE%;%INCLIB% - set LIB=%LIB%;%INCLIB% - cd /D %BUILD%\harfbuzz-2.6.1 - call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" ${{ matrix.platform-vcvars }} - echo on - set CMAKE=cmake.exe -DCMAKE_VERBOSE_MAKEFILE=ON -DCMAKE_RULE_MESSAGES:BOOL=OFF - set CMAKE=%CMAKE% -DHB_HAVE_FREETYPE:BOOL=ON -DCMAKE_BUILD_TYPE=Release - %CMAKE% -G "NMake Makefiles" . - nmake -nologo -f Makefile clean - nmake -nologo -f Makefile harfbuzz - copy /Y /B src\*.h %INCLIB% - copy /Y /B *.lib %INCLIB% - shell: cmd + if: steps.build-cache.outputs.cache-hit != 'true' + run: "& winbuild\\build\\build_dep_harfbuzz.cmd" - # for Raqm - name: Build dependencies / FriBidi - run: | - set INCLUDE=C:\Program Files (x86)\Microsoft SDKs\Windows\V7.1A\Include - set INCLIB=%GITHUB_WORKSPACE%\winbuild\depends\msvcr10-x32 - set BUILD=%GITHUB_WORKSPACE%\winbuild\build - cd /D %BUILD%\fribidi-1.0.7 - call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" ${{ matrix.platform-vcvars }} - echo on - copy /Y /B %GITHUB_WORKSPACE%\winbuild\fribidi.cmake CMakeLists.txt - set CMAKE=cmake.exe -DCMAKE_VERBOSE_MAKEFILE=ON -DCMAKE_RULE_MESSAGES:BOOL=OFF - set CMAKE=%CMAKE% -DCMAKE_BUILD_TYPE=Release - %CMAKE% -G "NMake Makefiles" . - nmake -nologo -f Makefile clean - nmake -nologo -f Makefile fribidi - copy /Y /B lib\*.h %INCLIB% - copy /Y /B *.lib %INCLIB% - shell: cmd + if: steps.build-cache.outputs.cache-hit != 'true' + run: "& winbuild\\build\\build_dep_fribidi.cmd" - name: Build dependencies / Raqm - run: | - set INCLUDE=C:\Program Files (x86)\Microsoft SDKs\Windows\V7.1A\Include - set INCLIB=%GITHUB_WORKSPACE%\winbuild\depends\msvcr10-x32 - set BUILD=%GITHUB_WORKSPACE%\winbuild\build - set INCLUDE=%INCLUDE%;%INCLIB% - set LIB=%LIB%;%INCLIB% - cd /D %BUILD%\libraqm-0.7.0 - call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" ${{ matrix.platform-vcvars }} - echo on - copy /Y /B %GITHUB_WORKSPACE%\winbuild\raqm.cmake CMakeLists.txt - set CMAKE=cmake.exe -DCMAKE_VERBOSE_MAKEFILE=ON -DCMAKE_RULE_MESSAGES:BOOL=OFF - set CMAKE=%CMAKE% -DCMAKE_BUILD_TYPE=Release - %CMAKE% -G "NMake Makefiles" . - nmake -nologo -f Makefile clean - nmake -nologo -f Makefile libraqm - copy /Y /B src\*.h %INCLIB% - copy /Y /B libraqm.dll %INCLIB% + if: steps.build-cache.outputs.cache-hit != 'true' + run: "& winbuild\\build\\build_dep_libraqm.cmd" + + # trim ~150MB x 9 + - 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: | - set PYTHON=%pythonLocation% - set INCLUDE=C:\Program Files (x86)\Microsoft SDKs\Windows\V7.1A\Include - set MPLSRC=%GITHUB_WORKSPACE% - set INCLIB=%GITHUB_WORKSPACE%\winbuild\depends\msvcr10-x32 - cd /D %GITHUB_WORKSPACE% - set LIB=%INCLIB%;%PYTHON%\tcl - set INCLUDE=%INCLIB%;%GITHUB_WORKSPACE%\depends\tcl86\include;%INCLUDE% - call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" ${{ matrix.platform-vcvars }} - set MSSdk=1 - set DISTUTILS_USE_SDK=1 - set py_vcruntime_redist=true - %PYTHON%\python.exe setup.py build_ext install - rem Add libraqm.dll (copied to INCLIB) to PATH. - path %INCLIB%;%PATH% - %PYTHON%\python.exe selftest.py --installed - shell: cmd + $FLAGS="" + if ('${{ github.event_name }}' -eq 'push') { $FLAGS="--disable-imagequant" } + & winbuild\build\build_pillow.cmd $FLAGS install + & $env:pythonLocation\python.exe selftest.py --installed + shell: pwsh # failing with PyPy3 - name: Enable heap verification if: "!contains(matrix.python-version, 'pypy')" - run: | - c:\"Program Files (x86)"\"Windows Kits"\10\Debuggers\x86\gflags.exe /p /enable %PYTHON%\python.exe - shell: cmd + run: "& 'C:\\Program Files (x86)\\Windows Kits\\10\\Debuggers\\x86\\gflags.exe' /p /enable $env:pythonLocation\\python.exe" - name: Test Pillow run: | - set PYTHON=%pythonLocation% - set INCLIB=%GITHUB_WORKSPACE%\winbuild\depends\msvcr10-x32 - rem Add libraqm.dll (copied to INCLIB) to PATH. - path %INCLIB%;%PATH% - cd /D %GITHUB_WORKSPACE% - %PYTHON%\python.exe -m pytest -vx -W always --cov PIL --cov Tests --cov-report term --cov-report xml Tests + 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 @@ -345,50 +177,115 @@ jobs: shell: pwsh - name: Upload errors - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v2 if: failure() with: name: errors path: Tests/errors - name: After success - if: success() run: | .ci/after_success.sh shell: pwsh - - name: Prepare coverage token - if: success() && github.repository == 'python-pillow/Pillow' - run: cp .github/codecov-upstream.yml codecov.yml - - name: Upload coverage - if: success() uses: codecov/codecov-action@v1 with: - token: ${{ secrets.CODECOV_TOKEN }} - file: ./coverage.xml - flags: GHA_Windows - name: ${{ runner.os }} Python ${{ matrix.python-version }} + file: ./coverage.xml + flags: GHA_Windows + name: ${{ runner.os }} Python ${{ matrix.python-version }} ${{ matrix.architecture }} - name: Build wheel id: wheel - if: "github.event_name == 'push' && !contains(matrix.python-version, 'pypy')" + if: "github.event_name == 'push'" run: | - for /f "tokens=3 delims=/" %%a in ("${{ github.ref }}") do echo ##[set-output name=dist;]dist-%%a for /f "tokens=3 delims=/" %%a in ("${{ github.ref }}") do echo ::set-output name=dist::dist-%%a - set PYTHON=%pythonLocation% - set INCLUDE=C:\Program Files (x86)\Microsoft SDKs\Windows\V7.1A\Include - set MPLSRC=%GITHUB_WORKSPACE% - set INCLIB=%GITHUB_WORKSPACE%\winbuild\depends\msvcr10-x32 - cd /D %GITHUB_WORKSPACE% - set LIB=%INCLIB%;%PYTHON%\tcl - set INCLUDE=%INCLIB%;%GITHUB_WORKSPACE%\depends\tcl86\include;%INCLUDE% - call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" ${{ matrix.platform-vcvars }} - %PYTHON%\python.exe setup.py bdist_wheel + winbuild\\build\\build_pillow.cmd --disable-imagequant bdist_wheel shell: cmd - - uses: actions/upload-artifact@v1 - if: "github.event_name == 'push' && !contains(matrix.python-version, 'pypy')" + - uses: actions/upload-artifact@v2 + if: "github.event_name == 'push'" with: name: ${{ steps.wheel.outputs.dist }} - path: dist + path: dist\*.whl + + msys: + runs-on: windows-2019 + + strategy: + fail-fast: false + matrix: + mingw: ["MINGW32", "MINGW64"] + include: + - mingw: "MINGW32" + name: "MSYS2 MinGW 32-bit" + package: "mingw-w64-i686" + - mingw: "MINGW64" + name: "MSYS2 MinGW 64-bit" + package: "mingw-w64-x86_64" + + defaults: + run: + shell: bash.exe --login -eo pipefail "{0}" + env: + MSYSTEM: ${{ matrix.mingw }} + CHERE_INVOKING: 1 + + timeout-minutes: 30 + name: ${{ matrix.name }} + + steps: + - uses: actions/checkout@v2 + + - name: Set up shell + run: echo "C:\msys64\usr\bin\" >> $env:GITHUB_PATH + shell: pwsh + + - 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 }}-python3-pyqt5 \ + ${{ matrix.package }}-python3-pytest \ + ${{ matrix.package }}-python3-pytest-cov \ + ${{ matrix.package }}-python3-setuptools \ + ${{ matrix.package }}-freetype \ + ${{ matrix.package }}-ghostscript \ + ${{ matrix.package }}-lcms2 \ + ${{ matrix.package }}-libimagequant \ + ${{ matrix.package }}-libjpeg-turbo \ + ${{ matrix.package }}-libraqm \ + ${{ matrix.package }}-libtiff \ + ${{ matrix.package }}-libwebp \ + ${{ matrix.package }}-openjpeg2 \ + subversion + + python3 -m pip install pyroma + + pushd depends && ./install_extra_test_images.sh && popd + + - name: Build Pillow + run: CFLAGS="-coverage" python3 setup.py build_ext install + + - name: Test Pillow + run: | + python3 selftest.py --installed + python3 -m pytest -vx --cov PIL --cov Tests --cov-report term --cov-report xml Tests + + - name: Upload coverage + run: | + python3 -m pip install codecov + bash <(curl -s https://codecov.io/bash) -F GHA_Windows + env: + CODECOV_NAME: ${{ matrix.name }} + + success: + needs: [build, msys] + 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 index ba0e18cf4..d127916ea 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -13,17 +13,19 @@ jobs: "macOS-latest", ] python-version: [ - "pypy3", + "pypy-3.7", + "pypy-3.6", + "3.10-dev", + "3.9", "3.8", "3.7", "3.6", - "3.5", ] include: - - python-version: "3.5" - env: PYTHONOPTIMIZE=2 - python-version: "3.6" - env: PYTHONOPTIMIZE=1 + PYTHONOPTIMIZE: 1 + - python-version: "3.7" + PYTHONOPTIMIZE: 2 # Include new variables for Codecov - os: ubuntu-latest codecov-flag: GHA_Ubuntu @@ -34,33 +36,27 @@ jobs: name: ${{ matrix.os }} Python ${{ matrix.python-version }} steps: - - uses: actions/checkout@v1 - - - name: Ubuntu cache - uses: actions/cache@v1 - if: startsWith(matrix.os, 'ubuntu') - with: - path: ~/.cache/pip - key: - ${{ matrix.os }}-${{ matrix.python-version }}-${{ hashFiles('**/.ci/*.sh') }} - restore-keys: | - ${{ matrix.os }}-${{ matrix.python-version }}- - - - name: macOS cache - uses: actions/cache@v1 - if: startsWith(matrix.os, 'macOS') - with: - path: ~/Library/Caches/pip - key: - ${{ matrix.os }}-${{ matrix.python-version }}-${{ hashFiles('**/.ci/*.sh') }} - restore-keys: | - ${{ matrix.os }}-${{ matrix.python-version }}- + - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v1 + uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} + - name: Get pip cache dir + id: pip-cache + run: | + echo "::set-output name=dir::$(python3 -m pip cache dir)" + + - name: pip cache + uses: actions/cache@v2 + with: + path: ${{ steps.pip-cache.outputs.dir }} + key: + ${{ matrix.os }}-${{ matrix.python-version }}-${{ hashFiles('**/.ci/*.sh') }} + restore-keys: | + ${{ matrix.os }}-${{ matrix.python-version }}- + - name: Build system information run: python .github/workflows/system-info.py @@ -68,11 +64,15 @@ jobs: 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: | @@ -80,7 +80,13 @@ jobs: - name: Test run: | - .ci/test.sh + if [ "${{ matrix.os }}" = "ubuntu-latest" ]; then + xvfb-run -s '-screen 0 1024x768x24' .ci/test.sh + else + .ci/test.sh + fi + env: + PYTHONOPTIMIZE: ${{ matrix.PYTHONOPTIMIZE }} - name: Prepare to upload errors if: failure() @@ -89,25 +95,31 @@ jobs: shell: pwsh - name: Upload errors - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v2 if: failure() with: name: errors path: Tests/errors + - name: Docs + if: startsWith(matrix.os, 'ubuntu') && matrix.python-version == 3.9 + run: | + python3 -m pip install sphinx-issues sphinx-removed-in sphinx-rtd-theme + make doccheck + - name: After success - if: success() run: | .ci/after_success.sh - - name: Prepare coverage token - if: success() && github.repository == 'python-pillow/Pillow' - run: cp .github/codecov-upstream.yml codecov.yml - - name: Upload coverage - if: success() - uses: codecov/codecov-action@v1 - with: - token: ${{ secrets.CODECOV_TOKEN }} - flags: ${{ matrix.codecov-flag }} - name: ${{ matrix.os }} Python ${{ matrix.python-version }} + run: bash <(curl -s https://codecov.io/bash) -F ${{ matrix.codecov-flag }} + env: + CODECOV_NAME: ${{ matrix.os }} Python ${{ matrix.python-version }} + + success: + needs: build + runs-on: ubuntu-latest + name: Test Successful + steps: + - name: Success + run: echo Test Successful diff --git a/.gitignore b/.gitignore index ef7520c0d..15add232b 100644 --- a/.gitignore +++ b/.gitignore @@ -81,6 +81,10 @@ docs/_build/ # Extra test images installed from pillow-depends/test_images Tests/images/README.md +Tests/images/crash_1.tif +Tests/images/crash_2.tif +Tests/images/string_dimension.tiff +Tests/images/jpeg2000 Tests/images/msp Tests/images/picins Tests/images/sunraster diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 000000000..8d38375f0 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,43 @@ +repos: + - repo: https://github.com/psf/black + rev: e66be67b9b6811913470f70c28b4d50f94d05b22 # frozen: 20.8b1 + hooks: + - id: black + args: ["--target-version", "py36"] + # Only .py files, until https://github.com/psf/black/issues/402 resolved + files: \.py$ + types: [] + + - repo: https://github.com/PyCQA/isort + rev: 377d260ffa6f746693f97b46d95025afc4bd8275 # frozen: 5.4.2 + hooks: + - id: isort + + - repo: https://github.com/asottile/yesqa + rev: 7a009f3ee493c796827ee334f9058b110a0e0db8 # frozen: v1.2.1 + hooks: + - id: yesqa + + - repo: https://github.com/Lucas-C/pre-commit-hooks + rev: f30f4974a08a6b2f6a1eeaf30a4d501cf909163a # frozen: v1.1.9 + hooks: + - id: remove-tabs + exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.opt$) + + - repo: https://gitlab.com/pycqa/flake8 + rev: 05f6544aef321e2fee03a1277ce2eef8880fb927 # frozen: 3.8.3 + hooks: + - id: flake8 + additional_dependencies: [flake8-2020, flake8-implicit-str-concat] + + - repo: https://github.com/pre-commit/pygrep-hooks + rev: eae6397e4c259ed3d057511f6dd5330b92867e62 # frozen: v1.6.0 + hooks: + - id: python-check-blanket-noqa + - id: rst-backticks + + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: e1668fe86af3810fbca72b8653fe478e66a0afdc # frozen: v3.2.0 + hooks: + - id: check-merge-conflict + - id: check-yaml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 038bf0b76..000000000 --- a/.travis.yml +++ /dev/null @@ -1,64 +0,0 @@ -dist: xenial -language: python -cache: pip - -notifications: - irc: "chat.freenode.net#pil" - -# Run fast lint first to get fast feedback. -# Run slower CPUs next, to give them a headstart and reduce waiting time. -# Then run the remainder. - -matrix: - fast_finish: true - include: - - python: "3.6" - name: "Lint" - env: LINT="true" - - - python: "3.6" - arch: arm64 - - python: "3.7" - arch: ppc64le - - python: "3.5" - arch: s390x - - - python: "pypy3" - name: "PyPy3 Xenial" - - python: "3.8" - name: "3.8 Xenial" - services: xvfb - - python: '3.7' - name: "3.7 Xenial" - services: xvfb - - python: '3.6' - name: "3.6 Xenial PYTHONOPTIMIZE=1" - env: PYTHONOPTIMIZE=1 - services: xvfb - - python: '3.5' - name: "3.5 Xenial PYTHONOPTIMIZE=2" - env: PYTHONOPTIMIZE=2 - services: xvfb - -install: - - | - if [ "$LINT" == "true" ]; then - pip install tox - else - .ci/install.sh; - fi - -script: -- | - if [ "$LINT" == "true" ]; then - tox -e lint - else - .ci/build.sh - .ci/test.sh - fi - -after_success: -- | - if [ "$LINT" == "" ]; then - .ci/after_success.sh - fi diff --git a/CHANGES.rst b/CHANGES.rst index 8b48ac1a8..6296c09c7 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -2,9 +2,363 @@ Changelog (Pillow) ================== -7.1.0 (unreleased) +8.1.0 (2020-01-02) ------------------ +- Fix TIFF OOB Write error. CVE-2020-35654 #5175 + [wiredfool] + +- Fix for Read Overflow in PCX Decoding. CVE-2020-35653 #5174 + [wiredfool, radarhere] + +- Fix for SGI Decode buffer overrun. CVE-2020-35655 #5173 + [wiredfool, radarhere] + +- Fix OOB Read when saving GIF of xsize=1 #5149 + [wiredfool] + +- Makefile updates #5159 + [wiredfool, radarhere] + +- Add support for PySide6 #5161 + [hugovk] + +- Use disposal settings from previous frame in APNG #5126 + [radarhere] + +- Added exception explaining that _repr_png_ saves to PNG #5139 + [radarhere] + +- Use previous disposal method in GIF load_end #5125 + [radarhere] + +- Allow putpalette to accept 1024 integers to include alpha values #5089 + [radarhere] + +- Fix OOB Read when writing TIFF with custom Metadata #5148 + [wiredfool] + +- Added append_images support for ICO #4568 + [ziplantil, radarhere] + +- Block TIFFTAG_SUBIFD #5120 + [radarhere] + +- Fixed dereferencing potential null pointers #5108, #5111 + [cgohlke, radarhere] + +- Deprecate FreeType 2.7 #5098 + [hugovk, radarhere] + +- Moved warning to end of execution #4965 + [radarhere] + +- Removed unused fromstring and tostring C methods #5026 + [radarhere] + +- init() if one of the formats is unrecognised #5037 + [radarhere] + +- Moved string_dimension CVE image to pillow-depends #4993 + [radarhere] + +- Support raw rgba8888 for DDS #4760 + [qiankanglai] + +8.0.1 (2020-10-22) +------------------ + +- Update FreeType used in binary wheels to 2.10.4 to fix CVE-2020-15999. + [radarhere] + +- Moved string_dimension image to pillow-depends #4993 + [radarhere] + +8.0.0 (2020-10-15) +------------------ + +- Drop support for EOL Python 3.5 #4746, #4794 + [hugovk, radarhere, nulano] + +- Drop support for PyPy3 < 7.2.0 #4964 + [nulano] + +- Remove ImageCms.CmsProfile attributes deprecated since 3.2.0 #4768 + [hugovk, radarhere] + +- Remove long-deprecated Image.py functions #4798 + [hugovk, nulano, radarhere] + +- Add support for 16-bit precision JPEG quantization values #4918 + [gofr] + +- Added reading of IFD tag type #4979 + [radarhere] + +- Initialize offset memory for PyImagingPhotoPut #4806 + [nqbit] + +- Fix TiffDecode comparison warnings #4756 + [nulano] + +- Docs: Add dark mode #4968 + [hugovk, nulano] + +- Added macOS SDK install path to library and include directories #4974 + [radarhere, fxcoudert] + +- Imaging.h: prevent confusion with system #4923 + [ax3l, ,radarhere] + +- Avoid using pkg_resources in PIL.features.pilinfo #4975 + [nulano] + +- Add getlength and getbbox functions for TrueType fonts #4959 + [nulano, radarhere, hugovk] + +- Allow tuples with one item to give single color value in getink #4927 + [radarhere, nulano] + +- Add support for CBDT and COLR fonts #4955 + [nulano, hugovk] + +- Removed OSError in favour of DecompressionBombError for BMP #4966 + [radarhere] + +- Implemented another ellipse drawing algorithm #4523 + [xtsm, radarhere] + +- Removed unused JpegImagePlugin._fixup_dict function #4957 + [radarhere] + +- Added reading and writing of private PNG chunks #4292 + [radarhere] + +- Implement anchor for TrueType fonts #4930 + [nulano, hugovk] + +- Fixed bug in Exif __delitem__ #4942 + [radarhere] + +- Fix crash in ImageTk.PhotoImage on MinGW 64-bit #4946 + [nulano] + +- Moved CVE images to pillow-depends #4929 + [radarhere] + +- Refactor font_getsize and font_render #4910 + [nulano] + +- Fixed loading profile with non-ASCII path on Windows #4914 + [radarhere] + +- Fixed effect_spread bug for zero distance #4908 + [radarhere, hugovk] + +- Added formats parameter to Image.open #4837 + [nulano, radarhere] + +- Added regular_polygon draw method #4846 + [comhar] + +- Raise proper TypeError in putpixel #4882 + [nulano, hugovk] + +- Added writing of subIFDs #4862 + [radarhere] + +- Fix IFDRational __eq__ bug #4888 + [luphord, radarhere] + +- Fixed duplicate variable name #4885 + [liZe, radarhere] + +- Added homebrew zlib include directory #4842 + [radarhere] + +- Corrected inverted PDF CMYK colors #4866 + [radarhere] + +- Do not try to close file pointer if file pointer is empty #4823 + [radarhere] + +- ImageOps.autocontrast: add mask parameter #4843 + [navneeth, hugovk] + +- Read EXIF data tEXt chunk into info as bytes instead of string #4828 + [radarhere] + +- Replaced distutils with setuptools #4797, #4809, #4814, #4817, #4829, #4890 + [hugovk, radarhere] + +- Add MIME type to PsdImagePlugin #4788 + [samamorgan] + +- Allow ImageOps.autocontrast to specify low and high cutoffs separately #4749 + [millionhz, radarhere] + +7.2.0 (2020-07-01) +------------------ + +- Do not convert I;16 images when showing PNGs #4744 + [radarhere] + +- Fixed ICNS file pointer saving #4741 + [radarhere] + +- Fixed loading non-RGBA mode APNGs with dispose background #4742 + [radarhere] + +- Deprecated _showxv #4714 + [radarhere] + +- Deprecate Image.show(command="...") #4646 + [nulano, hugovk, radarhere] + +- Updated JPEG magic number #4707 + [Cykooz, radarhere] + +- Change STRIPBYTECOUNTS to LONG if necessary when saving #4626 + [radarhere, hugovk] + +- Write JFIF header when saving JPEG #4639 + [radarhere] + +- Replaced tiff_jpeg with jpeg compression when saving TIFF images #4627 + [radarhere] + +- Writing TIFF tags: improved BYTE, added UNDEFINED #4605 + [radarhere] + +- Consider transparency when pasting text on an RGBA image #4566 + [radarhere] + +- Added method argument to single frame WebP saving #4547 + [radarhere] + +- Use ImageFileDirectory_v2 in Image.Exif #4637 + [radarhere] + +- Corrected reading EXIF metadata without prefix #4677 + [radarhere] + +- Fixed drawing a jointed line with a sequence of numeric values #4580 + [radarhere] + +- Added support for 1-D NumPy arrays #4608 + [radarhere] + +- Parse orientation from XMP tags #4560 + [radarhere] + +- Speed up text layout by not rendering glyphs #4652 + [nulano] + +- Fixed ZeroDivisionError in Image.thumbnail #4625 + [radarhere] + +- Replaced TiffImagePlugin DEBUG with logging #4550 + [radarhere] + +- Fix repeatedly loading .gbr #4620 + [ElinksFr, radarhere] + +- JPEG: Truncate icclist instead of setting to None #4613 + [homm] + +- Fixes default offset for Exif #4594 + [rodrigob, radarhere] + +- Fixed bug when unpickling TIFF images #4565 + [radarhere] + +- Fix pickling WebP #4561 + [hugovk, radarhere] + +- Replace IOError and WindowsError aliases with OSError #4536 + [hugovk, radarhere] + +7.1.2 (2020-04-25) +------------------ + +- Raise an EOFError when seeking too far in PNG #4528 + [radarhere] + +7.1.1 (2020-04-02) +------------------ + +- Fix regression seeking and telling PNGs #4512 #4514 + [hugovk, radarhere] + +7.1.0 (2020-04-01) +------------------ + +- Fix multiple OOB reads in FLI decoding #4503 + [wiredfool] + +- Fix buffer overflow in SGI-RLE decoding #4504 + [wiredfool, hugovk] + +- Fix bounds overflow in JPEG 2000 decoding #4505 + [wiredfool] + +- Fix bounds overflow in PCX decoding #4506 + [wiredfool] + +- Fix 2 buffer overflows in TIFF decoding #4507 + [wiredfool] + +- Add APNG support #4243 + [pmrowla, radarhere, hugovk] + +- ImageGrab.grab() for Linux with XCB #4260 + [nulano, radarhere] + +- Added three new channel operations #4230 + [dwastberg, radarhere] + +- Prevent masking of Image reduce method in Jpeg2KImagePlugin #4474 + [radarhere, homm] + +- Added reading of earlier ImageMagick PNG EXIF data #4471 + [radarhere] + +- Fixed endian handling for I;16 getextrema #4457 + [radarhere] + +- Release buffer if function returns prematurely #4381 + [radarhere] + +- Add JPEG comment to info dictionary #4455 + [radarhere] + +- Fix size calculation of Image.thumbnail() #4404 + [orlnub123] + +- Fixed stroke on FreeType < 2.9 #4401 + [radarhere] + +- If present, only use alpha channel for bounding box #4454 + [radarhere] + +- Warn if an unknown feature is passed to features.check() #4438 + [jdufresne] + +- Fix Name field length when saving IM images #4424 + [hugovk, radarhere] + +- Allow saving of zero quality JPEG images #4440 + [radarhere] + +- Allow explicit zero width to hide outline #4334 + [radarhere] + +- Change ContainerIO return type to match file object mode #4297 + [jdufresne, radarhere] + +- Only draw each polygon pixel once #4333 + [radarhere] + - Add support for shooting situation Exif IFD tags #4398 [alexagv] @@ -3742,8 +4096,8 @@ Changelog (Pillow) 1.0 (07/30/2010) ---------------- -- Remove support for ``import Image``, etc. from the standard namespace. ``from PIL import Image`` etc. now required. -- Forked PIL based on `Hanno Schlichting's re-packaging `_ +- Remove support for ``import Image``. ``from PIL import Image`` now required. +- Forked PIL based on `Chris McDonough and Hanno Schlichting's setuptools compatible re-packaging `_ [aclark4life] Pre-fork @@ -3789,7 +4143,7 @@ Pre-fork This section may not be fully complete. For changes since this file was last updated, see the repository revision history: - https://bitbucket.org/effbot/pil-2009-raclette/commits/all + http://svn.effbot.org/public/pil/ (1.1.7 final) @@ -5223,23 +5577,23 @@ Pre-fork + Added keyword options to the "save" method. The following options are currently supported: - format option description + Format Option Description -------------------------------------------------------- - JPEG optimize minimize output file at the - expense of compression speed. + JPEG optimize Minimize output file at the + expense of compression speed. - JPEG progressive enable progressive output. the - option value is ignored. + JPEG progressive Enable progressive output. + The option value is ignored. - JPEG quality set compression quality (1-100). - the default value is 75. + JPEG quality Set compression quality (1-100). + The default value is 75. - JPEG smooth smooth dithered images. value - is strength (1-100). default is - off (0). + JPEG smooth Smooth dithered images. + Value is strength (1-100). + Default is off (0). - PNG optimize minimize output file at the - expense of compression speed. + PNG optimize Minimize output file at the + expense of compression speed. Expect more options in future releases. Also note that file writers silently ignore unknown options. @@ -5260,31 +5614,31 @@ Pre-fork + Various improvements to the sample scripts: "pilconvert" Carries out some extra tricks in order to make - the resulting file as small as possible. + the resulting file as small as possible. - "explode" (NEW) Split an image sequence into individual frames. + "explode" (NEW) Split an image sequence into individual frames. - "gifmaker" (NEW) Convert a sequence file into a GIF animation. - Note that the GIF encoder create "uncompressed" GIF - files, so animations created by this script are - rather large (typically 2-5 times the compressed - sizes). + "gifmaker" (NEW) Convert a sequence file into a GIF animation. + Note that the GIF encoder create "uncompressed" GIF + files, so animations created by this script are + rather large (typically 2-5 times the compressed + sizes). - "image2py" (NEW) Convert a single image to a python module. See - comments in this script for details. + "image2py" (NEW) Convert a single image to a python module. See + comments in this script for details. - "player" If multiple images are given on the command line, - they are interpreted as frames in a sequence. The - script assumes that they all have the same size. - Also note that this script now can play FLI/FLC - and GIF animations. + "player" If multiple images are given on the command line, + they are interpreted as frames in a sequence. The + script assumes that they all have the same size. + Also note that this script now can play FLI/FLC + and GIF animations. This player can also execute embedded Python animation applets (ARG format only). - "viewer" Transparent images ("P" with transparency property, - and "RGBA") are superimposed on the standard Tk back- - ground. + "viewer" Transparent images ("P" with transparency property, + and "RGBA") are superimposed on the standard Tk back- + ground. + Fixed colour argument to "new". For multilayer images, pass a tuple: (Red, Green, Blue), (Red, Green, Blue, Alpha), or (Cyan, @@ -5422,7 +5776,7 @@ Pre-fork any other pixel value means opaque. This is faster than using an "L" transparency mask. - + Properly writes EPS files (and properly prints images to postscript + + Properly writes EPS files (and properly prints images to PostScript printers as well). + Reads 4-bit BMP files, as well as 4 and 8-bit Windows ICO and CUR @@ -5505,7 +5859,7 @@ Pre-fork + Added the "pilfile" utility, which quickly identifies image files (without loading them, in most cases). - + Added the "pilprint" utility, which prints image files to Postscript + + Added the "pilprint" utility, which prints image files to PostScript printers. + Added a rudimentary version of the "pilview" utility, which is @@ -5519,5 +5873,5 @@ Pre-fork Jack). This allows you to read images through the Img extensions file format handlers. See the file "Lib/ImgExtImagePlugin.py" for details. - + Postscript printing is provided through the PSDraw module. See the + + PostScript printing is provided through the PSDraw module. See the handbook for details. diff --git a/LICENSE b/LICENSE index 4aac532f4..1197291bc 100644 --- a/LICENSE +++ b/LICENSE @@ -5,9 +5,9 @@ The Python Imaging Library (PIL) is Pillow is the friendly PIL fork. It is - Copyright © 2010-2020 by Alex Clark and contributors + Copyright © 2010-2021 by Alex Clark and contributors -Like PIL, Pillow is licensed under the open source PIL Software License: +Like PIL, Pillow is licensed under the open source HPND License: By obtaining, using, and/or copying this software and/or its associated documentation, you agree that you have read, understood, and will comply diff --git a/MANIFEST.in b/MANIFEST.in index 47daf4a11..e9aaa8318 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -6,6 +6,7 @@ include *.py include *.rst include *.sh include *.txt +include *.yaml include LICENSE include Makefile include tox.ini @@ -17,13 +18,12 @@ graft docs # build/src control detritus exclude .appveyor.yml +exclude .clang-format exclude .coveragerc exclude .editorconfig exclude .readthedocs.yml -exclude azure-pipelines.yml exclude codecov.yml global-exclude .git* global-exclude *.pyc global-exclude *.so -prune .azure-pipelines prune .ci diff --git a/Makefile b/Makefile index 6e55e4c7a..53eaa0566 100644 --- a/Makefile +++ b/Makefile @@ -1,36 +1,34 @@ -# https://www.gnu.org/software/make/manual/html_node/Phony-Targets.html -.PHONY: clean coverage doc docserve help inplace install install-req release-test sdist test upload upload-test -.DEFAULT_GOAL := release-test +.DEFAULT_GOAL := help +.PHONY: clean clean: python3 setup.py clean rm src/PIL/*.so || true rm -r build || true find . -name __pycache__ | xargs rm -r || true -BRANCHES=`git branch -a | grep -v HEAD | grep -v master | grep remote` -co: - -for i in $(BRANCHES) ; do \ - git checkout -t $$i ; \ - done - +.PHONY: coverage coverage: pytest -qq rm -r htmlcov || true coverage report +.PHONY: doc doc: $(MAKE) -C docs html +.PHONY: doccheck doccheck: $(MAKE) -C docs html # Don't make our tests rely on the links in the docs being up every single build. # We don't control them. But do check, and update them to the target of their redirects. $(MAKE) -C docs linkcheck || true +.PHONY: docserve docserve: - cd docs/_build/html && python3 -mSimpleHTTPServer 2> /dev/null& + cd docs/_build/html && python3 -m http.server 2> /dev/null& +.PHONY: help help: @echo "Welcome to Pillow development. Please use \`make \` where is one of" @echo " clean remove build products" @@ -42,23 +40,29 @@ help: @echo " install make and install" @echo " install-coverage make and install with C coverage" @echo " install-req install documentation and test dependencies" - @echo " install-venv install in virtualenv" + @echo " install-venv (deprecated) install in virtualenv" + @echo " lint run the lint checks" + @echo " lint-fix run black and isort to (mostly) fix lint issues." @echo " release-test run code and package tests before release" @echo " test run tests on installed pillow" @echo " upload build and upload sdists to PyPI" @echo " upload-test build and upload sdists to test.pythonpackages.com" +.PHONY: inplace inplace: clean python3 setup.py develop build_ext --inplace +.PHONY: install install: python3 setup.py install python3 selftest.py +.PHONY: install-coverage install-coverage: - CFLAGS="-coverage" python3 setup.py build_ext install + CFLAGS="-coverage -Werror=implicit-function-declaration" python3 setup.py build_ext install python3 selftest.py +.PHONY: debug debug: # make a debug version if we don't have a -dbg python. Leaves in symbols # for our stuff, kills optimization, and redirects to dev null so we @@ -66,40 +70,49 @@ debug: make clean > /dev/null CFLAGS='-g -O0' python3 setup.py build_ext install > /dev/null +.PHONY: install-req install-req: - pip install -r requirements.txt + python3 -m pip install -r requirements.txt +.PHONY: install-venv install-venv: + echo "'install-venv' is deprecated and will be removed in a future Pillow release" virtualenv . bin/pip install -r requirements.txt +.PHONY: release-test release-test: $(MAKE) install-req python3 setup.py develop python3 selftest.py python3 -m pytest Tests python3 setup.py install + -rm dist/*.egg + -rmdir dist python3 -m pytest -qq check-manifest pyroma . - viewdoc + $(MAKE) readme +.PHONY: sdist sdist: python3 setup.py sdist --format=gztar +.PHONY: test test: pytest -qq -# https://docs.python.org/3/distutils/packageindex.html#the-pypirc-file -upload-test: -# [test] -# username: -# password: -# repository = http://test.pythonpackages.com - python3 setup.py sdist --format=gztar upload -r test - -upload: - python3 setup.py sdist --format=gztar upload - +.PHONY: readme readme: - viewdoc + python3 setup.py --long-description | markdown2 > .long-description.html && open .long-description.html + + +.PHONY: lint +lint: + tox --help > /dev/null || python3 -m pip install tox + tox -e lint + +.PHONY: lint-fix +lint-fix: + black --target-version py36 . + isort . diff --git a/README.md b/README.md new file mode 100644 index 000000000..0408f4c28 --- /dev/null +++ b/README.md @@ -0,0 +1,102 @@ +

+ Pillow logo +

+ +# Pillow + +## Python Imaging Library (Fork) + +Pillow is the friendly PIL fork by [Alex Clark and +Contributors](https://github.com/python-pillow/Pillow/graphs/contributors). +PIL is the Python Imaging Library by Fredrik Lundh and Contributors. +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). + + + + + + + + + + + + + + + + + + +
docs + Documentation Status +
tests + GitHub Actions build status (Lint) + GitHub Actions build status (Test Linux and macOS) + GitHub Actions build status (Test Windows) + GitHub Actions build status (Test Docker) + AppVeyor CI build status (Windows) + Travis CI build status (macOS) + Code coverage +
package + Zenodo + Tidelift + Newest PyPI version + Number of PyPI downloads +
social + Join the chat at https://gitter.im/python-pillow/Pillow + Follow on https://twitter.com/PythonPillow +
+ +## Overview + +The Python Imaging Library adds image processing capabilities to your Python interpreter. + +This library provides extensive file format support, an efficient internal representation, and fairly powerful image processing capabilities. + +The core image library is designed for fast access to data stored in a few basic pixel formats. It should provide a solid foundation for a general image processing tool. + +## More Information + +- [Documentation](https://pillow.readthedocs.io/) + - [Installation](https://pillow.readthedocs.io/en/latest/installation.html) + - [Handbook](https://pillow.readthedocs.io/en/latest/handbook/index.html) +- [Contribute](https://github.com/python-pillow/Pillow/blob/master/.github/CONTRIBUTING.md) + - [Issues](https://github.com/python-pillow/Pillow/issues) + - [Pull requests](https://github.com/python-pillow/Pillow/pulls) +- [Release notes](https://pillow.readthedocs.io/en/stable/releasenotes/index.html) +- [Changelog](https://github.com/python-pillow/Pillow/blob/master/CHANGES.rst) + - [Pre-fork](https://github.com/python-pillow/Pillow/blob/master/CHANGES.rst#pre-fork) + +## Report a Vulnerability + +To report a security vulnerability, please follow the procedure described in the [Tidelift security policy](https://tidelift.com/docs/security). diff --git a/README.rst b/README.rst deleted file mode 100644 index 253dc89a5..000000000 --- a/README.rst +++ /dev/null @@ -1,99 +0,0 @@ -Pillow -====== - -Python Imaging Library (Fork) ------------------------------ - -Pillow is the friendly PIL fork by `Alex Clark and Contributors `_. PIL is the Python Imaging Library by Fredrik Lundh and Contributors. As of 2019, Pillow development is `supported by Tidelift `_. - -.. start-badges - -.. list-table:: - :stub-columns: 1 - - * - docs - - |docs| - * - tests - - |linux| |macos| |windows| |gha_lint| |gha| |gha_windows| |gha_docker| |coverage| - * - package - - |zenodo| |tidelift| |version| |downloads| - * - social - - |gitter| |twitter| - -.. end-badges - -More Information ----------------- - -- `Documentation `_ - - - `Installation `_ - - `Handbook `_ - -- `Contribute `_ - - - `Issues `_ - - `Pull requests `_ - -- `Changelog `_ - - - `Pre-fork `_ - -Report a Vulnerability ----------------------- - -To report a security vulnerability, please follow the procedure described in the `Tidelift security policy `_. - -.. |docs| image:: https://readthedocs.org/projects/pillow/badge/?version=latest - :target: https://pillow.readthedocs.io/?badge=latest - :alt: Documentation Status - -.. |linux| image:: https://img.shields.io/travis/python-pillow/Pillow/master.svg?label=Linux%20build - :target: https://travis-ci.org/python-pillow/Pillow - :alt: Travis CI build status (Linux) - -.. |macos| image:: https://img.shields.io/travis/python-pillow/pillow-wheels/master.svg?label=macOS%20build - :target: https://travis-ci.org/python-pillow/pillow-wheels - :alt: Travis CI build status (macOS) - -.. |windows| image:: https://img.shields.io/appveyor/build/python-pillow/Pillow/master.svg?label=Windows%20build - :target: https://ci.appveyor.com/project/python-pillow/Pillow - :alt: AppVeyor CI build status (Windows) - -.. |gha_lint| image:: https://github.com/python-pillow/Pillow/workflows/Lint/badge.svg - :alt: GitHub Actions build status (Lint) - -.. |gha_docker| image:: https://github.com/python-pillow/Pillow/workflows/Test%20Docker/badge.svg - :alt: GitHub Actions build status (Test Docker) - -.. |gha| image:: https://github.com/python-pillow/Pillow/workflows/Test/badge.svg - :alt: GitHub Actions build status (Test Linux and macOS) - -.. |gha_windows| image:: https://github.com/python-pillow/Pillow/workflows/Test%20Windows/badge.svg - :alt: GitHub Actions build status (Test Windows) - -.. |coverage| image:: https://codecov.io/gh/python-pillow/Pillow/branch/master/graph/badge.svg - :target: https://codecov.io/gh/python-pillow/Pillow - :alt: Code coverage - -.. |zenodo| image:: https://zenodo.org/badge/17549/python-pillow/Pillow.svg - :target: https://zenodo.org/badge/latestdoi/17549/python-pillow/Pillow - -.. |tidelift| image:: https://tidelift.com/badges/package/pypi/Pillow?style=flat - :target: https://tidelift.com/subscription/pkg/pypi-pillow?utm_source=pypi-pillow&utm_medium=referral&utm_campaign=readme - -.. |version| image:: https://img.shields.io/pypi/v/pillow.svg - :target: https://pypi.org/project/Pillow/ - :alt: Latest PyPI version - -.. |downloads| image:: https://img.shields.io/pypi/dm/pillow.svg - :target: https://pypi.org/project/Pillow/ - :alt: Number of PyPI downloads - -.. |gitter| image:: https://badges.gitter.im/python-pillow/Pillow.svg - :target: https://gitter.im/python-pillow/Pillow?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge - :alt: Join the chat at https://gitter.im/python-pillow/Pillow - -.. |twitter| image:: https://img.shields.io/badge/tweet-on%20Twitter-00aced.svg - :target: https://twitter.com/PythonPillow - :alt: Follow on https://twitter.com/PythonPillow diff --git a/RELEASING.md b/RELEASING.md index 9614b133f..6045f84ac 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -1,13 +1,16 @@ # Release Checklist +See https://pillow.readthedocs.io/en/stable/releasenotes/versioning.html for +information about how the version numbers line up with releases. + ## Main Release -Released quarterly on the first day of January, April, July, October. +Released quarterly on January 2nd, April 1st, July 1st and October 15th. * [ ] Open a release ticket e.g. https://github.com/python-pillow/Pillow/issues/3154 * [ ] Develop and prepare release in `master` branch. -* [ ] Check [Travis CI](https://travis-ci.org/python-pillow/Pillow) and [AppVeyor CI](https://ci.appveyor.com/project/python-pillow/Pillow) to confirm passing tests in `master` branch. -* [ ] Check that all of the wheel builds [Pillow Wheel Builder](https://github.com/python-pillow/pillow-wheels) pass the tests in Travis CI. +* [ ] Check [GitHub Actions](https://github.com/python-pillow/Pillow/actions) and [AppVeyor](https://ci.appveyor.com/project/python-pillow/Pillow) to confirm passing tests in `master` branch. +* [ ] Check that all of the wheel builds [Pillow Wheel Builder](https://github.com/python-pillow/pillow-wheels) pass the tests in Travis CI and GitHub Actions. * [ ] In compliance with [PEP 440](https://www.python.org/dev/peps/pep-0440/), update version identifier in `src/PIL/_version.py` * [ ] Update `CHANGES.rst`. * [ ] Run pre-release check via `make release-test` in a freshly cloned repo. @@ -18,13 +21,18 @@ Released quarterly on the first day of January, April, July, October. git push --all git push --tags ``` -* [ ] Create source distributions e.g.: +* [ ] Create and check source distribution: ```bash make sdist + twine check dist/* ``` * [ ] Create [binary distributions](https://github.com/python-pillow/Pillow/blob/master/RELEASING.md#binary-distributions) -* [ ] Upload all binaries and source distributions e.g. `twine upload dist/Pillow-5.2.0*` -* [ ] Create a [new release on GitHub](https://github.com/python-pillow/Pillow/releases/new) +* [ ] Check and upload all binaries and source distributions e.g.: + ```bash + twine check dist/* + twine upload dist/Pillow-5.2.0* + ``` +* [ ] Publish the [release on GitHub](https://github.com/python-pillow/Pillow/releases) * [ ] In compliance with [PEP 440](https://www.python.org/dev/peps/pep-0440/), increment and append `.dev0` to version identifier in `src/PIL/_version.py` ## Point Release @@ -37,22 +45,31 @@ Released as needed for security, installation or critical bug fixes. ```bash git checkout -t remotes/origin/5.2.x ``` -* [ ] Cherry pick individual commits from `master` branch to release branch e.g. `5.2.x`. -* [ ] Check [Travis CI](https://travis-ci.org/python-pillow/Pillow) to confirm passing tests in release branch e.g. `5.2.x`. +* [ ] Cherry pick individual commits from `master` branch to release branch e.g. `5.2.x`, then `git push`. + + + +* [ ] Check [GitHub Actions](https://github.com/python-pillow/Pillow/actions) and [AppVeyor](https://ci.appveyor.com/project/python-pillow/Pillow) to confirm passing tests in release branch e.g. `5.2.x`. * [ ] In compliance with [PEP 440](https://www.python.org/dev/peps/pep-0440/), update version identifier in `src/PIL/_version.py` * [ ] Run pre-release check via `make release-test`. * [ ] Create tag for release e.g.: ```bash git tag 5.2.1 + git push git push --tags ``` -* [ ] Create source distributions e.g.: +* [ ] Create and check source distribution: ```bash make sdist + twine check dist/* ``` * [ ] Create [binary distributions](https://github.com/python-pillow/Pillow/blob/master/RELEASING.md#binary-distributions) -* [ ] Upload all binaries and source distributions e.g. `twine upload dist/Pillow-5.2.1*` -* [ ] Create a [new release on GitHub](https://github.com/python-pillow/Pillow/releases/new) +* [ ] Check and upload all binaries and source distributions e.g.: + ```bash + twine check dist/* + twine upload dist/Pillow-5.2.1* + ``` +* [ ] Publish the [release on GitHub](https://github.com/python-pillow/Pillow/releases) ## Embargoed Release @@ -71,18 +88,19 @@ Released as needed privately to individual vendors for critical security-related git push origin 2.5.x git push origin --tags ``` -* [ ] Create source distributions e.g.: +* [ ] Create and check source distribution: ```bash make sdist + twine check dist/* ``` * [ ] Create [binary distributions](https://github.com/python-pillow/Pillow/blob/master/RELEASING.md#binary-distributions) -* [ ] Create a [new release on GitHub](https://github.com/python-pillow/Pillow/releases/new) +* [ ] Publish the [release on GitHub](https://github.com/python-pillow/Pillow/releases) ## Binary Distributions ### Windows * [ ] Contact `@cgohlke` for Windows binaries via release ticket e.g. https://github.com/python-pillow/Pillow/issues/1174. -* [ ] Download and extract tarball from `@cgohlke` and `twine upload *`. +* [ ] Download and extract tarball from `@cgohlke` and copy into `dist/` ### Mac and Linux * [ ] Use the [Pillow Wheel Builder](https://github.com/python-pillow/pillow-wheels): @@ -91,11 +109,8 @@ Released as needed privately to individual vendors for critical security-related cd pillow-wheels ./update-pillow-tag.sh [[release tag]] ``` -* [ ] Download distributions from the [Pillow Wheel Builder container](http://a365fff413fe338398b6-1c8a9b3114517dc5fe17b7c3f8c63a43.r19.cf2.rackcdn.com/). - ```bash - wget -m -A 'Pillow--*' \ - http://a365fff413fe338398b6-1c8a9b3114517dc5fe17b7c3f8c63a43.r19.cf2.rackcdn.com - ``` +* [ ] Download wheels from the [Pillow Wheel Builder release](https://github.com/python-pillow/pillow-wheels/releases) + and copy into `dist/` ## Publicize Release @@ -104,3 +119,12 @@ Released as needed privately to individual vendors for critical security-related ## Documentation * [ ] Make sure the [default version for Read the Docs](https://pillow.readthedocs.io/en/stable/) is up-to-date with the release changes + +## Docker Images + +* [ ] Update Pillow in the Docker Images repository + ```bash + git clone https://github.com/python-pillow/docker-images + cd docker-images + ./update-pillow-tag.sh [[release tag]] + ``` diff --git a/Tests/README.rst b/Tests/README.rst index 9c959a4bf..554645787 100644 --- a/Tests/README.rst +++ b/Tests/README.rst @@ -1,14 +1,14 @@ Pillow Tests ============ -Test scripts are named ``test_xxx.py`` and use the ``unittest`` module. A base class and helper functions can be found in ``helper.py``. +Test scripts are named ``test_xxx.py``. Helper classes and functions can be found in ``helper.py``. Dependencies ------------ Install:: - pip install pytest pytest-cov + python3 -m pip install pytest pytest-cov Execution --------- diff --git a/Tests/bench_cffi_access.py b/Tests/bench_cffi_access.py index 1797d34fc..87cad699d 100644 --- a/Tests/bench_cffi_access.py +++ b/Tests/bench_cffi_access.py @@ -1,11 +1,10 @@ import time -import unittest from PIL import PyAccess -from .helper import PillowTestCase, hopper +from .helper import hopper -# Not running this test by default. No DOS against Travis CI. +# Not running this test by default. No DOS against CI. def iterate_get(size, access): @@ -29,34 +28,31 @@ def timer(func, label, *args): func(*args) if time.time() - starttime > 10: print( - "%s: breaking at %s iterations, %.6f per iteration" - % (label, x + 1, (time.time() - starttime) / (x + 1.0)) + "{}: breaking at {} iterations, {:.6f} per iteration".format( + label, x + 1, (time.time() - starttime) / (x + 1.0) + ) ) break if x == iterations - 1: endtime = time.time() print( - "%s: %.4f s %.6f per iteration" - % (label, endtime - starttime, (endtime - starttime) / (x + 1.0)) + "{}: {:.4f} s {:.6f} per iteration".format( + label, endtime - starttime, (endtime - starttime) / (x + 1.0) + ) ) -class BenchCffiAccess(PillowTestCase): - def test_direct(self): - im = hopper() - im.load() - # im = Image.new( "RGB", (2000, 2000), (1, 3, 2)) - caccess = im.im.pixel_access(False) - access = PyAccess.new(im, False) +def test_direct(): + im = hopper() + im.load() + # im = Image.new( "RGB", (2000, 2000), (1, 3, 2)) + caccess = im.im.pixel_access(False) + access = PyAccess.new(im, False) - self.assertEqual(caccess[(0, 0)], access[(0, 0)]) + assert caccess[(0, 0)] == access[(0, 0)] - print("Size: %sx%s" % im.size) - timer(iterate_get, "PyAccess - get", im.size, access) - timer(iterate_set, "PyAccess - set", im.size, access) - timer(iterate_get, "C-api - get", im.size, caccess) - timer(iterate_set, "C-api - set", im.size, caccess) - - -if __name__ == "__main__": - unittest.main() + print("Size: %sx%s" % im.size) + timer(iterate_get, "PyAccess - get", im.size, access) + timer(iterate_set, "PyAccess - set", im.size, access) + timer(iterate_get, "C-api - get", im.size, caccess) + timer(iterate_set, "C-api - set", im.size, caccess) diff --git a/Tests/check_fli_oob.py b/Tests/check_fli_oob.py new file mode 100644 index 000000000..739ad224e --- /dev/null +++ b/Tests/check_fli_oob.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python + +from PIL import Image + +repro_ss2 = ( + "images/fli_oob/06r/06r00.fli", + "images/fli_oob/06r/others/06r01.fli", + "images/fli_oob/06r/others/06r02.fli", + "images/fli_oob/06r/others/06r03.fli", + "images/fli_oob/06r/others/06r04.fli", +) + +repro_lc = ( + "images/fli_oob/05r/05r00.fli", + "images/fli_oob/05r/others/05r03.fli", + "images/fli_oob/05r/others/05r06.fli", + "images/fli_oob/05r/others/05r05.fli", + "images/fli_oob/05r/others/05r01.fli", + "images/fli_oob/05r/others/05r04.fli", + "images/fli_oob/05r/others/05r02.fli", + "images/fli_oob/05r/others/05r07.fli", + "images/fli_oob/patch0/000000", + "images/fli_oob/patch0/000001", + "images/fli_oob/patch0/000002", + "images/fli_oob/patch0/000003", +) + + +repro_advance = ( + "images/fli_oob/03r/03r00.fli", + "images/fli_oob/03r/others/03r01.fli", + "images/fli_oob/03r/others/03r09.fli", + "images/fli_oob/03r/others/03r11.fli", + "images/fli_oob/03r/others/03r05.fli", + "images/fli_oob/03r/others/03r10.fli", + "images/fli_oob/03r/others/03r06.fli", + "images/fli_oob/03r/others/03r08.fli", + "images/fli_oob/03r/others/03r03.fli", + "images/fli_oob/03r/others/03r07.fli", + "images/fli_oob/03r/others/03r02.fli", + "images/fli_oob/03r/others/03r04.fli", +) + +repro_brun = ( + "images/fli_oob/04r/initial.fli", + "images/fli_oob/04r/others/04r02.fli", + "images/fli_oob/04r/others/04r05.fli", + "images/fli_oob/04r/others/04r04.fli", + "images/fli_oob/04r/others/04r03.fli", + "images/fli_oob/04r/others/04r01.fli", + "images/fli_oob/04r/04r00.fli", +) + +repro_copy = ( + "images/fli_oob/02r/others/02r02.fli", + "images/fli_oob/02r/others/02r04.fli", + "images/fli_oob/02r/others/02r03.fli", + "images/fli_oob/02r/others/02r01.fli", + "images/fli_oob/02r/02r00.fli", +) + + +for path in repro_ss2 + repro_lc + repro_advance + repro_brun + repro_copy: + im = Image.open(path) + try: + im.load() + except Exception as msg: + print(msg) diff --git a/Tests/check_fli_overflow.py b/Tests/check_fli_overflow.py index 206a86007..08a55d349 100644 --- a/Tests/check_fli_overflow.py +++ b/Tests/check_fli_overflow.py @@ -1,19 +1,10 @@ -import unittest - from PIL import Image -from .helper import PillowTestCase - TEST_FILE = "Tests/images/fli_overflow.fli" -class TestFliOverflow(PillowTestCase): - def test_fli_overflow(self): +def test_fli_overflow(): - # this should not crash with a malloc error or access violation - with Image.open(TEST_FILE) as im: - im.load() - - -if __name__ == "__main__": - unittest.main() + # this should not crash with a malloc error or access violation + with Image.open(TEST_FILE) as im: + im.load() diff --git a/Tests/check_imaging_leaks.py b/Tests/check_imaging_leaks.py index 2c1793a4f..407f3ea80 100755 --- a/Tests/check_imaging_leaks.py +++ b/Tests/check_imaging_leaks.py @@ -1,46 +1,45 @@ #!/usr/bin/env python -import unittest +import pytest from PIL import Image -from .helper import PillowTestCase, is_win32 +from .helper import is_win32 min_iterations = 100 max_iterations = 10000 - -@unittest.skipIf(is_win32(), "requires Unix or macOS") -class TestImagingLeaks(PillowTestCase): - def _get_mem_usage(self): - from resource import getpagesize, getrusage, RUSAGE_SELF - - mem = getrusage(RUSAGE_SELF).ru_maxrss - return mem * getpagesize() / 1024 / 1024 - - def _test_leak(self, min_iterations, max_iterations, fn, *args, **kwargs): - mem_limit = None - for i in range(max_iterations): - fn(*args, **kwargs) - mem = self._get_mem_usage() - if i < min_iterations: - mem_limit = mem + 1 - continue - msg = "memory usage limit exceeded after %d iterations" % (i + 1) - self.assertLessEqual(mem, mem_limit, msg) - - def test_leak_putdata(self): - im = Image.new("RGB", (25, 25)) - self._test_leak(min_iterations, max_iterations, im.putdata, im.getdata()) - - def test_leak_getlist(self): - im = Image.new("P", (25, 25)) - self._test_leak( - min_iterations, - max_iterations, - # Pass a new list at each iteration. - lambda: im.point(range(256)), - ) +pytestmark = pytest.mark.skipif(is_win32(), reason="requires Unix or macOS") -if __name__ == "__main__": - unittest.main() +def _get_mem_usage(): + from resource import RUSAGE_SELF, getpagesize, getrusage + + mem = getrusage(RUSAGE_SELF).ru_maxrss + return mem * getpagesize() / 1024 / 1024 + + +def _test_leak(min_iterations, max_iterations, fn, *args, **kwargs): + mem_limit = None + for i in range(max_iterations): + fn(*args, **kwargs) + mem = _get_mem_usage() + if i < min_iterations: + mem_limit = mem + 1 + continue + msg = f"memory usage limit exceeded after {i + 1} iterations" + assert mem <= mem_limit, msg + + +def test_leak_putdata(): + im = Image.new("RGB", (25, 25)) + _test_leak(min_iterations, max_iterations, im.putdata, im.getdata()) + + +def test_leak_getlist(): + im = Image.new("P", (25, 25)) + _test_leak( + min_iterations, + max_iterations, + # Pass a new list at each iteration. + lambda: im.point(range(256)), + ) diff --git a/Tests/check_j2k_leaks.py b/Tests/check_j2k_leaks.py index 1635f1001..afe5836f3 100755 --- a/Tests/check_j2k_leaks.py +++ b/Tests/check_j2k_leaks.py @@ -1,46 +1,42 @@ -import unittest from io import BytesIO +import pytest + from PIL import Image -from .helper import PillowTestCase, is_win32 +from .helper import is_win32, skip_unless_feature # Limits for testing the leak mem_limit = 1024 * 1048576 stack_size = 8 * 1048576 iterations = int((mem_limit / stack_size) * 2) -codecs = dir(Image.core) test_file = "Tests/images/rgb_trns_ycbc.jp2" - -@unittest.skipIf(is_win32(), "requires Unix or macOS") -class TestJpegLeaks(PillowTestCase): - def setUp(self): - if "jpeg2k_encoder" not in codecs or "jpeg2k_decoder" not in codecs: - self.skipTest("JPEG 2000 support not available") - - def test_leak_load(self): - from resource import setrlimit, RLIMIT_AS, RLIMIT_STACK - - setrlimit(RLIMIT_STACK, (stack_size, stack_size)) - setrlimit(RLIMIT_AS, (mem_limit, mem_limit)) - for _ in range(iterations): - with Image.open(test_file) as im: - im.load() - - def test_leak_save(self): - from resource import setrlimit, RLIMIT_AS, RLIMIT_STACK - - setrlimit(RLIMIT_STACK, (stack_size, stack_size)) - setrlimit(RLIMIT_AS, (mem_limit, mem_limit)) - for _ in range(iterations): - with Image.open(test_file) as im: - im.load() - test_output = BytesIO() - im.save(test_output, "JPEG2000") - test_output.seek(0) - test_output.read() +pytestmark = [ + pytest.mark.skipif(is_win32(), reason="requires Unix or macOS"), + skip_unless_feature("jpg_2000"), +] -if __name__ == "__main__": - unittest.main() +def test_leak_load(): + from resource import RLIMIT_AS, RLIMIT_STACK, setrlimit + + setrlimit(RLIMIT_STACK, (stack_size, stack_size)) + setrlimit(RLIMIT_AS, (mem_limit, mem_limit)) + for _ in range(iterations): + with Image.open(test_file) as im: + im.load() + + +def test_leak_save(): + from resource import RLIMIT_AS, RLIMIT_STACK, setrlimit + + setrlimit(RLIMIT_STACK, (stack_size, stack_size)) + setrlimit(RLIMIT_AS, (mem_limit, mem_limit)) + for _ in range(iterations): + with Image.open(test_file) as im: + im.load() + test_output = BytesIO() + im.save(test_output, "JPEG2000") + test_output.seek(0) + test_output.read() diff --git a/Tests/check_j2k_overflow.py b/Tests/check_j2k_overflow.py index d5b6e455f..b16412898 100644 --- a/Tests/check_j2k_overflow.py +++ b/Tests/check_j2k_overflow.py @@ -1,18 +1,10 @@ -import unittest +import pytest from PIL import Image -from .helper import PillowTestCase - -class TestJ2kEncodeOverflow(PillowTestCase): - def test_j2k_overflow(self): - - im = Image.new("RGBA", (1024, 131584)) - target = self.tempfile("temp.jpc") - with self.assertRaises(IOError): - im.save(target) - - -if __name__ == "__main__": - unittest.main() +def test_j2k_overflow(tmp_path): + im = Image.new("RGBA", (1024, 131584)) + target = str(tmp_path / "temp.jpc") + with pytest.raises(OSError): + im.save(target) diff --git a/Tests/check_jp2_overflow.py b/Tests/check_jp2_overflow.py new file mode 100755 index 000000000..a7a343c98 --- /dev/null +++ b/Tests/check_jp2_overflow.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python + +# Reproductions/tests for OOB read errors in FliDecode.c + +# When run in python, all of these images should fail for +# one reason or another, either as a buffer overrun, +# unrecognized datastream, or truncated image file. +# There shouldn't be any segfaults. +# +# if run like +# `valgrind --tool=memcheck python check_jp2_overflow.py 2>&1 | grep Decode.c` +# the output should be empty. There may be python issues +# in the valgrind especially if run in a debug python +# version. + + +from PIL import Image + +repro = ("00r0_gray_l.jp2", "00r1_graya_la.jp2") + +for path in repro: + im = Image.open(path) + try: + im.load() + except Exception as msg: + print(msg) diff --git a/Tests/check_jpeg_leaks.py b/Tests/check_jpeg_leaks.py index 6b2801a21..ab8d77719 100644 --- a/Tests/check_jpeg_leaks.py +++ b/Tests/check_jpeg_leaks.py @@ -1,7 +1,8 @@ -import unittest from io import BytesIO -from .helper import PillowTestCase, hopper, is_win32 +import pytest + +from .helper import hopper, is_win32 iterations = 5000 @@ -15,10 +16,9 @@ valgrind --tool=massif python test-installed.py -s -v Tests/check_jpeg_leaks.py """ -@unittest.skipIf(is_win32(), "requires Unix or macOS") -class TestJpegLeaks(PillowTestCase): +pytestmark = pytest.mark.skipif(is_win32(), reason="requires Unix or macOS") - """ +""" pre patch: MB @@ -74,143 +74,140 @@ post-patch: """ - def test_qtables_leak(self): - im = hopper("RGB") - standard_l_qtable = [ - int(s) - for s in """ - 16 11 10 16 24 40 51 61 - 12 12 14 19 26 58 60 55 - 14 13 16 24 40 57 69 56 - 14 17 22 29 51 87 80 62 - 18 22 37 56 68 109 103 77 - 24 35 55 64 81 104 113 92 - 49 64 78 87 103 121 120 101 - 72 92 95 98 112 100 103 99 - """.split( - None - ) - ] +def test_qtables_leak(): + im = hopper("RGB") - standard_chrominance_qtable = [ - int(s) - for s in """ - 17 18 24 47 99 99 99 99 - 18 21 26 66 99 99 99 99 - 24 26 56 99 99 99 99 99 - 47 66 99 99 99 99 99 99 - 99 99 99 99 99 99 99 99 - 99 99 99 99 99 99 99 99 - 99 99 99 99 99 99 99 99 - 99 99 99 99 99 99 99 99 - """.split( - None - ) - ] + standard_l_qtable = [ + int(s) + for s in """ + 16 11 10 16 24 40 51 61 + 12 12 14 19 26 58 60 55 + 14 13 16 24 40 57 69 56 + 14 17 22 29 51 87 80 62 + 18 22 37 56 68 109 103 77 + 24 35 55 64 81 104 113 92 + 49 64 78 87 103 121 120 101 + 72 92 95 98 112 100 103 99 + """.split( + None + ) + ] - qtables = [standard_l_qtable, standard_chrominance_qtable] + standard_chrominance_qtable = [ + int(s) + for s in """ + 17 18 24 47 99 99 99 99 + 18 21 26 66 99 99 99 99 + 24 26 56 99 99 99 99 99 + 47 66 99 99 99 99 99 99 + 99 99 99 99 99 99 99 99 + 99 99 99 99 99 99 99 99 + 99 99 99 99 99 99 99 99 + 99 99 99 99 99 99 99 99 + """.split( + None + ) + ] - for _ in range(iterations): - test_output = BytesIO() - im.save(test_output, "JPEG", qtables=qtables) + qtables = [standard_l_qtable, standard_chrominance_qtable] - def test_exif_leak(self): - """ -pre patch: - - MB -177.1^ # - | @@@# - | :@@@@@@# - | ::::@@@@@@# - | ::::::::@@@@@@# - | @@::::: ::::@@@@@@# - | @@@@ ::::: ::::@@@@@@# - | @@@@@@@ ::::: ::::@@@@@@# - | @@::@@@@@@@ ::::: ::::@@@@@@# - | @@@@ : @@@@@@@ ::::: ::::@@@@@@# - | @@@@@@ @@ : @@@@@@@ ::::: ::::@@@@@@# - | @@@@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# - | @::@@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# - | ::::@: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# - | :@@: : @: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# - | ::@@::@@: : @: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# - | @@::: @ ::@@: : @: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# - | @::@ : : @ ::@@: : @: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# - | :::@: @ : : @ ::@@: : @: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# - | @@@:: @: @ : : @ ::@@: : @: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# - 0 +----------------------------------------------------------------------->Gi - 0 11.37 + for _ in range(iterations): + test_output = BytesIO() + im.save(test_output, "JPEG", qtables=qtables) -post patch: +def test_exif_leak(): + """ + pre patch: - MB -21.06^ ::::::::::::::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - | ##::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - | # ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - | # ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - | # ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - | @# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - | @# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - | @# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - | @# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - | @@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - | @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - | @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - | @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - | @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - | @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - | @@@@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - | @ @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - | @ @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - | @ @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - | @ @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - 0 +----------------------------------------------------------------------->Gi - 0 11.33 - -""" - im = hopper("RGB") - exif = b"12345678" * 4096 - - for _ in range(iterations): - test_output = BytesIO() - im.save(test_output, "JPEG", exif=exif) - - def test_base_save(self): - """ -base case: - MB -20.99^ ::::: :::::::::::::::::::::::::::::::::::::::::::@::: - | ##: : ::::::@::::::: :::: :::: : : : : : : :::::::::::: :::@::: - | # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: - | # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: - | # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: - | @@# : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: - | @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: - | @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: - | @@@ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: - | @ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: - | @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: - | @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: - | @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: - | @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: - | @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: - | :@@@@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: - | :@ @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: - | :@ @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: - | :@ @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: - | :@ @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: - 0 +----------------------------------------------------------------------->Gi - 0 7.882 -""" - im = hopper("RGB") - - for _ in range(iterations): - test_output = BytesIO() - im.save(test_output, "JPEG") + MB + 177.1^ # + | @@@# + | :@@@@@@# + | ::::@@@@@@# + | ::::::::@@@@@@# + | @@::::: ::::@@@@@@# + | @@@@ ::::: ::::@@@@@@# + | @@@@@@@ ::::: ::::@@@@@@# + | @@::@@@@@@@ ::::: ::::@@@@@@# + | @@@@ : @@@@@@@ ::::: ::::@@@@@@# + | @@@@@@ @@ : @@@@@@@ ::::: ::::@@@@@@# + | @@@@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# + | @::@@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# + | ::::@: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# + | :@@: : @: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# + | ::@@::@@: : @: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# + | @@::: @ ::@@: : @: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# + | @::@ : : @ ::@@: : @: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# + | :::@: @ : : @ ::@@: : @: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# + | @@@:: @: @ : : @ ::@@: : @: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# + 0 +----------------------------------------------------------------------->Gi + 0 11.37 -if __name__ == "__main__": - unittest.main() + post patch: + + MB + 21.06^ ::::::::::::::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + | ##::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + | # ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + | # ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + | # ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + | @# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + | @# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + | @# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + | @# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + | @@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + | @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + | @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + | @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + | @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + | @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + | @@@@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + | @ @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + | @ @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + | @ @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + | @ @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + 0 +----------------------------------------------------------------------->Gi + 0 11.33 + """ + im = hopper("RGB") + exif = b"12345678" * 4096 + + for _ in range(iterations): + test_output = BytesIO() + im.save(test_output, "JPEG", exif=exif) + + +def test_base_save(): + """ + base case: + MB + 20.99^ ::::: :::::::::::::::::::::::::::::::::::::::::::@::: + | ##: : ::::::@::::::: :::: :::: : : : : : : :::::::::::: :::@::: + | # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: + | # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: + | # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: + | @@# : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: + | @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: + | @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: + | @@@ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: + | @ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: + | @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: + | @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: + | @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: + | @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: + | @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: + | :@@@@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: + | :@ @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: + | :@ @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: + | :@ @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: + | :@ @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: + 0 +----------------------------------------------------------------------->Gi + 0 7.882""" + im = hopper("RGB") + + for _ in range(iterations): + test_output = BytesIO() + im.save(test_output, "JPEG") diff --git a/Tests/check_large_memory.py b/Tests/check_large_memory.py index 7fcaa4cf9..723a1a21e 100644 --- a/Tests/check_large_memory.py +++ b/Tests/check_large_memory.py @@ -1,10 +1,9 @@ import sys -import unittest + +import pytest from PIL import Image -from .helper import PillowTestCase - # This test is not run automatically. # # It requires > 2gb memory for the >2 gigapixel image generated in the @@ -24,26 +23,26 @@ YDIM = 32769 XDIM = 48000 -@unittest.skipIf(sys.maxsize <= 2 ** 32, "requires 64-bit system") -class LargeMemoryTest(PillowTestCase): - def _write_png(self, xdim, ydim): - f = self.tempfile("temp.png") - im = Image.new("L", (xdim, ydim), 0) - im.save(f) - - def test_large(self): - """ succeeded prepatch""" - self._write_png(XDIM, YDIM) - - def test_2gpx(self): - """failed prepatch""" - self._write_png(XDIM, XDIM) - - @unittest.skipIf(numpy is None, "Numpy is not installed") - def test_size_greater_than_int(self): - arr = numpy.ndarray(shape=(16394, 16394)) - Image.fromarray(arr) +pytestmark = pytest.mark.skipif(sys.maxsize <= 2 ** 32, reason="requires 64-bit system") -if __name__ == "__main__": - unittest.main() +def _write_png(tmp_path, xdim, ydim): + f = str(tmp_path / "temp.png") + im = Image.new("L", (xdim, ydim), 0) + im.save(f) + + +def test_large(tmp_path): + """ succeeded prepatch""" + _write_png(tmp_path, XDIM, YDIM) + + +def test_2gpx(tmp_path): + """failed prepatch""" + _write_png(tmp_path, XDIM, XDIM) + + +@pytest.mark.skipif(numpy is None, reason="Numpy is not installed") +def test_size_greater_than_int(): + arr = numpy.ndarray(shape=(16394, 16394)) + Image.fromarray(arr) diff --git a/Tests/check_large_memory_numpy.py b/Tests/check_large_memory_numpy.py index 8e65dc1cb..79d1cfd5b 100644 --- a/Tests/check_large_memory_numpy.py +++ b/Tests/check_large_memory_numpy.py @@ -1,10 +1,9 @@ import sys -import unittest + +import pytest from PIL import Image -from .helper import PillowTestCase - # This test is not run automatically. # # It requires > 2gb memory for the >2 gigapixel image generated in the @@ -14,32 +13,28 @@ from .helper import PillowTestCase # Raspberry Pis). -try: - import numpy as np -except ImportError: - raise unittest.SkipTest("numpy not installed") +np = pytest.importorskip("numpy", reason="NumPy not installed") YDIM = 32769 XDIM = 48000 -@unittest.skipIf(sys.maxsize <= 2 ** 32, "requires 64-bit system") -class LargeMemoryNumpyTest(PillowTestCase): - def _write_png(self, xdim, ydim): - dtype = np.uint8 - a = np.zeros((xdim, ydim), dtype=dtype) - f = self.tempfile("temp.png") - im = Image.fromarray(a, "L") - im.save(f) - - def test_large(self): - """ succeeded prepatch""" - self._write_png(XDIM, YDIM) - - def test_2gpx(self): - """failed prepatch""" - self._write_png(XDIM, XDIM) +pytestmark = pytest.mark.skipif(sys.maxsize <= 2 ** 32, reason="requires 64-bit system") -if __name__ == "__main__": - unittest.main() +def _write_png(tmp_path, xdim, ydim): + dtype = np.uint8 + a = np.zeros((xdim, ydim), dtype=dtype) + f = str(tmp_path / "temp.png") + im = Image.fromarray(a, "L") + im.save(f) + + +def test_large(tmp_path): + """ succeeded prepatch""" + _write_png(tmp_path, XDIM, YDIM) + + +def test_2gpx(tmp_path): + """failed prepatch""" + _write_png(tmp_path, XDIM, XDIM) diff --git a/Tests/check_libtiff_segfault.py b/Tests/check_libtiff_segfault.py index b272c601c..bd7f407e4 100644 --- a/Tests/check_libtiff_segfault.py +++ b/Tests/check_libtiff_segfault.py @@ -1,22 +1,15 @@ -import unittest +import pytest from PIL import Image -from .helper import PillowTestCase - TEST_FILE = "Tests/images/libtiff_segfault.tif" -class TestLibtiffSegfault(PillowTestCase): - def test_segfault(self): - """ This test should not segfault. It will on Pillow <= 3.1.0 and - libtiff >= 4.0.0 - """ +def test_libtiff_segfault(): + """This test should not segfault. It will on Pillow <= 3.1.0 and + libtiff >= 4.0.0 + """ - with self.assertRaises(IOError): - with Image.open(TEST_FILE) as im: - im.load() - - -if __name__ == "__main__": - unittest.main() + with pytest.raises(OSError): + with Image.open(TEST_FILE) as im: + im.load() diff --git a/Tests/check_png_dos.py b/Tests/check_png_dos.py index f133b2695..d8d645189 100644 --- a/Tests/check_png_dos.py +++ b/Tests/check_png_dos.py @@ -1,69 +1,61 @@ -import unittest import zlib from io import BytesIO from PIL import Image, ImageFile, PngImagePlugin -from .helper import PillowTestCase - TEST_FILE = "Tests/images/png_decompression_dos.png" -class TestPngDos(PillowTestCase): - def test_ignore_dos_text(self): - ImageFile.LOAD_TRUNCATED_IMAGES = True +def test_ignore_dos_text(): + ImageFile.LOAD_TRUNCATED_IMAGES = True - try: - im = Image.open(TEST_FILE) - im.load() - finally: - ImageFile.LOAD_TRUNCATED_IMAGES = False + try: + im = Image.open(TEST_FILE) + im.load() + finally: + ImageFile.LOAD_TRUNCATED_IMAGES = False - for s in im.text.values(): - self.assertLess(len(s), 1024 * 1024, "Text chunk larger than 1M") + for s in im.text.values(): + assert len(s) < 1024 * 1024, "Text chunk larger than 1M" - for s in im.info.values(): - self.assertLess(len(s), 1024 * 1024, "Text chunk larger than 1M") - - def test_dos_text(self): - - try: - im = Image.open(TEST_FILE) - im.load() - except ValueError as msg: - self.assertTrue(msg, "Decompressed Data Too Large") - return - - for s in im.text.values(): - self.assertLess(len(s), 1024 * 1024, "Text chunk larger than 1M") - - def test_dos_total_memory(self): - im = Image.new("L", (1, 1)) - compressed_data = zlib.compress(b"a" * 1024 * 1023) - - info = PngImagePlugin.PngInfo() - - for x in range(64): - info.add_text("t%s" % x, compressed_data, zip=True) - info.add_itxt("i%s" % x, compressed_data, zip=True) - - b = BytesIO() - im.save(b, "PNG", pnginfo=info) - b.seek(0) - - try: - im2 = Image.open(b) - except ValueError as msg: - self.assertIn("Too much memory", msg) - return - - total_len = 0 - for txt in im2.text.values(): - total_len += len(txt) - self.assertLess( - total_len, 64 * 1024 * 1024, "Total text chunks greater than 64M" - ) + for s in im.info.values(): + assert len(s) < 1024 * 1024, "Text chunk larger than 1M" -if __name__ == "__main__": - unittest.main() +def test_dos_text(): + + try: + im = Image.open(TEST_FILE) + im.load() + except ValueError as msg: + assert msg, "Decompressed Data Too Large" + return + + for s in im.text.values(): + assert len(s) < 1024 * 1024, "Text chunk larger than 1M" + + +def test_dos_total_memory(): + im = Image.new("L", (1, 1)) + compressed_data = zlib.compress(b"a" * 1024 * 1023) + + info = PngImagePlugin.PngInfo() + + for x in range(64): + info.add_text(f"t{x}", compressed_data, zip=True) + info.add_itxt(f"i{x}", compressed_data, zip=True) + + b = BytesIO() + im.save(b, "PNG", pnginfo=info) + b.seek(0) + + try: + im2 = Image.open(b) + except ValueError as msg: + assert "Too much memory" in msg + return + + total_len = 0 + for txt in im2.text.values(): + total_len += len(txt) + assert total_len < 64 * 1024 * 1024, "Total text chunks greater than 64M" diff --git a/Tests/conftest.py b/Tests/conftest.py index 2b796148d..082f2f7c3 100644 --- a/Tests/conftest.py +++ b/Tests/conftest.py @@ -1,6 +1,7 @@ -def pytest_report_header(config): - import io +import io + +def pytest_report_header(config): try: from PIL import features @@ -8,4 +9,4 @@ def pytest_report_header(config): features.pilinfo(out=out, supported_formats=False) return out.getvalue() except Exception as e: - return "pytest_report_header failed: %s" % str(e) + return f"pytest_report_header failed: {e}" diff --git a/Tests/createfontdatachunk.py b/Tests/createfontdatachunk.py index c7055995e..011bb0bed 100755 --- a/Tests/createfontdatachunk.py +++ b/Tests/createfontdatachunk.py @@ -6,7 +6,7 @@ if __name__ == "__main__": # create font data chunk for embedding font = "Tests/images/courB08" print(" f._load_pilfont_data(") - print(" # %s" % os.path.basename(font)) + print(f" # {os.path.basename(font)}") print(" BytesIO(base64.decodestring(b'''") with open(font + ".pil", "rb") as fp: print(base64.b64encode(fp.read()).decode()) diff --git a/Tests/fonts/BungeeColor-Regular_colr_Windows.ttf b/Tests/fonts/BungeeColor-Regular_colr_Windows.ttf new file mode 100644 index 000000000..d8eabb3b6 Binary files /dev/null and b/Tests/fonts/BungeeColor-Regular_colr_Windows.ttf differ diff --git a/Tests/fonts/DejaVuSans-24-1-stripped.ttf b/Tests/fonts/DejaVuSans-24-1-stripped.ttf new file mode 100644 index 000000000..8eaf1ee08 Binary files /dev/null and b/Tests/fonts/DejaVuSans-24-1-stripped.ttf differ diff --git a/Tests/fonts/DejaVuSans-24-2-stripped.ttf b/Tests/fonts/DejaVuSans-24-2-stripped.ttf new file mode 100644 index 000000000..233667251 Binary files /dev/null and b/Tests/fonts/DejaVuSans-24-2-stripped.ttf differ diff --git a/Tests/fonts/DejaVuSans-24-4-stripped.ttf b/Tests/fonts/DejaVuSans-24-4-stripped.ttf new file mode 100644 index 000000000..9accc9ebc Binary files /dev/null and b/Tests/fonts/DejaVuSans-24-4-stripped.ttf differ diff --git a/Tests/fonts/DejaVuSans-24-8-stripped.ttf b/Tests/fonts/DejaVuSans-24-8-stripped.ttf new file mode 100644 index 000000000..0f9344267 Binary files /dev/null and b/Tests/fonts/DejaVuSans-24-8-stripped.ttf differ diff --git a/Tests/fonts/DejaVuSans-bitmap.ttf b/Tests/fonts/DejaVuSans-bitmap.ttf deleted file mode 100644 index 702cce37d..000000000 Binary files a/Tests/fonts/DejaVuSans-bitmap.ttf and /dev/null differ diff --git a/Tests/fonts/LICENSE.txt b/Tests/fonts/LICENSE.txt index 726d5d797..06eaa9a4e 100644 --- a/Tests/fonts/LICENSE.txt +++ b/Tests/fonts/LICENSE.txt @@ -1,12 +1,22 @@ NotoNastaliqUrdu-Regular.ttf and NotoSansSymbols-Regular.ttf, from https://github.com/googlei18n/noto-fonts +NotoSans-Regular.ttf, from https://www.google.com/get/noto/ NotoSansJP-Thin.otf, from https://www.google.com/get/noto/help/cjk/ +NotoColorEmoji.ttf, from https://github.com/googlefonts/noto-emoji AdobeVFPrototype.ttf, from https://github.com/adobe-fonts/adobe-variable-font-prototype TINY5x3GX.ttf, from http://velvetyne.fr/fonts/tiny ArefRuqaa-Regular.ttf, from https://github.com/google/fonts/tree/master/ofl/arefruqaa +ter-x20b.pcf, from http://terminus-font.sourceforge.net/ +BungeeColor-Regular_colr_Windows.ttf, from https://github.com/djrrb/bungee All of the above fonts are published under the SIL Open Font License (OFL) v1.1 (http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL), which allows you to copy, modify, and redistribute them if you need to. +FreeMono.ttf is licensed under GPLv3, with the GPL font exception. + +OpenSansCondensed-LightItalic.tt, from https://fonts.google.com/specimen/Open+Sans, under Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) + +DejaVuSans-24-{1,2,4,8}-stripped.ttf are based on DejaVuSans.ttf converted using FontForge to add bitmap strikes and keep only the ASCII range. + 10x20-ISO8859-1.pcf, from https://packages.ubuntu.com/xenial/xfonts-base diff --git a/Tests/fonts/NotoColorEmoji.ttf b/Tests/fonts/NotoColorEmoji.ttf new file mode 100644 index 000000000..ef7b72575 Binary files /dev/null and b/Tests/fonts/NotoColorEmoji.ttf differ diff --git a/Tests/fonts/NotoSans-Regular.ttf b/Tests/fonts/NotoSans-Regular.ttf new file mode 100644 index 000000000..a1b8994ed Binary files /dev/null and b/Tests/fonts/NotoSans-Regular.ttf differ diff --git a/Tests/fonts/OpenSansCondensed-LightItalic.ttf b/Tests/fonts/OpenSansCondensed-LightItalic.ttf new file mode 100644 index 000000000..b4ee4951f Binary files /dev/null and b/Tests/fonts/OpenSansCondensed-LightItalic.ttf differ diff --git a/Tests/fonts/ter-x20b-cp1250.pbm b/Tests/fonts/ter-x20b-cp1250.pbm new file mode 100644 index 000000000..fe7e2c4dc Binary files /dev/null and b/Tests/fonts/ter-x20b-cp1250.pbm differ diff --git a/Tests/fonts/ter-x20b-cp1250.pil b/Tests/fonts/ter-x20b-cp1250.pil new file mode 100644 index 000000000..4da49e5fd Binary files /dev/null and b/Tests/fonts/ter-x20b-cp1250.pil differ diff --git a/Tests/fonts/ter-x20b-iso8859-1.pbm b/Tests/fonts/ter-x20b-iso8859-1.pbm new file mode 100644 index 000000000..ffd840ae9 Binary files /dev/null and b/Tests/fonts/ter-x20b-iso8859-1.pbm differ diff --git a/Tests/fonts/ter-x20b-iso8859-1.pil b/Tests/fonts/ter-x20b-iso8859-1.pil new file mode 100644 index 000000000..14d6e8be7 Binary files /dev/null and b/Tests/fonts/ter-x20b-iso8859-1.pil differ diff --git a/Tests/fonts/ter-x20b-iso8859-2.pbm b/Tests/fonts/ter-x20b-iso8859-2.pbm new file mode 100644 index 000000000..ad5b3af8d Binary files /dev/null and b/Tests/fonts/ter-x20b-iso8859-2.pbm differ diff --git a/Tests/fonts/ter-x20b-iso8859-2.pil b/Tests/fonts/ter-x20b-iso8859-2.pil new file mode 100644 index 000000000..14d6e8be7 Binary files /dev/null and b/Tests/fonts/ter-x20b-iso8859-2.pil differ diff --git a/Tests/fonts/ter-x20b.pcf b/Tests/fonts/ter-x20b.pcf new file mode 100644 index 000000000..962bcca6a Binary files /dev/null and b/Tests/fonts/ter-x20b.pcf differ diff --git a/Tests/helper.py b/Tests/helper.py index 3b2341012..be3bdb76f 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -5,14 +5,15 @@ Helper functions. import logging import os import shutil -import subprocess import sys +import sysconfig import tempfile -import unittest from io import BytesIO import pytest -from PIL import Image, ImageMath +from packaging.version import parse as parse_version + +from PIL import Image, ImageMath, features logger = logging.getLogger(__name__) @@ -68,37 +69,31 @@ def convert_to_comparable(a, b): def assert_deep_equal(a, b, msg=None): try: - assert len(a) == len(b), msg or "got length {}, expected {}".format( - len(a), len(b) - ) + assert len(a) == len(b), msg or f"got length {len(a)}, expected {len(b)}" except Exception: assert a == b, msg def assert_image(im, mode, size, msg=None): if mode is not None: - assert im.mode == mode, msg or "got mode {!r}, expected {!r}".format( - im.mode, mode + assert im.mode == mode, ( + msg or f"got mode {repr(im.mode)}, expected {repr(mode)}" ) if size is not None: - assert im.size == size, msg or "got size {!r}, expected {!r}".format( - im.size, size + assert im.size == size, ( + msg or f"got size {repr(im.size)}, expected {repr(size)}" ) def assert_image_equal(a, b, msg=None): - assert a.mode == b.mode, msg or "got mode {!r}, expected {!r}".format( - a.mode, b.mode - ) - assert a.size == b.size, msg or "got size {!r}, expected {!r}".format( - a.size, b.size - ) + assert a.mode == b.mode, msg or f"got mode {repr(a.mode)}, expected {repr(b.mode)}" + assert a.size == b.size, msg or f"got size {repr(a.size)}, expected {repr(b.size)}" if a.tobytes() != b.tobytes(): if HAS_UPLOADER: try: url = test_image_results.upload(a, b) - logger.error("Url for test images: %s" % url) + logger.error(f"Url for test images: {url}") except Exception: pass @@ -113,12 +108,8 @@ def assert_image_equal_tofile(a, filename, msg=None, mode=None): def assert_image_similar(a, b, epsilon, msg=None): - assert a.mode == b.mode, msg or "got mode {!r}, expected {!r}".format( - a.mode, b.mode - ) - assert a.size == b.size, msg or "got size {!r}, expected {!r}".format( - a.size, b.size - ) + assert a.mode == b.mode, msg or f"got mode {repr(a.mode)}, expected {repr(b.mode)}" + assert a.size == b.size, msg or f"got size {repr(a.size)}, expected {repr(b.size)}" a, b = convert_to_comparable(a, b) @@ -130,13 +121,14 @@ def assert_image_similar(a, b, epsilon, msg=None): ave_diff = diff / (a.size[0] * a.size[1]) try: assert epsilon >= ave_diff, ( - msg or "" - ) + " average pixel value difference %.4f > epsilon %.4f" % (ave_diff, epsilon) + (msg or "") + + f" average pixel value difference {ave_diff:.4f} > epsilon {epsilon:.4f}" + ) except Exception as e: if HAS_UPLOADER: try: url = test_image_results.upload(a, b) - logger.error("Url for test images: %s" % url) + logger.error(f"Url for test images: {url}") except Exception: pass raise e @@ -166,42 +158,23 @@ def assert_tuple_approx_equal(actuals, targets, threshold, msg): assert value, msg + ": " + repr(actuals) + " != " + repr(targets) -def skip_known_bad_test(msg=None): - # Skip if PILLOW_RUN_KNOWN_BAD is not true in the environment. - if not os.environ.get("PILLOW_RUN_KNOWN_BAD", False): - pytest.skip(msg or "Known bad test") +def skip_unless_feature(feature): + reason = f"{feature} not available" + return pytest.mark.skipif(not features.check(feature), reason=reason) -class PillowTestCase(unittest.TestCase): - def delete_tempfile(self, path): - try: - os.remove(path) - except OSError: - pass # report? - - def tempfile(self, template): - assert template[:5] in ("temp.", "temp_") - fd, path = tempfile.mkstemp(template[4:], template[:4]) - os.close(fd) - - self.addCleanup(self.delete_tempfile, path) - return path - - def open_withImagemagick(self, f): - if not imagemagick_available(): - raise OSError() - - outfile = self.tempfile("temp.png") - rc = subprocess.call( - [IMCONVERT, f, outfile], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT - ) - if rc: - raise OSError - return Image.open(outfile) +def skip_unless_feature_version(feature, version_required, reason=None): + if not features.check(feature): + return pytest.mark.skip(f"{feature} not available") + if reason is None: + reason = f"{feature} is older than {version_required}" + version_required = parse_version(version_required) + version_available = parse_version(features.version(feature)) + return pytest.mark.skipif(version_available < version_required, reason=reason) -@unittest.skipIf(sys.platform.startswith("win32"), "requires Unix or macOS") -class PillowLeakTestCase(PillowTestCase): +@pytest.mark.skipif(sys.platform.startswith("win32"), reason="Requires Unix or macOS") +class PillowLeakTestCase: # requires unix/macOS iterations = 100 # count mem_limit = 512 # k @@ -214,7 +187,7 @@ class PillowLeakTestCase(PillowTestCase): :returns: memory usage in kilobytes """ - from resource import getrusage, RUSAGE_SELF + from resource import RUSAGE_SELF, getrusage mem = getrusage(RUSAGE_SELF).ru_maxrss if sys.platform == "darwin": @@ -234,8 +207,8 @@ class PillowLeakTestCase(PillowTestCase): for cycle in range(self.iterations): core() mem = self._get_mem_usage() - start_mem - msg = "memory usage limit exceeded in iteration %d" % cycle - self.assertLess(mem, self.mem_limit, msg) + msg = f"memory usage limit exceeded in iteration {cycle}" + assert mem < self.mem_limit, msg # helpers @@ -297,18 +270,20 @@ def on_github_actions(): def on_ci(): - # Travis and AppVeyor have "CI" - # Azure Pipelines has "TF_BUILD" - # GitHub Actions has "GITHUB_ACTIONS" - return ( - "CI" in os.environ or "TF_BUILD" in os.environ or "GITHUB_ACTIONS" in os.environ - ) + # GitHub Actions and AppVeyor have "CI" + return "CI" in os.environ def is_big_endian(): return sys.byteorder == "big" +def is_ppc64le(): + import platform + + return platform.machine() == "ppc64le" + + def is_win32(): return sys.platform.startswith("win32") @@ -317,6 +292,10 @@ def is_pypy(): return hasattr(sys, "pypy_translation_info") +def is_mingw(): + return sysconfig.get_platform() == "mingw" + + if sys.platform == "win32": IMCONVERT = os.environ.get("MAGICK_HOME", "") if IMCONVERT: diff --git a/Tests/images/00r0_gray_l.jp2 b/Tests/images/00r0_gray_l.jp2 new file mode 100644 index 000000000..28612238a Binary files /dev/null and b/Tests/images/00r0_gray_l.jp2 differ diff --git a/Tests/images/00r1_graya_la.jp2 b/Tests/images/00r1_graya_la.jp2 new file mode 100644 index 000000000..f3f840a08 Binary files /dev/null and b/Tests/images/00r1_graya_la.jp2 differ diff --git a/Tests/images/01r_00.pcx b/Tests/images/01r_00.pcx new file mode 100644 index 000000000..f40777ac5 Binary files /dev/null and b/Tests/images/01r_00.pcx differ diff --git a/Tests/images/DXGI_FORMAT_R8G8B8A8_UNORM_SRGB.dds b/Tests/images/DXGI_FORMAT_R8G8B8A8_UNORM_SRGB.dds new file mode 100644 index 000000000..1da9293de Binary files /dev/null and b/Tests/images/DXGI_FORMAT_R8G8B8A8_UNORM_SRGB.dds differ diff --git a/Tests/images/DXGI_FORMAT_R8G8B8A8_UNORM_SRGB.png b/Tests/images/DXGI_FORMAT_R8G8B8A8_UNORM_SRGB.png new file mode 100644 index 000000000..57177fe2b Binary files /dev/null and b/Tests/images/DXGI_FORMAT_R8G8B8A8_UNORM_SRGB.png differ diff --git a/Tests/images/apng/blend_op_over.png b/Tests/images/apng/blend_op_over.png new file mode 100644 index 000000000..3fe0f4ca7 Binary files /dev/null and b/Tests/images/apng/blend_op_over.png differ diff --git a/Tests/images/apng/blend_op_over_near_transparent.png b/Tests/images/apng/blend_op_over_near_transparent.png new file mode 100644 index 000000000..3ee5fe3bf Binary files /dev/null and b/Tests/images/apng/blend_op_over_near_transparent.png differ diff --git a/Tests/images/apng/blend_op_source_near_transparent.png b/Tests/images/apng/blend_op_source_near_transparent.png new file mode 100644 index 000000000..1af30f81f Binary files /dev/null and b/Tests/images/apng/blend_op_source_near_transparent.png differ diff --git a/Tests/images/apng/blend_op_source_solid.png b/Tests/images/apng/blend_op_source_solid.png new file mode 100644 index 000000000..d90c54967 Binary files /dev/null and b/Tests/images/apng/blend_op_source_solid.png differ diff --git a/Tests/images/apng/blend_op_source_transparent.png b/Tests/images/apng/blend_op_source_transparent.png new file mode 100644 index 000000000..0f290fd7f Binary files /dev/null and b/Tests/images/apng/blend_op_source_transparent.png differ diff --git a/Tests/images/apng/chunk_actl_after_idat.png b/Tests/images/apng/chunk_actl_after_idat.png new file mode 100644 index 000000000..296a29d4c Binary files /dev/null and b/Tests/images/apng/chunk_actl_after_idat.png differ diff --git a/Tests/images/apng/chunk_multi_actl.png b/Tests/images/apng/chunk_multi_actl.png new file mode 100644 index 000000000..213f88549 Binary files /dev/null and b/Tests/images/apng/chunk_multi_actl.png differ diff --git a/Tests/images/apng/chunk_no_actl.png b/Tests/images/apng/chunk_no_actl.png new file mode 100644 index 000000000..5b68c7b44 Binary files /dev/null and b/Tests/images/apng/chunk_no_actl.png differ diff --git a/Tests/images/apng/chunk_no_fctl.png b/Tests/images/apng/chunk_no_fctl.png new file mode 100644 index 000000000..58ca904ab Binary files /dev/null and b/Tests/images/apng/chunk_no_fctl.png differ diff --git a/Tests/images/apng/chunk_no_fdat.png b/Tests/images/apng/chunk_no_fdat.png new file mode 100644 index 000000000..af42766b5 Binary files /dev/null and b/Tests/images/apng/chunk_no_fdat.png differ diff --git a/Tests/images/apng/chunk_repeat_fctl.png b/Tests/images/apng/chunk_repeat_fctl.png new file mode 100644 index 000000000..a5779855f Binary files /dev/null and b/Tests/images/apng/chunk_repeat_fctl.png differ diff --git a/Tests/images/apng/delay.png b/Tests/images/apng/delay.png new file mode 100644 index 000000000..64cceaae8 Binary files /dev/null and b/Tests/images/apng/delay.png differ diff --git a/Tests/images/apng/delay_round.png b/Tests/images/apng/delay_round.png new file mode 100644 index 000000000..3f082665c Binary files /dev/null and b/Tests/images/apng/delay_round.png differ diff --git a/Tests/images/apng/delay_short_max.png b/Tests/images/apng/delay_short_max.png new file mode 100644 index 000000000..99d53b718 Binary files /dev/null and b/Tests/images/apng/delay_short_max.png differ diff --git a/Tests/images/apng/delay_zero_denom.png b/Tests/images/apng/delay_zero_denom.png new file mode 100644 index 000000000..bad60c767 Binary files /dev/null and b/Tests/images/apng/delay_zero_denom.png differ diff --git a/Tests/images/apng/delay_zero_numer.png b/Tests/images/apng/delay_zero_numer.png new file mode 100644 index 000000000..a029a959b Binary files /dev/null and b/Tests/images/apng/delay_zero_numer.png differ diff --git a/Tests/images/apng/dispose_op_background.png b/Tests/images/apng/dispose_op_background.png new file mode 100644 index 000000000..b63ebc0b3 Binary files /dev/null and b/Tests/images/apng/dispose_op_background.png differ diff --git a/Tests/images/apng/dispose_op_background_before_region.png b/Tests/images/apng/dispose_op_background_before_region.png new file mode 100644 index 000000000..427b829a0 Binary files /dev/null and b/Tests/images/apng/dispose_op_background_before_region.png differ diff --git a/Tests/images/apng/dispose_op_background_final.png b/Tests/images/apng/dispose_op_background_final.png new file mode 100644 index 000000000..77694ff1d Binary files /dev/null and b/Tests/images/apng/dispose_op_background_final.png differ diff --git a/Tests/images/apng/dispose_op_background_p_mode.png b/Tests/images/apng/dispose_op_background_p_mode.png new file mode 100644 index 000000000..e5fb4784d Binary files /dev/null and b/Tests/images/apng/dispose_op_background_p_mode.png differ diff --git a/Tests/images/apng/dispose_op_background_region.png b/Tests/images/apng/dispose_op_background_region.png new file mode 100644 index 000000000..05948d44a Binary files /dev/null and b/Tests/images/apng/dispose_op_background_region.png differ diff --git a/Tests/images/apng/dispose_op_none.png b/Tests/images/apng/dispose_op_none.png new file mode 100644 index 000000000..3094c1d23 Binary files /dev/null and b/Tests/images/apng/dispose_op_none.png differ diff --git a/Tests/images/apng/dispose_op_none_region.png b/Tests/images/apng/dispose_op_none_region.png new file mode 100644 index 000000000..4e1dbf77e Binary files /dev/null and b/Tests/images/apng/dispose_op_none_region.png differ diff --git a/Tests/images/apng/dispose_op_previous.png b/Tests/images/apng/dispose_op_previous.png new file mode 100644 index 000000000..1c15f132f Binary files /dev/null and b/Tests/images/apng/dispose_op_previous.png differ diff --git a/Tests/images/apng/dispose_op_previous_final.png b/Tests/images/apng/dispose_op_previous_final.png new file mode 100644 index 000000000..858f6f038 Binary files /dev/null and b/Tests/images/apng/dispose_op_previous_final.png differ diff --git a/Tests/images/apng/dispose_op_previous_first.png b/Tests/images/apng/dispose_op_previous_first.png new file mode 100644 index 000000000..3f9b3cfae Binary files /dev/null and b/Tests/images/apng/dispose_op_previous_first.png differ diff --git a/Tests/images/apng/dispose_op_previous_frame.png b/Tests/images/apng/dispose_op_previous_frame.png new file mode 100644 index 000000000..14168da89 Binary files /dev/null and b/Tests/images/apng/dispose_op_previous_frame.png differ diff --git a/Tests/images/apng/dispose_op_previous_region.png b/Tests/images/apng/dispose_op_previous_region.png new file mode 100644 index 000000000..f326afa5c Binary files /dev/null and b/Tests/images/apng/dispose_op_previous_region.png differ diff --git a/Tests/images/apng/fctl_actl.png b/Tests/images/apng/fctl_actl.png new file mode 100644 index 000000000..d0418ddd7 Binary files /dev/null and b/Tests/images/apng/fctl_actl.png differ diff --git a/Tests/images/apng/mode_16bit.png b/Tests/images/apng/mode_16bit.png new file mode 100644 index 000000000..1210e3737 Binary files /dev/null and b/Tests/images/apng/mode_16bit.png differ diff --git a/Tests/images/apng/mode_greyscale.png b/Tests/images/apng/mode_greyscale.png new file mode 100644 index 000000000..29ed7d1ea Binary files /dev/null and b/Tests/images/apng/mode_greyscale.png differ diff --git a/Tests/images/apng/mode_greyscale_alpha.png b/Tests/images/apng/mode_greyscale_alpha.png new file mode 100644 index 000000000..f9307f635 Binary files /dev/null and b/Tests/images/apng/mode_greyscale_alpha.png differ diff --git a/Tests/images/apng/mode_palette.png b/Tests/images/apng/mode_palette.png new file mode 100644 index 000000000..11ccfb6cb Binary files /dev/null and b/Tests/images/apng/mode_palette.png differ diff --git a/Tests/images/apng/mode_palette_1bit_alpha.png b/Tests/images/apng/mode_palette_1bit_alpha.png new file mode 100644 index 000000000..e95425ac1 Binary files /dev/null and b/Tests/images/apng/mode_palette_1bit_alpha.png differ diff --git a/Tests/images/apng/mode_palette_alpha.png b/Tests/images/apng/mode_palette_alpha.png new file mode 100644 index 000000000..f3c4c9f9e Binary files /dev/null and b/Tests/images/apng/mode_palette_alpha.png differ diff --git a/Tests/images/apng/num_plays.png b/Tests/images/apng/num_plays.png new file mode 100644 index 000000000..4d76802e4 Binary files /dev/null and b/Tests/images/apng/num_plays.png differ diff --git a/Tests/images/apng/num_plays_1.png b/Tests/images/apng/num_plays_1.png new file mode 100644 index 000000000..fb2539430 Binary files /dev/null and b/Tests/images/apng/num_plays_1.png differ diff --git a/Tests/images/apng/sequence_fdat_fctl.png b/Tests/images/apng/sequence_fdat_fctl.png new file mode 100644 index 000000000..29ac75e16 Binary files /dev/null and b/Tests/images/apng/sequence_fdat_fctl.png differ diff --git a/Tests/images/apng/sequence_gap.png b/Tests/images/apng/sequence_gap.png new file mode 100644 index 000000000..25dd9bcd8 Binary files /dev/null and b/Tests/images/apng/sequence_gap.png differ diff --git a/Tests/images/apng/sequence_reorder.png b/Tests/images/apng/sequence_reorder.png new file mode 100644 index 000000000..dc78e9bb1 Binary files /dev/null and b/Tests/images/apng/sequence_reorder.png differ diff --git a/Tests/images/apng/sequence_reorder_chunk.png b/Tests/images/apng/sequence_reorder_chunk.png new file mode 100644 index 000000000..5d951ffe2 Binary files /dev/null and b/Tests/images/apng/sequence_reorder_chunk.png differ diff --git a/Tests/images/apng/sequence_repeat.png b/Tests/images/apng/sequence_repeat.png new file mode 100644 index 000000000..d5cf83f9f Binary files /dev/null and b/Tests/images/apng/sequence_repeat.png differ diff --git a/Tests/images/apng/sequence_repeat_chunk.png b/Tests/images/apng/sequence_repeat_chunk.png new file mode 100644 index 000000000..27d1d3eb5 Binary files /dev/null and b/Tests/images/apng/sequence_repeat_chunk.png differ diff --git a/Tests/images/apng/sequence_start.png b/Tests/images/apng/sequence_start.png new file mode 100644 index 000000000..5e040743a Binary files /dev/null and b/Tests/images/apng/sequence_start.png differ diff --git a/Tests/images/apng/single_frame.png b/Tests/images/apng/single_frame.png new file mode 100644 index 000000000..0cd5bea85 Binary files /dev/null and b/Tests/images/apng/single_frame.png differ diff --git a/Tests/images/apng/single_frame_default.png b/Tests/images/apng/single_frame_default.png new file mode 100644 index 000000000..db7581fbd Binary files /dev/null and b/Tests/images/apng/single_frame_default.png differ diff --git a/Tests/images/apng/split_fdat.png b/Tests/images/apng/split_fdat.png new file mode 100644 index 000000000..2dc58b929 Binary files /dev/null and b/Tests/images/apng/split_fdat.png differ diff --git a/Tests/images/apng/split_fdat_zero_chunk.png b/Tests/images/apng/split_fdat_zero_chunk.png new file mode 100644 index 000000000..14a76d9d6 Binary files /dev/null and b/Tests/images/apng/split_fdat_zero_chunk.png differ diff --git a/Tests/images/apng/syntax_num_frames_high.png b/Tests/images/apng/syntax_num_frames_high.png new file mode 100644 index 000000000..bba9cdfd5 Binary files /dev/null and b/Tests/images/apng/syntax_num_frames_high.png differ diff --git a/Tests/images/apng/syntax_num_frames_invalid.png b/Tests/images/apng/syntax_num_frames_invalid.png new file mode 100644 index 000000000..ca7b13ab8 Binary files /dev/null and b/Tests/images/apng/syntax_num_frames_invalid.png differ diff --git a/Tests/images/apng/syntax_num_frames_low.png b/Tests/images/apng/syntax_num_frames_low.png new file mode 100644 index 000000000..6f895f91d Binary files /dev/null and b/Tests/images/apng/syntax_num_frames_low.png differ diff --git a/Tests/images/apng/syntax_num_frames_zero.png b/Tests/images/apng/syntax_num_frames_zero.png new file mode 100644 index 000000000..0cb7ea36e Binary files /dev/null and b/Tests/images/apng/syntax_num_frames_zero.png differ diff --git a/Tests/images/apng/syntax_num_frames_zero_default.png b/Tests/images/apng/syntax_num_frames_zero_default.png new file mode 100644 index 000000000..89f2b75e2 Binary files /dev/null and b/Tests/images/apng/syntax_num_frames_zero_default.png differ diff --git a/Tests/images/argb-32bpp_MipMaps-1.dds b/Tests/images/argb-32bpp_MipMaps-1.dds new file mode 100644 index 000000000..d1d1998b1 Binary files /dev/null and b/Tests/images/argb-32bpp_MipMaps-1.dds differ diff --git a/Tests/images/argb-32bpp_MipMaps-1.png b/Tests/images/argb-32bpp_MipMaps-1.png new file mode 100644 index 000000000..3570ccf35 Binary files /dev/null and b/Tests/images/argb-32bpp_MipMaps-1.png differ diff --git a/Tests/images/bitmap_font_1_basic.png b/Tests/images/bitmap_font_1_basic.png new file mode 100644 index 000000000..01a05606c Binary files /dev/null and b/Tests/images/bitmap_font_1_basic.png differ diff --git a/Tests/images/bitmap_font_1_raqm.png b/Tests/images/bitmap_font_1_raqm.png new file mode 100644 index 000000000..560efb685 Binary files /dev/null and b/Tests/images/bitmap_font_1_raqm.png differ diff --git a/Tests/images/bitmap_font_2_basic.png b/Tests/images/bitmap_font_2_basic.png new file mode 100644 index 000000000..44d137dd6 Binary files /dev/null and b/Tests/images/bitmap_font_2_basic.png differ diff --git a/Tests/images/bitmap_font_2_raqm.png b/Tests/images/bitmap_font_2_raqm.png new file mode 100644 index 000000000..7a40bd6c2 Binary files /dev/null and b/Tests/images/bitmap_font_2_raqm.png differ diff --git a/Tests/images/bitmap_font_4_basic.png b/Tests/images/bitmap_font_4_basic.png new file mode 100644 index 000000000..e79d86aa8 Binary files /dev/null and b/Tests/images/bitmap_font_4_basic.png differ diff --git a/Tests/images/bitmap_font_4_raqm.png b/Tests/images/bitmap_font_4_raqm.png new file mode 100644 index 000000000..d98a3bc3e Binary files /dev/null and b/Tests/images/bitmap_font_4_raqm.png differ diff --git a/Tests/images/bitmap_font_8_basic.png b/Tests/images/bitmap_font_8_basic.png new file mode 100644 index 000000000..15a7c9809 Binary files /dev/null and b/Tests/images/bitmap_font_8_basic.png differ diff --git a/Tests/images/bitmap_font_8_raqm.png b/Tests/images/bitmap_font_8_raqm.png new file mode 100644 index 000000000..1ad088c93 Binary files /dev/null and b/Tests/images/bitmap_font_8_raqm.png differ diff --git a/Tests/images/cbdt_notocoloremoji.png b/Tests/images/cbdt_notocoloremoji.png new file mode 100644 index 000000000..1da12fba1 Binary files /dev/null and b/Tests/images/cbdt_notocoloremoji.png differ diff --git a/Tests/images/cbdt_notocoloremoji_mask.png b/Tests/images/cbdt_notocoloremoji_mask.png new file mode 100644 index 000000000..6d036a0b6 Binary files /dev/null and b/Tests/images/cbdt_notocoloremoji_mask.png differ diff --git a/Tests/images/colr_bungee.png b/Tests/images/colr_bungee.png new file mode 100644 index 000000000..b10a60be0 Binary files /dev/null and b/Tests/images/colr_bungee.png differ diff --git a/Tests/images/colr_bungee_mask.png b/Tests/images/colr_bungee_mask.png new file mode 100644 index 000000000..f13e17677 Binary files /dev/null and b/Tests/images/colr_bungee_mask.png differ diff --git a/Tests/images/crash-2020-10-test.tif b/Tests/images/crash-2020-10-test.tif new file mode 100644 index 000000000..958cdde22 Binary files /dev/null and b/Tests/images/crash-2020-10-test.tif differ diff --git a/Tests/images/crash-6b7f2244da6d0ae297ee0754a424213444e92778.sgi b/Tests/images/crash-6b7f2244da6d0ae297ee0754a424213444e92778.sgi new file mode 100644 index 000000000..74396935b Binary files /dev/null and b/Tests/images/crash-6b7f2244da6d0ae297ee0754a424213444e92778.sgi differ diff --git a/Tests/images/dispose_none_load_end.gif b/Tests/images/dispose_none_load_end.gif new file mode 100644 index 000000000..3c94eb1b6 Binary files /dev/null and b/Tests/images/dispose_none_load_end.gif differ diff --git a/Tests/images/dispose_none_load_end_second.gif b/Tests/images/dispose_none_load_end_second.gif new file mode 100644 index 000000000..5d8462ceb Binary files /dev/null and b/Tests/images/dispose_none_load_end_second.gif differ diff --git a/Tests/images/empty_gps_ifd.jpg b/Tests/images/empty_gps_ifd.jpg new file mode 100644 index 000000000..28f180b87 Binary files /dev/null and b/Tests/images/empty_gps_ifd.jpg differ diff --git a/Tests/images/exif_imagemagick.png b/Tests/images/exif_imagemagick.png new file mode 100644 index 000000000..6f59224c8 Binary files /dev/null and b/Tests/images/exif_imagemagick.png differ diff --git a/Tests/images/exif_text.png b/Tests/images/exif_text.png new file mode 100644 index 000000000..e2d8dc0ff Binary files /dev/null and b/Tests/images/exif_text.png differ diff --git a/Tests/images/fli_oob/02r/02r00.fli b/Tests/images/fli_oob/02r/02r00.fli new file mode 100644 index 000000000..eac0e4304 Binary files /dev/null and b/Tests/images/fli_oob/02r/02r00.fli differ diff --git a/Tests/images/fli_oob/02r/notes b/Tests/images/fli_oob/02r/notes new file mode 100644 index 000000000..49f92b19b --- /dev/null +++ b/Tests/images/fli_oob/02r/notes @@ -0,0 +1 @@ +Is this because a file-originating field is being interpreted as a *signed* int32, allowing it to provide negative values for 'advance'? diff --git a/Tests/images/fli_oob/02r/others/02r01.fli b/Tests/images/fli_oob/02r/others/02r01.fli new file mode 100644 index 000000000..3a5864c84 Binary files /dev/null and b/Tests/images/fli_oob/02r/others/02r01.fli differ diff --git a/Tests/images/fli_oob/02r/others/02r02.fli b/Tests/images/fli_oob/02r/others/02r02.fli new file mode 100644 index 000000000..2b3d15b55 Binary files /dev/null and b/Tests/images/fli_oob/02r/others/02r02.fli differ diff --git a/Tests/images/fli_oob/02r/others/02r03.fli b/Tests/images/fli_oob/02r/others/02r03.fli new file mode 100644 index 000000000..a63172132 Binary files /dev/null and b/Tests/images/fli_oob/02r/others/02r03.fli differ diff --git a/Tests/images/fli_oob/02r/others/02r04.fli b/Tests/images/fli_oob/02r/others/02r04.fli new file mode 100644 index 000000000..4c17cbb3d Binary files /dev/null and b/Tests/images/fli_oob/02r/others/02r04.fli differ diff --git a/Tests/images/fli_oob/02r/reproducing b/Tests/images/fli_oob/02r/reproducing new file mode 100644 index 000000000..3286d94f1 --- /dev/null +++ b/Tests/images/fli_oob/02r/reproducing @@ -0,0 +1 @@ +Image.open(...).seek(212) diff --git a/Tests/images/fli_oob/03r/03r00.fli b/Tests/images/fli_oob/03r/03r00.fli new file mode 100644 index 000000000..7972880ce Binary files /dev/null and b/Tests/images/fli_oob/03r/03r00.fli differ diff --git a/Tests/images/fli_oob/03r/notes b/Tests/images/fli_oob/03r/notes new file mode 100644 index 000000000..d75605cea --- /dev/null +++ b/Tests/images/fli_oob/03r/notes @@ -0,0 +1 @@ +ridiculous bytes value passed to ImagingFliDecode diff --git a/Tests/images/fli_oob/03r/others/03r01.fli b/Tests/images/fli_oob/03r/others/03r01.fli new file mode 100644 index 000000000..1102c69ca Binary files /dev/null and b/Tests/images/fli_oob/03r/others/03r01.fli differ diff --git a/Tests/images/fli_oob/03r/others/03r02.fli b/Tests/images/fli_oob/03r/others/03r02.fli new file mode 100644 index 000000000..d30326fe0 Binary files /dev/null and b/Tests/images/fli_oob/03r/others/03r02.fli differ diff --git a/Tests/images/fli_oob/03r/others/03r03.fli b/Tests/images/fli_oob/03r/others/03r03.fli new file mode 100644 index 000000000..7f3db178e Binary files /dev/null and b/Tests/images/fli_oob/03r/others/03r03.fli differ diff --git a/Tests/images/fli_oob/03r/others/03r04.fli b/Tests/images/fli_oob/03r/others/03r04.fli new file mode 100644 index 000000000..f05375e84 Binary files /dev/null and b/Tests/images/fli_oob/03r/others/03r04.fli differ diff --git a/Tests/images/fli_oob/03r/others/03r05.fli b/Tests/images/fli_oob/03r/others/03r05.fli new file mode 100644 index 000000000..037944324 Binary files /dev/null and b/Tests/images/fli_oob/03r/others/03r05.fli differ diff --git a/Tests/images/fli_oob/03r/others/03r06.fli b/Tests/images/fli_oob/03r/others/03r06.fli new file mode 100644 index 000000000..1527cbf91 Binary files /dev/null and b/Tests/images/fli_oob/03r/others/03r06.fli differ diff --git a/Tests/images/fli_oob/03r/others/03r07.fli b/Tests/images/fli_oob/03r/others/03r07.fli new file mode 100644 index 000000000..c9dea4135 Binary files /dev/null and b/Tests/images/fli_oob/03r/others/03r07.fli differ diff --git a/Tests/images/fli_oob/03r/others/03r08.fli b/Tests/images/fli_oob/03r/others/03r08.fli new file mode 100644 index 000000000..698101443 Binary files /dev/null and b/Tests/images/fli_oob/03r/others/03r08.fli differ diff --git a/Tests/images/fli_oob/03r/others/03r09.fli b/Tests/images/fli_oob/03r/others/03r09.fli new file mode 100644 index 000000000..12058480a Binary files /dev/null and b/Tests/images/fli_oob/03r/others/03r09.fli differ diff --git a/Tests/images/fli_oob/03r/others/03r10.fli b/Tests/images/fli_oob/03r/others/03r10.fli new file mode 100644 index 000000000..448b0a812 Binary files /dev/null and b/Tests/images/fli_oob/03r/others/03r10.fli differ diff --git a/Tests/images/fli_oob/03r/others/03r11.fli b/Tests/images/fli_oob/03r/others/03r11.fli new file mode 100644 index 000000000..db1b5fe58 Binary files /dev/null and b/Tests/images/fli_oob/03r/others/03r11.fli differ diff --git a/Tests/images/fli_oob/03r/reproducing b/Tests/images/fli_oob/03r/reproducing new file mode 100644 index 000000000..145b8b074 --- /dev/null +++ b/Tests/images/fli_oob/03r/reproducing @@ -0,0 +1 @@ +im = Image.open(d); im.seek(0); im.getdata() diff --git a/Tests/images/fli_oob/04r/04r00.fli b/Tests/images/fli_oob/04r/04r00.fli new file mode 100644 index 000000000..c4e416f39 Binary files /dev/null and b/Tests/images/fli_oob/04r/04r00.fli differ diff --git a/Tests/images/fli_oob/04r/initial.fli b/Tests/images/fli_oob/04r/initial.fli new file mode 100644 index 000000000..5a8659f7c Binary files /dev/null and b/Tests/images/fli_oob/04r/initial.fli differ diff --git a/Tests/images/fli_oob/04r/notes b/Tests/images/fli_oob/04r/notes new file mode 100644 index 000000000..7922e0ba8 --- /dev/null +++ b/Tests/images/fli_oob/04r/notes @@ -0,0 +1 @@ +failure to check input buffer (`data`) boundaries in BRUN chunk diff --git a/Tests/images/fli_oob/04r/others/04r01.fli b/Tests/images/fli_oob/04r/others/04r01.fli new file mode 100644 index 000000000..af968970b Binary files /dev/null and b/Tests/images/fli_oob/04r/others/04r01.fli differ diff --git a/Tests/images/fli_oob/04r/others/04r02.fli b/Tests/images/fli_oob/04r/others/04r02.fli new file mode 100644 index 000000000..ae027fc11 Binary files /dev/null and b/Tests/images/fli_oob/04r/others/04r02.fli differ diff --git a/Tests/images/fli_oob/04r/others/04r03.fli b/Tests/images/fli_oob/04r/others/04r03.fli new file mode 100644 index 000000000..ab92f4b6a Binary files /dev/null and b/Tests/images/fli_oob/04r/others/04r03.fli differ diff --git a/Tests/images/fli_oob/04r/others/04r04.fli b/Tests/images/fli_oob/04r/others/04r04.fli new file mode 100644 index 000000000..533ffa027 Binary files /dev/null and b/Tests/images/fli_oob/04r/others/04r04.fli differ diff --git a/Tests/images/fli_oob/04r/others/04r05.fli b/Tests/images/fli_oob/04r/others/04r05.fli new file mode 100644 index 000000000..b07ef6496 Binary files /dev/null and b/Tests/images/fli_oob/04r/others/04r05.fli differ diff --git a/Tests/images/fli_oob/04r/reproducing b/Tests/images/fli_oob/04r/reproducing new file mode 100644 index 000000000..145b8b074 --- /dev/null +++ b/Tests/images/fli_oob/04r/reproducing @@ -0,0 +1 @@ +im = Image.open(d); im.seek(0); im.getdata() diff --git a/Tests/images/fli_oob/05r/05r00.fli b/Tests/images/fli_oob/05r/05r00.fli new file mode 100644 index 000000000..dff5a01e8 Binary files /dev/null and b/Tests/images/fli_oob/05r/05r00.fli differ diff --git a/Tests/images/fli_oob/05r/notes b/Tests/images/fli_oob/05r/notes new file mode 100644 index 000000000..bec9db779 --- /dev/null +++ b/Tests/images/fli_oob/05r/notes @@ -0,0 +1 @@ +failure to check input buffer (`data`) boundaries in LC chunk diff --git a/Tests/images/fli_oob/05r/others/05r01.fli b/Tests/images/fli_oob/05r/others/05r01.fli new file mode 100644 index 000000000..1ad3444fe Binary files /dev/null and b/Tests/images/fli_oob/05r/others/05r01.fli differ diff --git a/Tests/images/fli_oob/05r/others/05r02.fli b/Tests/images/fli_oob/05r/others/05r02.fli new file mode 100644 index 000000000..cd6429884 Binary files /dev/null and b/Tests/images/fli_oob/05r/others/05r02.fli differ diff --git a/Tests/images/fli_oob/05r/others/05r03.fli b/Tests/images/fli_oob/05r/others/05r03.fli new file mode 100644 index 000000000..2a4be914c Binary files /dev/null and b/Tests/images/fli_oob/05r/others/05r03.fli differ diff --git a/Tests/images/fli_oob/05r/others/05r04.fli b/Tests/images/fli_oob/05r/others/05r04.fli new file mode 100644 index 000000000..0b547c7e2 Binary files /dev/null and b/Tests/images/fli_oob/05r/others/05r04.fli differ diff --git a/Tests/images/fli_oob/05r/others/05r05.fli b/Tests/images/fli_oob/05r/others/05r05.fli new file mode 100644 index 000000000..0bf775230 Binary files /dev/null and b/Tests/images/fli_oob/05r/others/05r05.fli differ diff --git a/Tests/images/fli_oob/05r/others/05r06.fli b/Tests/images/fli_oob/05r/others/05r06.fli new file mode 100644 index 000000000..c35b8e232 Binary files /dev/null and b/Tests/images/fli_oob/05r/others/05r06.fli differ diff --git a/Tests/images/fli_oob/05r/others/05r07.fli b/Tests/images/fli_oob/05r/others/05r07.fli new file mode 100644 index 000000000..b99ce01b3 Binary files /dev/null and b/Tests/images/fli_oob/05r/others/05r07.fli differ diff --git a/Tests/images/fli_oob/05r/reproducing b/Tests/images/fli_oob/05r/reproducing new file mode 100644 index 000000000..145b8b074 --- /dev/null +++ b/Tests/images/fli_oob/05r/reproducing @@ -0,0 +1 @@ +im = Image.open(d); im.seek(0); im.getdata() diff --git a/Tests/images/fli_oob/06r/06r00.fli b/Tests/images/fli_oob/06r/06r00.fli new file mode 100644 index 000000000..9189d6ed0 Binary files /dev/null and b/Tests/images/fli_oob/06r/06r00.fli differ diff --git a/Tests/images/fli_oob/06r/notes b/Tests/images/fli_oob/06r/notes new file mode 100644 index 000000000..397ad4748 --- /dev/null +++ b/Tests/images/fli_oob/06r/notes @@ -0,0 +1 @@ +failure to check input buffer (`data`) boundaries in SS2 chunk diff --git a/Tests/images/fli_oob/06r/others/06r01.fli b/Tests/images/fli_oob/06r/others/06r01.fli new file mode 100644 index 000000000..24a99dacc Binary files /dev/null and b/Tests/images/fli_oob/06r/others/06r01.fli differ diff --git a/Tests/images/fli_oob/06r/others/06r02.fli b/Tests/images/fli_oob/06r/others/06r02.fli new file mode 100644 index 000000000..02067a32c Binary files /dev/null and b/Tests/images/fli_oob/06r/others/06r02.fli differ diff --git a/Tests/images/fli_oob/06r/others/06r03.fli b/Tests/images/fli_oob/06r/others/06r03.fli new file mode 100644 index 000000000..649668c0a Binary files /dev/null and b/Tests/images/fli_oob/06r/others/06r03.fli differ diff --git a/Tests/images/fli_oob/06r/others/06r04.fli b/Tests/images/fli_oob/06r/others/06r04.fli new file mode 100644 index 000000000..bff28ccfc Binary files /dev/null and b/Tests/images/fli_oob/06r/others/06r04.fli differ diff --git a/Tests/images/fli_oob/06r/reproducing b/Tests/images/fli_oob/06r/reproducing new file mode 100644 index 000000000..145b8b074 --- /dev/null +++ b/Tests/images/fli_oob/06r/reproducing @@ -0,0 +1 @@ +im = Image.open(d); im.seek(0); im.getdata() diff --git a/Tests/images/fli_oob/patch0/000000 b/Tests/images/fli_oob/patch0/000000 new file mode 100644 index 000000000..e074e4a76 Binary files /dev/null and b/Tests/images/fli_oob/patch0/000000 differ diff --git a/Tests/images/fli_oob/patch0/000001 b/Tests/images/fli_oob/patch0/000001 new file mode 100644 index 000000000..6cfd7f647 Binary files /dev/null and b/Tests/images/fli_oob/patch0/000001 differ diff --git a/Tests/images/fli_oob/patch0/000002 b/Tests/images/fli_oob/patch0/000002 new file mode 100644 index 000000000..ff5a6b63b Binary files /dev/null and b/Tests/images/fli_oob/patch0/000002 differ diff --git a/Tests/images/fli_oob/patch0/000003 b/Tests/images/fli_oob/patch0/000003 new file mode 100644 index 000000000..12c15b43e Binary files /dev/null and b/Tests/images/fli_oob/patch0/000003 differ diff --git a/Tests/images/hopper_16bit_qtables.jpg b/Tests/images/hopper_16bit_qtables.jpg new file mode 100644 index 000000000..88abef943 Binary files /dev/null and b/Tests/images/hopper_16bit_qtables.jpg differ diff --git a/Tests/images/hopper_long_name.im b/Tests/images/hopper_long_name.im new file mode 100644 index 000000000..ff45b7c75 Binary files /dev/null and b/Tests/images/hopper_long_name.im differ diff --git a/Tests/images/icc-after-SOF.jpg b/Tests/images/icc-after-SOF.jpg new file mode 100644 index 000000000..a284a2298 Binary files /dev/null and b/Tests/images/icc-after-SOF.jpg differ diff --git a/Tests/images/ifd_tag_type.tiff b/Tests/images/ifd_tag_type.tiff new file mode 100644 index 000000000..316d2089e Binary files /dev/null and b/Tests/images/ifd_tag_type.tiff differ diff --git a/Tests/images/imagedraw_arc.png b/Tests/images/imagedraw_arc.png index b09774389..967e214d9 100644 Binary files a/Tests/images/imagedraw_arc.png and b/Tests/images/imagedraw_arc.png differ diff --git a/Tests/images/imagedraw_arc_end_le_start.png b/Tests/images/imagedraw_arc_end_le_start.png index aee48e1c6..191cc0b3a 100644 Binary files a/Tests/images/imagedraw_arc_end_le_start.png and b/Tests/images/imagedraw_arc_end_le_start.png differ diff --git a/Tests/images/imagedraw_arc_high.png b/Tests/images/imagedraw_arc_high.png new file mode 100644 index 000000000..e3fb66cd0 Binary files /dev/null and b/Tests/images/imagedraw_arc_high.png differ diff --git a/Tests/images/imagedraw_arc_no_loops.png b/Tests/images/imagedraw_arc_no_loops.png index e45ad57a5..03bbd4b43 100644 Binary files a/Tests/images/imagedraw_arc_no_loops.png and b/Tests/images/imagedraw_arc_no_loops.png differ diff --git a/Tests/images/imagedraw_arc_width.png b/Tests/images/imagedraw_arc_width.png index ff3f1f0b2..70dae7d5f 100644 Binary files a/Tests/images/imagedraw_arc_width.png and b/Tests/images/imagedraw_arc_width.png differ diff --git a/Tests/images/imagedraw_arc_width_fill.png b/Tests/images/imagedraw_arc_width_fill.png index 9572a6059..6c135ab76 100644 Binary files a/Tests/images/imagedraw_arc_width_fill.png and b/Tests/images/imagedraw_arc_width_fill.png differ diff --git a/Tests/images/imagedraw_arc_width_non_whole_angle.png b/Tests/images/imagedraw_arc_width_non_whole_angle.png index 1fb9a3c86..f54eb1c29 100644 Binary files a/Tests/images/imagedraw_arc_width_non_whole_angle.png and b/Tests/images/imagedraw_arc_width_non_whole_angle.png differ diff --git a/Tests/images/imagedraw_arc_width_pieslice.png b/Tests/images/imagedraw_arc_width_pieslice.png index 950d95dd6..e1aa95e88 100644 Binary files a/Tests/images/imagedraw_arc_width_pieslice.png and b/Tests/images/imagedraw_arc_width_pieslice.png differ diff --git a/Tests/images/imagedraw_chord_L.png b/Tests/images/imagedraw_chord_L.png index a5a0078d0..6c89da9ea 100644 Binary files a/Tests/images/imagedraw_chord_L.png and b/Tests/images/imagedraw_chord_L.png differ diff --git a/Tests/images/imagedraw_chord_RGB.png b/Tests/images/imagedraw_chord_RGB.png index af6fc7660..d4592cda1 100644 Binary files a/Tests/images/imagedraw_chord_RGB.png and b/Tests/images/imagedraw_chord_RGB.png differ diff --git a/Tests/images/imagedraw_chord_too_fat.png b/Tests/images/imagedraw_chord_too_fat.png new file mode 100644 index 000000000..2021202fe Binary files /dev/null and b/Tests/images/imagedraw_chord_too_fat.png differ diff --git a/Tests/images/imagedraw_chord_width.png b/Tests/images/imagedraw_chord_width.png index 33a59b487..04d3dadf8 100644 Binary files a/Tests/images/imagedraw_chord_width.png and b/Tests/images/imagedraw_chord_width.png differ diff --git a/Tests/images/imagedraw_chord_width_fill.png b/Tests/images/imagedraw_chord_width_fill.png index 809c3ea1c..77475d266 100644 Binary files a/Tests/images/imagedraw_chord_width_fill.png and b/Tests/images/imagedraw_chord_width_fill.png differ diff --git a/Tests/images/imagedraw_chord_zero_width.png b/Tests/images/imagedraw_chord_zero_width.png new file mode 100644 index 000000000..c8ebe3917 Binary files /dev/null and b/Tests/images/imagedraw_chord_zero_width.png differ diff --git a/Tests/images/imagedraw_ellipse_L.png b/Tests/images/imagedraw_ellipse_L.png index e47e6e441..d5959cc08 100644 Binary files a/Tests/images/imagedraw_ellipse_L.png and b/Tests/images/imagedraw_ellipse_L.png differ diff --git a/Tests/images/imagedraw_ellipse_RGB.png b/Tests/images/imagedraw_ellipse_RGB.png index b52b12802..1d310ce11 100644 Binary files a/Tests/images/imagedraw_ellipse_RGB.png and b/Tests/images/imagedraw_ellipse_RGB.png differ diff --git a/Tests/images/imagedraw_ellipse_edge.png b/Tests/images/imagedraw_ellipse_edge.png index 25a95a601..a2235115a 100644 Binary files a/Tests/images/imagedraw_ellipse_edge.png and b/Tests/images/imagedraw_ellipse_edge.png differ diff --git a/Tests/images/imagedraw_ellipse_translucent.png b/Tests/images/imagedraw_ellipse_translucent.png new file mode 100644 index 000000000..964dce678 Binary files /dev/null and b/Tests/images/imagedraw_ellipse_translucent.png differ diff --git a/Tests/images/imagedraw_ellipse_various_sizes.png b/Tests/images/imagedraw_ellipse_various_sizes.png new file mode 100644 index 000000000..11a1be6fa Binary files /dev/null and b/Tests/images/imagedraw_ellipse_various_sizes.png differ diff --git a/Tests/images/imagedraw_ellipse_various_sizes_filled.png b/Tests/images/imagedraw_ellipse_various_sizes_filled.png new file mode 100644 index 000000000..d71e175b8 Binary files /dev/null and b/Tests/images/imagedraw_ellipse_various_sizes_filled.png differ diff --git a/Tests/images/imagedraw_ellipse_width.png b/Tests/images/imagedraw_ellipse_width.png index ec0ca6731..54cfc291c 100644 Binary files a/Tests/images/imagedraw_ellipse_width.png and b/Tests/images/imagedraw_ellipse_width.png differ diff --git a/Tests/images/imagedraw_ellipse_width_fill.png b/Tests/images/imagedraw_ellipse_width_fill.png index 9b7be6029..4a1edc379 100644 Binary files a/Tests/images/imagedraw_ellipse_width_fill.png and b/Tests/images/imagedraw_ellipse_width_fill.png differ diff --git a/Tests/images/imagedraw_ellipse_width_large.png b/Tests/images/imagedraw_ellipse_width_large.png index 9d3c3326b..e95186019 100644 Binary files a/Tests/images/imagedraw_ellipse_width_large.png and b/Tests/images/imagedraw_ellipse_width_large.png differ diff --git a/Tests/images/imagedraw_ellipse_zero_width.png b/Tests/images/imagedraw_ellipse_zero_width.png new file mode 100644 index 000000000..6661b7d99 Binary files /dev/null and b/Tests/images/imagedraw_ellipse_zero_width.png differ diff --git a/Tests/images/imagedraw_outline_chord_RGB.png b/Tests/images/imagedraw_outline_chord_RGB.png index 9e9cb5af0..3c71312c7 100644 Binary files a/Tests/images/imagedraw_outline_chord_RGB.png and b/Tests/images/imagedraw_outline_chord_RGB.png differ diff --git a/Tests/images/imagedraw_pieslice.png b/Tests/images/imagedraw_pieslice.png index 2f8c09191..41c786e77 100644 Binary files a/Tests/images/imagedraw_pieslice.png and b/Tests/images/imagedraw_pieslice.png differ diff --git a/Tests/images/imagedraw_pieslice_wide.png b/Tests/images/imagedraw_pieslice_wide.png new file mode 100644 index 000000000..446874788 Binary files /dev/null and b/Tests/images/imagedraw_pieslice_wide.png differ diff --git a/Tests/images/imagedraw_pieslice_width.png b/Tests/images/imagedraw_pieslice_width.png index 3bd69222c..422d92f3b 100644 Binary files a/Tests/images/imagedraw_pieslice_width.png and b/Tests/images/imagedraw_pieslice_width.png differ diff --git a/Tests/images/imagedraw_pieslice_width_fill.png b/Tests/images/imagedraw_pieslice_width_fill.png index c5a34e0f3..bee6aac3b 100644 Binary files a/Tests/images/imagedraw_pieslice_width_fill.png and b/Tests/images/imagedraw_pieslice_width_fill.png differ diff --git a/Tests/images/imagedraw_pieslice_zero_width.png b/Tests/images/imagedraw_pieslice_zero_width.png new file mode 100644 index 000000000..d6ceb0b5a Binary files /dev/null and b/Tests/images/imagedraw_pieslice_zero_width.png differ diff --git a/Tests/images/imagedraw_polygon_1px_high.png b/Tests/images/imagedraw_polygon_1px_high.png new file mode 100644 index 000000000..e06508a0a Binary files /dev/null and b/Tests/images/imagedraw_polygon_1px_high.png differ diff --git a/Tests/images/imagedraw_rectangle_zero_width.png b/Tests/images/imagedraw_rectangle_zero_width.png new file mode 100644 index 000000000..989c95761 Binary files /dev/null and b/Tests/images/imagedraw_rectangle_zero_width.png differ diff --git a/Tests/images/imagedraw_regular_octagon.png b/Tests/images/imagedraw_regular_octagon.png new file mode 100644 index 000000000..7f215dc08 Binary files /dev/null and b/Tests/images/imagedraw_regular_octagon.png differ diff --git a/Tests/images/imagedraw_square.png b/Tests/images/imagedraw_square.png new file mode 100644 index 000000000..fd75f2f3b Binary files /dev/null and b/Tests/images/imagedraw_square.png differ diff --git a/Tests/images/imagedraw_square_rotate_45.png b/Tests/images/imagedraw_square_rotate_45.png new file mode 100644 index 000000000..8ab0e3c18 Binary files /dev/null and b/Tests/images/imagedraw_square_rotate_45.png differ diff --git a/Tests/images/imagedraw_stroke_descender.png b/Tests/images/imagedraw_stroke_descender.png new file mode 100644 index 000000000..93462334a Binary files /dev/null and b/Tests/images/imagedraw_stroke_descender.png differ diff --git a/Tests/images/imagedraw_wide_line_larger_than_int.png b/Tests/images/imagedraw_wide_line_larger_than_int.png new file mode 100644 index 000000000..68073ce48 Binary files /dev/null and b/Tests/images/imagedraw_wide_line_larger_than_int.png differ diff --git a/Tests/images/ossfuzz-4836216264589312.pcx b/Tests/images/ossfuzz-4836216264589312.pcx new file mode 100644 index 000000000..fdde9716a Binary files /dev/null and b/Tests/images/ossfuzz-4836216264589312.pcx differ diff --git a/Tests/images/ossfuzz-5730089102868480.sgi b/Tests/images/ossfuzz-5730089102868480.sgi new file mode 100644 index 000000000..a92c1ed01 Binary files /dev/null and b/Tests/images/ossfuzz-5730089102868480.sgi differ diff --git a/Tests/images/sgi_crash.bin b/Tests/images/sgi_crash.bin new file mode 100644 index 000000000..9b138f6fe Binary files /dev/null and b/Tests/images/sgi_crash.bin differ diff --git a/Tests/images/sgi_overrun_expandrowF04.bin b/Tests/images/sgi_overrun_expandrowF04.bin new file mode 100644 index 000000000..1907d5d3d Binary files /dev/null and b/Tests/images/sgi_overrun_expandrowF04.bin differ diff --git a/Tests/images/standard_embedded.png b/Tests/images/standard_embedded.png new file mode 100644 index 000000000..890532531 Binary files /dev/null and b/Tests/images/standard_embedded.png differ diff --git a/Tests/images/string_dimension.tiff b/Tests/images/string_dimension.tiff deleted file mode 100644 index d0b558301..000000000 Binary files a/Tests/images/string_dimension.tiff and /dev/null differ diff --git a/Tests/images/test_anchor_multiline_lm_center.png b/Tests/images/test_anchor_multiline_lm_center.png new file mode 100644 index 000000000..6fff287e4 Binary files /dev/null and b/Tests/images/test_anchor_multiline_lm_center.png differ diff --git a/Tests/images/test_anchor_multiline_lm_left.png b/Tests/images/test_anchor_multiline_lm_left.png new file mode 100644 index 000000000..b76a81b81 Binary files /dev/null and b/Tests/images/test_anchor_multiline_lm_left.png differ diff --git a/Tests/images/test_anchor_multiline_lm_right.png b/Tests/images/test_anchor_multiline_lm_right.png new file mode 100644 index 000000000..c12a8d63e Binary files /dev/null and b/Tests/images/test_anchor_multiline_lm_right.png differ diff --git a/Tests/images/test_anchor_multiline_ma_center.png b/Tests/images/test_anchor_multiline_ma_center.png new file mode 100644 index 000000000..4f35d781f Binary files /dev/null and b/Tests/images/test_anchor_multiline_ma_center.png differ diff --git a/Tests/images/test_anchor_multiline_md_center.png b/Tests/images/test_anchor_multiline_md_center.png new file mode 100644 index 000000000..8290d045c Binary files /dev/null and b/Tests/images/test_anchor_multiline_md_center.png differ diff --git a/Tests/images/test_anchor_multiline_mm_center.png b/Tests/images/test_anchor_multiline_mm_center.png new file mode 100644 index 000000000..773cf2a4a Binary files /dev/null and b/Tests/images/test_anchor_multiline_mm_center.png differ diff --git a/Tests/images/test_anchor_multiline_mm_left.png b/Tests/images/test_anchor_multiline_mm_left.png new file mode 100644 index 000000000..87d56636a Binary files /dev/null and b/Tests/images/test_anchor_multiline_mm_left.png differ diff --git a/Tests/images/test_anchor_multiline_mm_right.png b/Tests/images/test_anchor_multiline_mm_right.png new file mode 100644 index 000000000..cf002b12c Binary files /dev/null and b/Tests/images/test_anchor_multiline_mm_right.png differ diff --git a/Tests/images/test_anchor_multiline_rm_center.png b/Tests/images/test_anchor_multiline_rm_center.png new file mode 100644 index 000000000..98073144b Binary files /dev/null and b/Tests/images/test_anchor_multiline_rm_center.png differ diff --git a/Tests/images/test_anchor_multiline_rm_left.png b/Tests/images/test_anchor_multiline_rm_left.png new file mode 100644 index 000000000..838fd7858 Binary files /dev/null and b/Tests/images/test_anchor_multiline_rm_left.png differ diff --git a/Tests/images/test_anchor_multiline_rm_right.png b/Tests/images/test_anchor_multiline_rm_right.png new file mode 100644 index 000000000..290f58417 Binary files /dev/null and b/Tests/images/test_anchor_multiline_rm_right.png differ diff --git a/Tests/images/test_anchor_quick_ls.png b/Tests/images/test_anchor_quick_ls.png new file mode 100644 index 000000000..524c417c3 Binary files /dev/null and b/Tests/images/test_anchor_quick_ls.png differ diff --git a/Tests/images/test_anchor_quick_ma.png b/Tests/images/test_anchor_quick_ma.png new file mode 100644 index 000000000..cfff27f7d Binary files /dev/null and b/Tests/images/test_anchor_quick_ma.png differ diff --git a/Tests/images/test_anchor_quick_mb.png b/Tests/images/test_anchor_quick_mb.png new file mode 100644 index 000000000..ff11f478e Binary files /dev/null and b/Tests/images/test_anchor_quick_mb.png differ diff --git a/Tests/images/test_anchor_quick_md.png b/Tests/images/test_anchor_quick_md.png new file mode 100644 index 000000000..5cbccb170 Binary files /dev/null and b/Tests/images/test_anchor_quick_md.png differ diff --git a/Tests/images/test_anchor_quick_mm.png b/Tests/images/test_anchor_quick_mm.png new file mode 100644 index 000000000..500294c3b Binary files /dev/null and b/Tests/images/test_anchor_quick_mm.png differ diff --git a/Tests/images/test_anchor_quick_ms.png b/Tests/images/test_anchor_quick_ms.png new file mode 100644 index 000000000..b1012463e Binary files /dev/null and b/Tests/images/test_anchor_quick_ms.png differ diff --git a/Tests/images/test_anchor_quick_mt.png b/Tests/images/test_anchor_quick_mt.png new file mode 100644 index 000000000..19423e51a Binary files /dev/null and b/Tests/images/test_anchor_quick_mt.png differ diff --git a/Tests/images/test_anchor_quick_rs.png b/Tests/images/test_anchor_quick_rs.png new file mode 100644 index 000000000..20a5e6c6e Binary files /dev/null and b/Tests/images/test_anchor_quick_rs.png differ diff --git a/Tests/images/test_anchor_ttb_f_lt.png b/Tests/images/test_anchor_ttb_f_lt.png new file mode 100644 index 000000000..5f70a65c4 Binary files /dev/null and b/Tests/images/test_anchor_ttb_f_lt.png differ diff --git a/Tests/images/test_anchor_ttb_f_mm.png b/Tests/images/test_anchor_ttb_f_mm.png new file mode 100644 index 000000000..e7be557d2 Binary files /dev/null and b/Tests/images/test_anchor_ttb_f_mm.png differ diff --git a/Tests/images/test_anchor_ttb_f_rb.png b/Tests/images/test_anchor_ttb_f_rb.png new file mode 100644 index 000000000..b78e2f954 Binary files /dev/null and b/Tests/images/test_anchor_ttb_f_rb.png differ diff --git a/Tests/images/test_anchor_ttb_f_sm.png b/Tests/images/test_anchor_ttb_f_sm.png new file mode 100644 index 000000000..f6dc7c70f Binary files /dev/null and b/Tests/images/test_anchor_ttb_f_sm.png differ diff --git a/Tests/images/test_arabictext_features.png b/Tests/images/test_arabictext_features.png index 9bfa5a931..a03845ace 100644 Binary files a/Tests/images/test_arabictext_features.png and b/Tests/images/test_arabictext_features.png differ diff --git a/Tests/images/test_combine_caron.png b/Tests/images/test_combine_caron.png new file mode 100644 index 000000000..1097f4be5 Binary files /dev/null and b/Tests/images/test_combine_caron.png differ diff --git a/Tests/images/test_combine_caron_below.png b/Tests/images/test_combine_caron_below.png new file mode 100644 index 000000000..6e7d88a92 Binary files /dev/null and b/Tests/images/test_combine_caron_below.png differ diff --git a/Tests/images/test_combine_caron_below_lb.png b/Tests/images/test_combine_caron_below_lb.png new file mode 100644 index 000000000..f59e722b2 Binary files /dev/null and b/Tests/images/test_combine_caron_below_lb.png differ diff --git a/Tests/images/test_combine_caron_below_ld.png b/Tests/images/test_combine_caron_below_ld.png new file mode 100644 index 000000000..540ab7d42 Binary files /dev/null and b/Tests/images/test_combine_caron_below_ld.png differ diff --git a/Tests/images/test_combine_caron_below_ls.png b/Tests/images/test_combine_caron_below_ls.png new file mode 100644 index 000000000..1109b4ee6 Binary files /dev/null and b/Tests/images/test_combine_caron_below_ls.png differ diff --git a/Tests/images/test_combine_caron_below_ttb.png b/Tests/images/test_combine_caron_below_ttb.png new file mode 100644 index 000000000..5c7576de0 Binary files /dev/null and b/Tests/images/test_combine_caron_below_ttb.png differ diff --git a/Tests/images/test_combine_caron_below_ttb_lb.png b/Tests/images/test_combine_caron_below_ttb_lb.png new file mode 100644 index 000000000..bacd6a141 Binary files /dev/null and b/Tests/images/test_combine_caron_below_ttb_lb.png differ diff --git a/Tests/images/test_combine_caron_la.png b/Tests/images/test_combine_caron_la.png new file mode 100644 index 000000000..1097f4be5 Binary files /dev/null and b/Tests/images/test_combine_caron_la.png differ diff --git a/Tests/images/test_combine_caron_ls.png b/Tests/images/test_combine_caron_ls.png new file mode 100644 index 000000000..1a721873c Binary files /dev/null and b/Tests/images/test_combine_caron_ls.png differ diff --git a/Tests/images/test_combine_caron_lt.png b/Tests/images/test_combine_caron_lt.png new file mode 100644 index 000000000..91e50d45f Binary files /dev/null and b/Tests/images/test_combine_caron_lt.png differ diff --git a/Tests/images/test_combine_caron_ttb.png b/Tests/images/test_combine_caron_ttb.png new file mode 100644 index 000000000..a94be2f0a Binary files /dev/null and b/Tests/images/test_combine_caron_ttb.png differ diff --git a/Tests/images/test_combine_caron_ttb_lt.png b/Tests/images/test_combine_caron_ttb_lt.png new file mode 100644 index 000000000..a94be2f0a Binary files /dev/null and b/Tests/images/test_combine_caron_ttb_lt.png differ diff --git a/Tests/images/test_combine_double_breve_below.png b/Tests/images/test_combine_double_breve_below.png new file mode 100644 index 000000000..30252107f Binary files /dev/null and b/Tests/images/test_combine_double_breve_below.png differ diff --git a/Tests/images/test_combine_double_breve_below_ma.png b/Tests/images/test_combine_double_breve_below_ma.png new file mode 100644 index 000000000..aea09538f Binary files /dev/null and b/Tests/images/test_combine_double_breve_below_ma.png differ diff --git a/Tests/images/test_combine_double_breve_below_ra.png b/Tests/images/test_combine_double_breve_below_ra.png new file mode 100644 index 000000000..febd3ab67 Binary files /dev/null and b/Tests/images/test_combine_double_breve_below_ra.png differ diff --git a/Tests/images/test_combine_double_breve_below_ttb.png b/Tests/images/test_combine_double_breve_below_ttb.png new file mode 100644 index 000000000..8e42c0d16 Binary files /dev/null and b/Tests/images/test_combine_double_breve_below_ttb.png differ diff --git a/Tests/images/test_combine_double_breve_below_ttb_mt.png b/Tests/images/test_combine_double_breve_below_ttb_mt.png new file mode 100644 index 000000000..9a755e8f8 Binary files /dev/null and b/Tests/images/test_combine_double_breve_below_ttb_mt.png differ diff --git a/Tests/images/test_combine_double_breve_below_ttb_rt.png b/Tests/images/test_combine_double_breve_below_ttb_rt.png new file mode 100644 index 000000000..b954a5415 Binary files /dev/null and b/Tests/images/test_combine_double_breve_below_ttb_rt.png differ diff --git a/Tests/images/test_combine_double_breve_below_ttb_st.png b/Tests/images/test_combine_double_breve_below_ttb_st.png new file mode 100644 index 000000000..b6b08145e Binary files /dev/null and b/Tests/images/test_combine_double_breve_below_ttb_st.png differ diff --git a/Tests/images/test_combine_multiline_lm_center.png b/Tests/images/test_combine_multiline_lm_center.png new file mode 100644 index 000000000..7b1e9c4e4 Binary files /dev/null and b/Tests/images/test_combine_multiline_lm_center.png differ diff --git a/Tests/images/test_combine_multiline_lm_left.png b/Tests/images/test_combine_multiline_lm_left.png new file mode 100644 index 000000000..a26996c2d Binary files /dev/null and b/Tests/images/test_combine_multiline_lm_left.png differ diff --git a/Tests/images/test_combine_multiline_lm_right.png b/Tests/images/test_combine_multiline_lm_right.png new file mode 100644 index 000000000..7caf5cb74 Binary files /dev/null and b/Tests/images/test_combine_multiline_lm_right.png differ diff --git a/Tests/images/test_combine_multiline_mm_center.png b/Tests/images/test_combine_multiline_mm_center.png new file mode 100644 index 000000000..a859e9570 Binary files /dev/null and b/Tests/images/test_combine_multiline_mm_center.png differ diff --git a/Tests/images/test_combine_multiline_mm_left.png b/Tests/images/test_combine_multiline_mm_left.png new file mode 100644 index 000000000..aadb5191f Binary files /dev/null and b/Tests/images/test_combine_multiline_mm_left.png differ diff --git a/Tests/images/test_combine_multiline_mm_right.png b/Tests/images/test_combine_multiline_mm_right.png new file mode 100644 index 000000000..8238d4ec8 Binary files /dev/null and b/Tests/images/test_combine_multiline_mm_right.png differ diff --git a/Tests/images/test_combine_multiline_rm_center.png b/Tests/images/test_combine_multiline_rm_center.png new file mode 100644 index 000000000..7568dd63a Binary files /dev/null and b/Tests/images/test_combine_multiline_rm_center.png differ diff --git a/Tests/images/test_combine_multiline_rm_left.png b/Tests/images/test_combine_multiline_rm_left.png new file mode 100644 index 000000000..b8c3b5b14 Binary files /dev/null and b/Tests/images/test_combine_multiline_rm_left.png differ diff --git a/Tests/images/test_combine_multiline_rm_right.png b/Tests/images/test_combine_multiline_rm_right.png new file mode 100644 index 000000000..14c478a72 Binary files /dev/null and b/Tests/images/test_combine_multiline_rm_right.png differ diff --git a/Tests/images/test_combine_overline.png b/Tests/images/test_combine_overline.png new file mode 100644 index 000000000..dc5e86361 Binary files /dev/null and b/Tests/images/test_combine_overline.png differ diff --git a/Tests/images/test_combine_overline_la.png b/Tests/images/test_combine_overline_la.png new file mode 100644 index 000000000..dc5e86361 Binary files /dev/null and b/Tests/images/test_combine_overline_la.png differ diff --git a/Tests/images/test_combine_overline_ra.png b/Tests/images/test_combine_overline_ra.png new file mode 100644 index 000000000..cbb2d472d Binary files /dev/null and b/Tests/images/test_combine_overline_ra.png differ diff --git a/Tests/images/test_combine_overline_ttb.png b/Tests/images/test_combine_overline_ttb.png new file mode 100644 index 000000000..f74538d9c Binary files /dev/null and b/Tests/images/test_combine_overline_ttb.png differ diff --git a/Tests/images/test_combine_overline_ttb_mt.png b/Tests/images/test_combine_overline_ttb_mt.png new file mode 100644 index 000000000..e915543d6 Binary files /dev/null and b/Tests/images/test_combine_overline_ttb_mt.png differ diff --git a/Tests/images/test_combine_overline_ttb_rt.png b/Tests/images/test_combine_overline_ttb_rt.png new file mode 100644 index 000000000..186d6ee84 Binary files /dev/null and b/Tests/images/test_combine_overline_ttb_rt.png differ diff --git a/Tests/images/test_combine_overline_ttb_st.png b/Tests/images/test_combine_overline_ttb_st.png new file mode 100644 index 000000000..e915543d6 Binary files /dev/null and b/Tests/images/test_combine_overline_ttb_st.png differ diff --git a/Tests/images/test_complex_unicode_text.png b/Tests/images/test_complex_unicode_text.png index f1a6f7ec6..61174d75f 100644 Binary files a/Tests/images/test_complex_unicode_text.png and b/Tests/images/test_complex_unicode_text.png differ diff --git a/Tests/images/test_complex_unicode_text2.png b/Tests/images/test_complex_unicode_text2.png index 543b174c0..0526233c0 100644 Binary files a/Tests/images/test_complex_unicode_text2.png and b/Tests/images/test_complex_unicode_text2.png differ diff --git a/Tests/images/test_direction_ltr.png b/Tests/images/test_direction_ltr.png index 42239334d..b30fcd5d8 100644 Binary files a/Tests/images/test_direction_ltr.png and b/Tests/images/test_direction_ltr.png differ diff --git a/Tests/images/test_direction_rtl.png b/Tests/images/test_direction_rtl.png index 966b67d6b..282eed883 100644 Binary files a/Tests/images/test_direction_rtl.png and b/Tests/images/test_direction_rtl.png differ diff --git a/Tests/images/test_direction_ttb.png b/Tests/images/test_direction_ttb.png index 825f3213e..52dbf5723 100644 Binary files a/Tests/images/test_direction_ttb.png and b/Tests/images/test_direction_ttb.png differ diff --git a/Tests/images/test_direction_ttb_stroke.png b/Tests/images/test_direction_ttb_stroke.png index 3fa844e9a..4b689c38e 100644 Binary files a/Tests/images/test_direction_ttb_stroke.png and b/Tests/images/test_direction_ttb_stroke.png differ diff --git a/Tests/images/test_draw_pbm_ter_en_target.png b/Tests/images/test_draw_pbm_ter_en_target.png new file mode 100644 index 000000000..f1fa25b55 Binary files /dev/null and b/Tests/images/test_draw_pbm_ter_en_target.png differ diff --git a/Tests/images/test_draw_pbm_ter_pl_target.png b/Tests/images/test_draw_pbm_ter_pl_target.png new file mode 100644 index 000000000..503337d2b Binary files /dev/null and b/Tests/images/test_draw_pbm_ter_pl_target.png differ diff --git a/Tests/images/test_kerning_features.png b/Tests/images/test_kerning_features.png index ca895735c..78bcd951b 100644 Binary files a/Tests/images/test_kerning_features.png and b/Tests/images/test_kerning_features.png differ diff --git a/Tests/images/test_language.png b/Tests/images/test_language.png index 8daf007b0..c77215318 100644 Binary files a/Tests/images/test_language.png and b/Tests/images/test_language.png differ diff --git a/Tests/images/test_ligature_features.png b/Tests/images/test_ligature_features.png index 664e9929d..89ea648bf 100644 Binary files a/Tests/images/test_ligature_features.png and b/Tests/images/test_ligature_features.png differ diff --git a/Tests/images/test_text.png b/Tests/images/test_text.png index c156399cd..5888e52e5 100644 Binary files a/Tests/images/test_text.png and b/Tests/images/test_text.png differ diff --git a/Tests/images/test_x_max_and_y_offset.png b/Tests/images/test_x_max_and_y_offset.png index f8bec3e95..21401813f 100644 Binary files a/Tests/images/test_x_max_and_y_offset.png and b/Tests/images/test_x_max_and_y_offset.png differ diff --git a/Tests/images/test_y_offset.png b/Tests/images/test_y_offset.png index 2d57890cb..bda2490df 100644 Binary files a/Tests/images/test_y_offset.png and b/Tests/images/test_y_offset.png differ diff --git a/Tests/images/text_mono.gif b/Tests/images/text_mono.gif new file mode 100644 index 000000000..b350c10e6 Binary files /dev/null and b/Tests/images/text_mono.gif differ diff --git a/Tests/images/transparent_background_text.png b/Tests/images/transparent_background_text.png new file mode 100644 index 000000000..40acd92b6 Binary files /dev/null and b/Tests/images/transparent_background_text.png differ diff --git a/Tests/images/variation_adobe.png b/Tests/images/variation_adobe.png index 71b879bc5..e9cfafb48 100644 Binary files a/Tests/images/variation_adobe.png and b/Tests/images/variation_adobe.png differ diff --git a/Tests/images/variation_adobe_axes.png b/Tests/images/variation_adobe_axes.png index 9376c1d7b..ad3a3a960 100644 Binary files a/Tests/images/variation_adobe_axes.png and b/Tests/images/variation_adobe_axes.png differ diff --git a/Tests/images/variation_adobe_name.png b/Tests/images/variation_adobe_name.png index 9e5fe70e5..11ceaf6e6 100644 Binary files a/Tests/images/variation_adobe_name.png and b/Tests/images/variation_adobe_name.png differ diff --git a/Tests/images/variation_adobe_older_harfbuzz.png b/Tests/images/variation_adobe_older_harfbuzz.png new file mode 100644 index 000000000..5abc907ca Binary files /dev/null and b/Tests/images/variation_adobe_older_harfbuzz.png differ diff --git a/Tests/images/variation_adobe_older_harfbuzz_axes.png b/Tests/images/variation_adobe_older_harfbuzz_axes.png new file mode 100644 index 000000000..b39d460f9 Binary files /dev/null and b/Tests/images/variation_adobe_older_harfbuzz_axes.png differ diff --git a/Tests/images/variation_adobe_older_harfbuzz_name.png b/Tests/images/variation_adobe_older_harfbuzz_name.png new file mode 100644 index 000000000..2adb517a7 Binary files /dev/null and b/Tests/images/variation_adobe_older_harfbuzz_name.png differ diff --git a/Tests/images/variation_tiny_axes.png b/Tests/images/variation_tiny_axes.png index d06ac7a60..8cb6d1f62 100644 Binary files a/Tests/images/variation_tiny_axes.png and b/Tests/images/variation_tiny_axes.png differ diff --git a/Tests/images/variation_tiny_name.png b/Tests/images/variation_tiny_name.png index a0c6ffe3f..69f1550db 100644 Binary files a/Tests/images/variation_tiny_name.png and b/Tests/images/variation_tiny_name.png differ diff --git a/Tests/images/xmp_tags_orientation.png b/Tests/images/xmp_tags_orientation.png new file mode 100644 index 000000000..c1be1665f Binary files /dev/null and b/Tests/images/xmp_tags_orientation.png differ diff --git a/Tests/test_bmp_reference.py b/Tests/test_bmp_reference.py index c4fe25910..19602c1e7 100644 --- a/Tests/test_bmp_reference.py +++ b/Tests/test_bmp_reference.py @@ -1,111 +1,111 @@ import os import pytest + from PIL import Image -from .helper import PillowTestCase, assert_image_similar +from .helper import assert_image_similar base = os.path.join("Tests", "images", "bmp") -class TestBmpReference(PillowTestCase): - def get_files(self, d, ext=".bmp"): - return [ - os.path.join(base, d, f) - for f in os.listdir(os.path.join(base, d)) - if ext in f - ] +def get_files(d, ext=".bmp"): + return [ + os.path.join(base, d, f) for f in os.listdir(os.path.join(base, d)) if ext in f + ] - def test_bad(self): - """ These shouldn't crash/dos, but they shouldn't return anything - either """ - for f in self.get_files("b"): - def open(f): - try: - with Image.open(f) as im: - im.load() - except Exception: # as msg: - pass +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 - pytest.warns(None, open, f) - - def test_questionable(self): - """ These shouldn't crash/dos, but it's not well defined that these - are in spec """ - supported = [ - "pal8os2v2.bmp", - "rgb24prof.bmp", - "pal1p1.bmp", - "pal8offs.bmp", - "rgb24lprof.bmp", - "rgb32fakealpha.bmp", - "rgb24largepal.bmp", - "pal8os2sp.bmp", - "rgb32bf-xbgr.bmp", - ] - for f in self.get_files("q"): + def open(f): try: with Image.open(f) as im: im.load() - if os.path.basename(f) not in supported: - print("Please add %s to the partially supported bmp specs." % f) except Exception: # as msg: - if os.path.basename(f) in supported: - raise + pass - def test_good(self): - """ These should all work. There's a set of target files in the - html directory that we can compare against. """ + # Assert that there is no unclosed file warning + pytest.warns(None, open, f) - # Target files, if they're not just replacing the extension - file_map = { - "pal1wb.bmp": "pal1.png", - "pal4rle.bmp": "pal4.png", - "pal8-0.bmp": "pal8.png", - "pal8rle.bmp": "pal8.png", - "pal8topdown.bmp": "pal8.png", - "pal8nonsquare.bmp": "pal8nonsquare-v.png", - "pal8os2.bmp": "pal8.png", - "pal8os2sp.bmp": "pal8.png", - "pal8os2v2.bmp": "pal8.png", - "pal8os2v2-16.bmp": "pal8.png", - "pal8v4.bmp": "pal8.png", - "pal8v5.bmp": "pal8.png", - "rgb16-565pal.bmp": "rgb16-565.png", - "rgb24pal.bmp": "rgb24.png", - "rgb32.bmp": "rgb24.png", - "rgb32bf.bmp": "rgb24.png", - } - def get_compare(f): - name = os.path.split(f)[1] - if name in file_map: - return os.path.join(base, "html", file_map[name]) - name = os.path.splitext(name)[0] - return os.path.join(base, "html", "%s.png" % name) +def test_questionable(): + """These shouldn't crash/dos, but it's not well defined that these + are in spec""" + supported = [ + "pal8os2v2.bmp", + "rgb24prof.bmp", + "pal1p1.bmp", + "pal8offs.bmp", + "rgb24lprof.bmp", + "rgb32fakealpha.bmp", + "rgb24largepal.bmp", + "pal8os2sp.bmp", + "rgb32bf-xbgr.bmp", + ] + for f in get_files("q"): + try: + with Image.open(f) as im: + im.load() + if os.path.basename(f) not in supported: + print(f"Please add {f} to the partially supported bmp specs.") + except Exception: # as msg: + if os.path.basename(f) in supported: + raise - for f in self.get_files("g"): - try: - with Image.open(f) as im: - im.load() - with Image.open(get_compare(f)) as compare: - compare.load() - if im.mode == "P": - # assert image similar doesn't really work - # with paletized image, since the palette might - # be differently ordered for an equivalent image. - im = im.convert("RGBA") - compare = im.convert("RGBA") - assert_image_similar(im, compare, 5) - except Exception as msg: - # there are three here that are unsupported: - unsupported = ( - os.path.join(base, "g", "rgb32bf.bmp"), - os.path.join(base, "g", "pal8rle.bmp"), - os.path.join(base, "g", "pal4rle.bmp"), - ) - if f not in unsupported: - self.fail("Unsupported Image {}: {}".format(f, msg)) +def test_good(): + """These should all work. There's a set of target files in the + html directory that we can compare against.""" + + # Target files, if they're not just replacing the extension + file_map = { + "pal1wb.bmp": "pal1.png", + "pal4rle.bmp": "pal4.png", + "pal8-0.bmp": "pal8.png", + "pal8rle.bmp": "pal8.png", + "pal8topdown.bmp": "pal8.png", + "pal8nonsquare.bmp": "pal8nonsquare-v.png", + "pal8os2.bmp": "pal8.png", + "pal8os2sp.bmp": "pal8.png", + "pal8os2v2.bmp": "pal8.png", + "pal8os2v2-16.bmp": "pal8.png", + "pal8v4.bmp": "pal8.png", + "pal8v5.bmp": "pal8.png", + "rgb16-565pal.bmp": "rgb16-565.png", + "rgb24pal.bmp": "rgb24.png", + "rgb32.bmp": "rgb24.png", + "rgb32bf.bmp": "rgb24.png", + } + + def get_compare(f): + name = os.path.split(f)[1] + if name in file_map: + return os.path.join(base, "html", file_map[name]) + name = os.path.splitext(name)[0] + return os.path.join(base, "html", f"{name}.png") + + for f in get_files("g"): + try: + with Image.open(f) as im: + im.load() + with Image.open(get_compare(f)) as compare: + compare.load() + if im.mode == "P": + # assert image similar doesn't really work + # with paletized image, since the palette might + # be differently ordered for an equivalent image. + im = im.convert("RGBA") + compare = im.convert("RGBA") + assert_image_similar(im, compare, 5) + + except Exception as msg: + # there are three here that are unsupported: + unsupported = ( + os.path.join(base, "g", "rgb32bf.bmp"), + os.path.join(base, "g", "pal8rle.bmp"), + os.path.join(base, "g", "pal4rle.bmp"), + ) + assert f in unsupported, f"Unsupported Image {f}: {msg}" diff --git a/Tests/test_box_blur.py b/Tests/test_box_blur.py index 378d1f045..94f504e0b 100644 --- a/Tests/test_box_blur.py +++ b/Tests/test_box_blur.py @@ -1,6 +1,6 @@ -from PIL import Image, ImageFilter +import pytest -from .helper import PillowTestCase +from PIL import Image, ImageFilter sample = Image.new("L", (7, 5)) # fmt: off @@ -21,226 +21,244 @@ def test_imageops_box_blur(): assert isinstance(i, Image.Image) -class TestBoxBlur(PillowTestCase): - def box_blur(self, image, radius=1, n=1): - return image._new(image.im.box_blur(radius, n)) +def box_blur(image, radius=1, n=1): + return image._new(image.im.box_blur(radius, n)) - def assertImage(self, im, data, delta=0): - it = iter(im.getdata()) - for data_row in data: - im_row = [next(it) for _ in range(im.size[0])] - if any( - abs(data_v - im_v) > delta for data_v, im_v in zip(data_row, im_row) - ): - self.assertEqual(im_row, data_row) - self.assertRaises(StopIteration, next, it) - def assertBlur(self, im, radius, data, passes=1, delta=0): - # check grayscale image - self.assertImage(self.box_blur(im, radius, passes), data, delta) - rgba = Image.merge("RGBA", (im, im, im, im)) - for band in self.box_blur(rgba, radius, passes).split(): - self.assertImage(band, data, delta) +def assertImage(im, data, delta=0): + it = iter(im.getdata()) + for data_row in data: + im_row = [next(it) for _ in range(im.size[0])] + if any(abs(data_v - im_v) > delta for data_v, im_v in zip(data_row, im_row)): + assert im_row == data_row + with pytest.raises(StopIteration): + next(it) - def test_color_modes(self): - self.assertRaises(ValueError, self.box_blur, sample.convert("1")) - self.assertRaises(ValueError, self.box_blur, sample.convert("P")) - self.box_blur(sample.convert("L")) - self.box_blur(sample.convert("LA")) - self.box_blur(sample.convert("LA").convert("La")) - self.assertRaises(ValueError, self.box_blur, sample.convert("I")) - self.assertRaises(ValueError, self.box_blur, sample.convert("F")) - self.box_blur(sample.convert("RGB")) - self.box_blur(sample.convert("RGBA")) - self.box_blur(sample.convert("RGBA").convert("RGBa")) - self.box_blur(sample.convert("CMYK")) - self.assertRaises(ValueError, self.box_blur, sample.convert("YCbCr")) - def test_radius_0(self): - self.assertBlur( - sample, - 0, - [ - # fmt: off - [210, 50, 20, 10, 220, 230, 80], - [190, 210, 20, 180, 170, 40, 110], - [120, 210, 250, 60, 220, 0, 220], - [220, 40, 230, 80, 130, 250, 40], - [250, 0, 80, 30, 60, 20, 110], - # fmt: on - ], - ) +def assertBlur(im, radius, data, passes=1, delta=0): + # check grayscale image + assertImage(box_blur(im, radius, passes), data, delta) + rgba = Image.merge("RGBA", (im, im, im, im)) + for band in box_blur(rgba, radius, passes).split(): + assertImage(band, data, delta) - def test_radius_0_02(self): - self.assertBlur( - sample, - 0.02, - [ - # fmt: off - [206, 55, 20, 17, 215, 223, 83], - [189, 203, 31, 171, 169, 46, 110], - [125, 206, 241, 69, 210, 13, 210], - [215, 49, 221, 82, 131, 235, 48], - [244, 7, 80, 32, 60, 27, 107], - # fmt: on - ], - delta=2, - ) - def test_radius_0_05(self): - self.assertBlur( - sample, - 0.05, - [ - # fmt: off - [202, 62, 22, 27, 209, 215, 88], - [188, 194, 44, 161, 168, 56, 111], - [131, 201, 229, 81, 198, 31, 198], - [209, 62, 209, 86, 133, 216, 59], - [237, 17, 80, 36, 60, 35, 103], - # fmt: on - ], - delta=2, - ) +def test_color_modes(): + with pytest.raises(ValueError): + box_blur(sample.convert("1")) + with pytest.raises(ValueError): + box_blur(sample.convert("P")) + box_blur(sample.convert("L")) + box_blur(sample.convert("LA")) + box_blur(sample.convert("LA").convert("La")) + with pytest.raises(ValueError): + box_blur(sample.convert("I")) + with pytest.raises(ValueError): + box_blur(sample.convert("F")) + box_blur(sample.convert("RGB")) + box_blur(sample.convert("RGBA")) + box_blur(sample.convert("RGBA").convert("RGBa")) + box_blur(sample.convert("CMYK")) + with pytest.raises(ValueError): + box_blur(sample.convert("YCbCr")) - def test_radius_0_1(self): - self.assertBlur( - sample, - 0.1, - [ - # fmt: off - [196, 72, 24, 40, 200, 203, 93], - [187, 183, 62, 148, 166, 68, 111], - [139, 193, 213, 96, 182, 54, 182], - [201, 78, 193, 91, 133, 191, 73], - [227, 31, 80, 42, 61, 47, 99], - # fmt: on - ], - delta=1, - ) - def test_radius_0_5(self): - self.assertBlur( - sample, - 0.5, - [ - # fmt: off - [176, 101, 46, 83, 163, 165, 111], - [176, 149, 108, 122, 144, 120, 117], - [164, 171, 159, 141, 134, 119, 129], - [170, 136, 133, 114, 116, 124, 109], - [184, 95, 72, 70, 69, 81, 89], - # fmt: on - ], - delta=1, - ) +def test_radius_0(): + assertBlur( + sample, + 0, + [ + # fmt: off + [210, 50, 20, 10, 220, 230, 80], + [190, 210, 20, 180, 170, 40, 110], + [120, 210, 250, 60, 220, 0, 220], + [220, 40, 230, 80, 130, 250, 40], + [250, 0, 80, 30, 60, 20, 110], + # fmt: on + ], + ) - def test_radius_1(self): - self.assertBlur( - sample, - 1, - [ - # fmt: off - [170, 109, 63, 97, 146, 153, 116], - [168, 142, 112, 128, 126, 143, 121], - [169, 166, 142, 149, 126, 131, 114], - [159, 156, 109, 127, 94, 117, 112], - [164, 128, 63, 87, 76, 89, 90], - # fmt: on - ], - delta=1, - ) - def test_radius_1_5(self): - self.assertBlur( - sample, - 1.5, - [ - # fmt: off - [155, 120, 105, 112, 124, 137, 130], - [160, 136, 124, 125, 127, 134, 130], - [166, 147, 130, 125, 120, 121, 119], - [168, 145, 119, 109, 103, 105, 110], - [168, 134, 96, 85, 85, 89, 97], - # fmt: on - ], - delta=1, - ) +def test_radius_0_02(): + assertBlur( + sample, + 0.02, + [ + # fmt: off + [206, 55, 20, 17, 215, 223, 83], + [189, 203, 31, 171, 169, 46, 110], + [125, 206, 241, 69, 210, 13, 210], + [215, 49, 221, 82, 131, 235, 48], + [244, 7, 80, 32, 60, 27, 107], + # fmt: on + ], + delta=2, + ) - def test_radius_bigger_then_half(self): - self.assertBlur( - sample, - 3, - [ - # fmt: off - [144, 145, 142, 128, 114, 115, 117], - [148, 145, 137, 122, 109, 111, 112], - [152, 145, 131, 117, 103, 107, 108], - [156, 144, 126, 111, 97, 102, 103], - [160, 144, 121, 106, 92, 98, 99], - # fmt: on - ], - delta=1, - ) - def test_radius_bigger_then_width(self): - self.assertBlur( - sample, - 10, - [ - [158, 153, 147, 141, 135, 129, 123], - [159, 153, 147, 141, 136, 130, 124], - [159, 154, 148, 142, 136, 130, 124], - [160, 154, 148, 142, 137, 131, 125], - [160, 155, 149, 143, 137, 131, 125], - ], - delta=0, - ) +def test_radius_0_05(): + assertBlur( + sample, + 0.05, + [ + # fmt: off + [202, 62, 22, 27, 209, 215, 88], + [188, 194, 44, 161, 168, 56, 111], + [131, 201, 229, 81, 198, 31, 198], + [209, 62, 209, 86, 133, 216, 59], + [237, 17, 80, 36, 60, 35, 103], + # fmt: on + ], + delta=2, + ) - def test_extreme_large_radius(self): - self.assertBlur( - sample, - 600, - [ - [162, 162, 162, 162, 162, 162, 162], - [162, 162, 162, 162, 162, 162, 162], - [162, 162, 162, 162, 162, 162, 162], - [162, 162, 162, 162, 162, 162, 162], - [162, 162, 162, 162, 162, 162, 162], - ], - delta=1, - ) - def test_two_passes(self): - self.assertBlur( - sample, - 1, - [ - # fmt: off - [153, 123, 102, 109, 132, 135, 129], - [159, 138, 123, 121, 133, 131, 126], - [162, 147, 136, 124, 127, 121, 121], - [159, 140, 125, 108, 111, 106, 108], - [154, 126, 105, 87, 94, 93, 97], - # fmt: on - ], - passes=2, - delta=1, - ) +def test_radius_0_1(): + assertBlur( + sample, + 0.1, + [ + # fmt: off + [196, 72, 24, 40, 200, 203, 93], + [187, 183, 62, 148, 166, 68, 111], + [139, 193, 213, 96, 182, 54, 182], + [201, 78, 193, 91, 133, 191, 73], + [227, 31, 80, 42, 61, 47, 99], + # fmt: on + ], + delta=1, + ) - def test_three_passes(self): - self.assertBlur( - sample, - 1, - [ - # fmt: off - [146, 131, 116, 118, 126, 131, 130], - [151, 138, 125, 123, 126, 128, 127], - [154, 143, 129, 123, 120, 120, 119], - [152, 139, 122, 113, 108, 108, 108], - [148, 132, 112, 102, 97, 99, 100], - # fmt: on - ], - passes=3, - delta=1, - ) + +def test_radius_0_5(): + assertBlur( + sample, + 0.5, + [ + # fmt: off + [176, 101, 46, 83, 163, 165, 111], + [176, 149, 108, 122, 144, 120, 117], + [164, 171, 159, 141, 134, 119, 129], + [170, 136, 133, 114, 116, 124, 109], + [184, 95, 72, 70, 69, 81, 89], + # fmt: on + ], + delta=1, + ) + + +def test_radius_1(): + assertBlur( + sample, + 1, + [ + # fmt: off + [170, 109, 63, 97, 146, 153, 116], + [168, 142, 112, 128, 126, 143, 121], + [169, 166, 142, 149, 126, 131, 114], + [159, 156, 109, 127, 94, 117, 112], + [164, 128, 63, 87, 76, 89, 90], + # fmt: on + ], + delta=1, + ) + + +def test_radius_1_5(): + assertBlur( + sample, + 1.5, + [ + # fmt: off + [155, 120, 105, 112, 124, 137, 130], + [160, 136, 124, 125, 127, 134, 130], + [166, 147, 130, 125, 120, 121, 119], + [168, 145, 119, 109, 103, 105, 110], + [168, 134, 96, 85, 85, 89, 97], + # fmt: on + ], + delta=1, + ) + + +def test_radius_bigger_then_half(): + assertBlur( + sample, + 3, + [ + # fmt: off + [144, 145, 142, 128, 114, 115, 117], + [148, 145, 137, 122, 109, 111, 112], + [152, 145, 131, 117, 103, 107, 108], + [156, 144, 126, 111, 97, 102, 103], + [160, 144, 121, 106, 92, 98, 99], + # fmt: on + ], + delta=1, + ) + + +def test_radius_bigger_then_width(): + assertBlur( + sample, + 10, + [ + [158, 153, 147, 141, 135, 129, 123], + [159, 153, 147, 141, 136, 130, 124], + [159, 154, 148, 142, 136, 130, 124], + [160, 154, 148, 142, 137, 131, 125], + [160, 155, 149, 143, 137, 131, 125], + ], + delta=0, + ) + + +def test_extreme_large_radius(): + assertBlur( + sample, + 600, + [ + [162, 162, 162, 162, 162, 162, 162], + [162, 162, 162, 162, 162, 162, 162], + [162, 162, 162, 162, 162, 162, 162], + [162, 162, 162, 162, 162, 162, 162], + [162, 162, 162, 162, 162, 162, 162], + ], + delta=1, + ) + + +def test_two_passes(): + assertBlur( + sample, + 1, + [ + # fmt: off + [153, 123, 102, 109, 132, 135, 129], + [159, 138, 123, 121, 133, 131, 126], + [162, 147, 136, 124, 127, 121, 121], + [159, 140, 125, 108, 111, 106, 108], + [154, 126, 105, 87, 94, 93, 97], + # fmt: on + ], + passes=2, + delta=1, + ) + + +def test_three_passes(): + assertBlur( + sample, + 1, + [ + # fmt: off + [146, 131, 116, 118, 126, 131, 130], + [151, 138, 125, 123, 126, 128, 127], + [154, 143, 129, 123, 120, 120, 119], + [152, 139, 122, 113, 108, 108, 108], + [148, 132, 112, 102, 97, 99, 100], + # fmt: on + ], + passes=3, + delta=1, + ) diff --git a/Tests/test_color_lut.py b/Tests/test_color_lut.py index aace47d38..99776ce58 100644 --- a/Tests/test_color_lut.py +++ b/Tests/test_color_lut.py @@ -1,9 +1,10 @@ -import unittest from array import array +import pytest + from PIL import Image, ImageFilter -from .helper import PillowTestCase, assert_image_equal +from .helper import assert_image_equal try: import numpy @@ -11,7 +12,7 @@ except ImportError: numpy = None -class TestColorLut3DCoreAPI(PillowTestCase): +class TestColorLut3DCoreAPI: def generate_identity_table(self, channels, size): if isinstance(size, tuple): size1D, size2D, size3D = size @@ -41,43 +42,43 @@ class TestColorLut3DCoreAPI(PillowTestCase): def test_wrong_args(self): im = Image.new("RGB", (10, 10), 0) - with self.assertRaisesRegex(ValueError, "filter"): + with pytest.raises(ValueError, match="filter"): im.im.color_lut_3d("RGB", Image.CUBIC, *self.generate_identity_table(3, 3)) - with self.assertRaisesRegex(ValueError, "image mode"): + with pytest.raises(ValueError, match="image mode"): im.im.color_lut_3d( "wrong", Image.LINEAR, *self.generate_identity_table(3, 3) ) - with self.assertRaisesRegex(ValueError, "table_channels"): + with pytest.raises(ValueError, match="table_channels"): im.im.color_lut_3d("RGB", Image.LINEAR, *self.generate_identity_table(5, 3)) - with self.assertRaisesRegex(ValueError, "table_channels"): + with pytest.raises(ValueError, match="table_channels"): im.im.color_lut_3d("RGB", Image.LINEAR, *self.generate_identity_table(1, 3)) - with self.assertRaisesRegex(ValueError, "table_channels"): + with pytest.raises(ValueError, match="table_channels"): im.im.color_lut_3d("RGB", Image.LINEAR, *self.generate_identity_table(2, 3)) - with self.assertRaisesRegex(ValueError, "Table size"): + with pytest.raises(ValueError, match="Table size"): im.im.color_lut_3d( "RGB", Image.LINEAR, *self.generate_identity_table(3, (1, 3, 3)) ) - with self.assertRaisesRegex(ValueError, "Table size"): + with pytest.raises(ValueError, match="Table size"): im.im.color_lut_3d( "RGB", Image.LINEAR, *self.generate_identity_table(3, (66, 3, 3)) ) - with self.assertRaisesRegex(ValueError, r"size1D \* size2D \* size3D"): + with pytest.raises(ValueError, match=r"size1D \* size2D \* size3D"): im.im.color_lut_3d("RGB", Image.LINEAR, 3, 2, 2, 2, [0, 0, 0] * 7) - with self.assertRaisesRegex(ValueError, r"size1D \* size2D \* size3D"): + with pytest.raises(ValueError, match=r"size1D \* size2D \* size3D"): im.im.color_lut_3d("RGB", Image.LINEAR, 3, 2, 2, 2, [0, 0, 0] * 9) - with self.assertRaises(TypeError): + with pytest.raises(TypeError): im.im.color_lut_3d("RGB", Image.LINEAR, 3, 2, 2, 2, [0, 0, "0"] * 8) - with self.assertRaises(TypeError): + with pytest.raises(TypeError): im.im.color_lut_3d("RGB", Image.LINEAR, 3, 2, 2, 2, 16) def test_correct_args(self): @@ -104,25 +105,25 @@ class TestColorLut3DCoreAPI(PillowTestCase): ) def test_wrong_mode(self): - with self.assertRaisesRegex(ValueError, "wrong mode"): + with pytest.raises(ValueError, match="wrong mode"): im = Image.new("L", (10, 10), 0) im.im.color_lut_3d("RGB", Image.LINEAR, *self.generate_identity_table(3, 3)) - with self.assertRaisesRegex(ValueError, "wrong mode"): + with pytest.raises(ValueError, match="wrong mode"): im = Image.new("RGB", (10, 10), 0) im.im.color_lut_3d("L", Image.LINEAR, *self.generate_identity_table(3, 3)) - with self.assertRaisesRegex(ValueError, "wrong mode"): + with pytest.raises(ValueError, match="wrong mode"): im = Image.new("L", (10, 10), 0) im.im.color_lut_3d("L", Image.LINEAR, *self.generate_identity_table(3, 3)) - with self.assertRaisesRegex(ValueError, "wrong mode"): + with pytest.raises(ValueError, match="wrong mode"): im = Image.new("RGB", (10, 10), 0) im.im.color_lut_3d( "RGBA", Image.LINEAR, *self.generate_identity_table(3, 3) ) - with self.assertRaisesRegex(ValueError, "wrong mode"): + with pytest.raises(ValueError, match="wrong mode"): im = Image.new("RGB", (10, 10), 0) im.im.color_lut_3d("RGB", Image.LINEAR, *self.generate_identity_table(4, 3)) @@ -240,14 +241,14 @@ class TestColorLut3DCoreAPI(PillowTestCase): -1, 2, 2, 2, 2, 2, ])).load() # fmt: on - self.assertEqual(transformed[0, 0], (0, 0, 255)) - self.assertEqual(transformed[50, 50], (0, 0, 255)) - self.assertEqual(transformed[255, 0], (0, 255, 255)) - self.assertEqual(transformed[205, 50], (0, 255, 255)) - self.assertEqual(transformed[0, 255], (255, 0, 0)) - self.assertEqual(transformed[50, 205], (255, 0, 0)) - self.assertEqual(transformed[255, 255], (255, 255, 0)) - self.assertEqual(transformed[205, 205], (255, 255, 0)) + assert transformed[0, 0] == (0, 0, 255) + assert transformed[50, 50] == (0, 0, 255) + assert transformed[255, 0] == (0, 255, 255) + assert transformed[205, 50] == (0, 255, 255) + assert transformed[0, 255] == (255, 0, 0) + assert transformed[50, 205] == (255, 0, 0) + assert transformed[255, 255] == (255, 255, 0) + assert transformed[205, 205] == (255, 255, 0) # fmt: off transformed = im._new(im.im.color_lut_3d('RGB', Image.LINEAR, @@ -260,96 +261,96 @@ class TestColorLut3DCoreAPI(PillowTestCase): -3, 5, 5, 5, 5, 5, ])).load() # fmt: on - self.assertEqual(transformed[0, 0], (0, 0, 255)) - self.assertEqual(transformed[50, 50], (0, 0, 255)) - self.assertEqual(transformed[255, 0], (0, 255, 255)) - self.assertEqual(transformed[205, 50], (0, 255, 255)) - self.assertEqual(transformed[0, 255], (255, 0, 0)) - self.assertEqual(transformed[50, 205], (255, 0, 0)) - self.assertEqual(transformed[255, 255], (255, 255, 0)) - self.assertEqual(transformed[205, 205], (255, 255, 0)) + assert transformed[0, 0] == (0, 0, 255) + assert transformed[50, 50] == (0, 0, 255) + assert transformed[255, 0] == (0, 255, 255) + assert transformed[205, 50] == (0, 255, 255) + assert transformed[0, 255] == (255, 0, 0) + assert transformed[50, 205] == (255, 0, 0) + assert transformed[255, 255] == (255, 255, 0) + assert transformed[205, 205] == (255, 255, 0) -class TestColorLut3DFilter(PillowTestCase): +class TestColorLut3DFilter: def test_wrong_args(self): - with self.assertRaisesRegex(ValueError, "should be either an integer"): + with pytest.raises(ValueError, match="should be either an integer"): ImageFilter.Color3DLUT("small", [1]) - with self.assertRaisesRegex(ValueError, "should be either an integer"): + with pytest.raises(ValueError, match="should be either an integer"): ImageFilter.Color3DLUT((11, 11), [1]) - with self.assertRaisesRegex(ValueError, r"in \[2, 65\] range"): + with pytest.raises(ValueError, match=r"in \[2, 65\] range"): ImageFilter.Color3DLUT((11, 11, 1), [1]) - with self.assertRaisesRegex(ValueError, r"in \[2, 65\] range"): + with pytest.raises(ValueError, match=r"in \[2, 65\] range"): ImageFilter.Color3DLUT((11, 11, 66), [1]) - with self.assertRaisesRegex(ValueError, "table should have .+ items"): + with pytest.raises(ValueError, match="table should have .+ items"): ImageFilter.Color3DLUT((3, 3, 3), [1, 1, 1]) - with self.assertRaisesRegex(ValueError, "table should have .+ items"): + with pytest.raises(ValueError, match="table should have .+ items"): ImageFilter.Color3DLUT((3, 3, 3), [[1, 1, 1]] * 2) - with self.assertRaisesRegex(ValueError, "should have a length of 4"): + with pytest.raises(ValueError, match="should have a length of 4"): ImageFilter.Color3DLUT((3, 3, 3), [[1, 1, 1]] * 27, channels=4) - with self.assertRaisesRegex(ValueError, "should have a length of 3"): + with pytest.raises(ValueError, match="should have a length of 3"): ImageFilter.Color3DLUT((2, 2, 2), [[1, 1]] * 8) - with self.assertRaisesRegex(ValueError, "Only 3 or 4 output"): + with pytest.raises(ValueError, match="Only 3 or 4 output"): ImageFilter.Color3DLUT((2, 2, 2), [[1, 1]] * 8, channels=2) def test_convert_table(self): lut = ImageFilter.Color3DLUT(2, [0, 1, 2] * 8) - self.assertEqual(tuple(lut.size), (2, 2, 2)) - self.assertEqual(lut.name, "Color 3D LUT") + assert tuple(lut.size) == (2, 2, 2) + assert lut.name == "Color 3D LUT" # fmt: off lut = ImageFilter.Color3DLUT((2, 2, 2), [ (0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11), (12, 13, 14), (15, 16, 17), (18, 19, 20), (21, 22, 23)]) # fmt: on - self.assertEqual(tuple(lut.size), (2, 2, 2)) - self.assertEqual(lut.table, list(range(24))) + assert tuple(lut.size) == (2, 2, 2) + assert lut.table == list(range(24)) lut = ImageFilter.Color3DLUT((2, 2, 2), [(0, 1, 2, 3)] * 8, channels=4) - self.assertEqual(tuple(lut.size), (2, 2, 2)) - self.assertEqual(lut.table, list(range(4)) * 8) + assert tuple(lut.size) == (2, 2, 2) + assert lut.table == list(range(4)) * 8 - @unittest.skipIf(numpy is None, "Numpy is not installed") + @pytest.mark.skipif(numpy is None, reason="NumPy not installed") def test_numpy_sources(self): table = numpy.ones((5, 6, 7, 3), dtype=numpy.float16) - with self.assertRaisesRegex(ValueError, "should have either channels"): + with pytest.raises(ValueError, match="should have either channels"): lut = ImageFilter.Color3DLUT((5, 6, 7), table) table = numpy.ones((7, 6, 5, 3), dtype=numpy.float16) lut = ImageFilter.Color3DLUT((5, 6, 7), table) - self.assertIsInstance(lut.table, numpy.ndarray) - self.assertEqual(lut.table.dtype, table.dtype) - self.assertEqual(lut.table.shape, (table.size,)) + assert isinstance(lut.table, numpy.ndarray) + assert lut.table.dtype == table.dtype + assert lut.table.shape == (table.size,) table = numpy.ones((7 * 6 * 5, 3), dtype=numpy.float16) lut = ImageFilter.Color3DLUT((5, 6, 7), table) - self.assertEqual(lut.table.shape, (table.size,)) + assert lut.table.shape == (table.size,) table = numpy.ones((7 * 6 * 5 * 3), dtype=numpy.float16) lut = ImageFilter.Color3DLUT((5, 6, 7), table) - self.assertEqual(lut.table.shape, (table.size,)) + assert lut.table.shape == (table.size,) # Check application Image.new("RGB", (10, 10), 0).filter(lut) # Check copy table[0] = 33 - self.assertEqual(lut.table[0], 1) + assert lut.table[0] == 1 # Check not copy table = numpy.ones((7 * 6 * 5 * 3), dtype=numpy.float16) lut = ImageFilter.Color3DLUT((5, 6, 7), table, _copy_table=False) table[0] = 33 - self.assertEqual(lut.table[0], 33) + assert lut.table[0] == 33 - @unittest.skipIf(numpy is None, "Numpy is not installed") + @pytest.mark.skipif(numpy is None, reason="NumPy not installed") def test_numpy_formats(self): g = Image.linear_gradient("L") im = Image.merge( @@ -358,12 +359,12 @@ class TestColorLut3DFilter(PillowTestCase): lut = ImageFilter.Color3DLUT.generate((7, 9, 11), lambda r, g, b: (r, g, b)) lut.table = numpy.array(lut.table, dtype=numpy.float32)[:-1] - with self.assertRaisesRegex(ValueError, "should have table_channels"): + with pytest.raises(ValueError, match="should have table_channels"): im.filter(lut) lut = ImageFilter.Color3DLUT.generate((7, 9, 11), lambda r, g, b: (r, g, b)) lut.table = numpy.array(lut.table, dtype=numpy.float32).reshape((7 * 9 * 11), 3) - with self.assertRaisesRegex(ValueError, "should have table_channels"): + with pytest.raises(ValueError, match="should have table_channels"): im.filter(lut) lut = ImageFilter.Color3DLUT.generate((7, 9, 11), lambda r, g, b: (r, g, b)) @@ -386,7 +387,7 @@ class TestColorLut3DFilter(PillowTestCase): def test_repr(self): lut = ImageFilter.Color3DLUT(2, [0, 1, 2] * 8) - self.assertEqual(repr(lut), "") + assert repr(lut) == "" lut = ImageFilter.Color3DLUT( (3, 4, 5), @@ -395,47 +396,48 @@ class TestColorLut3DFilter(PillowTestCase): target_mode="YCbCr", _copy_table=False, ) - self.assertEqual( - repr(lut), "" + assert ( + repr(lut) + == "" ) -class TestGenerateColorLut3D(PillowTestCase): +class TestGenerateColorLut3D: def test_wrong_channels_count(self): - with self.assertRaisesRegex(ValueError, "3 or 4 output channels"): + with pytest.raises(ValueError, match="3 or 4 output channels"): ImageFilter.Color3DLUT.generate( 5, channels=2, callback=lambda r, g, b: (r, g, b) ) - with self.assertRaisesRegex(ValueError, "should have either channels"): + with pytest.raises(ValueError, match="should have either channels"): ImageFilter.Color3DLUT.generate(5, lambda r, g, b: (r, g, b, r)) - with self.assertRaisesRegex(ValueError, "should have either channels"): + with pytest.raises(ValueError, match="should have either channels"): ImageFilter.Color3DLUT.generate( 5, channels=4, callback=lambda r, g, b: (r, g, b) ) def test_3_channels(self): lut = ImageFilter.Color3DLUT.generate(5, lambda r, g, b: (r, g, b)) - self.assertEqual(tuple(lut.size), (5, 5, 5)) - self.assertEqual(lut.name, "Color 3D LUT") + assert tuple(lut.size) == (5, 5, 5) + assert lut.name == "Color 3D LUT" # fmt: off - self.assertEqual(lut.table[:24], [ + assert lut.table[:24] == [ 0.0, 0.0, 0.0, 0.25, 0.0, 0.0, 0.5, 0.0, 0.0, 0.75, 0.0, 0.0, - 1.0, 0.0, 0.0, 0.0, 0.25, 0.0, 0.25, 0.25, 0.0, 0.5, 0.25, 0.0]) + 1.0, 0.0, 0.0, 0.0, 0.25, 0.0, 0.25, 0.25, 0.0, 0.5, 0.25, 0.0] # fmt: on def test_4_channels(self): lut = ImageFilter.Color3DLUT.generate( 5, channels=4, callback=lambda r, g, b: (b, r, g, (r + g + b) / 2) ) - self.assertEqual(tuple(lut.size), (5, 5, 5)) - self.assertEqual(lut.name, "Color 3D LUT") + assert tuple(lut.size) == (5, 5, 5) + assert lut.name == "Color 3D LUT" # fmt: off - self.assertEqual(lut.table[:24], [ + assert lut.table[:24] == [ 0.0, 0.0, 0.0, 0.0, 0.0, 0.25, 0.0, 0.125, 0.0, 0.5, 0.0, 0.25, 0.0, 0.75, 0.0, 0.375, 0.0, 1.0, 0.0, 0.5, 0.0, 0.0, 0.25, 0.125 - ]) + ] # fmt: on def test_apply(self): @@ -445,23 +447,23 @@ class TestGenerateColorLut3D(PillowTestCase): im = Image.merge( "RGB", [g, g.transpose(Image.ROTATE_90), g.transpose(Image.ROTATE_180)] ) - self.assertEqual(im, im.filter(lut)) + assert im == im.filter(lut) -class TestTransformColorLut3D(PillowTestCase): +class TestTransformColorLut3D: def test_wrong_args(self): source = ImageFilter.Color3DLUT.generate(5, lambda r, g, b: (r, g, b)) - with self.assertRaisesRegex(ValueError, "Only 3 or 4 output"): + with pytest.raises(ValueError, match="Only 3 or 4 output"): source.transform(lambda r, g, b: (r, g, b), channels=8) - with self.assertRaisesRegex(ValueError, "should have either channels"): + with pytest.raises(ValueError, match="should have either channels"): source.transform(lambda r, g, b: (r, g, b), channels=4) - with self.assertRaisesRegex(ValueError, "should have either channels"): + with pytest.raises(ValueError, match="should have either channels"): source.transform(lambda r, g, b: (r, g, b, 1)) - with self.assertRaises(TypeError): + with pytest.raises(TypeError): source.transform(lambda r, g, b, a: (r, g, b)) def test_target_mode(self): @@ -470,31 +472,29 @@ class TestTransformColorLut3D(PillowTestCase): ) lut = source.transform(lambda r, g, b: (r, g, b)) - self.assertEqual(lut.mode, "HSV") + assert lut.mode == "HSV" lut = source.transform(lambda r, g, b: (r, g, b), target_mode="RGB") - self.assertEqual(lut.mode, "RGB") + assert lut.mode == "RGB" def test_3_to_3_channels(self): source = ImageFilter.Color3DLUT.generate((3, 4, 5), lambda r, g, b: (r, g, b)) lut = source.transform(lambda r, g, b: (r * r, g * g, b * b)) - self.assertEqual(tuple(lut.size), tuple(source.size)) - self.assertEqual(len(lut.table), len(source.table)) - self.assertNotEqual(lut.table, source.table) - self.assertEqual( - lut.table[0:10], [0.0, 0.0, 0.0, 0.25, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0] - ) + assert tuple(lut.size) == tuple(source.size) + assert len(lut.table) == len(source.table) + assert lut.table != source.table + assert lut.table[0:10] == [0.0, 0.0, 0.0, 0.25, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0] def test_3_to_4_channels(self): source = ImageFilter.Color3DLUT.generate((6, 5, 4), lambda r, g, b: (r, g, b)) lut = source.transform(lambda r, g, b: (r * r, g * g, b * b, 1), channels=4) - self.assertEqual(tuple(lut.size), tuple(source.size)) - self.assertNotEqual(len(lut.table), len(source.table)) - self.assertNotEqual(lut.table, source.table) + assert tuple(lut.size) == tuple(source.size) + assert len(lut.table) != len(source.table) + assert lut.table != source.table # fmt: off - self.assertEqual(lut.table[0:16], [ + assert lut.table[0:16] == [ 0.0, 0.0, 0.0, 1, 0.2**2, 0.0, 0.0, 1, - 0.4**2, 0.0, 0.0, 1, 0.6**2, 0.0, 0.0, 1]) + 0.4**2, 0.0, 0.0, 1, 0.6**2, 0.0, 0.0, 1] # fmt: on def test_4_to_3_channels(self): @@ -504,13 +504,13 @@ class TestTransformColorLut3D(PillowTestCase): lut = source.transform( lambda r, g, b, a: (a - r * r, a - g * g, a - b * b), channels=3 ) - self.assertEqual(tuple(lut.size), tuple(source.size)) - self.assertNotEqual(len(lut.table), len(source.table)) - self.assertNotEqual(lut.table, source.table) + assert tuple(lut.size) == tuple(source.size) + assert len(lut.table) != len(source.table) + assert lut.table != source.table # fmt: off - self.assertEqual(lut.table[0:18], [ + assert lut.table[0:18] == [ 1.0, 1.0, 1.0, 0.75, 1.0, 1.0, 0.0, 1.0, 1.0, - 1.0, 0.96, 1.0, 0.75, 0.96, 1.0, 0.0, 0.96, 1.0]) + 1.0, 0.96, 1.0, 0.75, 0.96, 1.0, 0.0, 0.96, 1.0] # fmt: on def test_4_to_4_channels(self): @@ -518,13 +518,13 @@ class TestTransformColorLut3D(PillowTestCase): (6, 5, 4), lambda r, g, b: (r, g, b, 1), channels=4 ) lut = source.transform(lambda r, g, b, a: (r * r, g * g, b * b, a - 0.5)) - self.assertEqual(tuple(lut.size), tuple(source.size)) - self.assertEqual(len(lut.table), len(source.table)) - self.assertNotEqual(lut.table, source.table) + assert tuple(lut.size) == tuple(source.size) + assert len(lut.table) == len(source.table) + assert lut.table != source.table # fmt: off - self.assertEqual(lut.table[0:16], [ + assert lut.table[0:16] == [ 0.0, 0.0, 0.0, 0.5, 0.2**2, 0.0, 0.0, 0.5, - 0.4**2, 0.0, 0.0, 0.5, 0.6**2, 0.0, 0.0, 0.5]) + 0.4**2, 0.0, 0.0, 0.5, 0.6**2, 0.0, 0.0, 0.5] # fmt: on def test_with_normals_3_channels(self): @@ -534,13 +534,13 @@ class TestTransformColorLut3D(PillowTestCase): lut = source.transform( lambda nr, ng, nb, r, g, b: (nr - r, ng - g, nb - b), with_normals=True ) - self.assertEqual(tuple(lut.size), tuple(source.size)) - self.assertEqual(len(lut.table), len(source.table)) - self.assertNotEqual(lut.table, source.table) + assert tuple(lut.size) == tuple(source.size) + assert len(lut.table) == len(source.table) + assert lut.table != source.table # fmt: off - self.assertEqual(lut.table[0:18], [ + assert lut.table[0:18] == [ 0.0, 0.0, 0.0, 0.16, 0.0, 0.0, 0.24, 0.0, 0.0, - 0.24, 0.0, 0.0, 0.8 - (0.8**2), 0, 0, 0, 0, 0]) + 0.24, 0.0, 0.0, 0.8 - (0.8**2), 0, 0, 0, 0, 0] # fmt: on def test_with_normals_4_channels(self): @@ -551,11 +551,11 @@ class TestTransformColorLut3D(PillowTestCase): lambda nr, ng, nb, r, g, b, a: (nr - r, ng - g, nb - b, a - 0.5), with_normals=True, ) - self.assertEqual(tuple(lut.size), tuple(source.size)) - self.assertEqual(len(lut.table), len(source.table)) - self.assertNotEqual(lut.table, source.table) + assert tuple(lut.size) == tuple(source.size) + assert len(lut.table) == len(source.table) + assert lut.table != source.table # fmt: off - self.assertEqual(lut.table[0:16], [ + assert lut.table[0:16] == [ 0.0, 0.0, 0.0, 0.5, 0.25, 0.0, 0.0, 0.5, - 0.0, 0.0, 0.0, 0.5, 0.0, 0.16, 0.0, 0.5]) + 0.0, 0.0, 0.0, 0.5, 0.0, 0.16, 0.0, 0.5] # fmt: on diff --git a/Tests/test_core_resources.py b/Tests/test_core_resources.py index 22563b92a..6c52d25a4 100644 --- a/Tests/test_core_resources.py +++ b/Tests/test_core_resources.py @@ -1,10 +1,10 @@ import sys -import unittest import pytest + from PIL import Image -from .helper import PillowTestCase, is_pypy +from .helper import is_pypy def test_get_stats(): @@ -32,8 +32,8 @@ def test_reset_stats(): assert stats["blocks_cached"] == 0 -class TestCoreMemory(PillowTestCase): - def tearDown(self): +class TestCoreMemory: + def teardown_method(self): # Restore default values Image.core.set_alignment(1) Image.core.set_block_size(1024 * 1024) @@ -43,38 +43,44 @@ class TestCoreMemory(PillowTestCase): def test_get_alignment(self): alignment = Image.core.get_alignment() - self.assertGreater(alignment, 0) + assert alignment > 0 def test_set_alignment(self): for i in [1, 2, 4, 8, 16, 32]: Image.core.set_alignment(i) alignment = Image.core.get_alignment() - self.assertEqual(alignment, i) + assert alignment == i # Try to construct new image Image.new("RGB", (10, 10)) - self.assertRaises(ValueError, Image.core.set_alignment, 0) - self.assertRaises(ValueError, Image.core.set_alignment, -1) - self.assertRaises(ValueError, Image.core.set_alignment, 3) + with pytest.raises(ValueError): + Image.core.set_alignment(0) + with pytest.raises(ValueError): + Image.core.set_alignment(-1) + with pytest.raises(ValueError): + Image.core.set_alignment(3) def test_get_block_size(self): block_size = Image.core.get_block_size() - self.assertGreaterEqual(block_size, 4096) + assert block_size >= 4096 def test_set_block_size(self): for i in [4096, 2 * 4096, 3 * 4096]: Image.core.set_block_size(i) block_size = Image.core.get_block_size() - self.assertEqual(block_size, i) + assert block_size == i # Try to construct new image Image.new("RGB", (10, 10)) - self.assertRaises(ValueError, Image.core.set_block_size, 0) - self.assertRaises(ValueError, Image.core.set_block_size, -1) - self.assertRaises(ValueError, Image.core.set_block_size, 4000) + with pytest.raises(ValueError): + Image.core.set_block_size(0) + with pytest.raises(ValueError): + Image.core.set_block_size(-1) + with pytest.raises(ValueError): + Image.core.set_block_size(4000) def test_set_block_size_stats(self): Image.core.reset_stats() @@ -83,30 +89,32 @@ class TestCoreMemory(PillowTestCase): Image.new("RGB", (256, 256)) stats = Image.core.get_stats() - self.assertGreaterEqual(stats["new_count"], 1) - self.assertGreaterEqual(stats["allocated_blocks"], 64) + assert stats["new_count"] >= 1 + assert stats["allocated_blocks"] >= 64 if not is_pypy(): - self.assertGreaterEqual(stats["freed_blocks"], 64) + assert stats["freed_blocks"] >= 64 def test_get_blocks_max(self): blocks_max = Image.core.get_blocks_max() - self.assertGreaterEqual(blocks_max, 0) + assert blocks_max >= 0 def test_set_blocks_max(self): for i in [0, 1, 10]: Image.core.set_blocks_max(i) blocks_max = Image.core.get_blocks_max() - self.assertEqual(blocks_max, i) + assert blocks_max == i # Try to construct new image Image.new("RGB", (10, 10)) - self.assertRaises(ValueError, Image.core.set_blocks_max, -1) + with pytest.raises(ValueError): + Image.core.set_blocks_max(-1) if sys.maxsize < 2 ** 32: - self.assertRaises(ValueError, Image.core.set_blocks_max, 2 ** 29) + with pytest.raises(ValueError): + Image.core.set_blocks_max(2 ** 29) - @unittest.skipIf(is_pypy(), "images are not collected") + @pytest.mark.skipif(is_pypy(), reason="Images not collected") def test_set_blocks_max_stats(self): Image.core.reset_stats() Image.core.set_blocks_max(128) @@ -115,13 +123,13 @@ class TestCoreMemory(PillowTestCase): Image.new("RGB", (256, 256)) stats = Image.core.get_stats() - self.assertGreaterEqual(stats["new_count"], 2) - self.assertGreaterEqual(stats["allocated_blocks"], 64) - self.assertGreaterEqual(stats["reused_blocks"], 64) - self.assertEqual(stats["freed_blocks"], 0) - self.assertEqual(stats["blocks_cached"], 64) + assert stats["new_count"] >= 2 + assert stats["allocated_blocks"] >= 64 + assert stats["reused_blocks"] >= 64 + assert stats["freed_blocks"] == 0 + assert stats["blocks_cached"] == 64 - @unittest.skipIf(is_pypy(), "images are not collected") + @pytest.mark.skipif(is_pypy(), reason="Images not collected") def test_clear_cache_stats(self): Image.core.reset_stats() Image.core.clear_cache() @@ -133,11 +141,11 @@ class TestCoreMemory(PillowTestCase): Image.core.clear_cache(16) stats = Image.core.get_stats() - self.assertGreaterEqual(stats["new_count"], 2) - self.assertGreaterEqual(stats["allocated_blocks"], 64) - self.assertGreaterEqual(stats["reused_blocks"], 64) - self.assertGreaterEqual(stats["freed_blocks"], 48) - self.assertEqual(stats["blocks_cached"], 16) + assert stats["new_count"] >= 2 + assert stats["allocated_blocks"] >= 64 + assert stats["reused_blocks"] >= 64 + assert stats["freed_blocks"] >= 48 + assert stats["blocks_cached"] == 16 def test_large_images(self): Image.core.reset_stats() @@ -147,16 +155,16 @@ class TestCoreMemory(PillowTestCase): Image.core.clear_cache() stats = Image.core.get_stats() - self.assertGreaterEqual(stats["new_count"], 1) - self.assertGreaterEqual(stats["allocated_blocks"], 16) - self.assertGreaterEqual(stats["reused_blocks"], 0) - self.assertEqual(stats["blocks_cached"], 0) + assert stats["new_count"] >= 1 + assert stats["allocated_blocks"] >= 16 + assert stats["reused_blocks"] >= 0 + assert stats["blocks_cached"] == 0 if not is_pypy(): - self.assertGreaterEqual(stats["freed_blocks"], 16) + assert stats["freed_blocks"] >= 16 -class TestEnvVars(PillowTestCase): - def tearDown(self): +class TestEnvVars: + def teardown_method(self): # Restore default values Image.core.set_alignment(1) Image.core.set_block_size(1024 * 1024) @@ -165,9 +173,9 @@ class TestEnvVars(PillowTestCase): def test_units(self): Image._apply_env_variables({"PILLOW_BLOCKS_MAX": "2K"}) - self.assertEqual(Image.core.get_blocks_max(), 2 * 1024) + assert Image.core.get_blocks_max() == 2 * 1024 Image._apply_env_variables({"PILLOW_BLOCK_SIZE": "2m"}) - self.assertEqual(Image.core.get_block_size(), 2 * 1024 * 1024) + assert Image.core.get_block_size() == 2 * 1024 * 1024 def test_warnings(self): pytest.warns( diff --git a/Tests/test_decompression_bomb.py b/Tests/test_decompression_bomb.py index e55cf23dc..7671cdc09 100644 --- a/Tests/test_decompression_bomb.py +++ b/Tests/test_decompression_bomb.py @@ -1,15 +1,17 @@ import pytest + from PIL import Image -from .helper import PillowTestCase, hopper +from .helper import hopper TEST_FILE = "Tests/images/hopper.ppm" ORIGINAL_LIMIT = Image.MAX_IMAGE_PIXELS -class TestDecompressionBomb(PillowTestCase): - def tearDown(self): +class TestDecompressionBomb: + @classmethod + def teardown_class(cls): Image.MAX_IMAGE_PIXELS = ORIGINAL_LIMIT def test_no_warning_small_file(self): @@ -22,7 +24,7 @@ class TestDecompressionBomb(PillowTestCase): # Arrange # Turn limit off Image.MAX_IMAGE_PIXELS = None - self.assertIsNone(Image.MAX_IMAGE_PIXELS) + assert Image.MAX_IMAGE_PIXELS is None # Act / Assert # Implicit assert: no warning. @@ -33,7 +35,7 @@ class TestDecompressionBomb(PillowTestCase): def test_warning(self): # Set limit to trigger warning on the test file Image.MAX_IMAGE_PIXELS = 128 * 128 - 1 - self.assertEqual(Image.MAX_IMAGE_PIXELS, 128 * 128 - 1) + assert Image.MAX_IMAGE_PIXELS == 128 * 128 - 1 def open(): with Image.open(TEST_FILE): @@ -44,35 +46,41 @@ class TestDecompressionBomb(PillowTestCase): def test_exception(self): # Set limit to trigger exception on the test file Image.MAX_IMAGE_PIXELS = 64 * 128 - 1 - self.assertEqual(Image.MAX_IMAGE_PIXELS, 64 * 128 - 1) + assert Image.MAX_IMAGE_PIXELS == 64 * 128 - 1 - with self.assertRaises(Image.DecompressionBombError): + with pytest.raises(Image.DecompressionBombError): with Image.open(TEST_FILE): pass def test_exception_ico(self): - with self.assertRaises(Image.DecompressionBombError): + with pytest.raises(Image.DecompressionBombError): Image.open("Tests/images/decompression_bomb.ico") def test_exception_gif(self): - with self.assertRaises(Image.DecompressionBombError): + with pytest.raises(Image.DecompressionBombError): Image.open("Tests/images/decompression_bomb.gif") + def test_exception_bmp(self): + with pytest.raises(Image.DecompressionBombError): + Image.open("Tests/images/bmp/b/reallybig.bmp") -class TestDecompressionCrop(PillowTestCase): - def setUp(self): - self.src = hopper() - self.addCleanup(self.src.close) - Image.MAX_IMAGE_PIXELS = self.src.height * self.src.width * 4 - 1 - def tearDown(self): +class TestDecompressionCrop: + @classmethod + def setup_class(self): + width, height = 128, 128 + Image.MAX_IMAGE_PIXELS = height * width * 4 - 1 + + @classmethod + def teardown_class(self): Image.MAX_IMAGE_PIXELS = ORIGINAL_LIMIT def testEnlargeCrop(self): # Crops can extend the extents, therefore we should have the # same decompression bomb warnings on them. - box = (0, 0, self.src.width * 2, self.src.height * 2) - pytest.warns(Image.DecompressionBombWarning, self.src.crop, box) + with hopper() as src: + box = (0, 0, src.width * 2, src.height * 2) + pytest.warns(Image.DecompressionBombWarning, src.crop, box) def test_crop_decompression_checks(self): @@ -85,11 +93,11 @@ class TestDecompressionCrop(PillowTestCase): error_values = ((-99909, -99990, 99999, 99999), (99909, 99990, -99999, -99999)) for value in good_values: - self.assertEqual(im.crop(value).size, (9, 9)) + assert im.crop(value).size == (9, 9) for value in warning_values: pytest.warns(Image.DecompressionBombWarning, im.crop, value) for value in error_values: - with self.assertRaises(Image.DecompressionBombError): + with pytest.raises(Image.DecompressionBombError): im.crop(value) diff --git a/Tests/test_features.py b/Tests/test_features.py index 88d10f652..284f72205 100644 --- a/Tests/test_features.py +++ b/Tests/test_features.py @@ -1,14 +1,16 @@ import io +import re import pytest + from PIL import features +from .helper import skip_unless_feature + try: from PIL import _webp - - HAVE_WEBP = True except ImportError: - HAVE_WEBP = False + pass def test_check(): @@ -21,29 +23,70 @@ def test_check(): assert features.check_feature(feature) == features.check(feature) -@pytest.mark.skipif(not HAVE_WEBP, reason="WebP not available") +def test_version(): + # Check the correctness of the convenience function + # and the format of version numbers + + def test(name, function): + version = features.version(name) + if not features.check(name): + assert version is None + else: + assert function(name) == version + if name != "PIL": + assert version is None or re.search(r"\d+(\.\d+)*$", version) + + for module in features.modules: + test(module, features.version_module) + for codec in features.codecs: + test(codec, features.version_codec) + for feature in features.features: + test(feature, features.version_feature) + + +@skip_unless_feature("webp") def test_webp_transparency(): assert features.check("transp_webp") != _webp.WebPDecoderBuggyAlpha() assert features.check("transp_webp") == _webp.HAVE_TRANSPARENCY -@pytest.mark.skipif(not HAVE_WEBP, reason="WebP not available") +@skip_unless_feature("webp") def test_webp_mux(): assert features.check("webp_mux") == _webp.HAVE_WEBPMUX -@pytest.mark.skipif(not HAVE_WEBP, reason="WebP not available") +@skip_unless_feature("webp") def test_webp_anim(): assert features.check("webp_anim") == _webp.HAVE_WEBPANIM +@skip_unless_feature("libjpeg_turbo") +def test_libjpeg_turbo_version(): + assert re.search(r"\d+\.\d+\.\d+$", features.version("libjpeg_turbo")) + + +@skip_unless_feature("libimagequant") +def test_libimagequant_version(): + assert re.search(r"\d+\.\d+\.\d+$", features.version("libimagequant")) + + def test_check_modules(): for feature in features.modules: assert features.check_module(feature) in [True, False] + + +def test_check_codecs(): for feature in features.codecs: assert features.check_codec(feature) in [True, False] +def test_check_warns_on_nonexistent(): + with pytest.warns(UserWarning) as cm: + has_feature = features.check("typo") + assert has_feature is False + assert str(cm[-1].message) == "Unknown feature 'typo'." + + def test_supported_modules(): assert isinstance(features.get_supported_modules(), list) assert isinstance(features.get_supported_codecs(), list) @@ -57,6 +100,8 @@ def test_unsupported_codec(): # Act / Assert with pytest.raises(ValueError): features.check_codec(codec) + with pytest.raises(ValueError): + features.version_codec(codec) def test_unsupported_module(): @@ -65,6 +110,8 @@ def test_unsupported_module(): # Act / Assert with pytest.raises(ValueError): features.check_module(module) + with pytest.raises(ValueError): + features.version_module(module) def test_pilinfo(): diff --git a/Tests/test_file_apng.py b/Tests/test_file_apng.py new file mode 100644 index 000000000..97e2a150e --- /dev/null +++ b/Tests/test_file_apng.py @@ -0,0 +1,611 @@ +import pytest + +from PIL import Image, ImageSequence, PngImagePlugin + + +# APNG browser support tests and fixtures via: +# https://philip.html5.org/tests/apng/tests.html +# (referenced from https://wiki.mozilla.org/APNG_Specification) +def test_apng_basic(): + with Image.open("Tests/images/apng/single_frame.png") as im: + assert not im.is_animated + assert im.n_frames == 1 + assert im.get_format_mimetype() == "image/apng" + assert im.info.get("default_image") is None + assert im.getpixel((0, 0)) == (0, 255, 0, 255) + assert im.getpixel((64, 32)) == (0, 255, 0, 255) + + with Image.open("Tests/images/apng/single_frame_default.png") as im: + assert im.is_animated + assert im.n_frames == 2 + assert im.get_format_mimetype() == "image/apng" + assert im.info.get("default_image") + assert im.getpixel((0, 0)) == (255, 0, 0, 255) + assert im.getpixel((64, 32)) == (255, 0, 0, 255) + im.seek(1) + assert im.getpixel((0, 0)) == (0, 255, 0, 255) + assert im.getpixel((64, 32)) == (0, 255, 0, 255) + + # test out of bounds seek + with pytest.raises(EOFError): + im.seek(2) + + # test rewind support + im.seek(0) + assert im.getpixel((0, 0)) == (255, 0, 0, 255) + assert im.getpixel((64, 32)) == (255, 0, 0, 255) + im.seek(1) + assert im.getpixel((0, 0)) == (0, 255, 0, 255) + assert im.getpixel((64, 32)) == (0, 255, 0, 255) + + +def test_apng_fdat(): + with Image.open("Tests/images/apng/split_fdat.png") as im: + im.seek(im.n_frames - 1) + assert im.getpixel((0, 0)) == (0, 255, 0, 255) + assert im.getpixel((64, 32)) == (0, 255, 0, 255) + + with Image.open("Tests/images/apng/split_fdat_zero_chunk.png") as im: + im.seek(im.n_frames - 1) + assert im.getpixel((0, 0)) == (0, 255, 0, 255) + assert im.getpixel((64, 32)) == (0, 255, 0, 255) + + +def test_apng_dispose(): + with Image.open("Tests/images/apng/dispose_op_none.png") as im: + im.seek(im.n_frames - 1) + assert im.getpixel((0, 0)) == (0, 255, 0, 255) + assert im.getpixel((64, 32)) == (0, 255, 0, 255) + + with Image.open("Tests/images/apng/dispose_op_background.png") as im: + im.seek(im.n_frames - 1) + assert im.getpixel((0, 0)) == (0, 0, 0, 0) + assert im.getpixel((64, 32)) == (0, 0, 0, 0) + + with Image.open("Tests/images/apng/dispose_op_background_final.png") as im: + im.seek(im.n_frames - 1) + assert im.getpixel((0, 0)) == (0, 255, 0, 255) + assert im.getpixel((64, 32)) == (0, 255, 0, 255) + + with Image.open("Tests/images/apng/dispose_op_previous.png") as im: + im.seek(im.n_frames - 1) + assert im.getpixel((0, 0)) == (0, 255, 0, 255) + assert im.getpixel((64, 32)) == (0, 255, 0, 255) + + with Image.open("Tests/images/apng/dispose_op_previous_final.png") as im: + im.seek(im.n_frames - 1) + assert im.getpixel((0, 0)) == (0, 255, 0, 255) + assert im.getpixel((64, 32)) == (0, 255, 0, 255) + + with Image.open("Tests/images/apng/dispose_op_previous_first.png") as im: + im.seek(im.n_frames - 1) + assert im.getpixel((0, 0)) == (0, 0, 0, 0) + assert im.getpixel((64, 32)) == (0, 0, 0, 0) + + +def test_apng_dispose_region(): + with Image.open("Tests/images/apng/dispose_op_none_region.png") as im: + im.seek(im.n_frames - 1) + assert im.getpixel((0, 0)) == (0, 255, 0, 255) + assert im.getpixel((64, 32)) == (0, 255, 0, 255) + + with Image.open("Tests/images/apng/dispose_op_background_before_region.png") as im: + im.seek(im.n_frames - 1) + assert im.getpixel((0, 0)) == (0, 0, 0, 0) + assert im.getpixel((64, 32)) == (0, 0, 0, 0) + + with Image.open("Tests/images/apng/dispose_op_background_region.png") as im: + im.seek(im.n_frames - 1) + assert im.getpixel((0, 0)) == (0, 0, 255, 255) + assert im.getpixel((64, 32)) == (0, 0, 0, 0) + + with Image.open("Tests/images/apng/dispose_op_previous_region.png") as im: + im.seek(im.n_frames - 1) + assert im.getpixel((0, 0)) == (0, 255, 0, 255) + assert im.getpixel((64, 32)) == (0, 255, 0, 255) + + +def test_apng_dispose_op_previous_frame(): + # Test that the dispose settings being used are from the previous frame + # + # Image created with: + # red = Image.new("RGBA", (128, 64), (255, 0, 0, 255)) + # green = red.copy() + # green.paste(Image.new("RGBA", (64, 32), (0, 255, 0, 255))) + # blue = red.copy() + # blue.paste(Image.new("RGBA", (64, 32), (0, 255, 0, 255)), (64, 32)) + # + # red.save( + # "Tests/images/apng/dispose_op_previous_frame.png", + # save_all=True, + # append_images=[green, blue], + # disposal=[ + # PngImagePlugin.APNG_DISPOSE_OP_NONE, + # PngImagePlugin.APNG_DISPOSE_OP_PREVIOUS, + # PngImagePlugin.APNG_DISPOSE_OP_PREVIOUS + # ], + # ) + with Image.open("Tests/images/apng/dispose_op_previous_frame.png") as im: + im.seek(im.n_frames - 1) + assert im.getpixel((0, 0)) == (255, 0, 0, 255) + + +def test_apng_dispose_op_background_p_mode(): + with Image.open("Tests/images/apng/dispose_op_background_p_mode.png") as im: + im.seek(1) + im.load() + assert im.size == (128, 64) + + +def test_apng_blend(): + with Image.open("Tests/images/apng/blend_op_source_solid.png") as im: + im.seek(im.n_frames - 1) + assert im.getpixel((0, 0)) == (0, 255, 0, 255) + assert im.getpixel((64, 32)) == (0, 255, 0, 255) + + with Image.open("Tests/images/apng/blend_op_source_transparent.png") as im: + im.seek(im.n_frames - 1) + assert im.getpixel((0, 0)) == (0, 0, 0, 0) + assert im.getpixel((64, 32)) == (0, 0, 0, 0) + + with Image.open("Tests/images/apng/blend_op_source_near_transparent.png") as im: + im.seek(im.n_frames - 1) + assert im.getpixel((0, 0)) == (0, 255, 0, 2) + assert im.getpixel((64, 32)) == (0, 255, 0, 2) + + with Image.open("Tests/images/apng/blend_op_over.png") as im: + im.seek(im.n_frames - 1) + assert im.getpixel((0, 0)) == (0, 255, 0, 255) + assert im.getpixel((64, 32)) == (0, 255, 0, 255) + + with Image.open("Tests/images/apng/blend_op_over_near_transparent.png") as im: + im.seek(im.n_frames - 1) + assert im.getpixel((0, 0)) == (0, 255, 0, 97) + assert im.getpixel((64, 32)) == (0, 255, 0, 255) + + +def test_apng_chunk_order(): + with Image.open("Tests/images/apng/fctl_actl.png") as im: + im.seek(im.n_frames - 1) + assert im.getpixel((0, 0)) == (0, 255, 0, 255) + assert im.getpixel((64, 32)) == (0, 255, 0, 255) + + +def test_apng_delay(): + with Image.open("Tests/images/apng/delay.png") as im: + im.seek(1) + assert im.info.get("duration") == 500.0 + im.seek(2) + assert im.info.get("duration") == 1000.0 + im.seek(3) + assert im.info.get("duration") == 500.0 + im.seek(4) + assert im.info.get("duration") == 1000.0 + + with Image.open("Tests/images/apng/delay_round.png") as im: + im.seek(1) + assert im.info.get("duration") == 500.0 + im.seek(2) + assert im.info.get("duration") == 1000.0 + + with Image.open("Tests/images/apng/delay_short_max.png") as im: + im.seek(1) + assert im.info.get("duration") == 500.0 + im.seek(2) + assert im.info.get("duration") == 1000.0 + + with Image.open("Tests/images/apng/delay_zero_denom.png") as im: + im.seek(1) + assert im.info.get("duration") == 500.0 + im.seek(2) + assert im.info.get("duration") == 1000.0 + + with Image.open("Tests/images/apng/delay_zero_numer.png") as im: + im.seek(1) + assert im.info.get("duration") == 0.0 + im.seek(2) + assert im.info.get("duration") == 0.0 + im.seek(3) + assert im.info.get("duration") == 500.0 + im.seek(4) + assert im.info.get("duration") == 1000.0 + + +def test_apng_num_plays(): + with Image.open("Tests/images/apng/num_plays.png") as im: + assert im.info.get("loop") == 0 + + with Image.open("Tests/images/apng/num_plays_1.png") as im: + assert im.info.get("loop") == 1 + + +def test_apng_mode(): + with Image.open("Tests/images/apng/mode_16bit.png") as im: + assert im.mode == "RGBA" + im.seek(im.n_frames - 1) + assert im.getpixel((0, 0)) == (0, 0, 128, 191) + assert im.getpixel((64, 32)) == (0, 0, 128, 191) + + with Image.open("Tests/images/apng/mode_greyscale.png") as im: + assert im.mode == "L" + im.seek(im.n_frames - 1) + assert im.getpixel((0, 0)) == 128 + assert im.getpixel((64, 32)) == 255 + + with Image.open("Tests/images/apng/mode_greyscale_alpha.png") as im: + assert im.mode == "LA" + im.seek(im.n_frames - 1) + assert im.getpixel((0, 0)) == (128, 191) + assert im.getpixel((64, 32)) == (128, 191) + + with Image.open("Tests/images/apng/mode_palette.png") as im: + assert im.mode == "P" + im.seek(im.n_frames - 1) + im = im.convert("RGB") + assert im.getpixel((0, 0)) == (0, 255, 0) + assert im.getpixel((64, 32)) == (0, 255, 0) + + with Image.open("Tests/images/apng/mode_palette_alpha.png") as im: + assert im.mode == "P" + im.seek(im.n_frames - 1) + im = im.convert("RGBA") + assert im.getpixel((0, 0)) == (0, 255, 0, 255) + assert im.getpixel((64, 32)) == (0, 255, 0, 255) + + with Image.open("Tests/images/apng/mode_palette_1bit_alpha.png") as im: + assert im.mode == "P" + im.seek(im.n_frames - 1) + im = im.convert("RGBA") + assert im.getpixel((0, 0)) == (0, 0, 255, 128) + assert im.getpixel((64, 32)) == (0, 0, 255, 128) + + +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 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 + + with Image.open("Tests/images/apng/chunk_no_fctl.png") as im: + with pytest.raises(SyntaxError): + im.seek(im.n_frames - 1) + + with Image.open("Tests/images/apng/chunk_repeat_fctl.png") as im: + with pytest.raises(SyntaxError): + im.seek(im.n_frames - 1) + + with Image.open("Tests/images/apng/chunk_no_fdat.png") as im: + with pytest.raises(SyntaxError): + im.seek(im.n_frames - 1) + + +def test_apng_syntax_errors(): + def open_frames_zero(): + 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 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: + try: + im.seek(im.n_frames - 1) + except Exception as e: + exception = e + assert exception is None + + with pytest.raises(SyntaxError): + with Image.open("Tests/images/apng/syntax_num_frames_high.png") as im: + im.seek(im.n_frames - 1) + im.load() + + def open(): + with Image.open("Tests/images/apng/syntax_num_frames_invalid.png") as im: + assert not im.is_animated + im.load() + + pytest.warns(UserWarning, open) + + +def test_apng_sequence_errors(): + test_files = [ + "sequence_start.png", + "sequence_gap.png", + "sequence_repeat.png", + "sequence_repeat_chunk.png", + "sequence_reorder.png", + "sequence_reorder_chunk.png", + "sequence_fdat_fctl.png", + ] + for f in test_files: + with pytest.raises(SyntaxError): + with Image.open(f"Tests/images/apng/{f}") as im: + im.seek(im.n_frames - 1) + im.load() + + +def test_apng_save(tmp_path): + with Image.open("Tests/images/apng/single_frame.png") as im: + test_file = str(tmp_path / "temp.png") + im.save(test_file, save_all=True) + + with Image.open(test_file) as im: + im.load() + assert not im.is_animated + assert im.n_frames == 1 + assert im.get_format_mimetype() == "image/apng" + assert im.info.get("default_image") is None + assert im.getpixel((0, 0)) == (0, 255, 0, 255) + assert im.getpixel((64, 32)) == (0, 255, 0, 255) + + with Image.open("Tests/images/apng/single_frame_default.png") as im: + frames = [] + for frame_im in ImageSequence.Iterator(im): + frames.append(frame_im.copy()) + frames[0].save( + test_file, save_all=True, default_image=True, append_images=frames[1:] + ) + + with Image.open(test_file) as im: + im.load() + assert im.is_animated + assert im.n_frames == 2 + assert im.get_format_mimetype() == "image/apng" + assert im.info.get("default_image") + im.seek(1) + assert im.getpixel((0, 0)) == (0, 255, 0, 255) + assert im.getpixel((64, 32)) == (0, 255, 0, 255) + + +def test_apng_save_split_fdat(tmp_path): + # test to make sure we do not generate sequence errors when writing + # frames with image data spanning multiple fdAT chunks (in this case + # both the default image and first animation frame will span multiple + # data chunks) + test_file = str(tmp_path / "temp.png") + with Image.open("Tests/images/old-style-jpeg-compression.png") as im: + frames = [im.copy(), Image.new("RGBA", im.size, (255, 0, 0, 255))] + im.save( + test_file, + save_all=True, + default_image=True, + append_images=frames, + ) + with Image.open(test_file) as im: + exception = None + try: + im.seek(im.n_frames - 1) + im.load() + except Exception as e: + exception = e + assert exception is None + + +def test_apng_save_duration_loop(tmp_path): + test_file = str(tmp_path / "temp.png") + with Image.open("Tests/images/apng/delay.png") as im: + frames = [] + durations = [] + loop = im.info.get("loop") + default_image = im.info.get("default_image") + for i, frame_im in enumerate(ImageSequence.Iterator(im)): + frames.append(frame_im.copy()) + if i != 0 or not default_image: + durations.append(frame_im.info.get("duration", 0)) + frames[0].save( + test_file, + save_all=True, + default_image=default_image, + append_images=frames[1:], + duration=durations, + loop=loop, + ) + + with Image.open(test_file) as im: + im.load() + assert im.info.get("loop") == loop + im.seek(1) + assert im.info.get("duration") == 500.0 + im.seek(2) + assert im.info.get("duration") == 1000.0 + im.seek(3) + assert im.info.get("duration") == 500.0 + im.seek(4) + assert im.info.get("duration") == 1000.0 + + # test removal of duplicated frames + frame = Image.new("RGBA", (128, 64), (255, 0, 0, 255)) + frame.save(test_file, save_all=True, append_images=[frame], duration=[500, 250]) + with Image.open(test_file) as im: + im.load() + assert im.n_frames == 1 + assert im.info.get("duration") == 750 + + +def test_apng_save_disposal(tmp_path): + test_file = str(tmp_path / "temp.png") + size = (128, 64) + red = Image.new("RGBA", size, (255, 0, 0, 255)) + green = Image.new("RGBA", size, (0, 255, 0, 255)) + transparent = Image.new("RGBA", size, (0, 0, 0, 0)) + + # test APNG_DISPOSE_OP_NONE + red.save( + test_file, + save_all=True, + append_images=[green, transparent], + disposal=PngImagePlugin.APNG_DISPOSE_OP_NONE, + blend=PngImagePlugin.APNG_BLEND_OP_OVER, + ) + with Image.open(test_file) as im: + im.seek(2) + assert im.getpixel((0, 0)) == (0, 255, 0, 255) + assert im.getpixel((64, 32)) == (0, 255, 0, 255) + + # test APNG_DISPOSE_OP_BACKGROUND + disposal = [ + PngImagePlugin.APNG_DISPOSE_OP_NONE, + PngImagePlugin.APNG_DISPOSE_OP_BACKGROUND, + PngImagePlugin.APNG_DISPOSE_OP_NONE, + ] + red.save( + test_file, + save_all=True, + append_images=[red, transparent], + disposal=disposal, + blend=PngImagePlugin.APNG_BLEND_OP_OVER, + ) + with Image.open(test_file) as im: + im.seek(2) + assert im.getpixel((0, 0)) == (0, 0, 0, 0) + assert im.getpixel((64, 32)) == (0, 0, 0, 0) + + disposal = [ + PngImagePlugin.APNG_DISPOSE_OP_NONE, + PngImagePlugin.APNG_DISPOSE_OP_BACKGROUND, + ] + red.save( + test_file, + save_all=True, + append_images=[green], + disposal=disposal, + blend=PngImagePlugin.APNG_BLEND_OP_OVER, + ) + with Image.open(test_file) as im: + im.seek(1) + assert im.getpixel((0, 0)) == (0, 255, 0, 255) + assert im.getpixel((64, 32)) == (0, 255, 0, 255) + + # test APNG_DISPOSE_OP_PREVIOUS + disposal = [ + PngImagePlugin.APNG_DISPOSE_OP_NONE, + PngImagePlugin.APNG_DISPOSE_OP_PREVIOUS, + PngImagePlugin.APNG_DISPOSE_OP_NONE, + ] + red.save( + test_file, + save_all=True, + append_images=[green, red, transparent], + default_image=True, + disposal=disposal, + blend=PngImagePlugin.APNG_BLEND_OP_OVER, + ) + with Image.open(test_file) as im: + im.seek(3) + assert im.getpixel((0, 0)) == (0, 255, 0, 255) + assert im.getpixel((64, 32)) == (0, 255, 0, 255) + + disposal = [ + PngImagePlugin.APNG_DISPOSE_OP_NONE, + PngImagePlugin.APNG_DISPOSE_OP_PREVIOUS, + ] + red.save( + test_file, + save_all=True, + append_images=[green], + disposal=disposal, + blend=PngImagePlugin.APNG_BLEND_OP_OVER, + ) + with Image.open(test_file) as im: + im.seek(1) + assert im.getpixel((0, 0)) == (0, 255, 0, 255) + assert im.getpixel((64, 32)) == (0, 255, 0, 255) + + +def test_apng_save_disposal_previous(tmp_path): + test_file = str(tmp_path / "temp.png") + size = (128, 64) + transparent = Image.new("RGBA", size, (0, 0, 0, 0)) + red = Image.new("RGBA", size, (255, 0, 0, 255)) + green = Image.new("RGBA", size, (0, 255, 0, 255)) + + # test APNG_DISPOSE_OP_NONE + transparent.save( + test_file, + save_all=True, + append_images=[red, green], + disposal=PngImagePlugin.APNG_DISPOSE_OP_PREVIOUS, + ) + with Image.open(test_file) as im: + im.seek(2) + assert im.getpixel((0, 0)) == (0, 255, 0, 255) + assert im.getpixel((64, 32)) == (0, 255, 0, 255) + + +def test_apng_save_blend(tmp_path): + test_file = str(tmp_path / "temp.png") + size = (128, 64) + red = Image.new("RGBA", size, (255, 0, 0, 255)) + green = Image.new("RGBA", size, (0, 255, 0, 255)) + transparent = Image.new("RGBA", size, (0, 0, 0, 0)) + + # test APNG_BLEND_OP_SOURCE on solid color + blend = [ + PngImagePlugin.APNG_BLEND_OP_OVER, + PngImagePlugin.APNG_BLEND_OP_SOURCE, + ] + red.save( + test_file, + save_all=True, + append_images=[red, green], + default_image=True, + disposal=PngImagePlugin.APNG_DISPOSE_OP_NONE, + blend=blend, + ) + with Image.open(test_file) as im: + im.seek(2) + assert im.getpixel((0, 0)) == (0, 255, 0, 255) + assert im.getpixel((64, 32)) == (0, 255, 0, 255) + + # test APNG_BLEND_OP_SOURCE on transparent color + blend = [ + PngImagePlugin.APNG_BLEND_OP_OVER, + PngImagePlugin.APNG_BLEND_OP_SOURCE, + ] + red.save( + test_file, + save_all=True, + append_images=[red, transparent], + default_image=True, + disposal=PngImagePlugin.APNG_DISPOSE_OP_NONE, + blend=blend, + ) + with Image.open(test_file) as im: + im.seek(2) + assert im.getpixel((0, 0)) == (0, 0, 0, 0) + assert im.getpixel((64, 32)) == (0, 0, 0, 0) + + # test APNG_BLEND_OP_OVER + red.save( + test_file, + save_all=True, + append_images=[green, transparent], + default_image=True, + disposal=PngImagePlugin.APNG_DISPOSE_OP_NONE, + blend=PngImagePlugin.APNG_BLEND_OP_OVER, + ) + with Image.open(test_file) as im: + im.seek(1) + assert im.getpixel((0, 0)) == (0, 255, 0, 255) + assert im.getpixel((64, 32)) == (0, 255, 0, 255) + im.seek(2) + assert im.getpixel((0, 0)) == (0, 255, 0, 255) + assert im.getpixel((64, 32)) == (0, 255, 0, 255) diff --git a/Tests/test_file_blp.py b/Tests/test_file_blp.py index d0bfe6eda..94c469c7f 100644 --- a/Tests/test_file_blp.py +++ b/Tests/test_file_blp.py @@ -1,20 +1,21 @@ from PIL import Image -from .helper import PillowTestCase, assert_image_equal +from .helper import assert_image_equal -class TestFileBlp(PillowTestCase): - def test_load_blp2_raw(self): - with Image.open("Tests/images/blp/blp2_raw.blp") as im: - with Image.open("Tests/images/blp/blp2_raw.png") as target: - assert_image_equal(im, target) +def test_load_blp2_raw(): + with Image.open("Tests/images/blp/blp2_raw.blp") as im: + with Image.open("Tests/images/blp/blp2_raw.png") as target: + assert_image_equal(im, target) - def test_load_blp2_dxt1(self): - with Image.open("Tests/images/blp/blp2_dxt1.blp") as im: - with Image.open("Tests/images/blp/blp2_dxt1.png") as target: - assert_image_equal(im, target) - def test_load_blp2_dxt1a(self): - with Image.open("Tests/images/blp/blp2_dxt1a.blp") as im: - with Image.open("Tests/images/blp/blp2_dxt1a.png") as target: - assert_image_equal(im, target) +def test_load_blp2_dxt1(): + with Image.open("Tests/images/blp/blp2_dxt1.blp") as im: + with Image.open("Tests/images/blp/blp2_dxt1.png") as target: + assert_image_equal(im, target) + + +def test_load_blp2_dxt1a(): + with Image.open("Tests/images/blp/blp2_dxt1a.blp") as im: + with Image.open("Tests/images/blp/blp2_dxt1a.png") as target: + assert_image_equal(im, target) diff --git a/Tests/test_file_bmp.py b/Tests/test_file_bmp.py index b3a01b95a..e2381df1e 100644 --- a/Tests/test_file_bmp.py +++ b/Tests/test_file_bmp.py @@ -1,128 +1,140 @@ import io +import pytest + from PIL import BmpImagePlugin, Image -from .helper import PillowTestCase, assert_image_equal, hopper +from .helper import assert_image_equal, hopper -class TestFileBmp(PillowTestCase): - def roundtrip(self, im): - outfile = self.tempfile("temp.bmp") +def test_sanity(tmp_path): + def roundtrip(im): + outfile = str(tmp_path / "temp.bmp") im.save(outfile, "BMP") with Image.open(outfile) as reloaded: reloaded.load() - self.assertEqual(im.mode, reloaded.mode) - self.assertEqual(im.size, reloaded.size) - self.assertEqual(reloaded.format, "BMP") - self.assertEqual(reloaded.get_format_mimetype(), "image/bmp") + assert im.mode == reloaded.mode + assert im.size == reloaded.size + assert reloaded.format == "BMP" + assert reloaded.get_format_mimetype() == "image/bmp" - def test_sanity(self): - self.roundtrip(hopper()) + roundtrip(hopper()) - self.roundtrip(hopper("1")) - self.roundtrip(hopper("L")) - self.roundtrip(hopper("P")) - self.roundtrip(hopper("RGB")) + roundtrip(hopper("1")) + roundtrip(hopper("L")) + roundtrip(hopper("P")) + roundtrip(hopper("RGB")) - def test_invalid_file(self): - with open("Tests/images/flower.jpg", "rb") as fp: - self.assertRaises(SyntaxError, BmpImagePlugin.BmpImageFile, fp) - def test_save_to_bytes(self): - output = io.BytesIO() - im = hopper() - im.save(output, "BMP") +def test_invalid_file(): + with open("Tests/images/flower.jpg", "rb") as fp: + with pytest.raises(SyntaxError): + BmpImagePlugin.BmpImageFile(fp) - output.seek(0) - with Image.open(output) as reloaded: - self.assertEqual(im.mode, reloaded.mode) - self.assertEqual(im.size, reloaded.size) - self.assertEqual(reloaded.format, "BMP") - def test_save_too_large(self): - outfile = self.tempfile("temp.bmp") - with Image.new("RGB", (1, 1)) as im: - im._size = (37838, 37838) - with self.assertRaises(ValueError): - im.save(outfile) +def test_save_to_bytes(): + output = io.BytesIO() + im = hopper() + im.save(output, "BMP") - def test_dpi(self): - dpi = (72, 72) + output.seek(0) + with Image.open(output) as reloaded: + assert im.mode == reloaded.mode + assert im.size == reloaded.size + assert reloaded.format == "BMP" - output = io.BytesIO() - with hopper() as im: - im.save(output, "BMP", dpi=dpi) - output.seek(0) - with Image.open(output) as reloaded: - self.assertEqual(reloaded.info["dpi"], dpi) - - def test_save_bmp_with_dpi(self): - # Test for #1301 - # Arrange - outfile = self.tempfile("temp.jpg") - with Image.open("Tests/images/hopper.bmp") as im: - - # Act - im.save(outfile, "JPEG", dpi=im.info["dpi"]) - - # Assert - with Image.open(outfile) as reloaded: - reloaded.load() - self.assertEqual(im.info["dpi"], reloaded.info["dpi"]) - self.assertEqual(im.size, reloaded.size) - self.assertEqual(reloaded.format, "JPEG") - - def test_load_dpi_rounding(self): - # Round up - with Image.open("Tests/images/hopper.bmp") as im: - self.assertEqual(im.info["dpi"], (96, 96)) - - # Round down - with Image.open("Tests/images/hopper_roundDown.bmp") as im: - self.assertEqual(im.info["dpi"], (72, 72)) - - def test_save_dpi_rounding(self): - outfile = self.tempfile("temp.bmp") - with Image.open("Tests/images/hopper.bmp") as im: - im.save(outfile, dpi=(72.2, 72.2)) - with Image.open(outfile) as reloaded: - self.assertEqual(reloaded.info["dpi"], (72, 72)) - - im.save(outfile, dpi=(72.8, 72.8)) - with Image.open(outfile) as reloaded: - self.assertEqual(reloaded.info["dpi"], (73, 73)) - - def test_load_dib(self): - # test for #1293, Imagegrab returning Unsupported Bitfields Format - with Image.open("Tests/images/clipboard.dib") as im: - self.assertEqual(im.format, "DIB") - self.assertEqual(im.get_format_mimetype(), "image/bmp") - - with Image.open("Tests/images/clipboard_target.png") as target: - assert_image_equal(im, target) - - def test_save_dib(self): - outfile = self.tempfile("temp.dib") - - with Image.open("Tests/images/clipboard.dib") as im: +def test_save_too_large(tmp_path): + outfile = str(tmp_path / "temp.bmp") + with Image.new("RGB", (1, 1)) as im: + im._size = (37838, 37838) + with pytest.raises(ValueError): im.save(outfile) - with Image.open(outfile) as reloaded: - self.assertEqual(reloaded.format, "DIB") - self.assertEqual(reloaded.get_format_mimetype(), "image/bmp") - assert_image_equal(im, reloaded) - def test_rgba_bitfields(self): - # 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: +def test_dpi(): + dpi = (72, 72) - # So before the comparing the image, swap the channels - b, g, r = im.split()[1:] - im = Image.merge("RGB", (r, g, b)) + output = io.BytesIO() + with hopper() as im: + im.save(output, "BMP", dpi=dpi) - with Image.open("Tests/images/bmp/q/rgb32bf-xbgr.bmp") as target: + output.seek(0) + with Image.open(output) as reloaded: + assert reloaded.info["dpi"] == dpi + + +def test_save_bmp_with_dpi(tmp_path): + # Test for #1301 + # Arrange + outfile = str(tmp_path / "temp.jpg") + with Image.open("Tests/images/hopper.bmp") as im: + + # Act + im.save(outfile, "JPEG", dpi=im.info["dpi"]) + + # Assert + with Image.open(outfile) as reloaded: + reloaded.load() + assert im.info["dpi"] == reloaded.info["dpi"] + assert im.size == reloaded.size + assert reloaded.format == "JPEG" + + +def test_load_dpi_rounding(): + # Round up + with Image.open("Tests/images/hopper.bmp") as im: + assert im.info["dpi"] == (96, 96) + + # Round down + with Image.open("Tests/images/hopper_roundDown.bmp") as im: + assert im.info["dpi"] == (72, 72) + + +def test_save_dpi_rounding(tmp_path): + outfile = str(tmp_path / "temp.bmp") + with Image.open("Tests/images/hopper.bmp") as im: + im.save(outfile, dpi=(72.2, 72.2)) + with Image.open(outfile) as reloaded: + assert reloaded.info["dpi"] == (72, 72) + + im.save(outfile, dpi=(72.8, 72.8)) + with Image.open(outfile) as reloaded: + assert reloaded.info["dpi"] == (73, 73) + + +def test_load_dib(): + # test for #1293, Imagegrab returning Unsupported Bitfields Format + with Image.open("Tests/images/clipboard.dib") as im: + assert im.format == "DIB" + assert im.get_format_mimetype() == "image/bmp" + + with Image.open("Tests/images/clipboard_target.png") as target: assert_image_equal(im, target) + + +def test_save_dib(tmp_path): + outfile = str(tmp_path / "temp.dib") + + with Image.open("Tests/images/clipboard.dib") as im: + im.save(outfile) + + with Image.open(outfile) as reloaded: + assert reloaded.format == "DIB" + assert reloaded.get_format_mimetype() == "image/bmp" + assert_image_equal(im, reloaded) + + +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)) + + with Image.open("Tests/images/bmp/q/rgb32bf-xbgr.bmp") as target: + assert_image_equal(im, target) diff --git a/Tests/test_file_bufrstub.py b/Tests/test_file_bufrstub.py index ee6f3f2a4..11acc1c88 100644 --- a/Tests/test_file_bufrstub.py +++ b/Tests/test_file_bufrstub.py @@ -1,4 +1,5 @@ import pytest + from PIL import BufrStubImagePlugin, Image from .helper import hopper @@ -32,7 +33,7 @@ def test_load(): with Image.open(TEST_FILE) as im: # Act / Assert: stub cannot load without an implemented handler - with pytest.raises(IOError): + with pytest.raises(OSError): im.load() @@ -42,5 +43,5 @@ def test_save(tmp_path): tmpfile = str(tmp_path / "temp.bufr") # Act / Assert: stub cannot save without an implemented handler - with pytest.raises(IOError): + with pytest.raises(OSError): im.save(tmpfile) diff --git a/Tests/test_file_container.py b/Tests/test_file_container.py index 91166b39e..b752e217f 100644 --- a/Tests/test_file_container.py +++ b/Tests/test_file_container.py @@ -20,7 +20,7 @@ def test_isatty(): def test_seek_mode_0(): # Arrange mode = 0 - with open(TEST_FILE) as fh: + with open(TEST_FILE, "rb") as fh: container = ContainerIO.ContainerIO(fh, 22, 100) # Act @@ -34,7 +34,7 @@ def test_seek_mode_0(): def test_seek_mode_1(): # Arrange mode = 1 - with open(TEST_FILE) as fh: + with open(TEST_FILE, "rb") as fh: container = ContainerIO.ContainerIO(fh, 22, 100) # Act @@ -48,7 +48,7 @@ def test_seek_mode_1(): def test_seek_mode_2(): # Arrange mode = 2 - with open(TEST_FILE) as fh: + with open(TEST_FILE, "rb") as fh: container = ContainerIO.ContainerIO(fh, 22, 100) # Act @@ -61,73 +61,87 @@ def test_seek_mode_2(): def test_read_n0(): # Arrange - with open(TEST_FILE) as fh: - container = ContainerIO.ContainerIO(fh, 22, 100) + for bytesmode in (True, False): + with open(TEST_FILE, "rb" if bytesmode else "r") as fh: + container = ContainerIO.ContainerIO(fh, 22, 100) - # Act - container.seek(81) - data = container.read() + # Act + container.seek(81) + data = container.read() - # Assert - assert data == "7\nThis is line 8\n" + # Assert + if bytesmode: + data = data.decode() + assert data == "7\nThis is line 8\n" def test_read_n(): # Arrange - with open(TEST_FILE) as fh: - container = ContainerIO.ContainerIO(fh, 22, 100) + for bytesmode in (True, False): + with open(TEST_FILE, "rb" if bytesmode else "r") as fh: + container = ContainerIO.ContainerIO(fh, 22, 100) - # Act - container.seek(81) - data = container.read(3) + # Act + container.seek(81) + data = container.read(3) - # Assert - assert data == "7\nT" + # Assert + if bytesmode: + data = data.decode() + assert data == "7\nT" def test_read_eof(): # Arrange - with open(TEST_FILE) as fh: - container = ContainerIO.ContainerIO(fh, 22, 100) + for bytesmode in (True, False): + with open(TEST_FILE, "rb" if bytesmode else "r") as fh: + container = ContainerIO.ContainerIO(fh, 22, 100) - # Act - container.seek(100) - data = container.read() + # Act + container.seek(100) + data = container.read() - # Assert - assert data == "" + # Assert + if bytesmode: + data = data.decode() + assert data == "" def test_readline(): # Arrange - with open(TEST_FILE) as fh: - container = ContainerIO.ContainerIO(fh, 0, 120) + for bytesmode in (True, False): + with open(TEST_FILE, "rb" if bytesmode else "r") as fh: + container = ContainerIO.ContainerIO(fh, 0, 120) - # Act - data = container.readline() + # Act + data = container.readline() - # Assert - assert data == "This is line 1\n" + # Assert + if bytesmode: + data = data.decode() + assert data == "This is line 1\n" def test_readlines(): # Arrange - expected = [ - "This is line 1\n", - "This is line 2\n", - "This is line 3\n", - "This is line 4\n", - "This is line 5\n", - "This is line 6\n", - "This is line 7\n", - "This is line 8\n", - ] - with open(TEST_FILE) as fh: - container = ContainerIO.ContainerIO(fh, 0, 120) + for bytesmode in (True, False): + expected = [ + "This is line 1\n", + "This is line 2\n", + "This is line 3\n", + "This is line 4\n", + "This is line 5\n", + "This is line 6\n", + "This is line 7\n", + "This is line 8\n", + ] + with open(TEST_FILE, "rb" if bytesmode else "r") as fh: + container = ContainerIO.ContainerIO(fh, 0, 120) - # Act - data = container.readlines() + # Act + data = container.readlines() - # Assert - - assert data == expected + # Assert + if bytesmode: + data = [line.decode() for line in data] + assert data == expected diff --git a/Tests/test_file_cur.py b/Tests/test_file_cur.py index 3200fd8f6..f04a20a22 100644 --- a/Tests/test_file_cur.py +++ b/Tests/test_file_cur.py @@ -1,4 +1,5 @@ import pytest + from PIL import CurImagePlugin, Image TEST_FILE = "Tests/images/deerstalker.cur" diff --git a/Tests/test_file_dcx.py b/Tests/test_file_dcx.py index 36f46299c..818d6ed5e 100644 --- a/Tests/test_file_dcx.py +++ b/Tests/test_file_dcx.py @@ -1,84 +1,93 @@ -import unittest - import pytest + from PIL import DcxImagePlugin, Image -from .helper import PillowTestCase, assert_image_equal, hopper, is_pypy +from .helper import assert_image_equal, hopper, is_pypy # Created with ImageMagick: convert hopper.ppm hopper.dcx TEST_FILE = "Tests/images/hopper.dcx" -class TestFileDcx(PillowTestCase): - def test_sanity(self): - # Arrange +def test_sanity(): + # Arrange + + # Act + with Image.open(TEST_FILE) as im: + + # Assert + assert im.size == (128, 128) + assert isinstance(im, DcxImagePlugin.DcxImageFile) + orig = hopper() + assert_image_equal(im, orig) + + +@pytest.mark.skipif(is_pypy(), reason="Requires CPython") +def test_unclosed_file(): + def open(): + im = Image.open(TEST_FILE) + im.load() + + pytest.warns(ResourceWarning, open) + + +def test_closed_file(): + def open(): + im = Image.open(TEST_FILE) + im.load() + im.close() + + pytest.warns(None, open) + + +def test_context_manager(): + def open(): + with Image.open(TEST_FILE) as im: + im.load() + + pytest.warns(None, open) + + +def test_invalid_file(): + with open("Tests/images/flower.jpg", "rb") as fp: + with pytest.raises(SyntaxError): + DcxImagePlugin.DcxImageFile(fp) + + +def test_tell(): + # Arrange + with Image.open(TEST_FILE) as im: # Act - with Image.open(TEST_FILE) as im: + frame = im.tell() - # Assert - self.assertEqual(im.size, (128, 128)) - self.assertIsInstance(im, DcxImagePlugin.DcxImageFile) - orig = hopper() - assert_image_equal(im, orig) + # Assert + assert frame == 0 - @unittest.skipIf(is_pypy(), "Requires CPython") - def test_unclosed_file(self): - def open(): - im = Image.open(TEST_FILE) - im.load() - pytest.warns(ResourceWarning, open) +def test_n_frames(): + with Image.open(TEST_FILE) as im: + assert im.n_frames == 1 + assert not im.is_animated - def test_closed_file(self): - def open(): - im = Image.open(TEST_FILE) - im.load() - im.close() - pytest.warns(None, open) +def test_eoferror(): + with Image.open(TEST_FILE) as im: + n_frames = im.n_frames - def test_context_manager(self): - def open(): - with Image.open(TEST_FILE) as im: - im.load() + # Test seeking past the last frame + with pytest.raises(EOFError): + im.seek(n_frames) + assert im.tell() < n_frames - pytest.warns(None, open) + # Test that seeking to the last frame does not raise an error + im.seek(n_frames - 1) - def test_invalid_file(self): - with open("Tests/images/flower.jpg", "rb") as fp: - self.assertRaises(SyntaxError, DcxImagePlugin.DcxImageFile, fp) - def test_tell(self): - # Arrange - with Image.open(TEST_FILE) as im: +def test_seek_too_far(): + # Arrange + with Image.open(TEST_FILE) as im: + frame = 999 # too big on purpose - # Act - frame = im.tell() - - # Assert - self.assertEqual(frame, 0) - - def test_n_frames(self): - with Image.open(TEST_FILE) as im: - self.assertEqual(im.n_frames, 1) - self.assertFalse(im.is_animated) - - def test_eoferror(self): - with Image.open(TEST_FILE) as im: - n_frames = im.n_frames - - # Test seeking past the last frame - self.assertRaises(EOFError, im.seek, n_frames) - self.assertLess(im.tell(), n_frames) - - # Test that seeking to the last frame does not raise an error - im.seek(n_frames - 1) - - def test_seek_too_far(self): - # Arrange - with Image.open(TEST_FILE) as im: - frame = 999 # too big on purpose - - # Act / Assert - self.assertRaises(EOFError, im.seek, frame) + # Act / Assert + with pytest.raises(EOFError): + im.seek(frame) diff --git a/Tests/test_file_dds.py b/Tests/test_file_dds.py index 4a967ce80..1cd7a1be7 100644 --- a/Tests/test_file_dds.py +++ b/Tests/test_file_dds.py @@ -1,157 +1,195 @@ +"""Test DdsImagePlugin""" from io import BytesIO +import pytest + from PIL import DdsImagePlugin, Image -from .helper import PillowTestCase, assert_image_equal +from .helper import assert_image_equal TEST_FILE_DXT1 = "Tests/images/dxt1-rgb-4bbp-noalpha_MipMaps-1.dds" TEST_FILE_DXT3 = "Tests/images/dxt3-argb-8bbp-explicitalpha_MipMaps-1.dds" TEST_FILE_DXT5 = "Tests/images/dxt5-argb-8bbp-interpolatedalpha_MipMaps-1.dds" TEST_FILE_DX10_BC7 = "Tests/images/bc7-argb-8bpp_MipMaps-1.dds" TEST_FILE_DX10_BC7_UNORM_SRGB = "Tests/images/DXGI_FORMAT_BC7_UNORM_SRGB.dds" +TEST_FILE_DX10_R8G8B8A8 = "Tests/images/argb-32bpp_MipMaps-1.dds" +TEST_FILE_DX10_R8G8B8A8_UNORM_SRGB = "Tests/images/DXGI_FORMAT_R8G8B8A8_UNORM_SRGB.dds" TEST_FILE_UNCOMPRESSED_RGB = "Tests/images/uncompressed_rgb.dds" -class TestFileDds(PillowTestCase): - """Test DdsImagePlugin""" +def test_sanity_dxt1(): + """Check DXT1 images can be opened""" + with Image.open(TEST_FILE_DXT1.replace(".dds", ".png")) as target: + target = target.convert("RGBA") + with Image.open(TEST_FILE_DXT1) as im: + im.load() - def test_sanity_dxt1(self): - """Check DXT1 images can be opened""" - with Image.open(TEST_FILE_DXT1.replace(".dds", ".png")) as target: - target = target.convert("RGBA") - with Image.open(TEST_FILE_DXT1) as im: + assert im.format == "DDS" + assert im.mode == "RGBA" + assert im.size == (256, 256) + + assert_image_equal(im, target) + + +def test_sanity_dxt5(): + """Check DXT5 images can be opened""" + + with Image.open(TEST_FILE_DXT5) as im: + im.load() + + assert im.format == "DDS" + assert im.mode == "RGBA" + assert im.size == (256, 256) + + with Image.open(TEST_FILE_DXT5.replace(".dds", ".png")) as target: + assert_image_equal(target, im) + + +def test_sanity_dxt3(): + """Check DXT3 images can be opened""" + + with Image.open(TEST_FILE_DXT3.replace(".dds", ".png")) as target: + with Image.open(TEST_FILE_DXT3) as im: im.load() - self.assertEqual(im.format, "DDS") - self.assertEqual(im.mode, "RGBA") - self.assertEqual(im.size, (256, 256)) + assert im.format == "DDS" + assert im.mode == "RGBA" + assert im.size == (256, 256) - assert_image_equal(im, target) - - def test_sanity_dxt5(self): - """Check DXT5 images can be opened""" - - with Image.open(TEST_FILE_DXT5) as im: - im.load() - - self.assertEqual(im.format, "DDS") - self.assertEqual(im.mode, "RGBA") - self.assertEqual(im.size, (256, 256)) - - with Image.open(TEST_FILE_DXT5.replace(".dds", ".png")) as target: assert_image_equal(target, im) - def test_sanity_dxt3(self): - """Check DXT3 images can be opened""" - with Image.open(TEST_FILE_DXT3.replace(".dds", ".png")) as target: - with Image.open(TEST_FILE_DXT3) as im: - im.load() +def test_dx10_bc7(): + """Check DX10 images can be opened""" - self.assertEqual(im.format, "DDS") - self.assertEqual(im.mode, "RGBA") - self.assertEqual(im.size, (256, 256)) + with Image.open(TEST_FILE_DX10_BC7) as im: + im.load() - assert_image_equal(target, im) + assert im.format == "DDS" + assert im.mode == "RGBA" + assert im.size == (256, 256) - def test_dx10_bc7(self): - """Check DX10 images can be opened""" + with Image.open(TEST_FILE_DX10_BC7.replace(".dds", ".png")) as target: + assert_image_equal(target, im) - with Image.open(TEST_FILE_DX10_BC7) as im: + +def test_dx10_bc7_unorm_srgb(): + """Check DX10 unsigned normalized integer images can be opened""" + + with Image.open(TEST_FILE_DX10_BC7_UNORM_SRGB) as im: + im.load() + + assert im.format == "DDS" + assert im.mode == "RGBA" + assert im.size == (16, 16) + assert im.info["gamma"] == 1 / 2.2 + + with Image.open( + TEST_FILE_DX10_BC7_UNORM_SRGB.replace(".dds", ".png") + ) as target: + assert_image_equal(target, im) + + +def test_dx10_r8g8b8a8(): + """Check DX10 images can be opened""" + + with Image.open(TEST_FILE_DX10_R8G8B8A8) as im: + im.load() + + assert im.format == "DDS" + assert im.mode == "RGBA" + assert im.size == (256, 256) + + with Image.open(TEST_FILE_DX10_R8G8B8A8.replace(".dds", ".png")) as target: + assert_image_equal(target, im) + + +def test_dx10_r8g8b8a8_unorm_srgb(): + """Check DX10 unsigned normalized integer images can be opened""" + + with Image.open(TEST_FILE_DX10_R8G8B8A8_UNORM_SRGB) as im: + im.load() + + assert im.format == "DDS" + assert im.mode == "RGBA" + assert im.size == (16, 16) + assert im.info["gamma"] == 1 / 2.2 + + with Image.open( + TEST_FILE_DX10_R8G8B8A8_UNORM_SRGB.replace(".dds", ".png") + ) as target: + assert_image_equal(target, im) + + +def test_unimplemented_dxgi_format(): + with pytest.raises(NotImplementedError): + Image.open("Tests/images/unimplemented_dxgi_format.dds") + + +def test_uncompressed_rgb(): + """Check uncompressed RGB images can be opened""" + + with Image.open(TEST_FILE_UNCOMPRESSED_RGB) as im: + im.load() + + assert im.format == "DDS" + assert im.mode == "RGBA" + assert im.size == (800, 600) + + with Image.open(TEST_FILE_UNCOMPRESSED_RGB.replace(".dds", ".png")) as target: + assert_image_equal(target, im) + + +def test__validate_true(): + """Check valid prefix""" + # Arrange + prefix = b"DDS etc" + + # Act + output = DdsImagePlugin._validate(prefix) + + # Assert + assert output + + +def test__validate_false(): + """Check invalid prefix""" + # Arrange + prefix = b"something invalid" + + # Act + output = DdsImagePlugin._validate(prefix) + + # Assert + assert not output + + +def test_short_header(): + """ Check a short header""" + with open(TEST_FILE_DXT5, "rb") as f: + img_file = f.read() + + def short_header(): + Image.open(BytesIO(img_file[:119])) + + with pytest.raises(OSError): + short_header() + + +def test_short_file(): + """ Check that the appropriate error is thrown for a short file""" + + with open(TEST_FILE_DXT5, "rb") as f: + img_file = f.read() + + def short_file(): + with Image.open(BytesIO(img_file[:-100])) as im: im.load() - self.assertEqual(im.format, "DDS") - self.assertEqual(im.mode, "RGBA") - self.assertEqual(im.size, (256, 256)) + with pytest.raises(OSError): + short_file() - with Image.open(TEST_FILE_DX10_BC7.replace(".dds", ".png")) as target: - assert_image_equal(target, im) - def test_dx10_bc7_unorm_srgb(self): - """Check DX10 unsigned normalized integer images can be opened""" - - with Image.open(TEST_FILE_DX10_BC7_UNORM_SRGB) as im: - im.load() - - self.assertEqual(im.format, "DDS") - self.assertEqual(im.mode, "RGBA") - self.assertEqual(im.size, (16, 16)) - self.assertEqual(im.info["gamma"], 1 / 2.2) - - with Image.open( - TEST_FILE_DX10_BC7_UNORM_SRGB.replace(".dds", ".png") - ) as target: - assert_image_equal(target, im) - - def test_unimplemented_dxgi_format(self): - self.assertRaises( - NotImplementedError, - Image.open, - "Tests/images/unimplemented_dxgi_format.dds", - ) - - def test_uncompressed_rgb(self): - """Check uncompressed RGB images can be opened""" - - with Image.open(TEST_FILE_UNCOMPRESSED_RGB) as im: - im.load() - - self.assertEqual(im.format, "DDS") - self.assertEqual(im.mode, "RGBA") - self.assertEqual(im.size, (800, 600)) - - with Image.open( - TEST_FILE_UNCOMPRESSED_RGB.replace(".dds", ".png") - ) as target: - assert_image_equal(target, im) - - def test__validate_true(self): - """Check valid prefix""" - # Arrange - prefix = b"DDS etc" - - # Act - output = DdsImagePlugin._validate(prefix) - - # Assert - self.assertTrue(output) - - def test__validate_false(self): - """Check invalid prefix""" - # Arrange - prefix = b"something invalid" - - # Act - output = DdsImagePlugin._validate(prefix) - - # Assert - self.assertFalse(output) - - def test_short_header(self): - """ Check a short header""" - with open(TEST_FILE_DXT5, "rb") as f: - img_file = f.read() - - def short_header(): - Image.open(BytesIO(img_file[:119])) - - self.assertRaises(IOError, short_header) - - def test_short_file(self): - """ Check that the appropriate error is thrown for a short file""" - - with open(TEST_FILE_DXT5, "rb") as f: - img_file = f.read() - - def short_file(): - with Image.open(BytesIO(img_file[:-100])) as im: - im.load() - - self.assertRaises(IOError, short_file) - - def test_unimplemented_pixel_format(self): - self.assertRaises( - NotImplementedError, - Image.open, - "Tests/images/unimplemented_pixel_format.dds", - ) +def test_unimplemented_pixel_format(): + with pytest.raises(NotImplementedError): + Image.open("Tests/images/unimplemented_pixel_format.dds") diff --git a/Tests/test_file_eps.py b/Tests/test_file_eps.py index c49139d82..f585a0669 100644 --- a/Tests/test_file_eps.py +++ b/Tests/test_file_eps.py @@ -1,246 +1,259 @@ import io -import unittest -from PIL import EpsImagePlugin, Image +import pytest -from .helper import PillowTestCase, assert_image_similar, hopper +from PIL import EpsImagePlugin, Image, features + +from .helper import assert_image_similar, hopper, skip_unless_feature HAS_GHOSTSCRIPT = EpsImagePlugin.has_ghostscript() # Our two EPS test files (they are identical except for their bounding boxes) -file1 = "Tests/images/zero_bb.eps" -file2 = "Tests/images/non_zero_bb.eps" +FILE1 = "Tests/images/zero_bb.eps" +FILE2 = "Tests/images/non_zero_bb.eps" # Due to palletization, we'll need to convert these to RGB after load -file1_compare = "Tests/images/zero_bb.png" -file1_compare_scale2 = "Tests/images/zero_bb_scale2.png" +FILE1_COMPARE = "Tests/images/zero_bb.png" +FILE1_COMPARE_SCALE2 = "Tests/images/zero_bb_scale2.png" -file2_compare = "Tests/images/non_zero_bb.png" -file2_compare_scale2 = "Tests/images/non_zero_bb_scale2.png" +FILE2_COMPARE = "Tests/images/non_zero_bb.png" +FILE2_COMPARE_SCALE2 = "Tests/images/non_zero_bb_scale2.png" # EPS test files with binary preview -file3 = "Tests/images/binary_preview_map.eps" +FILE3 = "Tests/images/binary_preview_map.eps" -class TestFileEps(PillowTestCase): - @unittest.skipUnless(HAS_GHOSTSCRIPT, "Ghostscript not available") - def test_sanity(self): - # Regular scale - with Image.open(file1) as image1: - image1.load() - self.assertEqual(image1.mode, "RGB") - self.assertEqual(image1.size, (460, 352)) - self.assertEqual(image1.format, "EPS") +@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available") +def test_sanity(): + # Regular scale + with Image.open(FILE1) as image1: + image1.load() + assert image1.mode == "RGB" + assert image1.size == (460, 352) + assert image1.format == "EPS" - with Image.open(file2) as image2: - image2.load() - self.assertEqual(image2.mode, "RGB") - self.assertEqual(image2.size, (360, 252)) - self.assertEqual(image2.format, "EPS") + with Image.open(FILE2) as image2: + image2.load() + assert image2.mode == "RGB" + assert image2.size == (360, 252) + assert image2.format == "EPS" - # Double scale - with Image.open(file1) as image1_scale2: - image1_scale2.load(scale=2) - self.assertEqual(image1_scale2.mode, "RGB") - self.assertEqual(image1_scale2.size, (920, 704)) - self.assertEqual(image1_scale2.format, "EPS") + # Double scale + with Image.open(FILE1) as image1_scale2: + image1_scale2.load(scale=2) + assert image1_scale2.mode == "RGB" + assert image1_scale2.size == (920, 704) + assert image1_scale2.format == "EPS" - with Image.open(file2) as image2_scale2: - image2_scale2.load(scale=2) - self.assertEqual(image2_scale2.mode, "RGB") - self.assertEqual(image2_scale2.size, (720, 504)) - self.assertEqual(image2_scale2.format, "EPS") + with Image.open(FILE2) as image2_scale2: + image2_scale2.load(scale=2) + assert image2_scale2.mode == "RGB" + assert image2_scale2.size == (720, 504) + assert image2_scale2.format == "EPS" - def test_invalid_file(self): - invalid_file = "Tests/images/flower.jpg" - self.assertRaises(SyntaxError, EpsImagePlugin.EpsImageFile, invalid_file) +def test_invalid_file(): + invalid_file = "Tests/images/flower.jpg" - @unittest.skipUnless(HAS_GHOSTSCRIPT, "Ghostscript not available") - def test_cmyk(self): - with Image.open("Tests/images/pil_sample_cmyk.eps") as cmyk_image: + with pytest.raises(SyntaxError): + EpsImagePlugin.EpsImageFile(invalid_file) - self.assertEqual(cmyk_image.mode, "CMYK") - self.assertEqual(cmyk_image.size, (100, 100)) - self.assertEqual(cmyk_image.format, "EPS") - cmyk_image.load() - self.assertEqual(cmyk_image.mode, "RGB") +@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: - if "jpeg_decoder" in dir(Image.core): - with Image.open("Tests/images/pil_sample_rgb.jpg") as target: - assert_image_similar(cmyk_image, target, 10) + assert cmyk_image.mode == "CMYK" + assert cmyk_image.size == (100, 100) + assert cmyk_image.format == "EPS" - @unittest.skipUnless(HAS_GHOSTSCRIPT, "Ghostscript not available") - def test_showpage(self): - # See https://github.com/python-pillow/Pillow/issues/2615 - with Image.open("Tests/images/reqd_showpage.eps") as plot_image: - with Image.open("Tests/images/reqd_showpage.png") as target: - # should not crash/hang - plot_image.load() - # fonts could be slightly different - assert_image_similar(plot_image, target, 6) + cmyk_image.load() + assert cmyk_image.mode == "RGB" - @unittest.skipUnless(HAS_GHOSTSCRIPT, "Ghostscript not available") - def test_file_object(self): - # issue 479 - with Image.open(file1) as image1: - with open(self.tempfile("temp_file.eps"), "wb") as fh: - image1.save(fh, "EPS") + if features.check("jpg"): + with Image.open("Tests/images/pil_sample_rgb.jpg") as target: + assert_image_similar(cmyk_image, target, 10) - @unittest.skipUnless(HAS_GHOSTSCRIPT, "Ghostscript not available") - def test_iobase_object(self): - # issue 479 - with Image.open(file1) as image1: - with open(self.tempfile("temp_iobase.eps"), "wb") as fh: - image1.save(fh, "EPS") - @unittest.skipUnless(HAS_GHOSTSCRIPT, "Ghostscript not available") - def test_bytesio_object(self): - with open(file1, "rb") as f: - img_bytes = io.BytesIO(f.read()) +@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available") +def test_showpage(): + # See https://github.com/python-pillow/Pillow/issues/2615 + with Image.open("Tests/images/reqd_showpage.eps") as plot_image: + with Image.open("Tests/images/reqd_showpage.png") as target: + # should not crash/hang + plot_image.load() + # fonts could be slightly different + assert_image_similar(plot_image, target, 6) - with Image.open(img_bytes) as img: - img.load() - with Image.open(file1_compare) as image1_scale1_compare: - image1_scale1_compare = image1_scale1_compare.convert("RGB") - image1_scale1_compare.load() - assert_image_similar(img, image1_scale1_compare, 5) +@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available") +def test_file_object(tmp_path): + # issue 479 + with Image.open(FILE1) as image1: + with open(str(tmp_path / "temp.eps"), "wb") as fh: + image1.save(fh, "EPS") - def test_image_mode_not_supported(self): - im = hopper("RGBA") - tmpfile = self.tempfile("temp.eps") - self.assertRaises(ValueError, im.save, tmpfile) - @unittest.skipUnless(HAS_GHOSTSCRIPT, "Ghostscript not available") - def test_render_scale1(self): - # We need png support for these render test - codecs = dir(Image.core) - if "zip_encoder" not in codecs or "zip_decoder" not in codecs: - self.skipTest("zip/deflate support not available") +@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available") +def test_iobase_object(tmp_path): + # issue 479 + with Image.open(FILE1) as image1: + with open(str(tmp_path / "temp_iobase.eps"), "wb") as fh: + image1.save(fh, "EPS") - # Zero bounding box - with Image.open(file1) as image1_scale1: - image1_scale1.load() - with Image.open(file1_compare) as image1_scale1_compare: - image1_scale1_compare = image1_scale1_compare.convert("RGB") - image1_scale1_compare.load() - assert_image_similar(image1_scale1, image1_scale1_compare, 5) - # Non-Zero bounding box - with Image.open(file2) as image2_scale1: - image2_scale1.load() - with Image.open(file2_compare) as image2_scale1_compare: - image2_scale1_compare = image2_scale1_compare.convert("RGB") - image2_scale1_compare.load() - assert_image_similar(image2_scale1, image2_scale1_compare, 10) +@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available") +def test_bytesio_object(): + with open(FILE1, "rb") as f: + img_bytes = io.BytesIO(f.read()) - @unittest.skipUnless(HAS_GHOSTSCRIPT, "Ghostscript not available") - def test_render_scale2(self): - # We need png support for these render test - codecs = dir(Image.core) - if "zip_encoder" not in codecs or "zip_decoder" not in codecs: - self.skipTest("zip/deflate support not available") + with Image.open(img_bytes) as img: + img.load() - # Zero bounding box - with Image.open(file1) as image1_scale2: - image1_scale2.load(scale=2) - with Image.open(file1_compare_scale2) as image1_scale2_compare: - image1_scale2_compare = image1_scale2_compare.convert("RGB") - image1_scale2_compare.load() - assert_image_similar(image1_scale2, image1_scale2_compare, 5) + with Image.open(FILE1_COMPARE) as image1_scale1_compare: + image1_scale1_compare = image1_scale1_compare.convert("RGB") + image1_scale1_compare.load() + assert_image_similar(img, image1_scale1_compare, 5) - # Non-Zero bounding box - with Image.open(file2) as image2_scale2: - image2_scale2.load(scale=2) - with Image.open(file2_compare_scale2) as image2_scale2_compare: - image2_scale2_compare = image2_scale2_compare.convert("RGB") - image2_scale2_compare.load() - assert_image_similar(image2_scale2, image2_scale2_compare, 10) - @unittest.skipUnless(HAS_GHOSTSCRIPT, "Ghostscript not available") - def test_resize(self): - files = [file1, file2, "Tests/images/illu10_preview.eps"] - for fn in files: - with Image.open(fn) as im: - new_size = (100, 100) - im = im.resize(new_size) - self.assertEqual(im.size, new_size) +def test_image_mode_not_supported(tmp_path): + im = hopper("RGBA") + tmpfile = str(tmp_path / "temp.eps") + with pytest.raises(ValueError): + im.save(tmpfile) - @unittest.skipUnless(HAS_GHOSTSCRIPT, "Ghostscript not available") - def test_thumbnail(self): - # Issue #619 - # Arrange - files = [file1, file2] - for fn in files: - with Image.open(file1) as im: - new_size = (100, 100) - im.thumbnail(new_size) - self.assertEqual(max(im.size), max(new_size)) - def test_read_binary_preview(self): - # Issue 302 - # open image with binary preview - with Image.open(file3): - pass +@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available") +@skip_unless_feature("zlib") +def test_render_scale1(): + # We need png support for these render test - def _test_readline(self, t, ending): + # Zero bounding box + with Image.open(FILE1) as image1_scale1: + image1_scale1.load() + with Image.open(FILE1_COMPARE) as image1_scale1_compare: + image1_scale1_compare = image1_scale1_compare.convert("RGB") + image1_scale1_compare.load() + assert_image_similar(image1_scale1, image1_scale1_compare, 5) + + # Non-Zero bounding box + with Image.open(FILE2) as image2_scale1: + image2_scale1.load() + with Image.open(FILE2_COMPARE) as image2_scale1_compare: + image2_scale1_compare = image2_scale1_compare.convert("RGB") + image2_scale1_compare.load() + assert_image_similar(image2_scale1, image2_scale1_compare, 10) + + +@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available") +@skip_unless_feature("zlib") +def test_render_scale2(): + # We need png support for these render test + + # Zero bounding box + with Image.open(FILE1) as image1_scale2: + image1_scale2.load(scale=2) + with Image.open(FILE1_COMPARE_SCALE2) as image1_scale2_compare: + image1_scale2_compare = image1_scale2_compare.convert("RGB") + image1_scale2_compare.load() + assert_image_similar(image1_scale2, image1_scale2_compare, 5) + + # Non-Zero bounding box + with Image.open(FILE2) as image2_scale2: + image2_scale2.load(scale=2) + with Image.open(FILE2_COMPARE_SCALE2) as image2_scale2_compare: + image2_scale2_compare = image2_scale2_compare.convert("RGB") + image2_scale2_compare.load() + assert_image_similar(image2_scale2, image2_scale2_compare, 10) + + +@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available") +def test_resize(): + files = [FILE1, FILE2, "Tests/images/illu10_preview.eps"] + for fn in files: + with Image.open(fn) as im: + new_size = (100, 100) + im = im.resize(new_size) + assert im.size == new_size + + +@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available") +def test_thumbnail(): + # Issue #619 + # Arrange + files = [FILE1, FILE2] + for fn in files: + with Image.open(FILE1) as im: + new_size = (100, 100) + im.thumbnail(new_size) + assert max(im.size) == max(new_size) + + +def test_read_binary_preview(): + # Issue 302 + # open image with binary preview + with Image.open(FILE3): + pass + + +def test_readline(tmp_path): + # check all the freaking line endings possible from the spec + # test_string = u'something\r\nelse\n\rbaz\rbif\n' + line_endings = ["\r\n", "\n", "\n\r", "\r"] + strings = ["something", "else", "baz", "bif"] + + def _test_readline(t, ending): ending = "Failure with line ending: %s" % ( "".join("%s" % ord(s) for s in ending) ) - self.assertEqual(t.readline().strip("\r\n"), "something", ending) - self.assertEqual(t.readline().strip("\r\n"), "else", ending) - self.assertEqual(t.readline().strip("\r\n"), "baz", ending) - self.assertEqual(t.readline().strip("\r\n"), "bif", ending) + assert t.readline().strip("\r\n") == "something", ending + assert t.readline().strip("\r\n") == "else", ending + assert t.readline().strip("\r\n") == "baz", ending + assert t.readline().strip("\r\n") == "bif", ending - def _test_readline_io_psfile(self, test_string, ending): + def _test_readline_io_psfile(test_string, ending): f = io.BytesIO(test_string.encode("latin-1")) t = EpsImagePlugin.PSFile(f) - self._test_readline(t, ending) + _test_readline(t, ending) - def _test_readline_file_psfile(self, test_string, ending): - f = self.tempfile("temp.txt") + def _test_readline_file_psfile(test_string, ending): + f = str(tmp_path / "temp.txt") with open(f, "wb") as w: w.write(test_string.encode("latin-1")) with open(f, "rb") as r: t = EpsImagePlugin.PSFile(r) - self._test_readline(t, ending) + _test_readline(t, ending) - def test_readline(self): - # check all the freaking line endings possible from the spec - # test_string = u'something\r\nelse\n\rbaz\rbif\n' - line_endings = ["\r\n", "\n", "\n\r", "\r"] - strings = ["something", "else", "baz", "bif"] + for ending in line_endings: + s = ending.join(strings) + _test_readline_io_psfile(s, ending) + _test_readline_file_psfile(s, ending) - for ending in line_endings: - s = ending.join(strings) - self._test_readline_io_psfile(s, ending) - self._test_readline_file_psfile(s, ending) - def test_open_eps(self): - # https://github.com/python-pillow/Pillow/issues/1104 - # Arrange - FILES = [ - "Tests/images/illu10_no_preview.eps", - "Tests/images/illu10_preview.eps", - "Tests/images/illuCS6_no_preview.eps", - "Tests/images/illuCS6_preview.eps", - ] +def test_open_eps(): + # https://github.com/python-pillow/Pillow/issues/1104 + # Arrange + FILES = [ + "Tests/images/illu10_no_preview.eps", + "Tests/images/illu10_preview.eps", + "Tests/images/illuCS6_no_preview.eps", + "Tests/images/illuCS6_preview.eps", + ] - # Act / Assert - for filename in FILES: - with Image.open(filename) as img: - self.assertEqual(img.mode, "RGB") + # Act / Assert + for filename in FILES: + with Image.open(filename) as img: + assert img.mode == "RGB" - @unittest.skipUnless(HAS_GHOSTSCRIPT, "Ghostscript not available") - def test_emptyline(self): - # Test file includes an empty line in the header data - emptyline_file = "Tests/images/zero_bb_emptyline.eps" - with Image.open(emptyline_file) as image: - image.load() - self.assertEqual(image.mode, "RGB") - self.assertEqual(image.size, (460, 352)) - self.assertEqual(image.format, "EPS") +@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available") +def test_emptyline(): + # Test file includes an empty line in the header data + emptyline_file = "Tests/images/zero_bb_emptyline.eps" + + with Image.open(emptyline_file) as image: + image.load() + assert image.mode == "RGB" + assert image.size == (460, 352) + assert image.format == "EPS" diff --git a/Tests/test_file_fitsstub.py b/Tests/test_file_fitsstub.py index f9f6c4ba9..6dc7c4602 100644 --- a/Tests/test_file_fitsstub.py +++ b/Tests/test_file_fitsstub.py @@ -1,4 +1,5 @@ import pytest + from PIL import FitsStubImagePlugin, Image TEST_FILE = "Tests/images/hopper.fits" @@ -30,7 +31,7 @@ def test_load(): with Image.open(TEST_FILE) as im: # Act / Assert: stub cannot load without an implemented handler - with pytest.raises(IOError): + with pytest.raises(OSError): im.load() @@ -41,7 +42,7 @@ def test_save(): dummy_filename = "dummy.filename" # Act / Assert: stub cannot save without an implemented handler - with pytest.raises(IOError): + with pytest.raises(OSError): im.save(dummy_filename) - with pytest.raises(IOError): + with pytest.raises(OSError): FitsStubImagePlugin._save(im, dummy_fp, dummy_filename) diff --git a/Tests/test_file_fli.py b/Tests/test_file_fli.py index 45c0fd1a1..16b3dc59a 100644 --- a/Tests/test_file_fli.py +++ b/Tests/test_file_fli.py @@ -1,9 +1,8 @@ -import unittest - import pytest + from PIL import FliImagePlugin, Image -from .helper import PillowTestCase, assert_image_equal, is_pypy +from .helper import assert_image_equal, is_pypy # created as an export of a palette image from Gimp2.6 # save as...-> hopper.fli, default options. @@ -13,105 +12,115 @@ static_test_file = "Tests/images/hopper.fli" animated_test_file = "Tests/images/a.fli" -class TestFileFli(PillowTestCase): - def test_sanity(self): +def test_sanity(): + with Image.open(static_test_file) as im: + im.load() + assert im.mode == "P" + assert im.size == (128, 128) + assert im.format == "FLI" + assert not im.is_animated + + with Image.open(animated_test_file) as im: + assert im.mode == "P" + assert im.size == (320, 200) + assert im.format == "FLI" + assert im.info["duration"] == 71 + assert im.is_animated + + +@pytest.mark.skipif(is_pypy(), reason="Requires CPython") +def test_unclosed_file(): + def open(): + im = Image.open(static_test_file) + im.load() + + pytest.warns(ResourceWarning, open) + + +def test_closed_file(): + def open(): + im = Image.open(static_test_file) + im.load() + im.close() + + pytest.warns(None, open) + + +def test_context_manager(): + def open(): with Image.open(static_test_file) as im: im.load() - self.assertEqual(im.mode, "P") - self.assertEqual(im.size, (128, 128)) - self.assertEqual(im.format, "FLI") - self.assertFalse(im.is_animated) - with Image.open(animated_test_file) as im: - self.assertEqual(im.mode, "P") - self.assertEqual(im.size, (320, 200)) - self.assertEqual(im.format, "FLI") - self.assertEqual(im.info["duration"], 71) - self.assertTrue(im.is_animated) + pytest.warns(None, open) - @unittest.skipIf(is_pypy(), "Requires CPython") - def test_unclosed_file(self): - def open(): - im = Image.open(static_test_file) - im.load() - pytest.warns(ResourceWarning, open) +def test_tell(): + # Arrange + with Image.open(static_test_file) as im: - def test_closed_file(self): - def open(): - im = Image.open(static_test_file) - im.load() - im.close() + # Act + frame = im.tell() - pytest.warns(None, open) + # Assert + assert frame == 0 - def test_context_manager(self): - def open(): - with Image.open(static_test_file) as im: - im.load() - pytest.warns(None, open) +def test_invalid_file(): + invalid_file = "Tests/images/flower.jpg" - def test_tell(self): - # Arrange - with Image.open(static_test_file) as im: + with pytest.raises(SyntaxError): + FliImagePlugin.FliImageFile(invalid_file) - # Act - frame = im.tell() - # Assert - self.assertEqual(frame, 0) +def test_n_frames(): + with Image.open(static_test_file) as im: + assert im.n_frames == 1 + assert not im.is_animated - def test_invalid_file(self): - invalid_file = "Tests/images/flower.jpg" + with Image.open(animated_test_file) as im: + assert im.n_frames == 384 + assert im.is_animated - self.assertRaises(SyntaxError, FliImagePlugin.FliImageFile, invalid_file) - def test_n_frames(self): - with Image.open(static_test_file) as im: - self.assertEqual(im.n_frames, 1) - self.assertFalse(im.is_animated) +def test_eoferror(): + with Image.open(animated_test_file) as im: + n_frames = im.n_frames - with Image.open(animated_test_file) as im: - self.assertEqual(im.n_frames, 384) - self.assertTrue(im.is_animated) + # Test seeking past the last frame + with pytest.raises(EOFError): + im.seek(n_frames) + assert im.tell() < n_frames - def test_eoferror(self): - with Image.open(animated_test_file) as im: - n_frames = im.n_frames + # Test that seeking to the last frame does not raise an error + im.seek(n_frames - 1) - # Test seeking past the last frame - self.assertRaises(EOFError, im.seek, n_frames) - self.assertLess(im.tell(), n_frames) - # Test that seeking to the last frame does not raise an error - im.seek(n_frames - 1) +def test_seek_tell(): + with Image.open(animated_test_file) as im: - def test_seek_tell(self): - with Image.open(animated_test_file) as im: + layer_number = im.tell() + assert layer_number == 0 - layer_number = im.tell() - self.assertEqual(layer_number, 0) + im.seek(0) + layer_number = im.tell() + assert layer_number == 0 - im.seek(0) - layer_number = im.tell() - self.assertEqual(layer_number, 0) + im.seek(1) + layer_number = im.tell() + assert layer_number == 1 - im.seek(1) - layer_number = im.tell() - self.assertEqual(layer_number, 1) + im.seek(2) + layer_number = im.tell() + assert layer_number == 2 - im.seek(2) - layer_number = im.tell() - self.assertEqual(layer_number, 2) + im.seek(1) + layer_number = im.tell() + assert layer_number == 1 - im.seek(1) - layer_number = im.tell() - self.assertEqual(layer_number, 1) - def test_seek(self): - with Image.open(animated_test_file) as im: - im.seek(50) +def test_seek(): + with Image.open(animated_test_file) as im: + im.seek(50) - with Image.open("Tests/images/a_fli.png") as expected: - assert_image_equal(im, expected) + with Image.open("Tests/images/a_fli.png") as expected: + assert_image_equal(im, expected) diff --git a/Tests/test_file_fpx.py b/Tests/test_file_fpx.py index 25a7ff24b..c3cc37ddf 100644 --- a/Tests/test_file_fpx.py +++ b/Tests/test_file_fpx.py @@ -1,28 +1,24 @@ -import unittest +import pytest from PIL import Image -from .helper import PillowTestCase - -try: - from PIL import FpxImagePlugin -except ImportError: - olefile_installed = False -else: - olefile_installed = True +FpxImagePlugin = pytest.importorskip( + "PIL.FpxImagePlugin", reason="olefile not installed" +) -@unittest.skipUnless(olefile_installed, "olefile package not installed") -class TestFileFpx(PillowTestCase): - def test_invalid_file(self): - # Test an invalid OLE file - invalid_file = "Tests/images/flower.jpg" - self.assertRaises(SyntaxError, FpxImagePlugin.FpxImageFile, invalid_file) +def test_invalid_file(): + # Test an invalid OLE file + invalid_file = "Tests/images/flower.jpg" + with pytest.raises(SyntaxError): + FpxImagePlugin.FpxImageFile(invalid_file) - # Test a valid OLE file, but not an FPX file - ole_file = "Tests/images/test-ole-file.doc" - self.assertRaises(SyntaxError, FpxImagePlugin.FpxImageFile, ole_file) + # Test a valid OLE file, but not an FPX file + ole_file = "Tests/images/test-ole-file.doc" + with pytest.raises(SyntaxError): + FpxImagePlugin.FpxImageFile(ole_file) - def test_fpx_invalid_number_of_bands(self): - with self.assertRaisesRegex(IOError, "Invalid number of bands"): - Image.open("Tests/images/input_bw_five_bands.fpx") + +def test_fpx_invalid_number_of_bands(): + with pytest.raises(OSError, match="Invalid number of bands"): + Image.open("Tests/images/input_bw_five_bands.fpx") diff --git a/Tests/test_file_ftex.py b/Tests/test_file_ftex.py index 67ce8506f..9b4375cd4 100644 --- a/Tests/test_file_ftex.py +++ b/Tests/test_file_ftex.py @@ -1,15 +1,15 @@ from PIL import Image -from .helper import PillowTestCase, assert_image_equal, assert_image_similar +from .helper import assert_image_equal, assert_image_similar -class TestFileFtex(PillowTestCase): - def test_load_raw(self): - with Image.open("Tests/images/ftex_uncompressed.ftu") as im: - with Image.open("Tests/images/ftex_uncompressed.png") as target: - assert_image_equal(im, target) +def test_load_raw(): + with Image.open("Tests/images/ftex_uncompressed.ftu") as im: + with Image.open("Tests/images/ftex_uncompressed.png") as target: + assert_image_equal(im, target) - def test_load_dxt1(self): - with Image.open("Tests/images/ftex_dxt1.ftc") as im: - with Image.open("Tests/images/ftex_dxt1.png") as target: - assert_image_similar(im, target.convert("RGBA"), 15) + +def test_load_dxt1(): + with Image.open("Tests/images/ftex_dxt1.ftc") as im: + with Image.open("Tests/images/ftex_dxt1.png") as target: + assert_image_similar(im, target.convert("RGBA"), 15) diff --git a/Tests/test_file_gbr.py b/Tests/test_file_gbr.py index 4304d8ae8..760f12e4d 100644 --- a/Tests/test_file_gbr.py +++ b/Tests/test_file_gbr.py @@ -1,15 +1,26 @@ +import pytest + from PIL import GbrImagePlugin, Image -from .helper import PillowTestCase, assert_image_equal +from .helper import assert_image_equal -class TestFileGbr(PillowTestCase): - def test_invalid_file(self): - invalid_file = "Tests/images/flower.jpg" +def test_invalid_file(): + invalid_file = "Tests/images/flower.jpg" - self.assertRaises(SyntaxError, GbrImagePlugin.GbrImageFile, invalid_file) + with pytest.raises(SyntaxError): + GbrImagePlugin.GbrImageFile(invalid_file) - def test_gbr_file(self): - with Image.open("Tests/images/gbr.gbr") as im: - with Image.open("Tests/images/gbr.png") as target: - assert_image_equal(target, im) + +def test_gbr_file(): + with Image.open("Tests/images/gbr.gbr") as im: + with Image.open("Tests/images/gbr.png") as target: + assert_image_equal(target, im) + + +def test_multiple_load_operations(): + with Image.open("Tests/images/gbr.gbr") as im: + im.load() + im.load() + with Image.open("Tests/images/gbr.png") as target: + assert_image_equal(target, im) diff --git a/Tests/test_file_gd.py b/Tests/test_file_gd.py index b6f8594be..5594e5bbb 100644 --- a/Tests/test_file_gd.py +++ b/Tests/test_file_gd.py @@ -1,4 +1,5 @@ import pytest + from PIL import GdImageFile, UnidentifiedImageError TEST_GD_FILE = "Tests/images/hopper.gd" diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index d9226cdba..cf3a65e18 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -1,11 +1,10 @@ -import unittest from io import BytesIO import pytest -from PIL import GifImagePlugin, Image, ImageDraw, ImagePalette + +from PIL import GifImagePlugin, Image, ImageDraw, ImagePalette, features from .helper import ( - PillowTestCase, assert_image_equal, assert_image_similar, hopper, @@ -13,15 +12,6 @@ from .helper import ( netpbm_available, ) -try: - from PIL import _webp - - HAVE_WEBP = True -except ImportError: - HAVE_WEBP = False - -codecs = dir(Image.core) - # sample gif stream TEST_GIF = "Tests/images/hopper.gif" @@ -29,776 +19,825 @@ with open(TEST_GIF, "rb") as f: data = f.read() -class TestFileGif(PillowTestCase): - def setUp(self): - if "gif_encoder" not in codecs or "gif_decoder" not in codecs: - self.skipTest("gif support not available") # can this happen? +def test_sanity(): + with Image.open(TEST_GIF) as im: + im.load() + assert im.mode == "P" + assert im.size == (128, 128) + assert im.format == "GIF" + assert im.info["version"] == b"GIF89a" - def test_sanity(self): + +@pytest.mark.skipif(is_pypy(), reason="Requires CPython") +def test_unclosed_file(): + def open(): + im = Image.open(TEST_GIF) + im.load() + + pytest.warns(ResourceWarning, open) + + +def test_closed_file(): + def open(): + im = Image.open(TEST_GIF) + im.load() + im.close() + + pytest.warns(None, open) + + +def test_context_manager(): + def open(): with Image.open(TEST_GIF) as im: im.load() - self.assertEqual(im.mode, "P") - self.assertEqual(im.size, (128, 128)) - self.assertEqual(im.format, "GIF") - self.assertEqual(im.info["version"], b"GIF89a") - @unittest.skipIf(is_pypy(), "Requires CPython") - def test_unclosed_file(self): - def open(): - im = Image.open(TEST_GIF) - im.load() + pytest.warns(None, open) - pytest.warns(ResourceWarning, open) - def test_closed_file(self): - def open(): - im = Image.open(TEST_GIF) - im.load() - im.close() +def test_invalid_file(): + invalid_file = "Tests/images/flower.jpg" - pytest.warns(None, open) + with pytest.raises(SyntaxError): + GifImagePlugin.GifImageFile(invalid_file) - def test_context_manager(self): - def open(): - with Image.open(TEST_GIF) as im: - im.load() - pytest.warns(None, open) +def test_optimize(): + def test_grayscale(optimize): + im = Image.new("L", (1, 1), 0) + filename = BytesIO() + im.save(filename, "GIF", optimize=optimize) + return len(filename.getvalue()) - def test_invalid_file(self): - invalid_file = "Tests/images/flower.jpg" - - self.assertRaises(SyntaxError, GifImagePlugin.GifImageFile, invalid_file) - - def test_optimize(self): - def test_grayscale(optimize): - im = Image.new("L", (1, 1), 0) - filename = BytesIO() - im.save(filename, "GIF", optimize=optimize) - return len(filename.getvalue()) - - def test_bilevel(optimize): - im = Image.new("1", (1, 1), 0) - test_file = BytesIO() - im.save(test_file, "GIF", optimize=optimize) - return len(test_file.getvalue()) - - self.assertEqual(test_grayscale(0), 800) - self.assertEqual(test_grayscale(1), 44) - self.assertEqual(test_bilevel(0), 800) - self.assertEqual(test_bilevel(1), 800) - - def test_optimize_correctness(self): - # 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): - # make an image with empty colors in the start of the palette range - im = Image.frombytes( - "P", (colors, colors), bytes(range(256 - colors, 256)) * colors - ) - im = im.resize((size, size)) - outfile = BytesIO() - im.save(outfile, "GIF") - outfile.seek(0) - with Image.open(outfile) as reloaded: - # check palette length - palette_length = max( - i + 1 for i, v in enumerate(reloaded.histogram()) if v - ) - self.assertEqual(expected_palette_length, palette_length) - - assert_image_equal(im.convert("RGB"), reloaded.convert("RGB")) - - # These do optimize the palette - 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) - - # other limits that don't optimize the palette - check(129, 511, 256) - check(255, 511, 256) - check(256, 511, 256) - - def test_optimize_full_l(self): - im = Image.frombytes("L", (16, 16), bytes(range(256))) + def test_bilevel(optimize): + im = Image.new("1", (1, 1), 0) test_file = BytesIO() - im.save(test_file, "GIF", optimize=True) - self.assertEqual(im.mode, "L") + im.save(test_file, "GIF", optimize=optimize) + return len(test_file.getvalue()) - def test_roundtrip(self): - out = self.tempfile("temp.gif") - im = hopper() - im.save(out) - with Image.open(out) as reread: + assert test_grayscale(0) == 799 + assert test_grayscale(1) == 43 + assert test_bilevel(0) == 799 + assert test_bilevel(1) == 799 - assert_image_similar(reread.convert("RGB"), im, 50) - def test_roundtrip2(self): - # see https://github.com/python-pillow/Pillow/issues/403 - out = self.tempfile("temp.gif") - with Image.open(TEST_GIF) as im: - im2 = im.copy() - im2.save(out) - with Image.open(out) as reread: +def test_optimize_correctness(): + # 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): + # make an image with empty colors in the start of the palette range + im = Image.frombytes( + "P", (colors, colors), bytes(range(256 - colors, 256)) * colors + ) + im = im.resize((size, size)) + outfile = BytesIO() + im.save(outfile, "GIF") + outfile.seek(0) + with Image.open(outfile) as reloaded: + # check palette length + palette_length = max(i + 1 for i, v in enumerate(reloaded.histogram()) if v) + assert expected_palette_length == palette_length - assert_image_similar(reread.convert("RGB"), hopper(), 50) + assert_image_equal(im.convert("RGB"), reloaded.convert("RGB")) - def test_roundtrip_save_all(self): - # Single frame image - out = self.tempfile("temp.gif") - im = hopper() + # These do optimize the palette + 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) + + # Other limits that don't optimize the palette + check(129, 511, 256) + check(255, 511, 256) + check(256, 511, 256) + + +def test_optimize_full_l(): + im = Image.frombytes("L", (16, 16), bytes(range(256))) + test_file = BytesIO() + im.save(test_file, "GIF", optimize=True) + assert im.mode == "L" + + +def test_roundtrip(tmp_path): + out = str(tmp_path / "temp.gif") + im = hopper() + im.save(out) + with Image.open(out) as reread: + + assert_image_similar(reread.convert("RGB"), im, 50) + + +def test_roundtrip2(tmp_path): + # see https://github.com/python-pillow/Pillow/issues/403 + out = str(tmp_path / "temp.gif") + with Image.open(TEST_GIF) as im: + im2 = im.copy() + im2.save(out) + with Image.open(out) as reread: + + assert_image_similar(reread.convert("RGB"), hopper(), 50) + + +def test_roundtrip_save_all(tmp_path): + # Single frame image + out = str(tmp_path / "temp.gif") + im = hopper() + im.save(out, save_all=True) + with Image.open(out) as reread: + + assert_image_similar(reread.convert("RGB"), im, 50) + + # Multiframe image + with Image.open("Tests/images/dispose_bgnd.gif") as im: + out = str(tmp_path / "temp.gif") im.save(out, save_all=True) - with Image.open(out) as reread: - assert_image_similar(reread.convert("RGB"), im, 50) + with Image.open(out) as reread: + assert reread.n_frames == 5 - # Multiframe image - with Image.open("Tests/images/dispose_bgnd.gif") as im: - out = self.tempfile("temp.gif") - im.save(out, save_all=True) - with Image.open(out) as reread: +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: - self.assertEqual(reread.n_frames, 5) + info = im.info.copy() - def test_headers_saving_for_animated_gifs(self): - important_headers = ["background", "version", "duration", "loop"] - # Multiframe image - with Image.open("Tests/images/dispose_bgnd.gif") as im: + out = str(tmp_path / "temp.gif") + im.save(out, save_all=True) + with Image.open(out) as reread: - info = im.info.copy() + for header in important_headers: + assert info[header] == reread.info[header] - out = self.tempfile("temp.gif") - im.save(out, save_all=True) - with Image.open(out) as reread: - for header in important_headers: - self.assertEqual(info[header], reread.info[header]) +def test_palette_handling(tmp_path): + # see https://github.com/python-pillow/Pillow/issues/513 - def test_palette_handling(self): - # see https://github.com/python-pillow/Pillow/issues/513 + with Image.open(TEST_GIF) as im: + im = im.convert("RGB") - with Image.open(TEST_GIF) as im: - im = im.convert("RGB") + im = im.resize((100, 100), Image.LANCZOS) + im2 = im.convert("P", palette=Image.ADAPTIVE, colors=256) - im = im.resize((100, 100), Image.LANCZOS) - im2 = im.convert("P", palette=Image.ADAPTIVE, colors=256) + f = str(tmp_path / "temp.gif") + im2.save(f, optimize=True) - f = self.tempfile("temp.gif") - im2.save(f, optimize=True) + with Image.open(f) as reloaded: - with Image.open(f) as reloaded: + assert_image_similar(im, reloaded.convert("RGB"), 10) - assert_image_similar(im, reloaded.convert("RGB"), 10) - def test_palette_434(self): - # see https://github.com/python-pillow/Pillow/issues/434 +def test_palette_434(tmp_path): + # see https://github.com/python-pillow/Pillow/issues/434 - def roundtrip(im, *args, **kwargs): - out = self.tempfile("temp.gif") - im.copy().save(out, *args, **kwargs) - reloaded = Image.open(out) + def roundtrip(im, *args, **kwargs): + out = str(tmp_path / "temp.gif") + im.copy().save(out, *args, **kwargs) + reloaded = Image.open(out) - return reloaded + return reloaded - orig = "Tests/images/test.colors.gif" - with Image.open(orig) as im: + 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: - assert_image_similar(im, reloaded, 1) + with roundtrip(im) as reloaded: + assert_image_similar(im, reloaded, 1) + with roundtrip(im, optimize=True) as reloaded: + assert_image_similar(im, reloaded, 1) - im = im.convert("RGB") - # check automatic P conversion - with roundtrip(im) as reloaded: - reloaded = reloaded.convert("RGB") - assert_image_equal(im, reloaded) + im = im.convert("RGB") + # check automatic P conversion + with roundtrip(im) as reloaded: + reloaded = reloaded.convert("RGB") + assert_image_equal(im, reloaded) - @unittest.skipUnless(netpbm_available(), "netpbm not available") - def test_save_netpbm_bmp_mode(self): - with Image.open(TEST_GIF) as img: - img = img.convert("RGB") - tempfile = self.tempfile("temp.gif") - GifImagePlugin._save_netpbm(img, 0, tempfile) - with Image.open(tempfile) as reloaded: - assert_image_similar(img, reloaded.convert("RGB"), 0) +@pytest.mark.skipif(not netpbm_available(), reason="Netpbm not available") +def test_save_netpbm_bmp_mode(tmp_path): + with Image.open(TEST_GIF) as img: + img = img.convert("RGB") - @unittest.skipUnless(netpbm_available(), "netpbm not available") - def test_save_netpbm_l_mode(self): - with Image.open(TEST_GIF) as img: - img = img.convert("L") + tempfile = str(tmp_path / "temp.gif") + GifImagePlugin._save_netpbm(img, 0, tempfile) + with Image.open(tempfile) as reloaded: + assert_image_similar(img, reloaded.convert("RGB"), 0) - tempfile = self.tempfile("temp.gif") - GifImagePlugin._save_netpbm(img, 0, tempfile) - with Image.open(tempfile) as reloaded: - assert_image_similar(img, reloaded.convert("L"), 0) - def test_seek(self): - with Image.open("Tests/images/dispose_none.gif") as img: - framecount = 0 - try: - while True: - framecount += 1 - img.seek(img.tell() + 1) - except EOFError: - self.assertEqual(framecount, 5) +@pytest.mark.skipif(not netpbm_available(), reason="Netpbm not available") +def test_save_netpbm_l_mode(tmp_path): + with Image.open(TEST_GIF) as img: + img = img.convert("L") - def test_seek_info(self): - with Image.open("Tests/images/iss634.gif") as im: - info = im.info.copy() + tempfile = str(tmp_path / "temp.gif") + GifImagePlugin._save_netpbm(img, 0, tempfile) + with Image.open(tempfile) as reloaded: + assert_image_similar(img, reloaded.convert("L"), 0) - im.seek(1) - im.seek(0) - self.assertEqual(im.info, info) - - def test_seek_rewind(self): - with Image.open("Tests/images/iss634.gif") as im: - im.seek(2) - im.seek(1) - - with Image.open("Tests/images/iss634.gif") as expected: - expected.seek(1) - assert_image_equal(im, expected) - - def test_n_frames(self): - for path, n_frames in [[TEST_GIF, 1], ["Tests/images/iss634.gif", 42]]: - # Test is_animated before n_frames - with Image.open(path) as im: - self.assertEqual(im.is_animated, n_frames != 1) - - # Test is_animated after n_frames - with Image.open(path) as im: - self.assertEqual(im.n_frames, n_frames) - self.assertEqual(im.is_animated, n_frames != 1) - - def test_eoferror(self): - with Image.open(TEST_GIF) as im: - n_frames = im.n_frames - - # Test seeking past the last frame - self.assertRaises(EOFError, im.seek, n_frames) - self.assertLess(im.tell(), n_frames) - - # Test that seeking to the last frame does not raise an error - im.seek(n_frames - 1) - - def test_dispose_none(self): - with Image.open("Tests/images/dispose_none.gif") as img: - try: - while True: - img.seek(img.tell() + 1) - self.assertEqual(img.disposal_method, 1) - except EOFError: - pass - - def test_dispose_background(self): - with Image.open("Tests/images/dispose_bgnd.gif") as img: - try: - while True: - img.seek(img.tell() + 1) - self.assertEqual(img.disposal_method, 2) - except EOFError: - pass - - def test_dispose_previous(self): - with Image.open("Tests/images/dispose_prev.gif") as img: - try: - while True: - img.seek(img.tell() + 1) - self.assertEqual(img.disposal_method, 3) - except EOFError: - pass - - def test_save_dispose(self): - out = self.tempfile("temp.gif") - im_list = [ - Image.new("L", (100, 100), "#000"), - Image.new("L", (100, 100), "#111"), - Image.new("L", (100, 100), "#222"), - ] - for method in range(0, 4): - im_list[0].save( - out, save_all=True, append_images=im_list[1:], disposal=method - ) - with Image.open(out) as img: - for _ in range(2): - img.seek(img.tell() + 1) - self.assertEqual(img.disposal_method, method) - - # check per frame disposal - im_list[0].save( - out, - save_all=True, - append_images=im_list[1:], - disposal=tuple(range(len(im_list))), - ) - - with Image.open(out) as img: - - for i in range(2): +def test_seek(): + with Image.open("Tests/images/dispose_none.gif") as img: + frame_count = 0 + try: + while True: + frame_count += 1 img.seek(img.tell() + 1) - self.assertEqual(img.disposal_method, i + 1) + except EOFError: + assert frame_count == 5 - def test_dispose2_palette(self): - out = self.tempfile("temp.gif") - # 4 backgrounds: White, Grey, Black, Red - circles = [(255, 255, 255), (153, 153, 153), (0, 0, 0), (255, 0, 0)] +def test_seek_info(): + with Image.open("Tests/images/iss634.gif") as im: + info = im.info.copy() - im_list = [] - for circle in circles: - img = Image.new("RGB", (100, 100), (255, 0, 0)) + im.seek(1) + im.seek(0) - # Red circle in center of each frame - d = ImageDraw.Draw(img) - d.ellipse([(40, 40), (60, 60)], fill=circle) + assert im.info == info - im_list.append(img) - im_list[0].save(out, save_all=True, append_images=im_list[1:], disposal=2) +def test_seek_rewind(): + with Image.open("Tests/images/iss634.gif") as im: + im.seek(2) + im.seek(1) + with Image.open("Tests/images/iss634.gif") as expected: + expected.seek(1) + assert_image_equal(im, expected) + + +def test_n_frames(): + for path, n_frames in [[TEST_GIF, 1], ["Tests/images/iss634.gif", 42]]: + # Test is_animated before n_frames + with Image.open(path) as im: + assert im.is_animated == (n_frames != 1) + + # Test is_animated after n_frames + with Image.open(path) as im: + assert im.n_frames == n_frames + assert im.is_animated == (n_frames != 1) + + +def test_eoferror(): + with Image.open(TEST_GIF) as im: + n_frames = im.n_frames + + # Test seeking past the last frame + with pytest.raises(EOFError): + im.seek(n_frames) + assert im.tell() < n_frames + + # Test that seeking to the last frame does not raise an error + im.seek(n_frames - 1) + + +def test_dispose_none(): + with Image.open("Tests/images/dispose_none.gif") as img: + try: + while True: + img.seek(img.tell() + 1) + assert img.disposal_method == 1 + except EOFError: + pass + + +def test_dispose_none_load_end(): + # Test image created with: + # + # im = Image.open("transparent.gif") + # im_rotated = im.rotate(180) + # im.save("dispose_none_load_end.gif", + # save_all=True, append_images=[im_rotated], disposal=[1,2]) + with Image.open("Tests/images/dispose_none_load_end.gif") as img: + img.seek(1) + + with Image.open("Tests/images/dispose_none_load_end_second.gif") as expected: + assert_image_equal(img, expected) + + +def test_dispose_background(): + with Image.open("Tests/images/dispose_bgnd.gif") as img: + try: + while True: + img.seek(img.tell() + 1) + assert img.disposal_method == 2 + except EOFError: + pass + + +def test_dispose_previous(): + with Image.open("Tests/images/dispose_prev.gif") as img: + try: + while True: + img.seek(img.tell() + 1) + assert img.disposal_method == 3 + except EOFError: + pass + + +def test_save_dispose(tmp_path): + out = str(tmp_path / "temp.gif") + im_list = [ + Image.new("L", (100, 100), "#000"), + Image.new("L", (100, 100), "#111"), + Image.new("L", (100, 100), "#222"), + ] + for method in range(0, 4): + im_list[0].save(out, save_all=True, append_images=im_list[1:], disposal=method) with Image.open(out) as img: - for i, circle in enumerate(circles): - img.seek(i) - rgb_img = img.convert("RGB") + for _ in range(2): + img.seek(img.tell() + 1) + assert img.disposal_method == method - # Check top left pixel matches background - self.assertEqual(rgb_img.getpixel((0, 0)), (255, 0, 0)) + # Check per frame disposal + im_list[0].save( + out, + save_all=True, + append_images=im_list[1:], + disposal=tuple(range(len(im_list))), + ) - # Center remains red every frame - self.assertEqual(rgb_img.getpixel((50, 50)), circle) + with Image.open(out) as img: - def test_dispose2_diff(self): - out = self.tempfile("temp.gif") - - # 4 frames: red/blue, red/red, blue/blue, red/blue - circles = [ - ((255, 0, 0, 255), (0, 0, 255, 255)), - ((255, 0, 0, 255), (255, 0, 0, 255)), - ((0, 0, 255, 255), (0, 0, 255, 255)), - ((255, 0, 0, 255), (0, 0, 255, 255)), - ] - - im_list = [] - for i in range(len(circles)): - # Transparent BG - img = Image.new("RGBA", (100, 100), (255, 255, 255, 0)) - - # Two circles per frame - d = ImageDraw.Draw(img) - d.ellipse([(0, 30), (40, 70)], fill=circles[i][0]) - d.ellipse([(60, 30), (100, 70)], fill=circles[i][1]) - - im_list.append(img) - - im_list[0].save( - out, save_all=True, append_images=im_list[1:], disposal=2, transparency=0 - ) - - with Image.open(out) as img: - for i, colours in enumerate(circles): - img.seek(i) - rgb_img = img.convert("RGBA") - - # Check left circle is correct colour - self.assertEqual(rgb_img.getpixel((20, 50)), colours[0]) - - # Check right circle is correct colour - self.assertEqual(rgb_img.getpixel((80, 50)), colours[1]) - - # Check BG is correct colour - self.assertEqual(rgb_img.getpixel((1, 1)), (255, 255, 255, 0)) - - def test_dispose2_background(self): - out = self.tempfile("temp.gif") - - im_list = [] - - im = Image.new("P", (100, 100)) - d = ImageDraw.Draw(im) - d.rectangle([(50, 0), (100, 100)], fill="#f00") - d.rectangle([(0, 0), (50, 100)], fill="#0f0") - im_list.append(im) - - im = Image.new("P", (100, 100)) - d = ImageDraw.Draw(im) - d.rectangle([(0, 0), (100, 50)], fill="#f00") - d.rectangle([(0, 50), (100, 100)], fill="#0f0") - im_list.append(im) - - im_list[0].save( - out, save_all=True, append_images=im_list[1:], disposal=[0, 2], background=1 - ) - - with Image.open(out) as im: - im.seek(1) - self.assertEqual(im.getpixel((0, 0)), 0) - - def test_iss634(self): - with Image.open("Tests/images/iss634.gif") as img: - # seek to the second frame + for i in range(2): img.seek(img.tell() + 1) - # all transparent pixels should be replaced with the color from the - # first frame - self.assertEqual(img.histogram()[img.info["transparency"]], 0) + assert img.disposal_method == i + 1 - def test_duration(self): - duration = 1000 - out = self.tempfile("temp.gif") - im = Image.new("L", (100, 100), "#000") +def test_dispose2_palette(tmp_path): + out = str(tmp_path / "temp.gif") - # Check that the argument has priority over the info settings - im.info["duration"] = 100 - im.save(out, duration=duration) + # 4 backgrounds: White, Grey, Black, Red + circles = [(255, 255, 255), (153, 153, 153), (0, 0, 0), (255, 0, 0)] - with Image.open(out) as reread: - self.assertEqual(reread.info["duration"], duration) + im_list = [] + for circle in circles: + img = Image.new("RGB", (100, 100), (255, 0, 0)) - def test_multiple_duration(self): - duration_list = [1000, 2000, 3000] + # Red circle in center of each frame + d = ImageDraw.Draw(img) + d.ellipse([(40, 40), (60, 60)], fill=circle) - out = self.tempfile("temp.gif") - im_list = [ - Image.new("L", (100, 100), "#000"), - Image.new("L", (100, 100), "#111"), - Image.new("L", (100, 100), "#222"), - ] + im_list.append(img) - # duration as list - im_list[0].save( - out, save_all=True, append_images=im_list[1:], duration=duration_list - ) - with Image.open(out) as reread: + im_list[0].save(out, save_all=True, append_images=im_list[1:], disposal=2) - for duration in duration_list: - self.assertEqual(reread.info["duration"], duration) - try: - reread.seek(reread.tell() + 1) - except EOFError: - pass + with Image.open(out) as img: + for i, circle in enumerate(circles): + img.seek(i) + rgb_img = img.convert("RGB") - # duration as tuple - im_list[0].save( - out, save_all=True, append_images=im_list[1:], duration=tuple(duration_list) - ) - with Image.open(out) as reread: + # Check top left pixel matches background + assert rgb_img.getpixel((0, 0)) == (255, 0, 0) - for duration in duration_list: - self.assertEqual(reread.info["duration"], duration) - try: - reread.seek(reread.tell() + 1) - except EOFError: - pass + # Center remains red every frame + assert rgb_img.getpixel((50, 50)) == circle - def test_identical_frames(self): - duration_list = [1000, 1500, 2000, 4000] - out = self.tempfile("temp.gif") +def test_dispose2_diff(tmp_path): + out = str(tmp_path / "temp.gif") + + # 4 frames: red/blue, red/red, blue/blue, red/blue + circles = [ + ((255, 0, 0, 255), (0, 0, 255, 255)), + ((255, 0, 0, 255), (255, 0, 0, 255)), + ((0, 0, 255, 255), (0, 0, 255, 255)), + ((255, 0, 0, 255), (0, 0, 255, 255)), + ] + + im_list = [] + for i in range(len(circles)): + # Transparent BG + img = Image.new("RGBA", (100, 100), (255, 255, 255, 0)) + + # Two circles per frame + d = ImageDraw.Draw(img) + d.ellipse([(0, 30), (40, 70)], fill=circles[i][0]) + d.ellipse([(60, 30), (100, 70)], fill=circles[i][1]) + + im_list.append(img) + + im_list[0].save( + out, save_all=True, append_images=im_list[1:], disposal=2, transparency=0 + ) + + with Image.open(out) as img: + for i, colours in enumerate(circles): + img.seek(i) + rgb_img = img.convert("RGBA") + + # Check left circle is correct colour + assert rgb_img.getpixel((20, 50)) == colours[0] + + # Check right circle is correct colour + assert rgb_img.getpixel((80, 50)) == colours[1] + + # Check BG is correct colour + assert rgb_img.getpixel((1, 1)) == (255, 255, 255, 0) + + +def test_dispose2_background(tmp_path): + out = str(tmp_path / "temp.gif") + + im_list = [] + + im = Image.new("P", (100, 100)) + d = ImageDraw.Draw(im) + d.rectangle([(50, 0), (100, 100)], fill="#f00") + d.rectangle([(0, 0), (50, 100)], fill="#0f0") + im_list.append(im) + + im = Image.new("P", (100, 100)) + d = ImageDraw.Draw(im) + d.rectangle([(0, 0), (100, 50)], fill="#f00") + d.rectangle([(0, 50), (100, 100)], fill="#0f0") + im_list.append(im) + + im_list[0].save( + out, save_all=True, append_images=im_list[1:], disposal=[0, 2], background=1 + ) + + with Image.open(out) as im: + im.seek(1) + assert im.getpixel((0, 0)) == 0 + + +def test_iss634(): + with Image.open("Tests/images/iss634.gif") as img: + # Seek to the second frame + img.seek(img.tell() + 1) + # All transparent pixels should be replaced with the color from the first frame + assert img.histogram()[img.info["transparency"]] == 0 + + +def test_duration(tmp_path): + duration = 1000 + + out = str(tmp_path / "temp.gif") + im = Image.new("L", (100, 100), "#000") + + # Check that the argument has priority over the info settings + im.info["duration"] = 100 + im.save(out, duration=duration) + + with Image.open(out) as reread: + assert reread.info["duration"] == duration + + +def test_multiple_duration(tmp_path): + duration_list = [1000, 2000, 3000] + + out = str(tmp_path / "temp.gif") + im_list = [ + Image.new("L", (100, 100), "#000"), + Image.new("L", (100, 100), "#111"), + Image.new("L", (100, 100), "#222"), + ] + + # Duration as list + im_list[0].save( + 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: + reread.seek(reread.tell() + 1) + except EOFError: + pass + + # Duration as tuple + im_list[0].save( + 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: + reread.seek(reread.tell() + 1) + except EOFError: + pass + + +def test_identical_frames(tmp_path): + duration_list = [1000, 1500, 2000, 4000] + + out = str(tmp_path / "temp.gif") + im_list = [ + Image.new("L", (100, 100), "#000"), + Image.new("L", (100, 100), "#000"), + Image.new("L", (100, 100), "#000"), + Image.new("L", (100, 100), "#111"), + ] + + # Duration as list + im_list[0].save( + 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 + + # Assert that the new duration is the total of the identical frames + assert reread.info["duration"] == 4500 + + +def test_identical_frames_to_single_frame(tmp_path): + for duration in ([1000, 1500, 2000, 4000], (1000, 1500, 2000, 4000), 8500): + out = str(tmp_path / "temp.gif") im_list = [ Image.new("L", (100, 100), "#000"), Image.new("L", (100, 100), "#000"), Image.new("L", (100, 100), "#000"), - Image.new("L", (100, 100), "#111"), ] - # duration as list im_list[0].save( - out, save_all=True, append_images=im_list[1:], duration=duration_list + out, save_all=True, append_images=im_list[1:], duration=duration ) with Image.open(out) as reread: - - # Assert that the first three frames were combined - self.assertEqual(reread.n_frames, 2) + # Assert that all frames were combined + assert reread.n_frames == 1 # Assert that the new duration is the total of the identical frames - self.assertEqual(reread.info["duration"], 4500) + assert reread.info["duration"] == 8500 - def test_identical_frames_to_single_frame(self): - for duration in ([1000, 1500, 2000, 4000], (1000, 1500, 2000, 4000), 8500): - out = self.tempfile("temp.gif") - im_list = [ - Image.new("L", (100, 100), "#000"), - Image.new("L", (100, 100), "#000"), - Image.new("L", (100, 100), "#000"), - ] - im_list[0].save( - out, save_all=True, append_images=im_list[1:], duration=duration - ) - with Image.open(out) as reread: - # Assert that all frames were combined - self.assertEqual(reread.n_frames, 1) +def test_number_of_loops(tmp_path): + number_of_loops = 2 - # Assert that the new duration is the total of the identical frames - self.assertEqual(reread.info["duration"], 8500) + out = str(tmp_path / "temp.gif") + im = Image.new("L", (100, 100), "#000") + im.save(out, loop=number_of_loops) + with Image.open(out) as reread: - def test_number_of_loops(self): - number_of_loops = 2 + assert reread.info["loop"] == number_of_loops - out = self.tempfile("temp.gif") - im = Image.new("L", (100, 100), "#000") - im.save(out, loop=number_of_loops) - with Image.open(out) as reread: - self.assertEqual(reread.info["loop"], number_of_loops) +def test_background(tmp_path): + out = str(tmp_path / "temp.gif") + im = Image.new("L", (100, 100), "#000") + im.info["background"] = 1 + im.save(out) + with Image.open(out) as reread: - def test_background(self): - out = self.tempfile("temp.gif") - im = Image.new("L", (100, 100), "#000") - im.info["background"] = 1 - im.save(out) - with Image.open(out) as reread: + assert reread.info["background"] == im.info["background"] - self.assertEqual(reread.info["background"], im.info["background"]) - - if HAVE_WEBP and _webp.HAVE_WEBPANIM: - with Image.open("Tests/images/hopper.webp") as im: - self.assertIsInstance(im.info["background"], tuple) - im.save(out) - - def test_comment(self): - with Image.open(TEST_GIF) as im: - self.assertEqual( - im.info["comment"], b"File written by Adobe Photoshop\xa8 4.0" - ) - - out = self.tempfile("temp.gif") - im = Image.new("L", (100, 100), "#000") - im.info["comment"] = b"Test comment text" - im.save(out) - with Image.open(out) as reread: - self.assertEqual(reread.info["comment"], im.info["comment"]) - - im.info["comment"] = "Test comment text" - im.save(out) - with Image.open(out) as reread: - self.assertEqual(reread.info["comment"], im.info["comment"].encode()) - - def test_comment_over_255(self): - out = self.tempfile("temp.gif") - im = Image.new("L", (100, 100), "#000") - comment = b"Test comment text" - while len(comment) < 256: - comment += comment - im.info["comment"] = comment - im.save(out) - with Image.open(out) as reread: - - self.assertEqual(reread.info["comment"], comment) - - def test_zero_comment_subblocks(self): - with Image.open("Tests/images/hopper_zero_comment_subblocks.gif") as im: - with Image.open(TEST_GIF) as expected: - assert_image_equal(im, expected) - - def test_version(self): - out = self.tempfile("temp.gif") - - def assertVersionAfterSave(im, version): + if features.check("webp") and features.check("webp_anim"): + with Image.open("Tests/images/hopper.webp") as im: + assert isinstance(im.info["background"], tuple) im.save(out) - with Image.open(out) as reread: - self.assertEqual(reread.info["version"], version) - # Test that GIF87a is used by default - im = Image.new("L", (100, 100), "#000") + +def test_comment(tmp_path): + with Image.open(TEST_GIF) as im: + assert im.info["comment"] == b"File written by Adobe Photoshop\xa8 4.0" + + out = str(tmp_path / "temp.gif") + im = Image.new("L", (100, 100), "#000") + im.info["comment"] = b"Test comment text" + im.save(out) + with Image.open(out) as reread: + assert reread.info["comment"] == im.info["comment"] + + im.info["comment"] = "Test comment text" + im.save(out) + with Image.open(out) as reread: + assert reread.info["comment"] == im.info["comment"].encode() + + +def test_comment_over_255(tmp_path): + out = str(tmp_path / "temp.gif") + im = Image.new("L", (100, 100), "#000") + comment = b"Test comment text" + while len(comment) < 256: + comment += comment + im.info["comment"] = comment + im.save(out) + with Image.open(out) as reread: + + assert reread.info["comment"] == comment + + +def test_zero_comment_subblocks(): + with Image.open("Tests/images/hopper_zero_comment_subblocks.gif") as im: + with Image.open(TEST_GIF) as expected: + assert_image_equal(im, expected) + + +def test_version(tmp_path): + out = str(tmp_path / "temp.gif") + + def assertVersionAfterSave(im, version): + im.save(out) + with Image.open(out) as reread: + assert reread.info["version"] == version + + # Test that GIF87a is used by default + im = Image.new("L", (100, 100), "#000") + assertVersionAfterSave(im, b"GIF87a") + + # Test setting the version to 89a + im = Image.new("L", (100, 100), "#000") + im.info["version"] = b"89a" + assertVersionAfterSave(im, b"GIF89a") + + # Test that adding a GIF89a feature changes the version + im.info["transparency"] = 1 + assertVersionAfterSave(im, b"GIF89a") + + # Test that a GIF87a image is also saved in that format + with Image.open("Tests/images/test.colors.gif") as im: assertVersionAfterSave(im, b"GIF87a") - # Test setting the version to 89a - im = Image.new("L", (100, 100), "#000") - im.info["version"] = b"89a" - assertVersionAfterSave(im, b"GIF89a") + # Test that a GIF89a image is also saved in that format + im.info["version"] = b"GIF89a" + assertVersionAfterSave(im, b"GIF87a") - # Test that adding a GIF89a feature changes the version - im.info["transparency"] = 1 - assertVersionAfterSave(im, b"GIF89a") - # Test that a GIF87a image is also saved in that format - with Image.open("Tests/images/test.colors.gif") as im: - assertVersionAfterSave(im, b"GIF87a") +def test_append_images(tmp_path): + out = str(tmp_path / "temp.gif") - # Test that a GIF89a image is also saved in that format - im.info["version"] = b"GIF89a" - assertVersionAfterSave(im, b"GIF87a") + # Test appending single frame images + im = Image.new("RGB", (100, 100), "#f00") + ims = [Image.new("RGB", (100, 100), color) for color in ["#0f0", "#00f"]] + im.copy().save(out, save_all=True, append_images=ims) - def test_append_images(self): - out = self.tempfile("temp.gif") + with Image.open(out) as reread: + assert reread.n_frames == 3 - # Test appending single frame images - im = Image.new("RGB", (100, 100), "#f00") - ims = [Image.new("RGB", (100, 100), color) for color in ["#0f0", "#00f"]] - im.copy().save(out, save_all=True, append_images=ims) + # Tests appending using a generator + def imGenerator(ims): + yield from ims - with Image.open(out) as reread: - self.assertEqual(reread.n_frames, 3) + im.save(out, save_all=True, append_images=imGenerator(ims)) - # Tests appending using a generator - def imGenerator(ims): - yield from ims + with Image.open(out) as reread: + assert reread.n_frames == 3 - im.save(out, save_all=True, append_images=imGenerator(ims)) + # Tests appending single and multiple frame images + with Image.open("Tests/images/dispose_none.gif") as im: + with Image.open("Tests/images/dispose_prev.gif") as im2: + im.save(out, save_all=True, append_images=[im2]) - with Image.open(out) as reread: - self.assertEqual(reread.n_frames, 3) + with Image.open(out) as reread: + assert reread.n_frames == 10 - # Tests appending single and multiple frame images - with Image.open("Tests/images/dispose_none.gif") as im: - with Image.open("Tests/images/dispose_prev.gif") as im2: - im.save(out, save_all=True, append_images=[im2]) - with Image.open(out) as reread: - self.assertEqual(reread.n_frames, 10) +def test_transparent_optimize(tmp_path): + # From issue #2195, if the transparent color is incorrectly optimized out, GIF loses + # transparency. + # Need a palette that isn't using the 0 color, and one that's > 128 items where the + # transparent color is actually the top palette entry to trigger the bug. - def test_transparent_optimize(self): - # from issue #2195, if the transparent color is incorrectly - # optimized out, gif loses transparency - # Need a palette that isn't using the 0 color, and one - # that's > 128 items where the transparent color is actually - # the top palette entry to trigger the bug. + data = bytes(range(1, 254)) + palette = ImagePalette.ImagePalette("RGB", list(range(256)) * 3) - data = bytes(range(1, 254)) - palette = ImagePalette.ImagePalette("RGB", list(range(256)) * 3) + im = Image.new("L", (253, 1)) + im.frombytes(data) + im.putpalette(palette) - im = Image.new("L", (253, 1)) - im.frombytes(data) + out = str(tmp_path / "temp.gif") + im.save(out, transparency=253) + with Image.open(out) as reloaded: + + assert reloaded.info["transparency"] == 253 + + +def test_rgb_transparency(tmp_path): + out = str(tmp_path / "temp.gif") + + # Single frame + im = Image.new("RGB", (1, 1)) + im.info["transparency"] = (255, 0, 0) + pytest.warns(UserWarning, im.save, out) + + with Image.open(out) as reloaded: + assert "transparency" not in reloaded.info + + # Multiple frames + 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 Image.open(out) as reloaded: + assert "transparency" not in reloaded.info + + +def test_bbox(tmp_path): + out = str(tmp_path / "temp.gif") + + im = Image.new("RGB", (100, 100), "#fff") + ims = [Image.new("RGB", (100, 100), "#000")] + im.save(out, save_all=True, append_images=ims) + + with Image.open(out) as reread: + assert reread.n_frames == 2 + + +def test_palette_save_L(tmp_path): + # Generate an L mode image with a separate palette + + im = hopper("P") + im_l = Image.frombytes("L", im.size, im.tobytes()) + palette = bytes(im.getpalette()) + + out = str(tmp_path / "temp.gif") + im_l.save(out, palette=palette) + + with Image.open(out) as reloaded: + assert_image_equal(reloaded.convert("RGB"), im.convert("RGB")) + + +def test_palette_save_P(tmp_path): + # Pass in a different palette, then construct what the image would look like. + # Forcing a non-straight grayscale palette. + + im = hopper("P") + palette = bytes([255 - i // 3 for i in range(768)]) + + out = str(tmp_path / "temp.gif") + im.save(out, palette=palette) + + with Image.open(out) as reloaded: im.putpalette(palette) + assert_image_equal(reloaded, im) - out = self.tempfile("temp.gif") - im.save(out, transparency=253) - with Image.open(out) as reloaded: - self.assertEqual(reloaded.info["transparency"], 253) +def test_palette_save_ImagePalette(tmp_path): + # Pass in a different palette, as an ImagePalette.ImagePalette + # effectively the same as test_palette_save_P - def test_rgb_transparency(self): - out = self.tempfile("temp.gif") + im = hopper("P") + palette = ImagePalette.ImagePalette("RGB", list(range(256))[::-1] * 3) - # Single frame - im = Image.new("RGB", (1, 1)) - im.info["transparency"] = (255, 0, 0) - pytest.warns(UserWarning, im.save, out) + out = str(tmp_path / "temp.gif") + im.save(out, palette=palette) - with Image.open(out) as reloaded: - self.assertNotIn("transparency", reloaded.info) + with Image.open(out) as reloaded: + im.putpalette(palette) + assert_image_equal(reloaded, im) - # Multiple frames - 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 Image.open(out) as reloaded: - self.assertNotIn("transparency", reloaded.info) +def test_save_I(tmp_path): + # Test saving something that would trigger the auto-convert to 'L' - def test_bbox(self): - out = self.tempfile("temp.gif") + im = hopper("I") - im = Image.new("RGB", (100, 100), "#fff") - ims = [Image.new("RGB", (100, 100), "#000")] - im.save(out, save_all=True, append_images=ims) + out = str(tmp_path / "temp.gif") + im.save(out) - with Image.open(out) as reread: - self.assertEqual(reread.n_frames, 2) + with Image.open(out) as reloaded: + assert_image_equal(reloaded.convert("L"), im.convert("L")) - def test_palette_save_L(self): - # generate an L mode image with a separate palette - im = hopper("P") - im_l = Image.frombytes("L", im.size, im.tobytes()) - palette = bytes(im.getpalette()) +def test_getdata(): + # Test getheader/getdata against legacy values. + # Create a 'P' image with holes in the palette. + im = Image._wedge().resize((16, 16), Image.NEAREST) + im.putpalette(ImagePalette.ImagePalette("RGB")) + im.info = {"background": 0} - out = self.tempfile("temp.gif") - im_l.save(out, palette=palette) + passed_palette = bytes([255 - i // 3 for i in range(768)]) - with Image.open(out) as reloaded: + GifImagePlugin._FORCE_OPTIMIZE = True + try: + h = GifImagePlugin.getheader(im, passed_palette) + d = GifImagePlugin.getdata(im) - assert_image_equal(reloaded.convert("RGB"), im.convert("RGB")) + import pickle - def test_palette_save_P(self): - # pass in a different palette, then construct what the image - # would look like. - # Forcing a non-straight grayscale palette. + # Enable to get target values on pre-refactor version + # with open('Tests/images/gif_header_data.pkl', 'wb') as f: + # pickle.dump((h, d), f, 1) + with open("Tests/images/gif_header_data.pkl", "rb") as f: + (h_target, d_target) = pickle.load(f) - im = hopper("P") - palette = bytes([255 - i // 3 for i in range(768)]) + assert h == h_target + assert d == d_target + finally: + GifImagePlugin._FORCE_OPTIMIZE = False - out = self.tempfile("temp.gif") - im.save(out, palette=palette) - with Image.open(out) as reloaded: - im.putpalette(palette) - assert_image_equal(reloaded, im) +def test_lzw_bits(): + # see https://github.com/python-pillow/Pillow/issues/2811 + with Image.open("Tests/images/issue_2811.gif") as im: + assert im.tile[0][3][0] == 11 # LZW bits + # codec error prepatch + im.load() - def test_palette_save_ImagePalette(self): - # pass in a different palette, as an ImagePalette.ImagePalette - # effectively the same as test_palette_save_P - im = hopper("P") - palette = ImagePalette.ImagePalette("RGB", list(range(256))[::-1] * 3) - - out = self.tempfile("temp.gif") - im.save(out, palette=palette) - - with Image.open(out) as reloaded: - im.putpalette(palette) - assert_image_equal(reloaded, im) - - def test_save_I(self): - # Test saving something that would trigger the auto-convert to 'L' - - im = hopper("I") - - out = self.tempfile("temp.gif") - im.save(out) - - with Image.open(out) as reloaded: - assert_image_equal(reloaded.convert("L"), im.convert("L")) - - def test_getdata(self): - # test getheader/getdata against legacy values - # Create a 'P' image with holes in the palette - im = Image._wedge().resize((16, 16), Image.NEAREST) - im.putpalette(ImagePalette.ImagePalette("RGB")) - im.info = {"background": 0} - - passed_palette = bytes([255 - i // 3 for i in range(768)]) - - GifImagePlugin._FORCE_OPTIMIZE = True - try: - h = GifImagePlugin.getheader(im, passed_palette) - d = GifImagePlugin.getdata(im) - - import pickle - - # Enable to get target values on pre-refactor version - # with open('Tests/images/gif_header_data.pkl', 'wb') as f: - # pickle.dump((h, d), f, 1) - with open("Tests/images/gif_header_data.pkl", "rb") as f: - (h_target, d_target) = pickle.load(f) - - self.assertEqual(h, h_target) - self.assertEqual(d, d_target) - finally: - GifImagePlugin._FORCE_OPTIMIZE = False - - def test_lzw_bits(self): - # see https://github.com/python-pillow/Pillow/issues/2811 - with Image.open("Tests/images/issue_2811.gif") as im: - self.assertEqual(im.tile[0][3][0], 11) # LZW bits - # codec error prepatch - im.load() - - def test_extents(self): - with Image.open("Tests/images/test_extents.gif") as im: - self.assertEqual(im.size, (100, 100)) - im.seek(1) - self.assertEqual(im.size, (150, 150)) +def test_extents(): + with Image.open("Tests/images/test_extents.gif") as im: + assert im.size == (100, 100) + im.seek(1) + assert im.size == (150, 150) diff --git a/Tests/test_file_gimpgradient.py b/Tests/test_file_gimpgradient.py index bafee79a3..3f056fdae 100644 --- a/Tests/test_file_gimpgradient.py +++ b/Tests/test_file_gimpgradient.py @@ -1,122 +1,124 @@ -from PIL import GimpGradientFile - -from .helper import PillowTestCase +from PIL import GimpGradientFile, ImagePalette -class TestImage(PillowTestCase): - def test_linear_pos_le_middle(self): - # Arrange - middle = 0.5 - pos = 0.25 +def test_linear_pos_le_middle(): + # Arrange + middle = 0.5 + pos = 0.25 - # Act - ret = GimpGradientFile.linear(middle, pos) + # Act + ret = GimpGradientFile.linear(middle, pos) - # Assert - self.assertEqual(ret, 0.25) + # Assert + assert ret == 0.25 - def test_linear_pos_le_small_middle(self): - # Arrange - middle = 1e-11 - pos = 1e-12 - # Act - ret = GimpGradientFile.linear(middle, pos) +def test_linear_pos_le_small_middle(): + # Arrange + middle = 1e-11 + pos = 1e-12 - # Assert - self.assertEqual(ret, 0.0) + # Act + ret = GimpGradientFile.linear(middle, pos) - def test_linear_pos_gt_middle(self): - # Arrange - middle = 0.5 - pos = 0.75 + # Assert + assert ret == 0.0 - # Act - ret = GimpGradientFile.linear(middle, pos) - # Assert - self.assertEqual(ret, 0.75) +def test_linear_pos_gt_middle(): + # Arrange + middle = 0.5 + pos = 0.75 - def test_linear_pos_gt_small_middle(self): - # Arrange - middle = 1 - 1e-11 - pos = 1 - 1e-12 + # Act + ret = GimpGradientFile.linear(middle, pos) - # Act - ret = GimpGradientFile.linear(middle, pos) + # Assert + assert ret == 0.75 - # Assert - self.assertEqual(ret, 1.0) - def test_curved(self): - # Arrange - middle = 0.5 - pos = 0.75 +def test_linear_pos_gt_small_middle(): + # Arrange + middle = 1 - 1e-11 + pos = 1 - 1e-12 - # Act - ret = GimpGradientFile.curved(middle, pos) + # Act + ret = GimpGradientFile.linear(middle, pos) - # Assert - self.assertEqual(ret, 0.75) + # Assert + assert ret == 1.0 - def test_sine(self): - # Arrange - middle = 0.5 - pos = 0.75 - # Act - ret = GimpGradientFile.sine(middle, pos) +def test_curved(): + # Arrange + middle = 0.5 + pos = 0.75 - # Assert - self.assertEqual(ret, 0.8535533905932737) + # Act + ret = GimpGradientFile.curved(middle, pos) - def test_sphere_increasing(self): - # Arrange - middle = 0.5 - pos = 0.75 + # Assert + assert ret == 0.75 - # Act - ret = GimpGradientFile.sphere_increasing(middle, pos) - # Assert - self.assertAlmostEqual(ret, 0.9682458365518543) +def test_sine(): + # Arrange + middle = 0.5 + pos = 0.75 - def test_sphere_decreasing(self): - # Arrange - middle = 0.5 - pos = 0.75 + # Act + ret = GimpGradientFile.sine(middle, pos) - # Act - ret = GimpGradientFile.sphere_decreasing(middle, pos) + # Assert + assert ret == 0.8535533905932737 - # Assert - self.assertEqual(ret, 0.3385621722338523) - def test_load_via_imagepalette(self): - # Arrange - from PIL import ImagePalette +def test_sphere_increasing(): + # Arrange + middle = 0.5 + pos = 0.75 - test_file = "Tests/images/gimp_gradient.ggr" + # Act + ret = GimpGradientFile.sphere_increasing(middle, pos) - # Act - palette = ImagePalette.load(test_file) + # Assert + assert round(abs(ret - 0.9682458365518543), 7) == 0 - # Assert - # load returns raw palette information - self.assertEqual(len(palette[0]), 1024) - self.assertEqual(palette[1], "RGBA") - def test_load_1_3_via_imagepalette(self): - # Arrange - from PIL import ImagePalette +def test_sphere_decreasing(): + # Arrange + middle = 0.5 + pos = 0.75 - # GIMP 1.3 gradient files contain a name field - test_file = "Tests/images/gimp_gradient_with_name.ggr" + # Act + ret = GimpGradientFile.sphere_decreasing(middle, pos) - # Act - palette = ImagePalette.load(test_file) + # Assert + assert ret == 0.3385621722338523 - # Assert - # load returns raw palette information - self.assertEqual(len(palette[0]), 1024) - self.assertEqual(palette[1], "RGBA") + +def test_load_via_imagepalette(): + # Arrange + test_file = "Tests/images/gimp_gradient.ggr" + + # Act + palette = ImagePalette.load(test_file) + + # Assert + # load returns raw palette information + assert len(palette[0]) == 1024 + assert palette[1] == "RGBA" + + +def test_load_1_3_via_imagepalette(): + # Arrange + # GIMP 1.3 gradient files contain a name field + test_file = "Tests/images/gimp_gradient_with_name.ggr" + + # Act + palette = ImagePalette.load(test_file) + + # Assert + # load returns raw palette information + assert len(palette[0]) == 1024 + assert palette[1] == "RGBA" diff --git a/Tests/test_file_gimppalette.py b/Tests/test_file_gimppalette.py index a38c6320c..caec9cf21 100644 --- a/Tests/test_file_gimppalette.py +++ b/Tests/test_file_gimppalette.py @@ -1,4 +1,5 @@ import pytest + from PIL.GimpPaletteFile import GimpPaletteFile diff --git a/Tests/test_file_gribstub.py b/Tests/test_file_gribstub.py index 1cc1f47ac..e4930d8dc 100644 --- a/Tests/test_file_gribstub.py +++ b/Tests/test_file_gribstub.py @@ -1,4 +1,5 @@ import pytest + from PIL import GribStubImagePlugin, Image from .helper import hopper @@ -32,7 +33,7 @@ def test_load(): with Image.open(TEST_FILE) as im: # Act / Assert: stub cannot load without an implemented handler - with pytest.raises(IOError): + with pytest.raises(OSError): im.load() @@ -42,5 +43,5 @@ def test_save(tmp_path): tmpfile = str(tmp_path / "temp.grib") # Act / Assert: stub cannot save without an implemented handler - with pytest.raises(IOError): + with pytest.raises(OSError): im.save(tmpfile) diff --git a/Tests/test_file_hdf5stub.py b/Tests/test_file_hdf5stub.py index 526fd7c99..ff3397055 100644 --- a/Tests/test_file_hdf5stub.py +++ b/Tests/test_file_hdf5stub.py @@ -1,4 +1,5 @@ import pytest + from PIL import Hdf5StubImagePlugin, Image TEST_FILE = "Tests/images/hdf5.h5" @@ -30,7 +31,7 @@ def test_load(): with Image.open(TEST_FILE) as im: # Act / Assert: stub cannot load without an implemented handler - with pytest.raises(IOError): + with pytest.raises(OSError): im.load() @@ -41,7 +42,7 @@ def test_save(): dummy_filename = "dummy.filename" # Act / Assert: stub cannot save without an implemented handler - with pytest.raises(IOError): + with pytest.raises(OSError): im.save(dummy_filename) - with pytest.raises(IOError): + with pytest.raises(OSError): Hdf5StubImagePlugin._save(im, dummy_fp, dummy_filename) diff --git a/Tests/test_file_icns.py b/Tests/test_file_icns.py index 5693a19fd..a3d502d42 100644 --- a/Tests/test_file_icns.py +++ b/Tests/test_file_icns.py @@ -1,121 +1,141 @@ import io import sys -import unittest import pytest -from PIL import IcnsImagePlugin, Image -from .helper import PillowTestCase, assert_image_equal, assert_image_similar +from PIL import IcnsImagePlugin, Image, features + +from .helper import assert_image_equal, assert_image_similar # sample icon file TEST_FILE = "Tests/images/pillow.icns" -enable_jpeg2k = hasattr(Image.core, "jp2klib_version") +ENABLE_JPEG2K = features.check_codec("jpg_2000") -class TestFileIcns(PillowTestCase): - def test_sanity(self): - # Loading this icon by default should result in the largest size - # (512x512@2x) being loaded - with Image.open(TEST_FILE) as im: +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 - pytest.warns(None, im.load) + # Assert that there is no unclosed file warning + pytest.warns(None, im.load) - self.assertEqual(im.mode, "RGBA") - self.assertEqual(im.size, (1024, 1024)) - self.assertEqual(im.format, "ICNS") + assert im.mode == "RGBA" + assert im.size == (1024, 1024) + assert im.format == "ICNS" - @unittest.skipIf(sys.platform != "darwin", "requires macOS") - def test_save(self): - temp_file = self.tempfile("temp.icns") - with Image.open(TEST_FILE) as im: - im.save(temp_file) +@pytest.mark.skipif(sys.platform != "darwin", reason="Requires macOS") +def test_save(tmp_path): + temp_file = str(tmp_path / "temp.icns") + + with Image.open(TEST_FILE) as im: + im.save(temp_file) + + with Image.open(temp_file) as reread: + assert reread.mode == "RGBA" + assert reread.size == (1024, 1024) + assert reread.format == "ICNS" + + +@pytest.mark.skipif(sys.platform != "darwin", reason="Requires macOS") +def test_save_append_images(tmp_path): + temp_file = str(tmp_path / "temp.icns") + provided_im = Image.new("RGBA", (32, 32), (255, 0, 0, 128)) + + with Image.open(TEST_FILE) as im: + im.save(temp_file, append_images=[provided_im]) with Image.open(temp_file) as reread: - self.assertEqual(reread.mode, "RGBA") - self.assertEqual(reread.size, (1024, 1024)) - self.assertEqual(reread.format, "ICNS") + assert_image_similar(reread, im, 1) - @unittest.skipIf(sys.platform != "darwin", "requires macOS") - def test_save_append_images(self): - temp_file = self.tempfile("temp.icns") - provided_im = Image.new("RGBA", (32, 32), (255, 0, 0, 128)) + with Image.open(temp_file) as reread: + reread.size = (16, 16, 2) + reread.load() + assert_image_equal(reread, provided_im) - with Image.open(TEST_FILE) as im: - im.save(temp_file, append_images=[provided_im]) - with Image.open(temp_file) as reread: - assert_image_similar(reread, im, 1) +@pytest.mark.skipif(sys.platform != "darwin", reason="Requires macOS") +def test_save_fp(): + fp = io.BytesIO() - with Image.open(temp_file) as reread: - reread.size = (16, 16, 2) - reread.load() - assert_image_equal(reread, provided_im) + with Image.open(TEST_FILE) as im: + im.save(fp, format="ICNS") - def test_sizes(self): - # Check that we can load all of the sizes, and that the final pixel - # dimensions are as expected - with Image.open(TEST_FILE) as im: - for w, h, r in im.info["sizes"]: - wr = w * r - hr = h * r - im.size = (w, h, r) - im.load() - self.assertEqual(im.mode, "RGBA") - self.assertEqual(im.size, (wr, hr)) + with Image.open(fp) as reread: + assert reread.mode == "RGBA" + assert reread.size == (1024, 1024) + assert reread.format == "ICNS" - # Check that we cannot load an incorrect size - with self.assertRaises(ValueError): - im.size = (1, 1) - def test_older_icon(self): - # This icon was made with Icon Composer rather than iconutil; it still - # uses PNG rather than JP2, however (since it was made on 10.9). - with Image.open("Tests/images/pillow2.icns") as im: - for w, h, r in im.info["sizes"]: - wr = w * r - hr = h * r - with Image.open("Tests/images/pillow2.icns") as im2: - im2.size = (w, h, r) - im2.load() - self.assertEqual(im2.mode, "RGBA") - self.assertEqual(im2.size, (wr, hr)) +def test_sizes(): + # Check that we can load all of the sizes, and that the final pixel + # dimensions are as expected + with Image.open(TEST_FILE) as im: + for w, h, r in im.info["sizes"]: + wr = w * r + hr = h * r + im.size = (w, h, r) + im.load() + assert im.mode == "RGBA" + assert im.size == (wr, hr) - def test_jp2_icon(self): - # This icon was made by using Uli Kusterer's oldiconutil to replace - # the PNG images with JPEG 2000 ones. The advantage of doing this is - # that OS X 10.5 supports JPEG 2000 but not PNG; some commercial - # software therefore does just this. + # Check that we cannot load an incorrect size + with pytest.raises(ValueError): + im.size = (1, 1) - # (oldiconutil is here: https://github.com/uliwitness/oldiconutil) - if not enable_jpeg2k: - return +def test_older_icon(): + # This icon was made with Icon Composer rather than iconutil; it still + # uses PNG rather than JP2, however (since it was made on 10.9). + with Image.open("Tests/images/pillow2.icns") as im: + for w, h, r in im.info["sizes"]: + wr = w * r + hr = h * r + with Image.open("Tests/images/pillow2.icns") as im2: + im2.size = (w, h, r) + im2.load() + assert im2.mode == "RGBA" + assert im2.size == (wr, hr) - with Image.open("Tests/images/pillow3.icns") as im: - for w, h, r in im.info["sizes"]: - wr = w * r - hr = h * r - with Image.open("Tests/images/pillow3.icns") as im2: - im2.size = (w, h, r) - im2.load() - self.assertEqual(im2.mode, "RGBA") - self.assertEqual(im2.size, (wr, hr)) - def test_getimage(self): - with open(TEST_FILE, "rb") as fp: - icns_file = IcnsImagePlugin.IcnsFile(fp) +def test_jp2_icon(): + # This icon was made by using Uli Kusterer's oldiconutil to replace + # the PNG images with JPEG 2000 ones. The advantage of doing this is + # that OS X 10.5 supports JPEG 2000 but not PNG; some commercial + # software therefore does just this. - im = icns_file.getimage() - self.assertEqual(im.mode, "RGBA") - self.assertEqual(im.size, (1024, 1024)) + # (oldiconutil is here: https://github.com/uliwitness/oldiconutil) - im = icns_file.getimage((512, 512)) - self.assertEqual(im.mode, "RGBA") - self.assertEqual(im.size, (512, 512)) + if not ENABLE_JPEG2K: + return - def test_not_an_icns_file(self): - with io.BytesIO(b"invalid\n") as fp: - self.assertRaises(SyntaxError, IcnsImagePlugin.IcnsFile, fp) + with Image.open("Tests/images/pillow3.icns") as im: + for w, h, r in im.info["sizes"]: + wr = w * r + hr = h * r + with Image.open("Tests/images/pillow3.icns") as im2: + im2.size = (w, h, r) + im2.load() + assert im2.mode == "RGBA" + assert im2.size == (wr, hr) + + +def test_getimage(): + with open(TEST_FILE, "rb") as fp: + icns_file = IcnsImagePlugin.IcnsFile(fp) + + im = icns_file.getimage() + assert im.mode == "RGBA" + assert im.size == (1024, 1024) + + im = icns_file.getimage((512, 512)) + assert im.mode == "RGBA" + assert im.size == (512, 512) + + +def test_not_an_icns_file(): + with io.BytesIO(b"invalid\n") as fp: + with pytest.raises(SyntaxError): + IcnsImagePlugin.IcnsFile(fp) diff --git a/Tests/test_file_ico.py b/Tests/test_file_ico.py index 47fae7ed9..940001a21 100644 --- a/Tests/test_file_ico.py +++ b/Tests/test_file_ico.py @@ -1,104 +1,124 @@ import io import pytest + from PIL import IcoImagePlugin, Image, ImageDraw -from .helper import PillowTestCase, assert_image_equal, hopper +from .helper import assert_image_equal, hopper TEST_ICO_FILE = "Tests/images/hopper.ico" -class TestFileIco(PillowTestCase): - def test_sanity(self): - with Image.open(TEST_ICO_FILE) as im: - im.load() - self.assertEqual(im.mode, "RGBA") - self.assertEqual(im.size, (16, 16)) - self.assertEqual(im.format, "ICO") - self.assertEqual(im.get_format_mimetype(), "image/x-icon") +def test_sanity(): + with Image.open(TEST_ICO_FILE) as im: + im.load() + assert im.mode == "RGBA" + assert im.size == (16, 16) + assert im.format == "ICO" + assert im.get_format_mimetype() == "image/x-icon" - def test_invalid_file(self): - with open("Tests/images/flower.jpg", "rb") as fp: - self.assertRaises(SyntaxError, IcoImagePlugin.IcoImageFile, fp) - def test_save_to_bytes(self): - output = io.BytesIO() - im = hopper() - im.save(output, "ico", sizes=[(32, 32), (64, 64)]) +def test_invalid_file(): + with open("Tests/images/flower.jpg", "rb") as fp: + with pytest.raises(SyntaxError): + IcoImagePlugin.IcoImageFile(fp) - # the default image - output.seek(0) - with Image.open(output) as reloaded: - self.assertEqual(reloaded.info["sizes"], {(32, 32), (64, 64)}) - self.assertEqual(im.mode, reloaded.mode) - self.assertEqual((64, 64), reloaded.size) - self.assertEqual(reloaded.format, "ICO") - assert_image_equal(reloaded, hopper().resize((64, 64), Image.LANCZOS)) +def test_save_to_bytes(): + output = io.BytesIO() + im = hopper() + im.save(output, "ico", sizes=[(32, 32), (64, 64)]) - # the other one - output.seek(0) - with Image.open(output) as reloaded: - reloaded.size = (32, 32) + # The default image + output.seek(0) + with Image.open(output) as reloaded: + assert reloaded.info["sizes"] == {(32, 32), (64, 64)} - self.assertEqual(im.mode, reloaded.mode) - self.assertEqual((32, 32), reloaded.size) - self.assertEqual(reloaded.format, "ICO") - assert_image_equal(reloaded, hopper().resize((32, 32), Image.LANCZOS)) + assert im.mode == reloaded.mode + assert (64, 64) == reloaded.size + assert reloaded.format == "ICO" + assert_image_equal(reloaded, hopper().resize((64, 64), Image.LANCZOS)) - def test_incorrect_size(self): - with Image.open(TEST_ICO_FILE) as im: - with self.assertRaises(ValueError): - im.size = (1, 1) + # The other one + output.seek(0) + with Image.open(output) as reloaded: + reloaded.size = (32, 32) - def test_save_256x256(self): - """Issue #2264 https://github.com/python-pillow/Pillow/issues/2264""" - # Arrange - with Image.open("Tests/images/hopper_256x256.ico") as im: - outfile = self.tempfile("temp_saved_hopper_256x256.ico") + assert im.mode == reloaded.mode + assert (32, 32) == reloaded.size + assert reloaded.format == "ICO" + assert_image_equal(reloaded, hopper().resize((32, 32), Image.LANCZOS)) - # Act - im.save(outfile) - with Image.open(outfile) as im_saved: - # Assert - self.assertEqual(im_saved.size, (256, 256)) +def test_incorrect_size(): + with Image.open(TEST_ICO_FILE) as im: + with pytest.raises(ValueError): + im.size = (1, 1) - def test_only_save_relevant_sizes(self): - """Issue #2266 https://github.com/python-pillow/Pillow/issues/2266 - Should save in 16x16, 24x24, 32x32, 48x48 sizes - and not in 16x16, 24x24, 32x32, 48x48, 48x48, 48x48, 48x48 sizes - """ - # Arrange - with Image.open("Tests/images/python.ico") as im: # 16x16, 32x32, 48x48 - outfile = self.tempfile("temp_saved_python.ico") - # Act - im.save(outfile) - with Image.open(outfile) as im_saved: - # Assert - self.assertEqual( - im_saved.info["sizes"], {(16, 16), (24, 24), (32, 32), (48, 48)} - ) +def test_save_256x256(tmp_path): + """Issue #2264 https://github.com/python-pillow/Pillow/issues/2264""" + # Arrange + with Image.open("Tests/images/hopper_256x256.ico") as im: + outfile = str(tmp_path / "temp_saved_hopper_256x256.ico") - def test_unexpected_size(self): - # This image has been manually hexedited to state that it is 16x32 - # while the image within is still 16x16 - def open(): - with Image.open("Tests/images/hopper_unexpected.ico") as im: - self.assertEqual(im.size, (16, 16)) + # Act + im.save(outfile) + with Image.open(outfile) as im_saved: - pytest.warns(UserWarning, open) + # Assert + assert im_saved.size == (256, 256) - def test_draw_reloaded(self): - with Image.open(TEST_ICO_FILE) as im: - outfile = self.tempfile("temp_saved_hopper_draw.ico") - draw = ImageDraw.Draw(im) - draw.line((0, 0) + im.size, "#f00") - im.save(outfile) +def test_only_save_relevant_sizes(tmp_path): + """Issue #2266 https://github.com/python-pillow/Pillow/issues/2266 + Should save in 16x16, 24x24, 32x32, 48x48 sizes + and not in 16x16, 24x24, 32x32, 48x48, 48x48, 48x48, 48x48 sizes + """ + # Arrange + with Image.open("Tests/images/python.ico") as im: # 16x16, 32x32, 48x48 + outfile = str(tmp_path / "temp_saved_python.ico") + # Act + im.save(outfile) - with Image.open(outfile) as im: - im.save("Tests/images/hopper_draw.ico") - with Image.open("Tests/images/hopper_draw.ico") as reloaded: - assert_image_equal(im, reloaded) + with Image.open(outfile) as im_saved: + # Assert + assert im_saved.info["sizes"] == {(16, 16), (24, 24), (32, 32), (48, 48)} + + +def test_save_append_images(tmp_path): + # append_images should be used for scaled down versions of the image + im = hopper("RGBA") + provided_im = Image.new("RGBA", (32, 32), (255, 0, 0)) + outfile = str(tmp_path / "temp_saved_multi_icon.ico") + im.save(outfile, sizes=[(32, 32), (128, 128)], append_images=[provided_im]) + + with Image.open(outfile) as reread: + assert_image_equal(reread, hopper("RGBA")) + + reread.size = (32, 32) + assert_image_equal(reread, provided_im) + + +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 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: + outfile = str(tmp_path / "temp_saved_hopper_draw.ico") + + draw = ImageDraw.Draw(im) + draw.line((0, 0) + im.size, "#f00") + im.save(outfile) + + with Image.open(outfile) as im: + im.save("Tests/images/hopper_draw.ico") + with Image.open("Tests/images/hopper_draw.ico") as reloaded: + assert_image_equal(im, reloaded) diff --git a/Tests/test_file_im.py b/Tests/test_file_im.py index ec046020e..afea82359 100644 --- a/Tests/test_file_im.py +++ b/Tests/test_file_im.py @@ -1,89 +1,111 @@ -import unittest +import filecmp import pytest + from PIL import Image, ImImagePlugin -from .helper import PillowTestCase, assert_image_equal, hopper, is_pypy +from .helper import assert_image_equal, hopper, is_pypy # sample im TEST_IM = "Tests/images/hopper.im" -class TestFileIm(PillowTestCase): - def test_sanity(self): +def test_sanity(): + with Image.open(TEST_IM) as im: + im.load() + assert im.mode == "RGB" + assert im.size == (128, 128) + assert im.format == "IM" + + +def test_name_limit(tmp_path): + out = str(tmp_path / ("name_limit_test" * 7 + ".im")) + with Image.open(TEST_IM) as im: + im.save(out) + assert filecmp.cmp(out, "Tests/images/hopper_long_name.im") + + +@pytest.mark.skipif(is_pypy(), reason="Requires CPython") +def test_unclosed_file(): + def open(): + im = Image.open(TEST_IM) + im.load() + + pytest.warns(ResourceWarning, open) + + +def test_closed_file(): + def open(): + im = Image.open(TEST_IM) + im.load() + im.close() + + pytest.warns(None, open) + + +def test_context_manager(): + def open(): with Image.open(TEST_IM) as im: im.load() - self.assertEqual(im.mode, "RGB") - self.assertEqual(im.size, (128, 128)) - self.assertEqual(im.format, "IM") - @unittest.skipIf(is_pypy(), "Requires CPython") - def test_unclosed_file(self): - def open(): - im = Image.open(TEST_IM) - im.load() + pytest.warns(None, open) - pytest.warns(ResourceWarning, open) - def test_closed_file(self): - def open(): - im = Image.open(TEST_IM) - im.load() - im.close() +def test_tell(): + # Arrange + with Image.open(TEST_IM) as im: - pytest.warns(None, open) + # Act + frame = im.tell() - def test_context_manager(self): - def open(): - with Image.open(TEST_IM) as im: - im.load() + # Assert + assert frame == 0 - pytest.warns(None, open) - def test_tell(self): - # Arrange - with Image.open(TEST_IM) as im: +def test_n_frames(): + with Image.open(TEST_IM) as im: + assert im.n_frames == 1 + assert not im.is_animated - # Act - frame = im.tell() - # Assert - self.assertEqual(frame, 0) +def test_eoferror(): + with Image.open(TEST_IM) as im: + n_frames = im.n_frames - def test_n_frames(self): - with Image.open(TEST_IM) as im: - self.assertEqual(im.n_frames, 1) - self.assertFalse(im.is_animated) + # Test seeking past the last frame + with pytest.raises(EOFError): + im.seek(n_frames) + assert im.tell() < n_frames - def test_eoferror(self): - with Image.open(TEST_IM) as im: - n_frames = im.n_frames + # Test that seeking to the last frame does not raise an error + im.seek(n_frames - 1) - # Test seeking past the last frame - self.assertRaises(EOFError, im.seek, n_frames) - self.assertLess(im.tell(), n_frames) - # Test that seeking to the last frame does not raise an error - im.seek(n_frames - 1) +def test_roundtrip(tmp_path): + def roundtrip(mode): + out = str(tmp_path / "temp.im") + im = hopper(mode) + im.save(out) + with Image.open(out) as reread: + assert_image_equal(reread, im) - def test_roundtrip(self): - for mode in ["RGB", "P", "PA"]: - out = self.tempfile("temp.im") - im = hopper(mode) - im.save(out) - with Image.open(out) as reread: + for mode in ["RGB", "P", "PA"]: + roundtrip(mode) - assert_image_equal(reread, im) - def test_save_unsupported_mode(self): - out = self.tempfile("temp.im") - im = hopper("HSV") - self.assertRaises(ValueError, im.save, out) +def test_save_unsupported_mode(tmp_path): + out = str(tmp_path / "temp.im") + im = hopper("HSV") + with pytest.raises(ValueError): + im.save(out) - def test_invalid_file(self): - invalid_file = "Tests/images/flower.jpg" - self.assertRaises(SyntaxError, ImImagePlugin.ImImageFile, invalid_file) +def test_invalid_file(): + invalid_file = "Tests/images/flower.jpg" - def test_number(self): - self.assertEqual(1.2, ImImagePlugin.number("1.2")) + with pytest.raises(SyntaxError): + ImImagePlugin.ImImageFile(invalid_file) + + +def test_number(): + assert ImImagePlugin.number("1.2") == 1.2 diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index 59eb7c641..ff469d15c 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -1,11 +1,20 @@ import os +import re from io import BytesIO import pytest -from PIL import Image, ImageFile, JpegImagePlugin + +from PIL import ( + ExifTags, + Image, + ImageFile, + ImageOps, + JpegImagePlugin, + UnidentifiedImageError, + features, +) from .helper import ( - PillowTestCase, assert_image, assert_image_equal, assert_image_similar, @@ -13,19 +22,14 @@ from .helper import ( djpeg_available, hopper, is_win32, - unittest, + skip_unless_feature, ) -codecs = dir(Image.core) - TEST_FILE = "Tests/images/hopper.jpg" -class TestFileJpeg(PillowTestCase): - def setUp(self): - if "jpeg_encoder" not in codecs or "jpeg_decoder" not in codecs: - self.skipTest("jpeg support not available") - +@skip_unless_feature("jpg") +class TestFileJpeg: def roundtrip(self, im, **options): out = BytesIO() im.save(out, "JPEG", **options) @@ -36,7 +40,7 @@ class TestFileJpeg(PillowTestCase): return im def gen_random_image(self, size, mode="RGB"): - """ Generates a very hard to compress file + """Generates a very hard to compress file :param size: tuple :param mode: optional image mode @@ -46,25 +50,26 @@ class TestFileJpeg(PillowTestCase): def test_sanity(self): # internal version number - self.assertRegex(Image.core.jpeglib_version, r"\d+\.\d+$") + assert re.search(r"\d+\.\d+$", features.version_codec("jpg")) with Image.open(TEST_FILE) as im: im.load() - self.assertEqual(im.mode, "RGB") - self.assertEqual(im.size, (128, 128)) - self.assertEqual(im.format, "JPEG") - self.assertEqual(im.get_format_mimetype(), "image/jpeg") + assert im.mode == "RGB" + assert im.size == (128, 128) + assert im.format == "JPEG" + assert im.get_format_mimetype() == "image/jpeg" def test_app(self): # Test APP/COM reader (@PIL135) with Image.open(TEST_FILE) as im: - self.assertEqual( - im.applist[0], ("APP0", b"JFIF\x00\x01\x01\x01\x00`\x00`\x00\x00") + assert im.applist[0] == ("APP0", b"JFIF\x00\x01\x01\x01\x00`\x00`\x00\x00") + assert im.applist[1] == ( + "COM", + b"File written by Adobe Photoshop\xa8 4.0\x00", ) - self.assertEqual( - im.applist[1], ("COM", b"File written by Adobe Photoshop\xa8 4.0\x00") - ) - self.assertEqual(len(im.applist), 2) + assert len(im.applist) == 2 + + assert im.info["comment"] == b"File written by Adobe Photoshop\xa8 4.0\x00" def test_cmyk(self): # Test CMYK handling. Thanks to Tim and Charlie for test data, @@ -73,54 +78,58 @@ class TestFileJpeg(PillowTestCase): with Image.open(f) as im: # the source image has red pixels in the upper left corner. c, m, y, k = [x / 255.0 for x in im.getpixel((0, 0))] - self.assertEqual(c, 0.0) - self.assertGreater(m, 0.8) - self.assertGreater(y, 0.8) - self.assertEqual(k, 0.0) + assert c == 0.0 + assert m > 0.8 + assert y > 0.8 + assert k == 0.0 # the opposite corner is black c, m, y, k = [ x / 255.0 for x in im.getpixel((im.size[0] - 1, im.size[1] - 1)) ] - self.assertGreater(k, 0.9) + assert k > 0.9 # roundtrip, and check again im = self.roundtrip(im) c, m, y, k = [x / 255.0 for x in im.getpixel((0, 0))] - self.assertEqual(c, 0.0) - self.assertGreater(m, 0.8) - self.assertGreater(y, 0.8) - self.assertEqual(k, 0.0) + assert c == 0.0 + assert m > 0.8 + assert y > 0.8 + assert k == 0.0 c, m, y, k = [ x / 255.0 for x in im.getpixel((im.size[0] - 1, im.size[1] - 1)) ] - self.assertGreater(k, 0.9) + assert k > 0.9 - def test_dpi(self): + @pytest.mark.parametrize( + "test_image_path", + [TEST_FILE, "Tests/images/pil_sample_cmyk.jpg"], + ) + def test_dpi(self, test_image_path): def test(xdpi, ydpi=None): - with Image.open(TEST_FILE) as im: + with Image.open(test_image_path) as im: im = self.roundtrip(im, dpi=(xdpi, ydpi or xdpi)) return im.info.get("dpi") - self.assertEqual(test(72), (72, 72)) - self.assertEqual(test(300), (300, 300)) - self.assertEqual(test(100, 200), (100, 200)) - self.assertIsNone(test(0)) # square pixels + assert test(72) == (72, 72) + assert test(300) == (300, 300) + assert test(100, 200) == (100, 200) + assert test(0) is None # square pixels - def test_icc(self): + def test_icc(self, tmp_path): # Test ICC support with Image.open("Tests/images/rgb.jpg") as im1: icc_profile = im1.info["icc_profile"] - self.assertEqual(len(icc_profile), 3144) + assert len(icc_profile) == 3144 # Roundtrip via physical file. - f = self.tempfile("temp.jpg") + f = str(tmp_path / "temp.jpg") im1.save(f, icc_profile=icc_profile) with Image.open(f) as im2: - self.assertEqual(im2.info.get("icc_profile"), icc_profile) + assert im2.info.get("icc_profile") == icc_profile # Roundtrip via memory buffer. im1 = self.roundtrip(hopper()) im2 = self.roundtrip(hopper(), icc_profile=icc_profile) assert_image_equal(im1, im2) - self.assertFalse(im1.info.get("icc_profile")) - self.assertTrue(im2.info.get("icc_profile")) + assert not im1.info.get("icc_profile") + assert im2.info.get("icc_profile") def test_icc_big(self): # Make sure that the "extra" support handles large blocks @@ -129,9 +138,9 @@ class TestFileJpeg(PillowTestCase): # using a 4-byte test code should allow us to detect out of # order issues. icc_profile = (b"Test" * int(n / 4 + 1))[:n] - self.assertEqual(len(icc_profile), n) # sanity + assert len(icc_profile) == n # sanity im1 = self.roundtrip(hopper(), icc_profile=icc_profile) - self.assertEqual(im1.info.get("icc_profile"), icc_profile or None) + assert im1.info.get("icc_profile") == (icc_profile or None) test(0) test(1) @@ -144,14 +153,14 @@ class TestFileJpeg(PillowTestCase): test(ImageFile.MAXBLOCK + 1) # full buffer block plus one byte test(ImageFile.MAXBLOCK * 4 + 3) # large block - def test_large_icc_meta(self): + def test_large_icc_meta(self, tmp_path): # https://github.com/python-pillow/Pillow/issues/148 # Sometimes the meta data on the icc_profile block is bigger than # Image.MAXBLOCK or the image size. with Image.open("Tests/images/icc_profile_big.jpg") as im: - f = self.tempfile("temp.jpg") + f = str(tmp_path / "temp.jpg") icc_profile = im.info["icc_profile"] - # Should not raise IOError for image with icc larger than image size. + # Should not raise OSError for image with icc larger than image size. im.save( f, format="JPEG", @@ -167,12 +176,12 @@ class TestFileJpeg(PillowTestCase): im3 = self.roundtrip(hopper(), optimize=1) assert_image_equal(im1, im2) assert_image_equal(im1, im3) - self.assertGreaterEqual(im1.bytes, im2.bytes) - self.assertGreaterEqual(im1.bytes, im3.bytes) + assert im1.bytes >= im2.bytes + assert im1.bytes >= im3.bytes - def test_optimize_large_buffer(self): + def test_optimize_large_buffer(self, tmp_path): # https://github.com/python-pillow/Pillow/issues/148 - f = self.tempfile("temp.jpg") + f = str(tmp_path / "temp.jpg") # this requires ~ 1.5x Image.MAXBLOCK im = Image.new("RGB", (4096, 4096), 0xFF3333) im.save(f, format="JPEG", optimize=True) @@ -181,21 +190,21 @@ class TestFileJpeg(PillowTestCase): im1 = self.roundtrip(hopper()) im2 = self.roundtrip(hopper(), progressive=False) im3 = self.roundtrip(hopper(), progressive=True) - self.assertFalse(im1.info.get("progressive")) - self.assertFalse(im2.info.get("progressive")) - self.assertTrue(im3.info.get("progressive")) + assert not im1.info.get("progressive") + assert not im2.info.get("progressive") + assert im3.info.get("progressive") assert_image_equal(im1, im3) - self.assertGreaterEqual(im1.bytes, im3.bytes) + assert im1.bytes >= im3.bytes - def test_progressive_large_buffer(self): - f = self.tempfile("temp.jpg") + def test_progressive_large_buffer(self, tmp_path): + f = str(tmp_path / "temp.jpg") # this requires ~ 1.5x Image.MAXBLOCK im = Image.new("RGB", (4096, 4096), 0xFF3333) im.save(f, format="JPEG", progressive=True) - def test_progressive_large_buffer_highest_quality(self): - f = self.tempfile("temp.jpg") + def test_progressive_large_buffer_highest_quality(self, tmp_path): + f = str(tmp_path / "temp.jpg") im = self.gen_random_image((255, 255)) # this requires more bytes than pixels in the image im.save(f, format="JPEG", progressive=True, quality=100) @@ -206,9 +215,9 @@ class TestFileJpeg(PillowTestCase): im = self.gen_random_image((256, 256), "CMYK") im.save(f, format="JPEG", progressive=True, quality=94) - def test_large_exif(self): + def test_large_exif(self, tmp_path): # https://github.com/python-pillow/Pillow/issues/148 - f = self.tempfile("temp.jpg") + f = str(tmp_path / "temp.jpg") im = hopper() im.save(f, "JPEG", quality=90, exif=b"1" * 65532) @@ -217,23 +226,58 @@ class TestFileJpeg(PillowTestCase): # Should not raise a TypeError im._getexif() - def test_exif_gps(self): - # Arrange + def test_exif_gps(self, tmp_path): + expected_exif_gps = { + 0: b"\x00\x00\x00\x01", + 2: 4294967295, + 5: b"\x01", + 30: 65535, + 29: "1999:99:99 99:99:99", + } + gps_index = 34853 + + # Reading with Image.open("Tests/images/exif_gps.jpg") as im: - gps_index = 34853 - expected_exif_gps = { - 0: b"\x00\x00\x00\x01", - 2: (4294967295, 1), - 5: b"\x01", - 30: 65535, - 29: "1999:99:99 99:99:99", - } - - # Act exif = im._getexif() + assert exif[gps_index] == expected_exif_gps - # Assert - self.assertEqual(exif[gps_index], expected_exif_gps) + # Writing + f = str(tmp_path / "temp.jpg") + exif = Image.Exif() + exif[gps_index] = expected_exif_gps + hopper().save(f, exif=exif) + + with Image.open(f) as reloaded: + exif = reloaded._getexif() + assert exif[gps_index] == expected_exif_gps + + def test_empty_exif_gps(self): + with Image.open("Tests/images/empty_gps_ifd.jpg") as im: + exif = im.getexif() + del exif[0x8769] + + # Assert that it needs to be transposed + assert exif[0x0112] == Image.TRANSVERSE + + # Assert that the GPS IFD is present and empty + assert exif[0x8825] == {} + + transposed = ImageOps.exif_transpose(im) + exif = transposed.getexif() + assert exif[0x8825] == {} + + # Assert that it was transposed + assert 0x0112 not in exif + + def test_exif_equality(self): + # In 7.2.0, Exif rationals were changed to be read as + # TiffImagePlugin.IFDRational. This class had a bug in __eq__, + # breaking the self-equality of Exif data + exifs = [] + for i in range(2): + with Image.open("Tests/images/exif-200dpcm.jpg") as im: + exifs.append(im._getexif()) + assert exifs[0] == exifs[1] def test_exif_rollback(self): # rolling back exif support in 3.1 to pre-3.0 formatting. @@ -245,7 +289,7 @@ class TestFileJpeg(PillowTestCase): 36867: "2099:09:29 10:10:10", 34853: { 0: b"\x00\x00\x00\x01", - 2: (4294967295, 1), + 2: 4294967295, 5: b"\x01", 30: 65535, 29: "1999:99:99 99:99:99", @@ -257,18 +301,18 @@ class TestFileJpeg(PillowTestCase): 271: "Make", 272: "XXX-XXX", 305: "PIL", - 42034: ((1, 1), (1, 1), (1, 1), (1, 1)), + 42034: (1, 1, 1, 1), 42035: "LensMake", 34856: b"\xaa\xaa\xaa\xaa\xaa\xaa", - 282: (4294967295, 1), - 33434: (4294967295, 1), + 282: 4294967295, + 33434: 4294967295, } with Image.open("Tests/images/exif_gps.jpg") as im: exif = im._getexif() for tag, value in expected_exif.items(): - self.assertEqual(value, exif[tag]) + assert value == exif[tag] def test_exif_gps_typeerror(self): with Image.open("Tests/images/exif_gps_typeerror.jpg") as im: @@ -278,30 +322,34 @@ class TestFileJpeg(PillowTestCase): def test_progressive_compat(self): im1 = self.roundtrip(hopper()) - self.assertFalse(im1.info.get("progressive")) - self.assertFalse(im1.info.get("progression")) + assert not im1.info.get("progressive") + assert not im1.info.get("progression") im2 = self.roundtrip(hopper(), progressive=0) im3 = self.roundtrip(hopper(), progression=0) # compatibility - self.assertFalse(im2.info.get("progressive")) - self.assertFalse(im2.info.get("progression")) - self.assertFalse(im3.info.get("progressive")) - self.assertFalse(im3.info.get("progression")) + assert not im2.info.get("progressive") + assert not im2.info.get("progression") + assert not im3.info.get("progressive") + assert not im3.info.get("progression") im2 = self.roundtrip(hopper(), progressive=1) im3 = self.roundtrip(hopper(), progression=1) # compatibility assert_image_equal(im1, im2) assert_image_equal(im1, im3) - self.assertTrue(im2.info.get("progressive")) - self.assertTrue(im2.info.get("progression")) - self.assertTrue(im3.info.get("progressive")) - self.assertTrue(im3.info.get("progression")) + assert im2.info.get("progressive") + assert im2.info.get("progression") + assert im3.info.get("progressive") + assert im3.info.get("progression") def test_quality(self): im1 = self.roundtrip(hopper()) im2 = self.roundtrip(hopper(), quality=50) assert_image(im1, im2.mode, im2.size) - self.assertGreaterEqual(im1.bytes, im2.bytes) + assert im1.bytes >= im2.bytes + + im3 = self.roundtrip(hopper(), quality=0) + assert_image(im1, im3.mode, im3.size) + assert im2.bytes > im3.bytes def test_smooth(self): im1 = self.roundtrip(hopper()) @@ -315,48 +363,49 @@ class TestFileJpeg(PillowTestCase): # experimental API im = self.roundtrip(hopper(), subsampling=-1) # default - self.assertEqual(getsampling(im), (2, 2, 1, 1, 1, 1)) + assert getsampling(im) == (2, 2, 1, 1, 1, 1) im = self.roundtrip(hopper(), subsampling=0) # 4:4:4 - self.assertEqual(getsampling(im), (1, 1, 1, 1, 1, 1)) + assert getsampling(im) == (1, 1, 1, 1, 1, 1) im = self.roundtrip(hopper(), subsampling=1) # 4:2:2 - self.assertEqual(getsampling(im), (2, 1, 1, 1, 1, 1)) + assert getsampling(im) == (2, 1, 1, 1, 1, 1) im = self.roundtrip(hopper(), subsampling=2) # 4:2:0 - self.assertEqual(getsampling(im), (2, 2, 1, 1, 1, 1)) + assert getsampling(im) == (2, 2, 1, 1, 1, 1) im = self.roundtrip(hopper(), subsampling=3) # default (undefined) - self.assertEqual(getsampling(im), (2, 2, 1, 1, 1, 1)) + assert getsampling(im) == (2, 2, 1, 1, 1, 1) im = self.roundtrip(hopper(), subsampling="4:4:4") - self.assertEqual(getsampling(im), (1, 1, 1, 1, 1, 1)) + assert getsampling(im) == (1, 1, 1, 1, 1, 1) im = self.roundtrip(hopper(), subsampling="4:2:2") - self.assertEqual(getsampling(im), (2, 1, 1, 1, 1, 1)) + assert getsampling(im) == (2, 1, 1, 1, 1, 1) im = self.roundtrip(hopper(), subsampling="4:2:0") - self.assertEqual(getsampling(im), (2, 2, 1, 1, 1, 1)) + assert getsampling(im) == (2, 2, 1, 1, 1, 1) im = self.roundtrip(hopper(), subsampling="4:1:1") - self.assertEqual(getsampling(im), (2, 2, 1, 1, 1, 1)) + assert getsampling(im) == (2, 2, 1, 1, 1, 1) - self.assertRaises(TypeError, self.roundtrip, hopper(), subsampling="1:1:1") + with pytest.raises(TypeError): + self.roundtrip(hopper(), subsampling="1:1:1") def test_exif(self): with Image.open("Tests/images/pil_sample_rgb.jpg") as im: info = im._getexif() - self.assertEqual(info[305], "Adobe Photoshop CS Macintosh") + assert info[305] == "Adobe Photoshop CS Macintosh" def test_mp(self): with Image.open("Tests/images/pil_sample_rgb.jpg") as im: - self.assertIsNone(im._getmp()) + assert im._getmp() is None - def test_quality_keep(self): + def test_quality_keep(self, tmp_path): # RGB with Image.open("Tests/images/hopper.jpg") as im: - f = self.tempfile("temp.jpg") + f = str(tmp_path / "temp.jpg") im.save(f, quality="keep") # Grayscale with Image.open("Tests/images/hopper_gray.jpg") as im: - f = self.tempfile("temp.jpg") + f = str(tmp_path / "temp.jpg") im.save(f, quality="keep") # CMYK with Image.open("Tests/images/pil_sample_cmyk.jpg") as im: - f = self.tempfile("temp.jpg") + f = str(tmp_path / "temp.jpg") im.save(f, quality="keep") def test_junk_jpeg_header(self): @@ -376,32 +425,33 @@ class TestFileJpeg(PillowTestCase): with Image.open(filename) as im: im.load() ImageFile.LOAD_TRUNCATED_IMAGES = False - self.assertIsNotNone(im.getbbox()) + assert im.getbbox() is not None - def test_truncated_jpeg_throws_IOError(self): + def test_truncated_jpeg_throws_oserror(self): filename = "Tests/images/truncated_jpeg.jpg" with Image.open(filename) as im: - with self.assertRaises(IOError): + with pytest.raises(OSError): im.load() # Test that the error is raised if loaded a second time - with self.assertRaises(IOError): + with pytest.raises(OSError): im.load() - def _n_qtables_helper(self, n, test_file): - with Image.open(test_file) as im: - f = self.tempfile("temp.jpg") - im.save(f, qtables=[[n] * 64] * n) - with Image.open(f) as im: - self.assertEqual(len(im.quantization), n) - reloaded = self.roundtrip(im, qtables="keep") - self.assertEqual(im.quantization, reloaded.quantization) + def test_qtables(self, tmp_path): + def _n_qtables_helper(n, test_file): + with Image.open(test_file) as im: + f = str(tmp_path / "temp.jpg") + im.save(f, qtables=[[n] * 64] * n) + with Image.open(f) as im: + assert len(im.quantization) == n + reloaded = self.roundtrip(im, qtables="keep") + assert im.quantization == reloaded.quantization + assert reloaded.quantization[0].typecode == "B" - def test_qtables(self): with Image.open("Tests/images/hopper.jpg") as im: qtables = im.quantization reloaded = self.roundtrip(im, qtables=qtables, subsampling=0) - self.assertEqual(im.quantization, reloaded.quantization) + assert im.quantization == reloaded.quantization assert_image_similar(im, self.roundtrip(im, qtables="web_low"), 30) assert_image_similar(im, self.roundtrip(im, qtables="web_high"), 30) assert_image_similar(im, self.roundtrip(im, qtables="keep"), 30) @@ -469,54 +519,81 @@ class TestFileJpeg(PillowTestCase): 30, ) - self._n_qtables_helper(1, "Tests/images/hopper_gray.jpg") - self._n_qtables_helper(1, "Tests/images/pil_sample_rgb.jpg") - self._n_qtables_helper(2, "Tests/images/pil_sample_rgb.jpg") - self._n_qtables_helper(3, "Tests/images/pil_sample_rgb.jpg") - self._n_qtables_helper(1, "Tests/images/pil_sample_cmyk.jpg") - self._n_qtables_helper(2, "Tests/images/pil_sample_cmyk.jpg") - self._n_qtables_helper(3, "Tests/images/pil_sample_cmyk.jpg") - self._n_qtables_helper(4, "Tests/images/pil_sample_cmyk.jpg") + _n_qtables_helper(1, "Tests/images/hopper_gray.jpg") + _n_qtables_helper(1, "Tests/images/pil_sample_rgb.jpg") + _n_qtables_helper(2, "Tests/images/pil_sample_rgb.jpg") + _n_qtables_helper(3, "Tests/images/pil_sample_rgb.jpg") + _n_qtables_helper(1, "Tests/images/pil_sample_cmyk.jpg") + _n_qtables_helper(2, "Tests/images/pil_sample_cmyk.jpg") + _n_qtables_helper(3, "Tests/images/pil_sample_cmyk.jpg") + _n_qtables_helper(4, "Tests/images/pil_sample_cmyk.jpg") # not a sequence - self.assertRaises(ValueError, self.roundtrip, im, qtables="a") + with pytest.raises(ValueError): + self.roundtrip(im, qtables="a") # sequence wrong length - self.assertRaises(ValueError, self.roundtrip, im, qtables=[]) + with pytest.raises(ValueError): + self.roundtrip(im, qtables=[]) # sequence wrong length - self.assertRaises(ValueError, self.roundtrip, im, qtables=[1, 2, 3, 4, 5]) + with pytest.raises(ValueError): + self.roundtrip(im, qtables=[1, 2, 3, 4, 5]) # qtable entry not a sequence - self.assertRaises(ValueError, self.roundtrip, im, qtables=[1]) + with pytest.raises(ValueError): + self.roundtrip(im, qtables=[1]) # qtable entry has wrong number of items - self.assertRaises(ValueError, self.roundtrip, im, qtables=[[1, 2, 3, 4]]) + with pytest.raises(ValueError): + self.roundtrip(im, qtables=[[1, 2, 3, 4]]) - @unittest.skipUnless(djpeg_available(), "djpeg not available") + def test_load_16bit_qtables(self): + with Image.open("Tests/images/hopper_16bit_qtables.jpg") as im: + assert len(im.quantization) == 2 + assert len(im.quantization[0]) == 64 + assert max(im.quantization[0]) > 255 + + def test_save_multiple_16bit_qtables(self): + with Image.open("Tests/images/hopper_16bit_qtables.jpg") as im: + im2 = self.roundtrip(im, qtables="keep") + assert im.quantization == im2.quantization + + def test_save_single_16bit_qtable(self): + with Image.open("Tests/images/hopper_16bit_qtables.jpg") as im: + im2 = self.roundtrip(im, qtables={0: im.quantization[0]}) + assert len(im2.quantization) == 1 + assert im2.quantization[0] == im.quantization[0] + + def test_save_low_quality_baseline_qtables(self): + with Image.open(TEST_FILE) as im: + im2 = self.roundtrip(im, quality=10) + assert len(im2.quantization) == 2 + assert max(im2.quantization[0]) <= 255 + assert max(im2.quantization[1]) <= 255 + + @pytest.mark.skipif(not djpeg_available(), reason="djpeg not available") def test_load_djpeg(self): with Image.open(TEST_FILE) as img: img.load_djpeg() - assert_image_similar(img, Image.open(TEST_FILE), 0) + assert_image_similar(img, Image.open(TEST_FILE), 5) - @unittest.skipUnless(cjpeg_available(), "cjpeg not available") - def test_save_cjpeg(self): + @pytest.mark.skipif(not cjpeg_available(), reason="cjpeg not available") + def test_save_cjpeg(self, tmp_path): with Image.open(TEST_FILE) as img: - tempfile = self.tempfile("temp.jpg") + tempfile = str(tmp_path / "temp.jpg") JpegImagePlugin._save_cjpeg(img, 0, tempfile) # Default save quality is 75%, so a tiny bit of difference is alright assert_image_similar(img, Image.open(tempfile), 17) def test_no_duplicate_0x1001_tag(self): # Arrange - from PIL import ExifTags - tag_ids = {v: k for k, v in ExifTags.TAGS.items()} # Assert - self.assertEqual(tag_ids["RelatedImageWidth"], 0x1001) - self.assertEqual(tag_ids["RelatedImageLength"], 0x1002) + assert tag_ids["RelatedImageWidth"] == 0x1001 + assert tag_ids["RelatedImageLength"] == 0x1002 - def test_MAXBLOCK_scaling(self): + def test_MAXBLOCK_scaling(self, tmp_path): im = self.gen_random_image((512, 512)) - f = self.tempfile("temp.jpeg") + f = str(tmp_path / "temp.jpeg") im.save(f, quality=100, optimize=True) with Image.open(f) as reloaded: @@ -535,7 +612,7 @@ class TestFileJpeg(PillowTestCase): with pytest.warns(UserWarning, Image.open, fn) as im: # Assert - self.assertEqual(im.format, "JPEG") + assert im.format == "JPEG" def test_save_correct_modes(self): out = BytesIO() @@ -548,11 +625,12 @@ class TestFileJpeg(PillowTestCase): out = BytesIO() for mode in ["LA", "La", "RGBA", "RGBa", "P"]: img = Image.new(mode, (20, 20)) - self.assertRaises(IOError, img.save, out, "JPEG") + with pytest.raises(OSError): + img.save(out, "JPEG") - def test_save_tiff_with_dpi(self): + def test_save_tiff_with_dpi(self, tmp_path): # Arrange - outfile = self.tempfile("temp.tif") + outfile = str(tmp_path / "temp.tif") with Image.open("Tests/images/hopper.tif") as im: # Act @@ -561,29 +639,29 @@ class TestFileJpeg(PillowTestCase): # Assert with Image.open(outfile) as reloaded: reloaded.load() - self.assertEqual(im.info["dpi"], reloaded.info["dpi"]) + assert im.info["dpi"] == reloaded.info["dpi"] def test_load_dpi_rounding(self): # Round up with Image.open("Tests/images/iptc_roundUp.jpg") as im: - self.assertEqual(im.info["dpi"], (44, 44)) + assert im.info["dpi"] == (44, 44) # Round down with Image.open("Tests/images/iptc_roundDown.jpg") as im: - self.assertEqual(im.info["dpi"], (2, 2)) + assert im.info["dpi"] == (2, 2) - def test_save_dpi_rounding(self): - outfile = self.tempfile("temp.jpg") + def test_save_dpi_rounding(self, tmp_path): + outfile = str(tmp_path / "temp.jpg") with Image.open("Tests/images/hopper.jpg") as im: im.save(outfile, dpi=(72.2, 72.2)) with Image.open(outfile) as reloaded: - self.assertEqual(reloaded.info["dpi"], (72, 72)) + assert reloaded.info["dpi"] == (72, 72) im.save(outfile, dpi=(72.8, 72.8)) with Image.open(outfile) as reloaded: - self.assertEqual(reloaded.info["dpi"], (73, 73)) + assert reloaded.info["dpi"] == (73, 73) def test_dpi_tuple_from_exif(self): # Arrange @@ -592,7 +670,7 @@ class TestFileJpeg(PillowTestCase): with Image.open("Tests/images/photoshop-200dpi.jpg") as im: # Act / Assert - self.assertEqual(im.info.get("dpi"), (200, 200)) + assert im.info.get("dpi") == (200, 200) def test_dpi_int_from_exif(self): # Arrange @@ -601,7 +679,7 @@ class TestFileJpeg(PillowTestCase): with Image.open("Tests/images/exif-72dpi-int.jpg") as im: # Act / Assert - self.assertEqual(im.info.get("dpi"), (72, 72)) + assert im.info.get("dpi") == (72, 72) def test_dpi_from_dpcm_exif(self): # Arrange @@ -610,7 +688,7 @@ class TestFileJpeg(PillowTestCase): with Image.open("Tests/images/exif-200dpcm.jpg") as im: # Act / Assert - self.assertEqual(im.info.get("dpi"), (508, 508)) + assert im.info.get("dpi") == (508, 508) def test_dpi_exif_zero_division(self): # Arrange @@ -620,7 +698,7 @@ class TestFileJpeg(PillowTestCase): # Act / Assert # This should return the default, and not raise a ZeroDivisionError - self.assertEqual(im.info.get("dpi"), (72, 72)) + assert im.info.get("dpi") == (72, 72) def test_no_dpi_in_exif(self): # Arrange @@ -631,7 +709,7 @@ class TestFileJpeg(PillowTestCase): # Act / Assert # "When the image resolution is unknown, 72 [dpi] is designated." # http://www.exiv2.org/tags.html - self.assertEqual(im.info.get("dpi"), (72, 72)) + assert im.info.get("dpi") == (72, 72) def test_invalid_exif(self): # This is no-dpi-in-exif with the tiff header of the exif block @@ -640,7 +718,20 @@ class TestFileJpeg(PillowTestCase): # This should return the default, and not a SyntaxError or # OSError for unidentified image. - self.assertEqual(im.info.get("dpi"), (72, 72)) + assert im.info.get("dpi") == (72, 72) + + def test_exif_x_resolution(self, tmp_path): + with Image.open("Tests/images/flower.jpg") as im: + exif = im.getexif() + assert exif[282] == 180 + + out = str(tmp_path / "out.jpg") + with pytest.warns(None) as record: + im.save(out, exif=exif) + assert len(record) == 0 + + with Image.open(out) as reloaded: + assert reloaded.getexif()[282] == 180 def test_invalid_exif_x_resolution(self): # When no x or y resolution is defined in EXIF @@ -648,7 +739,7 @@ class TestFileJpeg(PillowTestCase): # This should return the default, and not a ValueError or # OSError for an unidentified image. - self.assertEqual(im.info.get("dpi"), (72, 72)) + assert im.info.get("dpi") == (72, 72) def test_ifd_offset_exif(self): # Arrange @@ -657,19 +748,16 @@ class TestFileJpeg(PillowTestCase): with Image.open("Tests/images/exif-ifd-offset.jpg") as im: # Act / Assert - self.assertEqual(im._getexif()[306], "2017:03:13 23:03:09") + assert im._getexif()[306] == "2017:03:13 23:03:09" def test_photoshop(self): with Image.open("Tests/images/photoshop-200dpi.jpg") as im: - self.assertEqual( - im.info["photoshop"][0x03ED], - { - "XResolution": 200.0, - "DisplayedUnitsX": 1, - "YResolution": 200.0, - "DisplayedUnitsY": 1, - }, - ) + assert im.info["photoshop"][0x03ED] == { + "XResolution": 200.0, + "DisplayedUnitsX": 1, + "YResolution": 200.0, + "DisplayedUnitsY": 1, + } # Test that the image can still load, even with broken Photoshop data # This image had the APP13 length hexedited to be smaller @@ -678,33 +766,53 @@ class TestFileJpeg(PillowTestCase): # This image does not contain a Photoshop header string with Image.open("Tests/images/app13.jpg") as im: - self.assertNotIn("photoshop", im.info) + assert "photoshop" not in im.info def test_photoshop_malformed_and_multiple(self): with Image.open("Tests/images/app13-multiple.jpg") as im: - self.assertIn("photoshop", im.info) - self.assertEqual(24, len(im.info["photoshop"])) + assert "photoshop" in im.info + assert 24 == len(im.info["photoshop"]) apps_13_lengths = [len(v) for k, v in im.applist if k == "APP13"] - self.assertEqual([65504, 24], apps_13_lengths) + assert [65504, 24] == apps_13_lengths + + def test_icc_after_SOF(self): + with Image.open("Tests/images/icc-after-SOF.jpg") as im: + assert im.info["icc_profile"] == b"profile" + + def test_jpeg_magic_number(self): + size = 4097 + buffer = BytesIO(b"\xFF" * size) # Many xFF bytes + buffer.max_pos = 0 + orig_read = buffer.read + + def read(n=-1): + res = orig_read(n) + buffer.max_pos = max(buffer.max_pos, buffer.tell()) + return res + + buffer.read = read + with pytest.raises(UnidentifiedImageError): + Image.open(buffer) + + # Assert the entire file has not been read + assert 0 < buffer.max_pos < size -@unittest.skipUnless(is_win32(), "Windows only") -class TestFileCloseW32(PillowTestCase): - def setUp(self): - if "jpeg_encoder" not in codecs or "jpeg_decoder" not in codecs: - self.skipTest("jpeg support not available") - - def test_fd_leak(self): - tmpfile = self.tempfile("temp.jpg") +@pytest.mark.skipif(not is_win32(), reason="Windows only") +@skip_unless_feature("jpg") +class TestFileCloseW32: + def test_fd_leak(self, tmp_path): + tmpfile = str(tmp_path / "temp.jpg") with Image.open("Tests/images/hopper.jpg") as im: im.save(tmpfile) im = Image.open(tmpfile) fp = im.fp - self.assertFalse(fp.closed) - self.assertRaises(WindowsError, os.remove, tmpfile) + assert not fp.closed + with pytest.raises(OSError): + os.remove(tmpfile) im.load() - self.assertTrue(fp.closed) + assert fp.closed # this should not fail, as load should have closed the file. os.remove(tmpfile) diff --git a/Tests/test_file_jpeg2k.py b/Tests/test_file_jpeg2k.py index 02e580e9d..c9e37f8b0 100644 --- a/Tests/test_file_jpeg2k.py +++ b/Tests/test_file_jpeg2k.py @@ -1,17 +1,18 @@ +import re from io import BytesIO import pytest -from PIL import Image, Jpeg2KImagePlugin + +from PIL import Image, ImageFile, Jpeg2KImagePlugin, features from .helper import ( - PillowTestCase, assert_image_equal, assert_image_similar, is_big_endian, - on_ci, + skip_unless_feature, ) -codecs = dir(Image.core) +pytestmark = skip_unless_feature("jpg_2000") test_card = Image.open("Tests/images/test-card.png") test_card.load() @@ -21,195 +22,214 @@ test_card.load() # 'Not enough memory to handle tile data' -class TestFileJpeg2k(PillowTestCase): - def setUp(self): - if "jpeg2k_encoder" not in codecs or "jpeg2k_decoder" not in codecs: - self.skipTest("JPEG 2000 support not available") +def roundtrip(im, **options): + out = BytesIO() + im.save(out, "JPEG2000", **options) + test_bytes = out.tell() + out.seek(0) + im = Image.open(out) + im.bytes = test_bytes # for testing only + im.load() + return im - def roundtrip(self, im, **options): - out = BytesIO() - im.save(out, "JPEG2000", **options) - test_bytes = out.tell() - out.seek(0) - im = Image.open(out) - im.bytes = test_bytes # for testing only + +def test_sanity(): + # Internal version number + assert re.search(r"\d+\.\d+\.\d+$", features.version_codec("jpg_2000")) + + with Image.open("Tests/images/test-card-lossless.jp2") as im: + px = im.load() + assert px[0, 0] == (0, 0, 0) + assert im.mode == "RGB" + assert im.size == (640, 480) + assert im.format == "JPEG2000" + assert im.get_format_mimetype() == "image/jp2" + + +def test_jpf(): + with Image.open("Tests/images/balloon.jpf") as im: + assert im.format == "JPEG2000" + assert im.get_format_mimetype() == "image/jpx" + + +def test_invalid_file(): + invalid_file = "Tests/images/flower.jpg" + + with pytest.raises(SyntaxError): + Jpeg2KImagePlugin.Jpeg2KImageFile(invalid_file) + + +def test_bytesio(): + with open("Tests/images/test-card-lossless.jp2", "rb") as f: + data = BytesIO(f.read()) + with Image.open(data) as im: im.load() - return im - - def test_sanity(self): - # Internal version number - self.assertRegex(Image.core.jp2klib_version, r"\d+\.\d+\.\d+$") - - with Image.open("Tests/images/test-card-lossless.jp2") as im: - px = im.load() - self.assertEqual(px[0, 0], (0, 0, 0)) - self.assertEqual(im.mode, "RGB") - self.assertEqual(im.size, (640, 480)) - self.assertEqual(im.format, "JPEG2000") - self.assertEqual(im.get_format_mimetype(), "image/jp2") - - def test_jpf(self): - with Image.open("Tests/images/balloon.jpf") as im: - self.assertEqual(im.format, "JPEG2000") - self.assertEqual(im.get_format_mimetype(), "image/jpx") - - def test_invalid_file(self): - invalid_file = "Tests/images/flower.jpg" - - self.assertRaises(SyntaxError, Jpeg2KImagePlugin.Jpeg2KImageFile, invalid_file) - - def test_bytesio(self): - with open("Tests/images/test-card-lossless.jp2", "rb") as f: - data = BytesIO(f.read()) - with Image.open(data) as im: - im.load() - assert_image_similar(im, test_card, 1.0e-3) - - # These two test pre-written JPEG 2000 files that were not written with - # PIL (they were made using Adobe Photoshop) - - def test_lossless(self): - with Image.open("Tests/images/test-card-lossless.jp2") as im: - im.load() - outfile = self.tempfile("temp_test-card.png") - im.save(outfile) assert_image_similar(im, test_card, 1.0e-3) - def test_lossy_tiled(self): - with Image.open("Tests/images/test-card-lossy-tiled.jp2") as im: - im.load() - assert_image_similar(im, test_card, 2.0) - def test_lossless_rt(self): - im = self.roundtrip(test_card) - assert_image_equal(im, test_card) +# These two test pre-written JPEG 2000 files that were not written with +# PIL (they were made using Adobe Photoshop) - def test_lossy_rt(self): - im = self.roundtrip(test_card, quality_layers=[20]) + +def test_lossless(tmp_path): + with Image.open("Tests/images/test-card-lossless.jp2") as im: + im.load() + outfile = str(tmp_path / "temp_test-card.png") + im.save(outfile) + assert_image_similar(im, test_card, 1.0e-3) + + +def test_lossy_tiled(): + with Image.open("Tests/images/test-card-lossy-tiled.jp2") as im: + im.load() assert_image_similar(im, test_card, 2.0) - def test_tiled_rt(self): - im = self.roundtrip(test_card, tile_size=(128, 128)) - assert_image_equal(im, test_card) - def test_tiled_offset_rt(self): - im = self.roundtrip( - test_card, tile_size=(128, 128), tile_offset=(0, 0), offset=(32, 32) - ) - assert_image_equal(im, test_card) +def test_lossless_rt(): + im = roundtrip(test_card) + assert_image_equal(im, test_card) - def test_tiled_offset_too_small(self): - with self.assertRaises(ValueError): - self.roundtrip( - test_card, tile_size=(128, 128), tile_offset=(0, 0), offset=(128, 32) - ) - def test_irreversible_rt(self): - im = self.roundtrip(test_card, irreversible=True, quality_layers=[20]) - assert_image_similar(im, test_card, 2.0) +def test_lossy_rt(): + im = roundtrip(test_card, quality_layers=[20]) + assert_image_similar(im, test_card, 2.0) - def test_prog_qual_rt(self): - im = self.roundtrip(test_card, quality_layers=[60, 40, 20], progression="LRCP") - assert_image_similar(im, test_card, 2.0) - def test_prog_res_rt(self): - im = self.roundtrip(test_card, num_resolutions=8, progression="RLCP") - assert_image_equal(im, test_card) +def test_tiled_rt(): + im = roundtrip(test_card, tile_size=(128, 128)) + assert_image_equal(im, test_card) - def test_reduce(self): - with Image.open("Tests/images/test-card-lossless.jp2") as im: - im.reduce = 2 - im.load() - self.assertEqual(im.size, (160, 120)) - def test_layers_type(self): - outfile = self.tempfile("temp_layers.jp2") - for quality_layers in [[100, 50, 10], (100, 50, 10), None]: +def test_tiled_offset_rt(): + im = roundtrip(test_card, tile_size=(128, 128), tile_offset=(0, 0), offset=(32, 32)) + assert_image_equal(im, test_card) + + +def test_tiled_offset_too_small(): + with pytest.raises(ValueError): + roundtrip(test_card, tile_size=(128, 128), tile_offset=(0, 0), offset=(128, 32)) + + +def test_irreversible_rt(): + im = roundtrip(test_card, irreversible=True, quality_layers=[20]) + assert_image_similar(im, test_card, 2.0) + + +def test_prog_qual_rt(): + im = roundtrip(test_card, quality_layers=[60, 40, 20], progression="LRCP") + assert_image_similar(im, test_card, 2.0) + + +def test_prog_res_rt(): + im = roundtrip(test_card, num_resolutions=8, progression="RLCP") + assert_image_equal(im, test_card) + + +def test_reduce(): + with Image.open("Tests/images/test-card-lossless.jp2") as im: + assert callable(im.reduce) + + im.reduce = 2 + assert im.reduce == 2 + + im.load() + assert im.size == (160, 120) + + im.thumbnail((40, 40)) + assert im.size == (40, 30) + + +def test_layers_type(tmp_path): + outfile = str(tmp_path / "temp_layers.jp2") + for quality_layers in [[100, 50, 10], (100, 50, 10), None]: + test_card.save(outfile, quality_layers=quality_layers) + + for quality_layers in ["quality_layers", ("100", "50", "10")]: + with pytest.raises(ValueError): test_card.save(outfile, quality_layers=quality_layers) - for quality_layers in ["quality_layers", ("100", "50", "10")]: - self.assertRaises( - ValueError, test_card.save, outfile, quality_layers=quality_layers - ) - def test_layers(self): - out = BytesIO() - test_card.save( - out, "JPEG2000", quality_layers=[100, 50, 10], progression="LRCP" - ) - out.seek(0) +def test_layers(): + out = BytesIO() + test_card.save(out, "JPEG2000", quality_layers=[100, 50, 10], progression="LRCP") + out.seek(0) - with Image.open(out) as im: - im.layers = 1 - im.load() - assert_image_similar(im, test_card, 13) + with Image.open(out) as im: + im.layers = 1 + im.load() + assert_image_similar(im, test_card, 13) - out.seek(0) - with Image.open(out) as im: - im.layers = 3 - im.load() - assert_image_similar(im, test_card, 0.4) + out.seek(0) + with Image.open(out) as im: + im.layers = 3 + im.load() + assert_image_similar(im, test_card, 0.4) - def test_rgba(self): - # 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() +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: - # Assert - self.assertEqual(j2k.mode, "RGBA") - self.assertEqual(jp2.mode, "RGBA") - - def test_16bit_monochrome_has_correct_mode(self): - with Image.open("Tests/images/16bit.cropped.j2k") as j2k: + # Act j2k.load() - self.assertEqual(j2k.mode, "I;16") - - with Image.open("Tests/images/16bit.cropped.jp2") as jp2: jp2.load() - self.assertEqual(jp2.mode, "I;16") - @pytest.mark.xfail(is_big_endian() and on_ci(), reason="Fails on big-endian") - def test_16bit_monochrome_jp2_like_tiff(self): - with Image.open("Tests/images/16bit.cropped.tif") as tiff_16bit: - with Image.open("Tests/images/16bit.cropped.jp2") as jp2: - assert_image_similar(jp2, tiff_16bit, 1e-3) + # Assert + assert j2k.mode == "RGBA" + assert jp2.mode == "RGBA" - @pytest.mark.xfail(is_big_endian() and on_ci(), reason="Fails on big-endian") - def test_16bit_monochrome_j2k_like_tiff(self): - with Image.open("Tests/images/16bit.cropped.tif") as tiff_16bit: - with Image.open("Tests/images/16bit.cropped.j2k") as j2k: - assert_image_similar(j2k, tiff_16bit, 1e-3) - def test_16bit_j2k_roundtrips(self): - with Image.open("Tests/images/16bit.cropped.j2k") as j2k: - im = self.roundtrip(j2k) - assert_image_equal(im, j2k) +def test_16bit_monochrome_has_correct_mode(): + with Image.open("Tests/images/16bit.cropped.j2k") as j2k: + j2k.load() + assert j2k.mode == "I;16" - def test_16bit_jp2_roundtrips(self): + with Image.open("Tests/images/16bit.cropped.jp2") as jp2: + jp2.load() + assert jp2.mode == "I;16" + + +@pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian") +def test_16bit_monochrome_jp2_like_tiff(): + with Image.open("Tests/images/16bit.cropped.tif") as tiff_16bit: with Image.open("Tests/images/16bit.cropped.jp2") as jp2: - im = self.roundtrip(jp2) - assert_image_equal(im, jp2) + assert_image_similar(jp2, tiff_16bit, 1e-3) - def test_unbound_local(self): - # prepatch, a malformed jp2 file could cause an UnboundLocalError - # exception. - with self.assertRaises(IOError): - Image.open("Tests/images/unbound_variable.jp2") - def test_parser_feed(self): - # Arrange - from PIL import ImageFile +@pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian") +def test_16bit_monochrome_j2k_like_tiff(): + with Image.open("Tests/images/16bit.cropped.tif") as tiff_16bit: + with Image.open("Tests/images/16bit.cropped.j2k") as j2k: + assert_image_similar(j2k, tiff_16bit, 1e-3) - with open("Tests/images/test-card-lossless.jp2", "rb") as f: - data = f.read() - # Act - p = ImageFile.Parser() - p.feed(data) +def test_16bit_j2k_roundtrips(): + with Image.open("Tests/images/16bit.cropped.j2k") as j2k: + im = roundtrip(j2k) + assert_image_equal(im, j2k) - # Assert - self.assertEqual(p.image.size, (640, 480)) + +def test_16bit_jp2_roundtrips(): + with Image.open("Tests/images/16bit.cropped.jp2") as jp2: + im = roundtrip(jp2) + assert_image_equal(im, jp2) + + +def test_unbound_local(): + # prepatch, a malformed jp2 file could cause an UnboundLocalError exception. + with pytest.raises(OSError): + Image.open("Tests/images/unbound_variable.jp2") + + +def test_parser_feed(): + # Arrange + with open("Tests/images/test-card-lossless.jp2", "rb") as f: + data = f.read() + + # Act + p = ImageFile.Parser() + p.feed(data) + + # Assert + assert p.image.size == (640, 480) diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index b367bc205..7d3e10c24 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -1,47 +1,45 @@ import base64 import io import itertools -import logging import os +import re from collections import namedtuple from ctypes import c_float -from PIL import Image, TiffImagePlugin, TiffTags, features +import pytest + +from PIL import Image, ImageFilter, TiffImagePlugin, TiffTags, features from .helper import ( - PillowTestCase, assert_image_equal, assert_image_equal_tofile, assert_image_similar, assert_image_similar_tofile, hopper, + is_big_endian, + skip_unless_feature, ) -logger = logging.getLogger(__name__) - -class LibTiffTestCase(PillowTestCase): - def setUp(self): - if not features.check("libtiff"): - self.skipTest("tiff support not available") - - def _assert_noerr(self, im): +@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 - self.assertEqual(im.mode, "1") + assert im.mode == "1" # Does the data actually load im.load() im.getdata() try: - self.assertEqual(im._compression, "group4") + assert im._compression == "group4" except AttributeError: print("No _compression") print(dir(im)) # can we write it back out, in a different form. - out = self.tempfile("temp.png") + out = str(tmp_path / "temp.png") im.save(out) out_bytes = io.BytesIO() @@ -49,29 +47,32 @@ class LibTiffTestCase(PillowTestCase): class TestFileLibTiff(LibTiffTestCase): - def test_g4_tiff(self): + 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: - self.assertEqual(im.size, (500, 500)) - self._assert_noerr(im) + assert im.size == (500, 500) + self._assert_noerr(tmp_path, im) - def test_g4_large(self): + def test_g4_large(self, tmp_path): test_file = "Tests/images/pport_g4.tif" with Image.open(test_file) as im: - self._assert_noerr(im) + self._assert_noerr(tmp_path, im) - def test_g4_tiff_file(self): + 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: - self.assertEqual(im.size, (500, 500)) - self._assert_noerr(im) + assert im.size == (500, 500) + self._assert_noerr(tmp_path, im) - def test_g4_tiff_bytesio(self): + 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() @@ -79,10 +80,10 @@ class TestFileLibTiff(LibTiffTestCase): s.write(f.read()) s.seek(0) with Image.open(s) as im: - self.assertEqual(im.size, (500, 500)) - self._assert_noerr(im) + assert im.size == (500, 500) + self._assert_noerr(tmp_path, im) - def test_g4_non_disk_file_object(self): + 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() @@ -91,8 +92,8 @@ class TestFileLibTiff(LibTiffTestCase): s.seek(0) r = io.BufferedReader(s) with Image.open(r) as im: - self.assertEqual(im.size, (500, 500)) - self._assert_noerr(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""" @@ -107,39 +108,39 @@ class TestFileLibTiff(LibTiffTestCase): with Image.open("Tests/images/g4-fillorder-test.tif") as g4: assert_image_equal(g4, png) - def test_g4_write(self): + 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 = self.tempfile("temp.tif") + out = str(tmp_path / "temp.tif") rot = orig.transpose(Image.ROTATE_90) - self.assertEqual(rot.size, (500, 500)) + assert rot.size == (500, 500) rot.save(out) with Image.open(out) as reread: - self.assertEqual(reread.size, (500, 500)) - self._assert_noerr(reread) + assert reread.size == (500, 500) + self._assert_noerr(tmp_path, reread) assert_image_equal(reread, rot) - self.assertEqual(reread.info["compression"], "group4") + assert reread.info["compression"] == "group4" - self.assertEqual(reread.info["compression"], orig.info["compression"]) + assert reread.info["compression"] == orig.info["compression"] - self.assertNotEqual(orig.tobytes(), reread.tobytes()) + 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: - self.assertEqual(im.mode, "RGB") - self.assertEqual(im.size, (278, 374)) - self.assertEqual(im.tile[0][:3], ("libtiff", (0, 0, 278, 374), 0)) + 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") - def test_write_metadata(self): + def test_write_metadata(self, tmp_path): """ Test metadata writing through libtiff """ for legacy_api in [False, True]: - f = self.tempfile("temp.tiff") + f = str(tmp_path / "temp.tiff") with Image.open("Tests/images/hopper_g4.tif") as img: img.save(f, tiffinfo=img.tag) @@ -168,26 +169,23 @@ class TestFileLibTiff(LibTiffTestCase): val = original[tag] if tag.endswith("Resolution"): if legacy_api: - self.assertEqual( - c_float(val[0][0] / val[0][1]).value, - c_float(value[0][0] / value[0][1]).value, - msg="%s didn't roundtrip" % tag, - ) + assert ( + c_float(val[0][0] / val[0][1]).value + == c_float(value[0][0] / value[0][1]).value + ), f"{tag} didn't roundtrip" else: - self.assertEqual( - c_float(val).value, - c_float(value).value, - msg="%s didn't roundtrip" % tag, - ) + assert ( + c_float(val).value == c_float(value).value + ), f"{tag} didn't roundtrip" else: - self.assertEqual(val, value, msg="%s didn't roundtrip" % tag) + 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: - self.assertIn(field, reloaded, "%s not in metadata" % field) + assert field in reloaded, f"{field} not in metadata" - def test_additional_metadata(self): + 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. @@ -208,6 +206,7 @@ class TestFileLibTiff(LibTiffTestCase): del core_items[tag] except KeyError: pass + del core_items[320] # colormap is special, tested below # Type codes: # 2: "ascii", @@ -236,14 +235,14 @@ class TestFileLibTiff(LibTiffTestCase): # Extra samples really doesn't make sense in this application. del new_ifd[338] - out = self.tempfile("temp.tif") + 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): + def test_custom_metadata(self, tmp_path): tc = namedtuple("test_case", "value,type,supported_by_default") custom = { 37000 + k: v @@ -288,7 +287,7 @@ class TestFileLibTiff(LibTiffTestCase): def check_tags(tiffinfo): im = hopper() - out = self.tempfile("temp.tif") + out = str(tmp_path / "temp.tif") im.save(out, tiffinfo=tiffinfo) with Image.open(out) as reloaded: @@ -299,13 +298,12 @@ class TestFileLibTiff(LibTiffTestCase): and libtiff ): # libtiff does not support real RATIONALS - self.assertAlmostEqual(float(reloaded_value), float(value)) + assert ( + round(abs(float(reloaded_value) - float(value)), 7) == 0 + ) continue - if libtiff and isinstance(value, bytes): - value = value.decode() - - self.assertEqual(reloaded_value, value) + assert reloaded_value == value # Test with types ifd = TiffImagePlugin.ImageFileDirectory_v2() @@ -325,77 +323,88 @@ class TestFileLibTiff(LibTiffTestCase): ) TiffImagePlugin.WRITE_LIBTIFF = False - def test_int_dpi(self): + 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 = self.tempfile("temp.tif") + 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: - self.assertEqual(reloaded.info["dpi"], (72.0, 72.0)) + assert reloaded.info["dpi"] == (72.0, 72.0) - def test_g3_compression(self): + def test_g3_compression(self, tmp_path): with Image.open("Tests/images/hopper_g4_500.tif") as i: - out = self.tempfile("temp.tif") + out = str(tmp_path / "temp.tif") i.save(out, compression="group3") with Image.open(out) as reread: - self.assertEqual(reread.info["compression"], "group3") + assert reread.info["compression"] == "group3" assert_image_equal(reread, i) - def test_little_endian(self): + def test_little_endian(self, tmp_path): with Image.open("Tests/images/16bit.deflate.tif") as im: - self.assertEqual(im.getpixel((0, 0)), 480) - self.assertEqual(im.mode, "I;16") + assert im.getpixel((0, 0)) == 480 + assert im.mode == "I;16" b = im.tobytes() # Bytes are in image native order (little endian) - self.assertEqual(b[0], ord(b"\xe0")) - self.assertEqual(b[1], ord(b"\x01")) + assert b[0] == ord(b"\xe0") + assert b[1] == ord(b"\x01") - out = self.tempfile("temp.tif") + out = str(tmp_path / "temp.tif") # out = "temp.le.tif" im.save(out) with Image.open(out) as reread: - self.assertEqual(reread.info["compression"], im.info["compression"]) - self.assertEqual(reread.getpixel((0, 0)), 480) + 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): + def test_big_endian(self, tmp_path): with Image.open("Tests/images/16bit.MM.deflate.tif") as im: - self.assertEqual(im.getpixel((0, 0)), 480) - self.assertEqual(im.mode, "I;16B") + assert im.getpixel((0, 0)) == 480 + assert im.mode == "I;16B" b = im.tobytes() # Bytes are in image native order (big endian) - self.assertEqual(b[0], ord(b"\x01")) - self.assertEqual(b[1], ord(b"\xe0")) + assert b[0] == ord(b"\x01") + assert b[1] == ord(b"\xe0") - out = self.tempfile("temp.tif") + out = str(tmp_path / "temp.tif") im.save(out) with Image.open(out) as reread: - self.assertEqual(reread.info["compression"], im.info["compression"]) - self.assertEqual(reread.getpixel((0, 0)), 480) + assert reread.info["compression"] == im.info["compression"] + assert reread.getpixel((0, 0)) == 480 - def test_g4_string_info(self): + 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 = self.tempfile("temp.tif") + out = str(tmp_path / "temp.tif") orig.tag[269] = "temp.tif" orig.save(out) with Image.open(out) as reread: - self.assertEqual("temp.tif", reread.tag_v2[269]) - self.assertEqual("temp.tif", reread.tag[269][0]) + 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? """ + """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() @@ -408,12 +417,10 @@ class TestFileLibTiff(LibTiffTestCase): assert_image_equal_tofile(im, "Tests/images/12in16bit.tif") - def test_blur(self): + def test_blur(self, tmp_path): # test case from irc, how to do blur on b/w image # and save to compressed tif. - from PIL import ImageFilter - - out = self.tempfile("temp.tif") + out = str(tmp_path / "temp.tif") with Image.open("Tests/images/pport_g4.tif") as im: im = im.convert("L") @@ -425,11 +432,11 @@ class TestFileLibTiff(LibTiffTestCase): assert_image_equal(im, im2) - def test_compressions(self): + def test_compressions(self, tmp_path): # Test various tiff compressions and assert similar image content but reduced # file sizes. im = hopper("RGB") - out = self.tempfile("temp.tif") + out = str(tmp_path / "temp.tif") im.save(out) size_raw = os.path.getsize(out) @@ -449,40 +456,67 @@ class TestFileLibTiff(LibTiffTestCase): with Image.open(out) as im3: assert_image_similar(im2, im3, 30) - self.assertGreater(size_raw, size_compressed) - self.assertGreater(size_compressed, size_jpeg) - self.assertGreater(size_jpeg, size_jpeg_30) + assert size_raw > size_compressed + assert size_compressed > size_jpeg + assert size_jpeg > size_jpeg_30 - def test_quality(self): + def test_tiff_jpeg_compression(self, tmp_path): im = hopper("RGB") - out = self.tempfile("temp.tif") + out = str(tmp_path / "temp.tif") + im.save(out, compression="tiff_jpeg") - self.assertRaises(ValueError, im.save, out, compression="tiff_lzw", quality=50) - self.assertRaises(ValueError, im.save, out, compression="jpeg", quality=-1) - self.assertRaises(ValueError, im.save, out, compression="jpeg", quality=101) - self.assertRaises(ValueError, im.save, out, compression="jpeg", quality="good") + with Image.open(out) as reloaded: + assert reloaded.info["compression"] == "jpeg" + + 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): + def test_cmyk_save(self, tmp_path): im = hopper("CMYK") - out = self.tempfile("temp.tif") + out = str(tmp_path / "temp.tif") im.save(out, compression="tiff_adobe_deflate") with Image.open(out) as im2: assert_image_equal(im, im2) - def xtest_bw_compression_w_rgb(self): - """ This test passes, but when running all tests causes a failure due - to output on stderr from the error thrown by libtiff. We need to - capture that but not now""" + def test_palette_save(self, tmp_path): + im = hopper("P") + 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 + + def xtest_bw_compression_w_rgb(self, tmp_path): + """This test passes, but when running all tests causes a failure due + to output on stderr from the error thrown by libtiff. We need to + capture that but not now""" im = hopper("RGB") - out = self.tempfile("temp.tif") + out = str(tmp_path / "temp.tif") - self.assertRaises(IOError, im.save, out, compression="tiff_ccitt") - self.assertRaises(IOError, im.save, out, compression="group3") - self.assertRaises(IOError, im.save, out, compression="group4") + with pytest.raises(OSError): + im.save(out, compression="tiff_ccitt") + with pytest.raises(OSError): + im.save(out, compression="group3") + with pytest.raises(OSError): + im.save(out, compression="group4") def test_fp_leak(self): im = Image.open("Tests/images/hopper_g4_500.tif") @@ -490,10 +524,13 @@ class TestFileLibTiff(LibTiffTestCase): os.fstat(fn) im.load() # this should close it. - self.assertRaises(OSError, os.fstat, fn) + with pytest.raises(OSError): + os.fstat(fn) im = None # this should force even more closed. - self.assertRaises(OSError, os.fstat, fn) - self.assertRaises(OSError, os.close, fn) + with pytest.raises(OSError): + os.fstat(fn) + with pytest.raises(OSError): + os.close(fn) def test_multipage(self): # issue #862 @@ -502,19 +539,19 @@ class TestFileLibTiff(LibTiffTestCase): # file is a multipage tiff, 10x10 green, 10x10 red, 20x20 blue im.seek(0) - self.assertEqual(im.size, (10, 10)) - self.assertEqual(im.convert("RGB").getpixel((0, 0)), (0, 128, 0)) - self.assertTrue(im.tag.next) + assert im.size == (10, 10) + assert im.convert("RGB").getpixel((0, 0)) == (0, 128, 0) + assert im.tag.next im.seek(1) - self.assertEqual(im.size, (10, 10)) - self.assertEqual(im.convert("RGB").getpixel((0, 0)), (255, 0, 0)) - self.assertTrue(im.tag.next) + assert im.size == (10, 10) + assert im.convert("RGB").getpixel((0, 0)) == (255, 0, 0) + assert im.tag.next im.seek(2) - self.assertFalse(im.tag.next) - self.assertEqual(im.size, (20, 20)) - self.assertEqual(im.convert("RGB").getpixel((0, 0)), (0, 0, 255)) + assert not im.tag.next + assert im.size == (20, 20) + assert im.convert("RGB").getpixel((0, 0)) == (0, 0, 255) TiffImagePlugin.READ_LIBTIFF = False @@ -523,7 +560,7 @@ class TestFileLibTiff(LibTiffTestCase): TiffImagePlugin.READ_LIBTIFF = True with Image.open("Tests/images/multipage.tiff") as im: frames = im.n_frames - self.assertEqual(frames, 3) + assert frames == 3 for _ in range(frames): im.seek(0) # Should not raise ValueError: I/O operation on closed file @@ -534,9 +571,9 @@ class TestFileLibTiff(LibTiffTestCase): def test__next(self): TiffImagePlugin.READ_LIBTIFF = True with Image.open("Tests/images/hopper.tif") as im: - self.assertFalse(im.tag.next) + assert not im.tag.next im.load() - self.assertFalse(im.tag.next) + assert not im.tag.next def test_4bit(self): # Arrange @@ -549,8 +586,8 @@ class TestFileLibTiff(LibTiffTestCase): TiffImagePlugin.READ_LIBTIFF = False # Assert - self.assertEqual(im.size, (128, 128)) - self.assertEqual(im.mode, "L") + assert im.size == (128, 128) + assert im.mode == "L" assert_image_similar(im, original, 7.3) def test_gray_semibyte_per_pixel(self): @@ -577,13 +614,13 @@ class TestFileLibTiff(LibTiffTestCase): original = hopper("L") for epsilon, group in test_files: with Image.open(group[0]) as im: - self.assertEqual(im.size, (128, 128)) - self.assertEqual(im.mode, "L") + 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: - self.assertEqual(im2.size, (128, 128)) - self.assertEqual(im2.mode, "L") + assert im2.size == (128, 128) + assert im2.mode == "L" assert_image_equal(im, im2) def test_save_bytesio(self): @@ -613,22 +650,22 @@ class TestFileLibTiff(LibTiffTestCase): TiffImagePlugin.WRITE_LIBTIFF = False TiffImagePlugin.READ_LIBTIFF = False - def test_crashing_metadata(self): + def test_crashing_metadata(self, tmp_path): # issue 1597 with Image.open("Tests/images/rdf.tif") as im: - out = self.tempfile("temp.tif") + 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): + 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 = self.tempfile("temp.tif") + 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 @@ -637,10 +674,10 @@ class TestFileLibTiff(LibTiffTestCase): # Should not divide by zero im.save(outfile) - def test_fd_duplication(self): + def test_fd_duplication(self, tmp_path): # https://github.com/python-pillow/Pillow/issues/1651 - tmpfile = self.tempfile("temp.tif") + 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()) @@ -654,34 +691,54 @@ class TestFileLibTiff(LibTiffTestCase): def test_read_icc(self): with Image.open("Tests/images/hopper.iccprofile.tif") as img: icc = img.info.get("icc_profile") - self.assertIsNotNone(icc) + 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") - self.assertIsNotNone(icc_libtiff) + assert icc_libtiff is not None TiffImagePlugin.READ_LIBTIFF = False - self.assertEqual(icc, icc_libtiff) + 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) - self.assertEqual(im._compression, "tiff_ccitt") - self.assertEqual(im.size, (10, 10)) + assert im._compression == "tiff_ccitt" + assert im.size == (10, 10) im.seek(1) - self.assertEqual(im._compression, "packbits") - self.assertEqual(im.size, (10, 10)) + assert im._compression == "packbits" + assert im.size == (10, 10) im.load() im.seek(0) - self.assertEqual(im._compression, "tiff_ccitt") - self.assertEqual(im.size, (10, 10)) + assert im._compression == "tiff_ccitt" + assert im.size == (10, 10) im.load() - def test_save_tiff_with_jpegtables(self): + def test_save_tiff_with_jpegtables(self, tmp_path): # Arrange - outfile = self.tempfile("temp.tif") + outfile = str(tmp_path / "temp.tif") # Created with ImageMagick: convert hopper.jpg hopper_jpg.tif # Contains JPEGTables (347) tag @@ -693,57 +750,41 @@ class TestFileLibTiff(LibTiffTestCase): def test_16bit_RGB_tiff(self): with Image.open("Tests/images/tiff_16bit_RGB.tiff") as im: - self.assertEqual(im.mode, "RGB") - self.assertEqual(im.size, (100, 40)) - self.assertEqual( - im.tile, - [ - ( - "libtiff", - (0, 0, 100, 40), - 0, - ("RGB;16N", "tiff_adobe_deflate", False, 8), - ) - ], - ) + 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: - self.assertEqual(im.mode, "RGBA") - self.assertEqual(im.size, (100, 40)) - self.assertEqual( - im.tile, - [ - ( - "libtiff", - (0, 0, 100, 40), - 0, - ("RGBa;16N", "tiff_lzw", False, 38236), - ) - ], - ) + 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] - - codecs = dir(Image.core) - if "jpeg_decoder" not in codecs: - self.skipTest("jpeg support not available") - filename = "Tests/images/pil168.tif" with Image.open(filename) as im: - self.assertEqual(im.mode, "RGB") - self.assertEqual(im.size, (256, 256)) - self.assertEqual( - im.tile, - [("libtiff", (0, 0, 256, 256), 0, ("RGB", "jpeg", False, 5122))], - ) + 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") @@ -751,15 +792,15 @@ class TestFileLibTiff(LibTiffTestCase): def test_sampleformat(self): # https://github.com/python-pillow/Pillow/issues/1466 with Image.open("Tests/images/copyleft.tiff") as im: - self.assertEqual(im.mode, "RGB") + assert im.mode == "RGB" assert_image_equal_tofile(im, "Tests/images/copyleft.png", mode="RGB") def test_lzw(self): with Image.open("Tests/images/hopper_lzw.tif") as im: - self.assertEqual(im.mode, "RGB") - self.assertEqual(im.size, (128, 128)) - self.assertEqual(im.format, "TIFF") + assert im.mode == "RGB" + assert im.size == (128, 128) + assert im.format == "TIFF" im2 = hopper() assert_image_similar(im, im2, 5) @@ -773,11 +814,13 @@ class TestFileLibTiff(LibTiffTestCase): with Image.open(infile) as im: assert_image_similar_tofile(im, "Tests/images/pil_sample_cmyk.jpg", 0.5) + @pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian") 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", 0.5) + @pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian") def test_strip_ycbcr_jpeg_1x1_sampling(self): infile = "Tests/images/tiff_strip_ycbcr_jpeg_1x1_sampling.tif" with Image.open(infile) as im: @@ -788,16 +831,19 @@ class TestFileLibTiff(LibTiffTestCase): with Image.open(infile) as im: assert_image_similar_tofile(im, "Tests/images/pil_sample_cmyk.jpg", 0.5) + @pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian") 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_equal_tofile(im, "Tests/images/flower2.jpg") + @pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian") 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", 0.5) + @pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian") def test_old_style_jpeg(self): infile = "Tests/images/old-style-jpeg-compression.tif" with Image.open(infile) as im: @@ -808,7 +854,7 @@ class TestFileLibTiff(LibTiffTestCase): infile = "Tests/images/no_rows_per_strip.tif" with Image.open(infile) as im: im.load() - self.assertEqual(im.size, (950, 975)) + assert im.size == (950, 975) def test_orientation(self): with Image.open("Tests/images/g4_orientation_1.tif") as base_im: @@ -842,9 +888,9 @@ class TestFileLibTiff(LibTiffTestCase): def test_realloc_overflow(self): TiffImagePlugin.READ_LIBTIFF = True with Image.open("Tests/images/tiff_overflow_rows_per_strip.tif") as im: - with self.assertRaises(IOError) as e: + with pytest.raises(OSError) as e: im.load() # Assert that the error code is IMAGING_CODEC_MEMORY - self.assertEqual(str(e.exception), "-9") + assert str(e.value) == "-9" TiffImagePlugin.READ_LIBTIFF = False diff --git a/Tests/test_file_libtiff_small.py b/Tests/test_file_libtiff_small.py index a4e74896f..03137c8b6 100644 --- a/Tests/test_file_libtiff_small.py +++ b/Tests/test_file_libtiff_small.py @@ -7,24 +7,24 @@ 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. + """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. """ + 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): + 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: - self.assertEqual(im.size, (128, 128)) - self._assert_noerr(im) + assert im.size == (128, 128) + self._assert_noerr(tmp_path, im) - def test_g4_hopper_bytesio(self): + def test_g4_hopper_bytesio(self, tmp_path): """Testing the bytesio loading code path""" test_file = "Tests/images/hopper_g4.tif" s = BytesIO() @@ -32,13 +32,13 @@ class TestFileLibTiffSmall(LibTiffTestCase): s.write(f.read()) s.seek(0) with Image.open(s) as im: - self.assertEqual(im.size, (128, 128)) - self._assert_noerr(im) + assert im.size == (128, 128) + self._assert_noerr(tmp_path, im) - def test_g4_hopper(self): + 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: - self.assertEqual(im.size, (128, 128)) - self._assert_noerr(im) + assert im.size == (128, 128) + self._assert_noerr(tmp_path, im) diff --git a/Tests/test_file_mcidas.py b/Tests/test_file_mcidas.py index 4380f91e1..88c8f8f4f 100644 --- a/Tests/test_file_mcidas.py +++ b/Tests/test_file_mcidas.py @@ -1,28 +1,31 @@ +import pytest + from PIL import Image, McIdasImagePlugin -from .helper import PillowTestCase, assert_image_equal +from .helper import assert_image_equal -class TestFileMcIdas(PillowTestCase): - def test_invalid_file(self): - invalid_file = "Tests/images/flower.jpg" +def test_invalid_file(): + invalid_file = "Tests/images/flower.jpg" - self.assertRaises(SyntaxError, McIdasImagePlugin.McIdasImageFile, invalid_file) + with pytest.raises(SyntaxError): + McIdasImagePlugin.McIdasImageFile(invalid_file) - def test_valid_file(self): - # Arrange - # https://ghrc.nsstc.nasa.gov/hydro/details/cmx3g8 - # https://ghrc.nsstc.nasa.gov/pub/fieldCampaigns/camex3/cmx3g8/browse/ - test_file = "Tests/images/cmx3g8_wv_1998.260_0745_mcidas.ara" - saved_file = "Tests/images/cmx3g8_wv_1998.260_0745_mcidas.png" - # Act - with Image.open(test_file) as im: - im.load() +def test_valid_file(): + # Arrange + # https://ghrc.nsstc.nasa.gov/hydro/details/cmx3g8 + # https://ghrc.nsstc.nasa.gov/pub/fieldCampaigns/camex3/cmx3g8/browse/ + test_file = "Tests/images/cmx3g8_wv_1998.260_0745_mcidas.ara" + saved_file = "Tests/images/cmx3g8_wv_1998.260_0745_mcidas.png" - # Assert - self.assertEqual(im.format, "MCIDAS") - self.assertEqual(im.mode, "I") - self.assertEqual(im.size, (1800, 400)) - with Image.open(saved_file) as im2: - assert_image_equal(im, im2) + # Act + with Image.open(test_file) as im: + im.load() + + # Assert + assert im.format == "MCIDAS" + assert im.mode == "I" + assert im.size == (1800, 400) + with Image.open(saved_file) as im2: + assert_image_equal(im, im2) diff --git a/Tests/test_file_mic.py b/Tests/test_file_mic.py index 75f709725..464d138e2 100644 --- a/Tests/test_file_mic.py +++ b/Tests/test_file_mic.py @@ -1,65 +1,63 @@ -import unittest +import pytest -from PIL import Image, ImagePalette, features +from PIL import Image, ImagePalette -from .helper import PillowTestCase, assert_image_similar, hopper - -try: - from PIL import MicImagePlugin -except ImportError: - olefile_installed = False -else: - olefile_installed = True +from .helper import assert_image_similar, hopper, skip_unless_feature +MicImagePlugin = pytest.importorskip( + "PIL.MicImagePlugin", reason="olefile not installed" +) +pytestmark = skip_unless_feature("libtiff") TEST_FILE = "Tests/images/hopper.mic" -@unittest.skipUnless(olefile_installed, "olefile package not installed") -@unittest.skipUnless(features.check("libtiff"), "libtiff not installed") -class TestFileMic(PillowTestCase): - def test_sanity(self): - with Image.open(TEST_FILE) as im: - im.load() - self.assertEqual(im.mode, "RGBA") - self.assertEqual(im.size, (128, 128)) - self.assertEqual(im.format, "MIC") +def test_sanity(): + with Image.open(TEST_FILE) as im: + im.load() + assert im.mode == "RGBA" + assert im.size == (128, 128) + assert im.format == "MIC" - # Adjust for the gamma of 2.2 encoded into the file - lut = ImagePalette.make_gamma_lut(1 / 2.2) - im = Image.merge("RGBA", [chan.point(lut) for chan in im.split()]) + # Adjust for the gamma of 2.2 encoded into the file + lut = ImagePalette.make_gamma_lut(1 / 2.2) + im = Image.merge("RGBA", [chan.point(lut) for chan in im.split()]) - im2 = hopper("RGBA") - assert_image_similar(im, im2, 10) + im2 = hopper("RGBA") + assert_image_similar(im, im2, 10) - def test_n_frames(self): - with Image.open(TEST_FILE) as im: - self.assertEqual(im.n_frames, 1) +def test_n_frames(): + with Image.open(TEST_FILE) as im: + assert im.n_frames == 1 - def test_is_animated(self): - with Image.open(TEST_FILE) as im: - self.assertFalse(im.is_animated) +def test_is_animated(): + with Image.open(TEST_FILE) as im: + assert not im.is_animated - def test_tell(self): - with Image.open(TEST_FILE) as im: - self.assertEqual(im.tell(), 0) +def test_tell(): + with Image.open(TEST_FILE) as im: + assert im.tell() == 0 - def test_seek(self): - with Image.open(TEST_FILE) as im: - im.seek(0) - self.assertEqual(im.tell(), 0) +def test_seek(): + with Image.open(TEST_FILE) as im: + im.seek(0) + assert im.tell() == 0 - self.assertRaises(EOFError, im.seek, 99) - self.assertEqual(im.tell(), 0) + with pytest.raises(EOFError): + im.seek(99) + assert im.tell() == 0 - def test_invalid_file(self): - # Test an invalid OLE file - invalid_file = "Tests/images/flower.jpg" - self.assertRaises(SyntaxError, MicImagePlugin.MicImageFile, invalid_file) - # Test a valid OLE file, but not a MIC file - ole_file = "Tests/images/test-ole-file.doc" - self.assertRaises(SyntaxError, MicImagePlugin.MicImageFile, ole_file) +def test_invalid_file(): + # Test an invalid OLE file + invalid_file = "Tests/images/flower.jpg" + with pytest.raises(SyntaxError): + MicImagePlugin.MicImageFile(invalid_file) + + # Test a valid OLE file, but not a MIC file + ole_file = "Tests/images/test-ole-file.doc" + with pytest.raises(SyntaxError): + MicImagePlugin.MicImageFile(ole_file) diff --git a/Tests/test_file_mpo.py b/Tests/test_file_mpo.py index 5ed1bb75e..791efcc3f 100644 --- a/Tests/test_file_mpo.py +++ b/Tests/test_file_mpo.py @@ -1,202 +1,218 @@ -import unittest from io import BytesIO import pytest + from PIL import Image -from .helper import PillowTestCase, assert_image_similar, is_pypy +from .helper import assert_image_similar, is_pypy, skip_unless_feature test_files = ["Tests/images/sugarshack.mpo", "Tests/images/frozenpond.mpo"] +pytestmark = skip_unless_feature("jpg") -class TestFileMpo(PillowTestCase): - def setUp(self): - codecs = dir(Image.core) - if "jpeg_encoder" not in codecs or "jpeg_decoder" not in codecs: - self.skipTest("jpeg support not available") - def frame_roundtrip(self, im, **options): - # Note that for now, there is no MPO saving functionality - out = BytesIO() - im.save(out, "MPO", **options) - test_bytes = out.tell() - out.seek(0) - im = Image.open(out) - im.bytes = test_bytes # for testing only - return im +def frame_roundtrip(im, **options): + # Note that for now, there is no MPO saving functionality + out = BytesIO() + im.save(out, "MPO", **options) + test_bytes = out.tell() + out.seek(0) + im = Image.open(out) + im.bytes = test_bytes # for testing only + return im - def test_sanity(self): - for test_file in test_files: - with Image.open(test_file) as im: - im.load() - self.assertEqual(im.mode, "RGB") - self.assertEqual(im.size, (640, 480)) - self.assertEqual(im.format, "MPO") - @unittest.skipIf(is_pypy(), "Requires CPython") - def test_unclosed_file(self): - def open(): - im = Image.open(test_files[0]) +def test_sanity(): + for test_file in test_files: + with Image.open(test_file) as im: + im.load() + assert im.mode == "RGB" + assert im.size == (640, 480) + assert im.format == "MPO" + + +@pytest.mark.skipif(is_pypy(), reason="Requires CPython") +def test_unclosed_file(): + def open(): + im = Image.open(test_files[0]) + im.load() + + pytest.warns(ResourceWarning, open) + + +def test_closed_file(): + def open(): + im = Image.open(test_files[0]) + im.load() + im.close() + + pytest.warns(None, open) + + +def test_context_manager(): + def open(): + with Image.open(test_files[0]) as im: im.load() - pytest.warns(ResourceWarning, open) + pytest.warns(None, open) - def test_closed_file(self): - def open(): - im = Image.open(test_files[0]) - im.load() - im.close() - pytest.warns(None, open) - - def test_context_manager(self): - def open(): - with Image.open(test_files[0]) as im: - im.load() - - pytest.warns(None, open) - - def test_app(self): - for test_file in test_files: - # Test APP/COM reader (@PIL135) - with Image.open(test_file) as im: - self.assertEqual(im.applist[0][0], "APP1") - self.assertEqual(im.applist[1][0], "APP2") - self.assertEqual( - im.applist[1][1][:16], - b"MPF\x00MM\x00*\x00\x00\x00\x08\x00\x03\xb0\x00", - ) - self.assertEqual(len(im.applist), 2) - - def test_exif(self): - for test_file in test_files: - with Image.open(test_file) as im: - info = im._getexif() - self.assertEqual(info[272], "Nintendo 3DS") - self.assertEqual(info[296], 2) - self.assertEqual(info[34665], 188) - - def test_frame_size(self): - # This image has been hexedited to contain a different size - # in the EXIF data of the second frame - with Image.open("Tests/images/sugarshack_frame_size.mpo") as im: - self.assertEqual(im.size, (640, 480)) - - im.seek(1) - self.assertEqual(im.size, (680, 480)) - - def test_parallax(self): - # Nintendo - with Image.open("Tests/images/sugarshack.mpo") as im: - exif = im.getexif() - self.assertEqual( - exif.get_ifd(0x927C)[0x1101]["Parallax"], -44.798187255859375 +def test_app(): + for test_file in test_files: + # Test APP/COM reader (@PIL135) + with Image.open(test_file) as im: + assert im.applist[0][0] == "APP1" + assert im.applist[1][0] == "APP2" + assert ( + im.applist[1][1][:16] + == b"MPF\x00MM\x00*\x00\x00\x00\x08\x00\x03\xb0\x00" ) + assert len(im.applist) == 2 - # Fujifilm - with Image.open("Tests/images/fujifilm.mpo") as im: - im.seek(1) - exif = im.getexif() - self.assertEqual(exif.get_ifd(0x927C)[0xB211], -3.125) - def test_mp(self): - for test_file in test_files: - with Image.open(test_file) as im: - mpinfo = im._getmp() - self.assertEqual(mpinfo[45056], b"0100") - self.assertEqual(mpinfo[45057], 2) +def test_exif(): + for test_file in test_files: + with Image.open(test_file) as im: + info = im._getexif() + assert info[272] == "Nintendo 3DS" + assert info[296] == 2 + assert info[34665] == 188 - def test_mp_offset(self): - # This image has been manually hexedited to have an IFD offset of 10 - # in APP2 data, in contrast to normal 8 - with Image.open("Tests/images/sugarshack_ifd_offset.mpo") as im: + +def test_frame_size(): + # This image has been hexedited to contain a different size + # in the EXIF data of the second frame + with Image.open("Tests/images/sugarshack_frame_size.mpo") as im: + assert im.size == (640, 480) + + im.seek(1) + assert im.size == (680, 480) + + +def test_parallax(): + # Nintendo + with Image.open("Tests/images/sugarshack.mpo") as im: + exif = im.getexif() + assert exif.get_ifd(0x927C)[0x1101]["Parallax"] == -44.798187255859375 + + # Fujifilm + with Image.open("Tests/images/fujifilm.mpo") as im: + im.seek(1) + exif = im.getexif() + assert exif.get_ifd(0x927C)[0xB211] == -3.125 + + +def test_mp(): + for test_file in test_files: + with Image.open(test_file) as im: mpinfo = im._getmp() - self.assertEqual(mpinfo[45056], b"0100") - self.assertEqual(mpinfo[45057], 2) + assert mpinfo[45056] == b"0100" + assert mpinfo[45057] == 2 - def test_mp_no_data(self): - # This image has been manually hexedited to have the second frame - # beyond the end of the file - with Image.open("Tests/images/sugarshack_no_data.mpo") as im: - with self.assertRaises(ValueError): - im.seek(1) - def test_mp_attribute(self): - for test_file in test_files: - with Image.open(test_file) as im: - mpinfo = im._getmp() - frameNumber = 0 - for mpentry in mpinfo[45058]: - mpattr = mpentry["Attribute"] - if frameNumber: - self.assertFalse(mpattr["RepresentativeImageFlag"]) - else: - self.assertTrue(mpattr["RepresentativeImageFlag"]) - self.assertFalse(mpattr["DependentParentImageFlag"]) - self.assertFalse(mpattr["DependentChildImageFlag"]) - self.assertEqual(mpattr["ImageDataFormat"], "JPEG") - self.assertEqual(mpattr["MPType"], "Multi-Frame Image: (Disparity)") - self.assertEqual(mpattr["Reserved"], 0) - frameNumber += 1 +def test_mp_offset(): + # This image has been manually hexedited to have an IFD offset of 10 + # in APP2 data, in contrast to normal 8 + with Image.open("Tests/images/sugarshack_ifd_offset.mpo") as im: + mpinfo = im._getmp() + assert mpinfo[45056] == b"0100" + assert mpinfo[45057] == 2 - def test_seek(self): - for test_file in test_files: - with Image.open(test_file) as im: - self.assertEqual(im.tell(), 0) - # prior to first image raises an error, both blatant and borderline - self.assertRaises(EOFError, im.seek, -1) - self.assertRaises(EOFError, im.seek, -523) - # after the final image raises an error, - # both blatant and borderline - self.assertRaises(EOFError, im.seek, 2) - self.assertRaises(EOFError, im.seek, 523) - # bad calls shouldn't change the frame - self.assertEqual(im.tell(), 0) - # this one will work - im.seek(1) - self.assertEqual(im.tell(), 1) - # and this one, too - im.seek(0) - self.assertEqual(im.tell(), 0) - def test_n_frames(self): - with Image.open("Tests/images/sugarshack.mpo") as im: - self.assertEqual(im.n_frames, 2) - self.assertTrue(im.is_animated) +def test_mp_no_data(): + # This image has been manually hexedited to have the second frame + # beyond the end of the file + with Image.open("Tests/images/sugarshack_no_data.mpo") as im: + with pytest.raises(ValueError): + im.seek(1) - def test_eoferror(self): - with Image.open("Tests/images/sugarshack.mpo") as im: - n_frames = im.n_frames - # Test seeking past the last frame - self.assertRaises(EOFError, im.seek, n_frames) - self.assertLess(im.tell(), n_frames) +def test_mp_attribute(): + for test_file in test_files: + with Image.open(test_file) as im: + mpinfo = im._getmp() + frameNumber = 0 + for mpentry in mpinfo[45058]: + mpattr = mpentry["Attribute"] + if frameNumber: + assert not mpattr["RepresentativeImageFlag"] + else: + assert mpattr["RepresentativeImageFlag"] + assert not mpattr["DependentParentImageFlag"] + assert not mpattr["DependentChildImageFlag"] + assert mpattr["ImageDataFormat"] == "JPEG" + assert mpattr["MPType"] == "Multi-Frame Image: (Disparity)" + assert mpattr["Reserved"] == 0 + frameNumber += 1 - # Test that seeking to the last frame does not raise an error - im.seek(n_frames - 1) - def test_image_grab(self): - for test_file in test_files: - with Image.open(test_file) as im: - self.assertEqual(im.tell(), 0) - im0 = im.tobytes() - im.seek(1) - self.assertEqual(im.tell(), 1) - im1 = im.tobytes() - im.seek(0) - self.assertEqual(im.tell(), 0) - im02 = im.tobytes() - self.assertEqual(im0, im02) - self.assertNotEqual(im0, im1) +def test_seek(): + for test_file in test_files: + with Image.open(test_file) as im: + assert im.tell() == 0 + # prior to first image raises an error, both blatant and borderline + with pytest.raises(EOFError): + im.seek(-1) + with pytest.raises(EOFError): + im.seek(-523) + # after the final image raises an error, + # both blatant and borderline + with pytest.raises(EOFError): + im.seek(2) + with pytest.raises(EOFError): + im.seek(523) + # bad calls shouldn't change the frame + assert im.tell() == 0 + # this one will work + im.seek(1) + assert im.tell() == 1 + # and this one, too + im.seek(0) + assert im.tell() == 0 - def test_save(self): - # Note that only individual frames can be saved at present - for test_file in test_files: - with Image.open(test_file) as im: - self.assertEqual(im.tell(), 0) - jpg0 = self.frame_roundtrip(im) - assert_image_similar(im, jpg0, 30) - im.seek(1) - self.assertEqual(im.tell(), 1) - jpg1 = self.frame_roundtrip(im) - assert_image_similar(im, jpg1, 30) + +def test_n_frames(): + with Image.open("Tests/images/sugarshack.mpo") as im: + assert im.n_frames == 2 + assert im.is_animated + + +def test_eoferror(): + with Image.open("Tests/images/sugarshack.mpo") as im: + n_frames = im.n_frames + + # Test seeking past the last frame + with pytest.raises(EOFError): + im.seek(n_frames) + assert im.tell() < n_frames + + # Test that seeking to the last frame does not raise an error + im.seek(n_frames - 1) + + +def test_image_grab(): + for test_file in test_files: + with Image.open(test_file) as im: + assert im.tell() == 0 + im0 = im.tobytes() + im.seek(1) + assert im.tell() == 1 + im1 = im.tobytes() + im.seek(0) + assert im.tell() == 0 + im02 = im.tobytes() + assert im0 == im02 + assert im0 != im1 + + +def test_save(): + # Note that only individual frames can be saved at present + for test_file in test_files: + with Image.open(test_file) as im: + assert im.tell() == 0 + jpg0 = frame_roundtrip(im) + assert_image_similar(im, jpg0, 30) + im.seek(1) + assert im.tell() == 1 + jpg1 = frame_roundtrip(im) + assert_image_similar(im, jpg1, 30) diff --git a/Tests/test_file_msp.py b/Tests/test_file_msp.py index 4b355441a..293b856b0 100644 --- a/Tests/test_file_msp.py +++ b/Tests/test_file_msp.py @@ -1,79 +1,91 @@ import os -import unittest + +import pytest from PIL import Image, MspImagePlugin -from .helper import PillowTestCase, assert_image_equal, hopper +from .helper import assert_image_equal, hopper TEST_FILE = "Tests/images/hopper.msp" EXTRA_DIR = "Tests/images/picins" YA_EXTRA_DIR = "Tests/images/msp" -class TestFileMsp(PillowTestCase): - def test_sanity(self): - test_file = self.tempfile("temp.msp") +def test_sanity(tmp_path): + test_file = str(tmp_path / "temp.msp") - hopper("1").save(test_file) + hopper("1").save(test_file) - with Image.open(test_file) as im: - im.load() - self.assertEqual(im.mode, "1") - self.assertEqual(im.size, (128, 128)) - self.assertEqual(im.format, "MSP") + with Image.open(test_file) as im: + im.load() + assert im.mode == "1" + assert im.size == (128, 128) + assert im.format == "MSP" - def test_invalid_file(self): - invalid_file = "Tests/images/flower.jpg" - self.assertRaises(SyntaxError, MspImagePlugin.MspImageFile, invalid_file) +def test_invalid_file(): + invalid_file = "Tests/images/flower.jpg" - def test_bad_checksum(self): - # Arrange - # This was created by forcing Pillow to save with checksum=0 - bad_checksum = "Tests/images/hopper_bad_checksum.msp" + with pytest.raises(SyntaxError): + MspImagePlugin.MspImageFile(invalid_file) - # Act / Assert - self.assertRaises(SyntaxError, MspImagePlugin.MspImageFile, bad_checksum) - def test_open_windows_v1(self): - # Arrange - # Act - with Image.open(TEST_FILE) as im: +def test_bad_checksum(): + # Arrange + # This was created by forcing Pillow to save with checksum=0 + bad_checksum = "Tests/images/hopper_bad_checksum.msp" - # Assert - assert_image_equal(im, hopper("1")) - self.assertIsInstance(im, MspImagePlugin.MspImageFile) + # Act / Assert + with pytest.raises(SyntaxError): + MspImagePlugin.MspImageFile(bad_checksum) - def _assert_file_image_equal(self, source_path, target_path): - with Image.open(source_path) as im: - with Image.open(target_path) as target: - assert_image_equal(im, target) - @unittest.skipUnless(os.path.exists(EXTRA_DIR), "Extra image files not installed") - def test_open_windows_v2(self): +def test_open_windows_v1(): + # Arrange + # Act + with Image.open(TEST_FILE) as im: - files = ( - os.path.join(EXTRA_DIR, f) - for f in os.listdir(EXTRA_DIR) - if os.path.splitext(f)[1] == ".msp" - ) - for path in files: - self._assert_file_image_equal(path, path.replace(".msp", ".png")) + # Assert + assert_image_equal(im, hopper("1")) + assert isinstance(im, MspImagePlugin.MspImageFile) - @unittest.skipIf( - not os.path.exists(YA_EXTRA_DIR), "Even More Extra image files not installed" + +def _assert_file_image_equal(source_path, target_path): + with Image.open(source_path) as im: + with Image.open(target_path) as target: + assert_image_equal(im, target) + + +@pytest.mark.skipif( + 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) + if os.path.splitext(f)[1] == ".msp" ) - def test_msp_v2(self): - for f in os.listdir(YA_EXTRA_DIR): - if ".MSP" not in f: - continue - path = os.path.join(YA_EXTRA_DIR, f) - self._assert_file_image_equal(path, path.replace(".MSP", ".png")) + for path in files: + _assert_file_image_equal(path, path.replace(".msp", ".png")) - def test_cannot_save_wrong_mode(self): - # Arrange - im = hopper() - filename = self.tempfile("temp.msp") - # Act/Assert - self.assertRaises(IOError, im.save, filename) +@pytest.mark.skipif( + not os.path.exists(YA_EXTRA_DIR), reason="Even More Extra image files not installed" +) +def test_msp_v2(): + for f in os.listdir(YA_EXTRA_DIR): + if ".MSP" not in f: + continue + path = os.path.join(YA_EXTRA_DIR, f) + _assert_file_image_equal(path, path.replace(".MSP", ".png")) + + +def test_cannot_save_wrong_mode(tmp_path): + # Arrange + im = hopper() + filename = str(tmp_path / "temp.msp") + + # Act/Assert + with pytest.raises(OSError): + im.save(filename) diff --git a/Tests/test_file_palm.py b/Tests/test_file_palm.py index 0faa4c705..25d194b62 100644 --- a/Tests/test_file_palm.py +++ b/Tests/test_file_palm.py @@ -1,67 +1,85 @@ import os.path +import subprocess -from .helper import ( - PillowTestCase, - assert_image_equal, - hopper, - imagemagick_available, - skip_known_bad_test, -) +import pytest + +from PIL import Image + +from .helper import IMCONVERT, assert_image_equal, hopper, imagemagick_available + +_roundtrip = imagemagick_available() -class TestFilePalm(PillowTestCase): - _roundtrip = imagemagick_available() +def helper_save_as_palm(tmp_path, mode): + # Arrange + im = hopper(mode) + outfile = str(tmp_path / ("temp_" + mode + ".palm")) - def helper_save_as_palm(self, mode): - # Arrange - im = hopper(mode) - outfile = self.tempfile("temp_" + mode + ".palm") + # Act + im.save(outfile) - # Act - im.save(outfile) + # Assert + assert os.path.isfile(outfile) + assert os.path.getsize(outfile) > 0 - # Assert - self.assertTrue(os.path.isfile(outfile)) - self.assertGreater(os.path.getsize(outfile), 0) - def roundtrip(self, mode): - if not self._roundtrip: - return +def open_with_imagemagick(tmp_path, f): + if not imagemagick_available(): + raise OSError() - im = hopper(mode) - outfile = self.tempfile("temp.palm") + outfile = str(tmp_path / "temp.png") + rc = subprocess.call( + [IMCONVERT, f, outfile], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT + ) + if rc: + raise OSError + return Image.open(outfile) - im.save(outfile) - converted = self.open_withImagemagick(outfile) - assert_image_equal(converted, im) - def test_monochrome(self): - # Arrange - mode = "1" +def roundtrip(tmp_path, mode): + if not _roundtrip: + return - # Act / Assert - self.helper_save_as_palm(mode) - self.roundtrip(mode) + im = hopper(mode) + outfile = str(tmp_path / "temp.palm") - def test_p_mode(self): - # Arrange - mode = "P" + im.save(outfile) + converted = open_with_imagemagick(tmp_path, outfile) + assert_image_equal(converted, im) - # Act / Assert - self.helper_save_as_palm(mode) - skip_known_bad_test("Palm P image is wrong") - self.roundtrip(mode) - def test_l_ioerror(self): - # Arrange - mode = "L" +def test_monochrome(tmp_path): + # Arrange + mode = "1" - # Act / Assert - self.assertRaises(IOError, self.helper_save_as_palm, mode) + # Act / Assert + helper_save_as_palm(tmp_path, mode) + roundtrip(tmp_path, mode) - def test_rgb_ioerror(self): - # Arrange - mode = "RGB" - # Act / Assert - self.assertRaises(IOError, self.helper_save_as_palm, mode) +@pytest.mark.xfail(reason="Palm P image is wrong") +def test_p_mode(tmp_path): + # Arrange + mode = "P" + + # Act / Assert + helper_save_as_palm(tmp_path, mode) + roundtrip(tmp_path, mode) + + +def test_l_oserror(tmp_path): + # Arrange + mode = "L" + + # Act / Assert + with pytest.raises(OSError): + helper_save_as_palm(tmp_path, mode) + + +def test_rgb_oserror(tmp_path): + # Arrange + mode = "RGB" + + # Act / Assert + with pytest.raises(OSError): + helper_save_as_palm(tmp_path, mode) diff --git a/Tests/test_file_pcd.py b/Tests/test_file_pcd.py index a59631e1a..dc45a48c1 100644 --- a/Tests/test_file_pcd.py +++ b/Tests/test_file_pcd.py @@ -1,18 +1,15 @@ from PIL import Image -from .helper import PillowTestCase +def test_load_raw(): + with Image.open("Tests/images/hopper.pcd") as im: + im.load() # should not segfault. -class TestFilePcd(PillowTestCase): - def test_load_raw(self): - with Image.open("Tests/images/hopper.pcd") as im: - im.load() # should not segfault. + # Note that this image was created with a resized hopper + # image, which was then converted to pcd with imagemagick + # and the colors are wonky in Pillow. It's unclear if this + # is a pillow or a convert issue, as other images not generated + # from convert look find on pillow and not imagemagick. - # Note that this image was created with a resized hopper - # image, which was then converted to pcd with imagemagick - # and the colors are wonky in Pillow. It's unclear if this - # is a pillow or a convert issue, as other images not generated - # from convert look find on pillow and not imagemagick. - - # target = hopper().resize((768,512)) - # assert_image_similar(im, target, 10) + # target = hopper().resize((768,512)) + # assert_image_similar(im, target, 10) diff --git a/Tests/test_file_pcx.py b/Tests/test_file_pcx.py index 443bef8f5..670c03b95 100644 --- a/Tests/test_file_pcx.py +++ b/Tests/test_file_pcx.py @@ -1,127 +1,143 @@ +import pytest + from PIL import Image, ImageFile, PcxImagePlugin -from .helper import PillowTestCase, assert_image_equal, hopper +from .helper import assert_image_equal, hopper -class TestFilePcx(PillowTestCase): - def _roundtrip(self, im): - f = self.tempfile("temp.pcx") +def _roundtrip(tmp_path, im): + f = str(tmp_path / "temp.pcx") + im.save(f) + with Image.open(f) as im2: + assert im2.mode == im.mode + assert im2.size == im.size + assert im2.format == "PCX" + assert im2.get_format_mimetype() == "image/x-pcx" + assert_image_equal(im2, im) + + +def test_sanity(tmp_path): + for mode in ("1", "L", "P", "RGB"): + _roundtrip(tmp_path, hopper(mode)) + + # Test an unsupported mode + f = str(tmp_path / "temp.pcx") + im = hopper("RGBA") + with pytest.raises(ValueError): im.save(f) - with Image.open(f) as im2: - self.assertEqual(im2.mode, im.mode) - self.assertEqual(im2.size, im.size) - self.assertEqual(im2.format, "PCX") - self.assertEqual(im2.get_format_mimetype(), "image/x-pcx") - assert_image_equal(im2, im) - def test_sanity(self): - for mode in ("1", "L", "P", "RGB"): - self._roundtrip(hopper(mode)) - # Test an unsupported mode - f = self.tempfile("temp.pcx") - im = hopper("RGBA") - self.assertRaises(ValueError, im.save, f) +def test_invalid_file(): + invalid_file = "Tests/images/flower.jpg" - def test_invalid_file(self): - invalid_file = "Tests/images/flower.jpg" + with pytest.raises(SyntaxError): + PcxImagePlugin.PcxImageFile(invalid_file) - self.assertRaises(SyntaxError, PcxImagePlugin.PcxImageFile, invalid_file) - def test_odd(self): - # see issue #523, odd sized images should have a stride that's even. - # not that imagemagick or gimp write pcx that way. - # we were not handling properly. - for mode in ("1", "L", "P", "RGB"): - # larger, odd sized images are better here to ensure that - # we handle interrupted scan lines properly. - self._roundtrip(hopper(mode).resize((511, 511))) +def test_odd(tmp_path): + # See issue #523, odd sized images should have a stride that's even. + # Not that ImageMagick or GIMP write PCX that way. + # We were not handling properly. + for mode in ("1", "L", "P", "RGB"): + # larger, odd sized images are better here to ensure that + # we handle interrupted scan lines properly. + _roundtrip(tmp_path, hopper(mode).resize((511, 511))) - def test_pil184(self): - # Check reading of files where xmin/xmax is not zero. - test_file = "Tests/images/pil184.pcx" - with Image.open(test_file) as im: - self.assertEqual(im.size, (447, 144)) - self.assertEqual(im.tile[0][1], (0, 0, 447, 144)) +def test_pil184(): + # Check reading of files where xmin/xmax is not zero. - # Make sure all pixels are either 0 or 255. - self.assertEqual(im.histogram()[0] + im.histogram()[255], 447 * 144) + test_file = "Tests/images/pil184.pcx" + with Image.open(test_file) as im: + assert im.size == (447, 144) + assert im.tile[0][1] == (0, 0, 447, 144) - def test_1px_width(self): - im = Image.new("L", (1, 256)) - px = im.load() - for y in range(256): - px[0, y] = y - self._roundtrip(im) + # Make sure all pixels are either 0 or 255. + assert im.histogram()[0] + im.histogram()[255] == 447 * 144 - def test_large_count(self): - im = Image.new("L", (256, 1)) - px = im.load() + +def test_1px_width(tmp_path): + im = Image.new("L", (1, 256)) + px = im.load() + for y in range(256): + px[0, y] = y + _roundtrip(tmp_path, im) + + +def test_large_count(tmp_path): + im = Image.new("L", (256, 1)) + px = im.load() + for x in range(256): + px[x, 0] = x // 67 * 67 + _roundtrip(tmp_path, im) + + +def _test_buffer_overflow(tmp_path, im, size=1024): + _last = ImageFile.MAXBLOCK + ImageFile.MAXBLOCK = size + try: + _roundtrip(tmp_path, im) + finally: + ImageFile.MAXBLOCK = _last + + +def test_break_in_count_overflow(tmp_path): + im = Image.new("L", (256, 5)) + px = im.load() + for y in range(4): for x in range(256): - px[x, 0] = x // 67 * 67 - self._roundtrip(im) + px[x, y] = x % 128 + _test_buffer_overflow(tmp_path, im) - def _test_buffer_overflow(self, im, size=1024): - _last = ImageFile.MAXBLOCK - ImageFile.MAXBLOCK = size - try: - self._roundtrip(im) - finally: - ImageFile.MAXBLOCK = _last - def test_break_in_count_overflow(self): - im = Image.new("L", (256, 5)) - px = im.load() - for y in range(4): - for x in range(256): - px[x, y] = x % 128 - self._test_buffer_overflow(im) +def test_break_one_in_loop(tmp_path): + im = Image.new("L", (256, 5)) + px = im.load() + for y in range(5): + for x in range(256): + px[x, y] = x % 128 + _test_buffer_overflow(tmp_path, im) - def test_break_one_in_loop(self): - im = Image.new("L", (256, 5)) - px = im.load() - for y in range(5): - for x in range(256): - px[x, y] = x % 128 - self._test_buffer_overflow(im) - def test_break_many_in_loop(self): - im = Image.new("L", (256, 5)) - px = im.load() - for y in range(4): - for x in range(256): - px[x, y] = x % 128 - for x in range(8): - px[x, 4] = 16 - self._test_buffer_overflow(im) +def test_break_many_in_loop(tmp_path): + im = Image.new("L", (256, 5)) + px = im.load() + for y in range(4): + for x in range(256): + px[x, y] = x % 128 + for x in range(8): + px[x, 4] = 16 + _test_buffer_overflow(tmp_path, im) - def test_break_one_at_end(self): - im = Image.new("L", (256, 5)) - px = im.load() - for y in range(5): - for x in range(256): - px[x, y] = x % 128 - px[0, 3] = 128 + 64 - self._test_buffer_overflow(im) - def test_break_many_at_end(self): - im = Image.new("L", (256, 5)) - px = im.load() - for y in range(5): - for x in range(256): - px[x, y] = x % 128 - for x in range(4): - px[x * 2, 3] = 128 + 64 - px[x + 256 - 4, 3] = 0 - self._test_buffer_overflow(im) +def test_break_one_at_end(tmp_path): + im = Image.new("L", (256, 5)) + px = im.load() + for y in range(5): + for x in range(256): + px[x, y] = x % 128 + px[0, 3] = 128 + 64 + _test_buffer_overflow(tmp_path, im) - def test_break_padding(self): - im = Image.new("L", (257, 5)) - px = im.load() - for y in range(5): - for x in range(257): - px[x, y] = x % 128 - for x in range(5): - px[x, 3] = 0 - self._test_buffer_overflow(im) + +def test_break_many_at_end(tmp_path): + im = Image.new("L", (256, 5)) + px = im.load() + for y in range(5): + for x in range(256): + px[x, y] = x % 128 + for x in range(4): + px[x * 2, 3] = 128 + 64 + px[x + 256 - 4, 3] = 0 + _test_buffer_overflow(tmp_path, im) + + +def test_break_padding(tmp_path): + im = Image.new("L", (257, 5)) + px = im.load() + for y in range(5): + for x in range(257): + px[x, y] = x % 128 + for x in range(5): + px[x, 3] = 0 + _test_buffer_overflow(tmp_path, im) diff --git a/Tests/test_file_pdf.py b/Tests/test_file_pdf.py index 9b8b789b9..3e23beae7 100644 --- a/Tests/test_file_pdf.py +++ b/Tests/test_file_pdf.py @@ -4,272 +4,284 @@ import os.path import tempfile import time +import pytest + from PIL import Image, PdfParser -from .helper import PillowTestCase, hopper +from .helper import hopper -class TestFilePdf(PillowTestCase): - def helper_save_as_pdf(self, mode, **kwargs): - # Arrange - im = hopper(mode) - outfile = self.tempfile("temp_" + mode + ".pdf") +def helper_save_as_pdf(tmp_path, mode, **kwargs): + # Arrange + im = hopper(mode) + outfile = str(tmp_path / ("temp_" + mode + ".pdf")) - # Act - im.save(outfile, **kwargs) + # Act + im.save(outfile, **kwargs) - # Assert - self.assertTrue(os.path.isfile(outfile)) - self.assertGreater(os.path.getsize(outfile), 0) - with PdfParser.PdfParser(outfile) as pdf: - if kwargs.get("append_images", False) or kwargs.get("append", False): - self.assertGreater(len(pdf.pages), 1) - else: - self.assertGreater(len(pdf.pages), 0) - with open(outfile, "rb") as fp: - contents = fp.read() - size = tuple( - int(d) - for d in contents.split(b"/MediaBox [ 0 0 ")[1].split(b"]")[0].split() - ) - self.assertEqual(im.size, size) + # Assert + assert os.path.isfile(outfile) + assert os.path.getsize(outfile) > 0 + with PdfParser.PdfParser(outfile) as pdf: + if kwargs.get("append_images", False) or kwargs.get("append", False): + assert len(pdf.pages) > 1 + else: + assert len(pdf.pages) > 0 + with open(outfile, "rb") as fp: + contents = fp.read() + size = tuple( + int(d) for d in contents.split(b"/MediaBox [ 0 0 ")[1].split(b"]")[0].split() + ) + assert im.size == size - return outfile + return outfile - def test_monochrome(self): - # Arrange - mode = "1" - # Act / Assert - self.helper_save_as_pdf(mode) +def test_monochrome(tmp_path): + # Arrange + mode = "1" - def test_greyscale(self): - # Arrange - mode = "L" + # Act / Assert + helper_save_as_pdf(tmp_path, mode) - # Act / Assert - self.helper_save_as_pdf(mode) - def test_rgb(self): - # Arrange - mode = "RGB" +def test_greyscale(tmp_path): + # Arrange + mode = "L" - # Act / Assert - self.helper_save_as_pdf(mode) + # Act / Assert + helper_save_as_pdf(tmp_path, mode) - def test_p_mode(self): - # Arrange - mode = "P" - # Act / Assert - self.helper_save_as_pdf(mode) +def test_rgb(tmp_path): + # Arrange + mode = "RGB" - def test_cmyk_mode(self): - # Arrange - mode = "CMYK" + # Act / Assert + helper_save_as_pdf(tmp_path, mode) - # Act / Assert - self.helper_save_as_pdf(mode) - def test_unsupported_mode(self): - im = hopper("LA") - outfile = self.tempfile("temp_LA.pdf") +def test_p_mode(tmp_path): + # Arrange + mode = "P" - self.assertRaises(ValueError, im.save, outfile) + # Act / Assert + helper_save_as_pdf(tmp_path, mode) - def test_save_all(self): - # Single frame image - self.helper_save_as_pdf("RGB", save_all=True) - # Multiframe image - with Image.open("Tests/images/dispose_bgnd.gif") as im: +def test_cmyk_mode(tmp_path): + # Arrange + mode = "CMYK" - outfile = self.tempfile("temp.pdf") - im.save(outfile, save_all=True) + # Act / Assert + helper_save_as_pdf(tmp_path, mode) - self.assertTrue(os.path.isfile(outfile)) - self.assertGreater(os.path.getsize(outfile), 0) - # Append images - ims = [hopper()] - im.copy().save(outfile, save_all=True, append_images=ims) +def test_unsupported_mode(tmp_path): + im = hopper("LA") + outfile = str(tmp_path / "temp_LA.pdf") - self.assertTrue(os.path.isfile(outfile)) - self.assertGreater(os.path.getsize(outfile), 0) + with pytest.raises(ValueError): + im.save(outfile) - # Test appending using a generator - def imGenerator(ims): - yield from ims - im.save(outfile, save_all=True, append_images=imGenerator(ims)) +def test_save_all(tmp_path): + # Single frame image + helper_save_as_pdf(tmp_path, "RGB", save_all=True) - self.assertTrue(os.path.isfile(outfile)) - self.assertGreater(os.path.getsize(outfile), 0) + # Multiframe image + with Image.open("Tests/images/dispose_bgnd.gif") as im: - # Append JPEG images - with Image.open("Tests/images/flower.jpg") as jpeg: - jpeg.save(outfile, save_all=True, append_images=[jpeg.copy()]) + outfile = str(tmp_path / "temp.pdf") + im.save(outfile, save_all=True) - self.assertTrue(os.path.isfile(outfile)) - self.assertGreater(os.path.getsize(outfile), 0) + assert os.path.isfile(outfile) + assert os.path.getsize(outfile) > 0 - def test_multiframe_normal_save(self): - # Test saving a multiframe image without save_all - with Image.open("Tests/images/dispose_bgnd.gif") as im: + # Append images + ims = [hopper()] + im.copy().save(outfile, save_all=True, append_images=ims) - outfile = self.tempfile("temp.pdf") - im.save(outfile) + assert os.path.isfile(outfile) + assert os.path.getsize(outfile) > 0 - self.assertTrue(os.path.isfile(outfile)) - self.assertGreater(os.path.getsize(outfile), 0) + # Test appending using a generator + def imGenerator(ims): + yield from ims - def test_pdf_open(self): - # fail on a buffer full of null bytes - self.assertRaises( - PdfParser.PdfFormatError, PdfParser.PdfParser, buf=bytearray(65536) - ) + im.save(outfile, save_all=True, append_images=imGenerator(ims)) - # make an empty PDF object - with PdfParser.PdfParser() as empty_pdf: - self.assertEqual(len(empty_pdf.pages), 0) - self.assertEqual(len(empty_pdf.info), 0) - self.assertFalse(empty_pdf.should_close_buf) - self.assertFalse(empty_pdf.should_close_file) + assert os.path.isfile(outfile) + assert os.path.getsize(outfile) > 0 - # make a PDF file - pdf_filename = self.helper_save_as_pdf("RGB") + # Append JPEG images + with Image.open("Tests/images/flower.jpg") as jpeg: + jpeg.save(outfile, save_all=True, append_images=[jpeg.copy()]) - # open the PDF file - with PdfParser.PdfParser(filename=pdf_filename) as hopper_pdf: - self.assertEqual(len(hopper_pdf.pages), 1) - self.assertTrue(hopper_pdf.should_close_buf) - self.assertTrue(hopper_pdf.should_close_file) + assert os.path.isfile(outfile) + assert os.path.getsize(outfile) > 0 - # read a PDF file from a buffer with a non-zero offset - with open(pdf_filename, "rb") as f: - content = b"xyzzy" + f.read() - with PdfParser.PdfParser(buf=content, start_offset=5) as hopper_pdf: - self.assertEqual(len(hopper_pdf.pages), 1) - self.assertFalse(hopper_pdf.should_close_buf) - self.assertFalse(hopper_pdf.should_close_file) - # read a PDF file from an already open file - with open(pdf_filename, "rb") as f: - with PdfParser.PdfParser(f=f) as hopper_pdf: - self.assertEqual(len(hopper_pdf.pages), 1) - self.assertTrue(hopper_pdf.should_close_buf) - self.assertFalse(hopper_pdf.should_close_file) +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: - def test_pdf_append_fails_on_nonexistent_file(self): - im = hopper("RGB") - with tempfile.TemporaryDirectory() as temp_dir: - self.assertRaises( - IOError, im.save, os.path.join(temp_dir, "nonexistent.pdf"), append=True - ) + outfile = str(tmp_path / "temp.pdf") + im.save(outfile) - def check_pdf_pages_consistency(self, pdf): - pages_info = pdf.read_indirect(pdf.pages_ref) - self.assertNotIn(b"Parent", pages_info) - self.assertIn(b"Kids", pages_info) - kids_not_used = pages_info[b"Kids"] - for page_ref in pdf.pages: - while True: - if page_ref in kids_not_used: - kids_not_used.remove(page_ref) - page_info = pdf.read_indirect(page_ref) - self.assertIn(b"Parent", page_info) - page_ref = page_info[b"Parent"] - if page_ref == pdf.pages_ref: - break - self.assertEqual(pdf.pages_ref, page_info[b"Parent"]) - self.assertEqual(kids_not_used, []) + assert os.path.isfile(outfile) + assert os.path.getsize(outfile) > 0 - def test_pdf_append(self): - # make a PDF file - pdf_filename = self.helper_save_as_pdf("RGB", producer="PdfParser") - # open it, check pages and info - with PdfParser.PdfParser(pdf_filename, mode="r+b") as pdf: - self.assertEqual(len(pdf.pages), 1) - self.assertEqual(len(pdf.info), 4) - self.assertEqual( - pdf.info.Title, os.path.splitext(os.path.basename(pdf_filename))[0] - ) - self.assertEqual(pdf.info.Producer, "PdfParser") - self.assertIn(b"CreationDate", pdf.info) - self.assertIn(b"ModDate", pdf.info) - self.check_pdf_pages_consistency(pdf) +def test_pdf_open(tmp_path): + # fail on a buffer full of null bytes + with pytest.raises(PdfParser.PdfFormatError): + PdfParser.PdfParser(buf=bytearray(65536)) - # append some info - pdf.info.Title = "abc" - pdf.info.Author = "def" - pdf.info.Subject = "ghi\uABCD" - pdf.info.Keywords = "qw)e\\r(ty" - pdf.info.Creator = "hopper()" - pdf.start_writing() - pdf.write_xref_and_trailer() + # make an empty PDF object + with PdfParser.PdfParser() as empty_pdf: + assert len(empty_pdf.pages) == 0 + assert len(empty_pdf.info) == 0 + assert not empty_pdf.should_close_buf + assert not empty_pdf.should_close_file - # open it again, check pages and info again - with PdfParser.PdfParser(pdf_filename) as pdf: - self.assertEqual(len(pdf.pages), 1) - self.assertEqual(len(pdf.info), 8) - self.assertEqual(pdf.info.Title, "abc") - self.assertIn(b"CreationDate", pdf.info) - self.assertIn(b"ModDate", pdf.info) - self.check_pdf_pages_consistency(pdf) + # make a PDF file + pdf_filename = helper_save_as_pdf(tmp_path, "RGB") - # append two images - mode_CMYK = hopper("CMYK") - mode_P = hopper("P") - mode_CMYK.save(pdf_filename, append=True, save_all=True, append_images=[mode_P]) + # open the PDF file + with PdfParser.PdfParser(filename=pdf_filename) as hopper_pdf: + assert len(hopper_pdf.pages) == 1 + assert hopper_pdf.should_close_buf + assert hopper_pdf.should_close_file - # open the PDF again, check pages and info again - with PdfParser.PdfParser(pdf_filename) as pdf: - self.assertEqual(len(pdf.pages), 3) - self.assertEqual(len(pdf.info), 8) - self.assertEqual(PdfParser.decode_text(pdf.info[b"Title"]), "abc") - self.assertEqual(pdf.info.Title, "abc") - self.assertEqual(pdf.info.Producer, "PdfParser") - self.assertEqual(pdf.info.Keywords, "qw)e\\r(ty") - self.assertEqual(pdf.info.Subject, "ghi\uABCD") - self.assertIn(b"CreationDate", pdf.info) - self.assertIn(b"ModDate", pdf.info) - self.check_pdf_pages_consistency(pdf) + # read a PDF file from a buffer with a non-zero offset + with open(pdf_filename, "rb") as f: + content = b"xyzzy" + f.read() + with PdfParser.PdfParser(buf=content, start_offset=5) as hopper_pdf: + assert len(hopper_pdf.pages) == 1 + assert not hopper_pdf.should_close_buf + assert not hopper_pdf.should_close_file - def test_pdf_info(self): - # make a PDF file - pdf_filename = self.helper_save_as_pdf( - "RGB", - title="title", - author="author", - subject="subject", - keywords="keywords", - creator="creator", - producer="producer", - creationDate=time.strptime("2000", "%Y"), - modDate=time.strptime("2001", "%Y"), - ) + # read a PDF file from an already open file + with open(pdf_filename, "rb") as f: + with PdfParser.PdfParser(f=f) as hopper_pdf: + assert len(hopper_pdf.pages) == 1 + assert hopper_pdf.should_close_buf + assert not hopper_pdf.should_close_file - # open it, check pages and info - with PdfParser.PdfParser(pdf_filename) as pdf: - self.assertEqual(len(pdf.info), 8) - self.assertEqual(pdf.info.Title, "title") - self.assertEqual(pdf.info.Author, "author") - self.assertEqual(pdf.info.Subject, "subject") - self.assertEqual(pdf.info.Keywords, "keywords") - self.assertEqual(pdf.info.Creator, "creator") - self.assertEqual(pdf.info.Producer, "producer") - self.assertEqual(pdf.info.CreationDate, time.strptime("2000", "%Y")) - self.assertEqual(pdf.info.ModDate, time.strptime("2001", "%Y")) - self.check_pdf_pages_consistency(pdf) - def test_pdf_append_to_bytesio(self): - im = hopper("RGB") - f = io.BytesIO() - im.save(f, format="PDF") - initial_size = len(f.getvalue()) - self.assertGreater(initial_size, 0) - im = hopper("P") - f = io.BytesIO(f.getvalue()) - im.save(f, format="PDF", append=True) - self.assertGreater(len(f.getvalue()), initial_size) +def test_pdf_append_fails_on_nonexistent_file(): + im = hopper("RGB") + with tempfile.TemporaryDirectory() as temp_dir: + with pytest.raises(OSError): + im.save(os.path.join(temp_dir, "nonexistent.pdf"), append=True) + + +def check_pdf_pages_consistency(pdf): + pages_info = pdf.read_indirect(pdf.pages_ref) + assert b"Parent" not in pages_info + assert b"Kids" in pages_info + kids_not_used = pages_info[b"Kids"] + for page_ref in pdf.pages: + while True: + if page_ref in kids_not_used: + kids_not_used.remove(page_ref) + page_info = pdf.read_indirect(page_ref) + assert b"Parent" in page_info + page_ref = page_info[b"Parent"] + if page_ref == pdf.pages_ref: + break + assert pdf.pages_ref == page_info[b"Parent"] + assert kids_not_used == [] + + +def test_pdf_append(tmp_path): + # make a PDF file + pdf_filename = helper_save_as_pdf(tmp_path, "RGB", producer="PdfParser") + + # open it, check pages and info + with PdfParser.PdfParser(pdf_filename, mode="r+b") as pdf: + assert len(pdf.pages) == 1 + assert len(pdf.info) == 4 + assert pdf.info.Title == os.path.splitext(os.path.basename(pdf_filename))[0] + assert pdf.info.Producer == "PdfParser" + assert b"CreationDate" in pdf.info + assert b"ModDate" in pdf.info + check_pdf_pages_consistency(pdf) + + # append some info + pdf.info.Title = "abc" + pdf.info.Author = "def" + pdf.info.Subject = "ghi\uABCD" + pdf.info.Keywords = "qw)e\\r(ty" + pdf.info.Creator = "hopper()" + pdf.start_writing() + pdf.write_xref_and_trailer() + + # open it again, check pages and info again + with PdfParser.PdfParser(pdf_filename) as pdf: + assert len(pdf.pages) == 1 + assert len(pdf.info) == 8 + assert pdf.info.Title == "abc" + assert b"CreationDate" in pdf.info + assert b"ModDate" in pdf.info + check_pdf_pages_consistency(pdf) + + # append two images + mode_CMYK = hopper("CMYK") + mode_P = hopper("P") + mode_CMYK.save(pdf_filename, append=True, save_all=True, append_images=[mode_P]) + + # open the PDF again, check pages and info again + with PdfParser.PdfParser(pdf_filename) as pdf: + assert len(pdf.pages) == 3 + assert len(pdf.info) == 8 + assert PdfParser.decode_text(pdf.info[b"Title"]) == "abc" + assert pdf.info.Title == "abc" + assert pdf.info.Producer == "PdfParser" + assert pdf.info.Keywords == "qw)e\\r(ty" + assert pdf.info.Subject == "ghi\uABCD" + assert b"CreationDate" in pdf.info + assert b"ModDate" in pdf.info + check_pdf_pages_consistency(pdf) + + +def test_pdf_info(tmp_path): + # make a PDF file + pdf_filename = helper_save_as_pdf( + tmp_path, + "RGB", + title="title", + author="author", + subject="subject", + keywords="keywords", + creator="creator", + producer="producer", + creationDate=time.strptime("2000", "%Y"), + modDate=time.strptime("2001", "%Y"), + ) + + # open it, check pages and info + with PdfParser.PdfParser(pdf_filename) as pdf: + assert len(pdf.info) == 8 + assert pdf.info.Title == "title" + assert pdf.info.Author == "author" + assert pdf.info.Subject == "subject" + assert pdf.info.Keywords == "keywords" + assert pdf.info.Creator == "creator" + assert pdf.info.Producer == "producer" + assert pdf.info.CreationDate == time.strptime("2000", "%Y") + assert pdf.info.ModDate == time.strptime("2001", "%Y") + check_pdf_pages_consistency(pdf) + + +def test_pdf_append_to_bytesio(): + im = hopper("RGB") + f = io.BytesIO() + im.save(f, format="PDF") + initial_size = len(f.getvalue()) + assert initial_size > 0 + im = hopper("P") + f = io.BytesIO(f.getvalue()) + im.save(f, format="PDF", append=True) + assert len(f.getvalue()) > initial_size diff --git a/Tests/test_file_pixar.py b/Tests/test_file_pixar.py index 201f38416..315ea4676 100644 --- a/Tests/test_file_pixar.py +++ b/Tests/test_file_pixar.py @@ -1,23 +1,26 @@ +import pytest + from PIL import Image, PixarImagePlugin -from .helper import PillowTestCase, assert_image_similar, hopper +from .helper import assert_image_similar, hopper TEST_FILE = "Tests/images/hopper.pxr" -class TestFilePixar(PillowTestCase): - def test_sanity(self): - with Image.open(TEST_FILE) as im: - im.load() - self.assertEqual(im.mode, "RGB") - self.assertEqual(im.size, (128, 128)) - self.assertEqual(im.format, "PIXAR") - self.assertIsNone(im.get_format_mimetype()) +def test_sanity(): + with Image.open(TEST_FILE) as im: + im.load() + assert im.mode == "RGB" + assert im.size == (128, 128) + assert im.format == "PIXAR" + assert im.get_format_mimetype() is None - im2 = hopper() - assert_image_similar(im, im2, 4.8) + im2 = hopper() + assert_image_similar(im, im2, 4.8) - def test_invalid_file(self): - invalid_file = "Tests/images/flower.jpg" - self.assertRaises(SyntaxError, PixarImagePlugin.PixarImageFile, invalid_file) +def test_invalid_file(): + invalid_file = "Tests/images/flower.jpg" + + with pytest.raises(SyntaxError): + PixarImagePlugin.PixarImageFile(invalid_file) diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index a3d3c0897..9028aaf23 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -1,32 +1,21 @@ -import unittest +import re import zlib from io import BytesIO import pytest -from PIL import Image, ImageFile, PngImagePlugin + +from PIL import Image, ImageFile, PngImagePlugin, features from .helper import ( PillowLeakTestCase, - PillowTestCase, assert_image, assert_image_equal, - assert_image_similar, hopper, is_big_endian, is_win32, - on_ci, + skip_unless_feature, ) -try: - from PIL import _webp - - HAVE_WEBP = True -except ImportError: - HAVE_WEBP = False - -codecs = dir(Image.core) - - # sample png stream TEST_PNG_FILE = "Tests/images/hopper.png" @@ -63,11 +52,8 @@ def roundtrip(im, **options): return Image.open(out) -class TestFilePng(PillowTestCase): - def setUp(self): - if "zip_encoder" not in codecs or "zip_decoder" not in codecs: - self.skipTest("zip/deflate support not available") - +@skip_unless_feature("zlib") +class TestFilePng: def get_chunks(self, filename): chunks = [] with open(filename, "rb") as fp: @@ -83,22 +69,22 @@ class TestFilePng(PillowTestCase): png.crc(cid, s) return chunks - @pytest.mark.xfail(is_big_endian() and on_ci(), reason="Fails on big-endian") - def test_sanity(self): + @pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian") + def test_sanity(self, tmp_path): # internal version number - self.assertRegex(Image.core.zlib_version, r"\d+\.\d+\.\d+(\.\d+)?$") + assert re.search(r"\d+\.\d+\.\d+(\.\d+)?$", features.version_codec("zlib")) - test_file = self.tempfile("temp.png") + test_file = str(tmp_path / "temp.png") hopper("RGB").save(test_file) with Image.open(test_file) as im: im.load() - self.assertEqual(im.mode, "RGB") - self.assertEqual(im.size, (128, 128)) - self.assertEqual(im.format, "PNG") - self.assertEqual(im.get_format_mimetype(), "image/png") + assert im.mode == "RGB" + assert im.size == (128, 128) + assert im.format == "PNG" + assert im.get_format_mimetype() == "image/png" for mode in ["1", "L", "P", "RGB", "I", "I;16"]: im = hopper(mode) @@ -111,112 +97,114 @@ class TestFilePng(PillowTestCase): def test_invalid_file(self): invalid_file = "Tests/images/flower.jpg" - self.assertRaises(SyntaxError, PngImagePlugin.PngImageFile, invalid_file) + with pytest.raises(SyntaxError): + PngImagePlugin.PngImageFile(invalid_file) def test_broken(self): # Check reading of totally broken files. In this case, the test # file was checked into Subversion as a text file. test_file = "Tests/images/broken.png" - self.assertRaises(IOError, Image.open, test_file) + with pytest.raises(OSError): + Image.open(test_file) def test_bad_text(self): # Make sure PIL can read malformed tEXt chunks (@PIL152) im = load(HEAD + chunk(b"tEXt") + TAIL) - self.assertEqual(im.info, {}) + assert im.info == {} im = load(HEAD + chunk(b"tEXt", b"spam") + TAIL) - self.assertEqual(im.info, {"spam": ""}) + assert im.info == {"spam": ""} im = load(HEAD + chunk(b"tEXt", b"spam\0") + TAIL) - self.assertEqual(im.info, {"spam": ""}) + assert im.info == {"spam": ""} im = load(HEAD + chunk(b"tEXt", b"spam\0egg") + TAIL) - self.assertEqual(im.info, {"spam": "egg"}) + assert im.info == {"spam": "egg"} im = load(HEAD + chunk(b"tEXt", b"spam\0egg\0") + TAIL) - self.assertEqual(im.info, {"spam": "egg\x00"}) + assert im.info == {"spam": "egg\x00"} def test_bad_ztxt(self): # Test reading malformed zTXt chunks (python-pillow/Pillow#318) im = load(HEAD + chunk(b"zTXt") + TAIL) - self.assertEqual(im.info, {}) + assert im.info == {} im = load(HEAD + chunk(b"zTXt", b"spam") + TAIL) - self.assertEqual(im.info, {"spam": ""}) + assert im.info == {"spam": ""} im = load(HEAD + chunk(b"zTXt", b"spam\0") + TAIL) - self.assertEqual(im.info, {"spam": ""}) + assert im.info == {"spam": ""} im = load(HEAD + chunk(b"zTXt", b"spam\0\0") + TAIL) - self.assertEqual(im.info, {"spam": ""}) + assert im.info == {"spam": ""} im = load(HEAD + chunk(b"zTXt", b"spam\0\0" + zlib.compress(b"egg")[:1]) + TAIL) - self.assertEqual(im.info, {"spam": ""}) + assert im.info == {"spam": ""} im = load(HEAD + chunk(b"zTXt", b"spam\0\0" + zlib.compress(b"egg")) + TAIL) - self.assertEqual(im.info, {"spam": "egg"}) + assert im.info == {"spam": "egg"} def test_bad_itxt(self): im = load(HEAD + chunk(b"iTXt") + TAIL) - self.assertEqual(im.info, {}) + assert im.info == {} im = load(HEAD + chunk(b"iTXt", b"spam") + TAIL) - self.assertEqual(im.info, {}) + assert im.info == {} im = load(HEAD + chunk(b"iTXt", b"spam\0") + TAIL) - self.assertEqual(im.info, {}) + assert im.info == {} im = load(HEAD + chunk(b"iTXt", b"spam\0\x02") + TAIL) - self.assertEqual(im.info, {}) + assert im.info == {} im = load(HEAD + chunk(b"iTXt", b"spam\0\0\0foo\0") + TAIL) - self.assertEqual(im.info, {}) + assert im.info == {} im = load(HEAD + chunk(b"iTXt", b"spam\0\0\0en\0Spam\0egg") + TAIL) - self.assertEqual(im.info, {"spam": "egg"}) - self.assertEqual(im.info["spam"].lang, "en") - self.assertEqual(im.info["spam"].tkey, "Spam") + assert im.info == {"spam": "egg"} + assert im.info["spam"].lang == "en" + assert im.info["spam"].tkey == "Spam" im = load( HEAD + chunk(b"iTXt", b"spam\0\1\0en\0Spam\0" + zlib.compress(b"egg")[:1]) + TAIL ) - self.assertEqual(im.info, {"spam": ""}) + assert im.info == {"spam": ""} im = load( HEAD + chunk(b"iTXt", b"spam\0\1\1en\0Spam\0" + zlib.compress(b"egg")) + TAIL ) - self.assertEqual(im.info, {}) + assert im.info == {} im = load( HEAD + chunk(b"iTXt", b"spam\0\1\0en\0Spam\0" + zlib.compress(b"egg")) + TAIL ) - self.assertEqual(im.info, {"spam": "egg"}) - self.assertEqual(im.info["spam"].lang, "en") - self.assertEqual(im.info["spam"].tkey, "Spam") + assert im.info == {"spam": "egg"} + assert im.info["spam"].lang == "en" + 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)) - self.assertTrue(im.info.get("interlace")) + assert im.info.get("interlace") im.load() test_file = "Tests/images/pil123rgba.png" with Image.open(test_file) as im: assert_image(im, "RGBA", (162, 150)) - self.assertTrue(im.info.get("interlace")) + assert im.info.get("interlace") im.load() @@ -228,109 +216,107 @@ class TestFilePng(PillowTestCase): assert_image(im, "RGBA", (162, 150)) # image has 124 unique alpha values - self.assertEqual(len(im.getchannel("A").getcolors()), 124) + assert len(im.getchannel("A").getcolors()) == 124 def test_load_transparent_rgb(self): test_file = "Tests/images/rgb_trns.png" with Image.open(test_file) as im: - self.assertEqual(im.info["transparency"], (0, 255, 52)) + assert im.info["transparency"] == (0, 255, 52) assert_image(im, "RGB", (64, 64)) im = im.convert("RGBA") assert_image(im, "RGBA", (64, 64)) # image has 876 transparent pixels - self.assertEqual(im.getchannel("A").getcolors()[0][0], 876) + assert im.getchannel("A").getcolors()[0][0] == 876 - def test_save_p_transparent_palette(self): + def test_save_p_transparent_palette(self, tmp_path): in_file = "Tests/images/pil123p.png" with Image.open(in_file) as im: # 'transparency' contains a byte string with the opacity for # each palette entry - self.assertEqual(len(im.info["transparency"]), 256) + assert len(im.info["transparency"]) == 256 - test_file = self.tempfile("temp.png") + test_file = str(tmp_path / "temp.png") im.save(test_file) # check if saved image contains same transparency with Image.open(test_file) as im: - self.assertEqual(len(im.info["transparency"]), 256) + assert len(im.info["transparency"]) == 256 assert_image(im, "P", (162, 150)) im = im.convert("RGBA") assert_image(im, "RGBA", (162, 150)) # image has 124 unique alpha values - self.assertEqual(len(im.getchannel("A").getcolors()), 124) + assert len(im.getchannel("A").getcolors()) == 124 - def test_save_p_single_transparency(self): + def test_save_p_single_transparency(self, tmp_path): in_file = "Tests/images/p_trns_single.png" with Image.open(in_file) as im: # pixel value 164 is full transparent - self.assertEqual(im.info["transparency"], 164) - self.assertEqual(im.getpixel((31, 31)), 164) + assert im.info["transparency"] == 164 + assert im.getpixel((31, 31)) == 164 - test_file = self.tempfile("temp.png") + test_file = str(tmp_path / "temp.png") im.save(test_file) # check if saved image contains same transparency with Image.open(test_file) as im: - self.assertEqual(im.info["transparency"], 164) - self.assertEqual(im.getpixel((31, 31)), 164) + assert im.info["transparency"] == 164 + assert im.getpixel((31, 31)) == 164 assert_image(im, "P", (64, 64)) im = im.convert("RGBA") assert_image(im, "RGBA", (64, 64)) - self.assertEqual(im.getpixel((31, 31)), (0, 255, 52, 0)) + assert im.getpixel((31, 31)) == (0, 255, 52, 0) # image has 876 transparent pixels - self.assertEqual(im.getchannel("A").getcolors()[0][0], 876) + assert im.getchannel("A").getcolors()[0][0] == 876 - def test_save_p_transparent_black(self): + def test_save_p_transparent_black(self, tmp_path): # check if solid black image with full transparency # is supported (check for #1838) im = Image.new("RGBA", (10, 10), (0, 0, 0, 0)) - self.assertEqual(im.getcolors(), [(100, (0, 0, 0, 0))]) + assert im.getcolors() == [(100, (0, 0, 0, 0))] im = im.convert("P") - test_file = self.tempfile("temp.png") + test_file = str(tmp_path / "temp.png") im.save(test_file) # check if saved image contains same transparency with Image.open(test_file) as im: - self.assertEqual(len(im.info["transparency"]), 256) + assert len(im.info["transparency"]) == 256 assert_image(im, "P", (10, 10)) im = im.convert("RGBA") assert_image(im, "RGBA", (10, 10)) - self.assertEqual(im.getcolors(), [(100, (0, 0, 0, 0))]) + assert im.getcolors() == [(100, (0, 0, 0, 0))] - def test_save_greyscale_transparency(self): + def test_save_greyscale_transparency(self, tmp_path): for mode, num_transparent in {"1": 1994, "L": 559, "I": 559}.items(): in_file = "Tests/images/" + mode.lower() + "_trns.png" with Image.open(in_file) as im: - self.assertEqual(im.mode, mode) - self.assertEqual(im.info["transparency"], 255) + assert im.mode == mode + assert im.info["transparency"] == 255 im_rgba = im.convert("RGBA") - self.assertEqual(im_rgba.getchannel("A").getcolors()[0][0], num_transparent) + assert im_rgba.getchannel("A").getcolors()[0][0] == num_transparent - test_file = self.tempfile("temp.png") + test_file = str(tmp_path / "temp.png") im.save(test_file) with Image.open(test_file) as test_im: - self.assertEqual(test_im.mode, mode) - self.assertEqual(test_im.info["transparency"], 255) + assert test_im.mode == mode + assert test_im.info["transparency"] == 255 assert_image_equal(im, test_im) test_im_rgba = test_im.convert("RGBA") - self.assertEqual( - test_im_rgba.getchannel("A").getcolors()[0][0], num_transparent - ) + assert test_im_rgba.getchannel("A").getcolors()[0][0] == num_transparent - def test_save_rgb_single_transparency(self): + def test_save_rgb_single_transparency(self, tmp_path): in_file = "Tests/images/caption_6_33_22.png" with Image.open(in_file) as im: - test_file = self.tempfile("temp.png") + test_file = str(tmp_path / "temp.png") im.save(test_file) def test_load_verify(self): @@ -342,12 +328,13 @@ class TestFilePng(PillowTestCase): with Image.open(TEST_PNG_FILE) as im: im.load() - self.assertRaises(RuntimeError, im.verify) + with pytest.raises(RuntimeError): + im.verify() def test_verify_struct_error(self): # Check open/load/verify exception (#1755) - # offsets to test, -10: breaks in i32() in read. (IOError) + # offsets to test, -10: breaks in i32() in read. (OSError) # -13: breaks in crc, txt chunk. # -14: malformed chunk @@ -356,8 +343,9 @@ class TestFilePng(PillowTestCase): test_file = f.read()[:offset] with Image.open(BytesIO(test_file)) as im: - self.assertIsNotNone(im.fp) - self.assertRaises((IOError, SyntaxError), im.verify) + assert im.fp is not None + with pytest.raises((OSError, SyntaxError)): + im.verify() def test_verify_ignores_crc_error(self): # check ignores crc errors in ancillary chunks @@ -366,12 +354,13 @@ class TestFilePng(PillowTestCase): broken_crc_chunk_data = chunk_data[:-1] + b"q" # break CRC image_data = HEAD + broken_crc_chunk_data + TAIL - self.assertRaises(SyntaxError, PngImagePlugin.PngImageFile, BytesIO(image_data)) + with pytest.raises(SyntaxError): + PngImagePlugin.PngImageFile(BytesIO(image_data)) ImageFile.LOAD_TRUNCATED_IMAGES = True try: im = load(image_data) - self.assertIsNotNone(im) + assert im is not None finally: ImageFile.LOAD_TRUNCATED_IMAGES = False @@ -382,9 +371,8 @@ class TestFilePng(PillowTestCase): ImageFile.LOAD_TRUNCATED_IMAGES = True try: - self.assertRaises( - SyntaxError, PngImagePlugin.PngImageFile, BytesIO(image_data) - ) + with pytest.raises(SyntaxError): + PngImagePlugin.PngImageFile(BytesIO(image_data)) finally: ImageFile.LOAD_TRUNCATED_IMAGES = False @@ -393,24 +381,24 @@ class TestFilePng(PillowTestCase): with Image.open(TEST_PNG_FILE) as im: im = roundtrip(im, dpi=(100, 100)) - self.assertEqual(im.info["dpi"], (100, 100)) + assert im.info["dpi"] == (100, 100) def test_load_dpi_rounding(self): # Round up with Image.open(TEST_PNG_FILE) as im: - self.assertEqual(im.info["dpi"], (96, 96)) + assert im.info["dpi"] == (96, 96) # Round down with Image.open("Tests/images/icc_profile_none.png") as im: - self.assertEqual(im.info["dpi"], (72, 72)) + assert im.info["dpi"] == (72, 72) def test_save_dpi_rounding(self): with Image.open(TEST_PNG_FILE) as im: im = roundtrip(im, dpi=(72.2, 72.2)) - self.assertEqual(im.info["dpi"], (72, 72)) + assert im.info["dpi"] == (72, 72) im = roundtrip(im, dpi=(72.8, 72.8)) - self.assertEqual(im.info["dpi"], (73, 73)) + assert im.info["dpi"] == (73, 73) def test_roundtrip_text(self): # Check text roundtripping @@ -421,8 +409,8 @@ class TestFilePng(PillowTestCase): info.add_text("ZIP", "VALUE", zip=True) im = roundtrip(im, pnginfo=info) - self.assertEqual(im.info, {"TXT": "VALUE", "ZIP": "VALUE"}) - self.assertEqual(im.text, {"TXT": "VALUE", "ZIP": "VALUE"}) + assert im.info == {"TXT": "VALUE", "ZIP": "VALUE"} + assert im.text == {"TXT": "VALUE", "ZIP": "VALUE"} def test_roundtrip_itxt(self): # Check iTXt roundtripping @@ -433,12 +421,12 @@ class TestFilePng(PillowTestCase): info.add_text("eggs", PngImagePlugin.iTXt("Spam", "en", "Eggs"), zip=True) im = roundtrip(im, pnginfo=info) - self.assertEqual(im.info, {"spam": "Eggs", "eggs": "Spam"}) - self.assertEqual(im.text, {"spam": "Eggs", "eggs": "Spam"}) - self.assertEqual(im.text["spam"].lang, "en") - self.assertEqual(im.text["spam"].tkey, "Spam") - self.assertEqual(im.text["eggs"].lang, "en") - self.assertEqual(im.text["eggs"].tkey, "Eggs") + assert im.info == {"spam": "Eggs", "eggs": "Spam"} + assert im.text == {"spam": "Eggs", "eggs": "Spam"} + assert im.text["spam"].lang == "en" + assert im.text["spam"].tkey == "Spam" + assert im.text["eggs"].lang == "en" + assert im.text["eggs"].tkey == "Eggs" def test_nonunicode_text(self): # Check so that non-Unicode text is saved as a tEXt rather than iTXt @@ -447,7 +435,7 @@ class TestFilePng(PillowTestCase): info = PngImagePlugin.PngInfo() info.add_text("Text", "Ascii") im = roundtrip(im, pnginfo=info) - self.assertIsInstance(im.info["Text"], str) + assert isinstance(im.info["Text"], str) def test_unicode_text(self): # Check preservation of non-ASCII characters @@ -457,7 +445,7 @@ class TestFilePng(PillowTestCase): info = PngImagePlugin.PngInfo() info.add_text("Text", value) im = roundtrip(im, pnginfo=info) - self.assertEqual(im.info, {"Text": value}) + assert im.info == {"Text": value} rt_text(" Aa" + chr(0xA0) + chr(0xC4) + chr(0xFF)) # Latin1 rt_text(chr(0x400) + chr(0x472) + chr(0x4FF)) # Cyrillic @@ -475,7 +463,8 @@ class TestFilePng(PillowTestCase): data = b"\x89" + fd.read() pngfile = BytesIO(data) - self.assertRaises(IOError, Image.open, pngfile) + with pytest.raises(OSError): + Image.open(pngfile) def test_trns_rgb(self): # Check writing and reading of tRNS chunks for RGB images. @@ -483,25 +472,25 @@ class TestFilePng(PillowTestCase): test_file = "Tests/images/caption_6_33_22.png" with Image.open(test_file) as im: - self.assertEqual(im.info["transparency"], (248, 248, 248)) + assert im.info["transparency"] == (248, 248, 248) # check saving transparency by default im = roundtrip(im) - self.assertEqual(im.info["transparency"], (248, 248, 248)) + assert im.info["transparency"] == (248, 248, 248) im = roundtrip(im, transparency=(0, 1, 2)) - self.assertEqual(im.info["transparency"], (0, 1, 2)) + assert im.info["transparency"] == (0, 1, 2) - def test_trns_p(self): + def test_trns_p(self, tmp_path): # Check writing a transparency of 0, issue #528 im = hopper("P") im.info["transparency"] = 0 - f = self.tempfile("temp.png") + f = str(tmp_path / "temp.png") im.save(f) with Image.open(f) as im2: - self.assertIn("transparency", im2.info) + assert "transparency" in im2.info assert_image_equal(im2.convert("RGBA"), im.convert("RGBA")) @@ -510,149 +499,196 @@ class TestFilePng(PillowTestCase): test_file = "Tests/images/tRNS_null_1x1.png" with Image.open(test_file) as im: - self.assertEqual(im.info["transparency"], 0) + assert im.info["transparency"] == 0 def test_save_icc_profile(self): with Image.open("Tests/images/icc_profile_none.png") as im: - self.assertIsNone(im.info["icc_profile"]) + assert im.info["icc_profile"] is None with Image.open("Tests/images/icc_profile.png") as with_icc: expected_icc = with_icc.info["icc_profile"] im = roundtrip(im, icc_profile=expected_icc) - self.assertEqual(im.info["icc_profile"], expected_icc) + assert im.info["icc_profile"] == expected_icc def test_discard_icc_profile(self): with Image.open("Tests/images/icc_profile.png") as im: im = roundtrip(im, icc_profile=None) - self.assertNotIn("icc_profile", im.info) + assert "icc_profile" not in im.info def test_roundtrip_icc_profile(self): with Image.open("Tests/images/icc_profile.png") as im: expected_icc = im.info["icc_profile"] im = roundtrip(im) - self.assertEqual(im.info["icc_profile"], expected_icc) + assert im.info["icc_profile"] == expected_icc def test_roundtrip_no_icc_profile(self): with Image.open("Tests/images/icc_profile_none.png") as im: - self.assertIsNone(im.info["icc_profile"]) + assert im.info["icc_profile"] is None im = roundtrip(im) - self.assertNotIn("icc_profile", im.info) + assert "icc_profile" not in im.info def test_repr_png(self): im = hopper() with Image.open(BytesIO(im._repr_png_())) as repr_png: - self.assertEqual(repr_png.format, "PNG") + assert repr_png.format == "PNG" assert_image_equal(im, repr_png) - def test_chunk_order(self): + def test_repr_png_error(self): + im = hopper("F") + + with pytest.raises(ValueError): + im._repr_png_() + + def test_chunk_order(self, tmp_path): with Image.open("Tests/images/icc_profile.png") as im: - test_file = self.tempfile("temp.png") + test_file = str(tmp_path / "temp.png") im.convert("P").save(test_file, dpi=(100, 100)) chunks = self.get_chunks(test_file) # https://www.w3.org/TR/PNG/#5ChunkOrdering # IHDR - shall be first - self.assertEqual(chunks.index(b"IHDR"), 0) + assert chunks.index(b"IHDR") == 0 # PLTE - before first IDAT - self.assertLess(chunks.index(b"PLTE"), chunks.index(b"IDAT")) + assert chunks.index(b"PLTE") < chunks.index(b"IDAT") # iCCP - before PLTE and IDAT - self.assertLess(chunks.index(b"iCCP"), chunks.index(b"PLTE")) - self.assertLess(chunks.index(b"iCCP"), chunks.index(b"IDAT")) + assert chunks.index(b"iCCP") < chunks.index(b"PLTE") + assert chunks.index(b"iCCP") < chunks.index(b"IDAT") # tRNS - after PLTE, before IDAT - self.assertGreater(chunks.index(b"tRNS"), chunks.index(b"PLTE")) - self.assertLess(chunks.index(b"tRNS"), chunks.index(b"IDAT")) + assert chunks.index(b"tRNS") > chunks.index(b"PLTE") + assert chunks.index(b"tRNS") < chunks.index(b"IDAT") # pHYs - before IDAT - self.assertLess(chunks.index(b"pHYs"), chunks.index(b"IDAT")) + assert chunks.index(b"pHYs") < chunks.index(b"IDAT") def test_getchunks(self): im = hopper() chunks = PngImagePlugin.getchunks(im) - self.assertEqual(len(chunks), 3) + assert len(chunks) == 3 + + def test_read_private_chunks(self): + im = Image.open("Tests/images/exif.png") + assert im.private_chunks == [(b"orNT", b"\x01")] + + def test_roundtrip_private_chunk(self): + # Check private chunk roundtripping + + with Image.open(TEST_PNG_FILE) as im: + info = PngImagePlugin.PngInfo() + info.add(b"prIV", b"VALUE") + info.add(b"atEC", b"VALUE2") + info.add(b"prIV", b"VALUE3", True) + + im = roundtrip(im, pnginfo=info) + assert im.private_chunks == [(b"prIV", b"VALUE"), (b"atEC", b"VALUE2")] + im.load() + assert im.private_chunks == [ + (b"prIV", b"VALUE"), + (b"atEC", b"VALUE2"), + (b"prIV", b"VALUE3", True), + ] def test_textual_chunks_after_idat(self): with Image.open("Tests/images/hopper.png") as im: - self.assertIn("comment", im.text.keys()) + assert "comment" in im.text.keys() for k, v in { "date:create": "2014-09-04T09:37:08+03:00", "date:modify": "2014-09-04T09:37:08+03:00", }.items(): - self.assertEqual(im.text[k], v) + assert im.text[k] == v # Raises a SyntaxError in load_end with Image.open("Tests/images/broken_data_stream.png") as im: - with self.assertRaises(IOError): - self.assertIsInstance(im.text, dict) + with pytest.raises(OSError): + assert isinstance(im.text, dict) # Raises a UnicodeDecodeError in load_end with Image.open("Tests/images/truncated_image.png") as im: # The file is truncated - self.assertRaises(IOError, lambda: im.text) + with pytest.raises(OSError): + im.text() ImageFile.LOAD_TRUNCATED_IMAGES = True - self.assertIsInstance(im.text, dict) + assert isinstance(im.text, dict) ImageFile.LOAD_TRUNCATED_IMAGES = False # Raises an EOFError in load_end with Image.open("Tests/images/hopper_idat_after_image_end.png") as im: - self.assertEqual(im.text, {"TXT": "VALUE", "ZIP": "VALUE"}) + assert im.text == {"TXT": "VALUE", "ZIP": "VALUE"} def test_exif(self): + # With an EXIF chunk with Image.open("Tests/images/exif.png") as im: exif = im._getexif() - self.assertEqual(exif[274], 1) + assert exif[274] == 1 - def test_exif_save(self): + # With an ImageMagick zTXt chunk + with Image.open("Tests/images/exif_imagemagick.png") as im: + exif = im._getexif() + assert exif[274] == 1 + + # Assert that info still can be extracted + # when the image is no longer a PngImageFile instance + exif = im.copy().getexif() + assert exif[274] == 1 + + # With a tEXt chunk + with Image.open("Tests/images/exif_text.png") as im: + exif = im._getexif() + assert exif[274] == 1 + + # With XMP tags + with Image.open("Tests/images/xmp_tags_orientation.png") as im: + exif = im.getexif() + assert exif[274] == 3 + + def test_exif_save(self, tmp_path): with Image.open("Tests/images/exif.png") as im: - test_file = self.tempfile("temp.png") + test_file = str(tmp_path / "temp.png") im.save(test_file) with Image.open(test_file) as reloaded: exif = reloaded._getexif() - self.assertEqual(exif[274], 1) + assert exif[274] == 1 - def test_exif_from_jpg(self): + def test_exif_from_jpg(self, tmp_path): with Image.open("Tests/images/pil_sample_rgb.jpg") as im: - test_file = self.tempfile("temp.png") + test_file = str(tmp_path / "temp.png") im.save(test_file) with Image.open(test_file) as reloaded: exif = reloaded._getexif() - self.assertEqual(exif[305], "Adobe Photoshop CS Macintosh") + assert exif[305] == "Adobe Photoshop CS Macintosh" - def test_exif_argument(self): + def test_exif_argument(self, tmp_path): with Image.open(TEST_PNG_FILE) as im: - test_file = self.tempfile("temp.png") + test_file = str(tmp_path / "temp.png") im.save(test_file, exif=b"exifstring") with Image.open(test_file) as reloaded: - self.assertEqual(reloaded.info["exif"], b"Exif\x00\x00exifstring") + assert reloaded.info["exif"] == b"Exif\x00\x00exifstring" - @unittest.skipUnless( - HAVE_WEBP and _webp.HAVE_WEBPANIM, "WebP support not installed with animation" - ) - def test_apng(self): - with Image.open("Tests/images/iss634.apng") as im: - self.assertEqual(im.get_format_mimetype(), "image/apng") + def test_tell(self): + with Image.open(TEST_PNG_FILE) as im: + assert im.tell() == 0 - # This also tests reading unknown PNG chunks (fcTL and fdAT) in load_end - with Image.open("Tests/images/iss634.webp") as expected: - assert_image_similar(im, expected, 0.23) + def test_seek(self): + with Image.open(TEST_PNG_FILE) as im: + im.seek(0) + + with pytest.raises(EOFError): + im.seek(1) -@unittest.skipIf(is_win32(), "requires Unix or macOS") +@pytest.mark.skipif(is_win32(), reason="Requires Unix or macOS") +@skip_unless_feature("zlib") class TestTruncatedPngPLeaks(PillowLeakTestCase): mem_limit = 2 * 1024 # max increase in K iterations = 100 # Leak is 56k/iteration, this will leak 5.6megs - def setUp(self): - if "zip_encoder" not in codecs or "zip_decoder" not in codecs: - self.skipTest("zip/deflate support not available") - def test_leak_load(self): with open("Tests/images/hopper.png", "rb") as f: DATA = BytesIO(f.read(16 * 1024)) diff --git a/Tests/test_file_ppm.py b/Tests/test_file_ppm.py index 8dd99e7bb..e7c3fb06f 100644 --- a/Tests/test_file_ppm.py +++ b/Tests/test_file_ppm.py @@ -1,75 +1,83 @@ +import pytest + from PIL import Image -from .helper import PillowTestCase, assert_image_equal, assert_image_similar, hopper +from .helper import assert_image_equal, assert_image_similar, hopper # sample ppm stream -test_file = "Tests/images/hopper.ppm" +TEST_FILE = "Tests/images/hopper.ppm" -class TestFilePpm(PillowTestCase): - def test_sanity(self): - with Image.open(test_file) as im: - im.load() - self.assertEqual(im.mode, "RGB") - self.assertEqual(im.size, (128, 128)) - self.assertEqual(im.format, "PPM") - self.assertEqual(im.get_format_mimetype(), "image/x-portable-pixmap") +def test_sanity(): + with Image.open(TEST_FILE) as im: + im.load() + assert im.mode == "RGB" + assert im.size == (128, 128) + assert im.format, "PPM" + assert im.get_format_mimetype() == "image/x-portable-pixmap" - def test_16bit_pgm(self): - with Image.open("Tests/images/16_bit_binary.pgm") as im: - im.load() - self.assertEqual(im.mode, "I") - self.assertEqual(im.size, (20, 100)) - self.assertEqual(im.get_format_mimetype(), "image/x-portable-graymap") - with Image.open("Tests/images/16_bit_binary_pgm.png") as tgt: - assert_image_equal(im, tgt) +def test_16bit_pgm(): + with Image.open("Tests/images/16_bit_binary.pgm") as im: + im.load() + assert im.mode == "I" + assert im.size == (20, 100) + assert im.get_format_mimetype() == "image/x-portable-graymap" - def test_16bit_pgm_write(self): - with Image.open("Tests/images/16_bit_binary.pgm") as im: - im.load() + with Image.open("Tests/images/16_bit_binary_pgm.png") as tgt: + assert_image_equal(im, tgt) - f = self.tempfile("temp.pgm") - im.save(f, "PPM") - with Image.open(f) as reloaded: - assert_image_equal(im, reloaded) +def test_16bit_pgm_write(tmp_path): + with Image.open("Tests/images/16_bit_binary.pgm") as im: + im.load() - def test_pnm(self): - with Image.open("Tests/images/hopper.pnm") as im: - assert_image_similar(im, hopper(), 0.0001) + f = str(tmp_path / "temp.pgm") + im.save(f, "PPM") - f = self.tempfile("temp.pnm") - im.save(f) + with Image.open(f) as reloaded: + assert_image_equal(im, reloaded) - with Image.open(f) as reloaded: - assert_image_equal(im, reloaded) - def test_truncated_file(self): - path = self.tempfile("temp.pgm") - with open(path, "w") as f: - f.write("P6") +def test_pnm(tmp_path): + with Image.open("Tests/images/hopper.pnm") as im: + assert_image_similar(im, hopper(), 0.0001) - self.assertRaises(ValueError, Image.open, path) + f = str(tmp_path / "temp.pnm") + im.save(f) - def test_neg_ppm(self): - # Storage.c accepted negative values for xsize, ysize. the - # internal open_ppm function didn't check for sanity but it - # has been removed. The default opener doesn't accept negative - # sizes. + with Image.open(f) as reloaded: + assert_image_equal(im, reloaded) - with self.assertRaises(IOError): - Image.open("Tests/images/negative_size.ppm") - def test_mimetypes(self): - path = self.tempfile("temp.pgm") +def test_truncated_file(tmp_path): + path = str(tmp_path / "temp.pgm") + with open(path, "w") as f: + f.write("P6") - with open(path, "w") as f: - f.write("P4\n128 128\n255") - with Image.open(path) as im: - self.assertEqual(im.get_format_mimetype(), "image/x-portable-bitmap") + with pytest.raises(ValueError): + Image.open(path) - with open(path, "w") as f: - f.write("PyCMYK\n128 128\n255") - with Image.open(path) as im: - self.assertEqual(im.get_format_mimetype(), "image/x-portable-anymap") + +def test_neg_ppm(): + # Storage.c accepted negative values for xsize, ysize. the + # internal open_ppm function didn't check for sanity but it + # has been removed. The default opener doesn't accept negative + # sizes. + + with pytest.raises(OSError): + Image.open("Tests/images/negative_size.ppm") + + +def test_mimetypes(tmp_path): + path = str(tmp_path / "temp.pgm") + + with open(path, "w") as f: + f.write("P4\n128 128\n255") + with Image.open(path) as im: + assert im.get_format_mimetype() == "image/x-portable-bitmap" + + with open(path, "w") as f: + f.write("PyCMYK\n128 128\n255") + with Image.open(path) as im: + assert im.get_format_mimetype() == "image/x-portable-anymap" diff --git a/Tests/test_file_psd.py b/Tests/test_file_psd.py index e77964d32..8bb45630e 100644 --- a/Tests/test_file_psd.py +++ b/Tests/test_file_psd.py @@ -1,116 +1,131 @@ -import unittest - import pytest + from PIL import Image, PsdImagePlugin -from .helper import PillowTestCase, assert_image_similar, hopper, is_pypy +from .helper import assert_image_similar, hopper, is_pypy test_file = "Tests/images/hopper.psd" -class TestImagePsd(PillowTestCase): - def test_sanity(self): +def test_sanity(): + with Image.open(test_file) as im: + im.load() + assert im.mode == "RGB" + assert im.size == (128, 128) + assert im.format == "PSD" + assert im.get_format_mimetype() == "image/vnd.adobe.photoshop" + + im2 = hopper() + assert_image_similar(im, im2, 4.8) + + +@pytest.mark.skipif(is_pypy(), reason="Requires CPython") +def test_unclosed_file(): + def open(): + im = Image.open(test_file) + im.load() + + pytest.warns(ResourceWarning, open) + + +def test_closed_file(): + def open(): + im = Image.open(test_file) + im.load() + im.close() + + pytest.warns(None, open) + + +def test_context_manager(): + def open(): with Image.open(test_file) as im: im.load() - self.assertEqual(im.mode, "RGB") - self.assertEqual(im.size, (128, 128)) - self.assertEqual(im.format, "PSD") - im2 = hopper() - assert_image_similar(im, im2, 4.8) + pytest.warns(None, open) - @unittest.skipIf(is_pypy(), "Requires CPython") - def test_unclosed_file(self): - def open(): - im = Image.open(test_file) - im.load() - pytest.warns(ResourceWarning, open) +def test_invalid_file(): + invalid_file = "Tests/images/flower.jpg" - def test_closed_file(self): - def open(): - im = Image.open(test_file) - im.load() - im.close() + with pytest.raises(SyntaxError): + PsdImagePlugin.PsdImageFile(invalid_file) - pytest.warns(None, open) - def test_context_manager(self): - def open(): - with Image.open(test_file) as im: - im.load() +def test_n_frames(): + with Image.open("Tests/images/hopper_merged.psd") as im: + assert im.n_frames == 1 + assert not im.is_animated - pytest.warns(None, open) + with Image.open(test_file) as im: + assert im.n_frames == 2 + assert im.is_animated - def test_invalid_file(self): - invalid_file = "Tests/images/flower.jpg" - self.assertRaises(SyntaxError, PsdImagePlugin.PsdImageFile, invalid_file) +def test_eoferror(): + with Image.open(test_file) as im: + # PSD seek index starts at 1 rather than 0 + n_frames = im.n_frames + 1 - def test_n_frames(self): - with Image.open("Tests/images/hopper_merged.psd") as im: - self.assertEqual(im.n_frames, 1) - self.assertFalse(im.is_animated) + # Test seeking past the last frame + with pytest.raises(EOFError): + im.seek(n_frames) + assert im.tell() < n_frames - with Image.open(test_file) as im: - self.assertEqual(im.n_frames, 2) - self.assertTrue(im.is_animated) + # Test that seeking to the last frame does not raise an error + im.seek(n_frames - 1) - def test_eoferror(self): - with Image.open(test_file) as im: - # PSD seek index starts at 1 rather than 0 - n_frames = im.n_frames + 1 - # Test seeking past the last frame - self.assertRaises(EOFError, im.seek, n_frames) - self.assertLess(im.tell(), n_frames) +def test_seek_tell(): + with Image.open(test_file) as im: - # Test that seeking to the last frame does not raise an error - im.seek(n_frames - 1) + layer_number = im.tell() + assert layer_number == 1 - def test_seek_tell(self): - with Image.open(test_file) as im: + with pytest.raises(EOFError): + im.seek(0) - layer_number = im.tell() - self.assertEqual(layer_number, 1) + im.seek(1) + layer_number = im.tell() + assert layer_number == 1 - self.assertRaises(EOFError, im.seek, 0) + im.seek(2) + layer_number = im.tell() + assert layer_number == 2 - im.seek(1) - layer_number = im.tell() - self.assertEqual(layer_number, 1) - im.seek(2) - layer_number = im.tell() - self.assertEqual(layer_number, 2) +def test_seek_eoferror(): + with Image.open(test_file) as im: - def test_seek_eoferror(self): - with Image.open(test_file) as im: + with pytest.raises(EOFError): + im.seek(-1) - self.assertRaises(EOFError, im.seek, -1) - def test_open_after_exclusive_load(self): - with Image.open(test_file) as im: - im.load() - im.seek(im.tell() + 1) - im.load() +def test_open_after_exclusive_load(): + with Image.open(test_file) as im: + im.load() + im.seek(im.tell() + 1) + im.load() - def test_icc_profile(self): - with Image.open(test_file) as im: - self.assertIn("icc_profile", im.info) - icc_profile = im.info["icc_profile"] - self.assertEqual(len(icc_profile), 3144) +def test_icc_profile(): + with Image.open(test_file) as im: + assert "icc_profile" in im.info - def test_no_icc_profile(self): - with Image.open("Tests/images/hopper_merged.psd") as im: - self.assertNotIn("icc_profile", im.info) + icc_profile = im.info["icc_profile"] + assert len(icc_profile) == 3144 - def test_combined_larger_than_size(self): - # The 'combined' sizes of the individual parts is larger than the - # declared 'size' of the extra data field, resulting in a backwards seek. - # If we instead take the 'size' of the extra data field as the source of truth, - # then the seek can't be negative - with self.assertRaises(IOError): - Image.open("Tests/images/combined_larger_than_size.psd") +def test_no_icc_profile(): + with Image.open("Tests/images/hopper_merged.psd") as im: + assert "icc_profile" not in im.info + + +def test_combined_larger_than_size(): + # The 'combined' sizes of the individual parts is larger than the + # declared 'size' of the extra data field, resulting in a backwards seek. + + # If we instead take the 'size' of the extra data field as the source of truth, + # then the seek can't be negative + with pytest.raises(OSError): + Image.open("Tests/images/combined_larger_than_size.psd") diff --git a/Tests/test_file_sgi.py b/Tests/test_file_sgi.py index 586285485..a197fa775 100644 --- a/Tests/test_file_sgi.py +++ b/Tests/test_file_sgi.py @@ -1,89 +1,101 @@ +import pytest + from PIL import Image, SgiImagePlugin -from .helper import PillowTestCase, assert_image_equal, assert_image_similar, hopper +from .helper import assert_image_equal, assert_image_similar, hopper -class TestFileSgi(PillowTestCase): - def test_rgb(self): - # Created with ImageMagick then renamed: - # convert hopper.ppm -compress None sgi:hopper.rgb - test_file = "Tests/images/hopper.rgb" +def test_rgb(): + # Created with ImageMagick then renamed: + # convert hopper.ppm -compress None sgi:hopper.rgb + test_file = "Tests/images/hopper.rgb" - with Image.open(test_file) as im: - assert_image_equal(im, hopper()) - self.assertEqual(im.get_format_mimetype(), "image/rgb") + with Image.open(test_file) as im: + assert_image_equal(im, hopper()) + assert im.get_format_mimetype() == "image/rgb" - def test_rgb16(self): - test_file = "Tests/images/hopper16.rgb" - with Image.open(test_file) as im: - assert_image_equal(im, hopper()) +def test_rgb16(): + test_file = "Tests/images/hopper16.rgb" - def test_l(self): - # Created with ImageMagick - # convert hopper.ppm -monochrome -compress None sgi:hopper.bw - test_file = "Tests/images/hopper.bw" + with Image.open(test_file) as im: + assert_image_equal(im, hopper()) - with Image.open(test_file) as im: - assert_image_similar(im, hopper("L"), 2) - self.assertEqual(im.get_format_mimetype(), "image/sgi") - def test_rgba(self): - # Created with ImageMagick: - # convert transparent.png -compress None transparent.sgi - test_file = "Tests/images/transparent.sgi" +def test_l(): + # Created with ImageMagick + # convert hopper.ppm -monochrome -compress None sgi:hopper.bw + test_file = "Tests/images/hopper.bw" - with Image.open(test_file) as im: - with Image.open("Tests/images/transparent.png") as target: - assert_image_equal(im, target) - self.assertEqual(im.get_format_mimetype(), "image/sgi") + with Image.open(test_file) as im: + assert_image_similar(im, hopper("L"), 2) + assert im.get_format_mimetype() == "image/sgi" - def test_rle(self): - # Created with ImageMagick: - # convert hopper.ppm hopper.sgi - test_file = "Tests/images/hopper.sgi" - with Image.open(test_file) as im: - with Image.open("Tests/images/hopper.rgb") as target: - assert_image_equal(im, target) +def test_rgba(): + # Created with ImageMagick: + # convert transparent.png -compress None transparent.sgi + test_file = "Tests/images/transparent.sgi" - def test_rle16(self): - test_file = "Tests/images/tv16.sgi" + with Image.open(test_file) as im: + with Image.open("Tests/images/transparent.png") as target: + assert_image_equal(im, target) + assert im.get_format_mimetype() == "image/sgi" - with Image.open(test_file) as im: - with Image.open("Tests/images/tv.rgb") as target: - assert_image_equal(im, target) - def test_invalid_file(self): - invalid_file = "Tests/images/flower.jpg" +def test_rle(): + # Created with ImageMagick: + # convert hopper.ppm hopper.sgi + test_file = "Tests/images/hopper.sgi" - self.assertRaises(ValueError, SgiImagePlugin.SgiImageFile, invalid_file) + with Image.open(test_file) as im: + with Image.open("Tests/images/hopper.rgb") as target: + assert_image_equal(im, target) - def test_write(self): - def roundtrip(img): - out = self.tempfile("temp.sgi") - img.save(out, format="sgi") - with Image.open(out) as reloaded: - assert_image_equal(img, reloaded) - for mode in ("L", "RGB", "RGBA"): - roundtrip(hopper(mode)) +def test_rle16(): + test_file = "Tests/images/tv16.sgi" - # Test 1 dimension for an L mode image - roundtrip(Image.new("L", (10, 1))) + with Image.open(test_file) as im: + with Image.open("Tests/images/tv.rgb") as target: + assert_image_equal(im, target) - def test_write16(self): - test_file = "Tests/images/hopper16.rgb" - with Image.open(test_file) as im: - out = self.tempfile("temp.sgi") - im.save(out, format="sgi", bpc=2) +def test_invalid_file(): + invalid_file = "Tests/images/flower.jpg" - with Image.open(out) as reloaded: - assert_image_equal(im, reloaded) + with pytest.raises(ValueError): + SgiImagePlugin.SgiImageFile(invalid_file) - def test_unsupported_mode(self): - im = hopper("LA") - out = self.tempfile("temp.sgi") - self.assertRaises(ValueError, im.save, out, format="sgi") +def test_write(tmp_path): + def roundtrip(img): + out = str(tmp_path / "temp.sgi") + img.save(out, format="sgi") + with Image.open(out) as reloaded: + assert_image_equal(img, reloaded) + + for mode in ("L", "RGB", "RGBA"): + roundtrip(hopper(mode)) + + # Test 1 dimension for an L mode image + roundtrip(Image.new("L", (10, 1))) + + +def test_write16(tmp_path): + test_file = "Tests/images/hopper16.rgb" + + with Image.open(test_file) as im: + out = str(tmp_path / "temp.sgi") + im.save(out, format="sgi", bpc=2) + + with Image.open(out) as reloaded: + assert_image_equal(im, reloaded) + + +def test_unsupported_mode(tmp_path): + im = hopper("LA") + out = str(tmp_path / "temp.sgi") + + with pytest.raises(ValueError): + im.save(out, format="sgi") diff --git a/Tests/test_file_spider.py b/Tests/test_file_spider.py index 243b5e466..9cdb451c9 100644 --- a/Tests/test_file_spider.py +++ b/Tests/test_file_spider.py @@ -1,147 +1,163 @@ import tempfile -import unittest from io import BytesIO import pytest + from PIL import Image, ImageSequence, SpiderImagePlugin -from .helper import PillowTestCase, assert_image_equal, hopper, is_pypy +from .helper import assert_image_equal, hopper, is_pypy TEST_FILE = "Tests/images/hopper.spider" -class TestImageSpider(PillowTestCase): - def test_sanity(self): +def test_sanity(): + with Image.open(TEST_FILE) as im: + im.load() + assert im.mode == "F" + assert im.size == (128, 128) + assert im.format == "SPIDER" + + +@pytest.mark.skipif(is_pypy(), reason="Requires CPython") +def test_unclosed_file(): + def open(): + im = Image.open(TEST_FILE) + im.load() + + pytest.warns(ResourceWarning, open) + + +def test_closed_file(): + def open(): + im = Image.open(TEST_FILE) + im.load() + im.close() + + pytest.warns(None, open) + + +def test_context_manager(): + def open(): with Image.open(TEST_FILE) as im: im.load() - self.assertEqual(im.mode, "F") - self.assertEqual(im.size, (128, 128)) - self.assertEqual(im.format, "SPIDER") - @unittest.skipIf(is_pypy(), "Requires CPython") - def test_unclosed_file(self): - def open(): - im = Image.open(TEST_FILE) - im.load() + pytest.warns(None, open) - pytest.warns(ResourceWarning, open) - def test_closed_file(self): - def open(): - im = Image.open(TEST_FILE) - im.load() - im.close() +def test_save(tmp_path): + # Arrange + temp = str(tmp_path / "temp.spider") + im = hopper() - pytest.warns(None, open) + # Act + im.save(temp, "SPIDER") - def test_context_manager(self): - def open(): - with Image.open(TEST_FILE) as im: - im.load() + # Assert + with Image.open(temp) as im2: + assert im2.mode == "F" + assert im2.size == (128, 128) + assert im2.format == "SPIDER" - pytest.warns(None, open) - def test_save(self): - # Arrange - temp = self.tempfile("temp.spider") - im = hopper() +def test_tempfile(): + # Arrange + im = hopper() - # Act - im.save(temp, "SPIDER") + # Act + with tempfile.TemporaryFile() as fp: + im.save(fp, "SPIDER") # Assert - with Image.open(temp) as im2: - self.assertEqual(im2.mode, "F") - self.assertEqual(im2.size, (128, 128)) - self.assertEqual(im2.format, "SPIDER") + fp.seek(0) + with Image.open(fp) as reloaded: + assert reloaded.mode == "F" + assert reloaded.size == (128, 128) + assert reloaded.format == "SPIDER" - def test_tempfile(self): - # Arrange - im = hopper() + +def test_is_spider_image(): + assert SpiderImagePlugin.isSpiderImage(TEST_FILE) + + +def test_tell(): + # Arrange + with Image.open(TEST_FILE) as im: # Act - with tempfile.TemporaryFile() as fp: - im.save(fp, "SPIDER") - - # Assert - fp.seek(0) - with Image.open(fp) as reloaded: - self.assertEqual(reloaded.mode, "F") - self.assertEqual(reloaded.size, (128, 128)) - self.assertEqual(reloaded.format, "SPIDER") - - def test_isSpiderImage(self): - self.assertTrue(SpiderImagePlugin.isSpiderImage(TEST_FILE)) - - def test_tell(self): - # Arrange - with Image.open(TEST_FILE) as im: - - # Act - index = im.tell() - - # Assert - self.assertEqual(index, 0) - - def test_n_frames(self): - with Image.open(TEST_FILE) as im: - self.assertEqual(im.n_frames, 1) - self.assertFalse(im.is_animated) - - def test_loadImageSeries(self): - # Arrange - not_spider_file = "Tests/images/hopper.ppm" - file_list = [TEST_FILE, not_spider_file, "path/not_found.ext"] - - # Act - img_list = SpiderImagePlugin.loadImageSeries(file_list) + index = im.tell() # Assert - self.assertEqual(len(img_list), 1) - self.assertIsInstance(img_list[0], Image.Image) - self.assertEqual(img_list[0].size, (128, 128)) + assert index == 0 - def test_loadImageSeries_no_input(self): - # Arrange - file_list = None - # Act - img_list = SpiderImagePlugin.loadImageSeries(file_list) +def test_n_frames(): + with Image.open(TEST_FILE) as im: + assert im.n_frames == 1 + assert not im.is_animated - # Assert - self.assertIsNone(img_list) - def test_isInt_not_a_number(self): - # Arrange - not_a_number = "a" +def test_load_image_series(): + # Arrange + not_spider_file = "Tests/images/hopper.ppm" + file_list = [TEST_FILE, not_spider_file, "path/not_found.ext"] - # Act - ret = SpiderImagePlugin.isInt(not_a_number) + # Act + img_list = SpiderImagePlugin.loadImageSeries(file_list) - # Assert - self.assertEqual(ret, 0) + # Assert + assert len(img_list) == 1 + assert isinstance(img_list[0], Image.Image) + assert img_list[0].size == (128, 128) - def test_invalid_file(self): - invalid_file = "Tests/images/invalid.spider" - self.assertRaises(IOError, Image.open, invalid_file) +def test_load_image_series_no_input(): + # Arrange + file_list = None - def test_nonstack_file(self): - with Image.open(TEST_FILE) as im: - self.assertRaises(EOFError, im.seek, 0) + # Act + img_list = SpiderImagePlugin.loadImageSeries(file_list) - def test_nonstack_dos(self): - with Image.open(TEST_FILE) as im: - for i, frame in enumerate(ImageSequence.Iterator(im)): - self.assertLessEqual(i, 1, "Non-stack DOS file test failed") + # Assert + assert img_list is None - # for issue #4093 - def test_odd_size(self): - data = BytesIO() - width = 100 - im = Image.new("F", (width, 64)) - im.save(data, format="SPIDER") - data.seek(0) - with Image.open(data) as im2: - assert_image_equal(im, im2) +def test_is_int_not_a_number(): + # Arrange + not_a_number = "a" + + # Act + ret = SpiderImagePlugin.isInt(not_a_number) + + # Assert + assert ret == 0 + + +def test_invalid_file(): + invalid_file = "Tests/images/invalid.spider" + + with pytest.raises(OSError): + Image.open(invalid_file) + + +def test_nonstack_file(): + with Image.open(TEST_FILE) as im: + with pytest.raises(EOFError): + im.seek(0) + + +def test_nonstack_dos(): + with Image.open(TEST_FILE) as im: + for i, frame in enumerate(ImageSequence.Iterator(im)): + assert i <= 1, "Non-stack DOS file test failed" + + +# for issue #4093 +def test_odd_size(): + data = BytesIO() + width = 100 + im = Image.new("F", (width, 64)) + im.save(data, format="SPIDER") + + data.seek(0) + with Image.open(data) as im2: + assert_image_equal(im, im2) diff --git a/Tests/test_file_sun.py b/Tests/test_file_sun.py index adf97e1bf..8421106a2 100644 --- a/Tests/test_file_sun.py +++ b/Tests/test_file_sun.py @@ -1,47 +1,52 @@ import os -import unittest + +import pytest from PIL import Image, SunImagePlugin -from .helper import PillowTestCase, assert_image_equal, assert_image_similar, hopper +from .helper import assert_image_equal, assert_image_similar, hopper EXTRA_DIR = "Tests/images/sunraster" -class TestFileSun(PillowTestCase): - def test_sanity(self): - # Arrange - # Created with ImageMagick: convert hopper.jpg hopper.ras - test_file = "Tests/images/hopper.ras" +def test_sanity(): + # Arrange + # Created with ImageMagick: convert hopper.jpg hopper.ras + test_file = "Tests/images/hopper.ras" - # Act - with Image.open(test_file) as im: + # Act + with Image.open(test_file) as im: - # Assert - self.assertEqual(im.size, (128, 128)) + # Assert + assert im.size == (128, 128) - assert_image_similar(im, hopper(), 5) # visually verified + assert_image_similar(im, hopper(), 5) # visually verified - invalid_file = "Tests/images/flower.jpg" - self.assertRaises(SyntaxError, SunImagePlugin.SunImageFile, invalid_file) + invalid_file = "Tests/images/flower.jpg" + with pytest.raises(SyntaxError): + SunImagePlugin.SunImageFile(invalid_file) - def test_im1(self): - with Image.open("Tests/images/sunraster.im1") as im: - with Image.open("Tests/images/sunraster.im1.png") as target: + +def test_im1(): + with Image.open("Tests/images/sunraster.im1") as im: + with Image.open("Tests/images/sunraster.im1.png") as target: + assert_image_equal(im, target) + + +@pytest.mark.skipif( + not os.path.exists(EXTRA_DIR), reason="Extra image files not installed" +) +def test_others(): + files = ( + os.path.join(EXTRA_DIR, f) + for f in os.listdir(EXTRA_DIR) + if os.path.splitext(f)[1] in (".sun", ".SUN", ".ras") + ) + for path in files: + with Image.open(path) as im: + im.load() + assert isinstance(im, SunImagePlugin.SunImageFile) + target_path = f"{os.path.splitext(path)[0]}.png" + # im.save(target_file) + with Image.open(target_path) as target: assert_image_equal(im, target) - - @unittest.skipUnless(os.path.exists(EXTRA_DIR), "Extra image files not installed") - def test_others(self): - files = ( - os.path.join(EXTRA_DIR, f) - for f in os.listdir(EXTRA_DIR) - if os.path.splitext(f)[1] in (".sun", ".SUN", ".ras") - ) - for path in files: - with Image.open(path) as im: - im.load() - self.assertIsInstance(im, SunImagePlugin.SunImageFile) - target_path = "%s.png" % os.path.splitext(path)[0] - # im.save(target_file) - with Image.open(target_path) as target: - assert_image_equal(im, target) diff --git a/Tests/test_file_tar.py b/Tests/test_file_tar.py index 240f7acba..02001e5b1 100644 --- a/Tests/test_file_tar.py +++ b/Tests/test_file_tar.py @@ -1,51 +1,46 @@ -import unittest - import pytest -from PIL import Image, TarIO -from .helper import PillowTestCase, is_pypy +from PIL import Image, TarIO, features -codecs = dir(Image.core) +from .helper import is_pypy # Sample tar archive TEST_TAR_FILE = "Tests/images/hopper.tar" -class TestFileTar(PillowTestCase): - def setUp(self): - if "zip_decoder" not in codecs and "jpeg_decoder" not in codecs: - self.skipTest("neither jpeg nor zip support available") +def test_sanity(): + for codec, test_path, format in [ + ["zlib", "hopper.png", "PNG"], + ["jpg", "hopper.jpg", "JPEG"], + ]: + if features.check(codec): + with TarIO.TarIO(TEST_TAR_FILE, test_path) as tar: + with Image.open(tar) as im: + im.load() + assert im.mode == "RGB" + assert im.size == (128, 128) + assert im.format == format - def test_sanity(self): - for codec, test_path, format in [ - ["zip_decoder", "hopper.png", "PNG"], - ["jpeg_decoder", "hopper.jpg", "JPEG"], - ]: - if codec in codecs: - with TarIO.TarIO(TEST_TAR_FILE, test_path) as tar: - with Image.open(tar) as im: - im.load() - self.assertEqual(im.mode, "RGB") - self.assertEqual(im.size, (128, 128)) - self.assertEqual(im.format, format) - @unittest.skipIf(is_pypy(), "Requires CPython") - def test_unclosed_file(self): - def open(): - TarIO.TarIO(TEST_TAR_FILE, "hopper.jpg") +@pytest.mark.skipif(is_pypy(), reason="Requires CPython") +def test_unclosed_file(): + def open(): + TarIO.TarIO(TEST_TAR_FILE, "hopper.jpg") - pytest.warns(ResourceWarning, open) + pytest.warns(ResourceWarning, open) - def test_close(self): - def open(): - tar = TarIO.TarIO(TEST_TAR_FILE, "hopper.jpg") - tar.close() - pytest.warns(None, open) +def test_close(): + def open(): + tar = TarIO.TarIO(TEST_TAR_FILE, "hopper.jpg") + tar.close() - def test_contextmanager(self): - def open(): - with TarIO.TarIO(TEST_TAR_FILE, "hopper.jpg"): - pass + pytest.warns(None, open) - pytest.warns(None, open) + +def test_contextmanager(): + def open(): + with TarIO.TarIO(TEST_TAR_FILE, "hopper.jpg"): + pass + + pytest.warns(None, open) diff --git a/Tests/test_file_tga.py b/Tests/test_file_tga.py index 61d8e7efe..465e13316 100644 --- a/Tests/test_file_tga.py +++ b/Tests/test_file_tga.py @@ -3,208 +3,203 @@ from glob import glob from itertools import product import pytest + from PIL import Image -from .helper import PillowTestCase, assert_image_equal, hopper +from .helper import assert_image_equal, hopper _TGA_DIR = os.path.join("Tests", "images", "tga") _TGA_DIR_COMMON = os.path.join(_TGA_DIR, "common") -class TestFileTga(PillowTestCase): +_MODES = ("L", "LA", "P", "RGB", "RGBA") +_ORIGINS = ("tl", "bl") - _MODES = ("L", "LA", "P", "RGB", "RGBA") - _ORIGINS = ("tl", "bl") +_ORIGIN_TO_ORIENTATION = {"tl": 1, "bl": -1} - _ORIGIN_TO_ORIENTATION = {"tl": 1, "bl": -1} - def test_sanity(self): - for mode in self._MODES: - png_paths = glob( - os.path.join(_TGA_DIR_COMMON, "*x*_{}.png".format(mode.lower())) - ) +def test_sanity(tmp_path): + for mode in _MODES: - for png_path in png_paths: - with Image.open(png_path) as reference_im: - self.assertEqual(reference_im.mode, mode) + def roundtrip(original_im): + out = str(tmp_path / "temp.tga") - path_no_ext = os.path.splitext(png_path)[0] - for origin, rle in product(self._ORIGINS, (True, False)): - tga_path = "{}_{}_{}.tga".format( - path_no_ext, origin, "rle" if rle else "raw" + original_im.save(out, rle=rle) + with Image.open(out) as saved_im: + if rle: + assert ( + saved_im.info["compression"] == original_im.info["compression"] + ) + assert saved_im.info["orientation"] == original_im.info["orientation"] + if mode == "P": + assert saved_im.getpalette() == original_im.getpalette() + + assert_image_equal(saved_im, original_im) + + png_paths = glob(os.path.join(_TGA_DIR_COMMON, f"*x*_{mode.lower()}.png")) + + for png_path in png_paths: + with Image.open(png_path) as reference_im: + assert reference_im.mode == mode + + path_no_ext = os.path.splitext(png_path)[0] + for origin, rle in product(_ORIGINS, (True, False)): + tga_path = "{}_{}_{}.tga".format( + path_no_ext, origin, "rle" if rle else "raw" + ) + + with Image.open(tga_path) as original_im: + assert original_im.format == "TGA" + assert original_im.get_format_mimetype() == "image/x-tga" + if rle: + assert original_im.info["compression"] == "tga_rle" + assert ( + original_im.info["orientation"] + == _ORIGIN_TO_ORIENTATION[origin] ) + if mode == "P": + assert original_im.getpalette() == reference_im.getpalette() - with Image.open(tga_path) as original_im: - self.assertEqual(original_im.format, "TGA") - self.assertEqual( - original_im.get_format_mimetype(), "image/x-tga" - ) - if rle: - self.assertEqual( - original_im.info["compression"], "tga_rle" - ) - self.assertEqual( - original_im.info["orientation"], - self._ORIGIN_TO_ORIENTATION[origin], - ) - if mode == "P": - self.assertEqual( - original_im.getpalette(), reference_im.getpalette() - ) + assert_image_equal(original_im, reference_im) - assert_image_equal(original_im, reference_im) + roundtrip(original_im) - # Generate a new test name every time so the - # test will not fail with permission error - # on Windows. - out = self.tempfile("temp.tga") - original_im.save(out, rle=rle) - with Image.open(out) as saved_im: - if rle: - self.assertEqual( - saved_im.info["compression"], - original_im.info["compression"], - ) - self.assertEqual( - saved_im.info["orientation"], - original_im.info["orientation"], - ) - if mode == "P": - self.assertEqual( - saved_im.getpalette(), original_im.getpalette() - ) +def test_id_field(): + # tga file with id field + test_file = "Tests/images/tga_id_field.tga" - assert_image_equal(saved_im, original_im) + # Act + with Image.open(test_file) as im: - def test_id_field(self): - # tga file with id field - test_file = "Tests/images/tga_id_field.tga" + # Assert + assert im.size == (100, 100) - # Act - with Image.open(test_file) as im: - # Assert - self.assertEqual(im.size, (100, 100)) +def test_id_field_rle(): + # tga file with id field + test_file = "Tests/images/rgb32rle.tga" - def test_id_field_rle(self): - # tga file with id field - test_file = "Tests/images/rgb32rle.tga" + # Act + with Image.open(test_file) as im: - # Act - with Image.open(test_file) as im: + # Assert + assert im.size == (199, 199) - # Assert - self.assertEqual(im.size, (199, 199)) - def test_save(self): - test_file = "Tests/images/tga_id_field.tga" - with Image.open(test_file) as im: - out = self.tempfile("temp.tga") +def test_save(tmp_path): + test_file = "Tests/images/tga_id_field.tga" + with Image.open(test_file) as im: + out = str(tmp_path / "temp.tga") - # Save - im.save(out) - with Image.open(out) as test_im: - self.assertEqual(test_im.size, (100, 100)) - self.assertEqual(test_im.info["id_section"], im.info["id_section"]) - - # RGBA save - im.convert("RGBA").save(out) + # Save + im.save(out) with Image.open(out) as test_im: - self.assertEqual(test_im.size, (100, 100)) - - def test_save_wrong_mode(self): - im = hopper("PA") - out = self.tempfile("temp.tga") - - with self.assertRaises(OSError): - im.save(out) - - def test_save_id_section(self): - test_file = "Tests/images/rgb32rle.tga" - with Image.open(test_file) as im: - out = self.tempfile("temp.tga") - - # Check there is no id section - im.save(out) - with Image.open(out) as test_im: - self.assertNotIn("id_section", test_im.info) - - # Save with custom id section - im.save(out, id_section=b"Test content") - with Image.open(out) as test_im: - self.assertEqual(test_im.info["id_section"], b"Test content") - - # 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 Image.open(out) as test_im: - self.assertEqual(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: - self.assertNotIn("id_section", test_im.info) - - def test_save_orientation(self): - test_file = "Tests/images/rgb32rle.tga" - out = self.tempfile("temp.tga") - with Image.open(test_file) as im: - self.assertEqual(im.info["orientation"], -1) - - im.save(out, orientation=1) - with Image.open(out) as test_im: - self.assertEqual(test_im.info["orientation"], 1) - - def test_save_rle(self): - test_file = "Tests/images/rgb32rle.tga" - with Image.open(test_file) as im: - self.assertEqual(im.info["compression"], "tga_rle") - - out = self.tempfile("temp.tga") - - # Save - im.save(out) - with Image.open(out) as test_im: - self.assertEqual(test_im.size, (199, 199)) - self.assertEqual(test_im.info["compression"], "tga_rle") - - # Save without compression - im.save(out, compression=None) - with Image.open(out) as test_im: - self.assertNotIn("compression", test_im.info) + assert test_im.size == (100, 100) + assert test_im.info["id_section"] == im.info["id_section"] # RGBA save im.convert("RGBA").save(out) - with Image.open(out) as test_im: - self.assertEqual(test_im.size, (199, 199)) + with Image.open(out) as test_im: + assert test_im.size == (100, 100) - test_file = "Tests/images/tga_id_field.tga" - with Image.open(test_file) as im: - self.assertNotIn("compression", im.info) - # Save with compression - im.save(out, compression="tga_rle") - with Image.open(out) as test_im: - self.assertEqual(test_im.info["compression"], "tga_rle") +def test_save_wrong_mode(tmp_path): + im = hopper("PA") + out = str(tmp_path / "temp.tga") - def test_save_l_transparency(self): - # There are 559 transparent pixels in la.tga. - num_transparent = 559 + with pytest.raises(OSError): + im.save(out) - in_file = "Tests/images/la.tga" - with Image.open(in_file) as im: - self.assertEqual(im.mode, "LA") - self.assertEqual(im.getchannel("A").getcolors()[0][0], num_transparent) - out = self.tempfile("temp.tga") - im.save(out) +def test_save_id_section(tmp_path): + test_file = "Tests/images/rgb32rle.tga" + with Image.open(test_file) as im: + out = str(tmp_path / "temp.tga") - with Image.open(out) as test_im: - self.assertEqual(test_im.mode, "LA") - self.assertEqual(test_im.getchannel("A").getcolors()[0][0], num_transparent) + # Check there is no id section + im.save(out) + with Image.open(out) as test_im: + assert "id_section" not in test_im.info - assert_image_equal(im, test_im) + # Save with custom id section + im.save(out, id_section=b"Test content") + with Image.open(out) as test_im: + assert test_im.info["id_section"] == b"Test content" + + # 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 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: + assert "id_section" not in test_im.info + + +def test_save_orientation(tmp_path): + test_file = "Tests/images/rgb32rle.tga" + out = str(tmp_path / "temp.tga") + with Image.open(test_file) as im: + assert im.info["orientation"] == -1 + + im.save(out, orientation=1) + with Image.open(out) as test_im: + assert test_im.info["orientation"] == 1 + + +def test_save_rle(tmp_path): + test_file = "Tests/images/rgb32rle.tga" + with Image.open(test_file) as im: + assert im.info["compression"] == "tga_rle" + + out = str(tmp_path / "temp.tga") + + # Save + im.save(out) + with Image.open(out) as test_im: + assert test_im.size == (199, 199) + assert test_im.info["compression"] == "tga_rle" + + # Save without compression + im.save(out, compression=None) + with Image.open(out) as test_im: + assert "compression" not in test_im.info + + # RGBA save + im.convert("RGBA").save(out) + with Image.open(out) as test_im: + assert test_im.size == (199, 199) + + test_file = "Tests/images/tga_id_field.tga" + with Image.open(test_file) as im: + assert "compression" not in im.info + + # Save with compression + im.save(out, compression="tga_rle") + with Image.open(out) as test_im: + assert test_im.info["compression"] == "tga_rle" + + +def test_save_l_transparency(tmp_path): + # There are 559 transparent pixels in la.tga. + num_transparent = 559 + + in_file = "Tests/images/la.tga" + with Image.open(in_file) as im: + assert im.mode == "LA" + assert im.getchannel("A").getcolors()[0][0] == num_transparent + + out = str(tmp_path / "temp.tga") + im.save(out) + + with Image.open(out) as test_im: + assert test_im.mode == "LA" + assert test_im.getchannel("A").getcolors()[0][0] == num_transparent + + assert_image_equal(im, test_im) diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index 8140fa3fe..bb1bbda3e 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -1,14 +1,12 @@ -import logging import os -import unittest from io import BytesIO import pytest + from PIL import Image, TiffImagePlugin -from PIL.TiffImagePlugin import RESOLUTION_UNIT, X_RESOLUTION, Y_RESOLUTION +from PIL.TiffImagePlugin import RESOLUTION_UNIT, SUBIFD, X_RESOLUTION, Y_RESOLUTION from .helper import ( - PillowTestCase, assert_image_equal, assert_image_equal_tofile, assert_image_similar, @@ -18,21 +16,19 @@ from .helper import ( is_win32, ) -logger = logging.getLogger(__name__) +class TestFileTiff: + def test_sanity(self, tmp_path): -class TestFileTiff(PillowTestCase): - def test_sanity(self): - - filename = self.tempfile("temp.tif") + filename = str(tmp_path / "temp.tif") hopper("RGB").save(filename) with Image.open(filename) as im: im.load() - self.assertEqual(im.mode, "RGB") - self.assertEqual(im.size, (128, 128)) - self.assertEqual(im.format, "TIFF") + assert im.mode == "RGB" + assert im.size == (128, 128) + assert im.format == "TIFF" hopper("1").save(filename) with Image.open(filename): @@ -54,7 +50,7 @@ class TestFileTiff(PillowTestCase): with Image.open(filename): pass - @unittest.skipIf(is_pypy(), "Requires CPython") + @pytest.mark.skipif(is_pypy(), reason="Requires CPython") def test_unclosed_file(self): def open(): im = Image.open("Tests/images/multipage.tiff") @@ -82,53 +78,54 @@ class TestFileTiff(PillowTestCase): filename = "Tests/images/pil136.tiff" with Image.open(filename) as im: - self.assertEqual(im.mode, "RGBA") - self.assertEqual(im.size, (55, 43)) - self.assertEqual(im.tile, [("raw", (0, 0, 55, 43), 8, ("RGBa", 0, 1))]) + assert im.mode == "RGBA" + assert im.size == (55, 43) + assert im.tile == [("raw", (0, 0, 55, 43), 8, ("RGBa", 0, 1))] im.load() assert_image_similar_tofile(im, "Tests/images/pil136.png", 1) def test_wrong_bits_per_sample(self): with Image.open("Tests/images/tiff_wrong_bits_per_sample.tiff") as im: - self.assertEqual(im.mode, "RGBA") - self.assertEqual(im.size, (52, 53)) - self.assertEqual(im.tile, [("raw", (0, 0, 52, 53), 160, ("RGBA", 0, 1))]) + assert im.mode == "RGBA" + assert im.size == (52, 53) + assert im.tile == [("raw", (0, 0, 52, 53), 160, ("RGBA", 0, 1))] im.load() def test_set_legacy_api(self): ifd = TiffImagePlugin.ImageFileDirectory_v2() - with self.assertRaises(Exception) as e: + with pytest.raises(Exception) as e: ifd.legacy_api = None - self.assertEqual(str(e.exception), "Not allowing setting of legacy api") + assert str(e.value) == "Not allowing setting of legacy api" def test_xyres_tiff(self): filename = "Tests/images/pil168.tif" with Image.open(filename) as im: # legacy api - self.assertIsInstance(im.tag[X_RESOLUTION][0], tuple) - self.assertIsInstance(im.tag[Y_RESOLUTION][0], tuple) + assert isinstance(im.tag[X_RESOLUTION][0], tuple) + assert isinstance(im.tag[Y_RESOLUTION][0], tuple) # v2 api - self.assertIsInstance(im.tag_v2[X_RESOLUTION], TiffImagePlugin.IFDRational) - self.assertIsInstance(im.tag_v2[Y_RESOLUTION], TiffImagePlugin.IFDRational) + assert isinstance(im.tag_v2[X_RESOLUTION], TiffImagePlugin.IFDRational) + assert isinstance(im.tag_v2[Y_RESOLUTION], TiffImagePlugin.IFDRational) - self.assertEqual(im.info["dpi"], (72.0, 72.0)) + assert im.info["dpi"] == (72.0, 72.0) def test_xyres_fallback_tiff(self): filename = "Tests/images/compression.tif" with Image.open(filename) as im: # v2 api - self.assertIsInstance(im.tag_v2[X_RESOLUTION], TiffImagePlugin.IFDRational) - self.assertIsInstance(im.tag_v2[Y_RESOLUTION], TiffImagePlugin.IFDRational) - self.assertRaises(KeyError, lambda: im.tag_v2[RESOLUTION_UNIT]) + assert isinstance(im.tag_v2[X_RESOLUTION], TiffImagePlugin.IFDRational) + assert isinstance(im.tag_v2[Y_RESOLUTION], TiffImagePlugin.IFDRational) + with pytest.raises(KeyError): + im.tag_v2[RESOLUTION_UNIT] # Legacy. - self.assertEqual(im.info["resolution"], (100.0, 100.0)) + assert im.info["resolution"] == (100.0, 100.0) # Fallback "inch". - self.assertEqual(im.info["dpi"], (100.0, 100.0)) + assert im.info["dpi"] == (100.0, 100.0) def test_int_resolution(self): filename = "Tests/images/pil168.tif" @@ -138,31 +135,39 @@ class TestFileTiff(PillowTestCase): im.tag_v2[X_RESOLUTION] = 71 im.tag_v2[Y_RESOLUTION] = 71 im._setup() - self.assertEqual(im.info["dpi"], (71.0, 71.0)) + assert im.info["dpi"] == (71.0, 71.0) def test_load_dpi_rounding(self): for resolutionUnit, dpi in ((None, (72, 73)), (2, (72, 73)), (3, (183, 185))): with Image.open( "Tests/images/hopper_roundDown_" + str(resolutionUnit) + ".tif" ) as im: - self.assertEqual(im.tag_v2.get(RESOLUTION_UNIT), resolutionUnit) - self.assertEqual(im.info["dpi"], (dpi[0], dpi[0])) + assert im.tag_v2.get(RESOLUTION_UNIT) == resolutionUnit + assert im.info["dpi"] == (dpi[0], dpi[0]) with Image.open( "Tests/images/hopper_roundUp_" + str(resolutionUnit) + ".tif" ) as im: - self.assertEqual(im.tag_v2.get(RESOLUTION_UNIT), resolutionUnit) - self.assertEqual(im.info["dpi"], (dpi[1], dpi[1])) + assert im.tag_v2.get(RESOLUTION_UNIT) == resolutionUnit + assert im.info["dpi"] == (dpi[1], dpi[1]) - def test_save_dpi_rounding(self): - outfile = self.tempfile("temp.tif") + def test_save_dpi_rounding(self, tmp_path): + outfile = str(tmp_path / "temp.tif") with Image.open("Tests/images/hopper.tif") as im: for dpi in (72.2, 72.8): im.save(outfile, dpi=(dpi, dpi)) with Image.open(outfile) as reloaded: reloaded.load() - self.assertEqual((round(dpi), round(dpi)), reloaded.info["dpi"]) + assert (round(dpi), round(dpi)) == reloaded.info["dpi"] + + 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_save_setting_missing_resolution(self): b = BytesIO() @@ -170,16 +175,18 @@ class TestFileTiff(PillowTestCase): b, format="tiff", resolution=123.45 ) with Image.open(b) as im: - self.assertEqual(float(im.tag_v2[X_RESOLUTION]), 123.45) - self.assertEqual(float(im.tag_v2[Y_RESOLUTION]), 123.45) + assert float(im.tag_v2[X_RESOLUTION]) == 123.45 + assert float(im.tag_v2[Y_RESOLUTION]) == 123.45 def test_invalid_file(self): invalid_file = "Tests/images/flower.jpg" - self.assertRaises(SyntaxError, TiffImagePlugin.TiffImageFile, invalid_file) + with pytest.raises(SyntaxError): + TiffImagePlugin.TiffImageFile(invalid_file) TiffImagePlugin.PREFIXES.append(b"\xff\xd8\xff\xe0") - self.assertRaises(SyntaxError, TiffImagePlugin.TiffImageFile, invalid_file) + with pytest.raises(SyntaxError): + TiffImagePlugin.TiffImageFile(invalid_file) TiffImagePlugin.PREFIXES.pop() def test_bad_exif(self): @@ -187,46 +194,47 @@ class TestFileTiff(PillowTestCase): # Should not raise struct.error. pytest.warns(UserWarning, i._getexif) - def test_save_rgba(self): + def test_save_rgba(self, tmp_path): im = hopper("RGBA") - outfile = self.tempfile("temp.tif") + outfile = str(tmp_path / "temp.tif") im.save(outfile) - def test_save_unsupported_mode(self): + def test_save_unsupported_mode(self, tmp_path): im = hopper("HSV") - outfile = self.tempfile("temp.tif") - self.assertRaises(IOError, im.save, outfile) + outfile = str(tmp_path / "temp.tif") + with pytest.raises(OSError): + im.save(outfile) def test_little_endian(self): with Image.open("Tests/images/16bit.cropped.tif") as im: - self.assertEqual(im.getpixel((0, 0)), 480) - self.assertEqual(im.mode, "I;16") + assert im.getpixel((0, 0)) == 480 + assert im.mode == "I;16" b = im.tobytes() # Bytes are in image native order (little endian) - self.assertEqual(b[0], ord(b"\xe0")) - self.assertEqual(b[1], ord(b"\x01")) + assert b[0] == ord(b"\xe0") + assert b[1] == ord(b"\x01") def test_big_endian(self): with Image.open("Tests/images/16bit.MM.cropped.tif") as im: - self.assertEqual(im.getpixel((0, 0)), 480) - self.assertEqual(im.mode, "I;16B") + assert im.getpixel((0, 0)) == 480 + assert im.mode == "I;16B" b = im.tobytes() # Bytes are in image native order (big endian) - self.assertEqual(b[0], ord(b"\x01")) - self.assertEqual(b[1], ord(b"\xe0")) + assert b[0] == ord(b"\x01") + assert b[1] == ord(b"\xe0") def test_16bit_s(self): with Image.open("Tests/images/16bit.s.tif") as im: im.load() - self.assertEqual(im.mode, "I") - self.assertEqual(im.getpixel((0, 0)), 32767) - self.assertEqual(im.getpixel((0, 1)), 0) + assert im.mode == "I" + assert im.getpixel((0, 0)) == 32767 + assert im.getpixel((0, 1)) == 0 def test_12bit_rawmode(self): - """ Are we generating the same interpretation - of the image as Imagemagick is? """ + """Are we generating the same interpretation + of the image as Imagemagick is?""" with Image.open("Tests/images/12bit.cropped.tif") as im: # to make the target -- @@ -243,13 +251,12 @@ class TestFileTiff(PillowTestCase): with Image.open(path) as im: im.load() - self.assertEqual(im.getpixel((0, 0)), -0.4526388943195343) - self.assertEqual(im.getextrema(), (-3.140936851501465, 3.140684127807617)) + assert im.getpixel((0, 0)) == -0.4526388943195343 + assert im.getextrema() == (-3.140936851501465, 3.140684127807617) def test_unknown_pixel_mode(self): - self.assertRaises( - IOError, Image.open, "Tests/images/hopper_unknown_pixel_mode.tif" - ) + with pytest.raises(OSError): + Image.open("Tests/images/hopper_unknown_pixel_mode.tif") def test_n_frames(self): for path, n_frames in [ @@ -257,16 +264,17 @@ class TestFileTiff(PillowTestCase): ["Tests/images/multipage.tiff", 3], ]: with Image.open(path) as im: - self.assertEqual(im.n_frames, n_frames) - self.assertEqual(im.is_animated, n_frames != 1) + assert im.n_frames == n_frames + assert im.is_animated == (n_frames != 1) def test_eoferror(self): with Image.open("Tests/images/multipage-lastframe.tif") as im: n_frames = im.n_frames # Test seeking past the last frame - self.assertRaises(EOFError, im.seek, n_frames) - self.assertLess(im.tell(), n_frames) + with pytest.raises(EOFError): + im.seek(n_frames) + assert im.tell() < n_frames # Test that seeking to the last frame does not raise an error im.seek(n_frames - 1) @@ -277,29 +285,29 @@ class TestFileTiff(PillowTestCase): # file is a multipage tiff: 10x10 green, 10x10 red, 20x20 blue im.seek(0) - self.assertEqual(im.size, (10, 10)) - self.assertEqual(im.convert("RGB").getpixel((0, 0)), (0, 128, 0)) + assert im.size == (10, 10) + assert im.convert("RGB").getpixel((0, 0)) == (0, 128, 0) im.seek(1) im.load() - self.assertEqual(im.size, (10, 10)) - self.assertEqual(im.convert("RGB").getpixel((0, 0)), (255, 0, 0)) + assert im.size == (10, 10) + assert im.convert("RGB").getpixel((0, 0)) == (255, 0, 0) im.seek(0) im.load() - self.assertEqual(im.size, (10, 10)) - self.assertEqual(im.convert("RGB").getpixel((0, 0)), (0, 128, 0)) + assert im.size == (10, 10) + assert im.convert("RGB").getpixel((0, 0)) == (0, 128, 0) im.seek(2) im.load() - self.assertEqual(im.size, (20, 20)) - self.assertEqual(im.convert("RGB").getpixel((0, 0)), (0, 0, 255)) + assert im.size == (20, 20) + assert im.convert("RGB").getpixel((0, 0)) == (0, 0, 255) def test_multipage_last_frame(self): with Image.open("Tests/images/multipage-lastframe.tif") as im: im.load() - self.assertEqual(im.size, (20, 20)) - self.assertEqual(im.convert("RGB").getpixel((0, 0)), (0, 0, 255)) + assert im.size == (20, 20) + assert im.convert("RGB").getpixel((0, 0)) == (0, 0, 255) def test___str__(self): filename = "Tests/images/pil136.tiff" @@ -309,7 +317,7 @@ class TestFileTiff(PillowTestCase): ret = str(im.ifd) # Assert - self.assertIsInstance(ret, str) + assert isinstance(ret, str) def test_dict(self): # Arrange @@ -332,7 +340,7 @@ class TestFileTiff(PillowTestCase): 283: 72.0, 284: 1, } - self.assertEqual(dict(im.tag_v2), v2_tags) + assert dict(im.tag_v2) == v2_tags # legacy interface legacy_tags = { @@ -350,7 +358,7 @@ class TestFileTiff(PillowTestCase): 283: ((720000, 10000),), 284: (1,), } - self.assertEqual(dict(im.tag), legacy_tags) + assert dict(im.tag) == legacy_tags def test__delitem__(self): filename = "Tests/images/pil136.tiff" @@ -358,66 +366,72 @@ class TestFileTiff(PillowTestCase): len_before = len(dict(im.ifd)) del im.ifd[256] len_after = len(dict(im.ifd)) - self.assertEqual(len_before, len_after + 1) + assert len_before == len_after + 1 def test_load_byte(self): for legacy_api in [False, True]: ifd = TiffImagePlugin.ImageFileDirectory_v2() data = b"abc" ret = ifd.load_byte(data, legacy_api) - self.assertEqual(ret, b"abc") + assert ret == b"abc" def test_load_string(self): ifd = TiffImagePlugin.ImageFileDirectory_v2() data = b"abc\0" ret = ifd.load_string(data, False) - self.assertEqual(ret, "abc") + assert ret == "abc" def test_load_float(self): ifd = TiffImagePlugin.ImageFileDirectory_v2() data = b"abcdabcd" ret = ifd.load_float(data, False) - self.assertEqual(ret, (1.6777999408082104e22, 1.6777999408082104e22)) + assert ret == (1.6777999408082104e22, 1.6777999408082104e22) def test_load_double(self): ifd = TiffImagePlugin.ImageFileDirectory_v2() data = b"abcdefghabcdefgh" ret = ifd.load_double(data, False) - self.assertEqual(ret, (8.540883223036124e194, 8.540883223036124e194)) + assert ret == (8.540883223036124e194, 8.540883223036124e194) + + def test_ifd_tag_type(self): + with Image.open("Tests/images/ifd_tag_type.tiff") as im: + assert 0x8825 in im.tag_v2 def test_seek(self): filename = "Tests/images/pil136.tiff" with Image.open(filename) as im: im.seek(0) - self.assertEqual(im.tell(), 0) + assert im.tell() == 0 def test_seek_eof(self): filename = "Tests/images/pil136.tiff" with Image.open(filename) as im: - self.assertEqual(im.tell(), 0) - self.assertRaises(EOFError, im.seek, -1) - self.assertRaises(EOFError, im.seek, 1) + assert im.tell() == 0 + with pytest.raises(EOFError): + im.seek(-1) + with pytest.raises(EOFError): + im.seek(1) def test__limit_rational_int(self): from PIL.TiffImagePlugin import _limit_rational value = 34 ret = _limit_rational(value, 65536) - self.assertEqual(ret, (34, 1)) + assert ret == (34, 1) def test__limit_rational_float(self): from PIL.TiffImagePlugin import _limit_rational value = 22.3 ret = _limit_rational(value, 65536) - self.assertEqual(ret, (223, 10)) + assert ret == (223, 10) def test_4bit(self): test_file = "Tests/images/hopper_gray_4bpp.tif" original = hopper("L") with Image.open(test_file) as im: - self.assertEqual(im.size, (128, 128)) - self.assertEqual(im.mode, "L") + assert im.size == (128, 128) + assert im.mode == "L" assert_image_similar(im, original, 7.3) def test_gray_semibyte_per_pixel(self): @@ -444,37 +458,37 @@ class TestFileTiff(PillowTestCase): original = hopper("L") for epsilon, group in test_files: with Image.open(group[0]) as im: - self.assertEqual(im.size, (128, 128)) - self.assertEqual(im.mode, "L") + 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: - self.assertEqual(im2.size, (128, 128)) - self.assertEqual(im2.mode, "L") + assert im2.size == (128, 128) + assert im2.mode == "L" assert_image_equal(im, im2) - def test_with_underscores(self): + def test_with_underscores(self, tmp_path): kwargs = {"resolution_unit": "inch", "x_resolution": 72, "y_resolution": 36} - filename = self.tempfile("temp.tif") + filename = str(tmp_path / "temp.tif") hopper("RGB").save(filename, **kwargs) with Image.open(filename) as im: # legacy interface - self.assertEqual(im.tag[X_RESOLUTION][0][0], 72) - self.assertEqual(im.tag[Y_RESOLUTION][0][0], 36) + assert im.tag[X_RESOLUTION][0][0] == 72 + assert im.tag[Y_RESOLUTION][0][0] == 36 # v2 interface - self.assertEqual(im.tag_v2[X_RESOLUTION], 72) - self.assertEqual(im.tag_v2[Y_RESOLUTION], 36) + assert im.tag_v2[X_RESOLUTION] == 72 + assert im.tag_v2[Y_RESOLUTION] == 36 - def test_roundtrip_tiff_uint16(self): + def test_roundtrip_tiff_uint16(self, tmp_path): # Test an image of all '0' values pixel_value = 0x1234 infile = "Tests/images/uint16_1_4660.tif" with Image.open(infile) as im: - self.assertEqual(im.getpixel((0, 0)), pixel_value) + assert im.getpixel((0, 0)) == pixel_value - tmpfile = self.tempfile("temp.tif") + tmpfile = str(tmp_path / "temp.tif") im.save(tmpfile) with Image.open(tmpfile) as reloaded: @@ -506,9 +520,9 @@ class TestFileTiff(PillowTestCase): with Image.open(infile) as im: assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png") - def test_palette(self): - for mode in ["P", "PA"]: - outfile = self.tempfile("temp.tif") + def test_palette(self, tmp_path): + def roundtrip(mode): + outfile = str(tmp_path / "temp.tif") im = hopper(mode) im.save(outfile) @@ -516,6 +530,9 @@ class TestFileTiff(PillowTestCase): with Image.open(outfile) as reloaded: assert_image_equal(im.convert("RGB"), reloaded.convert("RGB")) + for mode in ["P", "PA"]: + roundtrip(mode) + def test_tiff_save_all(self): mp = BytesIO() with Image.open("Tests/images/multipage.tiff") as im: @@ -523,7 +540,7 @@ class TestFileTiff(PillowTestCase): mp.seek(0, os.SEEK_SET) with Image.open(mp) as im: - self.assertEqual(im.n_frames, 3) + assert im.n_frames == 3 # Test appending images mp = BytesIO() @@ -533,7 +550,7 @@ class TestFileTiff(PillowTestCase): mp.seek(0, os.SEEK_SET) with Image.open(mp) as reread: - self.assertEqual(reread.n_frames, 3) + assert reread.n_frames == 3 # Test appending using a generator def imGenerator(ims): @@ -544,9 +561,9 @@ class TestFileTiff(PillowTestCase): mp.seek(0, os.SEEK_SET) with Image.open(mp) as reread: - self.assertEqual(reread.n_frames, 3) + assert reread.n_frames == 3 - def test_saving_icc_profile(self): + def test_saving_icc_profile(self, tmp_path): # Tests saving TIFF with icc_profile set. # At the time of writing this will only work for non-compressed tiffs # as libtiff does not support embedded ICC profiles, @@ -555,26 +572,26 @@ class TestFileTiff(PillowTestCase): im.info["icc_profile"] = "Dummy value" # Try save-load round trip to make sure both handle icc_profile. - tmpfile = self.tempfile("temp.tif") + tmpfile = str(tmp_path / "temp.tif") im.save(tmpfile, "TIFF", compression="raw") with Image.open(tmpfile) as reloaded: - self.assertEqual(b"Dummy value", reloaded.info["icc_profile"]) + assert b"Dummy value" == reloaded.info["icc_profile"] - def test_close_on_load_exclusive(self): + def test_close_on_load_exclusive(self, tmp_path): # similar to test_fd_leak, but runs on unixlike os - tmpfile = self.tempfile("temp.tif") + tmpfile = str(tmp_path / "temp.tif") with Image.open("Tests/images/uint16_1_4660.tif") as im: im.save(tmpfile) im = Image.open(tmpfile) fp = im.fp - self.assertFalse(fp.closed) + assert not fp.closed im.load() - self.assertTrue(fp.closed) + assert fp.closed - def test_close_on_load_nonexclusive(self): - tmpfile = self.tempfile("temp.tif") + def test_close_on_load_nonexclusive(self, tmp_path): + tmpfile = str(tmp_path / "temp.tif") with Image.open("Tests/images/uint16_1_4660.tif") as im: im.save(tmpfile) @@ -582,23 +599,27 @@ class TestFileTiff(PillowTestCase): with open(tmpfile, "rb") as f: im = Image.open(f) fp = im.fp - self.assertFalse(fp.closed) + assert not fp.closed im.load() - self.assertFalse(fp.closed) + assert not fp.closed # Ignore this UserWarning which triggers for four tags: # "Possibly corrupt EXIF data. Expecting to read 50404352 bytes but..." @pytest.mark.filterwarnings("ignore:Possibly corrupt EXIF data") + @pytest.mark.skipif( + not os.path.exists("Tests/images/string_dimension.tiff"), + reason="Extra image files not installed", + ) def test_string_dimension(self): # Assert that an error is raised if one of the dimensions is a string - with self.assertRaises(ValueError): + with pytest.raises(ValueError): Image.open("Tests/images/string_dimension.tiff") -@unittest.skipUnless(is_win32(), "Windows only") -class TestFileTiffW32(PillowTestCase): - def test_fd_leak(self): - tmpfile = self.tempfile("temp.tif") +@pytest.mark.skipif(not is_win32(), reason="Windows only") +class TestFileTiffW32: + def test_fd_leak(self, tmp_path): + tmpfile = str(tmp_path / "temp.tif") # this is an mmaped file. with Image.open("Tests/images/uint16_1_4660.tif") as im: @@ -606,10 +627,11 @@ class TestFileTiffW32(PillowTestCase): im = Image.open(tmpfile) fp = im.fp - self.assertFalse(fp.closed) - self.assertRaises(WindowsError, os.remove, tmpfile) + assert not fp.closed + with pytest.raises(OSError): + os.remove(tmpfile) im.load() - self.assertTrue(fp.closed) + assert fp.closed # this closes the mmap im.close() diff --git a/Tests/test_file_tiff_metadata.py b/Tests/test_file_tiff_metadata.py index 5a9ca9d63..0f7f8adf1 100644 --- a/Tests/test_file_tiff_metadata.py +++ b/Tests/test_file_tiff_metadata.py @@ -2,336 +2,356 @@ import io import struct import pytest + from PIL import Image, TiffImagePlugin, TiffTags from PIL.TiffImagePlugin import IFDRational -from .helper import PillowTestCase, assert_deep_equal, hopper +from .helper import assert_deep_equal, hopper -tag_ids = {info.name: info.value for info in TiffTags.TAGS_V2.values()} +TAG_IDS = {info.name: info.value for info in TiffTags.TAGS_V2.values()} -class TestFileTiffMetadata(PillowTestCase): - def test_rt_metadata(self): - """ Test writing arbitrary metadata into the tiff image directory - Use case is ImageJ private tags, one numeric, one arbitrary - data. https://github.com/python-pillow/Pillow/issues/291 - """ +def test_rt_metadata(tmp_path): + """Test writing arbitrary metadata into the tiff image directory + Use case is ImageJ private tags, one numeric, one arbitrary + data. https://github.com/python-pillow/Pillow/issues/291 + """ - img = hopper() + img = hopper() - # Behaviour change: re #1416 - # Pre ifd rewrite, ImageJMetaData was being written as a string(2), - # Post ifd rewrite, it's defined as arbitrary bytes(7). It should - # roundtrip with the actual bytes, rather than stripped text - # of the premerge tests. - # - # For text items, we still have to decode('ascii','replace') because - # the tiff file format can't take 8 bit bytes in that field. + # Behaviour change: re #1416 + # Pre ifd rewrite, ImageJMetaData was being written as a string(2), + # Post ifd rewrite, it's defined as arbitrary bytes(7). It should + # roundtrip with the actual bytes, rather than stripped text + # of the premerge tests. + # + # For text items, we still have to decode('ascii','replace') because + # the tiff file format can't take 8 bit bytes in that field. - basetextdata = "This is some arbitrary metadata for a text field" - bindata = basetextdata.encode("ascii") + b" \xff" - textdata = basetextdata + " " + chr(255) - reloaded_textdata = basetextdata + " ?" - floatdata = 12.345 - doubledata = 67.89 - info = TiffImagePlugin.ImageFileDirectory() + basetextdata = "This is some arbitrary metadata for a text field" + bindata = basetextdata.encode("ascii") + b" \xff" + textdata = basetextdata + " " + chr(255) + reloaded_textdata = basetextdata + " ?" + floatdata = 12.345 + doubledata = 67.89 + info = TiffImagePlugin.ImageFileDirectory() - ImageJMetaData = tag_ids["ImageJMetaData"] - ImageJMetaDataByteCounts = tag_ids["ImageJMetaDataByteCounts"] - ImageDescription = tag_ids["ImageDescription"] + ImageJMetaData = TAG_IDS["ImageJMetaData"] + ImageJMetaDataByteCounts = TAG_IDS["ImageJMetaDataByteCounts"] + ImageDescription = TAG_IDS["ImageDescription"] - info[ImageJMetaDataByteCounts] = len(bindata) - info[ImageJMetaData] = bindata - info[tag_ids["RollAngle"]] = floatdata - info.tagtype[tag_ids["RollAngle"]] = 11 - info[tag_ids["YawAngle"]] = doubledata - info.tagtype[tag_ids["YawAngle"]] = 12 + info[ImageJMetaDataByteCounts] = len(bindata) + info[ImageJMetaData] = bindata + info[TAG_IDS["RollAngle"]] = floatdata + info.tagtype[TAG_IDS["RollAngle"]] = 11 + info[TAG_IDS["YawAngle"]] = doubledata + info.tagtype[TAG_IDS["YawAngle"]] = 12 - info[ImageDescription] = textdata + info[ImageDescription] = textdata - f = self.tempfile("temp.tif") + f = str(tmp_path / "temp.tif") - img.save(f, tiffinfo=info) + img.save(f, tiffinfo=info) - with Image.open(f) as loaded: + with Image.open(f) as loaded: - self.assertEqual(loaded.tag[ImageJMetaDataByteCounts], (len(bindata),)) - self.assertEqual(loaded.tag_v2[ImageJMetaDataByteCounts], (len(bindata),)) + assert loaded.tag[ImageJMetaDataByteCounts] == (len(bindata),) + assert loaded.tag_v2[ImageJMetaDataByteCounts] == (len(bindata),) - self.assertEqual(loaded.tag[ImageJMetaData], bindata) - self.assertEqual(loaded.tag_v2[ImageJMetaData], bindata) + assert loaded.tag[ImageJMetaData] == bindata + assert loaded.tag_v2[ImageJMetaData] == bindata - self.assertEqual(loaded.tag[ImageDescription], (reloaded_textdata,)) - self.assertEqual(loaded.tag_v2[ImageDescription], reloaded_textdata) + assert loaded.tag[ImageDescription] == (reloaded_textdata,) + assert loaded.tag_v2[ImageDescription] == reloaded_textdata - loaded_float = loaded.tag[tag_ids["RollAngle"]][0] - self.assertAlmostEqual(loaded_float, floatdata, places=5) - loaded_double = loaded.tag[tag_ids["YawAngle"]][0] - self.assertAlmostEqual(loaded_double, doubledata) + loaded_float = loaded.tag[TAG_IDS["RollAngle"]][0] + assert round(abs(loaded_float - floatdata), 5) == 0 + loaded_double = loaded.tag[TAG_IDS["YawAngle"]][0] + assert round(abs(loaded_double - doubledata), 7) == 0 - # check with 2 element ImageJMetaDataByteCounts, issue #2006 + # check with 2 element ImageJMetaDataByteCounts, issue #2006 - info[ImageJMetaDataByteCounts] = (8, len(bindata) - 8) - img.save(f, tiffinfo=info) - with Image.open(f) as loaded: + info[ImageJMetaDataByteCounts] = (8, len(bindata) - 8) + img.save(f, tiffinfo=info) + with Image.open(f) as loaded: - self.assertEqual( - loaded.tag[ImageJMetaDataByteCounts], (8, len(bindata) - 8) - ) - self.assertEqual( - loaded.tag_v2[ImageJMetaDataByteCounts], (8, len(bindata) - 8) + assert loaded.tag[ImageJMetaDataByteCounts] == (8, len(bindata) - 8) + assert loaded.tag_v2[ImageJMetaDataByteCounts] == (8, len(bindata) - 8) + + +def test_read_metadata(): + with Image.open("Tests/images/hopper_g4.tif") as img: + + assert { + "YResolution": IFDRational(4294967295, 113653537), + "PlanarConfiguration": 1, + "BitsPerSample": (1,), + "ImageLength": 128, + "Compression": 4, + "FillOrder": 1, + "RowsPerStrip": 128, + "ResolutionUnit": 3, + "PhotometricInterpretation": 0, + "PageNumber": (0, 1), + "XResolution": IFDRational(4294967295, 113653537), + "ImageWidth": 128, + "Orientation": 1, + "StripByteCounts": (1968,), + "SamplesPerPixel": 1, + "StripOffsets": (8,), + } == img.tag_v2.named() + + assert { + "YResolution": ((4294967295, 113653537),), + "PlanarConfiguration": (1,), + "BitsPerSample": (1,), + "ImageLength": (128,), + "Compression": (4,), + "FillOrder": (1,), + "RowsPerStrip": (128,), + "ResolutionUnit": (3,), + "PhotometricInterpretation": (0,), + "PageNumber": (0, 1), + "XResolution": ((4294967295, 113653537),), + "ImageWidth": (128,), + "Orientation": (1,), + "StripByteCounts": (1968,), + "SamplesPerPixel": (1,), + "StripOffsets": (8,), + } == img.tag.named() + + +def test_write_metadata(tmp_path): + """ Test metadata writing through the python code """ + with Image.open("Tests/images/hopper.tif") as img: + f = str(tmp_path / "temp.tiff") + img.save(f, tiffinfo=img.tag) + + original = img.tag_v2.named() + + with Image.open(f) as loaded: + reloaded = loaded.tag_v2.named() + + ignored = ["StripByteCounts", "RowsPerStrip", "PageNumber", "StripOffsets"] + + for tag, value in reloaded.items(): + if tag in ignored: + continue + if isinstance(original[tag], tuple) and isinstance( + original[tag][0], IFDRational + ): + # Need to compare element by element in the tuple, + # not comparing tuples of object references + assert_deep_equal( + original[tag], + value, + f"{tag} didn't roundtrip, {original[tag]}, {value}", ) + else: + assert ( + original[tag] == value + ), f"{tag} didn't roundtrip, {original[tag]}, {value}" - def test_read_metadata(self): - with Image.open("Tests/images/hopper_g4.tif") as img: + for tag, value in original.items(): + if tag not in ignored: + assert value == reloaded[tag], f"{tag} didn't roundtrip" - self.assertEqual( - { - "YResolution": IFDRational(4294967295, 113653537), - "PlanarConfiguration": 1, - "BitsPerSample": (1,), - "ImageLength": 128, - "Compression": 4, - "FillOrder": 1, - "RowsPerStrip": 128, - "ResolutionUnit": 3, - "PhotometricInterpretation": 0, - "PageNumber": (0, 1), - "XResolution": IFDRational(4294967295, 113653537), - "ImageWidth": 128, - "Orientation": 1, - "StripByteCounts": (1968,), - "SamplesPerPixel": 1, - "StripOffsets": (8,), - }, - img.tag_v2.named(), - ) - self.assertEqual( - { - "YResolution": ((4294967295, 113653537),), - "PlanarConfiguration": (1,), - "BitsPerSample": (1,), - "ImageLength": (128,), - "Compression": (4,), - "FillOrder": (1,), - "RowsPerStrip": (128,), - "ResolutionUnit": (3,), - "PhotometricInterpretation": (0,), - "PageNumber": (0, 1), - "XResolution": ((4294967295, 113653537),), - "ImageWidth": (128,), - "Orientation": (1,), - "StripByteCounts": (1968,), - "SamplesPerPixel": (1,), - "StripOffsets": (8,), - }, - img.tag.named(), - ) +def test_change_stripbytecounts_tag_type(tmp_path): + out = str(tmp_path / "temp.tiff") + with Image.open("Tests/images/hopper.tif") as im: + info = im.tag_v2 - def test_write_metadata(self): - """ Test metadata writing through the python code """ - with Image.open("Tests/images/hopper.tif") as img: - f = self.tempfile("temp.tiff") - img.save(f, tiffinfo=img.tag) + # Resize the image so that STRIPBYTECOUNTS will be larger than a SHORT + im = im.resize((500, 500)) - original = img.tag_v2.named() + # STRIPBYTECOUNTS can be a SHORT or a LONG + info.tagtype[TiffImagePlugin.STRIPBYTECOUNTS] = TiffTags.SHORT - with Image.open(f) as loaded: - reloaded = loaded.tag_v2.named() + im.save(out, tiffinfo=info) - ignored = ["StripByteCounts", "RowsPerStrip", "PageNumber", "StripOffsets"] + with Image.open(out) as reloaded: + assert reloaded.tag_v2.tagtype[TiffImagePlugin.STRIPBYTECOUNTS] == TiffTags.LONG - for tag, value in reloaded.items(): - if tag in ignored: - continue - if isinstance(original[tag], tuple) and isinstance( - original[tag][0], IFDRational - ): - # Need to compare element by element in the tuple, - # not comparing tuples of object references - assert_deep_equal( - original[tag], - value, - "{} didn't roundtrip, {}, {}".format(tag, original[tag], value), - ) - else: - self.assertEqual( - original[tag], - value, - "{} didn't roundtrip, {}, {}".format(tag, original[tag], value), - ) - for tag, value in original.items(): - if tag not in ignored: - self.assertEqual(value, reloaded[tag], "%s didn't roundtrip" % tag) +def test_no_duplicate_50741_tag(): + assert TAG_IDS["MakerNoteSafety"] == 50741 + assert TAG_IDS["BestQualityScale"] == 50780 - def test_no_duplicate_50741_tag(self): - self.assertEqual(tag_ids["MakerNoteSafety"], 50741) - self.assertEqual(tag_ids["BestQualityScale"], 50780) - def test_empty_metadata(self): - f = io.BytesIO(b"II*\x00\x08\x00\x00\x00") - head = f.read(8) - info = TiffImagePlugin.ImageFileDirectory(head) - # Should not raise struct.error. - pytest.warns(UserWarning, info.load, f) +def test_empty_metadata(): + f = io.BytesIO(b"II*\x00\x08\x00\x00\x00") + head = f.read(8) + info = TiffImagePlugin.ImageFileDirectory(head) + # Should not raise struct.error. + pytest.warns(UserWarning, info.load, f) - def test_iccprofile(self): - # https://github.com/python-pillow/Pillow/issues/1462 - out = self.tempfile("temp.tiff") - with Image.open("Tests/images/hopper.iccprofile.tif") as im: - im.save(out) - with Image.open(out) as reloaded: - self.assertNotIsInstance(im.info["icc_profile"], tuple) - self.assertEqual(im.info["icc_profile"], reloaded.info["icc_profile"]) +def test_iccprofile(tmp_path): + # https://github.com/python-pillow/Pillow/issues/1462 + out = str(tmp_path / "temp.tiff") + with Image.open("Tests/images/hopper.iccprofile.tif") as im: + im.save(out) - def test_iccprofile_binary(self): - # https://github.com/python-pillow/Pillow/issues/1526 - # We should be able to load this, - # but probably won't be able to save it. + with Image.open(out) as reloaded: + assert not isinstance(im.info["icc_profile"], tuple) + assert im.info["icc_profile"] == reloaded.info["icc_profile"] - with Image.open("Tests/images/hopper.iccprofile_binary.tif") as im: - self.assertEqual(im.tag_v2.tagtype[34675], 1) - self.assertTrue(im.info["icc_profile"]) - def test_iccprofile_save_png(self): - with Image.open("Tests/images/hopper.iccprofile.tif") as im: - outfile = self.tempfile("temp.png") - im.save(outfile) +def test_iccprofile_binary(): + # https://github.com/python-pillow/Pillow/issues/1526 + # We should be able to load this, + # but probably won't be able to save it. - def test_iccprofile_binary_save_png(self): - with Image.open("Tests/images/hopper.iccprofile_binary.tif") as im: - outfile = self.tempfile("temp.png") - im.save(outfile) + with Image.open("Tests/images/hopper.iccprofile_binary.tif") as im: + assert im.tag_v2.tagtype[34675] == 1 + assert im.info["icc_profile"] - def test_exif_div_zero(self): - im = hopper() - info = TiffImagePlugin.ImageFileDirectory_v2() - info[41988] = TiffImagePlugin.IFDRational(0, 0) - out = self.tempfile("temp.tiff") - im.save(out, tiffinfo=info, compression="raw") +def test_iccprofile_save_png(tmp_path): + with Image.open("Tests/images/hopper.iccprofile.tif") as im: + outfile = str(tmp_path / "temp.png") + im.save(outfile) - with Image.open(out) as reloaded: - self.assertEqual(0, reloaded.tag_v2[41988].numerator) - self.assertEqual(0, reloaded.tag_v2[41988].denominator) - def test_ifd_unsigned_rational(self): - im = hopper() - info = TiffImagePlugin.ImageFileDirectory_v2() +def test_iccprofile_binary_save_png(tmp_path): + with Image.open("Tests/images/hopper.iccprofile_binary.tif") as im: + outfile = str(tmp_path / "temp.png") + im.save(outfile) - max_long = 2 ** 32 - 1 - # 4 bytes unsigned long - numerator = max_long +def test_exif_div_zero(tmp_path): + im = hopper() + info = TiffImagePlugin.ImageFileDirectory_v2() + info[41988] = TiffImagePlugin.IFDRational(0, 0) - info[41493] = TiffImagePlugin.IFDRational(numerator, 1) + out = str(tmp_path / "temp.tiff") + im.save(out, tiffinfo=info, compression="raw") - out = self.tempfile("temp.tiff") - im.save(out, tiffinfo=info, compression="raw") + with Image.open(out) as reloaded: + assert 0 == reloaded.tag_v2[41988].numerator + assert 0 == reloaded.tag_v2[41988].denominator - with Image.open(out) as reloaded: - self.assertEqual(max_long, reloaded.tag_v2[41493].numerator) - self.assertEqual(1, reloaded.tag_v2[41493].denominator) - # out of bounds of 4 byte unsigned long - numerator = max_long + 1 +def test_ifd_unsigned_rational(tmp_path): + im = hopper() + info = TiffImagePlugin.ImageFileDirectory_v2() - info[41493] = TiffImagePlugin.IFDRational(numerator, 1) + max_long = 2 ** 32 - 1 - out = self.tempfile("temp.tiff") - im.save(out, tiffinfo=info, compression="raw") + # 4 bytes unsigned long + numerator = max_long - with Image.open(out) as reloaded: - self.assertEqual(max_long, reloaded.tag_v2[41493].numerator) - self.assertEqual(1, reloaded.tag_v2[41493].denominator) + info[41493] = TiffImagePlugin.IFDRational(numerator, 1) - def test_ifd_signed_rational(self): - im = hopper() - info = TiffImagePlugin.ImageFileDirectory_v2() + out = str(tmp_path / "temp.tiff") + im.save(out, tiffinfo=info, compression="raw") - # pair of 4 byte signed longs - numerator = 2 ** 31 - 1 - denominator = -(2 ** 31) + with Image.open(out) as reloaded: + assert max_long == reloaded.tag_v2[41493].numerator + assert 1 == reloaded.tag_v2[41493].denominator - info[37380] = TiffImagePlugin.IFDRational(numerator, denominator) + # out of bounds of 4 byte unsigned long + numerator = max_long + 1 - out = self.tempfile("temp.tiff") - im.save(out, tiffinfo=info, compression="raw") + info[41493] = TiffImagePlugin.IFDRational(numerator, 1) - with Image.open(out) as reloaded: - self.assertEqual(numerator, reloaded.tag_v2[37380].numerator) - self.assertEqual(denominator, reloaded.tag_v2[37380].denominator) + out = str(tmp_path / "temp.tiff") + im.save(out, tiffinfo=info, compression="raw") - numerator = -(2 ** 31) - denominator = 2 ** 31 - 1 + with Image.open(out) as reloaded: + assert max_long == reloaded.tag_v2[41493].numerator + assert 1 == reloaded.tag_v2[41493].denominator - info[37380] = TiffImagePlugin.IFDRational(numerator, denominator) - out = self.tempfile("temp.tiff") - im.save(out, tiffinfo=info, compression="raw") +def test_ifd_signed_rational(tmp_path): + im = hopper() + info = TiffImagePlugin.ImageFileDirectory_v2() - with Image.open(out) as reloaded: - self.assertEqual(numerator, reloaded.tag_v2[37380].numerator) - self.assertEqual(denominator, reloaded.tag_v2[37380].denominator) + # pair of 4 byte signed longs + numerator = 2 ** 31 - 1 + denominator = -(2 ** 31) - # out of bounds of 4 byte signed long - numerator = -(2 ** 31) - 1 - denominator = 1 + info[37380] = TiffImagePlugin.IFDRational(numerator, denominator) - info[37380] = TiffImagePlugin.IFDRational(numerator, denominator) + out = str(tmp_path / "temp.tiff") + im.save(out, tiffinfo=info, compression="raw") - out = self.tempfile("temp.tiff") - im.save(out, tiffinfo=info, compression="raw") + with Image.open(out) as reloaded: + assert numerator == reloaded.tag_v2[37380].numerator + assert denominator == reloaded.tag_v2[37380].denominator - with Image.open(out) as reloaded: - self.assertEqual(2 ** 31 - 1, reloaded.tag_v2[37380].numerator) - self.assertEqual(-1, reloaded.tag_v2[37380].denominator) + numerator = -(2 ** 31) + denominator = 2 ** 31 - 1 - def test_ifd_signed_long(self): - im = hopper() - info = TiffImagePlugin.ImageFileDirectory_v2() + info[37380] = TiffImagePlugin.IFDRational(numerator, denominator) - info[37000] = -60000 + out = str(tmp_path / "temp.tiff") + im.save(out, tiffinfo=info, compression="raw") - out = self.tempfile("temp.tiff") - im.save(out, tiffinfo=info, compression="raw") + with Image.open(out) as reloaded: + assert numerator == reloaded.tag_v2[37380].numerator + assert denominator == reloaded.tag_v2[37380].denominator - with Image.open(out) as reloaded: - self.assertEqual(reloaded.tag_v2[37000], -60000) + # out of bounds of 4 byte signed long + numerator = -(2 ** 31) - 1 + denominator = 1 - def test_empty_values(self): - data = io.BytesIO( - b"II*\x00\x08\x00\x00\x00\x03\x00\x1a\x01\x05\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x1b\x01\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x98\x82\x02\x00\x07\x00\x00\x002\x00\x00\x00\x00\x00\x00\x00a " - b"text\x00\x00" - ) - head = data.read(8) - info = TiffImagePlugin.ImageFileDirectory_v2(head) - info.load(data) - # Should not raise ValueError. - info = dict(info) - self.assertIn(33432, info) + info[37380] = TiffImagePlugin.IFDRational(numerator, denominator) - def test_PhotoshopInfo(self): - with Image.open("Tests/images/issue_2278.tif") as im: - self.assertEqual(len(im.tag_v2[34377]), 1) - self.assertIsInstance(im.tag_v2[34377][0], bytes) - out = self.tempfile("temp.tiff") - im.save(out) - with Image.open(out) as reloaded: - self.assertEqual(len(reloaded.tag_v2[34377]), 1) - self.assertIsInstance(reloaded.tag_v2[34377][0], bytes) + out = str(tmp_path / "temp.tiff") + im.save(out, tiffinfo=info, compression="raw") - def test_too_many_entries(self): - ifd = TiffImagePlugin.ImageFileDirectory_v2() + with Image.open(out) as reloaded: + assert 2 ** 31 - 1 == reloaded.tag_v2[37380].numerator + assert -1 == reloaded.tag_v2[37380].denominator - # 277: ("SamplesPerPixel", SHORT, 1), - ifd._tagdata[277] = struct.pack("hh", 4, 4) - ifd.tagtype[277] = TiffTags.SHORT - # Should not raise ValueError. - pytest.warns(UserWarning, lambda: ifd[277]) +def test_ifd_signed_long(tmp_path): + im = hopper() + info = TiffImagePlugin.ImageFileDirectory_v2() + + info[37000] = -60000 + + out = str(tmp_path / "temp.tiff") + im.save(out, tiffinfo=info, compression="raw") + + with Image.open(out) as reloaded: + assert reloaded.tag_v2[37000] == -60000 + + +def test_empty_values(): + data = io.BytesIO( + b"II*\x00\x08\x00\x00\x00\x03\x00\x1a\x01\x05\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x1b\x01\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x98\x82\x02\x00\x07\x00\x00\x002\x00\x00\x00\x00\x00\x00\x00a " + b"text\x00\x00" + ) + head = data.read(8) + info = TiffImagePlugin.ImageFileDirectory_v2(head) + info.load(data) + # Should not raise ValueError. + info = dict(info) + assert 33432 in info + + +def test_PhotoshopInfo(tmp_path): + with Image.open("Tests/images/issue_2278.tif") as im: + assert len(im.tag_v2[34377]) == 70 + assert isinstance(im.tag_v2[34377], bytes) + out = str(tmp_path / "temp.tiff") + im.save(out) + with Image.open(out) as reloaded: + assert len(reloaded.tag_v2[34377]) == 70 + assert isinstance(reloaded.tag_v2[34377], bytes) + + +def test_too_many_entries(): + ifd = TiffImagePlugin.ImageFileDirectory_v2() + + # 277: ("SamplesPerPixel", SHORT, 1), + ifd._tagdata[277] = struct.pack("hh", 4, 4) + ifd.tagtype[277] = TiffTags.SHORT + + # Should not raise ValueError. + pytest.warns(UserWarning, lambda: ifd[277]) diff --git a/Tests/test_file_webp.py b/Tests/test_file_webp.py index 81742c659..11fbd9fd5 100644 --- a/Tests/test_file_webp.py +++ b/Tests/test_file_webp.py @@ -1,13 +1,15 @@ -import unittest +import io +import re import pytest -from PIL import Image, WebPImagePlugin + +from PIL import Image, WebPImagePlugin, features from .helper import ( - PillowTestCase, assert_image_similar, assert_image_similar_tofile, hopper, + skip_unless_feature, ) try: @@ -18,28 +20,27 @@ except ImportError: HAVE_WEBP = False -class TestUnsupportedWebp(PillowTestCase): +class TestUnsupportedWebp: def test_unsupported(self): if HAVE_WEBP: WebPImagePlugin.SUPPORTED = False file_path = "Tests/images/hopper.webp" - pytest.warns( - UserWarning, lambda: self.assertRaises(IOError, Image.open, file_path) - ) + pytest.warns(UserWarning, lambda: pytest.raises(OSError, Image.open, file_path)) if HAVE_WEBP: WebPImagePlugin.SUPPORTED = True -@unittest.skipUnless(HAVE_WEBP, "WebP support not installed") -class TestFileWebp(PillowTestCase): - def setUp(self): +@skip_unless_feature("webp") +class TestFileWebp: + def setup_method(self): self.rgb_mode = "RGB" def test_version(self): _webp.WebPDecoderVersion() _webp.WebPDecoderBuggyAlpha() + assert re.search(r"\d+\.\d+\.\d+$", features.version_module("webp")) def test_read_rgb(self): """ @@ -48,9 +49,9 @@ class TestFileWebp(PillowTestCase): """ with Image.open("Tests/images/hopper.webp") as image: - self.assertEqual(image.mode, self.rgb_mode) - self.assertEqual(image.size, (128, 128)) - self.assertEqual(image.format, "WEBP") + assert image.mode == self.rgb_mode + assert image.size == (128, 128) + assert image.format == "WEBP" image.load() image.getdata() @@ -58,72 +59,65 @@ class TestFileWebp(PillowTestCase): # dwebp -ppm ../../Tests/images/hopper.webp -o hopper_webp_bits.ppm assert_image_similar_tofile(image, "Tests/images/hopper_webp_bits.ppm", 1.0) - def test_write_rgb(self): - """ - Can we write a RGB mode file to webp without error. - Does it have the bits we expect? - """ + def _roundtrip(self, tmp_path, mode, epsilon, args={}): + temp_file = str(tmp_path / "temp.webp") - temp_file = self.tempfile("temp.webp") - - hopper(self.rgb_mode).save(temp_file) + hopper(mode).save(temp_file, **args) with Image.open(temp_file) as image: - self.assertEqual(image.mode, self.rgb_mode) - self.assertEqual(image.size, (128, 128)) - self.assertEqual(image.format, "WEBP") + assert image.mode == self.rgb_mode + assert image.size == (128, 128) + assert image.format == "WEBP" image.load() image.getdata() - # generated with: dwebp -ppm temp.webp -o hopper_webp_write.ppm - assert_image_similar_tofile( - image, "Tests/images/hopper_webp_write.ppm", 12.0 - ) + if mode == self.rgb_mode: + # generated with: dwebp -ppm temp.webp -o hopper_webp_write.ppm + assert_image_similar_tofile( + image, "Tests/images/hopper_webp_write.ppm", 12.0 + ) # This test asserts that the images are similar. If the average pixel # difference between the two images is less than the epsilon value, # then we're going to accept that it's a reasonable lossy version of - # the image. The old lena images for WebP are showing ~16 on - # Ubuntu, the jpegs are showing ~18. - target = hopper(self.rgb_mode) - assert_image_similar(image, target, 12.0) + # the image. + target = hopper(mode) + if mode != self.rgb_mode: + target = target.convert(self.rgb_mode) + assert_image_similar(image, target, epsilon) - def test_write_unsupported_mode_L(self): + def test_write_rgb(self, tmp_path): + """ + Can we write a RGB mode file to webp without error? + Does it have the bits we expect? + """ + + self._roundtrip(tmp_path, self.rgb_mode, 12.5) + + def test_write_method(self, tmp_path): + self._roundtrip(tmp_path, self.rgb_mode, 12.0, {"method": 6}) + + buffer_no_args = io.BytesIO() + hopper().save(buffer_no_args, format="WEBP") + + buffer_method = io.BytesIO() + hopper().save(buffer_method, format="WEBP", method=6) + assert buffer_no_args.getbuffer() != buffer_method.getbuffer() + + def test_write_unsupported_mode_L(self, tmp_path): """ Saving a black-and-white file to WebP format should work, and be similar to the original file. """ - temp_file = self.tempfile("temp.webp") - hopper("L").save(temp_file) - with Image.open(temp_file) as image: - self.assertEqual(image.mode, self.rgb_mode) - self.assertEqual(image.size, (128, 128)) - self.assertEqual(image.format, "WEBP") + self._roundtrip(tmp_path, "L", 10.0) - image.load() - image.getdata() - target = hopper("L").convert(self.rgb_mode) - - assert_image_similar(image, target, 10.0) - - def test_write_unsupported_mode_P(self): + def test_write_unsupported_mode_P(self, tmp_path): """ Saving a palette-based file to WebP format should work, and be similar to the original file. """ - temp_file = self.tempfile("temp.webp") - hopper("P").save(temp_file) - with Image.open(temp_file) as image: - self.assertEqual(image.mode, self.rgb_mode) - self.assertEqual(image.size, (128, 128)) - self.assertEqual(image.format, "WEBP") - - image.load() - image.getdata() - target = hopper("P").convert(self.rgb_mode) - - assert_image_similar(image, target, 50.0) + self._roundtrip(tmp_path, "P", 50.0) def test_WebPEncode_with_invalid_args(self): """ @@ -131,8 +125,10 @@ class TestFileWebp(PillowTestCase): """ if _webp.HAVE_WEBPANIM: - self.assertRaises(TypeError, _webp.WebPAnimEncoder) - self.assertRaises(TypeError, _webp.WebPEncode) + with pytest.raises(TypeError): + _webp.WebPAnimEncoder() + with pytest.raises(TypeError): + _webp.WebPEncode() def test_WebPDecode_with_invalid_args(self): """ @@ -140,13 +136,15 @@ class TestFileWebp(PillowTestCase): """ if _webp.HAVE_WEBPANIM: - self.assertRaises(TypeError, _webp.WebPAnimDecoder) - self.assertRaises(TypeError, _webp.WebPDecode) + with pytest.raises(TypeError): + _webp.WebPAnimDecoder() + with pytest.raises(TypeError): + _webp.WebPDecode() - def test_no_resource_warning(self): + def test_no_resource_warning(self, tmp_path): file_path = "Tests/images/hopper.webp" with Image.open(file_path) as image: - temp_file = self.tempfile("temp.webp") + temp_file = str(tmp_path / "temp.webp") pytest.warns(None, image.save, temp_file) def test_file_pointer_could_be_reused(self): @@ -155,19 +153,18 @@ class TestFileWebp(PillowTestCase): Image.open(blob).load() Image.open(blob).load() - @unittest.skipUnless( - HAVE_WEBP and _webp.HAVE_WEBPANIM, "WebP save all not available" - ) - def test_background_from_gif(self): + @skip_unless_feature("webp") + @skip_unless_feature("webp_anim") + def test_background_from_gif(self, tmp_path): with Image.open("Tests/images/chi.gif") as im: original_value = im.convert("RGB").getpixel((1, 1)) # Save as WEBP - out_webp = self.tempfile("temp.webp") + out_webp = str(tmp_path / "temp.webp") im.save(out_webp, save_all=True) # Save as GIF - out_gif = self.tempfile("temp.gif") + out_gif = str(tmp_path / "temp.gif") Image.open(out_webp).save(out_gif) with Image.open(out_gif) as reread: @@ -175,4 +172,4 @@ class TestFileWebp(PillowTestCase): difference = sum( [abs(original_value[i] - reread_value[i]) for i in range(0, 3)] ) - self.assertLess(difference, 5) + assert difference < 5 diff --git a/Tests/test_file_webp_alpha.py b/Tests/test_file_webp_alpha.py index 78b552f18..362edac1a 100644 --- a/Tests/test_file_webp_alpha.py +++ b/Tests/test_file_webp_alpha.py @@ -1,119 +1,116 @@ -import unittest +import pytest from PIL import Image -from .helper import PillowTestCase, assert_image_equal, assert_image_similar, hopper +from .helper import assert_image_equal, assert_image_similar, hopper -try: - from PIL import _webp -except ImportError: - _webp = None +_webp = pytest.importorskip("PIL._webp", reason="WebP support not installed") -@unittest.skipIf(_webp is None, "WebP support not installed") -class TestFileWebpAlpha(PillowTestCase): - def setUp(self): - if _webp.WebPDecoderBuggyAlpha(self): - self.skipTest( - "Buggy early version of WebP installed, not testing transparency" - ) +def setup_module(): + if _webp.WebPDecoderBuggyAlpha(): + pytest.skip("Buggy early version of WebP installed, not testing transparency") - def test_read_rgba(self): - """ - Can we read an RGBA mode file without error? - Does it have the bits we expect? - """ - # Generated with `cwebp transparent.png -o transparent.webp` - file_path = "Tests/images/transparent.webp" - with Image.open(file_path) as image: - self.assertEqual(image.mode, "RGBA") - self.assertEqual(image.size, (200, 150)) - self.assertEqual(image.format, "WEBP") - image.load() - image.getdata() +def test_read_rgba(): + """ + Can we read an RGBA mode file without error? + Does it have the bits we expect? + """ - image.tobytes() + # Generated with `cwebp transparent.png -o transparent.webp` + file_path = "Tests/images/transparent.webp" + with Image.open(file_path) as image: + assert image.mode == "RGBA" + assert image.size == (200, 150) + assert image.format == "WEBP" + image.load() + image.getdata() - with Image.open("Tests/images/transparent.png") as target: - assert_image_similar(image, target, 20.0) + image.tobytes() - def test_write_lossless_rgb(self): - """ - Can we write an RGBA mode file with lossless compression without - error? Does it have the bits we expect? - """ + with Image.open("Tests/images/transparent.png") as target: + assert_image_similar(image, target, 20.0) - temp_file = self.tempfile("temp.webp") - # temp_file = "temp.webp" - pil_image = hopper("RGBA") +def test_write_lossless_rgb(tmp_path): + """ + Can we write an RGBA mode file with lossless compression without error? + Does it have the bits we expect? + """ - mask = Image.new("RGBA", (64, 64), (128, 128, 128, 128)) - # Add some partially transparent bits: - pil_image.paste(mask, (0, 0), mask) + temp_file = str(tmp_path / "temp.webp") + # temp_file = "temp.webp" - pil_image.save(temp_file, lossless=True) + pil_image = hopper("RGBA") - with Image.open(temp_file) as image: - image.load() + mask = Image.new("RGBA", (64, 64), (128, 128, 128, 128)) + # Add some partially transparent bits: + pil_image.paste(mask, (0, 0), mask) - self.assertEqual(image.mode, "RGBA") - self.assertEqual(image.size, pil_image.size) - self.assertEqual(image.format, "WEBP") - image.load() - image.getdata() + pil_image.save(temp_file, lossless=True) - assert_image_equal(image, pil_image) + with Image.open(temp_file) as image: + image.load() - def test_write_rgba(self): - """ - Can we write a RGBA mode file to webp without error. - Does it have the bits we expect? - """ + assert image.mode == "RGBA" + assert image.size == pil_image.size + assert image.format == "WEBP" + image.load() + image.getdata() - temp_file = self.tempfile("temp.webp") + assert_image_equal(image, pil_image) - pil_image = Image.new("RGBA", (10, 10), (255, 0, 0, 20)) - pil_image.save(temp_file) - if _webp.WebPDecoderBuggyAlpha(self): - return +def test_write_rgba(tmp_path): + """ + Can we write a RGBA mode file to WebP without error. + Does it have the bits we expect? + """ - with Image.open(temp_file) as image: - image.load() + temp_file = str(tmp_path / "temp.webp") - self.assertEqual(image.mode, "RGBA") - self.assertEqual(image.size, (10, 10)) - self.assertEqual(image.format, "WEBP") - image.load() - image.getdata() + pil_image = Image.new("RGBA", (10, 10), (255, 0, 0, 20)) + pil_image.save(temp_file) - # early versions of webp are known to produce higher deviations: - # deal with it - if _webp.WebPDecoderVersion(self) <= 0x201: - assert_image_similar(image, pil_image, 3.0) - else: - assert_image_similar(image, pil_image, 1.0) + if _webp.WebPDecoderBuggyAlpha(): + return - def test_write_unsupported_mode_PA(self): - """ - Saving a palette-based file with transparency to WebP format - should work, and be similar to the original file. - """ + with Image.open(temp_file) as image: + image.load() - temp_file = self.tempfile("temp.webp") - file_path = "Tests/images/transparent.gif" + assert image.mode == "RGBA" + assert image.size == (10, 10) + assert image.format == "WEBP" + image.load() + image.getdata() + + # Early versions of WebP are known to produce higher deviations: + # deal with it + if _webp.WebPDecoderVersion() <= 0x201: + assert_image_similar(image, pil_image, 3.0) + else: + assert_image_similar(image, pil_image, 1.0) + + +def test_write_unsupported_mode_PA(tmp_path): + """ + Saving a palette-based file with transparency to WebP format + should work, and be similar to the original file. + """ + + temp_file = str(tmp_path / "temp.webp") + file_path = "Tests/images/transparent.gif" + with Image.open(file_path) as im: + im.save(temp_file) + with Image.open(temp_file) as image: + assert image.mode == "RGBA" + assert image.size == (200, 150) + assert image.format == "WEBP" + + image.load() + image.getdata() with Image.open(file_path) as im: - im.save(temp_file) - with Image.open(temp_file) as image: - self.assertEqual(image.mode, "RGBA") - self.assertEqual(image.size, (200, 150)) - self.assertEqual(image.format, "WEBP") + target = im.convert("RGBA") - image.load() - image.getdata() - with Image.open(file_path) as im: - target = im.convert("RGBA") - - assert_image_similar(image, target, 25.0) + assert_image_similar(image, target, 25.0) diff --git a/Tests/test_file_webp_animated.py b/Tests/test_file_webp_animated.py index 3aab61e79..26e903488 100644 --- a/Tests/test_file_webp_animated.py +++ b/Tests/test_file_webp_animated.py @@ -1,180 +1,166 @@ import pytest + from PIL import Image from .helper import ( - PillowTestCase, assert_image_equal, assert_image_similar, is_big_endian, - on_ci, + skip_unless_feature, ) -try: - from PIL import _webp - - HAVE_WEBP = True -except ImportError: - HAVE_WEBP = False +pytestmark = [ + skip_unless_feature("webp"), + skip_unless_feature("webp_anim"), +] -class TestFileWebpAnimation(PillowTestCase): - def setUp(self): - if not HAVE_WEBP: - self.skipTest("WebP support not installed") - return +def test_n_frames(): + """Ensure that WebP format sets n_frames and is_animated attributes correctly.""" - if not _webp.HAVE_WEBPANIM: - self.skipTest( - "WebP library does not contain animation support, " - "not testing animation" + with Image.open("Tests/images/hopper.webp") as im: + assert im.n_frames == 1 + assert not im.is_animated + + with Image.open("Tests/images/iss634.webp") as im: + assert im.n_frames == 42 + assert im.is_animated + + +@pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian") +def test_write_animation_L(tmp_path): + """ + Convert an animated GIF to animated WebP, then compare the frame count, and first + and last frames to ensure they're visually similar. + """ + + with Image.open("Tests/images/iss634.gif") as orig: + assert orig.n_frames > 1 + + temp_file = str(tmp_path / "temp.webp") + orig.save(temp_file, save_all=True) + with Image.open(temp_file) as im: + assert im.n_frames == orig.n_frames + + # Compare first and last frames to the original animated GIF + orig.load() + im.load() + assert_image_similar(im, orig.convert("RGBA"), 25.0) + orig.seek(orig.n_frames - 1) + im.seek(im.n_frames - 1) + orig.load() + im.load() + assert_image_similar(im, orig.convert("RGBA"), 25.0) + + +@pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian") +def test_write_animation_RGB(tmp_path): + """ + Write an animated WebP from RGB frames, and ensure the frames + are visually similar to the originals. + """ + + def check(temp_file): + with Image.open(temp_file) as im: + assert im.n_frames == 2 + + # Compare first frame to original + im.load() + assert_image_equal(im, frame1.convert("RGBA")) + + # Compare second frame to original + im.seek(1) + im.load() + assert_image_equal(im, frame2.convert("RGBA")) + + with Image.open("Tests/images/anim_frame1.webp") as frame1: + with Image.open("Tests/images/anim_frame2.webp") as frame2: + temp_file1 = str(tmp_path / "temp.webp") + frame1.copy().save( + temp_file1, save_all=True, append_images=[frame2], lossless=True + ) + check(temp_file1) + + # Tests appending using a generator + def imGenerator(ims): + yield from ims + + temp_file2 = str(tmp_path / "temp_generator.webp") + frame1.copy().save( + temp_file2, + save_all=True, + append_images=imGenerator([frame2]), + lossless=True, + ) + check(temp_file2) + + +def test_timestamp_and_duration(tmp_path): + """ + Try passing a list of durations, and make sure the encoded + timestamps and durations are correct. + """ + + durations = [0, 10, 20, 30, 40] + temp_file = str(tmp_path / "temp.webp") + with Image.open("Tests/images/anim_frame1.webp") as frame1: + with Image.open("Tests/images/anim_frame2.webp") as frame2: + frame1.save( + temp_file, + save_all=True, + append_images=[frame2, frame1, frame2, frame1], + duration=durations, ) - def test_n_frames(self): - """ - Ensure that WebP format sets n_frames and is_animated - attributes correctly. - """ + with Image.open(temp_file) as im: + assert im.n_frames == 5 + assert im.is_animated - with Image.open("Tests/images/hopper.webp") as im: - self.assertEqual(im.n_frames, 1) - self.assertFalse(im.is_animated) + # Check that timestamps and durations match original values specified + ts = 0 + for frame in range(im.n_frames): + im.seek(frame) + im.load() + assert im.info["duration"] == durations[frame] + assert im.info["timestamp"] == ts + ts += durations[frame] - with Image.open("Tests/images/iss634.webp") as im: - self.assertEqual(im.n_frames, 42) - self.assertTrue(im.is_animated) - @pytest.mark.xfail(is_big_endian() and on_ci(), reason="Fails on big-endian") - def test_write_animation_L(self): - """ - Convert an animated GIF to animated WebP, then compare the - frame count, and first and last frames to ensure they're - visually similar. - """ +def test_seeking(tmp_path): + """ + Create an animated WebP file, and then try seeking through frames in reverse-order, + verifying the timestamps and durations are correct. + """ - with Image.open("Tests/images/iss634.gif") as orig: - self.assertGreater(orig.n_frames, 1) + dur = 33 + temp_file = str(tmp_path / "temp.webp") + with Image.open("Tests/images/anim_frame1.webp") as frame1: + with Image.open("Tests/images/anim_frame2.webp") as frame2: + frame1.save( + temp_file, + save_all=True, + append_images=[frame2, frame1, frame2, frame1], + duration=dur, + ) - temp_file = self.tempfile("temp.webp") - orig.save(temp_file, save_all=True) - with Image.open(temp_file) as im: - self.assertEqual(im.n_frames, orig.n_frames) + with Image.open(temp_file) as im: + assert im.n_frames == 5 + assert im.is_animated - # Compare first and last frames to the original animated GIF - orig.load() - im.load() - assert_image_similar(im, orig.convert("RGBA"), 25.0) - orig.seek(orig.n_frames - 1) - im.seek(im.n_frames - 1) - orig.load() - im.load() - assert_image_similar(im, orig.convert("RGBA"), 25.0) + # Traverse frames in reverse, checking timestamps and durations + ts = dur * (im.n_frames - 1) + for frame in reversed(range(im.n_frames)): + im.seek(frame) + im.load() + assert im.info["duration"] == dur + assert im.info["timestamp"] == ts + ts -= dur - @pytest.mark.xfail(is_big_endian() and on_ci(), reason="Fails on big-endian") - def test_write_animation_RGB(self): - """ - Write an animated WebP from RGB frames, and ensure the frames - are visually similar to the originals. - """ - def check(temp_file): - with Image.open(temp_file) as im: - self.assertEqual(im.n_frames, 2) +def test_seek_errors(): + with Image.open("Tests/images/iss634.webp") as im: + with pytest.raises(EOFError): + im.seek(-1) - # Compare first frame to original - im.load() - assert_image_equal(im, frame1.convert("RGBA")) - - # Compare second frame to original - im.seek(1) - im.load() - assert_image_equal(im, frame2.convert("RGBA")) - - with Image.open("Tests/images/anim_frame1.webp") as frame1: - with Image.open("Tests/images/anim_frame2.webp") as frame2: - temp_file1 = self.tempfile("temp.webp") - frame1.copy().save( - temp_file1, save_all=True, append_images=[frame2], lossless=True - ) - check(temp_file1) - - # Tests appending using a generator - def imGenerator(ims): - yield from ims - - temp_file2 = self.tempfile("temp_generator.webp") - frame1.copy().save( - temp_file2, - save_all=True, - append_images=imGenerator([frame2]), - lossless=True, - ) - check(temp_file2) - - def test_timestamp_and_duration(self): - """ - Try passing a list of durations, and make sure the encoded - timestamps and durations are correct. - """ - - durations = [0, 10, 20, 30, 40] - temp_file = self.tempfile("temp.webp") - with Image.open("Tests/images/anim_frame1.webp") as frame1: - with Image.open("Tests/images/anim_frame2.webp") as frame2: - frame1.save( - temp_file, - save_all=True, - append_images=[frame2, frame1, frame2, frame1], - duration=durations, - ) - - with Image.open(temp_file) as im: - self.assertEqual(im.n_frames, 5) - self.assertTrue(im.is_animated) - - # Check that timestamps and durations match original values specified - ts = 0 - for frame in range(im.n_frames): - im.seek(frame) - im.load() - self.assertEqual(im.info["duration"], durations[frame]) - self.assertEqual(im.info["timestamp"], ts) - ts += durations[frame] - - def test_seeking(self): - """ - Create an animated WebP file, and then try seeking through - frames in reverse-order, verifying the timestamps and durations - are correct. - """ - - dur = 33 - temp_file = self.tempfile("temp.webp") - with Image.open("Tests/images/anim_frame1.webp") as frame1: - with Image.open("Tests/images/anim_frame2.webp") as frame2: - frame1.save( - temp_file, - save_all=True, - append_images=[frame2, frame1, frame2, frame1], - duration=dur, - ) - - with Image.open(temp_file) as im: - self.assertEqual(im.n_frames, 5) - self.assertTrue(im.is_animated) - - # Traverse frames in reverse, checking timestamps and durations - ts = dur * (im.n_frames - 1) - for frame in reversed(range(im.n_frames)): - im.seek(frame) - im.load() - self.assertEqual(im.info["duration"], dur) - self.assertEqual(im.info["timestamp"], ts) - ts -= dur - - def test_seek_errors(self): - with Image.open("Tests/images/iss634.webp") as im: - with self.assertRaises(EOFError): - im.seek(-1) - - with self.assertRaises(EOFError): - im.seek(42) + with pytest.raises(EOFError): + im.seek(42) diff --git a/Tests/test_file_webp_lossless.py b/Tests/test_file_webp_lossless.py index 4c48237af..2da443628 100644 --- a/Tests/test_file_webp_lossless.py +++ b/Tests/test_file_webp_lossless.py @@ -1,38 +1,28 @@ +import pytest + from PIL import Image -from .helper import PillowTestCase, assert_image_equal, hopper +from .helper import assert_image_equal, hopper -try: - from PIL import _webp - - HAVE_WEBP = True -except ImportError: - HAVE_WEBP = False +_webp = pytest.importorskip("PIL._webp", reason="WebP support not installed") +RGB_MODE = "RGB" -class TestFileWebpLossless(PillowTestCase): - def setUp(self): - if not HAVE_WEBP: - self.skipTest("WebP support not installed") - return +def test_write_lossless_rgb(tmp_path): + if _webp.WebPDecoderVersion() < 0x0200: + pytest.skip("lossless not included") - if _webp.WebPDecoderVersion() < 0x0200: - self.skipTest("lossless not included") + temp_file = str(tmp_path / "temp.webp") - self.rgb_mode = "RGB" + hopper(RGB_MODE).save(temp_file, lossless=True) - def test_write_lossless_rgb(self): - temp_file = self.tempfile("temp.webp") + with Image.open(temp_file) as image: + image.load() - hopper(self.rgb_mode).save(temp_file, lossless=True) + assert image.mode == RGB_MODE + assert image.size == (128, 128) + assert image.format == "WEBP" + image.load() + image.getdata() - with Image.open(temp_file) as image: - image.load() - - self.assertEqual(image.mode, self.rgb_mode) - self.assertEqual(image.size, (128, 128)) - self.assertEqual(image.format, "WEBP") - image.load() - image.getdata() - - assert_image_equal(image, hopper(self.rgb_mode)) + assert_image_equal(image, hopper(RGB_MODE)) diff --git a/Tests/test_file_webp_metadata.py b/Tests/test_file_webp_metadata.py index 06c780299..a2a05f96b 100644 --- a/Tests/test_file_webp_metadata.py +++ b/Tests/test_file_webp_metadata.py @@ -2,128 +2,127 @@ from io import BytesIO from PIL import Image -from .helper import PillowTestCase +from .helper import skip_unless_feature -try: - from PIL import _webp - - HAVE_WEBP = True -except ImportError: - HAVE_WEBP = False +pytestmark = [ + skip_unless_feature("webp"), + skip_unless_feature("webp_mux"), +] -class TestFileWebpMetadata(PillowTestCase): - def setUp(self): - if not HAVE_WEBP: - self.skipTest("WebP support not installed") - return +def test_read_exif_metadata(): - if not _webp.HAVE_WEBPMUX: - self.skipTest("WebPMux support not installed") + file_path = "Tests/images/flower.webp" + with Image.open(file_path) as image: - def test_read_exif_metadata(self): + assert image.format == "WEBP" + exif_data = image.info.get("exif", None) + assert exif_data - file_path = "Tests/images/flower.webp" - with Image.open(file_path) as image: + exif = image._getexif() - self.assertEqual(image.format, "WEBP") - exif_data = image.info.get("exif", None) - self.assertTrue(exif_data) + # Camera make + assert exif[271] == "Canon" - exif = image._getexif() + with Image.open("Tests/images/flower.jpg") as jpeg_image: + expected_exif = jpeg_image.info["exif"] - # camera make - self.assertEqual(exif[271], "Canon") + assert exif_data == expected_exif - with Image.open("Tests/images/flower.jpg") as jpeg_image: - expected_exif = jpeg_image.info["exif"] - self.assertEqual(exif_data, expected_exif) +def test_read_exif_metadata_without_prefix(): + with Image.open("Tests/images/flower2.webp") as im: + # Assert prefix is not present + assert im.info["exif"][:6] != b"Exif\x00\x00" - def test_write_exif_metadata(self): - file_path = "Tests/images/flower.jpg" - test_buffer = BytesIO() - with Image.open(file_path) as image: - expected_exif = image.info["exif"] + exif = im.getexif() + assert exif[305] == "Adobe Photoshop CS6 (Macintosh)" - image.save(test_buffer, "webp", exif=expected_exif) - test_buffer.seek(0) - with Image.open(test_buffer) as webp_image: - webp_exif = webp_image.info.get("exif", None) - self.assertTrue(webp_exif) - if webp_exif: - self.assertEqual(webp_exif, expected_exif, "WebP EXIF didn't match") +def test_write_exif_metadata(): + file_path = "Tests/images/flower.jpg" + test_buffer = BytesIO() + with Image.open(file_path) as image: + expected_exif = image.info["exif"] - def test_read_icc_profile(self): + image.save(test_buffer, "webp", exif=expected_exif) - file_path = "Tests/images/flower2.webp" - with Image.open(file_path) as image: + test_buffer.seek(0) + with Image.open(test_buffer) as webp_image: + webp_exif = webp_image.info.get("exif", None) + assert webp_exif + if webp_exif: + assert webp_exif == expected_exif, "WebP EXIF didn't match" - self.assertEqual(image.format, "WEBP") - self.assertTrue(image.info.get("icc_profile", None)) - icc = image.info["icc_profile"] +def test_read_icc_profile(): - with Image.open("Tests/images/flower2.jpg") as jpeg_image: - expected_icc = jpeg_image.info["icc_profile"] + file_path = "Tests/images/flower2.webp" + with Image.open(file_path) as image: - self.assertEqual(icc, expected_icc) + assert image.format == "WEBP" + assert image.info.get("icc_profile", None) - def test_write_icc_metadata(self): - file_path = "Tests/images/flower2.jpg" - test_buffer = BytesIO() - with Image.open(file_path) as image: - expected_icc_profile = image.info["icc_profile"] + icc = image.info["icc_profile"] - image.save(test_buffer, "webp", icc_profile=expected_icc_profile) + with Image.open("Tests/images/flower2.jpg") as jpeg_image: + expected_icc = jpeg_image.info["icc_profile"] - test_buffer.seek(0) - with Image.open(test_buffer) as webp_image: - webp_icc_profile = webp_image.info.get("icc_profile", None) + assert icc == expected_icc - self.assertTrue(webp_icc_profile) - if webp_icc_profile: - self.assertEqual( - webp_icc_profile, expected_icc_profile, "Webp ICC didn't match" + +def test_write_icc_metadata(): + file_path = "Tests/images/flower2.jpg" + test_buffer = BytesIO() + with Image.open(file_path) as image: + expected_icc_profile = image.info["icc_profile"] + + image.save(test_buffer, "webp", icc_profile=expected_icc_profile) + + test_buffer.seek(0) + with Image.open(test_buffer) as webp_image: + webp_icc_profile = webp_image.info.get("icc_profile", None) + + assert webp_icc_profile + if webp_icc_profile: + assert webp_icc_profile == expected_icc_profile, "Webp ICC didn't match" + + +def test_read_no_exif(): + file_path = "Tests/images/flower.jpg" + test_buffer = BytesIO() + with Image.open(file_path) as image: + assert "exif" in image.info + + image.save(test_buffer, "webp") + + test_buffer.seek(0) + with Image.open(test_buffer) as webp_image: + assert not webp_image._getexif() + + +@skip_unless_feature("webp_anim") +def test_write_animated_metadata(tmp_path): + iccp_data = b"" + exif_data = b"" + xmp_data = b"" + + temp_file = str(tmp_path / "temp.webp") + with Image.open("Tests/images/anim_frame1.webp") as frame1: + with Image.open("Tests/images/anim_frame2.webp") as frame2: + frame1.save( + temp_file, + save_all=True, + append_images=[frame2, frame1, frame2], + icc_profile=iccp_data, + exif=exif_data, + xmp=xmp_data, ) - def test_read_no_exif(self): - file_path = "Tests/images/flower.jpg" - test_buffer = BytesIO() - with Image.open(file_path) as image: - self.assertIn("exif", image.info) - - image.save(test_buffer, "webp") - - test_buffer.seek(0) - with Image.open(test_buffer) as webp_image: - self.assertFalse(webp_image._getexif()) - - def test_write_animated_metadata(self): - if not _webp.HAVE_WEBPANIM: - self.skipTest("WebP animation support not available") - - iccp_data = b"" - exif_data = b"" - xmp_data = b"" - - temp_file = self.tempfile("temp.webp") - with Image.open("Tests/images/anim_frame1.webp") as frame1: - with Image.open("Tests/images/anim_frame2.webp") as frame2: - frame1.save( - temp_file, - save_all=True, - append_images=[frame2, frame1, frame2], - icc_profile=iccp_data, - exif=exif_data, - xmp=xmp_data, - ) - - with Image.open(temp_file) as image: - self.assertIn("icc_profile", image.info) - self.assertIn("exif", image.info) - self.assertIn("xmp", image.info) - self.assertEqual(iccp_data, image.info.get("icc_profile", None)) - self.assertEqual(exif_data, image.info.get("exif", None)) - self.assertEqual(xmp_data, image.info.get("xmp", None)) + with Image.open(temp_file) as image: + assert "icc_profile" in image.info + assert "exif" in image.info + assert "xmp" in image.info + assert iccp_data == image.info.get("icc_profile", None) + assert exif_data == image.info.get("exif", None) + assert xmp_data == image.info.get("xmp", None) diff --git a/Tests/test_file_wmf.py b/Tests/test_file_wmf.py index 7a49af91a..d18225680 100644 --- a/Tests/test_file_wmf.py +++ b/Tests/test_file_wmf.py @@ -1,72 +1,79 @@ +import pytest + from PIL import Image, WmfImagePlugin -from .helper import PillowTestCase, assert_image_similar, hopper +from .helper import assert_image_similar, hopper -class TestFileWmf(PillowTestCase): - def test_load_raw(self): +def test_load_raw(): - # Test basic EMF open and rendering - with Image.open("Tests/images/drawing.emf") as im: - if hasattr(Image.core, "drawwmf"): - # Currently, support for WMF/EMF is Windows-only - im.load() - # Compare to reference rendering - with Image.open("Tests/images/drawing_emf_ref.png") as imref: - imref.load() - assert_image_similar(im, imref, 0) + # Test basic EMF open and rendering + with Image.open("Tests/images/drawing.emf") as im: + if hasattr(Image.core, "drawwmf"): + # Currently, support for WMF/EMF is Windows-only + im.load() + # Compare to reference rendering + with Image.open("Tests/images/drawing_emf_ref.png") as imref: + imref.load() + assert_image_similar(im, imref, 0) - # Test basic WMF open and rendering - with Image.open("Tests/images/drawing.wmf") as im: - if hasattr(Image.core, "drawwmf"): - # Currently, support for WMF/EMF is Windows-only - im.load() - # Compare to reference rendering - with Image.open("Tests/images/drawing_wmf_ref.png") as imref: - imref.load() - assert_image_similar(im, imref, 2.0) + # Test basic WMF open and rendering + with Image.open("Tests/images/drawing.wmf") as im: + if hasattr(Image.core, "drawwmf"): + # Currently, support for WMF/EMF is Windows-only + im.load() + # Compare to reference rendering + with Image.open("Tests/images/drawing_wmf_ref.png") as imref: + imref.load() + assert_image_similar(im, imref, 2.0) - def test_register_handler(self): - class TestHandler: - methodCalled = False - def save(self, im, fp, filename): - self.methodCalled = True +def test_register_handler(tmp_path): + class TestHandler: + methodCalled = False - handler = TestHandler() - WmfImagePlugin.register_handler(handler) + def save(self, im, fp, filename): + self.methodCalled = True - im = hopper() - tmpfile = self.tempfile("temp.wmf") - im.save(tmpfile) - self.assertTrue(handler.methodCalled) + handler = TestHandler() + original_handler = WmfImagePlugin._handler + WmfImagePlugin.register_handler(handler) - # Restore the state before this test - WmfImagePlugin.register_handler(None) + im = hopper() + tmpfile = str(tmp_path / "temp.wmf") + im.save(tmpfile) + assert handler.methodCalled - def test_load_dpi_rounding(self): - # Round up - with Image.open("Tests/images/drawing.emf") as im: - self.assertEqual(im.info["dpi"], 1424) + # Restore the state before this test + WmfImagePlugin.register_handler(original_handler) - # Round down - with Image.open("Tests/images/drawing_roundDown.emf") as im: - self.assertEqual(im.info["dpi"], 1426) - def test_load_set_dpi(self): - with Image.open("Tests/images/drawing.wmf") as im: - self.assertEqual(im.size, (82, 82)) +def test_load_dpi_rounding(): + # Round up + with Image.open("Tests/images/drawing.emf") as im: + assert im.info["dpi"] == 1424 - if hasattr(Image.core, "drawwmf"): - im.load(144) - self.assertEqual(im.size, (164, 164)) + # Round down + with Image.open("Tests/images/drawing_roundDown.emf") as im: + assert im.info["dpi"] == 1426 - with Image.open("Tests/images/drawing_wmf_ref_144.png") as expected: - assert_image_similar(im, expected, 2.0) - def test_save(self): - im = hopper() +def test_load_set_dpi(): + with Image.open("Tests/images/drawing.wmf") as im: + assert im.size == (82, 82) - for ext in [".wmf", ".emf"]: - tmpfile = self.tempfile("temp" + ext) - self.assertRaises(IOError, im.save, tmpfile) + if hasattr(Image.core, "drawwmf"): + im.load(144) + assert im.size == (164, 164) + + with Image.open("Tests/images/drawing_wmf_ref_144.png") as expected: + assert_image_similar(im, expected, 2.1) + + +def test_save(tmp_path): + im = hopper() + + for ext in [".wmf", ".emf"]: + tmpfile = str(tmp_path / ("temp" + ext)) + with pytest.raises(OSError): + im.save(tmpfile) diff --git a/Tests/test_file_xbm.py b/Tests/test_file_xbm.py index 23a540569..487920a92 100644 --- a/Tests/test_file_xbm.py +++ b/Tests/test_file_xbm.py @@ -1,6 +1,7 @@ from io import BytesIO import pytest + from PIL import Image from .helper import hopper diff --git a/Tests/test_file_xpm.py b/Tests/test_file_xpm.py index 70e701d4c..8595b07eb 100644 --- a/Tests/test_file_xpm.py +++ b/Tests/test_file_xpm.py @@ -1,33 +1,37 @@ +import pytest + from PIL import Image, XpmImagePlugin -from .helper import PillowTestCase, assert_image_similar, hopper +from .helper import assert_image_similar, hopper TEST_FILE = "Tests/images/hopper.xpm" -class TestFileXpm(PillowTestCase): - def test_sanity(self): - with Image.open(TEST_FILE) as im: - im.load() - self.assertEqual(im.mode, "P") - self.assertEqual(im.size, (128, 128)) - self.assertEqual(im.format, "XPM") +def test_sanity(): + with Image.open(TEST_FILE) as im: + im.load() + assert im.mode == "P" + assert im.size == (128, 128) + assert im.format == "XPM" - # large error due to quantization->44 colors. - assert_image_similar(im.convert("RGB"), hopper("RGB"), 60) + # large error due to quantization->44 colors. + assert_image_similar(im.convert("RGB"), hopper("RGB"), 60) - def test_invalid_file(self): - invalid_file = "Tests/images/flower.jpg" - self.assertRaises(SyntaxError, XpmImagePlugin.XpmImageFile, invalid_file) +def test_invalid_file(): + invalid_file = "Tests/images/flower.jpg" - def test_load_read(self): - # Arrange - with Image.open(TEST_FILE) as im: - dummy_bytes = 1 + with pytest.raises(SyntaxError): + XpmImagePlugin.XpmImageFile(invalid_file) - # Act - data = im.load_read(dummy_bytes) - # Assert - self.assertEqual(len(data), 16384) +def test_load_read(): + # Arrange + with Image.open(TEST_FILE) as im: + dummy_bytes = 1 + + # Act + data = im.load_read(dummy_bytes) + + # Assert + assert len(data) == 16384 diff --git a/Tests/test_file_xvthumb.py b/Tests/test_file_xvthumb.py index 2a04b7bf1..ae53d2b63 100644 --- a/Tests/test_file_xvthumb.py +++ b/Tests/test_file_xvthumb.py @@ -1,35 +1,38 @@ +import pytest + from PIL import Image, XVThumbImagePlugin -from .helper import PillowTestCase, assert_image_similar, hopper +from .helper import assert_image_similar, hopper TEST_FILE = "Tests/images/hopper.p7" -class TestFileXVThumb(PillowTestCase): - def test_open(self): - # Act - with Image.open(TEST_FILE) as im: +def test_open(): + # Act + with Image.open(TEST_FILE) as im: - # Assert - self.assertEqual(im.format, "XVThumb") + # Assert + assert im.format == "XVThumb" - # Create a Hopper image with a similar XV palette - im_hopper = hopper().quantize(palette=im) - assert_image_similar(im, im_hopper, 9) + # Create a Hopper image with a similar XV palette + im_hopper = hopper().quantize(palette=im) + assert_image_similar(im, im_hopper, 9) - def test_unexpected_eof(self): - # Test unexpected EOF reading XV thumbnail file - # Arrange - bad_file = "Tests/images/hopper_bad.p7" - # Act / Assert - self.assertRaises(SyntaxError, XVThumbImagePlugin.XVThumbImageFile, bad_file) +def test_unexpected_eof(): + # Test unexpected EOF reading XV thumbnail file + # Arrange + bad_file = "Tests/images/hopper_bad.p7" - def test_invalid_file(self): - # Arrange - invalid_file = "Tests/images/flower.jpg" + # Act / Assert + with pytest.raises(SyntaxError): + XVThumbImagePlugin.XVThumbImageFile(bad_file) - # Act / Assert - self.assertRaises( - SyntaxError, XVThumbImagePlugin.XVThumbImageFile, invalid_file - ) + +def test_invalid_file(): + # Arrange + invalid_file = "Tests/images/flower.jpg" + + # Act / Assert + with pytest.raises(SyntaxError): + XVThumbImagePlugin.XVThumbImageFile(invalid_file) diff --git a/Tests/test_font_bdf.py b/Tests/test_font_bdf.py index 4be39c383..1e7caee32 100644 --- a/Tests/test_font_bdf.py +++ b/Tests/test_font_bdf.py @@ -1,4 +1,5 @@ import pytest + from PIL import BdfFontFile, FontFile filename = "Tests/images/courB08.bdf" diff --git a/Tests/test_font_leaks.py b/Tests/test_font_leaks.py index be0612fa2..015210b4d 100644 --- a/Tests/test_font_leaks.py +++ b/Tests/test_font_leaks.py @@ -1,8 +1,6 @@ -import unittest +from PIL import Image, ImageDraw, ImageFont -from PIL import Image, ImageDraw, ImageFont, features - -from .helper import PillowLeakTestCase +from .helper import PillowLeakTestCase, skip_unless_feature class TestTTypeFontLeak(PillowLeakTestCase): @@ -19,7 +17,7 @@ class TestTTypeFontLeak(PillowLeakTestCase): ) ) - @unittest.skipUnless(features.check("freetype2"), "Test requires freetype2") + @skip_unless_feature("freetype2") def test_leak(self): ttype = ImageFont.truetype("Tests/fonts/FreeMono.ttf", 20) self._test_font(ttype) diff --git a/Tests/test_font_pcf.py b/Tests/test_font_pcf.py index 4a4edf889..4db73e56e 100644 --- a/Tests/test_font_pcf.py +++ b/Tests/test_font_pcf.py @@ -1,77 +1,91 @@ +import os + +import pytest + from PIL import FontFile, Image, ImageDraw, ImageFont, PcfFontFile -from .helper import PillowTestCase, assert_image_equal, assert_image_similar - -codecs = dir(Image.core) +from .helper import assert_image_equal, assert_image_similar, skip_unless_feature fontname = "Tests/fonts/10x20-ISO8859-1.pcf" message = "hello, world" -class TestFontPcf(PillowTestCase): - def setUp(self): - if "zip_encoder" not in codecs or "zip_decoder" not in codecs: - self.skipTest("zlib support not available") +pytestmark = skip_unless_feature("zlib") - def save_font(self): - with open(fontname, "rb") as test_file: - font = PcfFontFile.PcfFontFile(test_file) - self.assertIsInstance(font, FontFile.FontFile) - # check the number of characters in the font - self.assertEqual(len([_f for _f in font.glyph if _f]), 223) - tempname = self.tempfile("temp.pil") - self.addCleanup(self.delete_tempfile, tempname[:-4] + ".pbm") - font.save(tempname) +def save_font(request, tmp_path): + with open(fontname, "rb") as test_file: + font = PcfFontFile.PcfFontFile(test_file) + assert isinstance(font, FontFile.FontFile) + # check the number of characters in the font + assert len([_f for _f in font.glyph if _f]) == 223 - with Image.open(tempname.replace(".pil", ".pbm")) as loaded: - with Image.open("Tests/fonts/10x20.pbm") as target: - assert_image_equal(loaded, target) + tempname = str(tmp_path / "temp.pil") - with open(tempname, "rb") as f_loaded: - with open("Tests/fonts/10x20.pil", "rb") as f_target: - self.assertEqual(f_loaded.read(), f_target.read()) - return tempname + def delete_tempfile(): + try: + os.remove(tempname[:-4] + ".pbm") + except OSError: + pass # report? - def test_sanity(self): - self.save_font() + request.addfinalizer(delete_tempfile) + font.save(tempname) - def test_invalid_file(self): - with open("Tests/images/flower.jpg", "rb") as fp: - self.assertRaises(SyntaxError, PcfFontFile.PcfFontFile, fp) + with Image.open(tempname.replace(".pil", ".pbm")) as loaded: + with Image.open("Tests/fonts/10x20.pbm") as target: + assert_image_equal(loaded, target) - def test_draw(self): - tempname = self.save_font() - font = ImageFont.load(tempname) - im = Image.new("L", (130, 30), "white") - draw = ImageDraw.Draw(im) - draw.text((0, 0), message, "black", font=font) - with Image.open("Tests/images/test_draw_pbm_target.png") as target: - assert_image_similar(im, target, 0) + with open(tempname, "rb") as f_loaded: + with open("Tests/fonts/10x20.pil", "rb") as f_target: + assert f_loaded.read() == f_target.read() + return tempname - def test_textsize(self): - tempname = self.save_font() - font = ImageFont.load(tempname) - for i in range(255): - (dx, dy) = font.getsize(chr(i)) - self.assertEqual(dy, 20) - self.assertIn(dx, (0, 10)) - for l in range(len(message)): - msg = message[: l + 1] - self.assertEqual(font.getsize(msg), (len(msg) * 10, 20)) - def _test_high_characters(self, message): - tempname = self.save_font() - font = ImageFont.load(tempname) - im = Image.new("L", (750, 30), "white") - draw = ImageDraw.Draw(im) - draw.text((0, 0), message, "black", font=font) - with Image.open("Tests/images/high_ascii_chars.png") as target: - assert_image_similar(im, target, 0) +def test_sanity(request, tmp_path): + save_font(request, tmp_path) - def test_high_characters(self): - message = "".join(chr(i + 1) for i in range(140, 232)) - self._test_high_characters(message) - # accept bytes instances. - self._test_high_characters(message.encode("latin1")) + +def test_invalid_file(): + with open("Tests/images/flower.jpg", "rb") as fp: + with pytest.raises(SyntaxError): + PcfFontFile.PcfFontFile(fp) + + +def test_draw(request, tmp_path): + tempname = save_font(request, tmp_path) + font = ImageFont.load(tempname) + im = Image.new("L", (130, 30), "white") + draw = ImageDraw.Draw(im) + draw.text((0, 0), message, "black", font=font) + with Image.open("Tests/images/test_draw_pbm_target.png") as target: + assert_image_similar(im, target, 0) + + +def test_textsize(request, tmp_path): + tempname = save_font(request, tmp_path) + font = ImageFont.load(tempname) + for i in range(255): + (dx, dy) = font.getsize(chr(i)) + assert dy == 20 + assert dx in (0, 10) + for i in range(len(message)): + msg = message[: i + 1] + assert font.getsize(msg) == (len(msg) * 10, 20) + + +def _test_high_characters(request, tmp_path, message): + tempname = save_font(request, tmp_path) + font = ImageFont.load(tempname) + im = Image.new("L", (750, 30), "white") + draw = ImageDraw.Draw(im) + draw.text((0, 0), message, "black", font=font) + with Image.open("Tests/images/high_ascii_chars.png") as target: + assert_image_similar(im, target, 0) + + +def test_high_characters(request, tmp_path): + message = "".join(chr(i + 1) for i in range(140, 232)) + _test_high_characters(request, tmp_path, message) + # accept bytes instances. + _test_high_characters(request, tmp_path, message.encode("latin1")) diff --git a/Tests/test_font_pcf_charsets.py b/Tests/test_font_pcf_charsets.py new file mode 100644 index 000000000..d7d1bf200 --- /dev/null +++ b/Tests/test_font_pcf_charsets.py @@ -0,0 +1,120 @@ +import os + +from PIL import FontFile, Image, ImageDraw, ImageFont, PcfFontFile + +from .helper import assert_image_equal, assert_image_similar, skip_unless_feature + +fontname = "Tests/fonts/ter-x20b.pcf" + +charsets = { + "iso8859-1": { + "glyph_count": 223, + "message": "hello, world", + "image1": "Tests/images/test_draw_pbm_ter_en_target.png", + }, + "iso8859-2": { + "glyph_count": 223, + "message": "witaj świecie", + "image1": "Tests/images/test_draw_pbm_ter_pl_target.png", + }, + "cp1250": { + "glyph_count": 250, + "message": "witaj świecie", + "image1": "Tests/images/test_draw_pbm_ter_pl_target.png", + }, +} + + +pytestmark = skip_unless_feature("zlib") + + +def save_font(request, tmp_path, encoding): + with open(fontname, "rb") as test_file: + font = PcfFontFile.PcfFontFile(test_file, encoding) + assert isinstance(font, FontFile.FontFile) + # check the number of characters in the font + assert len([_f for _f in font.glyph if _f]) == charsets[encoding]["glyph_count"] + + tempname = str(tmp_path / "temp.pil") + + def delete_tempfile(): + try: + os.remove(tempname[:-4] + ".pbm") + except OSError: + pass # report? + + request.addfinalizer(delete_tempfile) + font.save(tempname) + + with Image.open(tempname.replace(".pil", ".pbm")) as loaded: + with Image.open(f"Tests/fonts/ter-x20b-{encoding}.pbm") as target: + assert_image_equal(loaded, target) + + with open(tempname, "rb") as f_loaded: + with open(f"Tests/fonts/ter-x20b-{encoding}.pil", "rb") as f_target: + assert f_loaded.read() == f_target.read() + return tempname + + +def _test_sanity(request, tmp_path, encoding): + save_font(request, tmp_path, encoding) + + +def test_sanity_iso8859_1(request, tmp_path): + _test_sanity(request, tmp_path, "iso8859-1") + + +def test_sanity_iso8859_2(request, tmp_path): + _test_sanity(request, tmp_path, "iso8859-2") + + +def test_sanity_cp1250(request, tmp_path): + _test_sanity(request, tmp_path, "cp1250") + + +def _test_draw(request, tmp_path, encoding): + tempname = save_font(request, tmp_path, encoding) + font = ImageFont.load(tempname) + im = Image.new("L", (150, 30), "white") + draw = ImageDraw.Draw(im) + message = charsets[encoding]["message"].encode(encoding) + draw.text((0, 0), message, "black", font=font) + with Image.open(charsets[encoding]["image1"]) as target: + assert_image_similar(im, target, 0) + + +def test_draw_iso8859_1(request, tmp_path): + _test_draw(request, tmp_path, "iso8859-1") + + +def test_draw_iso8859_2(request, tmp_path): + _test_draw(request, tmp_path, "iso8859-2") + + +def test_draw_cp1250(request, tmp_path): + _test_draw(request, tmp_path, "cp1250") + + +def _test_textsize(request, tmp_path, encoding): + tempname = save_font(request, tmp_path, encoding) + font = ImageFont.load(tempname) + for i in range(255): + (dx, dy) = font.getsize(bytearray([i])) + assert dy == 20 + assert dx in (0, 10) + message = charsets[encoding]["message"].encode(encoding) + for i in range(len(message)): + msg = message[: i + 1] + assert font.getsize(msg) == (len(msg) * 10, 20) + + +def test_textsize_iso8859_1(request, tmp_path): + _test_textsize(request, tmp_path, "iso8859-1") + + +def test_textsize_iso8859_2(request, tmp_path): + _test_textsize(request, tmp_path, "iso8859-2") + + +def test_textsize_cp1250(request, tmp_path): + _test_textsize(request, tmp_path, "cp1250") diff --git a/Tests/test_format_hsv.py b/Tests/test_format_hsv.py index b940b823d..3b9c8b071 100644 --- a/Tests/test_format_hsv.py +++ b/Tests/test_format_hsv.py @@ -3,142 +3,149 @@ import itertools from PIL import Image -from .helper import PillowTestCase, assert_image_similar, hopper +from .helper import assert_image_similar, hopper -class TestFormatHSV(PillowTestCase): - def int_to_float(self, i): - return i / 255 +def int_to_float(i): + return i / 255 - def str_to_float(self, i): - return ord(i) / 255 - def tuple_to_ints(self, tp): - x, y, z = tp - return int(x * 255.0), int(y * 255.0), int(z * 255.0) +def str_to_float(i): + return ord(i) / 255 - def test_sanity(self): - Image.new("HSV", (100, 100)) - def wedge(self): - w = Image._wedge() - w90 = w.rotate(90) +def tuple_to_ints(tp): + x, y, z = tp + return int(x * 255.0), int(y * 255.0), int(z * 255.0) - (px, h) = w.size - r = Image.new("L", (px * 3, h)) - g = r.copy() - b = r.copy() +def test_sanity(): + Image.new("HSV", (100, 100)) - r.paste(w, (0, 0)) - r.paste(w90, (px, 0)) - g.paste(w90, (0, 0)) - g.paste(w, (2 * px, 0)) +def wedge(): + w = Image._wedge() + w90 = w.rotate(90) - b.paste(w, (px, 0)) - b.paste(w90, (2 * px, 0)) + (px, h) = w.size - img = Image.merge("RGB", (r, g, b)) + r = Image.new("L", (px * 3, h)) + g = r.copy() + b = r.copy() - return img + r.paste(w, (0, 0)) + r.paste(w90, (px, 0)) - def to_xxx_colorsys(self, im, func, mode): - # convert the hard way using the library colorsys routines. + g.paste(w90, (0, 0)) + g.paste(w, (2 * px, 0)) - (r, g, b) = im.split() + b.paste(w, (px, 0)) + b.paste(w90, (2 * px, 0)) - conv_func = self.int_to_float + img = Image.merge("RGB", (r, g, b)) - converted = [ - self.tuple_to_ints(func(conv_func(_r), conv_func(_g), conv_func(_b))) - for (_r, _g, _b) in itertools.zip_longest( - r.tobytes(), g.tobytes(), b.tobytes() - ) - ] + return img - new_bytes = b"".join( - bytes(chr(h) + chr(s) + chr(v), "latin-1") for (h, s, v) in converted - ) - hsv = Image.frombytes(mode, r.size, new_bytes) +def to_xxx_colorsys(im, func, mode): + # convert the hard way using the library colorsys routines. - return hsv + (r, g, b) = im.split() - def to_hsv_colorsys(self, im): - return self.to_xxx_colorsys(im, colorsys.rgb_to_hsv, "HSV") + conv_func = int_to_float - def to_rgb_colorsys(self, im): - return self.to_xxx_colorsys(im, colorsys.hsv_to_rgb, "RGB") + converted = [ + tuple_to_ints(func(conv_func(_r), conv_func(_g), conv_func(_b))) + for (_r, _g, _b) in itertools.zip_longest(r.tobytes(), g.tobytes(), b.tobytes()) + ] - def test_wedge(self): - src = self.wedge().resize((3 * 32, 32), Image.BILINEAR) - im = src.convert("HSV") - comparable = self.to_hsv_colorsys(src) + new_bytes = b"".join( + bytes(chr(h) + chr(s) + chr(v), "latin-1") for (h, s, v) in converted + ) - assert_image_similar( - im.getchannel(0), comparable.getchannel(0), 1, "Hue conversion is wrong" - ) - assert_image_similar( - im.getchannel(1), - comparable.getchannel(1), - 1, - "Saturation conversion is wrong", - ) - assert_image_similar( - im.getchannel(2), comparable.getchannel(2), 1, "Value conversion is wrong" - ) + hsv = Image.frombytes(mode, r.size, new_bytes) - comparable = src - im = im.convert("RGB") + return hsv - assert_image_similar( - im.getchannel(0), comparable.getchannel(0), 3, "R conversion is wrong" - ) - assert_image_similar( - im.getchannel(1), comparable.getchannel(1), 3, "G conversion is wrong" - ) - assert_image_similar( - im.getchannel(2), comparable.getchannel(2), 3, "B conversion is wrong" - ) - def test_convert(self): - im = hopper("RGB").convert("HSV") - comparable = self.to_hsv_colorsys(hopper("RGB")) +def to_hsv_colorsys(im): + return to_xxx_colorsys(im, colorsys.rgb_to_hsv, "HSV") - assert_image_similar( - im.getchannel(0), comparable.getchannel(0), 1, "Hue conversion is wrong" - ) - assert_image_similar( - im.getchannel(1), - comparable.getchannel(1), - 1, - "Saturation conversion is wrong", - ) - assert_image_similar( - im.getchannel(2), comparable.getchannel(2), 1, "Value conversion is wrong" - ) - def test_hsv_to_rgb(self): - comparable = self.to_hsv_colorsys(hopper("RGB")) - converted = comparable.convert("RGB") - comparable = self.to_rgb_colorsys(comparable) +def to_rgb_colorsys(im): + return to_xxx_colorsys(im, colorsys.hsv_to_rgb, "RGB") - assert_image_similar( - converted.getchannel(0), - comparable.getchannel(0), - 3, - "R conversion is wrong", - ) - assert_image_similar( - converted.getchannel(1), - comparable.getchannel(1), - 3, - "G conversion is wrong", - ) - assert_image_similar( - converted.getchannel(2), - comparable.getchannel(2), - 3, - "B conversion is wrong", - ) + +def test_wedge(): + src = wedge().resize((3 * 32, 32), Image.BILINEAR) + im = src.convert("HSV") + comparable = to_hsv_colorsys(src) + + assert_image_similar( + im.getchannel(0), comparable.getchannel(0), 1, "Hue conversion is wrong" + ) + assert_image_similar( + im.getchannel(1), + comparable.getchannel(1), + 1, + "Saturation conversion is wrong", + ) + assert_image_similar( + im.getchannel(2), comparable.getchannel(2), 1, "Value conversion is wrong" + ) + + comparable = src + im = im.convert("RGB") + + assert_image_similar( + im.getchannel(0), comparable.getchannel(0), 3, "R conversion is wrong" + ) + assert_image_similar( + im.getchannel(1), comparable.getchannel(1), 3, "G conversion is wrong" + ) + assert_image_similar( + im.getchannel(2), comparable.getchannel(2), 3, "B conversion is wrong" + ) + + +def test_convert(): + im = hopper("RGB").convert("HSV") + comparable = to_hsv_colorsys(hopper("RGB")) + + assert_image_similar( + im.getchannel(0), comparable.getchannel(0), 1, "Hue conversion is wrong" + ) + assert_image_similar( + im.getchannel(1), + comparable.getchannel(1), + 1, + "Saturation conversion is wrong", + ) + assert_image_similar( + im.getchannel(2), comparable.getchannel(2), 1, "Value conversion is wrong" + ) + + +def test_hsv_to_rgb(): + comparable = to_hsv_colorsys(hopper("RGB")) + converted = comparable.convert("RGB") + comparable = to_rgb_colorsys(comparable) + + assert_image_similar( + converted.getchannel(0), + comparable.getchannel(0), + 3, + "R conversion is wrong", + ) + assert_image_similar( + converted.getchannel(1), + comparable.getchannel(1), + 3, + "G conversion is wrong", + ) + assert_image_similar( + converted.getchannel(2), + comparable.getchannel(2), + 3, + "B conversion is wrong", + ) diff --git a/Tests/test_image.py b/Tests/test_image.py index 334e330f4..f2a1917e8 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -2,22 +2,23 @@ import io import os import shutil import tempfile -import unittest import pytest -from PIL import Image, UnidentifiedImageError + +import PIL +from PIL import Image, ImageDraw, ImagePalette, ImageShow, UnidentifiedImageError from .helper import ( - PillowTestCase, assert_image_equal, assert_image_similar, assert_not_all_same, hopper, is_win32, + skip_unless_feature, ) -class TestImage(PillowTestCase): +class TestImage: def test_image_modes_success(self): for mode in [ "1", @@ -53,74 +54,100 @@ class TestImage(PillowTestCase): "BGR;24", "BGR;32", ]: - with self.assertRaises(ValueError) as e: + with pytest.raises(ValueError) as e: Image.new(mode, (1, 1)) - self.assertEqual(str(e.exception), "unrecognized image mode") + assert str(e.value) == "unrecognized image mode" def test_exception_inheritance(self): - self.assertTrue(issubclass(UnidentifiedImageError, IOError)) + assert issubclass(UnidentifiedImageError, OSError) def test_sanity(self): im = Image.new("L", (100, 100)) - self.assertEqual(repr(im)[:45], "= 7 + + with pytest.warns(DeprecationWarning): + assert test_module.PILLOW_VERSION < "9.9.0" + + with pytest.warns(DeprecationWarning): + assert test_module.PILLOW_VERSION <= "9.9.0" + + with pytest.warns(DeprecationWarning): + assert test_module.PILLOW_VERSION != "7.0.0" + + with pytest.warns(DeprecationWarning): + assert test_module.PILLOW_VERSION >= "7.0.0" + + with pytest.warns(DeprecationWarning): + assert test_module.PILLOW_VERSION > "7.0.0" + + @pytest.mark.parametrize( + "path", + [ "fli_overrun.bin", "sgi_overrun.bin", "sgi_overrun_expandrow.bin", "sgi_overrun_expandrow2.bin", "pcx_overrun.bin", "pcx_overrun2.bin", - ]: - with Image.open(os.path.join("Tests/images", file)) as im: - try: - im.load() - self.assertFail() - except OSError as e: - self.assertEqual(str(e), "buffer overrun when reading image file") + "ossfuzz-4836216264589312.pcx", + "01r_00.pcx", + ], + ) + def test_overrun(self, path): + """For overrun completeness, test as: + valgrind pytest -qq Tests/test_image.py::TestImage::test_overrun | grep decode.c + """ + with Image.open(os.path.join("Tests/images", path)) as im: + try: + im.load() + assert False + except OSError as e: + buffer_overrun = str(e) == "buffer overrun when reading image file" + truncated = "image file is truncated" in str(e) + assert buffer_overrun or truncated + + def test_fli_overrun2(self): with Image.open("Tests/images/fli_overrun2.bin") as im: try: im.seek(1) - self.assertFail() + assert False except OSError as e: - self.assertEqual(str(e), "buffer overrun when reading image file") + assert str(e) == "buffer overrun when reading image file" + + def test_show_deprecation(self, monkeypatch): + monkeypatch.setattr(Image, "_show", lambda *args, **kwargs: None) + + im = Image.new("RGB", (50, 50), "white") + + with pytest.warns(None) as raised: + im.show() + assert not raised + + with pytest.warns(DeprecationWarning): + im.show(command="mock") class MockEncoder: @@ -627,23 +833,17 @@ def mock_encode(*args): return encoder -class TestRegistry(PillowTestCase): +class TestRegistry: def test_encode_registry(self): Image.register_encoder("MOCK", mock_encode) - self.assertIn("MOCK", Image.ENCODERS) + assert "MOCK" in Image.ENCODERS enc = Image._getencoder("RGB", "MOCK", ("args",), extra=("extra",)) - self.assertIsInstance(enc, MockEncoder) - self.assertEqual(enc.args, ("RGB", "args", "extra")) + assert isinstance(enc, MockEncoder) + assert enc.args == ("RGB", "args", "extra") def test_encode_registry_fail(self): - self.assertRaises( - IOError, - Image._getencoder, - "RGB", - "DoesNotExist", - ("args",), - extra=("extra",), - ) + with pytest.raises(OSError): + Image._getencoder("RGB", "DoesNotExist", ("args",), extra=("extra",)) diff --git a/Tests/test_image_access.py b/Tests/test_image_access.py index a0d471b92..e86dc8530 100644 --- a/Tests/test_image_access.py +++ b/Tests/test_image_access.py @@ -2,12 +2,14 @@ import ctypes import os import subprocess import sys -import unittest -from distutils import ccompiler, sysconfig +import sysconfig + +import pytest +from setuptools.command.build_ext import new_compiler from PIL import Image -from .helper import PillowTestCase, assert_image_equal, hopper, is_win32, on_ci +from .helper import assert_image_equal, hopper, is_win32, on_ci # CFFI imports pycparser which doesn't support PYTHONOPTIMIZE=2 # https://github.com/eliben/pycparser/pull/198#issuecomment-317001670 @@ -15,23 +17,24 @@ if os.environ.get("PYTHONOPTIMIZE") == "2": cffi = None else: try: - from PIL import PyAccess import cffi + + from PIL import PyAccess except ImportError: cffi = None -class AccessTest(PillowTestCase): +class AccessTest: # initial value _init_cffi_access = Image.USE_CFFI_ACCESS _need_cffi_access = False @classmethod - def setUpClass(cls): + def setup_class(cls): Image.USE_CFFI_ACCESS = cls._need_cffi_access @classmethod - def tearDownClass(cls): + def teardown_class(cls): Image.USE_CFFI_ACCESS = cls._init_cffi_access @@ -55,7 +58,7 @@ class TestImagePutPixel(AccessTest): pos = x, y im2.putpixel(pos, im1.getpixel(pos)) - self.assertFalse(im2.readonly) + assert not im2.readonly assert_image_equal(im1, im2) im2 = Image.new(im1.mode, im1.size, 0) @@ -74,8 +77,8 @@ class TestImagePutPixel(AccessTest): im2 = Image.new(im1.mode, im1.size, 0) width, height = im1.size - self.assertEqual(im1.getpixel((0, 0)), im1.getpixel((-width, -height))) - self.assertEqual(im1.getpixel((-1, -1)), im1.getpixel((width - 1, height - 1))) + assert im1.getpixel((0, 0)) == im1.getpixel((-width, -height)) + assert im1.getpixel((-1, -1)) == im1.getpixel((width - 1, height - 1)) for y in range(-1, -im1.size[1] - 1, -1): for x in range(-1, -im1.size[0] - 1, -1): @@ -92,7 +95,7 @@ class TestImagePutPixel(AccessTest): pos = x, y im2.putpixel(pos, im1.getpixel(pos)) - self.assertFalse(im2.readonly) + assert not im2.readonly assert_image_equal(im1, im2) im2 = Image.new(im1.mode, im1.size, 0) @@ -123,54 +126,44 @@ class TestImageGetPixel(AccessTest): # check putpixel im = Image.new(mode, (1, 1), None) im.putpixel((0, 0), c) - self.assertEqual( - im.getpixel((0, 0)), - c, - "put/getpixel roundtrip failed for mode {}, color {}".format(mode, c), - ) + assert ( + im.getpixel((0, 0)) == c + ), f"put/getpixel roundtrip failed for mode {mode}, color {c}" # check putpixel negative index im.putpixel((-1, -1), c) - self.assertEqual( - im.getpixel((-1, -1)), - c, - "put/getpixel roundtrip negative index failed" - " for mode %s, color %s" % (mode, c), - ) + assert ( + im.getpixel((-1, -1)) == c + ), f"put/getpixel roundtrip negative index failed for mode {mode}, color {c}" # Check 0 im = Image.new(mode, (0, 0), None) - with self.assertRaises(IndexError): + with pytest.raises(IndexError): im.putpixel((0, 0), c) - with self.assertRaises(IndexError): + with pytest.raises(IndexError): im.getpixel((0, 0)) # Check 0 negative index - with self.assertRaises(IndexError): + with pytest.raises(IndexError): im.putpixel((-1, -1), c) - with self.assertRaises(IndexError): + with pytest.raises(IndexError): im.getpixel((-1, -1)) # check initial color im = Image.new(mode, (1, 1), c) - self.assertEqual( - im.getpixel((0, 0)), - c, - "initial color failed for mode {}, color {} ".format(mode, c), - ) + assert ( + im.getpixel((0, 0)) == c + ), f"initial color failed for mode {mode}, color {c} " # check initial color negative index - self.assertEqual( - im.getpixel((-1, -1)), - c, - "initial color failed with negative index" - "for mode %s, color %s " % (mode, c), - ) + assert ( + im.getpixel((-1, -1)) == c + ), f"initial color failed with negative index for mode {mode}, color {c} " # Check 0 im = Image.new(mode, (0, 0), c) - with self.assertRaises(IndexError): + with pytest.raises(IndexError): im.getpixel((0, 0)) # Check 0 negative index - with self.assertRaises(IndexError): + with pytest.raises(IndexError): im.getpixel((-1, -1)) def test_basic(self): @@ -205,20 +198,20 @@ class TestImageGetPixel(AccessTest): for color in [(255, 0, 0), (255, 0, 0, 255)]: im = Image.new("P", (1, 1), 0) im.putpixel((0, 0), color) - self.assertEqual(im.convert("RGB").getpixel((0, 0)), (255, 0, 0)) + assert im.convert("RGB").getpixel((0, 0)) == (255, 0, 0) -@unittest.skipIf(cffi is None, "No cffi") +@pytest.mark.skipif(cffi is None, reason="No CFFI") class TestCffiPutPixel(TestImagePutPixel): _need_cffi_access = True -@unittest.skipIf(cffi is None, "No cffi") +@pytest.mark.skipif(cffi is None, reason="No CFFI") class TestCffiGetPixel(TestImageGetPixel): _need_cffi_access = True -@unittest.skipIf(cffi is None, "No cffi") +@pytest.mark.skipif(cffi is None, reason="No CFFI") class TestCffi(AccessTest): _need_cffi_access = True @@ -233,12 +226,11 @@ class TestCffi(AccessTest): w, h = im.size for x in range(0, w, 10): for y in range(0, h, 10): - self.assertEqual(access[(x, y)], caccess[(x, y)]) + assert access[(x, y)] == caccess[(x, y)] # Access an out-of-range pixel - self.assertRaises( - ValueError, lambda: access[(access.xsize + 1, access.ysize + 1)] - ) + with pytest.raises(ValueError): + access[(access.xsize + 1, access.ysize + 1)] def test_get_vs_c(self): rgb = hopper("RGB") @@ -280,11 +272,11 @@ class TestCffi(AccessTest): for x in range(0, w, 10): for y in range(0, h, 10): access[(x, y)] = color - self.assertEqual(color, caccess[(x, y)]) + assert color == caccess[(x, y)] # Attempt to set the value on a read-only image access = PyAccess.new(im, True) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): access[(0, 0)] = color def test_set_vs_c(self): @@ -314,7 +306,7 @@ class TestCffi(AccessTest): # self._test_set_access(im, 2**13-1) def test_not_implemented(self): - self.assertIsNone(PyAccess.new(hopper("BGR;15"))) + assert PyAccess.new(hopper("BGR;15")) is None # ref https://github.com/python-pillow/Pillow/pull/2009 def test_reference_counting(self): @@ -325,20 +317,54 @@ class TestCffi(AccessTest): px = Image.new("L", (size, 1), 0).load() for i in range(size): # pixels can contain garbage if image is released - self.assertEqual(px[i, 0], 0) + assert px[i, 0] == 0 def test_p_putpixel_rgb_rgba(self): for color in [(255, 0, 0), (255, 0, 0, 255)]: im = Image.new("P", (1, 1), 0) access = PyAccess.new(im, False) access.putpixel((0, 0), color) - self.assertEqual(im.convert("RGB").getpixel((0, 0)), (255, 0, 0)) + assert im.convert("RGB").getpixel((0, 0)) == (255, 0, 0) -class TestEmbeddable(unittest.TestCase): - @unittest.skipIf( +class TestImagePutPixelError(AccessTest): + IMAGE_MODES1 = ["L", "LA", "RGB", "RGBA"] + IMAGE_MODES2 = ["I", "I;16", "BGR;15"] + INVALID_TYPES = ["foo", 1.0, None] + + @pytest.mark.parametrize("mode", IMAGE_MODES1) + def test_putpixel_type_error1(self, mode): + im = hopper(mode) + for v in self.INVALID_TYPES: + with pytest.raises(TypeError, match="color must be int or tuple"): + im.putpixel((0, 0), v) + + @pytest.mark.parametrize("mode", IMAGE_MODES2) + def test_putpixel_type_error2(self, mode): + im = hopper(mode) + for v in self.INVALID_TYPES: + with pytest.raises( + TypeError, match="color must be int or single-element tuple" + ): + im.putpixel((0, 0), v) + + @pytest.mark.parametrize("mode", IMAGE_MODES1 + IMAGE_MODES2) + def test_putpixel_overflow_error(self, mode): + im = hopper(mode) + with pytest.raises(OverflowError): + im.putpixel((0, 0), 2 ** 80) + + def test_putpixel_unrecognized_mode(self): + im = hopper("BGR;15") + with pytest.raises(ValueError, match="unrecognized image mode"): + im.putpixel((0, 0), 0) + + +class TestEmbeddable: + @pytest.mark.skipif( not is_win32() or on_ci(), - "Failing on AppVeyor / GitHub Actions when run from subprocess, not from shell", + reason="Failing on AppVeyor / GitHub Actions when run from subprocess, " + "not from shell", ) def test_embeddable(self): with open("embed_pil.c", "w") as fh: @@ -368,13 +394,12 @@ int main(int argc, char* argv[]) % sys.prefix.replace("\\", "\\\\") ) - compiler = ccompiler.new_compiler() - compiler.add_include_dir(sysconfig.get_python_inc()) + compiler = new_compiler() + compiler.add_include_dir(sysconfig.get_config_var("INCLUDEPY")) - libdir = sysconfig.get_config_var( - "LIBDIR" - ) or sysconfig.get_python_inc().replace("include", "libs") - print(libdir) + libdir = sysconfig.get_config_var("LIBDIR") or sysconfig.get_config_var( + "INCLUDEPY" + ).replace("include", "libs") compiler.add_library_dir(libdir) objects = compiler.compile(["embed_pil.c"]) compiler.link_executable(objects, "embed_pil") @@ -387,4 +412,4 @@ int main(int argc, char* argv[]) process = subprocess.Popen(["embed_pil.exe"], env=env) process.communicate() - self.assertEqual(process.returncode, 0) + assert process.returncode == 0 diff --git a/Tests/test_image_array.py b/Tests/test_image_array.py index bf6d88a97..980458407 100644 --- a/Tests/test_image_array.py +++ b/Tests/test_image_array.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image from .helper import hopper diff --git a/Tests/test_image_convert.py b/Tests/test_image_convert.py index b7355a3ad..6fe1bd962 100644 --- a/Tests/test_image_convert.py +++ b/Tests/test_image_convert.py @@ -1,248 +1,262 @@ import pytest + from PIL import Image -from .helper import ( - PillowTestCase, - assert_image, - assert_image_equal, - assert_image_similar, - hopper, -) +from .helper import assert_image, assert_image_equal, assert_image_similar, hopper -class TestImageConvert(PillowTestCase): - def test_sanity(self): - def convert(im, mode): - out = im.convert(mode) - self.assertEqual(out.mode, mode) - self.assertEqual(out.size, im.size) +def test_sanity(): + def convert(im, mode): + out = im.convert(mode) + assert out.mode == mode + assert out.size == im.size - modes = ( - "1", - "L", - "LA", - "P", - "PA", - "I", - "F", - "RGB", - "RGBA", - "RGBX", - "CMYK", - "YCbCr", - "HSV", - ) + modes = ( + "1", + "L", + "LA", + "P", + "PA", + "I", + "F", + "RGB", + "RGBA", + "RGBX", + "CMYK", + "YCbCr", + "HSV", + ) + for mode in modes: + im = hopper(mode) for mode in modes: - im = hopper(mode) - for mode in modes: - convert(im, mode) + convert(im, mode) - # Check 0 - im = Image.new(mode, (0, 0)) - for mode in modes: - convert(im, mode) + # Check 0 + im = Image.new(mode, (0, 0)) + for mode in modes: + convert(im, mode) - def test_default(self): - im = hopper("P") - assert_image(im, "P", im.size) - im = im.convert() - assert_image(im, "RGB", im.size) - im = im.convert() - assert_image(im, "RGB", im.size) +def test_default(): - # ref https://github.com/python-pillow/Pillow/issues/274 + im = hopper("P") + assert_image(im, "P", im.size) + im = im.convert() + assert_image(im, "RGB", im.size) + im = im.convert() + assert_image(im, "RGB", im.size) - def _test_float_conversion(self, im): - orig = im.getpixel((5, 5)) - converted = im.convert("F").getpixel((5, 5)) - self.assertEqual(orig, converted) - def test_8bit(self): - with Image.open("Tests/images/hopper.jpg") as im: - self._test_float_conversion(im.convert("L")) +# ref https://github.com/python-pillow/Pillow/issues/274 - def test_16bit(self): - with Image.open("Tests/images/16bit.cropped.tif") as im: - self._test_float_conversion(im) - def test_16bit_workaround(self): - with Image.open("Tests/images/16bit.cropped.tif") as im: - self._test_float_conversion(im.convert("I")) +def _test_float_conversion(im): + orig = im.getpixel((5, 5)) + converted = im.convert("F").getpixel((5, 5)) + assert orig == converted - def test_rgba_p(self): - im = hopper("RGBA") - im.putalpha(hopper("L")) - converted = im.convert("P") - comparable = converted.convert("RGBA") +def test_8bit(): + with Image.open("Tests/images/hopper.jpg") as im: + _test_float_conversion(im.convert("L")) - assert_image_similar(im, comparable, 20) - def test_trns_p(self): - im = hopper("P") - im.info["transparency"] = 0 +def test_16bit(): + with Image.open("Tests/images/16bit.cropped.tif") as im: + _test_float_conversion(im) - f = self.tempfile("temp.png") - im_l = im.convert("L") - self.assertEqual(im_l.info["transparency"], 0) # undone - im_l.save(f) +def test_16bit_workaround(): + with Image.open("Tests/images/16bit.cropped.tif") as im: + _test_float_conversion(im.convert("I")) - im_rgb = im.convert("RGB") - self.assertEqual(im_rgb.info["transparency"], (0, 0, 0)) # undone - im_rgb.save(f) - # ref https://github.com/python-pillow/Pillow/issues/664 +def test_rgba_p(): + im = hopper("RGBA") + im.putalpha(hopper("L")) - def test_trns_p_rgba(self): - # Arrange - im = hopper("P") - im.info["transparency"] = 128 + converted = im.convert("P") + comparable = converted.convert("RGBA") - # Act - im_rgba = im.convert("RGBA") + assert_image_similar(im, comparable, 20) - # Assert - self.assertNotIn("transparency", im_rgba.info) - # https://github.com/python-pillow/Pillow/issues/2702 - self.assertIsNone(im_rgba.palette) - def test_trns_l(self): - im = hopper("L") - im.info["transparency"] = 128 +def test_trns_p(tmp_path): + im = hopper("P") + im.info["transparency"] = 0 - f = self.tempfile("temp.png") + f = str(tmp_path / "temp.png") - im_rgb = im.convert("RGB") - self.assertEqual(im_rgb.info["transparency"], (128, 128, 128)) # undone - im_rgb.save(f) + im_l = im.convert("L") + assert im_l.info["transparency"] == 0 # undone + im_l.save(f) + im_rgb = im.convert("RGB") + assert im_rgb.info["transparency"] == (0, 0, 0) # undone + im_rgb.save(f) + + +# ref https://github.com/python-pillow/Pillow/issues/664 + + +def test_trns_p_rgba(): + # Arrange + im = hopper("P") + im.info["transparency"] = 128 + + # Act + im_rgba = im.convert("RGBA") + + # Assert + assert "transparency" not in im_rgba.info + # https://github.com/python-pillow/Pillow/issues/2702 + assert im_rgba.palette is None + + +def test_trns_l(tmp_path): + im = hopper("L") + im.info["transparency"] = 128 + + f = str(tmp_path / "temp.png") + + im_rgb = im.convert("RGB") + assert im_rgb.info["transparency"] == (128, 128, 128) # undone + im_rgb.save(f) + + im_p = im.convert("P") + assert "transparency" in im_p.info + im_p.save(f) + + im_p = pytest.warns(UserWarning, im.convert, "P", palette=Image.ADAPTIVE) + assert "transparency" not in im_p.info + im_p.save(f) + + +def test_trns_RGB(tmp_path): + im = hopper("RGB") + im.info["transparency"] = im.getpixel((0, 0)) + + f = str(tmp_path / "temp.png") + + im_l = im.convert("L") + assert im_l.info["transparency"] == im_l.getpixel((0, 0)) # undone + im_l.save(f) + + im_p = im.convert("P") + assert "transparency" in im_p.info + im_p.save(f) + + im_rgba = im.convert("RGBA") + assert "transparency" not in im_rgba.info + im_rgba.save(f) + + im_p = pytest.warns(UserWarning, im.convert, "P", palette=Image.ADAPTIVE) + assert "transparency" not in im_p.info + im_p.save(f) + + +def test_gif_with_rgba_palette_to_p(): + # See https://github.com/python-pillow/Pillow/issues/2433 + with Image.open("Tests/images/hopper.gif") as im: + im.info["transparency"] = 255 + im.load() + assert im.palette.mode == "RGBA" im_p = im.convert("P") - self.assertIn("transparency", im_p.info) - im_p.save(f) - im_p = pytest.warns(UserWarning, im.convert, "P", palette=Image.ADAPTIVE) - self.assertNotIn("transparency", im_p.info) - im_p.save(f) + # Should not raise ValueError: unrecognized raw mode + im_p.load() - def test_trns_RGB(self): + +def test_p_la(): + im = hopper("RGBA") + alpha = hopper("L") + im.putalpha(alpha) + + comparable = im.convert("P").convert("LA").getchannel("A") + + assert_image_similar(alpha, comparable, 5) + + +def test_matrix_illegal_conversion(): + # Arrange + im = hopper("CMYK") + # fmt: off + matrix = ( + 0.412453, 0.357580, 0.180423, 0, + 0.212671, 0.715160, 0.072169, 0, + 0.019334, 0.119193, 0.950227, 0) + # fmt: on + assert im.mode != "RGB" + + # Act / Assert + with pytest.raises(ValueError): + im.convert(mode="CMYK", matrix=matrix) + + +def test_matrix_wrong_mode(): + # Arrange + im = hopper("L") + # fmt: off + matrix = ( + 0.412453, 0.357580, 0.180423, 0, + 0.212671, 0.715160, 0.072169, 0, + 0.019334, 0.119193, 0.950227, 0) + # fmt: on + assert im.mode == "L" + + # Act / Assert + with pytest.raises(ValueError): + im.convert(mode="L", matrix=matrix) + + +def test_matrix_xyz(): + def matrix_convert(mode): + # Arrange im = hopper("RGB") - im.info["transparency"] = im.getpixel((0, 0)) - - f = self.tempfile("temp.png") - - im_l = im.convert("L") - self.assertEqual(im_l.info["transparency"], im_l.getpixel((0, 0))) # undone - im_l.save(f) - - im_p = im.convert("P") - self.assertIn("transparency", im_p.info) - im_p.save(f) - - im_rgba = im.convert("RGBA") - self.assertNotIn("transparency", im_rgba.info) - im_rgba.save(f) - - im_p = pytest.warns(UserWarning, im.convert, "P", palette=Image.ADAPTIVE) - self.assertNotIn("transparency", im_p.info) - im_p.save(f) - - def test_gif_with_rgba_palette_to_p(self): - # See https://github.com/python-pillow/Pillow/issues/2433 - with Image.open("Tests/images/hopper.gif") as im: - im.info["transparency"] = 255 - im.load() - self.assertEqual(im.palette.mode, "RGBA") - im_p = im.convert("P") - - # Should not raise ValueError: unrecognized raw mode - im_p.load() - - def test_p_la(self): - im = hopper("RGBA") - alpha = hopper("L") - im.putalpha(alpha) - - comparable = im.convert("P").convert("LA").getchannel("A") - - assert_image_similar(alpha, comparable, 5) - - def test_matrix_illegal_conversion(self): - # Arrange - im = hopper("CMYK") + im.info["transparency"] = (255, 0, 0) # fmt: off matrix = ( 0.412453, 0.357580, 0.180423, 0, 0.212671, 0.715160, 0.072169, 0, 0.019334, 0.119193, 0.950227, 0) # fmt: on - self.assertNotEqual(im.mode, "RGB") - - # Act / Assert - self.assertRaises(ValueError, im.convert, mode="CMYK", matrix=matrix) - - def test_matrix_wrong_mode(self): - # Arrange - im = hopper("L") - # fmt: off - matrix = ( - 0.412453, 0.357580, 0.180423, 0, - 0.212671, 0.715160, 0.072169, 0, - 0.019334, 0.119193, 0.950227, 0) - # fmt: on - self.assertEqual(im.mode, "L") - - # Act / Assert - self.assertRaises(ValueError, im.convert, mode="L", matrix=matrix) - - def test_matrix_xyz(self): - def matrix_convert(mode): - # Arrange - im = hopper("RGB") - im.info["transparency"] = (255, 0, 0) - # fmt: off - matrix = ( - 0.412453, 0.357580, 0.180423, 0, - 0.212671, 0.715160, 0.072169, 0, - 0.019334, 0.119193, 0.950227, 0) - # fmt: on - self.assertEqual(im.mode, "RGB") - - # Act - # Convert an RGB image to the CIE XYZ colour space - converted_im = im.convert(mode=mode, matrix=matrix) - - # Assert - self.assertEqual(converted_im.mode, mode) - self.assertEqual(converted_im.size, im.size) - with Image.open("Tests/images/hopper-XYZ.png") as target: - if converted_im.mode == "RGB": - assert_image_similar(converted_im, target, 3) - self.assertEqual(converted_im.info["transparency"], (105, 54, 4)) - else: - assert_image_similar(converted_im, target.getchannel(0), 1) - self.assertEqual(converted_im.info["transparency"], 105) - - matrix_convert("RGB") - matrix_convert("L") - - def test_matrix_identity(self): - # Arrange - im = hopper("RGB") - # fmt: off - identity_matrix = ( - 1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0) - # fmt: on - self.assertEqual(im.mode, "RGB") + assert im.mode == "RGB" # Act - # Convert with an identity matrix - converted_im = im.convert(mode="RGB", matrix=identity_matrix) + # Convert an RGB image to the CIE XYZ colour space + converted_im = im.convert(mode=mode, matrix=matrix) # Assert - # No change - assert_image_equal(converted_im, im) + assert converted_im.mode == mode + assert converted_im.size == im.size + with Image.open("Tests/images/hopper-XYZ.png") as target: + if converted_im.mode == "RGB": + assert_image_similar(converted_im, target, 3) + assert converted_im.info["transparency"] == (105, 54, 4) + else: + assert_image_similar(converted_im, target.getchannel(0), 1) + assert converted_im.info["transparency"] == 105 + + matrix_convert("RGB") + matrix_convert("L") + + +def test_matrix_identity(): + # Arrange + im = hopper("RGB") + # fmt: off + identity_matrix = ( + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0) + # fmt: on + assert im.mode == "RGB" + + # Act + # Convert with an identity matrix + converted_im = im.convert(mode="RGB", matrix=identity_matrix) + + # Assert + # No change + assert_image_equal(converted_im, im) diff --git a/Tests/test_image_crop.py b/Tests/test_image_crop.py index 70ceb1c0f..e2228758c 100644 --- a/Tests/test_image_crop.py +++ b/Tests/test_image_crop.py @@ -1,103 +1,110 @@ +import pytest + from PIL import Image -from .helper import PillowTestCase, assert_image_equal, hopper +from .helper import assert_image_equal, hopper -class TestImageCrop(PillowTestCase): - def test_crop(self): - def crop(mode): - im = hopper(mode) - assert_image_equal(im.crop(), im) +def test_crop(): + def crop(mode): + im = hopper(mode) + assert_image_equal(im.crop(), im) - cropped = im.crop((50, 50, 100, 100)) - self.assertEqual(cropped.mode, mode) - self.assertEqual(cropped.size, (50, 50)) + cropped = im.crop((50, 50, 100, 100)) + assert cropped.mode == mode + assert cropped.size == (50, 50) - for mode in "1", "P", "L", "RGB", "I", "F": - crop(mode) + for mode in "1", "P", "L", "RGB", "I", "F": + crop(mode) - def test_wide_crop(self): - def crop(*bbox): - i = im.crop(bbox) - h = i.histogram() - while h and not h[-1]: - del h[-1] - return tuple(h) - im = Image.new("L", (100, 100), 1) +def test_wide_crop(): + def crop(*bbox): + i = im.crop(bbox) + h = i.histogram() + while h and not h[-1]: + del h[-1] + return tuple(h) - self.assertEqual(crop(0, 0, 100, 100), (0, 10000)) - self.assertEqual(crop(25, 25, 75, 75), (0, 2500)) + im = Image.new("L", (100, 100), 1) - # sides - self.assertEqual(crop(-25, 0, 25, 50), (1250, 1250)) - self.assertEqual(crop(0, -25, 50, 25), (1250, 1250)) - self.assertEqual(crop(75, 0, 125, 50), (1250, 1250)) - self.assertEqual(crop(0, 75, 50, 125), (1250, 1250)) + assert crop(0, 0, 100, 100) == (0, 10000) + assert crop(25, 25, 75, 75) == (0, 2500) - self.assertEqual(crop(-25, 25, 125, 75), (2500, 5000)) - self.assertEqual(crop(25, -25, 75, 125), (2500, 5000)) + # sides + assert crop(-25, 0, 25, 50) == (1250, 1250) + assert crop(0, -25, 50, 25) == (1250, 1250) + assert crop(75, 0, 125, 50) == (1250, 1250) + assert crop(0, 75, 50, 125) == (1250, 1250) - # corners - self.assertEqual(crop(-25, -25, 25, 25), (1875, 625)) - self.assertEqual(crop(75, -25, 125, 25), (1875, 625)) - self.assertEqual(crop(75, 75, 125, 125), (1875, 625)) - self.assertEqual(crop(-25, 75, 25, 125), (1875, 625)) + assert crop(-25, 25, 125, 75) == (2500, 5000) + assert crop(25, -25, 75, 125) == (2500, 5000) - def test_negative_crop(self): - # Check negative crop size (@PIL171) + # corners + assert crop(-25, -25, 25, 25) == (1875, 625) + assert crop(75, -25, 125, 25) == (1875, 625) + assert crop(75, 75, 125, 125) == (1875, 625) + assert crop(-25, 75, 25, 125) == (1875, 625) - im = Image.new("L", (512, 512)) - im = im.crop((400, 400, 200, 200)) - self.assertEqual(im.size, (0, 0)) - self.assertEqual(len(im.getdata()), 0) - self.assertRaises(IndexError, lambda: im.getdata()[0]) +def test_negative_crop(): + # Check negative crop size (@PIL171) - def test_crop_float(self): - # Check cropping floats are rounded to nearest integer - # https://github.com/python-pillow/Pillow/issues/1744 + im = Image.new("L", (512, 512)) + im = im.crop((400, 400, 200, 200)) - # Arrange - im = Image.new("RGB", (10, 10)) - self.assertEqual(im.size, (10, 10)) + assert im.size == (0, 0) + assert len(im.getdata()) == 0 + with pytest.raises(IndexError): + im.getdata()[0] - # Act - cropped = im.crop((0.9, 1.1, 4.2, 5.8)) - # Assert - self.assertEqual(cropped.size, (3, 5)) +def test_crop_float(): + # Check cropping floats are rounded to nearest integer + # https://github.com/python-pillow/Pillow/issues/1744 - def test_crop_crash(self): - # Image.crop crashes prepatch with an access violation - # apparently a use after free on windows, see - # https://github.com/python-pillow/Pillow/issues/1077 + # Arrange + im = Image.new("RGB", (10, 10)) + assert im.size == (10, 10) - test_img = "Tests/images/bmp/g/pal8-0.bmp" - extents = (1, 1, 10, 10) - # works prepatch - with Image.open(test_img) as img: - img2 = img.crop(extents) - img2.load() + # Act + cropped = im.crop((0.9, 1.1, 4.2, 5.8)) - # fail prepatch - with Image.open(test_img) as img: - img = img.crop(extents) - img.load() + # Assert + assert cropped.size == (3, 5) - def test_crop_zero(self): - im = Image.new("RGB", (0, 0), "white") +def test_crop_crash(): + # Image.crop crashes prepatch with an access violation + # apparently a use after free on Windows, see + # https://github.com/python-pillow/Pillow/issues/1077 - cropped = im.crop((0, 0, 0, 0)) - self.assertEqual(cropped.size, (0, 0)) + test_img = "Tests/images/bmp/g/pal8-0.bmp" + extents = (1, 1, 10, 10) + # works prepatch + with Image.open(test_img) as img: + img2 = img.crop(extents) + img2.load() - cropped = im.crop((10, 10, 20, 20)) - self.assertEqual(cropped.size, (10, 10)) - self.assertEqual(cropped.getdata()[0], (0, 0, 0)) + # fail prepatch + with Image.open(test_img) as img: + img = img.crop(extents) + img.load() - im = Image.new("RGB", (0, 0)) - cropped = im.crop((10, 10, 20, 20)) - self.assertEqual(cropped.size, (10, 10)) - self.assertEqual(cropped.getdata()[2], (0, 0, 0)) +def test_crop_zero(): + + im = Image.new("RGB", (0, 0), "white") + + cropped = im.crop((0, 0, 0, 0)) + assert cropped.size == (0, 0) + + cropped = im.crop((10, 10, 20, 20)) + assert cropped.size == (10, 10) + assert cropped.getdata()[0] == (0, 0, 0) + + im = Image.new("RGB", (0, 0)) + + cropped = im.crop((10, 10, 20, 20)) + assert cropped.size == (10, 10) + assert cropped.getdata()[2] == (0, 0, 0) diff --git a/Tests/test_image_draft.py b/Tests/test_image_draft.py index a185b3a63..8b4b44768 100644 --- a/Tests/test_image_draft.py +++ b/Tests/test_image_draft.py @@ -1,73 +1,72 @@ from PIL import Image -from .helper import PillowTestCase, fromstring, tostring +from .helper import fromstring, skip_unless_feature, tostring + +pytestmark = skip_unless_feature("jpg") -class TestImageDraft(PillowTestCase): - def setUp(self): - codecs = dir(Image.core) - if "jpeg_encoder" not in codecs or "jpeg_decoder" not in codecs: - self.skipTest("jpeg support not available") +def draft_roundtrip(in_mode, in_size, req_mode, req_size): + im = Image.new(in_mode, in_size) + data = tostring(im, "JPEG") + im = fromstring(data) + mode, box = im.draft(req_mode, req_size) + scale, _ = im.decoderconfig + assert box[:2] == (0, 0) + assert (im.width - scale) < box[2] <= im.width + assert (im.height - scale) < box[3] <= im.height + return im - def draft_roundtrip(self, in_mode, in_size, req_mode, req_size): - im = Image.new(in_mode, in_size) - data = tostring(im, "JPEG") - im = fromstring(data) - mode, box = im.draft(req_mode, req_size) - scale, _ = im.decoderconfig - self.assertEqual(box[:2], (0, 0)) - self.assertTrue((im.width - scale) < box[2] <= im.width) - self.assertTrue((im.height - scale) < box[3] <= im.height) - return im - def test_size(self): - for in_size, req_size, out_size in [ - ((435, 361), (2048, 2048), (435, 361)), # bigger - ((435, 361), (435, 361), (435, 361)), # same - ((128, 128), (64, 64), (64, 64)), - ((128, 128), (32, 32), (32, 32)), - ((128, 128), (16, 16), (16, 16)), - # large requested width - ((435, 361), (218, 128), (435, 361)), # almost 2x - ((435, 361), (217, 128), (218, 181)), # more than 2x - ((435, 361), (109, 64), (218, 181)), # almost 4x - ((435, 361), (108, 64), (109, 91)), # more than 4x - ((435, 361), (55, 32), (109, 91)), # almost 8x - ((435, 361), (54, 32), (55, 46)), # more than 8x - ((435, 361), (27, 16), (55, 46)), # more than 16x - # and vice versa - ((435, 361), (128, 181), (435, 361)), # almost 2x - ((435, 361), (128, 180), (218, 181)), # more than 2x - ((435, 361), (64, 91), (218, 181)), # almost 4x - ((435, 361), (64, 90), (109, 91)), # more than 4x - ((435, 361), (32, 46), (109, 91)), # almost 8x - ((435, 361), (32, 45), (55, 46)), # more than 8x - ((435, 361), (16, 22), (55, 46)), # more than 16x - ]: - im = self.draft_roundtrip("L", in_size, None, req_size) - im.load() - self.assertEqual(im.size, out_size) - - def test_mode(self): - for in_mode, req_mode, out_mode in [ - ("RGB", "1", "RGB"), - ("RGB", "L", "L"), - ("RGB", "RGB", "RGB"), - ("RGB", "YCbCr", "YCbCr"), - ("L", "1", "L"), - ("L", "L", "L"), - ("L", "RGB", "L"), - ("L", "YCbCr", "L"), - ("CMYK", "1", "CMYK"), - ("CMYK", "L", "CMYK"), - ("CMYK", "RGB", "CMYK"), - ("CMYK", "YCbCr", "CMYK"), - ]: - im = self.draft_roundtrip(in_mode, (64, 64), req_mode, None) - im.load() - self.assertEqual(im.mode, out_mode) - - def test_several_drafts(self): - im = self.draft_roundtrip("L", (128, 128), None, (64, 64)) - im.draft(None, (64, 64)) +def test_size(): + for in_size, req_size, out_size in [ + ((435, 361), (2048, 2048), (435, 361)), # bigger + ((435, 361), (435, 361), (435, 361)), # same + ((128, 128), (64, 64), (64, 64)), + ((128, 128), (32, 32), (32, 32)), + ((128, 128), (16, 16), (16, 16)), + # large requested width + ((435, 361), (218, 128), (435, 361)), # almost 2x + ((435, 361), (217, 128), (218, 181)), # more than 2x + ((435, 361), (109, 64), (218, 181)), # almost 4x + ((435, 361), (108, 64), (109, 91)), # more than 4x + ((435, 361), (55, 32), (109, 91)), # almost 8x + ((435, 361), (54, 32), (55, 46)), # more than 8x + ((435, 361), (27, 16), (55, 46)), # more than 16x + # and vice versa + ((435, 361), (128, 181), (435, 361)), # almost 2x + ((435, 361), (128, 180), (218, 181)), # more than 2x + ((435, 361), (64, 91), (218, 181)), # almost 4x + ((435, 361), (64, 90), (109, 91)), # more than 4x + ((435, 361), (32, 46), (109, 91)), # almost 8x + ((435, 361), (32, 45), (55, 46)), # more than 8x + ((435, 361), (16, 22), (55, 46)), # more than 16x + ]: + im = draft_roundtrip("L", in_size, None, req_size) im.load() + assert im.size == out_size + + +def test_mode(): + for in_mode, req_mode, out_mode in [ + ("RGB", "1", "RGB"), + ("RGB", "L", "L"), + ("RGB", "RGB", "RGB"), + ("RGB", "YCbCr", "YCbCr"), + ("L", "1", "L"), + ("L", "L", "L"), + ("L", "RGB", "L"), + ("L", "YCbCr", "L"), + ("CMYK", "1", "CMYK"), + ("CMYK", "L", "CMYK"), + ("CMYK", "RGB", "CMYK"), + ("CMYK", "YCbCr", "CMYK"), + ]: + im = draft_roundtrip(in_mode, (64, 64), req_mode, None) + im.load() + assert im.mode == out_mode + + +def test_several_drafts(): + im = draft_roundtrip("L", (128, 128), None, (64, 64)) + im.draft(None, (64, 64)) + im.load() diff --git a/Tests/test_image_entropy.py b/Tests/test_image_entropy.py index 941f7665d..876d676fe 100644 --- a/Tests/test_image_entropy.py +++ b/Tests/test_image_entropy.py @@ -1,17 +1,16 @@ -from .helper import PillowTestCase, hopper +from .helper import hopper -class TestImageEntropy(PillowTestCase): - def test_entropy(self): - def entropy(mode): - return hopper(mode).entropy() +def test_entropy(): + def entropy(mode): + return hopper(mode).entropy() - self.assertAlmostEqual(entropy("1"), 0.9138803254693582) - self.assertAlmostEqual(entropy("L"), 7.063008716585465) - self.assertAlmostEqual(entropy("I"), 7.063008716585465) - self.assertAlmostEqual(entropy("F"), 7.063008716585465) - self.assertAlmostEqual(entropy("P"), 5.0530452472519745) - self.assertAlmostEqual(entropy("RGB"), 8.821286587714319) - self.assertAlmostEqual(entropy("RGBA"), 7.42724306524488) - self.assertAlmostEqual(entropy("CMYK"), 7.4272430652448795) - self.assertAlmostEqual(entropy("YCbCr"), 7.698360534903628) + assert round(abs(entropy("1") - 0.9138803254693582), 7) == 0 + assert round(abs(entropy("L") - 7.063008716585465), 7) == 0 + assert round(abs(entropy("I") - 7.063008716585465), 7) == 0 + assert round(abs(entropy("F") - 7.063008716585465), 7) == 0 + assert round(abs(entropy("P") - 5.0530452472519745), 7) == 0 + assert round(abs(entropy("RGB") - 8.821286587714319), 7) == 0 + assert round(abs(entropy("RGBA") - 7.42724306524488), 7) == 0 + assert round(abs(entropy("CMYK") - 7.4272430652448795), 7) == 0 + assert round(abs(entropy("YCbCr") - 7.698360534903628), 7) == 0 diff --git a/Tests/test_image_filter.py b/Tests/test_image_filter.py index 6b36267b3..df8c353f3 100644 --- a/Tests/test_image_filter.py +++ b/Tests/test_image_filter.py @@ -1,143 +1,156 @@ +import pytest + from PIL import Image, ImageFilter -from .helper import PillowTestCase, assert_image_equal, hopper +from .helper import assert_image_equal, hopper -class TestImageFilter(PillowTestCase): - def test_sanity(self): - def filter(filter): - for mode in ["L", "RGB", "CMYK"]: - im = hopper(mode) - out = im.filter(filter) - self.assertEqual(out.mode, im.mode) - self.assertEqual(out.size, im.size) +def test_sanity(): + def apply_filter(filter_to_apply): + for mode in ["L", "RGB", "CMYK"]: + im = hopper(mode) + out = im.filter(filter_to_apply) + assert out.mode == im.mode + assert out.size == im.size - filter(ImageFilter.BLUR) - filter(ImageFilter.CONTOUR) - filter(ImageFilter.DETAIL) - filter(ImageFilter.EDGE_ENHANCE) - filter(ImageFilter.EDGE_ENHANCE_MORE) - filter(ImageFilter.EMBOSS) - filter(ImageFilter.FIND_EDGES) - filter(ImageFilter.SMOOTH) - filter(ImageFilter.SMOOTH_MORE) - filter(ImageFilter.SHARPEN) - filter(ImageFilter.MaxFilter) - filter(ImageFilter.MedianFilter) - filter(ImageFilter.MinFilter) - filter(ImageFilter.ModeFilter) - filter(ImageFilter.GaussianBlur) - filter(ImageFilter.GaussianBlur(5)) - filter(ImageFilter.BoxBlur(5)) - filter(ImageFilter.UnsharpMask) - filter(ImageFilter.UnsharpMask(10)) + apply_filter(ImageFilter.BLUR) + apply_filter(ImageFilter.CONTOUR) + apply_filter(ImageFilter.DETAIL) + apply_filter(ImageFilter.EDGE_ENHANCE) + apply_filter(ImageFilter.EDGE_ENHANCE_MORE) + apply_filter(ImageFilter.EMBOSS) + apply_filter(ImageFilter.FIND_EDGES) + apply_filter(ImageFilter.SMOOTH) + apply_filter(ImageFilter.SMOOTH_MORE) + apply_filter(ImageFilter.SHARPEN) + apply_filter(ImageFilter.MaxFilter) + apply_filter(ImageFilter.MedianFilter) + apply_filter(ImageFilter.MinFilter) + apply_filter(ImageFilter.ModeFilter) + apply_filter(ImageFilter.GaussianBlur) + apply_filter(ImageFilter.GaussianBlur(5)) + apply_filter(ImageFilter.BoxBlur(5)) + apply_filter(ImageFilter.UnsharpMask) + apply_filter(ImageFilter.UnsharpMask(10)) - self.assertRaises(TypeError, filter, "hello") + with pytest.raises(TypeError): + apply_filter("hello") - def test_crash(self): - # crashes on small images - im = Image.new("RGB", (1, 1)) - im.filter(ImageFilter.SMOOTH) +def test_crash(): - im = Image.new("RGB", (2, 2)) - im.filter(ImageFilter.SMOOTH) + # crashes on small images + im = Image.new("RGB", (1, 1)) + im.filter(ImageFilter.SMOOTH) - im = Image.new("RGB", (3, 3)) - im.filter(ImageFilter.SMOOTH) + im = Image.new("RGB", (2, 2)) + im.filter(ImageFilter.SMOOTH) - def test_modefilter(self): - def modefilter(mode): - im = Image.new(mode, (3, 3), None) - im.putdata(list(range(9))) - # image is: - # 0 1 2 - # 3 4 5 - # 6 7 8 - mod = im.filter(ImageFilter.ModeFilter).getpixel((1, 1)) - im.putdata([0, 0, 1, 2, 5, 1, 5, 2, 0]) # mode=0 - mod2 = im.filter(ImageFilter.ModeFilter).getpixel((1, 1)) - return mod, mod2 + im = Image.new("RGB", (3, 3)) + im.filter(ImageFilter.SMOOTH) - self.assertEqual(modefilter("1"), (4, 0)) - self.assertEqual(modefilter("L"), (4, 0)) - self.assertEqual(modefilter("P"), (4, 0)) - self.assertEqual(modefilter("RGB"), ((4, 0, 0), (0, 0, 0))) - def test_rankfilter(self): - def rankfilter(mode): - im = Image.new(mode, (3, 3), None) - im.putdata(list(range(9))) - # image is: - # 0 1 2 - # 3 4 5 - # 6 7 8 - minimum = im.filter(ImageFilter.MinFilter).getpixel((1, 1)) - med = im.filter(ImageFilter.MedianFilter).getpixel((1, 1)) - maximum = im.filter(ImageFilter.MaxFilter).getpixel((1, 1)) - return minimum, med, maximum +def test_modefilter(): + def modefilter(mode): + im = Image.new(mode, (3, 3), None) + im.putdata(list(range(9))) + # image is: + # 0 1 2 + # 3 4 5 + # 6 7 8 + mod = im.filter(ImageFilter.ModeFilter).getpixel((1, 1)) + im.putdata([0, 0, 1, 2, 5, 1, 5, 2, 0]) # mode=0 + mod2 = im.filter(ImageFilter.ModeFilter).getpixel((1, 1)) + return mod, mod2 - self.assertEqual(rankfilter("1"), (0, 4, 8)) - self.assertEqual(rankfilter("L"), (0, 4, 8)) - self.assertRaises(ValueError, rankfilter, "P") - self.assertEqual(rankfilter("RGB"), ((0, 0, 0), (4, 0, 0), (8, 0, 0))) - self.assertEqual(rankfilter("I"), (0, 4, 8)) - self.assertEqual(rankfilter("F"), (0.0, 4.0, 8.0)) + assert modefilter("1") == (4, 0) + assert modefilter("L") == (4, 0) + assert modefilter("P") == (4, 0) + assert modefilter("RGB") == ((4, 0, 0), (0, 0, 0)) - def test_rankfilter_properties(self): - rankfilter = ImageFilter.RankFilter(1, 2) - self.assertEqual(rankfilter.size, 1) - self.assertEqual(rankfilter.rank, 2) +def test_rankfilter(): + def rankfilter(mode): + im = Image.new(mode, (3, 3), None) + im.putdata(list(range(9))) + # image is: + # 0 1 2 + # 3 4 5 + # 6 7 8 + minimum = im.filter(ImageFilter.MinFilter).getpixel((1, 1)) + med = im.filter(ImageFilter.MedianFilter).getpixel((1, 1)) + maximum = im.filter(ImageFilter.MaxFilter).getpixel((1, 1)) + return minimum, med, maximum - def test_builtinfilter_p(self): - builtinFilter = ImageFilter.BuiltinFilter() + assert rankfilter("1") == (0, 4, 8) + assert rankfilter("L") == (0, 4, 8) + with pytest.raises(ValueError): + rankfilter("P") + assert rankfilter("RGB") == ((0, 0, 0), (4, 0, 0), (8, 0, 0)) + assert rankfilter("I") == (0, 4, 8) + assert rankfilter("F") == (0.0, 4.0, 8.0) - self.assertRaises(ValueError, builtinFilter.filter, hopper("P")) - def test_kernel_not_enough_coefficients(self): - self.assertRaises(ValueError, lambda: ImageFilter.Kernel((3, 3), (0, 0))) +def test_rankfilter_properties(): + rankfilter = ImageFilter.RankFilter(1, 2) - def test_consistency_3x3(self): - with Image.open("Tests/images/hopper.bmp") as source: - with Image.open("Tests/images/hopper_emboss.bmp") as reference: - kernel = ImageFilter.Kernel( # noqa: E127 - (3, 3), - # fmt: off - (-1, -1, 0, - -1, 0, 1, - 0, 1, 1), - # fmt: on - 0.3, + assert rankfilter.size == 1 + assert rankfilter.rank == 2 + + +def test_builtinfilter_p(): + builtinFilter = ImageFilter.BuiltinFilter() + + with pytest.raises(ValueError): + builtinFilter.filter(hopper("P")) + + +def test_kernel_not_enough_coefficients(): + with pytest.raises(ValueError): + ImageFilter.Kernel((3, 3), (0, 0)) + + +def test_consistency_3x3(): + with Image.open("Tests/images/hopper.bmp") as source: + with Image.open("Tests/images/hopper_emboss.bmp") as reference: + kernel = ImageFilter.Kernel( + (3, 3), + # fmt: off + (-1, -1, 0, + -1, 0, 1, + 0, 1, 1), + # fmt: on + 0.3, + ) + source = source.split() * 2 + reference = reference.split() * 2 + + for mode in ["L", "LA", "RGB", "CMYK"]: + assert_image_equal( + Image.merge(mode, source[: len(mode)]).filter(kernel), + Image.merge(mode, reference[: len(mode)]), ) - source = source.split() * 2 - reference = reference.split() * 2 - for mode in ["L", "LA", "RGB", "CMYK"]: - assert_image_equal( - Image.merge(mode, source[: len(mode)]).filter(kernel), - Image.merge(mode, reference[: len(mode)]), - ) - def test_consistency_5x5(self): - with Image.open("Tests/images/hopper.bmp") as source: - with Image.open("Tests/images/hopper_emboss_more.bmp") as reference: - kernel = ImageFilter.Kernel( # noqa: E127 - (5, 5), - # fmt: off - (-1, -1, -1, -1, 0, - -1, -1, -1, 0, 1, - -1, -1, 0, 1, 1, - -1, 0, 1, 1, 1, - 0, 1, 1, 1, 1), - # fmt: on - 0.3, +def test_consistency_5x5(): + with Image.open("Tests/images/hopper.bmp") as source: + with Image.open("Tests/images/hopper_emboss_more.bmp") as reference: + kernel = ImageFilter.Kernel( + (5, 5), + # fmt: off + (-1, -1, -1, -1, 0, + -1, -1, -1, 0, 1, + -1, -1, 0, 1, 1, + -1, 0, 1, 1, 1, + 0, 1, 1, 1, 1), + # fmt: on + 0.3, + ) + source = source.split() * 2 + reference = reference.split() * 2 + + for mode in ["L", "LA", "RGB", "CMYK"]: + assert_image_equal( + Image.merge(mode, source[: len(mode)]).filter(kernel), + Image.merge(mode, reference[: len(mode)]), ) - source = source.split() * 2 - reference = reference.split() * 2 - - for mode in ["L", "LA", "RGB", "CMYK"]: - assert_image_equal( - Image.merge(mode, source[: len(mode)]).filter(kernel), - Image.merge(mode, reference[: len(mode)]), - ) diff --git a/Tests/test_image_frombytes.py b/Tests/test_image_frombytes.py index 88c196432..7fb05cda7 100644 --- a/Tests/test_image_frombytes.py +++ b/Tests/test_image_frombytes.py @@ -1,14 +1,10 @@ from PIL import Image -from .helper import PillowTestCase, assert_image_equal, hopper +from .helper import assert_image_equal, hopper -class TestImageFromBytes(PillowTestCase): - def test_sanity(self): - im1 = hopper() - im2 = Image.frombytes(im1.mode, im1.size, im1.tobytes()) +def test_sanity(): + im1 = hopper() + im2 = Image.frombytes(im1.mode, im1.size, im1.tobytes()) - assert_image_equal(im1, im2) - - def test_not_implemented(self): - self.assertRaises(NotImplementedError, Image.fromstring) + assert_image_equal(im1, im2) diff --git a/Tests/test_image_fromqimage.py b/Tests/test_image_fromqimage.py index ee26b75bc..5ad5b5c3c 100644 --- a/Tests/test_image_fromqimage.py +++ b/Tests/test_image_fromqimage.py @@ -1,47 +1,60 @@ +import pytest + from PIL import Image, ImageQt -from .helper import PillowTestCase, assert_image_equal, hopper -from .test_imageqt import PillowQtTestCase +from .helper import assert_image_equal, hopper + +pytestmark = pytest.mark.skipif( + not ImageQt.qt_is_installed, reason="Qt bindings are not installed" +) -class TestFromQImage(PillowQtTestCase, PillowTestCase): - def setUp(self): - super().setUp() - self.files_to_test = [ - hopper(), - Image.open("Tests/images/transparent.png"), - Image.open("Tests/images/7x13.png"), - ] - for im in self.files_to_test: - self.addCleanup(im.close) +@pytest.fixture +def test_images(): + ims = [ + hopper(), + Image.open("Tests/images/transparent.png"), + Image.open("Tests/images/7x13.png"), + ] + try: + yield ims + finally: + for im in ims: + im.close() - def roundtrip(self, expected): - # PIL -> Qt - intermediate = expected.toqimage() - # Qt -> PIL - result = ImageQt.fromqimage(intermediate) - if intermediate.hasAlphaChannel(): - assert_image_equal(result, expected.convert("RGBA")) - else: - assert_image_equal(result, expected.convert("RGB")) +def roundtrip(expected): + # PIL -> Qt + intermediate = expected.toqimage() + # Qt -> PIL + result = ImageQt.fromqimage(intermediate) - def test_sanity_1(self): - for im in self.files_to_test: - self.roundtrip(im.convert("1")) + if intermediate.hasAlphaChannel(): + assert_image_equal(result, expected.convert("RGBA")) + else: + assert_image_equal(result, expected.convert("RGB")) - def test_sanity_rgb(self): - for im in self.files_to_test: - self.roundtrip(im.convert("RGB")) - def test_sanity_rgba(self): - for im in self.files_to_test: - self.roundtrip(im.convert("RGBA")) +def test_sanity_1(test_images): + for im in test_images: + roundtrip(im.convert("1")) - def test_sanity_l(self): - for im in self.files_to_test: - self.roundtrip(im.convert("L")) - def test_sanity_p(self): - for im in self.files_to_test: - self.roundtrip(im.convert("P")) +def test_sanity_rgb(test_images): + for im in test_images: + roundtrip(im.convert("RGB")) + + +def test_sanity_rgba(test_images): + for im in test_images: + roundtrip(im.convert("RGBA")) + + +def test_sanity_l(test_images): + for im in test_images: + roundtrip(im.convert("L")) + + +def test_sanity_p(test_images): + for im in test_images: + roundtrip(im.convert("P")) diff --git a/Tests/test_image_getbbox.py b/Tests/test_image_getbbox.py index a80a1ca9d..c86e33eb2 100644 --- a/Tests/test_image_getbbox.py +++ b/Tests/test_image_getbbox.py @@ -10,29 +10,32 @@ def test_sanity(): def test_bbox(): + def check(im, fill_color): + assert im.getbbox() is None + + im.paste(fill_color, (10, 25, 90, 75)) + assert im.getbbox() == (10, 25, 90, 75) + + im.paste(fill_color, (25, 10, 75, 90)) + assert im.getbbox() == (10, 10, 90, 90) + + im.paste(fill_color, (-10, -10, 110, 110)) + assert im.getbbox() == (0, 0, 100, 100) # 8-bit mode im = Image.new("L", (100, 100), 0) - assert im.getbbox() is None - - im.paste(255, (10, 25, 90, 75)) - assert im.getbbox() == (10, 25, 90, 75) - - im.paste(255, (25, 10, 75, 90)) - assert im.getbbox() == (10, 10, 90, 90) - - im.paste(255, (-10, -10, 110, 110)) - assert im.getbbox() == (0, 0, 100, 100) + check(im, 255) # 32-bit mode im = Image.new("RGB", (100, 100), 0) - assert im.getbbox() is None + check(im, 255) - im.paste(255, (10, 25, 90, 75)) - assert im.getbbox() == (10, 25, 90, 75) + for mode in ("RGBA", "RGBa"): + for color in ((0, 0, 0, 0), (127, 127, 127, 0), (255, 255, 255, 0)): + im = Image.new(mode, (100, 100), color) + check(im, (255, 255, 255, 255)) - im.paste(255, (25, 10, 75, 90)) - assert im.getbbox() == (10, 10, 90, 90) - - im.paste(255, (-10, -10, 110, 110)) - assert im.getbbox() == (0, 0, 100, 100) + for mode in ("La", "LA", "PA"): + for color in ((0, 0), (127, 0), (255, 0)): + im = Image.new(mode, (100, 100), color) + check(im, (255, 255)) diff --git a/Tests/test_image_getextrema.py b/Tests/test_image_getextrema.py index 17ec61e3e..710794da4 100644 --- a/Tests/test_image_getextrema.py +++ b/Tests/test_image_getextrema.py @@ -1,10 +1,8 @@ -import pytest from PIL import Image -from .helper import hopper, is_big_endian, on_ci +from .helper import hopper -@pytest.mark.xfail(is_big_endian() and on_ci(), reason="Fails on big-endian") def test_extrema(): def extrema(mode): return hopper(mode).getextrema() @@ -20,7 +18,6 @@ def test_extrema(): assert extrema("I;16") == (1, 255) -@pytest.mark.xfail(is_big_endian() and on_ci(), reason="Fails on big-endian") def test_true_16(): with Image.open("Tests/images/16_bit_noise.tif") as im: assert im.mode == "I;16" diff --git a/Tests/test_image_load.py b/Tests/test_image_load.py index efb9a1452..f7fe99bb4 100644 --- a/Tests/test_image_load.py +++ b/Tests/test_image_load.py @@ -1,6 +1,8 @@ +import logging import os import pytest + from PIL import Image from .helper import hopper @@ -22,6 +24,14 @@ def test_close(): im.getpixel((0, 0)) +def test_close_after_load(caplog): + im = Image.open("Tests/images/hopper.gif") + im.load() + with caplog.at_level(logging.DEBUG): + im.close() + assert len(caplog.records) == 0 + + def test_contextmanager(): fn = None with Image.open("Tests/images/hopper.gif") as im: diff --git a/Tests/test_image_mode.py b/Tests/test_image_mode.py index 23e09aaae..7f92c2264 100644 --- a/Tests/test_image_mode.py +++ b/Tests/test_image_mode.py @@ -1,4 +1,4 @@ -from PIL import Image +from PIL import Image, ImageMode from .helper import hopper @@ -8,8 +8,6 @@ def test_sanity(): with hopper() as im: im.mode - from PIL import ImageMode - ImageMode.getmode("1") ImageMode.getmode("L") ImageMode.getmode("P") diff --git a/Tests/test_image_paste.py b/Tests/test_image_paste.py index 7f484d5ec..3740fbcdc 100644 --- a/Tests/test_image_paste.py +++ b/Tests/test_image_paste.py @@ -1,9 +1,9 @@ from PIL import Image -from .helper import PillowTestCase, assert_image_equal, cached_property +from .helper import assert_image_equal, cached_property -class TestImagingPaste(PillowTestCase): +class TestImagingPaste: masks = {} size = 128 @@ -23,7 +23,7 @@ class TestImagingPaste(PillowTestCase): px[self.size // 2, self.size - 1], px[self.size - 1, self.size - 1], ] - self.assertEqual(actual, expected) + assert actual == expected def assert_9points_paste(self, im, im2, mask, expected): im3 = im.copy() @@ -199,8 +199,8 @@ class TestImagingPaste(PillowTestCase): hist = im.crop(rect).histogram() while hist: head, hist = hist[:256], hist[256:] - self.assertEqual(head[255], 128 * 128) - self.assertEqual(sum(head[:255]), 0) + assert head[255] == 128 * 128 + assert sum(head[:255]) == 0 def test_color_mask_1(self): for mode in ("RGBA", "RGB", "L"): @@ -236,7 +236,7 @@ class TestImagingPaste(PillowTestCase): [ (127, 191, 254, 191), (111, 207, 206, 110), - (127, 254, 127, 0), + (255, 255, 255, 0) if mode == "RGBA" else (127, 254, 127, 0), (207, 207, 239, 239), (191, 191, 190, 191), (207, 206, 111, 112), diff --git a/Tests/test_image_point.py b/Tests/test_image_point.py index 36772d72c..51108ead2 100644 --- a/Tests/test_image_point.py +++ b/Tests/test_image_point.py @@ -1,39 +1,48 @@ -from .helper import PillowTestCase, assert_image_equal, hopper +import pytest + +from .helper import assert_image_equal, hopper -class TestImagePoint(PillowTestCase): - def test_sanity(self): - im = hopper() +def test_sanity(): + im = hopper() - self.assertRaises(ValueError, im.point, list(range(256))) - im.point(list(range(256)) * 3) - im.point(lambda x: x) + with pytest.raises(ValueError): + im.point(list(range(256))) + im.point(list(range(256)) * 3) + im.point(lambda x: x) - im = im.convert("I") - self.assertRaises(ValueError, im.point, list(range(256))) - im.point(lambda x: x * 1) - im.point(lambda x: x + 1) - im.point(lambda x: x * 1 + 1) - self.assertRaises(TypeError, im.point, lambda x: x - 1) - self.assertRaises(TypeError, im.point, lambda x: x / 1) + im = im.convert("I") + with pytest.raises(ValueError): + im.point(list(range(256))) + im.point(lambda x: x * 1) + im.point(lambda x: x + 1) + im.point(lambda x: x * 1 + 1) + with pytest.raises(TypeError): + im.point(lambda x: x - 1) + with pytest.raises(TypeError): + im.point(lambda x: x / 1) - def test_16bit_lut(self): - """ Tests for 16 bit -> 8 bit lut for converting I->L images - see https://github.com/python-pillow/Pillow/issues/440 - """ - im = hopper("I") - im.point(list(range(256)) * 256, "L") - def test_f_lut(self): - """ Tests for floating point lut of 8bit gray image """ - im = hopper("L") - lut = [0.5 * float(x) for x in range(256)] +def test_16bit_lut(): + """Tests for 16 bit -> 8 bit lut for converting I->L images + see https://github.com/python-pillow/Pillow/issues/440 + """ + im = hopper("I") + im.point(list(range(256)) * 256, "L") - out = im.point(lut, "F") - int_lut = [x // 2 for x in range(256)] - assert_image_equal(out.convert("L"), im.point(int_lut, "L")) +def test_f_lut(): + """ Tests for floating point lut of 8bit gray image """ + im = hopper("L") + lut = [0.5 * float(x) for x in range(256)] - def test_f_mode(self): - im = hopper("F") - self.assertRaises(ValueError, im.point, None) + out = im.point(lut, "F") + + int_lut = [x // 2 for x in range(256)] + assert_image_equal(out.convert("L"), im.point(int_lut, "L")) + + +def test_f_mode(): + im = hopper("F") + with pytest.raises(ValueError): + im.point(None) diff --git a/Tests/test_image_putdata.py b/Tests/test_image_putdata.py index 2c6881179..54712fd6c 100644 --- a/Tests/test_image_putdata.py +++ b/Tests/test_image_putdata.py @@ -3,83 +3,87 @@ from array import array from PIL import Image -from .helper import PillowTestCase, assert_image_equal, hopper +from .helper import assert_image_equal, hopper -class TestImagePutData(PillowTestCase): - def test_sanity(self): +def test_sanity(): + im1 = hopper() - im1 = hopper() + data = list(im1.getdata()) - data = list(im1.getdata()) + im2 = Image.new(im1.mode, im1.size, 0) + im2.putdata(data) - im2 = Image.new(im1.mode, im1.size, 0) - im2.putdata(data) + assert_image_equal(im1, im2) - assert_image_equal(im1, im2) + # readonly + im2 = Image.new(im1.mode, im2.size, 0) + im2.readonly = 1 + im2.putdata(data) - # readonly - im2 = Image.new(im1.mode, im2.size, 0) - im2.readonly = 1 - im2.putdata(data) + assert not im2.readonly + assert_image_equal(im1, im2) - self.assertFalse(im2.readonly) - assert_image_equal(im1, im2) - def test_long_integers(self): - # see bug-200802-systemerror - def put(value): - im = Image.new("RGBA", (1, 1)) - im.putdata([value]) - return im.getpixel((0, 0)) +def test_long_integers(): + # see bug-200802-systemerror + def put(value): + im = Image.new("RGBA", (1, 1)) + im.putdata([value]) + return im.getpixel((0, 0)) - self.assertEqual(put(0xFFFFFFFF), (255, 255, 255, 255)) - self.assertEqual(put(0xFFFFFFFF), (255, 255, 255, 255)) - self.assertEqual(put(-1), (255, 255, 255, 255)) - self.assertEqual(put(-1), (255, 255, 255, 255)) - if sys.maxsize > 2 ** 32: - self.assertEqual(put(sys.maxsize), (255, 255, 255, 255)) - else: - self.assertEqual(put(sys.maxsize), (255, 255, 255, 127)) + assert put(0xFFFFFFFF) == (255, 255, 255, 255) + assert put(0xFFFFFFFF) == (255, 255, 255, 255) + assert put(-1) == (255, 255, 255, 255) + assert put(-1) == (255, 255, 255, 255) + if sys.maxsize > 2 ** 32: + assert put(sys.maxsize) == (255, 255, 255, 255) + else: + assert put(sys.maxsize) == (255, 255, 255, 127) - def test_pypy_performance(self): - im = Image.new("L", (256, 256)) - im.putdata(list(range(256)) * 256) - def test_mode_i(self): - src = hopper("L") - data = list(src.getdata()) - im = Image.new("I", src.size, 0) - im.putdata(data, 2, 256) +def test_pypy_performance(): + im = Image.new("L", (256, 256)) + im.putdata(list(range(256)) * 256) - target = [2 * elt + 256 for elt in data] - self.assertEqual(list(im.getdata()), target) - def test_mode_F(self): - src = hopper("L") - data = list(src.getdata()) - im = Image.new("F", src.size, 0) - im.putdata(data, 2.0, 256.0) +def test_mode_i(): + src = hopper("L") + data = list(src.getdata()) + im = Image.new("I", src.size, 0) + im.putdata(data, 2, 256) - target = [2.0 * float(elt) + 256.0 for elt in data] - self.assertEqual(list(im.getdata()), target) + target = [2 * elt + 256 for elt in data] + assert list(im.getdata()) == target - def test_array_B(self): - # shouldn't segfault - # see https://github.com/python-pillow/Pillow/issues/1008 - arr = array("B", [0]) * 15000 - im = Image.new("L", (150, 100)) - im.putdata(arr) +def test_mode_F(): + src = hopper("L") + data = list(src.getdata()) + im = Image.new("F", src.size, 0) + im.putdata(data, 2.0, 256.0) - self.assertEqual(len(im.getdata()), len(arr)) + target = [2.0 * float(elt) + 256.0 for elt in data] + assert list(im.getdata()) == target - def test_array_F(self): - # shouldn't segfault - # see https://github.com/python-pillow/Pillow/issues/1008 - im = Image.new("F", (150, 100)) - arr = array("f", [0.0]) * 15000 - im.putdata(arr) +def test_array_B(): + # shouldn't segfault + # see https://github.com/python-pillow/Pillow/issues/1008 - self.assertEqual(len(im.getdata()), len(arr)) + arr = array("B", [0]) * 15000 + im = Image.new("L", (150, 100)) + im.putdata(arr) + + assert len(im.getdata()) == len(arr) + + +def test_array_F(): + # shouldn't segfault + # see https://github.com/python-pillow/Pillow/issues/1008 + + im = Image.new("F", (150, 100)) + arr = array("f", [0.0]) * 15000 + im.putdata(arr) + + assert len(im.getdata()) == len(arr) diff --git a/Tests/test_image_putpalette.py b/Tests/test_image_putpalette.py index 7b05e88b6..5a9df11b1 100644 --- a/Tests/test_image_putpalette.py +++ b/Tests/test_image_putpalette.py @@ -1,7 +1,8 @@ import pytest -from PIL import ImagePalette -from .helper import hopper +from PIL import Image, ImagePalette + +from .helper import assert_image_equal, hopper def test_putpalette(): @@ -38,3 +39,20 @@ def test_imagepalette(): im.putpalette(ImagePalette.random()) im.putpalette(ImagePalette.sepia()) im.putpalette(ImagePalette.wedge()) + + +def test_putpalette_with_alpha_values(): + with Image.open("Tests/images/transparent.gif") as im: + expected = im.convert("RGBA") + + palette = im.getpalette() + transparency = im.info.pop("transparency") + + palette_with_alpha_values = [] + for i in range(256): + color = palette[i * 3 : i * 3 + 3] + alpha = 0 if i == transparency else 255 + palette_with_alpha_values += color + [alpha] + im.putpalette(palette_with_alpha_values, "RGBA") + + assert_image_equal(im.convert("RGBA"), expected) diff --git a/Tests/test_image_quantize.py b/Tests/test_image_quantize.py index 70bc3e488..af4172c88 100644 --- a/Tests/test_image_quantize.py +++ b/Tests/test_image_quantize.py @@ -1,67 +1,76 @@ +import pytest + from PIL import Image -from .helper import PillowTestCase, assert_image, assert_image_similar, hopper +from .helper import assert_image, assert_image_similar, hopper, is_ppc64le -class TestImageQuantize(PillowTestCase): - def test_sanity(self): - image = hopper() - converted = image.quantize() - assert_image(converted, "P", converted.size) - assert_image_similar(converted.convert("RGB"), image, 10) +def test_sanity(): + image = hopper() + converted = image.quantize() + assert_image(converted, "P", converted.size) + assert_image_similar(converted.convert("RGB"), image, 10) - image = hopper() - converted = image.quantize(palette=hopper("P")) - assert_image(converted, "P", converted.size) - assert_image_similar(converted.convert("RGB"), image, 60) + image = hopper() + converted = image.quantize(palette=hopper("P")) + assert_image(converted, "P", converted.size) + assert_image_similar(converted.convert("RGB"), image, 60) - def test_libimagequant_quantize(self): - image = hopper() - try: - converted = image.quantize(100, Image.LIBIMAGEQUANT) - except ValueError as ex: - if "dependency" in str(ex).lower(): - self.skipTest("libimagequant support not available") - else: - raise - assert_image(converted, "P", converted.size) - assert_image_similar(converted.convert("RGB"), image, 15) - self.assertEqual(len(converted.getcolors()), 100) - def test_octree_quantize(self): - image = hopper() - converted = image.quantize(100, Image.FASTOCTREE) - assert_image(converted, "P", converted.size) - assert_image_similar(converted.convert("RGB"), image, 20) - self.assertEqual(len(converted.getcolors()), 100) +@pytest.mark.xfail(is_ppc64le(), reason="failing on ppc64le on GHA") +def test_libimagequant_quantize(): + image = hopper() + try: + converted = image.quantize(100, Image.LIBIMAGEQUANT) + except ValueError as ex: # pragma: no cover + if "dependency" in str(ex).lower(): + pytest.skip("libimagequant support not available") + else: + raise + assert_image(converted, "P", converted.size) + assert_image_similar(converted.convert("RGB"), image, 15) + assert len(converted.getcolors()) == 100 - def test_rgba_quantize(self): - image = hopper("RGBA") - self.assertRaises(ValueError, image.quantize, method=0) - self.assertEqual(image.quantize().convert().mode, "RGBA") +def test_octree_quantize(): + image = hopper() + converted = image.quantize(100, Image.FASTOCTREE) + assert_image(converted, "P", converted.size) + assert_image_similar(converted.convert("RGB"), image, 20) + assert len(converted.getcolors()) == 100 - def test_quantize(self): - with Image.open("Tests/images/caption_6_33_22.png") as image: - image = image.convert("RGB") - converted = image.quantize() - assert_image(converted, "P", converted.size) - assert_image_similar(converted.convert("RGB"), image, 1) - def test_quantize_no_dither(self): - image = hopper() - with Image.open("Tests/images/caption_6_33_22.png") as palette: - palette = palette.convert("P") +def test_rgba_quantize(): + image = hopper("RGBA") + with pytest.raises(ValueError): + image.quantize(method=0) - converted = image.quantize(dither=0, palette=palette) - assert_image(converted, "P", converted.size) + assert image.quantize().convert().mode == "RGBA" - def test_quantize_dither_diff(self): - image = hopper() - with Image.open("Tests/images/caption_6_33_22.png") as palette: - palette = palette.convert("P") - dither = image.quantize(dither=1, palette=palette) - nodither = image.quantize(dither=0, palette=palette) +def test_quantize(): + with Image.open("Tests/images/caption_6_33_22.png") as image: + image = image.convert("RGB") + converted = image.quantize() + assert_image(converted, "P", converted.size) + assert_image_similar(converted.convert("RGB"), image, 1) - self.assertNotEqual(dither.tobytes(), nodither.tobytes()) + +def test_quantize_no_dither(): + image = hopper() + with Image.open("Tests/images/caption_6_33_22.png") as palette: + palette = palette.convert("P") + + converted = image.quantize(dither=0, palette=palette) + assert_image(converted, "P", converted.size) + + +def test_quantize_dither_diff(): + image = hopper() + with Image.open("Tests/images/caption_6_33_22.png") as palette: + palette = palette.convert("P") + + dither = image.quantize(dither=1, palette=palette) + nodither = image.quantize(dither=0, palette=palette) + + assert dither.tobytes() != nodither.tobytes() diff --git a/Tests/test_image_reduce.py b/Tests/test_image_reduce.py index 6f9821418..b4eebc142 100644 --- a/Tests/test_image_reduce.py +++ b/Tests/test_image_reduce.py @@ -1,244 +1,260 @@ +import pytest + from PIL import Image, ImageMath, ImageMode -from .helper import PillowTestCase, convert_to_comparable +from .helper import convert_to_comparable, skip_unless_feature + +codecs = dir(Image.core) -class TestImageReduce(PillowTestCase): - # There are several internal implementations - remarkable_factors = [ - # special implementations - 1, - 2, - 3, - 4, - 5, - 6, - # 1xN implementation - (1, 2), - (1, 3), - (1, 4), - (1, 7), - # Nx1 implementation - (2, 1), - (3, 1), - (4, 1), - (7, 1), - # general implementation with different paths - (4, 6), - (5, 6), - (4, 7), - (5, 7), - (19, 17), - ] +# There are several internal implementations +remarkable_factors = [ + # special implementations + 1, + 2, + 3, + 4, + 5, + 6, + # 1xN implementation + (1, 2), + (1, 3), + (1, 4), + (1, 7), + # Nx1 implementation + (2, 1), + (3, 1), + (4, 1), + (7, 1), + # general implementation with different paths + (4, 6), + (5, 6), + (4, 7), + (5, 7), + (19, 17), +] - @classmethod - def setUpClass(cls): - cls.gradients_image = Image.open("Tests/images/radial_gradients.png") - cls.gradients_image.load() +gradients_image = Image.open("Tests/images/radial_gradients.png") +gradients_image.load() - def test_args_factor(self): - im = Image.new("L", (10, 10)) - self.assertEqual((4, 4), im.reduce(3).size) - self.assertEqual((4, 10), im.reduce((3, 1)).size) - self.assertEqual((10, 4), im.reduce((1, 3)).size) +def test_args_factor(): + im = Image.new("L", (10, 10)) - with self.assertRaises(ValueError): - im.reduce(0) - with self.assertRaises(TypeError): - im.reduce(2.0) - with self.assertRaises(ValueError): - im.reduce((0, 10)) + assert (4, 4) == im.reduce(3).size + assert (4, 10) == im.reduce((3, 1)).size + assert (10, 4) == im.reduce((1, 3)).size - def test_args_box(self): - im = Image.new("L", (10, 10)) + with pytest.raises(ValueError): + im.reduce(0) + with pytest.raises(TypeError): + im.reduce(2.0) + with pytest.raises(ValueError): + im.reduce((0, 10)) - self.assertEqual((5, 5), im.reduce(2, (0, 0, 10, 10)).size) - self.assertEqual((1, 1), im.reduce(2, (5, 5, 6, 6)).size) - with self.assertRaises(TypeError): - im.reduce(2, "stri") - with self.assertRaises(TypeError): - im.reduce(2, 2) - with self.assertRaises(ValueError): - im.reduce(2, (0, 0, 11, 10)) - with self.assertRaises(ValueError): - im.reduce(2, (0, 0, 10, 11)) - with self.assertRaises(ValueError): - im.reduce(2, (-1, 0, 10, 10)) - with self.assertRaises(ValueError): - im.reduce(2, (0, -1, 10, 10)) - with self.assertRaises(ValueError): - im.reduce(2, (0, 5, 10, 5)) - with self.assertRaises(ValueError): - im.reduce(2, (5, 0, 5, 10)) +def test_args_box(): + im = Image.new("L", (10, 10)) - def test_unsupported_modes(self): - im = Image.new("P", (10, 10)) - with self.assertRaises(ValueError): - im.reduce(3) + assert (5, 5) == im.reduce(2, (0, 0, 10, 10)).size + assert (1, 1) == im.reduce(2, (5, 5, 6, 6)).size - im = Image.new("1", (10, 10)) - with self.assertRaises(ValueError): - im.reduce(3) + with pytest.raises(TypeError): + im.reduce(2, "stri") + with pytest.raises(TypeError): + im.reduce(2, 2) + with pytest.raises(ValueError): + im.reduce(2, (0, 0, 11, 10)) + with pytest.raises(ValueError): + im.reduce(2, (0, 0, 10, 11)) + with pytest.raises(ValueError): + im.reduce(2, (-1, 0, 10, 10)) + with pytest.raises(ValueError): + im.reduce(2, (0, -1, 10, 10)) + with pytest.raises(ValueError): + im.reduce(2, (0, 5, 10, 5)) + with pytest.raises(ValueError): + im.reduce(2, (5, 0, 5, 10)) - im = Image.new("I;16", (10, 10)) - with self.assertRaises(ValueError): - im.reduce(3) - def get_image(self, mode): - mode_info = ImageMode.getmode(mode) - if mode_info.basetype == "L": - bands = [self.gradients_image] - for _ in mode_info.bands[1:]: - # rotate previous image - band = bands[-1].transpose(Image.ROTATE_90) - bands.append(band) - # Correct alpha channel by transforming completely transparent pixels. - # Low alpha values also emphasize error after alpha multiplication. - if mode.endswith("A"): - bands[-1] = bands[-1].point(lambda x: int(85 + x / 1.5)) - im = Image.merge(mode, bands) - else: - assert len(mode_info.bands) == 1 - im = self.gradients_image.convert(mode) - # change the height to make a not-square image - return im.crop((0, 0, im.width, im.height - 5)) +def test_unsupported_modes(): + im = Image.new("P", (10, 10)) + with pytest.raises(ValueError): + im.reduce(3) - def compare_reduce_with_box(self, im, factor): - box = (11, 13, 146, 164) - reduced = im.reduce(factor, box=box) - reference = im.crop(box).reduce(factor) - self.assertEqual(reduced, reference) + im = Image.new("1", (10, 10)) + with pytest.raises(ValueError): + im.reduce(3) - def compare_reduce_with_reference(self, im, factor, average_diff=0.4, max_diff=1): - """Image.reduce() should look very similar to Image.resize(BOX). + im = Image.new("I;16", (10, 10)) + with pytest.raises(ValueError): + im.reduce(3) - A reference image is compiled from a large source area - and possible last column and last row. - +-----------+ - |..........c| - |..........c| - |..........c| - |rrrrrrrrrrp| - +-----------+ - """ - reduced = im.reduce(factor) - if not isinstance(factor, (list, tuple)): - factor = (factor, factor) +def get_image(mode): + mode_info = ImageMode.getmode(mode) + if mode_info.basetype == "L": + bands = [gradients_image] + for _ in mode_info.bands[1:]: + # rotate previous image + band = bands[-1].transpose(Image.ROTATE_90) + bands.append(band) + # Correct alpha channel by transforming completely transparent pixels. + # Low alpha values also emphasize error after alpha multiplication. + if mode.endswith("A"): + bands[-1] = bands[-1].point(lambda x: int(85 + x / 1.5)) + im = Image.merge(mode, bands) + else: + assert len(mode_info.bands) == 1 + im = gradients_image.convert(mode) + # change the height to make a not-square image + return im.crop((0, 0, im.width, im.height - 5)) - reference = Image.new(im.mode, reduced.size) - area_size = (im.size[0] // factor[0], im.size[1] // factor[1]) - area_box = (0, 0, area_size[0] * factor[0], area_size[1] * factor[1]) - area = im.resize(area_size, Image.BOX, area_box) - reference.paste(area, (0, 0)) - if area_size[0] < reduced.size[0]: - self.assertEqual(reduced.size[0] - area_size[0], 1) - last_column_box = (area_box[2], 0, im.size[0], area_box[3]) - last_column = im.resize((1, area_size[1]), Image.BOX, last_column_box) - reference.paste(last_column, (area_size[0], 0)) +def compare_reduce_with_box(im, factor): + box = (11, 13, 146, 164) + reduced = im.reduce(factor, box=box) + reference = im.crop(box).reduce(factor) + assert reduced == reference - if area_size[1] < reduced.size[1]: - self.assertEqual(reduced.size[1] - area_size[1], 1) - last_row_box = (0, area_box[3], area_box[2], im.size[1]) - last_row = im.resize((area_size[0], 1), Image.BOX, last_row_box) - reference.paste(last_row, (0, area_size[1])) - if area_size[0] < reduced.size[0] and area_size[1] < reduced.size[1]: - last_pixel_box = (area_box[2], area_box[3], im.size[0], im.size[1]) - last_pixel = im.resize((1, 1), Image.BOX, last_pixel_box) - reference.paste(last_pixel, area_size) +def compare_reduce_with_reference(im, factor, average_diff=0.4, max_diff=1): + """Image.reduce() should look very similar to Image.resize(BOX). - self.assert_compare_images(reduced, reference, average_diff, max_diff) + A reference image is compiled from a large source area + and possible last column and last row. + +-----------+ + |..........c| + |..........c| + |..........c| + |rrrrrrrrrrp| + +-----------+ + """ + reduced = im.reduce(factor) - def assert_compare_images(self, a, b, max_average_diff, max_diff=255): - self.assertEqual(a.mode, b.mode, "got mode %r, expected %r" % (a.mode, b.mode)) - self.assertEqual(a.size, b.size, "got size %r, expected %r" % (a.size, b.size)) + if not isinstance(factor, (list, tuple)): + factor = (factor, factor) - a, b = convert_to_comparable(a, b) + reference = Image.new(im.mode, reduced.size) + area_size = (im.size[0] // factor[0], im.size[1] // factor[1]) + area_box = (0, 0, area_size[0] * factor[0], area_size[1] * factor[1]) + area = im.resize(area_size, Image.BOX, area_box) + reference.paste(area, (0, 0)) - bands = ImageMode.getmode(a.mode).bands - for band, ach, bch in zip(bands, a.split(), b.split()): - ch_diff = ImageMath.eval("convert(abs(a - b), 'L')", a=ach, b=bch) - ch_hist = ch_diff.histogram() + if area_size[0] < reduced.size[0]: + assert reduced.size[0] - area_size[0] == 1 + last_column_box = (area_box[2], 0, im.size[0], area_box[3]) + last_column = im.resize((1, area_size[1]), Image.BOX, last_column_box) + reference.paste(last_column, (area_size[0], 0)) - average_diff = sum(i * num for i, num in enumerate(ch_hist)) / ( - a.size[0] * a.size[1] - ) - self.assertGreaterEqual( - max_average_diff, - average_diff, - ( - "average pixel value difference {:.4f} > expected {:.4f} " - "for '{}' band" - ).format(average_diff, max_average_diff, band), - ) + if area_size[1] < reduced.size[1]: + assert reduced.size[1] - area_size[1] == 1 + last_row_box = (0, area_box[3], area_box[2], im.size[1]) + last_row = im.resize((area_size[0], 1), Image.BOX, last_row_box) + reference.paste(last_row, (0, area_size[1])) - last_diff = [i for i, num in enumerate(ch_hist) if num > 0][-1] - self.assertGreaterEqual( - max_diff, - last_diff, - "max pixel value difference {} > expected {} for '{}' band".format( - last_diff, max_diff, band - ), - ) + if area_size[0] < reduced.size[0] and area_size[1] < reduced.size[1]: + last_pixel_box = (area_box[2], area_box[3], im.size[0], im.size[1]) + last_pixel = im.resize((1, 1), Image.BOX, last_pixel_box) + reference.paste(last_pixel, area_size) - def test_mode_L(self): - im = self.get_image("L") - for factor in self.remarkable_factors: - self.compare_reduce_with_reference(im, factor) - self.compare_reduce_with_box(im, factor) + assert_compare_images(reduced, reference, average_diff, max_diff) - def test_mode_LA(self): - im = self.get_image("LA") - for factor in self.remarkable_factors: - self.compare_reduce_with_reference(im, factor, 0.8, 5) - # With opaque alpha, an error should be way smaller. - im.putalpha(Image.new("L", im.size, 255)) - for factor in self.remarkable_factors: - self.compare_reduce_with_reference(im, factor) - self.compare_reduce_with_box(im, factor) +def assert_compare_images(a, b, max_average_diff, max_diff=255): + assert a.mode == b.mode, f"got mode {repr(a.mode)}, expected {repr(b.mode)}" + assert a.size == b.size, f"got size {repr(a.size)}, expected {repr(b.size)}" - def test_mode_La(self): - im = self.get_image("La") - for factor in self.remarkable_factors: - self.compare_reduce_with_reference(im, factor) - self.compare_reduce_with_box(im, factor) + a, b = convert_to_comparable(a, b) - def test_mode_RGB(self): - im = self.get_image("RGB") - for factor in self.remarkable_factors: - self.compare_reduce_with_reference(im, factor) - self.compare_reduce_with_box(im, factor) + bands = ImageMode.getmode(a.mode).bands + for band, ach, bch in zip(bands, a.split(), b.split()): + ch_diff = ImageMath.eval("convert(abs(a - b), 'L')", a=ach, b=bch) + ch_hist = ch_diff.histogram() - def test_mode_RGBA(self): - im = self.get_image("RGBA") - for factor in self.remarkable_factors: - self.compare_reduce_with_reference(im, factor, 0.8, 5) + average_diff = sum(i * num for i, num in enumerate(ch_hist)) / ( + a.size[0] * a.size[1] + ) + msg = ( + f"average pixel value difference {average_diff:.4f} > " + f"expected {max_average_diff:.4f} for '{band}' band" + ) + assert max_average_diff >= average_diff, msg - # With opaque alpha, an error should be way smaller. - im.putalpha(Image.new("L", im.size, 255)) - for factor in self.remarkable_factors: - self.compare_reduce_with_reference(im, factor) - self.compare_reduce_with_box(im, factor) + last_diff = [i for i, num in enumerate(ch_hist) if num > 0][-1] + assert max_diff >= last_diff, ( + f"max pixel value difference {last_diff} > expected {max_diff} " + f"for '{band}' band" + ) - def test_mode_RGBa(self): - im = self.get_image("RGBa") - for factor in self.remarkable_factors: - self.compare_reduce_with_reference(im, factor) - self.compare_reduce_with_box(im, factor) - def test_mode_I(self): - im = self.get_image("I") - for factor in self.remarkable_factors: - self.compare_reduce_with_reference(im, factor) - self.compare_reduce_with_box(im, factor) +def test_mode_L(): + im = get_image("L") + for factor in remarkable_factors: + compare_reduce_with_reference(im, factor) + compare_reduce_with_box(im, factor) - def test_mode_F(self): - im = self.get_image("F") - for factor in self.remarkable_factors: - self.compare_reduce_with_reference(im, factor, 0, 0) - self.compare_reduce_with_box(im, factor) + +def test_mode_LA(): + im = get_image("LA") + for factor in remarkable_factors: + compare_reduce_with_reference(im, factor, 0.8, 5) + + # With opaque alpha, an error should be way smaller. + im.putalpha(Image.new("L", im.size, 255)) + for factor in remarkable_factors: + compare_reduce_with_reference(im, factor) + compare_reduce_with_box(im, factor) + + +def test_mode_La(): + im = get_image("La") + for factor in remarkable_factors: + compare_reduce_with_reference(im, factor) + compare_reduce_with_box(im, factor) + + +def test_mode_RGB(): + im = get_image("RGB") + for factor in remarkable_factors: + compare_reduce_with_reference(im, factor) + compare_reduce_with_box(im, factor) + + +def test_mode_RGBA(): + im = get_image("RGBA") + for factor in remarkable_factors: + compare_reduce_with_reference(im, factor, 0.8, 5) + + # With opaque alpha, an error should be way smaller. + im.putalpha(Image.new("L", im.size, 255)) + for factor in remarkable_factors: + compare_reduce_with_reference(im, factor) + compare_reduce_with_box(im, factor) + + +def test_mode_RGBa(): + im = get_image("RGBa") + for factor in remarkable_factors: + compare_reduce_with_reference(im, factor) + compare_reduce_with_box(im, factor) + + +def test_mode_I(): + im = get_image("I") + for factor in remarkable_factors: + compare_reduce_with_reference(im, factor) + compare_reduce_with_box(im, factor) + + +def test_mode_F(): + im = get_image("F") + for factor in remarkable_factors: + compare_reduce_with_reference(im, factor, 0, 0) + compare_reduce_with_box(im, factor) + + +@skip_unless_feature("jpg_2000") +def test_jpeg2k(): + with Image.open("Tests/images/test-card-lossless.jp2") as im: + assert im.reduce(2).size == (320, 240) diff --git a/Tests/test_image_resample.py b/Tests/test_image_resample.py index e1ca4af7e..ef4ca4101 100644 --- a/Tests/test_image_resample.py +++ b/Tests/test_image_resample.py @@ -1,12 +1,13 @@ -import unittest from contextlib import contextmanager +import pytest + from PIL import Image, ImageDraw -from .helper import PillowTestCase, assert_image_equal, assert_image_similar, hopper +from .helper import assert_image_equal, assert_image_similar, hopper -class TestImagingResampleVulnerability(PillowTestCase): +class TestImagingResampleVulnerability: # see https://github.com/python-pillow/Pillow/issues/1710 def test_overflow(self): im = hopper("L") @@ -16,7 +17,7 @@ class TestImagingResampleVulnerability(PillowTestCase): (size_too_large, size_normal), (size_normal, size_too_large), ): - with self.assertRaises(MemoryError): + with pytest.raises(MemoryError): # any resampling filter will do here im.im.resize((xsize, ysize), Image.BILINEAR) @@ -26,10 +27,10 @@ class TestImagingResampleVulnerability(PillowTestCase): # Should not crash im.resize((100, 100)) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): im.resize((-100, 100)) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): im.resize((100, -100)) def test_modify_after_resizing(self): @@ -39,10 +40,10 @@ class TestImagingResampleVulnerability(PillowTestCase): # some in-place operation copy.paste("black", (0, 0, im.width // 2, im.height // 2)) # image should be different - self.assertNotEqual(im.tobytes(), copy.tobytes()) + assert im.tobytes() != copy.tobytes() -class TestImagingCoreResampleAccuracy(PillowTestCase): +class TestImagingCoreResampleAccuracy: def make_case(self, mode, size, color): """Makes a sample image with two dark and two bright squares. For example: @@ -81,15 +82,16 @@ class TestImagingCoreResampleAccuracy(PillowTestCase): for y in range(case.size[1]): for x in range(case.size[0]): if c_px[x, y] != s_px[x, y]: - message = "\nHave: \n{}\n\nExpected: \n{}".format( - self.serialize_image(case), self.serialize_image(sample) + message = ( + f"\nHave: \n{self.serialize_image(case)}\n" + f"\nExpected: \n{self.serialize_image(sample)}" ) - self.assertEqual(s_px[x, y], c_px[x, y], message) + assert s_px[x, y] == c_px[x, y], message def serialize_image(self, image): s_px = image.load() return "\n".join( - " ".join("{:02x}".format(s_px[x, y]) for x in range(image.size[0])) + " ".join(f"{s_px[x, y]:02x}" for x in range(image.size[0])) for y in range(image.size[1]) ) @@ -218,7 +220,7 @@ class TestImagingCoreResampleAccuracy(PillowTestCase): assert_image_equal(im, ref) -class CoreResampleConsistencyTest(PillowTestCase): +class TestCoreResampleConsistency: def make_case(self, mode, fill): im = Image.new(mode, (512, 9), fill) return im.resize((9, 512), Image.LANCZOS), im.load()[0, 0] @@ -229,8 +231,8 @@ class CoreResampleConsistencyTest(PillowTestCase): for x in range(channel.size[0]): for y in range(channel.size[1]): if px[x, y] != color: - message = "{} != {} for pixel {}".format(px[x, y], color, (x, y)) - self.assertEqual(px[x, y], color, message) + message = f"{px[x, y]} != {color} for pixel {(x, y)}" + assert px[x, y] == color, message def test_8u(self): im, color = self.make_case("RGB", (0, 64, 255)) @@ -253,7 +255,7 @@ class CoreResampleConsistencyTest(PillowTestCase): self.run_case(self.make_case("F", 1.192093e-07)) -class CoreResampleAlphaCorrectTest(PillowTestCase): +class TestCoreResampleAlphaCorrect: def make_levels_case(self, mode): i = Image.new(mode, (256, 16)) px = i.load() @@ -268,14 +270,12 @@ class CoreResampleAlphaCorrectTest(PillowTestCase): px = i.load() for y in range(i.size[1]): used_colors = {px[x, y][0] for x in range(i.size[0])} - self.assertEqual( - 256, - len(used_colors), - "All colors should present in resized image. " - "Only {} on {} line.".format(len(used_colors), y), + assert 256 == len(used_colors), ( + "All colors should be present in resized image. " + f"Only {len(used_colors)} on {y} line." ) - @unittest.skip("current implementation isn't precise enough") + @pytest.mark.xfail(reason="Current implementation isn't precise enough") def test_levels_rgba(self): case = self.make_levels_case("RGBA") self.run_levels_case(case.resize((512, 32), Image.BOX)) @@ -284,7 +284,7 @@ class CoreResampleAlphaCorrectTest(PillowTestCase): self.run_levels_case(case.resize((512, 32), Image.BICUBIC)) self.run_levels_case(case.resize((512, 32), Image.LANCZOS)) - @unittest.skip("current implementation isn't precise enough") + @pytest.mark.xfail(reason="Current implementation isn't precise enough") def test_levels_la(self): case = self.make_levels_case("LA") self.run_levels_case(case.resize((512, 32), Image.BOX)) @@ -308,10 +308,11 @@ class CoreResampleAlphaCorrectTest(PillowTestCase): for y in range(i.size[1]): for x in range(i.size[0]): if px[x, y][-1] != 0 and px[x, y][:-1] != clean_pixel: - message = "pixel at ({}, {}) is differ:\n{}\n{}".format( - x, y, px[x, y], clean_pixel + message = ( + f"pixel at ({x}, {y}) is different:\n" + f"{px[x, y]}\n{clean_pixel}" ) - self.assertEqual(px[x, y][:3], clean_pixel, message) + assert px[x, y][:3] == clean_pixel, message def test_dirty_pixels_rgba(self): case = self.make_dirty_case("RGBA", (255, 255, 0, 128), (0, 0, 255, 0)) @@ -330,12 +331,12 @@ class CoreResampleAlphaCorrectTest(PillowTestCase): self.run_dirty_case(case.resize((20, 20), Image.LANCZOS), (255,)) -class CoreResamplePassesTest(PillowTestCase): +class TestCoreResamplePasses: @contextmanager def count(self, diff): count = Image.core.get_stats()["new_count"] yield - self.assertEqual(Image.core.get_stats()["new_count"] - count, diff) + assert Image.core.get_stats()["new_count"] - count == diff def test_horizontal(self): im = hopper("L") @@ -373,7 +374,7 @@ class CoreResamplePassesTest(PillowTestCase): assert_image_similar(with_box, cropped, 0.1) -class CoreResampleCoefficientsTest(PillowTestCase): +class TestCoreResampleCoefficients: def test_reduce(self): test_color = 254 @@ -384,7 +385,7 @@ class CoreResampleCoefficientsTest(PillowTestCase): px = i.resize((5, i.size[1]), Image.BICUBIC).load() if px[2, 0] != test_color // 2: - self.assertEqual(test_color // 2, px[2, 0]) + assert test_color // 2 == px[2, 0] def test_nonzero_coefficients(self): # regression test for the wrong coefficients calculation @@ -393,16 +394,16 @@ class CoreResampleCoefficientsTest(PillowTestCase): histogram = im.resize((256, 256), Image.BICUBIC).histogram() # first channel - self.assertEqual(histogram[0x100 * 0 + 0x20], 0x10000) + assert histogram[0x100 * 0 + 0x20] == 0x10000 # second channel - self.assertEqual(histogram[0x100 * 1 + 0x40], 0x10000) + assert histogram[0x100 * 1 + 0x40] == 0x10000 # third channel - self.assertEqual(histogram[0x100 * 2 + 0x60], 0x10000) + assert histogram[0x100 * 2 + 0x60] == 0x10000 # fourth channel - self.assertEqual(histogram[0x100 * 3 + 0xFF], 0x10000) + assert histogram[0x100 * 3 + 0xFF] == 0x10000 -class CoreResampleBoxTest(PillowTestCase): +class TestCoreResampleBox: def test_wrong_arguments(self): im = hopper() for resample in ( @@ -418,24 +419,24 @@ class CoreResampleBoxTest(PillowTestCase): im.resize((32, 32), resample, (20, 20, 20, 100)) im.resize((32, 32), resample, (20, 20, 100, 20)) - with self.assertRaisesRegex(TypeError, "must be sequence of length 4"): + with pytest.raises(TypeError, match="must be sequence of length 4"): im.resize((32, 32), resample, (im.width, im.height)) - with self.assertRaisesRegex(ValueError, "can't be negative"): + with pytest.raises(ValueError, match="can't be negative"): im.resize((32, 32), resample, (-20, 20, 100, 100)) - with self.assertRaisesRegex(ValueError, "can't be negative"): + with pytest.raises(ValueError, match="can't be negative"): im.resize((32, 32), resample, (20, -20, 100, 100)) - with self.assertRaisesRegex(ValueError, "can't be empty"): + with pytest.raises(ValueError, match="can't be empty"): im.resize((32, 32), resample, (20.1, 20, 20, 100)) - with self.assertRaisesRegex(ValueError, "can't be empty"): + with pytest.raises(ValueError, match="can't be empty"): im.resize((32, 32), resample, (20, 20.1, 100, 20)) - with self.assertRaisesRegex(ValueError, "can't be empty"): + with pytest.raises(ValueError, match="can't be empty"): im.resize((32, 32), resample, (20.1, 20.1, 20, 20)) - with self.assertRaisesRegex(ValueError, "can't exceed"): + with pytest.raises(ValueError, match="can't exceed"): im.resize((32, 32), resample, (0, 0, im.width + 1, im.height)) - with self.assertRaisesRegex(ValueError, "can't exceed"): + with pytest.raises(ValueError, match="can't exceed"): im.resize((32, 32), resample, (0, 0, im.width, im.height + 1)) def resize_tiled(self, im, dst_size, xtiles, ytiles): @@ -456,7 +457,7 @@ class CoreResampleBoxTest(PillowTestCase): def test_tiles(self): with Image.open("Tests/images/flower.jpg") as im: - self.assertEqual(im.size, (480, 360)) + assert im.size == (480, 360) dst_size = (251, 188) reference = im.resize(dst_size, Image.BICUBIC) @@ -468,7 +469,7 @@ class CoreResampleBoxTest(PillowTestCase): # This test shows advantages of the subpixel resizing # after supersampling (e.g. during JPEG decoding). with Image.open("Tests/images/flower.jpg") as im: - self.assertEqual(im.size, (480, 360)) + assert im.size == (480, 360) dst_size = (48, 36) # Reference is cropped image resized to destination reference = im.crop((0, 0, 473, 353)).resize(dst_size, Image.BICUBIC) @@ -480,7 +481,7 @@ class CoreResampleBoxTest(PillowTestCase): # error with box should be much smaller than without assert_image_similar(reference, with_box, 6) - with self.assertRaisesRegex(AssertionError, r"difference 29\."): + with pytest.raises(AssertionError, match=r"difference 29\."): assert_image_similar(reference, without_box, 5) def test_formats(self): @@ -503,8 +504,8 @@ class CoreResampleBoxTest(PillowTestCase): ((40, 50), (10, 20, 50, 70)), ]: res = im.resize(size, Image.LANCZOS, box) - self.assertEqual(res.size, size) - assert_image_equal(res, im.crop(box), ">>> {} {}".format(size, box)) + assert res.size == size + assert_image_equal(res, im.crop(box), f">>> {size} {box}") def test_no_passthrough(self): # When resize is required @@ -517,12 +518,10 @@ class CoreResampleBoxTest(PillowTestCase): ((40, 50), (10.4, 20.4, 50.4, 70.4)), ]: res = im.resize(size, Image.LANCZOS, box) - self.assertEqual(res.size, size) - with self.assertRaisesRegex(AssertionError, r"difference \d"): + assert res.size == size + with pytest.raises(AssertionError, match=r"difference \d"): # check that the difference at least that much - assert_image_similar( - res, im.crop(box), 20, ">>> {} {}".format(size, box) - ) + assert_image_similar(res, im.crop(box), 20, f">>> {size} {box}") def test_skip_horizontal(self): # Can skip resize for one dimension @@ -536,13 +535,13 @@ class CoreResampleBoxTest(PillowTestCase): ((40, 50), (10, 20, 50, 90)), ]: res = im.resize(size, flt, box) - self.assertEqual(res.size, size) + assert res.size == size # Borders should be slightly different assert_image_similar( res, im.crop(box).resize(size, flt), 0.4, - ">>> {} {} {}".format(size, box, flt), + f">>> {size} {box} {flt}", ) def test_skip_vertical(self): @@ -557,11 +556,11 @@ class CoreResampleBoxTest(PillowTestCase): ((40, 50), (20, 10, 90, 60)), ]: res = im.resize(size, flt, box) - self.assertEqual(res.size, size) + assert res.size == size # Borders should be slightly different assert_image_similar( res, im.crop(box).resize(size, flt), 0.4, - ">>> {} {} {}".format(size, box, flt), + f">>> {size} {box} {flt}", ) diff --git a/Tests/test_image_resize.py b/Tests/test_image_resize.py index 66a82230d..a49abe1b9 100644 --- a/Tests/test_image_resize.py +++ b/Tests/test_image_resize.py @@ -3,12 +3,14 @@ Tests for resize functionality. """ from itertools import permutations +import pytest + from PIL import Image -from .helper import PillowTestCase, assert_image_equal, assert_image_similar, hopper +from .helper import assert_image_equal, assert_image_similar, hopper -class TestImagingCoreResize(PillowTestCase): +class TestImagingCoreResize: def resize(self, im, size, f): # Image class independent version of resize. im.load() @@ -29,26 +31,23 @@ class TestImagingCoreResize(PillowTestCase): ]: # exotic mode im = hopper(mode) r = self.resize(im, (15, 12), Image.NEAREST) - self.assertEqual(r.mode, mode) - self.assertEqual(r.size, (15, 12)) - self.assertEqual(r.im.bands, im.im.bands) + assert r.mode == mode + assert r.size == (15, 12) + assert r.im.bands == im.im.bands def test_convolution_modes(self): - self.assertRaises( - ValueError, self.resize, hopper("1"), (15, 12), Image.BILINEAR - ) - self.assertRaises( - ValueError, self.resize, hopper("P"), (15, 12), Image.BILINEAR - ) - self.assertRaises( - ValueError, self.resize, hopper("I;16"), (15, 12), Image.BILINEAR - ) + with pytest.raises(ValueError): + self.resize(hopper("1"), (15, 12), Image.BILINEAR) + with pytest.raises(ValueError): + self.resize(hopper("P"), (15, 12), Image.BILINEAR) + with pytest.raises(ValueError): + self.resize(hopper("I;16"), (15, 12), Image.BILINEAR) for mode in ["L", "I", "F", "RGB", "RGBA", "CMYK", "YCbCr"]: im = hopper(mode) r = self.resize(im, (15, 12), Image.BILINEAR) - self.assertEqual(r.mode, mode) - self.assertEqual(r.size, (15, 12)) - self.assertEqual(r.im.bands, im.im.bands) + assert r.mode == mode + assert r.size == (15, 12) + assert r.im.bands == im.im.bands def test_reduce_filters(self): for f in [ @@ -60,8 +59,8 @@ class TestImagingCoreResize(PillowTestCase): Image.LANCZOS, ]: r = self.resize(hopper("RGB"), (15, 12), f) - self.assertEqual(r.mode, "RGB") - self.assertEqual(r.size, (15, 12)) + assert r.mode == "RGB" + assert r.size == (15, 12) def test_enlarge_filters(self): for f in [ @@ -73,8 +72,8 @@ class TestImagingCoreResize(PillowTestCase): Image.LANCZOS, ]: r = self.resize(hopper("RGB"), (212, 195), f) - self.assertEqual(r.mode, "RGB") - self.assertEqual(r.size, (212, 195)) + assert r.mode == "RGB" + assert r.size == (212, 195) def test_endianness(self): # Make an image with one colored pixel, in one channel. @@ -128,107 +127,111 @@ class TestImagingCoreResize(PillowTestCase): Image.LANCZOS, ]: r = self.resize(Image.new("RGB", (0, 0), "white"), (212, 195), f) - self.assertEqual(r.mode, "RGB") - self.assertEqual(r.size, (212, 195)) - self.assertEqual(r.getdata()[0], (0, 0, 0)) + assert r.mode == "RGB" + assert r.size == (212, 195) + assert r.getdata()[0] == (0, 0, 0) def test_unknown_filter(self): - self.assertRaises(ValueError, self.resize, hopper(), (10, 10), 9) + with pytest.raises(ValueError): + self.resize(hopper(), (10, 10), 9) -class TestReducingGapResize(PillowTestCase): - @classmethod - def setUpClass(cls): - cls.gradients_image = Image.open("Tests/images/radial_gradients.png") - cls.gradients_image.load() +@pytest.fixture +def gradients_image(): + im = Image.open("Tests/images/radial_gradients.png") + im.load() + try: + yield im + finally: + im.close() - def test_reducing_gap_values(self): - ref = self.gradients_image.resize((52, 34), Image.BICUBIC, reducing_gap=None) - im = self.gradients_image.resize((52, 34), Image.BICUBIC) + +class TestReducingGapResize: + def test_reducing_gap_values(self, gradients_image): + ref = gradients_image.resize((52, 34), Image.BICUBIC, reducing_gap=None) + im = gradients_image.resize((52, 34), Image.BICUBIC) assert_image_equal(ref, im) - with self.assertRaises(ValueError): - self.gradients_image.resize((52, 34), Image.BICUBIC, reducing_gap=0) + with pytest.raises(ValueError): + gradients_image.resize((52, 34), Image.BICUBIC, reducing_gap=0) - with self.assertRaises(ValueError): - self.gradients_image.resize((52, 34), Image.BICUBIC, reducing_gap=0.99) + with pytest.raises(ValueError): + gradients_image.resize((52, 34), Image.BICUBIC, reducing_gap=0.99) - def test_reducing_gap_1(self): + def test_reducing_gap_1(self, gradients_image): for box, epsilon in [ (None, 4), ((1.1, 2.2, 510.8, 510.9), 4), ((3, 10, 410, 256), 10), ]: - ref = self.gradients_image.resize((52, 34), Image.BICUBIC, box=box) - im = self.gradients_image.resize( + ref = gradients_image.resize((52, 34), Image.BICUBIC, box=box) + im = gradients_image.resize( (52, 34), Image.BICUBIC, box=box, reducing_gap=1.0 ) - with self.assertRaises(AssertionError): + with pytest.raises(AssertionError): assert_image_equal(ref, im) assert_image_similar(ref, im, epsilon) - def test_reducing_gap_2(self): + def test_reducing_gap_2(self, gradients_image): for box, epsilon in [ (None, 1.5), ((1.1, 2.2, 510.8, 510.9), 1.5), ((3, 10, 410, 256), 1), ]: - ref = self.gradients_image.resize((52, 34), Image.BICUBIC, box=box) - im = self.gradients_image.resize( + ref = gradients_image.resize((52, 34), Image.BICUBIC, box=box) + im = gradients_image.resize( (52, 34), Image.BICUBIC, box=box, reducing_gap=2.0 ) - with self.assertRaises(AssertionError): + with pytest.raises(AssertionError): assert_image_equal(ref, im) assert_image_similar(ref, im, epsilon) - def test_reducing_gap_3(self): + def test_reducing_gap_3(self, gradients_image): for box, epsilon in [ (None, 1), ((1.1, 2.2, 510.8, 510.9), 1), ((3, 10, 410, 256), 0.5), ]: - ref = self.gradients_image.resize((52, 34), Image.BICUBIC, box=box) - im = self.gradients_image.resize( + ref = gradients_image.resize((52, 34), Image.BICUBIC, box=box) + im = gradients_image.resize( (52, 34), Image.BICUBIC, box=box, reducing_gap=3.0 ) - with self.assertRaises(AssertionError): + with pytest.raises(AssertionError): assert_image_equal(ref, im) assert_image_similar(ref, im, epsilon) - def test_reducing_gap_8(self): + def test_reducing_gap_8(self, gradients_image): for box in [None, (1.1, 2.2, 510.8, 510.9), (3, 10, 410, 256)]: - ref = self.gradients_image.resize((52, 34), Image.BICUBIC, box=box) - im = self.gradients_image.resize( + ref = gradients_image.resize((52, 34), Image.BICUBIC, box=box) + im = gradients_image.resize( (52, 34), Image.BICUBIC, box=box, reducing_gap=8.0 ) assert_image_equal(ref, im) - def test_box_filter(self): + def test_box_filter(self, gradients_image): for box, epsilon in [ ((0, 0, 512, 512), 5.5), ((0.9, 1.7, 128, 128), 9.5), ]: - ref = self.gradients_image.resize((52, 34), Image.BOX, box=box) - im = self.gradients_image.resize( - (52, 34), Image.BOX, box=box, reducing_gap=1.0 - ) + ref = gradients_image.resize((52, 34), Image.BOX, box=box) + im = gradients_image.resize((52, 34), Image.BOX, box=box, reducing_gap=1.0) assert_image_similar(ref, im, epsilon) -class TestImageResize(PillowTestCase): +class TestImageResize: def test_resize(self): def resize(mode, size): out = hopper(mode).resize(size) - self.assertEqual(out.mode, mode) - self.assertEqual(out.size, size) + assert out.mode == mode + assert out.size == size for mode in "1", "P", "L", "RGB", "I", "F": resize(mode, (112, 103)) @@ -236,13 +239,14 @@ class TestImageResize(PillowTestCase): # Test unknown resampling filter with hopper() as im: - self.assertRaises(ValueError, im.resize, (10, 10), "unknown") + with pytest.raises(ValueError): + im.resize((10, 10), "unknown") def test_default_filter(self): for mode in "L", "RGB", "I", "F": im = hopper(mode) - self.assertEqual(im.resize((20, 20), Image.BICUBIC), im.resize((20, 20))) + assert im.resize((20, 20), Image.BICUBIC) == im.resize((20, 20)) for mode in "1", "P": im = hopper(mode) - self.assertEqual(im.resize((20, 20), Image.NEAREST), im.resize((20, 20))) + assert im.resize((20, 20), Image.NEAREST) == im.resize((20, 20)) diff --git a/Tests/test_image_rotate.py b/Tests/test_image_rotate.py index 11da27aef..a41d850bb 100644 --- a/Tests/test_image_rotate.py +++ b/Tests/test_image_rotate.py @@ -1,127 +1,140 @@ from PIL import Image -from .helper import PillowTestCase, assert_image_equal, assert_image_similar, hopper +from .helper import assert_image_equal, assert_image_similar, hopper -class TestImageRotate(PillowTestCase): - def rotate(self, im, mode, angle, center=None, translate=None): - out = im.rotate(angle, center=center, translate=translate) - self.assertEqual(out.mode, mode) - self.assertEqual(out.size, im.size) # default rotate clips output - out = im.rotate(angle, center=center, translate=translate, expand=1) - self.assertEqual(out.mode, mode) - if angle % 180 == 0: - self.assertEqual(out.size, im.size) - elif im.size == (0, 0): - self.assertEqual(out.size, im.size) - else: - self.assertNotEqual(out.size, im.size) +def rotate(im, mode, angle, center=None, translate=None): + out = im.rotate(angle, center=center, translate=translate) + assert out.mode == mode + assert out.size == im.size # default rotate clips output + out = im.rotate(angle, center=center, translate=translate, expand=1) + assert out.mode == mode + if angle % 180 == 0: + assert out.size == im.size + elif im.size == (0, 0): + assert out.size == im.size + else: + assert out.size != im.size - def test_mode(self): - for mode in ("1", "P", "L", "RGB", "I", "F"): - im = hopper(mode) - self.rotate(im, mode, 45) - def test_angle(self): - for angle in (0, 90, 180, 270): - with Image.open("Tests/images/test-card.png") as im: - self.rotate(im, im.mode, angle) +def test_mode(): + for mode in ("1", "P", "L", "RGB", "I", "F"): + im = hopper(mode) + rotate(im, mode, 45) - def test_zero(self): - for angle in (0, 45, 90, 180, 270): - im = Image.new("RGB", (0, 0)) - self.rotate(im, im.mode, angle) - def test_resample(self): - # Target image creation, inspected by eye. - # >>> im = Image.open('Tests/images/hopper.ppm') - # >>> im = im.rotate(45, resample=Image.BICUBIC, expand=True) - # >>> im.save('Tests/images/hopper_45.png') +def test_angle(): + for angle in (0, 90, 180, 270): + with Image.open("Tests/images/test-card.png") as im: + rotate(im, im.mode, angle) - with Image.open("Tests/images/hopper_45.png") as target: - for (resample, epsilon) in ( - (Image.NEAREST, 10), - (Image.BILINEAR, 5), - (Image.BICUBIC, 0), - ): - im = hopper() - im = im.rotate(45, resample=resample, expand=True) - assert_image_similar(im, target, epsilon) - def test_center_0(self): - im = hopper() - im = im.rotate(45, center=(0, 0), resample=Image.BICUBIC) +def test_zero(): + for angle in (0, 45, 90, 180, 270): + im = Image.new("RGB", (0, 0)) + rotate(im, im.mode, angle) - with Image.open("Tests/images/hopper_45.png") as target: - target_origin = target.size[1] / 2 - target = target.crop((0, target_origin, 128, target_origin + 128)) - assert_image_similar(im, target, 15) +def test_resample(): + # Target image creation, inspected by eye. + # >>> im = Image.open('Tests/images/hopper.ppm') + # >>> im = im.rotate(45, resample=Image.BICUBIC, expand=True) + # >>> im.save('Tests/images/hopper_45.png') - def test_center_14(self): - im = hopper() - im = im.rotate(45, center=(14, 14), resample=Image.BICUBIC) + with Image.open("Tests/images/hopper_45.png") as target: + for (resample, epsilon) in ( + (Image.NEAREST, 10), + (Image.BILINEAR, 5), + (Image.BICUBIC, 0), + ): + im = hopper() + im = im.rotate(45, resample=resample, expand=True) + assert_image_similar(im, target, epsilon) - with Image.open("Tests/images/hopper_45.png") as target: - target_origin = target.size[1] / 2 - 14 - target = target.crop((6, target_origin, 128 + 6, target_origin + 128)) - assert_image_similar(im, target, 10) +def test_center_0(): + im = hopper() + im = im.rotate(45, center=(0, 0), resample=Image.BICUBIC) - def test_translate(self): - im = hopper() - with Image.open("Tests/images/hopper_45.png") as target: - target_origin = (target.size[1] / 2 - 64) - 5 - target = target.crop( - (target_origin, target_origin, target_origin + 128, target_origin + 128) - ) + with Image.open("Tests/images/hopper_45.png") as target: + target_origin = target.size[1] / 2 + target = target.crop((0, target_origin, 128, target_origin + 128)) - im = im.rotate(45, translate=(5, 5), resample=Image.BICUBIC) + assert_image_similar(im, target, 15) - assert_image_similar(im, target, 1) - def test_fastpath_center(self): - # if the center is -1,-1 and we rotate by 90<=x<=270 the - # resulting image should be black - for angle in (90, 180, 270): - im = hopper().rotate(angle, center=(-1, -1)) - assert_image_equal(im, Image.new("RGB", im.size, "black")) +def test_center_14(): + im = hopper() + im = im.rotate(45, center=(14, 14), resample=Image.BICUBIC) - def test_fastpath_translate(self): - # if we post-translate by -128 - # resulting image should be black - for angle in (0, 90, 180, 270): - im = hopper().rotate(angle, translate=(-128, -128)) - assert_image_equal(im, Image.new("RGB", im.size, "black")) + with Image.open("Tests/images/hopper_45.png") as target: + target_origin = target.size[1] / 2 - 14 + target = target.crop((6, target_origin, 128 + 6, target_origin + 128)) - def test_center(self): - im = hopper() - self.rotate(im, im.mode, 45, center=(0, 0)) - self.rotate(im, im.mode, 45, translate=(im.size[0] / 2, 0)) - self.rotate(im, im.mode, 45, center=(0, 0), translate=(im.size[0] / 2, 0)) + assert_image_similar(im, target, 10) - def test_rotate_no_fill(self): - im = Image.new("RGB", (100, 100), "green") - im = im.rotate(45) - with Image.open("Tests/images/rotate_45_no_fill.png") as target: - assert_image_equal(im, target) - def test_rotate_with_fill(self): - im = Image.new("RGB", (100, 100), "green") - im = im.rotate(45, fillcolor="white") - with Image.open("Tests/images/rotate_45_with_fill.png") as target: - assert_image_equal(im, target) +def test_translate(): + im = hopper() + with Image.open("Tests/images/hopper_45.png") as target: + target_origin = (target.size[1] / 2 - 64) - 5 + target = target.crop( + (target_origin, target_origin, target_origin + 128, target_origin + 128) + ) - def test_alpha_rotate_no_fill(self): - # Alpha images are handled differently internally - im = Image.new("RGBA", (10, 10), "green") - im = im.rotate(45, expand=1) - corner = im.getpixel((0, 0)) - self.assertEqual(corner, (0, 0, 0, 0)) + im = im.rotate(45, translate=(5, 5), resample=Image.BICUBIC) - def test_alpha_rotate_with_fill(self): - # Alpha images are handled differently internally - im = Image.new("RGBA", (10, 10), "green") - im = im.rotate(45, expand=1, fillcolor=(255, 0, 0, 255)) - corner = im.getpixel((0, 0)) - self.assertEqual(corner, (255, 0, 0, 255)) + assert_image_similar(im, target, 1) + + +def test_fastpath_center(): + # if the center is -1,-1 and we rotate by 90<=x<=270 the + # resulting image should be black + for angle in (90, 180, 270): + im = hopper().rotate(angle, center=(-1, -1)) + assert_image_equal(im, Image.new("RGB", im.size, "black")) + + +def test_fastpath_translate(): + # if we post-translate by -128 + # resulting image should be black + for angle in (0, 90, 180, 270): + im = hopper().rotate(angle, translate=(-128, -128)) + assert_image_equal(im, Image.new("RGB", im.size, "black")) + + +def test_center(): + im = hopper() + rotate(im, im.mode, 45, center=(0, 0)) + rotate(im, im.mode, 45, translate=(im.size[0] / 2, 0)) + rotate(im, im.mode, 45, center=(0, 0), translate=(im.size[0] / 2, 0)) + + +def test_rotate_no_fill(): + im = Image.new("RGB", (100, 100), "green") + im = im.rotate(45) + with Image.open("Tests/images/rotate_45_no_fill.png") as target: + assert_image_equal(im, target) + + +def test_rotate_with_fill(): + im = Image.new("RGB", (100, 100), "green") + im = im.rotate(45, fillcolor="white") + with Image.open("Tests/images/rotate_45_with_fill.png") as target: + assert_image_equal(im, target) + + +def test_alpha_rotate_no_fill(): + # Alpha images are handled differently internally + im = Image.new("RGBA", (10, 10), "green") + im = im.rotate(45, expand=1) + corner = im.getpixel((0, 0)) + assert corner == (0, 0, 0, 0) + + +def test_alpha_rotate_with_fill(): + # Alpha images are handled differently internally + im = Image.new("RGBA", (10, 10), "green") + im = im.rotate(45, expand=1, fillcolor=(255, 0, 0, 255)) + corner = im.getpixel((0, 0)) + assert corner == (255, 0, 0, 255) diff --git a/Tests/test_image_split.py b/Tests/test_image_split.py index 1be7eaf46..fbed276b8 100644 --- a/Tests/test_image_split.py +++ b/Tests/test_image_split.py @@ -1,64 +1,63 @@ -from PIL import Image +from PIL import Image, features -from .helper import PillowTestCase, assert_image_equal, hopper +from .helper import assert_image_equal, hopper -class TestImageSplit(PillowTestCase): - def test_split(self): - def split(mode): - layers = hopper(mode).split() - return [(i.mode, i.size[0], i.size[1]) for i in layers] +def test_split(): + def split(mode): + layers = hopper(mode).split() + return [(i.mode, i.size[0], i.size[1]) for i in layers] - self.assertEqual(split("1"), [("1", 128, 128)]) - self.assertEqual(split("L"), [("L", 128, 128)]) - self.assertEqual(split("I"), [("I", 128, 128)]) - self.assertEqual(split("F"), [("F", 128, 128)]) - self.assertEqual(split("P"), [("P", 128, 128)]) - self.assertEqual( - split("RGB"), [("L", 128, 128), ("L", 128, 128), ("L", 128, 128)] - ) - self.assertEqual( - split("RGBA"), - [("L", 128, 128), ("L", 128, 128), ("L", 128, 128), ("L", 128, 128)], - ) - self.assertEqual( - split("CMYK"), - [("L", 128, 128), ("L", 128, 128), ("L", 128, 128), ("L", 128, 128)], - ) - self.assertEqual( - split("YCbCr"), [("L", 128, 128), ("L", 128, 128), ("L", 128, 128)] - ) + assert split("1") == [("1", 128, 128)] + assert split("L") == [("L", 128, 128)] + assert split("I") == [("I", 128, 128)] + assert split("F") == [("F", 128, 128)] + assert split("P") == [("P", 128, 128)] + assert split("RGB") == [("L", 128, 128), ("L", 128, 128), ("L", 128, 128)] + assert split("RGBA") == [ + ("L", 128, 128), + ("L", 128, 128), + ("L", 128, 128), + ("L", 128, 128), + ] + assert split("CMYK") == [ + ("L", 128, 128), + ("L", 128, 128), + ("L", 128, 128), + ("L", 128, 128), + ] + assert split("YCbCr") == [("L", 128, 128), ("L", 128, 128), ("L", 128, 128)] - def test_split_merge(self): - def split_merge(mode): - return Image.merge(mode, hopper(mode).split()) - assert_image_equal(hopper("1"), split_merge("1")) - assert_image_equal(hopper("L"), split_merge("L")) - assert_image_equal(hopper("I"), split_merge("I")) - assert_image_equal(hopper("F"), split_merge("F")) - assert_image_equal(hopper("P"), split_merge("P")) - assert_image_equal(hopper("RGB"), split_merge("RGB")) - assert_image_equal(hopper("RGBA"), split_merge("RGBA")) - assert_image_equal(hopper("CMYK"), split_merge("CMYK")) - assert_image_equal(hopper("YCbCr"), split_merge("YCbCr")) +def test_split_merge(): + def split_merge(mode): + return Image.merge(mode, hopper(mode).split()) - def test_split_open(self): - codecs = dir(Image.core) + assert_image_equal(hopper("1"), split_merge("1")) + assert_image_equal(hopper("L"), split_merge("L")) + assert_image_equal(hopper("I"), split_merge("I")) + assert_image_equal(hopper("F"), split_merge("F")) + assert_image_equal(hopper("P"), split_merge("P")) + assert_image_equal(hopper("RGB"), split_merge("RGB")) + assert_image_equal(hopper("RGBA"), split_merge("RGBA")) + assert_image_equal(hopper("CMYK"), split_merge("CMYK")) + assert_image_equal(hopper("YCbCr"), split_merge("YCbCr")) - if "zip_encoder" in codecs: - test_file = self.tempfile("temp.png") - else: - test_file = self.tempfile("temp.pcx") - def split_open(mode): - hopper(mode).save(test_file) - with Image.open(test_file) as im: - return len(im.split()) +def test_split_open(tmp_path): + if features.check("zlib"): + test_file = str(tmp_path / "temp.png") + else: + test_file = str(tmp_path / "temp.pcx") - self.assertEqual(split_open("1"), 1) - self.assertEqual(split_open("L"), 1) - self.assertEqual(split_open("P"), 1) - self.assertEqual(split_open("RGB"), 3) - if "zip_encoder" in codecs: - self.assertEqual(split_open("RGBA"), 4) + def split_open(mode): + hopper(mode).save(test_file) + with Image.open(test_file) as im: + return len(im.split()) + + assert split_open("1") == 1 + assert split_open("L") == 1 + assert split_open("P") == 1 + assert split_open("RGB") == 3 + if features.check("zlib"): + assert split_open("RGBA") == 4 diff --git a/Tests/test_image_thumbnail.py b/Tests/test_image_thumbnail.py index 216793673..c42310c32 100644 --- a/Tests/test_image_thumbnail.py +++ b/Tests/test_image_thumbnail.py @@ -1,7 +1,8 @@ +import pytest + from PIL import Image from .helper import ( - PillowTestCase, assert_image_equal, assert_image_similar, fromstring, @@ -10,98 +11,121 @@ from .helper import ( ) -class TestImageThumbnail(PillowTestCase): - def test_sanity(self): - im = hopper() - self.assertIsNone(im.thumbnail((100, 100))) +def test_sanity(): + im = hopper() + assert im.thumbnail((100, 100)) is None - self.assertEqual(im.size, (100, 100)) + assert im.size == (100, 100) - def test_aspect(self): - im = Image.new("L", (128, 128)) - im.thumbnail((100, 100)) - self.assertEqual(im.size, (100, 100)) - im = Image.new("L", (128, 256)) - im.thumbnail((100, 100)) - self.assertEqual(im.size, (50, 100)) +def test_aspect(): + im = Image.new("L", (128, 128)) + im.thumbnail((100, 100)) + assert im.size == (100, 100) - im = Image.new("L", (128, 256)) - im.thumbnail((50, 100)) - self.assertEqual(im.size, (50, 100)) + im = Image.new("L", (128, 256)) + im.thumbnail((100, 100)) + assert im.size == (50, 100) - im = Image.new("L", (256, 128)) - im.thumbnail((100, 100)) - self.assertEqual(im.size, (100, 50)) + im = Image.new("L", (128, 256)) + im.thumbnail((50, 100)) + assert im.size == (50, 100) - im = Image.new("L", (256, 128)) - im.thumbnail((100, 50)) - self.assertEqual(im.size, (100, 50)) + im = Image.new("L", (256, 128)) + im.thumbnail((100, 100)) + assert im.size == (100, 50) - im = Image.new("L", (128, 128)) - im.thumbnail((100, 100)) - self.assertEqual(im.size, (100, 100)) + im = Image.new("L", (256, 128)) + im.thumbnail((100, 50)) + assert im.size == (100, 50) - im = Image.new("L", (256, 162)) # ratio is 1.5802469136 - im.thumbnail((33, 33)) - self.assertEqual(im.size, (33, 21)) # ratio is 1.5714285714 + im = Image.new("L", (64, 64)) + im.thumbnail((100, 100)) + assert im.size == (64, 64) - im = Image.new("L", (162, 256)) # ratio is 0.6328125 - im.thumbnail((33, 33)) - self.assertEqual(im.size, (21, 33)) # ratio is 0.6363636364 + im = Image.new("L", (256, 162)) # ratio is 1.5802469136 + im.thumbnail((33, 33)) + assert im.size == (33, 21) # ratio is 1.5714285714 - def test_float(self): - im = Image.new("L", (128, 128)) - im.thumbnail((99.9, 99.9)) - self.assertEqual(im.size, (100, 100)) + im = Image.new("L", (162, 256)) # ratio is 0.6328125 + im.thumbnail((33, 33)) + assert im.size == (21, 33) # ratio is 0.6363636364 - def test_no_resize(self): - # Check that draft() can resize the image to the destination size - with Image.open("Tests/images/hopper.jpg") as im: - im.draft(None, (64, 64)) - self.assertEqual(im.size, (64, 64)) + im = Image.new("L", (145, 100)) # ratio is 1.45 + im.thumbnail((50, 50)) + assert im.size == (50, 34) # ratio is 1.47058823529 - # Test thumbnail(), where only draft() is necessary to resize the image - with Image.open("Tests/images/hopper.jpg") as im: - im.thumbnail((64, 64)) - self.assertEqual(im.size, (64, 64)) + im = Image.new("L", (100, 145)) # ratio is 0.689655172414 + im.thumbnail((50, 50)) + assert im.size == (34, 50) # ratio is 0.68 - def test_DCT_scaling_edges(self): - # Make an image with red borders and size (N * 8) + 1 to cross DCT grid - im = Image.new("RGB", (257, 257), "red") - im.paste(Image.new("RGB", (235, 235)), (11, 11)) + im = Image.new("L", (100, 30)) # ratio is 3.333333333333 + im.thumbnail((75, 75)) + assert im.size == (75, 23) # ratio is 3.260869565217 - thumb = fromstring(tostring(im, "JPEG", quality=99, subsampling=0)) - # small reducing_gap to amplify the effect - thumb.thumbnail((32, 32), Image.BICUBIC, reducing_gap=1.0) - ref = im.resize((32, 32), Image.BICUBIC) - # This is still JPEG, some error is present. Without the fix it is 11.5 - assert_image_similar(thumb, ref, 1.5) +def test_division_by_zero(): + im = Image.new("L", (200, 2)) + im.thumbnail((75, 75)) + assert im.size == (75, 1) - def test_reducing_gap_values(self): - im = hopper() - im.thumbnail((18, 18), Image.BICUBIC) - ref = hopper() - ref.thumbnail((18, 18), Image.BICUBIC, reducing_gap=2.0) - # reducing_gap=2.0 should be the default +def test_float(): + im = Image.new("L", (128, 128)) + im.thumbnail((99.9, 99.9)) + assert im.size == (99, 99) + + +def test_no_resize(): + # Check that draft() can resize the image to the destination size + with Image.open("Tests/images/hopper.jpg") as im: + im.draft(None, (64, 64)) + assert im.size == (64, 64) + + # Test thumbnail(), where only draft() is necessary to resize the image + with Image.open("Tests/images/hopper.jpg") as im: + im.thumbnail((64, 64)) + assert im.size == (64, 64) + + +def test_DCT_scaling_edges(): + # Make an image with red borders and size (N * 8) + 1 to cross DCT grid + im = Image.new("RGB", (257, 257), "red") + im.paste(Image.new("RGB", (235, 235)), (11, 11)) + + thumb = fromstring(tostring(im, "JPEG", quality=99, subsampling=0)) + # small reducing_gap to amplify the effect + thumb.thumbnail((32, 32), Image.BICUBIC, reducing_gap=1.0) + + ref = im.resize((32, 32), Image.BICUBIC) + # This is still JPEG, some error is present. Without the fix it is 11.5 + assert_image_similar(thumb, ref, 1.5) + + +def test_reducing_gap_values(): + im = hopper() + im.thumbnail((18, 18), Image.BICUBIC) + + ref = hopper() + ref.thumbnail((18, 18), Image.BICUBIC, reducing_gap=2.0) + # reducing_gap=2.0 should be the default + assert_image_equal(ref, im) + + ref = hopper() + ref.thumbnail((18, 18), Image.BICUBIC, reducing_gap=None) + with pytest.raises(AssertionError): assert_image_equal(ref, im) - ref = hopper() - ref.thumbnail((18, 18), Image.BICUBIC, reducing_gap=None) - with self.assertRaises(AssertionError): + assert_image_similar(ref, im, 3.5) + + +def test_reducing_gap_for_DCT_scaling(): + with Image.open("Tests/images/hopper.jpg") as ref: + # thumbnail should call draft with reducing_gap scale + ref.draft(None, (18 * 3, 18 * 3)) + ref = ref.resize((18, 18), Image.BICUBIC) + + with Image.open("Tests/images/hopper.jpg") as im: + im.thumbnail((18, 18), Image.BICUBIC, reducing_gap=3.0) + assert_image_equal(ref, im) - - assert_image_similar(ref, im, 3.5) - - def test_reducing_gap_for_DCT_scaling(self): - with Image.open("Tests/images/hopper.jpg") as ref: - # thumbnail should call draft with reducing_gap scale - ref.draft(None, (18 * 3, 18 * 3)) - ref = ref.resize((18, 18), Image.BICUBIC) - - with Image.open("Tests/images/hopper.jpg") as im: - im.thumbnail((18, 18), Image.BICUBIC, reducing_gap=3.0) - - assert_image_equal(ref, im) diff --git a/Tests/test_image_tobitmap.py b/Tests/test_image_tobitmap.py index ab5b666cb..178cfcef3 100644 --- a/Tests/test_image_tobitmap.py +++ b/Tests/test_image_tobitmap.py @@ -1,14 +1,16 @@ -from .helper import PillowTestCase, assert_image_equal, fromstring, hopper +import pytest + +from .helper import assert_image_equal, fromstring, hopper -class TestImageToBitmap(PillowTestCase): - def test_sanity(self): +def test_sanity(): - self.assertRaises(ValueError, lambda: hopper().tobitmap()) + with pytest.raises(ValueError): + hopper().tobitmap() - im1 = hopper().convert("1") + im1 = hopper().convert("1") - bitmap = im1.tobitmap() + bitmap = im1.tobitmap() - self.assertIsInstance(bitmap, bytes) - assert_image_equal(im1, fromstring(bitmap)) + assert isinstance(bitmap, bytes) + assert_image_equal(im1, fromstring(bitmap)) diff --git a/Tests/test_image_transform.py b/Tests/test_image_transform.py index 7575fd1d8..3ee51178d 100644 --- a/Tests/test_image_transform.py +++ b/Tests/test_image_transform.py @@ -1,11 +1,13 @@ import math +import pytest + from PIL import Image, ImageTransform -from .helper import PillowTestCase, assert_image_equal, assert_image_similar, hopper +from .helper import assert_image_equal, assert_image_similar, hopper -class TestImageTransform(PillowTestCase): +class TestImageTransform: def test_sanity(self): im = Image.new("L", (100, 100)) @@ -24,11 +26,11 @@ class TestImageTransform(PillowTestCase): comment = b"File written by Adobe Photoshop\xa8 4.0" with Image.open("Tests/images/hopper.gif") as im: - self.assertEqual(im.info["comment"], comment) + assert im.info["comment"] == comment transform = ImageTransform.ExtentTransform((0, 0, 0, 0)) new_im = im.transform((100, 100), transform) - self.assertEqual(new_im.info["comment"], comment) + assert new_im.info["comment"] == comment def test_extent(self): im = hopper("RGB") @@ -79,7 +81,7 @@ class TestImageTransform(PillowTestCase): fillcolor="red", ) - self.assertEqual(transformed.getpixel((w - 1, h - 1)), pixel) + assert transformed.getpixel((w - 1, h - 1)) == pixel def test_mesh(self): # this should be a checkerboard of halfsized hoppers in ul, lr @@ -126,7 +128,7 @@ class TestImageTransform(PillowTestCase): im_background.paste(im, (0, 0), im) hist = im_background.histogram() - self.assertEqual(40 * 10, hist[-1]) + assert 40 * 10 == hist[-1] def test_alpha_premult_resize(self): def op(im, sz): @@ -165,23 +167,18 @@ class TestImageTransform(PillowTestCase): def test_missing_method_data(self): with hopper() as im: - self.assertRaises(ValueError, im.transform, (100, 100), None) + with pytest.raises(ValueError): + im.transform((100, 100), None) def test_unknown_resampling_filter(self): with hopper() as im: (w, h) = im.size for resample in (Image.BOX, "unknown"): - self.assertRaises( - ValueError, - im.transform, - (100, 100), - Image.EXTENT, - (0, 0, w, h), - resample, - ) + with pytest.raises(ValueError): + im.transform((100, 100), Image.EXTENT, (0, 0, w, h), resample) -class TestImageTransformAffine(PillowTestCase): +class TestImageTransformAffine: transform = Image.AFFINE def _test_image(self): diff --git a/Tests/test_image_transpose.py b/Tests/test_image_transpose.py index 940a48a53..a004434da 100644 --- a/Tests/test_image_transpose.py +++ b/Tests/test_image_transpose.py @@ -9,151 +9,154 @@ from PIL.Image import ( ) from . import helper -from .helper import PillowTestCase, assert_image_equal +from .helper import assert_image_equal + +HOPPER = { + mode: helper.hopper(mode).crop((0, 0, 121, 127)).copy() + for mode in ["L", "RGB", "I;16", "I;16L", "I;16B"] +} -class TestImageTranspose(PillowTestCase): +def test_flip_left_right(): + def transpose(mode): + im = HOPPER[mode] + out = im.transpose(FLIP_LEFT_RIGHT) + assert out.mode == mode + assert out.size == im.size - hopper = { - mode: helper.hopper(mode).crop((0, 0, 121, 127)).copy() - for mode in ["L", "RGB", "I;16", "I;16L", "I;16B"] - } + x, y = im.size + assert im.getpixel((1, 1)) == out.getpixel((x - 2, 1)) + assert im.getpixel((x - 2, 1)) == out.getpixel((1, 1)) + assert im.getpixel((1, y - 2)) == out.getpixel((x - 2, y - 2)) + assert im.getpixel((x - 2, y - 2)) == out.getpixel((1, y - 2)) - def test_flip_left_right(self): - def transpose(mode): - im = self.hopper[mode] - out = im.transpose(FLIP_LEFT_RIGHT) - self.assertEqual(out.mode, mode) - self.assertEqual(out.size, im.size) + for mode in HOPPER: + transpose(mode) - x, y = im.size - self.assertEqual(im.getpixel((1, 1)), out.getpixel((x - 2, 1))) - self.assertEqual(im.getpixel((x - 2, 1)), out.getpixel((1, 1))) - self.assertEqual(im.getpixel((1, y - 2)), out.getpixel((x - 2, y - 2))) - self.assertEqual(im.getpixel((x - 2, y - 2)), out.getpixel((1, y - 2))) - for mode in self.hopper: - transpose(mode) +def test_flip_top_bottom(): + def transpose(mode): + im = HOPPER[mode] + out = im.transpose(FLIP_TOP_BOTTOM) + assert out.mode == mode + assert out.size == im.size - def test_flip_top_bottom(self): - def transpose(mode): - im = self.hopper[mode] - out = im.transpose(FLIP_TOP_BOTTOM) - self.assertEqual(out.mode, mode) - self.assertEqual(out.size, im.size) + x, y = im.size + assert im.getpixel((1, 1)) == out.getpixel((1, y - 2)) + assert im.getpixel((x - 2, 1)) == out.getpixel((x - 2, y - 2)) + assert im.getpixel((1, y - 2)) == out.getpixel((1, 1)) + assert im.getpixel((x - 2, y - 2)) == out.getpixel((x - 2, 1)) - x, y = im.size - self.assertEqual(im.getpixel((1, 1)), out.getpixel((1, y - 2))) - self.assertEqual(im.getpixel((x - 2, 1)), out.getpixel((x - 2, y - 2))) - self.assertEqual(im.getpixel((1, y - 2)), out.getpixel((1, 1))) - self.assertEqual(im.getpixel((x - 2, y - 2)), out.getpixel((x - 2, 1))) + for mode in HOPPER: + transpose(mode) - for mode in self.hopper: - transpose(mode) - def test_rotate_90(self): - def transpose(mode): - im = self.hopper[mode] - out = im.transpose(ROTATE_90) - self.assertEqual(out.mode, mode) - self.assertEqual(out.size, im.size[::-1]) +def test_rotate_90(): + def transpose(mode): + im = HOPPER[mode] + out = im.transpose(ROTATE_90) + assert out.mode == mode + assert out.size == im.size[::-1] - x, y = im.size - self.assertEqual(im.getpixel((1, 1)), out.getpixel((1, x - 2))) - self.assertEqual(im.getpixel((x - 2, 1)), out.getpixel((1, 1))) - self.assertEqual(im.getpixel((1, y - 2)), out.getpixel((y - 2, x - 2))) - self.assertEqual(im.getpixel((x - 2, y - 2)), out.getpixel((y - 2, 1))) + x, y = im.size + assert im.getpixel((1, 1)) == out.getpixel((1, x - 2)) + assert im.getpixel((x - 2, 1)) == out.getpixel((1, 1)) + assert im.getpixel((1, y - 2)) == out.getpixel((y - 2, x - 2)) + assert im.getpixel((x - 2, y - 2)) == out.getpixel((y - 2, 1)) - for mode in self.hopper: - transpose(mode) + for mode in HOPPER: + transpose(mode) - def test_rotate_180(self): - def transpose(mode): - im = self.hopper[mode] - out = im.transpose(ROTATE_180) - self.assertEqual(out.mode, mode) - self.assertEqual(out.size, im.size) - x, y = im.size - self.assertEqual(im.getpixel((1, 1)), out.getpixel((x - 2, y - 2))) - self.assertEqual(im.getpixel((x - 2, 1)), out.getpixel((1, y - 2))) - self.assertEqual(im.getpixel((1, y - 2)), out.getpixel((x - 2, 1))) - self.assertEqual(im.getpixel((x - 2, y - 2)), out.getpixel((1, 1))) +def test_rotate_180(): + def transpose(mode): + im = HOPPER[mode] + out = im.transpose(ROTATE_180) + assert out.mode == mode + assert out.size == im.size - for mode in self.hopper: - transpose(mode) + x, y = im.size + assert im.getpixel((1, 1)) == out.getpixel((x - 2, y - 2)) + assert im.getpixel((x - 2, 1)) == out.getpixel((1, y - 2)) + assert im.getpixel((1, y - 2)) == out.getpixel((x - 2, 1)) + assert im.getpixel((x - 2, y - 2)) == out.getpixel((1, 1)) - def test_rotate_270(self): - def transpose(mode): - im = self.hopper[mode] - out = im.transpose(ROTATE_270) - self.assertEqual(out.mode, mode) - self.assertEqual(out.size, im.size[::-1]) + for mode in HOPPER: + transpose(mode) - x, y = im.size - self.assertEqual(im.getpixel((1, 1)), out.getpixel((y - 2, 1))) - self.assertEqual(im.getpixel((x - 2, 1)), out.getpixel((y - 2, x - 2))) - self.assertEqual(im.getpixel((1, y - 2)), out.getpixel((1, 1))) - self.assertEqual(im.getpixel((x - 2, y - 2)), out.getpixel((1, x - 2))) - for mode in self.hopper: - transpose(mode) +def test_rotate_270(): + def transpose(mode): + im = HOPPER[mode] + out = im.transpose(ROTATE_270) + assert out.mode == mode + assert out.size == im.size[::-1] - def test_transpose(self): - def transpose(mode): - im = self.hopper[mode] - out = im.transpose(TRANSPOSE) - self.assertEqual(out.mode, mode) - self.assertEqual(out.size, im.size[::-1]) + x, y = im.size + assert im.getpixel((1, 1)) == out.getpixel((y - 2, 1)) + assert im.getpixel((x - 2, 1)) == out.getpixel((y - 2, x - 2)) + assert im.getpixel((1, y - 2)) == out.getpixel((1, 1)) + assert im.getpixel((x - 2, y - 2)) == out.getpixel((1, x - 2)) - x, y = im.size - self.assertEqual(im.getpixel((1, 1)), out.getpixel((1, 1))) - self.assertEqual(im.getpixel((x - 2, 1)), out.getpixel((1, x - 2))) - self.assertEqual(im.getpixel((1, y - 2)), out.getpixel((y - 2, 1))) - self.assertEqual(im.getpixel((x - 2, y - 2)), out.getpixel((y - 2, x - 2))) + for mode in HOPPER: + transpose(mode) - for mode in self.hopper: - transpose(mode) - def test_tranverse(self): - def transpose(mode): - im = self.hopper[mode] - out = im.transpose(TRANSVERSE) - self.assertEqual(out.mode, mode) - self.assertEqual(out.size, im.size[::-1]) +def test_transpose(): + def transpose(mode): + im = HOPPER[mode] + out = im.transpose(TRANSPOSE) + assert out.mode == mode + assert out.size == im.size[::-1] - x, y = im.size - self.assertEqual(im.getpixel((1, 1)), out.getpixel((y - 2, x - 2))) - self.assertEqual(im.getpixel((x - 2, 1)), out.getpixel((y - 2, 1))) - self.assertEqual(im.getpixel((1, y - 2)), out.getpixel((1, x - 2))) - self.assertEqual(im.getpixel((x - 2, y - 2)), out.getpixel((1, 1))) + x, y = im.size + assert im.getpixel((1, 1)) == out.getpixel((1, 1)) + assert im.getpixel((x - 2, 1)) == out.getpixel((1, x - 2)) + assert im.getpixel((1, y - 2)) == out.getpixel((y - 2, 1)) + assert im.getpixel((x - 2, y - 2)) == out.getpixel((y - 2, x - 2)) - for mode in self.hopper: - transpose(mode) + for mode in HOPPER: + transpose(mode) - def test_roundtrip(self): - for mode in self.hopper: - im = self.hopper[mode] - def transpose(first, second): - return im.transpose(first).transpose(second) +def test_tranverse(): + def transpose(mode): + im = HOPPER[mode] + out = im.transpose(TRANSVERSE) + assert out.mode == mode + assert out.size == im.size[::-1] - assert_image_equal(im, transpose(FLIP_LEFT_RIGHT, FLIP_LEFT_RIGHT)) - assert_image_equal(im, transpose(FLIP_TOP_BOTTOM, FLIP_TOP_BOTTOM)) - assert_image_equal(im, transpose(ROTATE_90, ROTATE_270)) - assert_image_equal(im, transpose(ROTATE_180, ROTATE_180)) - assert_image_equal( - im.transpose(TRANSPOSE), transpose(ROTATE_90, FLIP_TOP_BOTTOM) - ) - assert_image_equal( - im.transpose(TRANSPOSE), transpose(ROTATE_270, FLIP_LEFT_RIGHT) - ) - assert_image_equal( - im.transpose(TRANSVERSE), transpose(ROTATE_90, FLIP_LEFT_RIGHT) - ) - assert_image_equal( - im.transpose(TRANSVERSE), transpose(ROTATE_270, FLIP_TOP_BOTTOM) - ) - assert_image_equal( - im.transpose(TRANSVERSE), transpose(ROTATE_180, TRANSPOSE) - ) + x, y = im.size + assert im.getpixel((1, 1)) == out.getpixel((y - 2, x - 2)) + assert im.getpixel((x - 2, 1)) == out.getpixel((y - 2, 1)) + assert im.getpixel((1, y - 2)) == out.getpixel((1, x - 2)) + assert im.getpixel((x - 2, y - 2)) == out.getpixel((1, 1)) + + for mode in HOPPER: + transpose(mode) + + +def test_roundtrip(): + for mode in HOPPER: + im = HOPPER[mode] + + def transpose(first, second): + return im.transpose(first).transpose(second) + + assert_image_equal(im, transpose(FLIP_LEFT_RIGHT, FLIP_LEFT_RIGHT)) + assert_image_equal(im, transpose(FLIP_TOP_BOTTOM, FLIP_TOP_BOTTOM)) + assert_image_equal(im, transpose(ROTATE_90, ROTATE_270)) + assert_image_equal(im, transpose(ROTATE_180, ROTATE_180)) + assert_image_equal( + im.transpose(TRANSPOSE), transpose(ROTATE_90, FLIP_TOP_BOTTOM) + ) + assert_image_equal( + im.transpose(TRANSPOSE), transpose(ROTATE_270, FLIP_LEFT_RIGHT) + ) + assert_image_equal( + im.transpose(TRANSVERSE), transpose(ROTATE_90, FLIP_LEFT_RIGHT) + ) + assert_image_equal( + im.transpose(TRANSVERSE), transpose(ROTATE_270, FLIP_TOP_BOTTOM) + ) + assert_image_equal(im.transpose(TRANSVERSE), transpose(ROTATE_180, TRANSPOSE)) diff --git a/Tests/test_imagechops.py b/Tests/test_imagechops.py index 6eb61ec59..a19fbf239 100644 --- a/Tests/test_imagechops.py +++ b/Tests/test_imagechops.py @@ -1,6 +1,6 @@ from PIL import Image, ImageChops -from .helper import PillowTestCase, assert_image_equal, hopper +from .helper import assert_image_equal, hopper BLACK = (0, 0, 0) BROWN = (127, 64, 0) @@ -13,352 +13,416 @@ WHITE = (255, 255, 255) GREY = 128 -class TestImageChops(PillowTestCase): - def test_sanity(self): +def test_sanity(): + im = hopper("L") - im = hopper("L") + ImageChops.constant(im, 128) + ImageChops.duplicate(im) + ImageChops.invert(im) + ImageChops.lighter(im, im) + ImageChops.darker(im, im) + ImageChops.difference(im, im) + ImageChops.multiply(im, im) + ImageChops.screen(im, im) - ImageChops.constant(im, 128) - ImageChops.duplicate(im) - ImageChops.invert(im) - ImageChops.lighter(im, im) - ImageChops.darker(im, im) - ImageChops.difference(im, im) - ImageChops.multiply(im, im) - ImageChops.screen(im, im) + ImageChops.add(im, im) + ImageChops.add(im, im, 2.0) + ImageChops.add(im, im, 2.0, 128) + ImageChops.subtract(im, im) + ImageChops.subtract(im, im, 2.0) + ImageChops.subtract(im, im, 2.0, 128) - ImageChops.add(im, im) - ImageChops.add(im, im, 2.0) - ImageChops.add(im, im, 2.0, 128) - ImageChops.subtract(im, im) - ImageChops.subtract(im, im, 2.0) - ImageChops.subtract(im, im, 2.0, 128) + ImageChops.add_modulo(im, im) + ImageChops.subtract_modulo(im, im) - ImageChops.add_modulo(im, im) - ImageChops.subtract_modulo(im, im) + ImageChops.blend(im, im, 0.5) + ImageChops.composite(im, im, im) - ImageChops.blend(im, im, 0.5) - ImageChops.composite(im, im, im) + ImageChops.soft_light(im, im) + ImageChops.hard_light(im, im) + ImageChops.overlay(im, im) - ImageChops.offset(im, 10) - ImageChops.offset(im, 10, 20) + ImageChops.offset(im, 10) + ImageChops.offset(im, 10, 20) - def test_add(self): - # 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) +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: - # Assert - self.assertEqual(new.getbbox(), (25, 25, 76, 76)) - self.assertEqual(new.getpixel((50, 50)), ORANGE) + # Act + new = ImageChops.add(im1, im2) - def test_add_scale_offset(self): - # Arrange - with Image.open("Tests/images/imagedraw_ellipse_RGB.png") as im1: - with Image.open("Tests/images/imagedraw_floodfill_RGB.png") as im2: + # Assert + assert new.getbbox() == (25, 25, 76, 76) + assert new.getpixel((50, 50)) == ORANGE - # Act - new = ImageChops.add(im1, im2, scale=2.5, offset=100) - # Assert - self.assertEqual(new.getbbox(), (0, 0, 100, 100)) - self.assertEqual(new.getpixel((50, 50)), (202, 151, 100)) +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: - def test_add_clip(self): - # Arrange - im = hopper() + # Act + new = ImageChops.add(im1, im2, scale=2.5, offset=100) - # Act - new = ImageChops.add(im, im) + # Assert + assert new.getbbox() == (0, 0, 100, 100) + assert new.getpixel((50, 50)) == (202, 151, 100) - # Assert - self.assertEqual(new.getpixel((50, 50)), (255, 255, 254)) - def test_add_modulo(self): - # Arrange - with Image.open("Tests/images/imagedraw_ellipse_RGB.png") as im1: - with Image.open("Tests/images/imagedraw_floodfill_RGB.png") as im2: +def test_add_clip(): + # Arrange + im = hopper() - # Act - new = ImageChops.add_modulo(im1, im2) + # Act + new = ImageChops.add(im, im) - # Assert - self.assertEqual(new.getbbox(), (25, 25, 76, 76)) - self.assertEqual(new.getpixel((50, 50)), ORANGE) + # Assert + assert new.getpixel((50, 50)) == (255, 255, 254) - def test_add_modulo_no_clip(self): - # Arrange - im = hopper() - # Act - new = ImageChops.add_modulo(im, im) +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: - # Assert - self.assertEqual(new.getpixel((50, 50)), (224, 76, 254)) + # Act + new = ImageChops.add_modulo(im1, im2) - def test_blend(self): - # Arrange - with Image.open("Tests/images/imagedraw_ellipse_RGB.png") as im1: - with Image.open("Tests/images/imagedraw_floodfill_RGB.png") as im2: + # Assert + assert new.getbbox() == (25, 25, 76, 76) + assert new.getpixel((50, 50)) == ORANGE - # Act - new = ImageChops.blend(im1, im2, 0.5) - # Assert - self.assertEqual(new.getbbox(), (25, 25, 76, 76)) - self.assertEqual(new.getpixel((50, 50)), BROWN) +def test_add_modulo_no_clip(): + # Arrange + im = hopper() - def test_constant(self): - # Arrange - im = Image.new("RGB", (20, 10)) + # Act + new = ImageChops.add_modulo(im, im) - # Act - new = ImageChops.constant(im, GREY) + # Assert + assert new.getpixel((50, 50)) == (224, 76, 254) - # Assert - self.assertEqual(new.size, im.size) - self.assertEqual(new.getpixel((0, 0)), GREY) - self.assertEqual(new.getpixel((19, 9)), GREY) - def test_darker_image(self): - # Arrange - with Image.open("Tests/images/imagedraw_chord_RGB.png") as im1: - with Image.open("Tests/images/imagedraw_outline_chord_RGB.png") as im2: +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.darker(im1, im2) + # Act + new = ImageChops.blend(im1, im2, 0.5) - # Assert - assert_image_equal(new, im2) + # Assert + assert new.getbbox() == (25, 25, 76, 76) + assert new.getpixel((50, 50)) == BROWN - def test_darker_pixel(self): - # Arrange - im1 = hopper() - with Image.open("Tests/images/imagedraw_chord_RGB.png") as im2: + +def test_constant(): + # Arrange + im = Image.new("RGB", (20, 10)) + + # Act + new = ImageChops.constant(im, GREY) + + # Assert + assert new.size == im.size + assert new.getpixel((0, 0)) == GREY + assert new.getpixel((19, 9)) == GREY + + +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) - # Assert - self.assertEqual(new.getpixel((50, 50)), (240, 166, 0)) + # Assert + assert_image_equal(new, im2) - def test_difference(self): - # 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) +def test_darker_pixel(): + # Arrange + im1 = hopper() + with Image.open("Tests/images/imagedraw_chord_RGB.png") as im2: - # Assert - self.assertEqual(new.getbbox(), (25, 25, 76, 76)) + # Act + new = ImageChops.darker(im1, im2) - def test_difference_pixel(self): - # Arrange - im1 = hopper() - with Image.open("Tests/images/imagedraw_polygon_kite_RGB.png") as im2: + # Assert + assert new.getpixel((50, 50)) == (240, 166, 0) + + +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) - # Assert - self.assertEqual(new.getpixel((50, 50)), (240, 166, 128)) + # Assert + assert new.getbbox() == (25, 25, 76, 76) - def test_duplicate(self): - # Arrange - im = hopper() + +def test_difference_pixel(): + # Arrange + im1 = hopper() + with Image.open("Tests/images/imagedraw_polygon_kite_RGB.png") as im2: # Act - new = ImageChops.duplicate(im) + new = ImageChops.difference(im1, im2) - # Assert - assert_image_equal(new, im) + # Assert + assert new.getpixel((50, 50)) == (240, 166, 128) - def test_invert(self): - # Arrange - with Image.open("Tests/images/imagedraw_floodfill_RGB.png") as im: - # Act - new = ImageChops.invert(im) +def test_duplicate(): + # Arrange + im = hopper() - # Assert - self.assertEqual(new.getbbox(), (0, 0, 100, 100)) - self.assertEqual(new.getpixel((0, 0)), WHITE) - self.assertEqual(new.getpixel((50, 50)), CYAN) + # Act + new = ImageChops.duplicate(im) - def test_lighter_image(self): - # Arrange - with Image.open("Tests/images/imagedraw_chord_RGB.png") as im1: - with Image.open("Tests/images/imagedraw_outline_chord_RGB.png") as im2: + # Assert + assert_image_equal(new, im) - # Act - new = ImageChops.lighter(im1, im2) - # Assert - assert_image_equal(new, im1) +def test_invert(): + # Arrange + with Image.open("Tests/images/imagedraw_floodfill_RGB.png") as im: - def test_lighter_pixel(self): - # Arrange - im1 = hopper() - with Image.open("Tests/images/imagedraw_chord_RGB.png") as im2: + # Act + new = ImageChops.invert(im) + + # Assert + assert new.getbbox() == (0, 0, 100, 100) + assert new.getpixel((0, 0)) == WHITE + assert new.getpixel((50, 50)) == CYAN + + +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) - # Assert - self.assertEqual(new.getpixel((50, 50)), (255, 255, 127)) - - def test_multiply_black(self): - """If you multiply an image with a solid black image, - the result is black.""" - # Arrange - im1 = hopper() - black = Image.new("RGB", im1.size, "black") - - # Act - new = ImageChops.multiply(im1, black) - - # Assert - assert_image_equal(new, black) - - def test_multiply_green(self): - # Arrange - with Image.open("Tests/images/imagedraw_floodfill_RGB.png") as im: - green = Image.new("RGB", im.size, "green") - - # Act - new = ImageChops.multiply(im, green) - - # Assert - self.assertEqual(new.getbbox(), (25, 25, 76, 76)) - self.assertEqual(new.getpixel((25, 25)), DARK_GREEN) - self.assertEqual(new.getpixel((50, 50)), BLACK) - - def test_multiply_white(self): - """If you multiply with a solid white image, - the image is unaffected.""" - # Arrange - im1 = hopper() - white = Image.new("RGB", im1.size, "white") - - # Act - new = ImageChops.multiply(im1, white) - # Assert assert_image_equal(new, im1) - def test_offset(self): - # Arrange - xoffset = 45 - yoffset = 20 - with Image.open("Tests/images/imagedraw_ellipse_RGB.png") as im: - # Act - new = ImageChops.offset(im, xoffset, yoffset) +def test_lighter_pixel(): + # Arrange + im1 = hopper() + with Image.open("Tests/images/imagedraw_chord_RGB.png") as im2: + + # Act + new = ImageChops.lighter(im1, im2) + + # Assert + assert new.getpixel((50, 50)) == (255, 255, 127) + + +def test_multiply_black(): + """If you multiply an image with a solid black image, + the result is black.""" + # Arrange + im1 = hopper() + black = Image.new("RGB", im1.size, "black") + + # Act + new = ImageChops.multiply(im1, black) + + # Assert + assert_image_equal(new, black) + + +def test_multiply_green(): + # Arrange + with Image.open("Tests/images/imagedraw_floodfill_RGB.png") as im: + green = Image.new("RGB", im.size, "green") + + # Act + new = ImageChops.multiply(im, green) + + # Assert + assert new.getbbox() == (25, 25, 76, 76) + assert new.getpixel((25, 25)) == DARK_GREEN + assert new.getpixel((50, 50)) == BLACK + + +def test_multiply_white(): + """If you multiply with a solid white image, the image is unaffected.""" + # Arrange + im1 = hopper() + white = Image.new("RGB", im1.size, "white") + + # Act + new = ImageChops.multiply(im1, white) + + # Assert + assert_image_equal(new, im1) + + +def test_offset(): + # Arrange + xoffset = 45 + yoffset = 20 + with Image.open("Tests/images/imagedraw_ellipse_RGB.png") as im: + + # Act + new = ImageChops.offset(im, xoffset, yoffset) # Assert - self.assertEqual(new.getbbox(), (0, 45, 100, 96)) - self.assertEqual(new.getpixel((50, 50)), BLACK) - self.assertEqual(new.getpixel((50 + xoffset, 50 + yoffset)), DARK_GREEN) + assert new.getbbox() == (0, 45, 100, 96) + assert new.getpixel((50, 50)) == BLACK + assert new.getpixel((50 + xoffset, 50 + yoffset)) == DARK_GREEN # Test no yoffset - self.assertEqual( - ImageChops.offset(im, xoffset), ImageChops.offset(im, xoffset, xoffset) - ) + assert ImageChops.offset(im, xoffset) == ImageChops.offset(im, xoffset, xoffset) - def test_screen(self): - # 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) +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: - # Assert - self.assertEqual(new.getbbox(), (25, 25, 76, 76)) - self.assertEqual(new.getpixel((50, 50)), ORANGE) + # Act + new = ImageChops.screen(im1, im2) - def test_subtract(self): - # Arrange - with Image.open("Tests/images/imagedraw_chord_RGB.png") as im1: - with Image.open("Tests/images/imagedraw_outline_chord_RGB.png") as im2: + # Assert + assert new.getbbox() == (25, 25, 76, 76) + assert new.getpixel((50, 50)) == ORANGE - # Act - new = ImageChops.subtract(im1, im2) - # Assert - self.assertEqual(new.getbbox(), (25, 50, 76, 76)) - self.assertEqual(new.getpixel((50, 50)), GREEN) - self.assertEqual(new.getpixel((50, 51)), BLACK) - - def test_subtract_scale_offset(self): - # 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) - - # Assert - self.assertEqual(new.getbbox(), (0, 0, 100, 100)) - self.assertEqual(new.getpixel((50, 50)), (100, 202, 100)) - - def test_subtract_clip(self): - # Arrange - im1 = hopper() - with Image.open("Tests/images/imagedraw_chord_RGB.png") as im2: +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) - # Assert - self.assertEqual(new.getpixel((50, 50)), (0, 0, 127)) + # Assert + assert new.getbbox() == (25, 50, 76, 76) + assert new.getpixel((50, 51)) == GREEN + assert new.getpixel((50, 52)) == BLACK - def test_subtract_modulo(self): - # 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) +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: - # Assert - self.assertEqual(new.getbbox(), (25, 50, 76, 76)) - self.assertEqual(new.getpixel((50, 50)), GREEN) - self.assertEqual(new.getpixel((50, 51)), BLACK) + # Act + new = ImageChops.subtract(im1, im2, scale=2.5, offset=100) - def test_subtract_modulo_no_clip(self): - # Arrange - im1 = hopper() - with Image.open("Tests/images/imagedraw_chord_RGB.png") as im2: + # Assert + assert new.getbbox() == (0, 0, 100, 100) + assert new.getpixel((50, 50)) == (100, 202, 100) + + +def test_subtract_clip(): + # Arrange + im1 = hopper() + with Image.open("Tests/images/imagedraw_chord_RGB.png") as im2: + + # Act + new = ImageChops.subtract(im1, im2) + + # Assert + assert new.getpixel((50, 50)) == (0, 0, 127) + + +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) - # Assert - self.assertEqual(new.getpixel((50, 50)), (241, 167, 127)) + # Assert + assert new.getbbox() == (25, 50, 76, 76) + assert new.getpixel((50, 51)) == GREEN + assert new.getpixel((50, 52)) == BLACK - def test_logical(self): - def table(op, a, b): - out = [] - for x in (a, b): - imx = Image.new("1", (1, 1), x) - for y in (a, b): - imy = Image.new("1", (1, 1), y) - out.append(op(imx, imy).getpixel((0, 0))) - return tuple(out) - self.assertEqual(table(ImageChops.logical_and, 0, 1), (0, 0, 0, 255)) - self.assertEqual(table(ImageChops.logical_or, 0, 1), (0, 255, 255, 255)) - self.assertEqual(table(ImageChops.logical_xor, 0, 1), (0, 255, 255, 0)) +def test_subtract_modulo_no_clip(): + # Arrange + im1 = hopper() + with Image.open("Tests/images/imagedraw_chord_RGB.png") as im2: - self.assertEqual(table(ImageChops.logical_and, 0, 128), (0, 0, 0, 255)) - self.assertEqual(table(ImageChops.logical_or, 0, 128), (0, 255, 255, 255)) - self.assertEqual(table(ImageChops.logical_xor, 0, 128), (0, 255, 255, 0)) + # Act + new = ImageChops.subtract_modulo(im1, im2) - self.assertEqual(table(ImageChops.logical_and, 0, 255), (0, 0, 0, 255)) - self.assertEqual(table(ImageChops.logical_or, 0, 255), (0, 255, 255, 255)) - self.assertEqual(table(ImageChops.logical_xor, 0, 255), (0, 255, 255, 0)) + # Assert + assert new.getpixel((50, 50)) == (241, 167, 127) + + +def test_soft_light(): + # Arrange + im1 = Image.open("Tests/images/hopper.png") + im2 = Image.open("Tests/images/hopper-XYZ.png") + + # Act + new = ImageChops.soft_light(im1, im2) + + # Assert + assert new.getpixel((64, 64)) == (163, 54, 32) + assert new.getpixel((15, 100)) == (1, 1, 3) + + +def test_hard_light(): + # Arrange + im1 = Image.open("Tests/images/hopper.png") + im2 = Image.open("Tests/images/hopper-XYZ.png") + + # Act + new = ImageChops.hard_light(im1, im2) + + # Assert + assert new.getpixel((64, 64)) == (144, 50, 27) + assert new.getpixel((15, 100)) == (1, 1, 2) + + +def test_overlay(): + # Arrange + im1 = Image.open("Tests/images/hopper.png") + im2 = Image.open("Tests/images/hopper-XYZ.png") + + # Act + new = ImageChops.overlay(im1, im2) + + # Assert + assert new.getpixel((64, 64)) == (159, 50, 27) + assert new.getpixel((15, 100)) == (1, 1, 2) + + +def test_logical(): + def table(op, a, b): + out = [] + for x in (a, b): + imx = Image.new("1", (1, 1), x) + for y in (a, b): + imy = Image.new("1", (1, 1), y) + out.append(op(imx, imy).getpixel((0, 0))) + return tuple(out) + + assert table(ImageChops.logical_and, 0, 1) == (0, 0, 0, 255) + assert table(ImageChops.logical_or, 0, 1) == (0, 255, 255, 255) + assert table(ImageChops.logical_xor, 0, 1) == (0, 255, 255, 0) + + assert table(ImageChops.logical_and, 0, 128) == (0, 0, 0, 255) + assert table(ImageChops.logical_or, 0, 128) == (0, 255, 255, 255) + assert table(ImageChops.logical_xor, 0, 128) == (0, 255, 255, 0) + + assert table(ImageChops.logical_and, 0, 255) == (0, 0, 0, 255) + assert table(ImageChops.logical_or, 0, 255) == (0, 255, 255, 255) + assert table(ImageChops.logical_xor, 0, 255) == (0, 255, 255, 0) diff --git a/Tests/test_imagecms.py b/Tests/test_imagecms.py index ba704f4c8..9fab41746 100644 --- a/Tests/test_imagecms.py +++ b/Tests/test_imagecms.py @@ -1,17 +1,14 @@ import datetime import os +import re +import shutil from io import BytesIO import pytest -from PIL import Image, ImageMode -from .helper import ( - PillowTestCase, - assert_image, - assert_image_equal, - assert_image_similar, - hopper, -) +from PIL import Image, ImageMode, features + +from .helper import assert_image, assert_image_equal, assert_image_similar, hopper try: from PIL import ImageCms @@ -19,7 +16,7 @@ try: ImageCms.core.profile_open except ImportError: - # Skipped via setUp() + # Skipped via setup_module() pass @@ -27,588 +24,567 @@ SRGB = "Tests/icc/sRGB_IEC61966-2-1_black_scaled.icc" HAVE_PROFILE = os.path.exists(SRGB) -class TestImageCms(PillowTestCase): - def setUp(self): - try: - from PIL import ImageCms +def setup_module(): + try: + from PIL import ImageCms - # need to hit getattr to trigger the delayed import error - ImageCms.core.profile_open - except ImportError as v: - self.skipTest(v) + # need to hit getattr to trigger the delayed import error + ImageCms.core.profile_open + except ImportError as v: + pytest.skip(str(v)) - def skip_missing(self): - if not HAVE_PROFILE: - self.skipTest("SRGB profile not available") - def test_sanity(self): +def skip_missing(): + if not HAVE_PROFILE: + pytest.skip("SRGB profile not available") - # basic smoke test. - # this mostly follows the cms_test outline. - v = ImageCms.versions() # should return four strings - self.assertEqual(v[0], "1.0.0 pil") - self.assertEqual(list(map(type, v)), [str, str, str, str]) +def test_sanity(): + # basic smoke test. + # this mostly follows the cms_test outline. - # internal version number - self.assertRegex(ImageCms.core.littlecms_version, r"\d+\.\d+$") + v = ImageCms.versions() # should return four strings + assert v[0] == "1.0.0 pil" + assert list(map(type, v)) == [str, str, str, str] - self.skip_missing() - i = ImageCms.profileToProfile(hopper(), SRGB, SRGB) - assert_image(i, "RGB", (128, 128)) + # internal version number + assert re.search(r"\d+\.\d+(\.\d+)?$", features.version_module("littlecms2")) - i = hopper() - ImageCms.profileToProfile(i, SRGB, SRGB, inPlace=True) - assert_image(i, "RGB", (128, 128)) + skip_missing() + i = ImageCms.profileToProfile(hopper(), SRGB, SRGB) + assert_image(i, "RGB", (128, 128)) + i = hopper() + ImageCms.profileToProfile(i, SRGB, SRGB, inPlace=True) + assert_image(i, "RGB", (128, 128)) + + t = ImageCms.buildTransform(SRGB, SRGB, "RGB", "RGB") + i = ImageCms.applyTransform(hopper(), t) + assert_image(i, "RGB", (128, 128)) + + with hopper() as i: t = ImageCms.buildTransform(SRGB, SRGB, "RGB", "RGB") - i = ImageCms.applyTransform(hopper(), t) + ImageCms.applyTransform(hopper(), t, inPlace=True) assert_image(i, "RGB", (128, 128)) - with hopper() as i: - t = ImageCms.buildTransform(SRGB, SRGB, "RGB", "RGB") - ImageCms.applyTransform(hopper(), t, inPlace=True) - assert_image(i, "RGB", (128, 128)) + p = ImageCms.createProfile("sRGB") + o = ImageCms.getOpenProfile(SRGB) + t = ImageCms.buildTransformFromOpenProfiles(p, o, "RGB", "RGB") + i = ImageCms.applyTransform(hopper(), t) + assert_image(i, "RGB", (128, 128)) - p = ImageCms.createProfile("sRGB") - o = ImageCms.getOpenProfile(SRGB) - t = ImageCms.buildTransformFromOpenProfiles(p, o, "RGB", "RGB") - i = ImageCms.applyTransform(hopper(), t) - assert_image(i, "RGB", (128, 128)) + t = ImageCms.buildProofTransform(SRGB, SRGB, SRGB, "RGB", "RGB") + assert t.inputMode == "RGB" + assert t.outputMode == "RGB" + i = ImageCms.applyTransform(hopper(), t) + assert_image(i, "RGB", (128, 128)) - t = ImageCms.buildProofTransform(SRGB, SRGB, SRGB, "RGB", "RGB") - self.assertEqual(t.inputMode, "RGB") - self.assertEqual(t.outputMode, "RGB") - i = ImageCms.applyTransform(hopper(), t) - assert_image(i, "RGB", (128, 128)) + # test PointTransform convenience API + hopper().point(t) - # test PointTransform convenience API - hopper().point(t) - def test_name(self): - self.skip_missing() - # get profile information for file - self.assertEqual( - ImageCms.getProfileName(SRGB).strip(), - "IEC 61966-2-1 Default RGB Colour Space - sRGB", - ) +def test_name(): + skip_missing() + # get profile information for file + assert ( + ImageCms.getProfileName(SRGB).strip() + == "IEC 61966-2-1 Default RGB Colour Space - sRGB" + ) - def test_info(self): - self.skip_missing() - self.assertEqual( - ImageCms.getProfileInfo(SRGB).splitlines(), - [ - "sRGB IEC61966-2-1 black scaled", - "", - "Copyright International Color Consortium, 2009", - "", - ], - ) - def test_copyright(self): - self.skip_missing() - self.assertEqual( - ImageCms.getProfileCopyright(SRGB).strip(), - "Copyright International Color Consortium, 2009", - ) +def test_info(): + skip_missing() + assert ImageCms.getProfileInfo(SRGB).splitlines() == [ + "sRGB IEC61966-2-1 black scaled", + "", + "Copyright International Color Consortium, 2009", + "", + ] - def test_manufacturer(self): - self.skip_missing() - self.assertEqual(ImageCms.getProfileManufacturer(SRGB).strip(), "") - def test_model(self): - self.skip_missing() - self.assertEqual( - ImageCms.getProfileModel(SRGB).strip(), - "IEC 61966-2-1 Default RGB Colour Space - sRGB", - ) +def test_copyright(): + skip_missing() + assert ( + ImageCms.getProfileCopyright(SRGB).strip() + == "Copyright International Color Consortium, 2009" + ) - def test_description(self): - self.skip_missing() - self.assertEqual( - ImageCms.getProfileDescription(SRGB).strip(), - "sRGB IEC61966-2-1 black scaled", - ) - def test_intent(self): - self.skip_missing() - self.assertEqual(ImageCms.getDefaultIntent(SRGB), 0) - self.assertEqual( - ImageCms.isIntentSupported( - SRGB, ImageCms.INTENT_ABSOLUTE_COLORIMETRIC, ImageCms.DIRECTION_INPUT - ), - 1, - ) +def test_manufacturer(): + skip_missing() + assert ImageCms.getProfileManufacturer(SRGB).strip() == "" - def test_profile_object(self): - # same, using profile object - p = ImageCms.createProfile("sRGB") - # self.assertEqual(ImageCms.getProfileName(p).strip(), - # 'sRGB built-in - (lcms internal)') - # self.assertEqual(ImageCms.getProfileInfo(p).splitlines(), - # ['sRGB built-in', '', 'WhitePoint : D65 (daylight)', '', '']) - self.assertEqual(ImageCms.getDefaultIntent(p), 0) - self.assertEqual( - ImageCms.isIntentSupported( - p, ImageCms.INTENT_ABSOLUTE_COLORIMETRIC, ImageCms.DIRECTION_INPUT - ), - 1, - ) - def test_extensions(self): - # extensions +def test_model(): + skip_missing() + assert ( + ImageCms.getProfileModel(SRGB).strip() + == "IEC 61966-2-1 Default RGB Colour Space - sRGB" + ) - with Image.open("Tests/images/rgb.jpg") as i: - p = ImageCms.getOpenProfile(BytesIO(i.info["icc_profile"])) - self.assertEqual( - ImageCms.getProfileName(p).strip(), - "IEC 61966-2.1 Default RGB colour space - sRGB", - ) - def test_exceptions(self): - # Test mode mismatch - psRGB = ImageCms.createProfile("sRGB") - pLab = ImageCms.createProfile("LAB") - t = ImageCms.buildTransform(pLab, psRGB, "LAB", "RGB") - self.assertRaises(ValueError, t.apply_in_place, hopper("RGBA")) +def test_description(): + skip_missing() + assert ( + ImageCms.getProfileDescription(SRGB).strip() == "sRGB IEC61966-2-1 black scaled" + ) - # the procedural pyCMS API uses PyCMSError for all sorts of errors - with hopper() as im: - self.assertRaises( - ImageCms.PyCMSError, ImageCms.profileToProfile, im, "foo", "bar" + +def test_intent(): + skip_missing() + assert ImageCms.getDefaultIntent(SRGB) == 0 + support = ImageCms.isIntentSupported( + SRGB, ImageCms.INTENT_ABSOLUTE_COLORIMETRIC, ImageCms.DIRECTION_INPUT + ) + assert support == 1 + + +def test_profile_object(): + # same, using profile object + p = ImageCms.createProfile("sRGB") + # assert ImageCms.getProfileName(p).strip() == "sRGB built-in - (lcms internal)" + # assert ImageCms.getProfileInfo(p).splitlines() == + # ["sRGB built-in", "", "WhitePoint : D65 (daylight)", "", ""] + assert ImageCms.getDefaultIntent(p) == 0 + support = ImageCms.isIntentSupported( + p, ImageCms.INTENT_ABSOLUTE_COLORIMETRIC, ImageCms.DIRECTION_INPUT + ) + assert support == 1 + + +def test_extensions(): + # extensions + + with Image.open("Tests/images/rgb.jpg") as i: + p = ImageCms.getOpenProfile(BytesIO(i.info["icc_profile"])) + assert ( + ImageCms.getProfileName(p).strip() + == "IEC 61966-2.1 Default RGB colour space - sRGB" + ) + + +def test_exceptions(): + # Test mode mismatch + psRGB = ImageCms.createProfile("sRGB") + pLab = ImageCms.createProfile("LAB") + t = ImageCms.buildTransform(pLab, psRGB, "LAB", "RGB") + with pytest.raises(ValueError): + t.apply_in_place(hopper("RGBA")) + + # the procedural pyCMS API uses PyCMSError for all sorts of errors + with hopper() as im: + with pytest.raises(ImageCms.PyCMSError): + ImageCms.profileToProfile(im, "foo", "bar") + with pytest.raises(ImageCms.PyCMSError): + ImageCms.buildTransform("foo", "bar", "RGB", "RGB") + with pytest.raises(ImageCms.PyCMSError): + ImageCms.getProfileName(None) + skip_missing() + with pytest.raises(ImageCms.PyCMSError): + ImageCms.isIntentSupported(SRGB, None, None) + + +def test_display_profile(): + # try fetching the profile for the current display device + ImageCms.get_display_profile() + + +def test_lab_color_profile(): + ImageCms.createProfile("LAB", 5000) + ImageCms.createProfile("LAB", 6500) + + +def test_unsupported_color_space(): + with pytest.raises(ImageCms.PyCMSError): + ImageCms.createProfile("unsupported") + + +def test_invalid_color_temperature(): + with pytest.raises(ImageCms.PyCMSError): + ImageCms.createProfile("LAB", "invalid") + + +def test_simple_lab(): + i = Image.new("RGB", (10, 10), (128, 128, 128)) + + psRGB = ImageCms.createProfile("sRGB") + pLab = ImageCms.createProfile("LAB") + t = ImageCms.buildTransform(psRGB, pLab, "RGB", "LAB") + + i_lab = ImageCms.applyTransform(i, t) + + assert i_lab.mode == "LAB" + + k = i_lab.getpixel((0, 0)) + # not a linear luminance map. so L != 128: + assert k == (137, 128, 128) + + l_data = i_lab.getdata(0) + a_data = i_lab.getdata(1) + b_data = i_lab.getdata(2) + + assert list(l_data) == [137] * 100 + assert list(a_data) == [128] * 100 + assert list(b_data) == [128] * 100 + + +def test_lab_color(): + psRGB = ImageCms.createProfile("sRGB") + pLab = ImageCms.createProfile("LAB") + t = ImageCms.buildTransform(psRGB, pLab, "RGB", "LAB") + + # Need to add a type mapping for some PIL type to TYPE_Lab_8 in findLCMSType, and + # have that mapping work back to a PIL mode (likely RGB). + i = ImageCms.applyTransform(hopper(), t) + assert_image(i, "LAB", (128, 128)) + + # i.save('temp.lab.tif') # visually verified vs PS. + + with Image.open("Tests/images/hopper.Lab.tif") as target: + assert_image_similar(i, target, 3.5) + + +def test_lab_srgb(): + psRGB = ImageCms.createProfile("sRGB") + pLab = ImageCms.createProfile("LAB") + t = ImageCms.buildTransform(pLab, psRGB, "LAB", "RGB") + + with Image.open("Tests/images/hopper.Lab.tif") as img: + img_srgb = ImageCms.applyTransform(img, t) + + # img_srgb.save('temp.srgb.tif') # visually verified vs ps. + + assert_image_similar(hopper(), img_srgb, 30) + assert img_srgb.info["icc_profile"] + + profile = ImageCmsProfile(BytesIO(img_srgb.info["icc_profile"])) + assert "sRGB" in ImageCms.getProfileDescription(profile) + + +def test_lab_roundtrip(): + # check to see if we're at least internally consistent. + psRGB = ImageCms.createProfile("sRGB") + pLab = ImageCms.createProfile("LAB") + t = ImageCms.buildTransform(psRGB, pLab, "RGB", "LAB") + + t2 = ImageCms.buildTransform(pLab, psRGB, "LAB", "RGB") + + i = ImageCms.applyTransform(hopper(), t) + + assert i.info["icc_profile"] == ImageCmsProfile(pLab).tobytes() + + out = ImageCms.applyTransform(i, t2) + + assert_image_similar(hopper(), out, 2) + + +def test_profile_tobytes(): + with Image.open("Tests/images/rgb.jpg") as i: + p = ImageCms.getOpenProfile(BytesIO(i.info["icc_profile"])) + + p2 = ImageCms.getOpenProfile(BytesIO(p.tobytes())) + + # not the same bytes as the original icc_profile, but it does roundtrip + assert p.tobytes() == p2.tobytes() + assert ImageCms.getProfileName(p) == ImageCms.getProfileName(p2) + assert ImageCms.getProfileDescription(p) == ImageCms.getProfileDescription(p2) + + +def test_extended_information(): + skip_missing() + o = ImageCms.getOpenProfile(SRGB) + p = o.profile + + def assert_truncated_tuple_equal(tup1, tup2, digits=10): + # Helper function to reduce precision of tuples of floats + # recursively and then check equality. + power = 10 ** digits + + def truncate_tuple(tuple_or_float): + return tuple( + truncate_tuple(val) + if isinstance(val, tuple) + else int(val * power) / power + for val in tuple_or_float ) - self.assertRaises( - ImageCms.PyCMSError, ImageCms.buildTransform, "foo", "bar", "RGB", "RGB" - ) - self.assertRaises(ImageCms.PyCMSError, ImageCms.getProfileName, None) - self.skip_missing() - self.assertRaises( - ImageCms.PyCMSError, ImageCms.isIntentSupported, SRGB, None, None - ) - def test_display_profile(self): - # try fetching the profile for the current display device - ImageCms.get_display_profile() + assert truncate_tuple(tup1) == truncate_tuple(tup2) - def test_lab_color_profile(self): - ImageCms.createProfile("LAB", 5000) - ImageCms.createProfile("LAB", 6500) - - def test_unsupported_color_space(self): - self.assertRaises(ImageCms.PyCMSError, ImageCms.createProfile, "unsupported") - - def test_invalid_color_temperature(self): - self.assertRaises(ImageCms.PyCMSError, ImageCms.createProfile, "LAB", "invalid") - - def test_simple_lab(self): - i = Image.new("RGB", (10, 10), (128, 128, 128)) - - psRGB = ImageCms.createProfile("sRGB") - pLab = ImageCms.createProfile("LAB") - t = ImageCms.buildTransform(psRGB, pLab, "RGB", "LAB") - - i_lab = ImageCms.applyTransform(i, t) - - self.assertEqual(i_lab.mode, "LAB") - - k = i_lab.getpixel((0, 0)) - # not a linear luminance map. so L != 128: - self.assertEqual(k, (137, 128, 128)) - - l_data = i_lab.getdata(0) - a_data = i_lab.getdata(1) - b_data = i_lab.getdata(2) - - self.assertEqual(list(l_data), [137] * 100) - self.assertEqual(list(a_data), [128] * 100) - self.assertEqual(list(b_data), [128] * 100) - - def test_lab_color(self): - psRGB = ImageCms.createProfile("sRGB") - pLab = ImageCms.createProfile("LAB") - t = ImageCms.buildTransform(psRGB, pLab, "RGB", "LAB") - - # Need to add a type mapping for some PIL type to TYPE_Lab_8 in - # findLCMSType, and have that mapping work back to a PIL mode - # (likely RGB). - i = ImageCms.applyTransform(hopper(), t) - assert_image(i, "LAB", (128, 128)) - - # i.save('temp.lab.tif') # visually verified vs PS. - - with Image.open("Tests/images/hopper.Lab.tif") as target: - assert_image_similar(i, target, 3.5) - - def test_lab_srgb(self): - psRGB = ImageCms.createProfile("sRGB") - pLab = ImageCms.createProfile("LAB") - t = ImageCms.buildTransform(pLab, psRGB, "LAB", "RGB") - - with Image.open("Tests/images/hopper.Lab.tif") as img: - img_srgb = ImageCms.applyTransform(img, t) - - # img_srgb.save('temp.srgb.tif') # visually verified vs ps. - - assert_image_similar(hopper(), img_srgb, 30) - self.assertTrue(img_srgb.info["icc_profile"]) - - profile = ImageCmsProfile(BytesIO(img_srgb.info["icc_profile"])) - self.assertIn("sRGB", ImageCms.getProfileDescription(profile)) - - def test_lab_roundtrip(self): - # check to see if we're at least internally consistent. - psRGB = ImageCms.createProfile("sRGB") - pLab = ImageCms.createProfile("LAB") - t = ImageCms.buildTransform(psRGB, pLab, "RGB", "LAB") - - t2 = ImageCms.buildTransform(pLab, psRGB, "LAB", "RGB") - - i = ImageCms.applyTransform(hopper(), t) - - self.assertEqual(i.info["icc_profile"], ImageCmsProfile(pLab).tobytes()) - - out = ImageCms.applyTransform(i, t2) - - assert_image_similar(hopper(), out, 2) - - def test_profile_tobytes(self): - with Image.open("Tests/images/rgb.jpg") as i: - p = ImageCms.getOpenProfile(BytesIO(i.info["icc_profile"])) - - p2 = ImageCms.getOpenProfile(BytesIO(p.tobytes())) - - # not the same bytes as the original icc_profile, - # but it does roundtrip - self.assertEqual(p.tobytes(), p2.tobytes()) - self.assertEqual(ImageCms.getProfileName(p), ImageCms.getProfileName(p2)) - self.assertEqual( - ImageCms.getProfileDescription(p), ImageCms.getProfileDescription(p2) - ) - - def test_extended_information(self): - self.skip_missing() - o = ImageCms.getOpenProfile(SRGB) - p = o.profile - - def assert_truncated_tuple_equal(tup1, tup2, digits=10): - # Helper function to reduce precision of tuples of floats - # recursively and then check equality. - power = 10 ** digits - - def truncate_tuple(tuple_or_float): - return tuple( - truncate_tuple(val) - if isinstance(val, tuple) - else int(val * power) / power - for val in tuple_or_float - ) - - self.assertEqual(truncate_tuple(tup1), truncate_tuple(tup2)) - - self.assertEqual(p.attributes, 4294967296) - assert_truncated_tuple_equal( - p.blue_colorant, + assert p.attributes == 4294967296 + assert_truncated_tuple_equal( + p.blue_colorant, + ( + (0.14306640625, 0.06060791015625, 0.7140960693359375), + (0.1558847490315394, 0.06603820639433387, 0.06060791015625), + ), + ) + assert_truncated_tuple_equal( + p.blue_primary, + ( + (0.14306641366715667, 0.06060790921083026, 0.7140960805782015), + (0.15588475410450106, 0.06603820408959558, 0.06060790921083026), + ), + ) + assert_truncated_tuple_equal( + p.chromatic_adaptation, + ( ( - (0.14306640625, 0.06060791015625, 0.7140960693359375), - (0.1558847490315394, 0.06603820639433387, 0.06060791015625), + (1.04791259765625, 0.0229339599609375, -0.050201416015625), + (0.02960205078125, 0.9904632568359375, -0.0170745849609375), + (-0.009246826171875, 0.0150604248046875, 0.7517852783203125), ), - ) - assert_truncated_tuple_equal( - p.blue_primary, ( - (0.14306641366715667, 0.06060790921083026, 0.7140960805782015), - (0.15588475410450106, 0.06603820408959558, 0.06060790921083026), + (1.0267159024652783, 0.022470062342089134, 0.0229339599609375), + (0.02951378324103937, 0.9875098886387147, 0.9904632568359375), + (-0.012205438066465256, 0.01987915407854985, 0.0150604248046875), ), - ) - assert_truncated_tuple_equal( - p.chromatic_adaptation, - ( - ( - (1.04791259765625, 0.0229339599609375, -0.050201416015625), - (0.02960205078125, 0.9904632568359375, -0.0170745849609375), - (-0.009246826171875, 0.0150604248046875, 0.7517852783203125), - ), - ( - (1.0267159024652783, 0.022470062342089134, 0.0229339599609375), - (0.02951378324103937, 0.9875098886387147, 0.9904632568359375), - (-0.012205438066465256, 0.01987915407854985, 0.0150604248046875), - ), - ), - ) - self.assertIsNone(p.chromaticity) - self.assertEqual( - p.clut, - { - 0: (False, False, True), - 1: (False, False, True), - 2: (False, False, True), - 3: (False, False, True), - }, - ) + ), + ) + assert p.chromaticity is None + assert p.clut == { + 0: (False, False, True), + 1: (False, False, True), + 2: (False, False, True), + 3: (False, False, True), + } - self.assertIsNone(p.colorant_table) - self.assertIsNone(p.colorant_table_out) - self.assertIsNone(p.colorimetric_intent) - self.assertEqual(p.connection_space, "XYZ ") - self.assertEqual(p.copyright, "Copyright International Color Consortium, 2009") - self.assertEqual(p.creation_date, datetime.datetime(2009, 2, 27, 21, 36, 31)) - self.assertEqual(p.device_class, "mntr") - assert_truncated_tuple_equal( - p.green_colorant, - ( - (0.3851470947265625, 0.7168731689453125, 0.097076416015625), - (0.32119769927720654, 0.5978443449048152, 0.7168731689453125), - ), - ) - assert_truncated_tuple_equal( - p.green_primary, - ( - (0.3851470888162112, 0.7168731974161346, 0.09707641738998518), - (0.32119768793686687, 0.5978443567149709, 0.7168731974161346), - ), - ) - self.assertEqual(p.header_flags, 0) - self.assertEqual(p.header_manufacturer, "\x00\x00\x00\x00") - self.assertEqual(p.header_model, "\x00\x00\x00\x00") - self.assertEqual( - p.icc_measurement_condition, - { - "backing": (0.0, 0.0, 0.0), - "flare": 0.0, - "geo": "unknown", - "observer": 1, - "illuminant_type": "D65", - }, - ) - self.assertEqual(p.icc_version, 33554432) - self.assertIsNone(p.icc_viewing_condition) - self.assertEqual( - p.intent_supported, - { - 0: (True, True, True), - 1: (True, True, True), - 2: (True, True, True), - 3: (True, True, True), - }, - ) - self.assertTrue(p.is_matrix_shaper) - self.assertEqual(p.luminance, ((0.0, 80.0, 0.0), (0.0, 1.0, 80.0))) - self.assertIsNone(p.manufacturer) - assert_truncated_tuple_equal( - p.media_black_point, - ( - (0.012054443359375, 0.0124969482421875, 0.01031494140625), - (0.34573304157549234, 0.35842450765864337, 0.0124969482421875), - ), - ) - assert_truncated_tuple_equal( - p.media_white_point, - ( - (0.964202880859375, 1.0, 0.8249053955078125), - (0.3457029219802284, 0.3585375327567059, 1.0), - ), - ) - assert_truncated_tuple_equal( - (p.media_white_point_temperature,), (5000.722328847392,) - ) - self.assertEqual(p.model, "IEC 61966-2-1 Default RGB Colour Space - sRGB") + assert p.colorant_table is None + assert p.colorant_table_out is None + assert p.colorimetric_intent is None + assert p.connection_space == "XYZ " + assert p.copyright == "Copyright International Color Consortium, 2009" + assert p.creation_date == datetime.datetime(2009, 2, 27, 21, 36, 31) + assert p.device_class == "mntr" + assert_truncated_tuple_equal( + p.green_colorant, + ( + (0.3851470947265625, 0.7168731689453125, 0.097076416015625), + (0.32119769927720654, 0.5978443449048152, 0.7168731689453125), + ), + ) + assert_truncated_tuple_equal( + p.green_primary, + ( + (0.3851470888162112, 0.7168731974161346, 0.09707641738998518), + (0.32119768793686687, 0.5978443567149709, 0.7168731974161346), + ), + ) + assert p.header_flags == 0 + assert p.header_manufacturer == "\x00\x00\x00\x00" + assert p.header_model == "\x00\x00\x00\x00" + assert p.icc_measurement_condition == { + "backing": (0.0, 0.0, 0.0), + "flare": 0.0, + "geo": "unknown", + "observer": 1, + "illuminant_type": "D65", + } + assert p.icc_version == 33554432 + assert p.icc_viewing_condition is None + assert p.intent_supported == { + 0: (True, True, True), + 1: (True, True, True), + 2: (True, True, True), + 3: (True, True, True), + } + assert p.is_matrix_shaper + assert p.luminance == ((0.0, 80.0, 0.0), (0.0, 1.0, 80.0)) + assert p.manufacturer is None + assert_truncated_tuple_equal( + p.media_black_point, + ( + (0.012054443359375, 0.0124969482421875, 0.01031494140625), + (0.34573304157549234, 0.35842450765864337, 0.0124969482421875), + ), + ) + assert_truncated_tuple_equal( + p.media_white_point, + ( + (0.964202880859375, 1.0, 0.8249053955078125), + (0.3457029219802284, 0.3585375327567059, 1.0), + ), + ) + assert_truncated_tuple_equal( + (p.media_white_point_temperature,), (5000.722328847392,) + ) + assert p.model == "IEC 61966-2-1 Default RGB Colour Space - sRGB" - self.assertIsNone(p.perceptual_rendering_intent_gamut) + assert p.perceptual_rendering_intent_gamut is None - self.assertEqual(p.profile_description, "sRGB IEC61966-2-1 black scaled") - self.assertEqual(p.profile_id, b")\xf8=\xde\xaf\xf2U\xaexB\xfa\xe4\xca\x839\r") - assert_truncated_tuple_equal( - p.red_colorant, - ( - (0.436065673828125, 0.2224884033203125, 0.013916015625), - (0.6484536316398539, 0.3308524880306778, 0.2224884033203125), - ), - ) - assert_truncated_tuple_equal( - p.red_primary, - ( - (0.43606566581047446, 0.22248840582960838, 0.013916015621759925), - (0.6484536250319214, 0.3308524944738204, 0.22248840582960838), - ), - ) - self.assertEqual(p.rendering_intent, 0) - self.assertIsNone(p.saturation_rendering_intent_gamut) - self.assertIsNone(p.screening_description) - self.assertIsNone(p.target) - self.assertEqual(p.technology, "CRT ") - self.assertEqual(p.version, 2.0) - self.assertEqual( - p.viewing_condition, "Reference Viewing Condition in IEC 61966-2-1" - ) - self.assertEqual(p.xcolor_space, "RGB ") + assert p.profile_description == "sRGB IEC61966-2-1 black scaled" + assert p.profile_id == b")\xf8=\xde\xaf\xf2U\xaexB\xfa\xe4\xca\x839\r" + assert_truncated_tuple_equal( + p.red_colorant, + ( + (0.436065673828125, 0.2224884033203125, 0.013916015625), + (0.6484536316398539, 0.3308524880306778, 0.2224884033203125), + ), + ) + assert_truncated_tuple_equal( + p.red_primary, + ( + (0.43606566581047446, 0.22248840582960838, 0.013916015621759925), + (0.6484536250319214, 0.3308524944738204, 0.22248840582960838), + ), + ) + assert p.rendering_intent == 0 + assert p.saturation_rendering_intent_gamut is None + assert p.screening_description is None + assert p.target is None + assert p.technology == "CRT " + assert p.version == 2.0 + assert p.viewing_condition == "Reference Viewing Condition in IEC 61966-2-1" + assert p.xcolor_space == "RGB " - def test_deprecations(self): - self.skip_missing() - o = ImageCms.getOpenProfile(SRGB) - p = o.profile - def helper_deprecated(attr, expected): - result = pytest.warns(DeprecationWarning, getattr, p, attr) - self.assertEqual(result, expected) +def test_non_ascii_path(tmp_path): + skip_missing() + tempfile = str(tmp_path / ("temp_" + chr(128) + ".icc")) + try: + shutil.copy(SRGB, tempfile) + except UnicodeEncodeError: + pytest.skip("Non-ASCII path could not be created") - # p.color_space - helper_deprecated("color_space", "RGB") + o = ImageCms.getOpenProfile(tempfile) + p = o.profile + assert p.model == "IEC 61966-2-1 Default RGB Colour Space - sRGB" - # p.pcs - helper_deprecated("pcs", "XYZ") - # p.product_copyright - helper_deprecated( - "product_copyright", "Copyright International Color Consortium, 2009" - ) +def test_profile_typesafety(): + """Profile init type safety - # p.product_desc - helper_deprecated("product_desc", "sRGB IEC61966-2-1 black scaled") + prepatch, these would segfault, postpatch they should emit a typeerror + """ - # p.product_description - helper_deprecated("product_description", "sRGB IEC61966-2-1 black scaled") + with pytest.raises(TypeError): + ImageCms.ImageCmsProfile(0).tobytes() + with pytest.raises(TypeError): + ImageCms.ImageCmsProfile(1).tobytes() - # p.product_manufacturer - helper_deprecated("product_manufacturer", "") - # p.product_model - helper_deprecated( - "product_model", "IEC 61966-2-1 Default RGB Colour Space - sRGB" - ) - - def test_profile_typesafety(self): - """ Profile init type safety - - prepatch, these would segfault, postpatch they should emit a typeerror - """ - - with self.assertRaises(TypeError): - ImageCms.ImageCmsProfile(0).tobytes() - with self.assertRaises(TypeError): - ImageCms.ImageCmsProfile(1).tobytes() - - def assert_aux_channel_preserved(self, mode, transform_in_place, preserved_channel): - def create_test_image(): - # set up test image with something interesting in the tested aux channel. - # fmt: off - nine_grid_deltas = [ # noqa: E131 - (-1, -1), (-1, 0), (-1, 1), - (0, -1), (0, 0), (0, 1), - (1, -1), (1, 0), (1, 1), - ] - # fmt: on - chans = [] - bands = ImageMode.getmode(mode).bands - for band_ndx in range(len(bands)): - channel_type = "L" # 8-bit unorm - channel_pattern = hopper(channel_type) - - # paste pattern with varying offsets to avoid correlation - # potentially hiding some bugs (like channels getting mixed). - paste_offset = ( - int(band_ndx / len(bands) * channel_pattern.size[0]), - int(band_ndx / (len(bands) * 2) * channel_pattern.size[1]), - ) - channel_data = Image.new(channel_type, channel_pattern.size) - for delta in nine_grid_deltas: - channel_data.paste( - channel_pattern, - tuple( - paste_offset[c] + delta[c] * channel_pattern.size[c] - for c in range(2) - ), - ) - chans.append(channel_data) - return Image.merge(mode, chans) - - source_image = create_test_image() - source_image_aux = source_image.getchannel(preserved_channel) - - # create some transform, it doesn't matter which one - source_profile = ImageCms.createProfile("sRGB") - destination_profile = ImageCms.createProfile("sRGB") - t = ImageCms.buildTransform( - source_profile, destination_profile, inMode=mode, outMode=mode - ) - - # apply transform - if transform_in_place: - ImageCms.applyTransform(source_image, t, inPlace=True) - result_image = source_image - else: - result_image = ImageCms.applyTransform(source_image, t, inPlace=False) - result_image_aux = result_image.getchannel(preserved_channel) - - assert_image_equal(source_image_aux, result_image_aux) - - def test_preserve_auxiliary_channels_rgba(self): - self.assert_aux_channel_preserved( - mode="RGBA", transform_in_place=False, preserved_channel="A" - ) - - def test_preserve_auxiliary_channels_rgba_in_place(self): - self.assert_aux_channel_preserved( - mode="RGBA", transform_in_place=True, preserved_channel="A" - ) - - def test_preserve_auxiliary_channels_rgbx(self): - self.assert_aux_channel_preserved( - mode="RGBX", transform_in_place=False, preserved_channel="X" - ) - - def test_preserve_auxiliary_channels_rgbx_in_place(self): - self.assert_aux_channel_preserved( - mode="RGBX", transform_in_place=True, preserved_channel="X" - ) - - def test_auxiliary_channels_isolated(self): - # test data in aux channels does not affect non-aux channels - aux_channel_formats = [ - # format, profile, color-only format, source test image - ("RGBA", "sRGB", "RGB", hopper("RGBA")), - ("RGBX", "sRGB", "RGB", hopper("RGBX")), - ("LAB", "LAB", "LAB", Image.open("Tests/images/hopper.Lab.tif")), +def assert_aux_channel_preserved(mode, transform_in_place, preserved_channel): + def create_test_image(): + # set up test image with something interesting in the tested aux channel. + # fmt: off + nine_grid_deltas = [ + (-1, -1), (-1, 0), (-1, 1), + (0, -1), (0, 0), (0, 1), + (1, -1), (1, 0), (1, 1), ] - for src_format in aux_channel_formats: - for dst_format in aux_channel_formats: - for transform_in_place in [True, False]: - # inplace only if format doesn't change - if transform_in_place and src_format[0] != dst_format[0]: - continue + # fmt: on + chans = [] + bands = ImageMode.getmode(mode).bands + for band_ndx in range(len(bands)): + channel_type = "L" # 8-bit unorm + channel_pattern = hopper(channel_type) - # convert with and without AUX data, test colors are equal - source_profile = ImageCms.createProfile(src_format[1]) - destination_profile = ImageCms.createProfile(dst_format[1]) - source_image = src_format[3] - test_transform = ImageCms.buildTransform( - source_profile, - destination_profile, - inMode=src_format[0], - outMode=dst_format[0], + # paste pattern with varying offsets to avoid correlation + # potentially hiding some bugs (like channels getting mixed). + paste_offset = ( + int(band_ndx / len(bands) * channel_pattern.size[0]), + int(band_ndx / (len(bands) * 2) * channel_pattern.size[1]), + ) + channel_data = Image.new(channel_type, channel_pattern.size) + for delta in nine_grid_deltas: + channel_data.paste( + channel_pattern, + tuple( + paste_offset[c] + delta[c] * channel_pattern.size[c] + for c in range(2) + ), + ) + chans.append(channel_data) + return Image.merge(mode, chans) + + source_image = create_test_image() + source_image_aux = source_image.getchannel(preserved_channel) + + # create some transform, it doesn't matter which one + source_profile = ImageCms.createProfile("sRGB") + destination_profile = ImageCms.createProfile("sRGB") + t = ImageCms.buildTransform( + source_profile, destination_profile, inMode=mode, outMode=mode + ) + + # apply transform + if transform_in_place: + ImageCms.applyTransform(source_image, t, inPlace=True) + result_image = source_image + else: + result_image = ImageCms.applyTransform(source_image, t, inPlace=False) + result_image_aux = result_image.getchannel(preserved_channel) + + assert_image_equal(source_image_aux, result_image_aux) + + +def test_preserve_auxiliary_channels_rgba(): + assert_aux_channel_preserved( + mode="RGBA", transform_in_place=False, preserved_channel="A" + ) + + +def test_preserve_auxiliary_channels_rgba_in_place(): + assert_aux_channel_preserved( + mode="RGBA", transform_in_place=True, preserved_channel="A" + ) + + +def test_preserve_auxiliary_channels_rgbx(): + assert_aux_channel_preserved( + mode="RGBX", transform_in_place=False, preserved_channel="X" + ) + + +def test_preserve_auxiliary_channels_rgbx_in_place(): + assert_aux_channel_preserved( + mode="RGBX", transform_in_place=True, preserved_channel="X" + ) + + +def test_auxiliary_channels_isolated(): + # test data in aux channels does not affect non-aux channels + aux_channel_formats = [ + # format, profile, color-only format, source test image + ("RGBA", "sRGB", "RGB", hopper("RGBA")), + ("RGBX", "sRGB", "RGB", hopper("RGBX")), + ("LAB", "LAB", "LAB", Image.open("Tests/images/hopper.Lab.tif")), + ] + for src_format in aux_channel_formats: + for dst_format in aux_channel_formats: + for transform_in_place in [True, False]: + # inplace only if format doesn't change + if transform_in_place and src_format[0] != dst_format[0]: + continue + + # convert with and without AUX data, test colors are equal + source_profile = ImageCms.createProfile(src_format[1]) + destination_profile = ImageCms.createProfile(dst_format[1]) + source_image = src_format[3] + test_transform = ImageCms.buildTransform( + source_profile, + destination_profile, + inMode=src_format[0], + outMode=dst_format[0], + ) + + # test conversion from aux-ful source + if transform_in_place: + test_image = source_image.copy() + ImageCms.applyTransform(test_image, test_transform, inPlace=True) + else: + test_image = ImageCms.applyTransform( + source_image, test_transform, inPlace=False ) - # test conversion from aux-ful source - if transform_in_place: - test_image = source_image.copy() - ImageCms.applyTransform( - test_image, test_transform, inPlace=True - ) - else: - test_image = ImageCms.applyTransform( - source_image, test_transform, inPlace=False - ) + # reference conversion from aux-less source + reference_transform = ImageCms.buildTransform( + source_profile, + destination_profile, + inMode=src_format[2], + outMode=dst_format[2], + ) + reference_image = ImageCms.applyTransform( + source_image.convert(src_format[2]), reference_transform + ) - # reference conversion from aux-less source - reference_transform = ImageCms.buildTransform( - source_profile, - destination_profile, - inMode=src_format[2], - outMode=dst_format[2], - ) - reference_image = ImageCms.applyTransform( - source_image.convert(src_format[2]), reference_transform - ) - - assert_image_equal( - test_image.convert(dst_format[2]), reference_image - ) + assert_image_equal(test_image.convert(dst_format[2]), reference_image) diff --git a/Tests/test_imagecolor.py b/Tests/test_imagecolor.py index d2fd07c81..b5d693796 100644 --- a/Tests/test_imagecolor.py +++ b/Tests/test_imagecolor.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image, ImageColor diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index 7dc51562c..a87c1f2be 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -1,9 +1,15 @@ import os.path -import unittest -from PIL import Image, ImageColor, ImageDraw, ImageFont, features +import pytest -from .helper import PillowTestCase, assert_image_equal, assert_image_similar, hopper +from PIL import Image, ImageColor, ImageDraw, ImageFont + +from .helper import ( + assert_image_equal, + assert_image_similar_tofile, + hopper, + skip_unless_feature, +) BLACK = (0, 0, 0) WHITE = (255, 255, 255) @@ -30,756 +36,942 @@ POINTS2 = [10, 10, 20, 40, 30, 30] KITE_POINTS = [(10, 50), (70, 10), (90, 50), (70, 90), (10, 50)] -HAS_FREETYPE = features.check("freetype2") + +def test_sanity(): + im = hopper("RGB").copy() + + draw = ImageDraw.ImageDraw(im) + draw = ImageDraw.Draw(im) + + draw.ellipse(list(range(4))) + draw.line(list(range(10))) + draw.polygon(list(range(100))) + draw.rectangle(list(range(4))) -class TestImageDraw(PillowTestCase): - def test_sanity(self): - im = hopper("RGB").copy() +def test_valueerror(): + with Image.open("Tests/images/chi.gif") as im: - draw = ImageDraw.ImageDraw(im) draw = ImageDraw.Draw(im) + draw.line((0, 0), fill=(0, 0, 0)) - draw.ellipse(list(range(4))) - draw.line(list(range(10))) - draw.polygon(list(range(100))) - draw.rectangle(list(range(4))) - def test_valueerror(self): - with Image.open("Tests/images/chi.gif") as im: +def test_mode_mismatch(): + im = hopper("RGB").copy() - draw = ImageDraw.Draw(im) - draw.line((0, 0), fill=(0, 0, 0)) + with pytest.raises(ValueError): + ImageDraw.ImageDraw(im, mode="L") - def test_mode_mismatch(self): - im = hopper("RGB").copy() - self.assertRaises(ValueError, ImageDraw.ImageDraw, im, mode="L") +def helper_arc(bbox, start, end): + # Arrange + im = Image.new("RGB", (W, H)) + draw = ImageDraw.Draw(im) - def helper_arc(self, bbox, start, end): - # Arrange - im = Image.new("RGB", (W, H)) - draw = ImageDraw.Draw(im) + # Act + draw.arc(bbox, start, end) + + # Assert + assert_image_similar_tofile(im, "Tests/images/imagedraw_arc.png", 1) + + +def test_arc1(): + helper_arc(BBOX1, 0, 180) + helper_arc(BBOX1, 0.5, 180.4) + + +def test_arc2(): + helper_arc(BBOX2, 0, 180) + helper_arc(BBOX2, 0.5, 180.4) + + +def test_arc_end_le_start(): + # Arrange + im = Image.new("RGB", (W, H)) + draw = ImageDraw.Draw(im) + start = 270.5 + end = 0 + + # Act + draw.arc(BBOX1, start=start, end=end) + + # Assert + assert_image_equal(im, Image.open("Tests/images/imagedraw_arc_end_le_start.png")) + + +def test_arc_no_loops(): + # No need to go in loops + # Arrange + im = Image.new("RGB", (W, H)) + draw = ImageDraw.Draw(im) + start = 5 + end = 370 + + # Act + draw.arc(BBOX1, start=start, end=end) + + # Assert + assert_image_similar_tofile(im, "Tests/images/imagedraw_arc_no_loops.png", 1) + + +def test_arc_width(): + # Arrange + im = Image.new("RGB", (W, H)) + draw = ImageDraw.Draw(im) + + # Act + draw.arc(BBOX1, 10, 260, width=5) + + # Assert + assert_image_similar_tofile(im, "Tests/images/imagedraw_arc_width.png", 1) + + +def test_arc_width_pieslice_large(): + # Tests an arc with a large enough width that it is a pieslice + # Arrange + im = Image.new("RGB", (W, H)) + draw = ImageDraw.Draw(im) + + # Act + draw.arc(BBOX1, 10, 260, fill="yellow", width=100) + + # Assert + assert_image_similar_tofile(im, "Tests/images/imagedraw_arc_width_pieslice.png", 1) + + +def test_arc_width_fill(): + # Arrange + im = Image.new("RGB", (W, H)) + draw = ImageDraw.Draw(im) + + # Act + draw.arc(BBOX1, 10, 260, fill="yellow", width=5) + + # Assert + assert_image_similar_tofile(im, "Tests/images/imagedraw_arc_width_fill.png", 1) + + +def test_arc_width_non_whole_angle(): + # Arrange + im = Image.new("RGB", (W, H)) + draw = ImageDraw.Draw(im) + expected = "Tests/images/imagedraw_arc_width_non_whole_angle.png" + + # Act + draw.arc(BBOX1, 10, 259.5, width=5) + + # Assert + assert_image_similar_tofile(im, expected, 1) + + +def test_arc_high(): + # Arrange + im = Image.new("RGB", (200, 200)) + draw = ImageDraw.Draw(im) + + # Act + draw.arc([10, 10, 89, 189], 20, 330, width=20, fill="white") + draw.arc([110, 10, 189, 189], 20, 150, width=20, fill="white") + + # Assert + assert_image_equal(im, Image.open("Tests/images/imagedraw_arc_high.png")) + + +def test_bitmap(): + # Arrange + im = Image.new("RGB", (W, H)) + draw = ImageDraw.Draw(im) + with Image.open("Tests/images/pil123rgba.png") as small: + small = small.resize((50, 50), Image.NEAREST) # Act - draw.arc(bbox, start, end) + draw.bitmap((10, 10), small) - # Assert - assert_image_similar(im, Image.open("Tests/images/imagedraw_arc.png"), 1) + # Assert + assert_image_equal(im, Image.open("Tests/images/imagedraw_bitmap.png")) - def test_arc1(self): - self.helper_arc(BBOX1, 0, 180) - self.helper_arc(BBOX1, 0.5, 180.4) - def test_arc2(self): - self.helper_arc(BBOX2, 0, 180) - self.helper_arc(BBOX2, 0.5, 180.4) +def helper_chord(mode, bbox, start, end): + # Arrange + im = Image.new(mode, (W, H)) + draw = ImageDraw.Draw(im) + expected = f"Tests/images/imagedraw_chord_{mode}.png" - def test_arc_end_le_start(self): - # Arrange - im = Image.new("RGB", (W, H)) + # Act + draw.chord(bbox, start, end, fill="red", outline="yellow") + + # Assert + assert_image_similar_tofile(im, expected, 1) + + +def test_chord1(): + for mode in ["RGB", "L"]: + helper_chord(mode, BBOX1, 0, 180) + + +def test_chord2(): + for mode in ["RGB", "L"]: + helper_chord(mode, BBOX2, 0, 180) + + +def test_chord_width(): + # Arrange + im = Image.new("RGB", (W, H)) + draw = ImageDraw.Draw(im) + + # Act + draw.chord(BBOX1, 10, 260, outline="yellow", width=5) + + # Assert + assert_image_similar_tofile(im, "Tests/images/imagedraw_chord_width.png", 1) + + +def test_chord_width_fill(): + # Arrange + im = Image.new("RGB", (W, H)) + draw = ImageDraw.Draw(im) + + # Act + draw.chord(BBOX1, 10, 260, fill="red", outline="yellow", width=5) + + # Assert + assert_image_similar_tofile(im, "Tests/images/imagedraw_chord_width_fill.png", 1) + + +def test_chord_zero_width(): + # Arrange + im = Image.new("RGB", (W, H)) + draw = ImageDraw.Draw(im) + + # Act + draw.chord(BBOX1, 10, 260, fill="red", outline="yellow", width=0) + + # Assert + with Image.open("Tests/images/imagedraw_chord_zero_width.png") as expected: + assert_image_equal(im, expected) + + +def test_chord_too_fat(): + # Arrange + im = Image.new("RGB", (100, 100)) + draw = ImageDraw.Draw(im) + + # Act + draw.chord([-150, -150, 99, 99], 15, 60, width=10, fill="white", outline="red") + + # Assert + assert_image_equal(im, Image.open("Tests/images/imagedraw_chord_too_fat.png")) + + +def helper_ellipse(mode, bbox): + # Arrange + im = Image.new(mode, (W, H)) + draw = ImageDraw.Draw(im) + expected = f"Tests/images/imagedraw_ellipse_{mode}.png" + + # Act + draw.ellipse(bbox, fill="green", outline="blue") + + # Assert + assert_image_similar_tofile(im, expected, 1) + + +def test_ellipse1(): + for mode in ["RGB", "L"]: + helper_ellipse(mode, BBOX1) + + +def test_ellipse2(): + for mode in ["RGB", "L"]: + helper_ellipse(mode, BBOX2) + + +def test_ellipse_translucent(): + # Arrange + im = Image.new("RGB", (W, H)) + draw = ImageDraw.Draw(im, "RGBA") + + # Act + draw.ellipse(BBOX1, fill=(0, 255, 0, 127)) + + # Assert + expected = "Tests/images/imagedraw_ellipse_translucent.png" + assert_image_similar_tofile(im, expected, 1) + + +def test_ellipse_edge(): + # Arrange + im = Image.new("RGB", (W, H)) + draw = ImageDraw.Draw(im) + + # Act + draw.ellipse(((0, 0), (W - 1, H - 1)), fill="white") + + # Assert + assert_image_similar_tofile(im, "Tests/images/imagedraw_ellipse_edge.png", 1) + + +def test_ellipse_symmetric(): + for width, bbox in ( + (100, (24, 24, 75, 75)), + (101, (25, 25, 75, 75)), + ): + im = Image.new("RGB", (width, 100)) draw = ImageDraw.Draw(im) - start = 270.5 - end = 0 - - # Act - draw.arc(BBOX1, start=start, end=end) - - # Assert - assert_image_equal( - im, Image.open("Tests/images/imagedraw_arc_end_le_start.png") - ) - - def test_arc_no_loops(self): - # No need to go in loops - # Arrange - im = Image.new("RGB", (W, H)) - draw = ImageDraw.Draw(im) - start = 5 - end = 370 - - # Act - draw.arc(BBOX1, start=start, end=end) - - # Assert - assert_image_similar( - im, Image.open("Tests/images/imagedraw_arc_no_loops.png"), 1 - ) - - def test_arc_width(self): - # Arrange - im = Image.new("RGB", (W, H)) - draw = ImageDraw.Draw(im) - expected = "Tests/images/imagedraw_arc_width.png" - - # Act - draw.arc(BBOX1, 10, 260, width=5) - - # Assert - assert_image_similar(im, Image.open(expected), 1) - - def test_arc_width_pieslice_large(self): - # Tests an arc with a large enough width that it is a pieslice - # Arrange - im = Image.new("RGB", (W, H)) - draw = ImageDraw.Draw(im) - expected = "Tests/images/imagedraw_arc_width_pieslice.png" - - # Act - draw.arc(BBOX1, 10, 260, fill="yellow", width=100) - - # Assert - assert_image_similar(im, Image.open(expected), 1) - - def test_arc_width_fill(self): - # Arrange - im = Image.new("RGB", (W, H)) - draw = ImageDraw.Draw(im) - expected = "Tests/images/imagedraw_arc_width_fill.png" - - # Act - draw.arc(BBOX1, 10, 260, fill="yellow", width=5) - - # Assert - assert_image_similar(im, Image.open(expected), 1) - - def test_arc_width_non_whole_angle(self): - # Arrange - im = Image.new("RGB", (W, H)) - draw = ImageDraw.Draw(im) - expected = "Tests/images/imagedraw_arc_width_non_whole_angle.png" - - # Act - draw.arc(BBOX1, 10, 259.5, width=5) - - # Assert - assert_image_similar(im, Image.open(expected), 1) - - def test_bitmap(self): - # Arrange - im = Image.new("RGB", (W, H)) - draw = ImageDraw.Draw(im) - with Image.open("Tests/images/pil123rgba.png") as small: - small = small.resize((50, 50), Image.NEAREST) - - # Act - draw.bitmap((10, 10), small) - - # Assert - assert_image_equal(im, Image.open("Tests/images/imagedraw_bitmap.png")) - - def helper_chord(self, mode, bbox, start, end): - # Arrange - im = Image.new(mode, (W, H)) - draw = ImageDraw.Draw(im) - expected = "Tests/images/imagedraw_chord_{}.png".format(mode) - - # Act - draw.chord(bbox, start, end, fill="red", outline="yellow") - - # Assert - assert_image_similar(im, Image.open(expected), 1) - - def test_chord1(self): - for mode in ["RGB", "L"]: - self.helper_chord(mode, BBOX1, 0, 180) - self.helper_chord(mode, BBOX1, 0.5, 180.4) - - def test_chord2(self): - for mode in ["RGB", "L"]: - self.helper_chord(mode, BBOX2, 0, 180) - self.helper_chord(mode, BBOX2, 0.5, 180.4) - - def test_chord_width(self): - # Arrange - im = Image.new("RGB", (W, H)) - draw = ImageDraw.Draw(im) - expected = "Tests/images/imagedraw_chord_width.png" - - # Act - draw.chord(BBOX1, 10, 260, outline="yellow", width=5) - - # Assert - assert_image_similar(im, Image.open(expected), 1) - - def test_chord_width_fill(self): - # Arrange - im = Image.new("RGB", (W, H)) - draw = ImageDraw.Draw(im) - expected = "Tests/images/imagedraw_chord_width_fill.png" - - # Act - draw.chord(BBOX1, 10, 260, fill="red", outline="yellow", width=5) - - # Assert - assert_image_similar(im, Image.open(expected), 1) - - def helper_ellipse(self, mode, bbox): - # Arrange - im = Image.new(mode, (W, H)) - draw = ImageDraw.Draw(im) - expected = "Tests/images/imagedraw_ellipse_{}.png".format(mode) - - # Act draw.ellipse(bbox, fill="green", outline="blue") + assert_image_equal(im, im.transpose(Image.FLIP_LEFT_RIGHT)) - # Assert - assert_image_similar(im, Image.open(expected), 1) - def test_ellipse1(self): - for mode in ["RGB", "L"]: - self.helper_ellipse(mode, BBOX1) +def test_ellipse_width(): + # Arrange + im = Image.new("RGB", (W, H)) + draw = ImageDraw.Draw(im) - def test_ellipse2(self): - for mode in ["RGB", "L"]: - self.helper_ellipse(mode, BBOX2) + # Act + draw.ellipse(BBOX1, outline="blue", width=5) - def test_ellipse_edge(self): + # Assert + assert_image_similar_tofile(im, "Tests/images/imagedraw_ellipse_width.png", 1) + + +def test_ellipse_width_large(): + # Arrange + im = Image.new("RGB", (500, 500)) + draw = ImageDraw.Draw(im) + + # Act + draw.ellipse((25, 25, 475, 475), outline="blue", width=75) + + # Assert + assert_image_similar_tofile(im, "Tests/images/imagedraw_ellipse_width_large.png", 1) + + +def test_ellipse_width_fill(): + # Arrange + im = Image.new("RGB", (W, H)) + draw = ImageDraw.Draw(im) + + # Act + draw.ellipse(BBOX1, fill="green", outline="blue", width=5) + + # Assert + assert_image_similar_tofile(im, "Tests/images/imagedraw_ellipse_width_fill.png", 1) + + +def test_ellipse_zero_width(): + # Arrange + im = Image.new("RGB", (W, H)) + draw = ImageDraw.Draw(im) + + # Act + draw.ellipse(BBOX1, fill="green", outline="blue", width=0) + + # Assert + with Image.open("Tests/images/imagedraw_ellipse_zero_width.png") as expected: + assert_image_equal(im, expected) + + +def ellipse_various_sizes_helper(filled): + ellipse_sizes = range(32) + image_size = sum(ellipse_sizes) + len(ellipse_sizes) + 1 + im = Image.new("RGB", (image_size, image_size)) + draw = ImageDraw.Draw(im) + + x = 1 + for w in ellipse_sizes: + y = 1 + for h in ellipse_sizes: + border = [x, y, x + w - 1, y + h - 1] + if filled: + draw.ellipse(border, fill="white") + else: + draw.ellipse(border, outline="white") + y += h + 1 + x += w + 1 + + return im + + +def test_ellipse_various_sizes(): + im = ellipse_various_sizes_helper(False) + + with Image.open("Tests/images/imagedraw_ellipse_various_sizes.png") as expected: + assert_image_equal(im, expected) + + +def test_ellipse_various_sizes_filled(): + im = ellipse_various_sizes_helper(True) + + with Image.open( + "Tests/images/imagedraw_ellipse_various_sizes_filled.png" + ) as expected: + assert_image_equal(im, expected) + + +def helper_line(points): + # Arrange + im = Image.new("RGB", (W, H)) + draw = ImageDraw.Draw(im) + + # Act + draw.line(points, fill="yellow", width=2) + + # Assert + assert_image_equal(im, Image.open("Tests/images/imagedraw_line.png")) + + +def test_line1(): + helper_line(POINTS1) + + +def test_line2(): + helper_line(POINTS2) + + +def test_shape1(): + # Arrange + im = Image.new("RGB", (100, 100), "white") + draw = ImageDraw.Draw(im) + x0, y0 = 5, 5 + x1, y1 = 5, 50 + x2, y2 = 95, 50 + x3, y3 = 95, 5 + + # Act + s = ImageDraw.Outline() + s.move(x0, y0) + s.curve(x1, y1, x2, y2, x3, y3) + s.line(x0, y0) + + draw.shape(s, fill=1) + + # Assert + assert_image_equal(im, Image.open("Tests/images/imagedraw_shape1.png")) + + +def test_shape2(): + # Arrange + im = Image.new("RGB", (100, 100), "white") + draw = ImageDraw.Draw(im) + x0, y0 = 95, 95 + x1, y1 = 95, 50 + x2, y2 = 5, 50 + x3, y3 = 5, 95 + + # Act + s = ImageDraw.Outline() + s.move(x0, y0) + s.curve(x1, y1, x2, y2, x3, y3) + s.line(x0, y0) + + draw.shape(s, outline="blue") + + # Assert + assert_image_equal(im, Image.open("Tests/images/imagedraw_shape2.png")) + + +def helper_pieslice(bbox, start, end): + # Arrange + im = Image.new("RGB", (W, H)) + draw = ImageDraw.Draw(im) + + # Act + draw.pieslice(bbox, start, end, fill="white", outline="blue") + + # Assert + assert_image_similar_tofile(im, "Tests/images/imagedraw_pieslice.png", 1) + + +def test_pieslice1(): + helper_pieslice(BBOX1, -92, 46) + helper_pieslice(BBOX1, -92.2, 46.2) + + +def test_pieslice2(): + helper_pieslice(BBOX2, -92, 46) + helper_pieslice(BBOX2, -92.2, 46.2) + + +def test_pieslice_width(): + # Arrange + im = Image.new("RGB", (W, H)) + draw = ImageDraw.Draw(im) + + # Act + draw.pieslice(BBOX1, 10, 260, outline="blue", width=5) + + # Assert + assert_image_similar_tofile(im, "Tests/images/imagedraw_pieslice_width.png", 1) + + +def test_pieslice_width_fill(): + # Arrange + im = Image.new("RGB", (W, H)) + draw = ImageDraw.Draw(im) + expected = "Tests/images/imagedraw_pieslice_width_fill.png" + + # Act + draw.pieslice(BBOX1, 10, 260, fill="white", outline="blue", width=5) + + # Assert + assert_image_similar_tofile(im, expected, 1) + + +def test_pieslice_zero_width(): + # Arrange + im = Image.new("RGB", (W, H)) + draw = ImageDraw.Draw(im) + + # Act + draw.pieslice(BBOX1, 10, 260, fill="white", outline="blue", width=0) + + # Assert + with Image.open("Tests/images/imagedraw_pieslice_zero_width.png") as expected: + assert_image_equal(im, expected) + + +def test_pieslice_wide(): + # Arrange + im = Image.new("RGB", (200, 100)) + draw = ImageDraw.Draw(im) + + # Act + draw.pieslice([0, 0, 199, 99], 190, 170, width=10, fill="white", outline="red") + + # Assert + assert_image_equal(im, Image.open("Tests/images/imagedraw_pieslice_wide.png")) + + +def helper_point(points): + # Arrange + im = Image.new("RGB", (W, H)) + draw = ImageDraw.Draw(im) + + # Act + draw.point(points, fill="yellow") + + # Assert + assert_image_equal(im, Image.open("Tests/images/imagedraw_point.png")) + + +def test_point1(): + helper_point(POINTS1) + + +def test_point2(): + helper_point(POINTS2) + + +def helper_polygon(points): + # Arrange + im = Image.new("RGB", (W, H)) + draw = ImageDraw.Draw(im) + + # Act + draw.polygon(points, fill="red", outline="blue") + + # Assert + assert_image_equal(im, Image.open("Tests/images/imagedraw_polygon.png")) + + +def test_polygon1(): + helper_polygon(POINTS1) + + +def test_polygon2(): + helper_polygon(POINTS2) + + +def test_polygon_kite(): + # Test drawing lines of different gradients (dx>dy, dy>dx) and + # vertical (dx==0) and horizontal (dy==0) lines + for mode in ["RGB", "L"]: # Arrange - im = Image.new("RGB", (W, H)) + im = Image.new(mode, (W, H)) draw = ImageDraw.Draw(im) + expected = f"Tests/images/imagedraw_polygon_kite_{mode}.png" # Act - draw.ellipse(((0, 0), (W - 1, H)), fill="white") - - # Assert - assert_image_similar( - im, Image.open("Tests/images/imagedraw_ellipse_edge.png"), 1 - ) - - def test_ellipse_symmetric(self): - for bbox in [(25, 25, 76, 76), (25, 25, 75, 75)]: - im = Image.new("RGB", (101, 101)) - draw = ImageDraw.Draw(im) - draw.ellipse(bbox, fill="green", outline="blue") - assert_image_equal(im, im.transpose(Image.FLIP_LEFT_RIGHT)) - - def test_ellipse_width(self): - # Arrange - im = Image.new("RGB", (W, H)) - draw = ImageDraw.Draw(im) - expected = "Tests/images/imagedraw_ellipse_width.png" - - # Act - draw.ellipse(BBOX1, outline="blue", width=5) - - # Assert - assert_image_similar(im, Image.open(expected), 1) - - def test_ellipse_width_large(self): - # Arrange - im = Image.new("RGB", (500, 500)) - draw = ImageDraw.Draw(im) - expected = "Tests/images/imagedraw_ellipse_width_large.png" - - # Act - draw.ellipse((25, 25, 475, 475), outline="blue", width=75) - - # Assert - assert_image_similar(im, Image.open(expected), 1) - - def test_ellipse_width_fill(self): - # Arrange - im = Image.new("RGB", (W, H)) - draw = ImageDraw.Draw(im) - expected = "Tests/images/imagedraw_ellipse_width_fill.png" - - # Act - draw.ellipse(BBOX1, fill="green", outline="blue", width=5) - - # Assert - assert_image_similar(im, Image.open(expected), 1) - - def helper_line(self, points): - # Arrange - im = Image.new("RGB", (W, H)) - draw = ImageDraw.Draw(im) - - # Act - draw.line(points, fill="yellow", width=2) - - # Assert - assert_image_equal(im, Image.open("Tests/images/imagedraw_line.png")) - - def test_line1(self): - self.helper_line(POINTS1) - - def test_line2(self): - self.helper_line(POINTS2) - - def test_shape1(self): - # Arrange - im = Image.new("RGB", (100, 100), "white") - draw = ImageDraw.Draw(im) - x0, y0 = 5, 5 - x1, y1 = 5, 50 - x2, y2 = 95, 50 - x3, y3 = 95, 5 - - # Act - s = ImageDraw.Outline() - s.move(x0, y0) - s.curve(x1, y1, x2, y2, x3, y3) - s.line(x0, y0) - - draw.shape(s, fill=1) - - # Assert - assert_image_equal(im, Image.open("Tests/images/imagedraw_shape1.png")) - - def test_shape2(self): - # Arrange - im = Image.new("RGB", (100, 100), "white") - draw = ImageDraw.Draw(im) - x0, y0 = 95, 95 - x1, y1 = 95, 50 - x2, y2 = 5, 50 - x3, y3 = 5, 95 - - # Act - s = ImageDraw.Outline() - s.move(x0, y0) - s.curve(x1, y1, x2, y2, x3, y3) - s.line(x0, y0) - - draw.shape(s, outline="blue") - - # Assert - assert_image_equal(im, Image.open("Tests/images/imagedraw_shape2.png")) - - def helper_pieslice(self, bbox, start, end): - # Arrange - im = Image.new("RGB", (W, H)) - draw = ImageDraw.Draw(im) - - # Act - draw.pieslice(bbox, start, end, fill="white", outline="blue") - - # Assert - assert_image_similar(im, Image.open("Tests/images/imagedraw_pieslice.png"), 1) - - def test_pieslice1(self): - self.helper_pieslice(BBOX1, -90, 45) - self.helper_pieslice(BBOX1, -90.5, 45.4) - - def test_pieslice2(self): - self.helper_pieslice(BBOX2, -90, 45) - self.helper_pieslice(BBOX2, -90.5, 45.4) - - def test_pieslice_width(self): - # Arrange - im = Image.new("RGB", (W, H)) - draw = ImageDraw.Draw(im) - expected = "Tests/images/imagedraw_pieslice_width.png" - - # Act - draw.pieslice(BBOX1, 10, 260, outline="blue", width=5) - - # Assert - assert_image_similar(im, Image.open(expected), 1) - - def test_pieslice_width_fill(self): - # Arrange - im = Image.new("RGB", (W, H)) - draw = ImageDraw.Draw(im) - expected = "Tests/images/imagedraw_pieslice_width_fill.png" - - # Act - draw.pieslice(BBOX1, 10, 260, fill="white", outline="blue", width=5) - - # Assert - assert_image_similar(im, Image.open(expected), 1) - - def helper_point(self, points): - # Arrange - im = Image.new("RGB", (W, H)) - draw = ImageDraw.Draw(im) - - # Act - draw.point(points, fill="yellow") - - # Assert - assert_image_equal(im, Image.open("Tests/images/imagedraw_point.png")) - - def test_point1(self): - self.helper_point(POINTS1) - - def test_point2(self): - self.helper_point(POINTS2) - - def helper_polygon(self, points): - # Arrange - im = Image.new("RGB", (W, H)) - draw = ImageDraw.Draw(im) - - # Act - draw.polygon(points, fill="red", outline="blue") - - # Assert - assert_image_equal(im, Image.open("Tests/images/imagedraw_polygon.png")) - - def test_polygon1(self): - self.helper_polygon(POINTS1) - - def test_polygon2(self): - self.helper_polygon(POINTS2) - - def test_polygon_kite(self): - # Test drawing lines of different gradients (dx>dy, dy>dx) and - # vertical (dx==0) and horizontal (dy==0) lines - for mode in ["RGB", "L"]: - # Arrange - im = Image.new(mode, (W, H)) - draw = ImageDraw.Draw(im) - expected = "Tests/images/imagedraw_polygon_kite_{}.png".format(mode) - - # Act - draw.polygon(KITE_POINTS, fill="blue", outline="yellow") - - # Assert - assert_image_equal(im, Image.open(expected)) - - def helper_rectangle(self, bbox): - # Arrange - im = Image.new("RGB", (W, H)) - draw = ImageDraw.Draw(im) - - # Act - draw.rectangle(bbox, fill="black", outline="green") - - # Assert - assert_image_equal(im, Image.open("Tests/images/imagedraw_rectangle.png")) - - def test_rectangle1(self): - self.helper_rectangle(BBOX1) - - def test_rectangle2(self): - self.helper_rectangle(BBOX2) - - def test_big_rectangle(self): - # Test drawing a rectangle bigger than the image - # Arrange - im = Image.new("RGB", (W, H)) - bbox = [(-1, -1), (W + 1, H + 1)] - draw = ImageDraw.Draw(im) - expected = "Tests/images/imagedraw_big_rectangle.png" - - # Act - draw.rectangle(bbox, fill="orange") - - # Assert - assert_image_similar(im, Image.open(expected), 1) - - def test_rectangle_width(self): - # Arrange - im = Image.new("RGB", (W, H)) - draw = ImageDraw.Draw(im) - expected = "Tests/images/imagedraw_rectangle_width.png" - - # Act - draw.rectangle(BBOX1, outline="green", width=5) + draw.polygon(KITE_POINTS, fill="blue", outline="yellow") # Assert assert_image_equal(im, Image.open(expected)) - def test_rectangle_width_fill(self): + +def test_polygon_1px_high(): + # Test drawing a 1px high polygon + # Arrange + im = Image.new("RGB", (3, 3)) + draw = ImageDraw.Draw(im) + expected = "Tests/images/imagedraw_polygon_1px_high.png" + + # Act + draw.polygon([(0, 1), (0, 1), (2, 1), (2, 1)], "#f00") + + # Assert + assert_image_equal(im, Image.open(expected)) + + +def helper_rectangle(bbox): + # Arrange + im = Image.new("RGB", (W, H)) + draw = ImageDraw.Draw(im) + + # Act + draw.rectangle(bbox, fill="black", outline="green") + + # Assert + assert_image_equal(im, Image.open("Tests/images/imagedraw_rectangle.png")) + + +def test_rectangle1(): + helper_rectangle(BBOX1) + + +def test_rectangle2(): + helper_rectangle(BBOX2) + + +def test_big_rectangle(): + # Test drawing a rectangle bigger than the image + # Arrange + im = Image.new("RGB", (W, H)) + bbox = [(-1, -1), (W + 1, H + 1)] + draw = ImageDraw.Draw(im) + + # Act + draw.rectangle(bbox, fill="orange") + + # Assert + assert_image_similar_tofile(im, "Tests/images/imagedraw_big_rectangle.png", 1) + + +def test_rectangle_width(): + # Arrange + im = Image.new("RGB", (W, H)) + draw = ImageDraw.Draw(im) + expected = "Tests/images/imagedraw_rectangle_width.png" + + # Act + draw.rectangle(BBOX1, outline="green", width=5) + + # Assert + assert_image_equal(im, Image.open(expected)) + + +def test_rectangle_width_fill(): + # Arrange + im = Image.new("RGB", (W, H)) + draw = ImageDraw.Draw(im) + expected = "Tests/images/imagedraw_rectangle_width_fill.png" + + # Act + draw.rectangle(BBOX1, fill="blue", outline="green", width=5) + + # Assert + assert_image_equal(im, Image.open(expected)) + + +def test_rectangle_zero_width(): + # Arrange + im = Image.new("RGB", (W, H)) + draw = ImageDraw.Draw(im) + + # Act + draw.rectangle(BBOX1, fill="blue", outline="green", width=0) + + # Assert + with Image.open("Tests/images/imagedraw_rectangle_zero_width.png") as expected: + assert_image_equal(im, expected) + + +def test_rectangle_I16(): + # Arrange + im = Image.new("I;16", (W, H)) + draw = ImageDraw.Draw(im) + + # Act + draw.rectangle(BBOX1, fill="black", outline="green") + + # Assert + assert_image_equal( + im.convert("I"), Image.open("Tests/images/imagedraw_rectangle_I.png") + ) + + +def test_floodfill(): + red = ImageColor.getrgb("red") + + for mode, value in [("L", 1), ("RGBA", (255, 0, 0, 0)), ("RGB", red)]: # Arrange - im = Image.new("RGB", (W, H)) - draw = ImageDraw.Draw(im) - expected = "Tests/images/imagedraw_rectangle_width_fill.png" - - # Act - draw.rectangle(BBOX1, fill="blue", outline="green", width=5) - - # Assert - assert_image_equal(im, Image.open(expected)) - - def test_rectangle_I16(self): - # Arrange - im = Image.new("I;16", (W, H)) - draw = ImageDraw.Draw(im) - - # Act - draw.rectangle(BBOX1, fill="black", outline="green") - - # Assert - assert_image_equal( - im.convert("I"), Image.open("Tests/images/imagedraw_rectangle_I.png") - ) - - def test_floodfill(self): - red = ImageColor.getrgb("red") - - for mode, value in [("L", 1), ("RGBA", (255, 0, 0, 0)), ("RGB", red)]: - # Arrange - im = Image.new(mode, (W, H)) - draw = ImageDraw.Draw(im) - draw.rectangle(BBOX2, outline="yellow", fill="green") - centre_point = (int(W / 2), int(H / 2)) - - # Act - ImageDraw.floodfill(im, centre_point, value) - - # Assert - expected = "Tests/images/imagedraw_floodfill_" + mode + ".png" - with Image.open(expected) as im_floodfill: - assert_image_equal(im, im_floodfill) - - # Test that using the same colour does not change the image - ImageDraw.floodfill(im, centre_point, red) - assert_image_equal(im, im_floodfill) - - # Test that filling outside the image does not change the image - ImageDraw.floodfill(im, (W, H), red) - assert_image_equal(im, im_floodfill) - - # Test filling at the edge of an image - im = Image.new("RGB", (1, 1)) - ImageDraw.floodfill(im, (0, 0), red) - assert_image_equal(im, Image.new("RGB", (1, 1), red)) - - def test_floodfill_border(self): - # floodfill() is experimental - - # Arrange - im = Image.new("RGB", (W, H)) + im = Image.new(mode, (W, H)) draw = ImageDraw.Draw(im) draw.rectangle(BBOX2, outline="yellow", fill="green") centre_point = (int(W / 2), int(H / 2)) # Act - ImageDraw.floodfill( - im, - centre_point, - ImageColor.getrgb("red"), - border=ImageColor.getrgb("black"), - ) + ImageDraw.floodfill(im, centre_point, value) # Assert - assert_image_equal(im, Image.open("Tests/images/imagedraw_floodfill2.png")) + expected = "Tests/images/imagedraw_floodfill_" + mode + ".png" + with Image.open(expected) as im_floodfill: + assert_image_equal(im, im_floodfill) - def test_floodfill_thresh(self): - # floodfill() is experimental + # Test that using the same colour does not change the image + ImageDraw.floodfill(im, centre_point, red) + assert_image_equal(im, im_floodfill) - # Arrange - im = Image.new("RGB", (W, H)) - draw = ImageDraw.Draw(im) - draw.rectangle(BBOX2, outline="darkgreen", fill="green") - centre_point = (int(W / 2), int(H / 2)) + # Test that filling outside the image does not change the image + ImageDraw.floodfill(im, (W, H), red) + assert_image_equal(im, im_floodfill) - # Act - ImageDraw.floodfill(im, centre_point, ImageColor.getrgb("red"), thresh=30) + # Test filling at the edge of an image + im = Image.new("RGB", (1, 1)) + ImageDraw.floodfill(im, (0, 0), red) + assert_image_equal(im, Image.new("RGB", (1, 1), red)) - # Assert - assert_image_equal(im, Image.open("Tests/images/imagedraw_floodfill2.png")) - def test_floodfill_not_negative(self): - # floodfill() is experimental - # Test that floodfill does not extend into negative coordinates +def test_floodfill_border(): + # floodfill() is experimental - # Arrange - im = Image.new("RGB", (W, H)) - draw = ImageDraw.Draw(im) - draw.line((W / 2, 0, W / 2, H / 2), fill="green") - draw.line((0, H / 2, W / 2, H / 2), fill="green") + # Arrange + im = Image.new("RGB", (W, H)) + draw = ImageDraw.Draw(im) + draw.rectangle(BBOX2, outline="yellow", fill="green") + centre_point = (int(W / 2), int(H / 2)) - # Act - ImageDraw.floodfill(im, (int(W / 4), int(H / 4)), ImageColor.getrgb("red")) + # Act + ImageDraw.floodfill( + im, + centre_point, + ImageColor.getrgb("red"), + border=ImageColor.getrgb("black"), + ) - # Assert + # Assert + assert_image_equal(im, Image.open("Tests/images/imagedraw_floodfill2.png")) + + +def test_floodfill_thresh(): + # floodfill() is experimental + + # Arrange + im = Image.new("RGB", (W, H)) + draw = ImageDraw.Draw(im) + draw.rectangle(BBOX2, outline="darkgreen", fill="green") + centre_point = (int(W / 2), int(H / 2)) + + # Act + ImageDraw.floodfill(im, centre_point, ImageColor.getrgb("red"), thresh=30) + + # Assert + assert_image_equal(im, Image.open("Tests/images/imagedraw_floodfill2.png")) + + +def test_floodfill_not_negative(): + # floodfill() is experimental + # Test that floodfill does not extend into negative coordinates + + # Arrange + im = Image.new("RGB", (W, H)) + draw = ImageDraw.Draw(im) + draw.line((W / 2, 0, W / 2, H / 2), fill="green") + draw.line((0, H / 2, W / 2, H / 2), fill="green") + + # Act + ImageDraw.floodfill(im, (int(W / 4), int(H / 4)), ImageColor.getrgb("red")) + + # Assert + assert_image_equal( + im, Image.open("Tests/images/imagedraw_floodfill_not_negative.png") + ) + + +def create_base_image_draw( + size, mode=DEFAULT_MODE, background1=WHITE, background2=GRAY +): + img = Image.new(mode, size, background1) + for x in range(0, size[0]): + for y in range(0, size[1]): + if (x + y) % 2 == 0: + img.putpixel((x, y), background2) + return img, ImageDraw.Draw(img) + + +def test_square(): + with Image.open(os.path.join(IMAGES_PATH, "square.png")) as expected: + expected.load() + img, draw = create_base_image_draw((10, 10)) + draw.polygon([(2, 2), (2, 7), (7, 7), (7, 2)], BLACK) + assert_image_equal(img, expected, "square as normal polygon failed") + img, draw = create_base_image_draw((10, 10)) + draw.polygon([(7, 7), (7, 2), (2, 2), (2, 7)], BLACK) + assert_image_equal(img, expected, "square as inverted polygon failed") + img, draw = create_base_image_draw((10, 10)) + draw.rectangle((2, 2, 7, 7), BLACK) + assert_image_equal(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(img, expected, "square as inverted rectangle failed") + + +def test_triangle_right(): + with Image.open(os.path.join(IMAGES_PATH, "triangle_right.png")) as expected: + expected.load() + img, draw = create_base_image_draw((20, 20)) + draw.polygon([(3, 5), (17, 5), (10, 12)], BLACK) + assert_image_equal(img, expected, "triangle right failed") + + +def test_line_horizontal(): + with Image.open( + os.path.join(IMAGES_PATH, "line_horizontal_w2px_normal.png") + ) as expected: + expected.load() + img, draw = create_base_image_draw((20, 20)) + draw.line((5, 5, 14, 5), BLACK, 2) assert_image_equal( - im, Image.open("Tests/images/imagedraw_floodfill_not_negative.png") + img, expected, "line straight horizontal normal 2px wide failed" ) + with Image.open( + os.path.join(IMAGES_PATH, "line_horizontal_w2px_inverted.png") + ) as expected: + expected.load() + img, draw = create_base_image_draw((20, 20)) + draw.line((14, 5, 5, 5), BLACK, 2) + assert_image_equal( + img, expected, "line straight horizontal inverted 2px wide failed" + ) + with Image.open(os.path.join(IMAGES_PATH, "line_horizontal_w3px.png")) as expected: + expected.load() + img, draw = create_base_image_draw((20, 20)) + draw.line((5, 5, 14, 5), BLACK, 3) + assert_image_equal( + img, expected, "line straight horizontal normal 3px wide failed" + ) + img, draw = create_base_image_draw((20, 20)) + draw.line((14, 5, 5, 5), BLACK, 3) + assert_image_equal( + img, expected, "line straight horizontal inverted 3px wide failed" + ) + with Image.open( + os.path.join(IMAGES_PATH, "line_horizontal_w101px.png") + ) as expected: + expected.load() + img, draw = create_base_image_draw((200, 110)) + draw.line((5, 55, 195, 55), BLACK, 101) + assert_image_equal(img, expected, "line straight horizontal 101px wide failed") - def create_base_image_draw( - self, size, mode=DEFAULT_MODE, background1=WHITE, background2=GRAY - ): - img = Image.new(mode, size, background1) - for x in range(0, size[0]): - for y in range(0, size[1]): - if (x + y) % 2 == 0: - img.putpixel((x, y), background2) - return img, ImageDraw.Draw(img) - def test_square(self): - with Image.open(os.path.join(IMAGES_PATH, "square.png")) as expected: - expected.load() - img, draw = self.create_base_image_draw((10, 10)) - draw.polygon([(2, 2), (2, 7), (7, 7), (7, 2)], BLACK) - assert_image_equal(img, expected, "square as normal polygon failed") - img, draw = self.create_base_image_draw((10, 10)) - draw.polygon([(7, 7), (7, 2), (2, 2), (2, 7)], BLACK) - assert_image_equal(img, expected, "square as inverted polygon failed") - img, draw = self.create_base_image_draw((10, 10)) - draw.rectangle((2, 2, 7, 7), BLACK) - assert_image_equal(img, expected, "square as normal rectangle failed") - img, draw = self.create_base_image_draw((10, 10)) - draw.rectangle((7, 7, 2, 2), BLACK) - assert_image_equal(img, expected, "square as inverted rectangle failed") +def test_line_h_s1_w2(): + pytest.skip("failing") + with Image.open( + os.path.join(IMAGES_PATH, "line_horizontal_slope1px_w2px.png") + ) as expected: + expected.load() + img, draw = create_base_image_draw((20, 20)) + draw.line((5, 5, 14, 6), BLACK, 2) + assert_image_equal(img, expected, "line horizontal 1px slope 2px wide failed") - def test_triangle_right(self): - with Image.open(os.path.join(IMAGES_PATH, "triangle_right.png")) as expected: - expected.load() - img, draw = self.create_base_image_draw((20, 20)) - draw.polygon([(3, 5), (17, 5), (10, 12)], BLACK) - assert_image_equal(img, expected, "triangle right failed") - def test_line_horizontal(self): - with Image.open( - os.path.join(IMAGES_PATH, "line_horizontal_w2px_normal.png") - ) as expected: - expected.load() - img, draw = self.create_base_image_draw((20, 20)) - draw.line((5, 5, 14, 5), BLACK, 2) - assert_image_equal( - img, expected, "line straight horizontal normal 2px wide failed" - ) - with Image.open( - os.path.join(IMAGES_PATH, "line_horizontal_w2px_inverted.png") - ) as expected: - expected.load() - img, draw = self.create_base_image_draw((20, 20)) - draw.line((14, 5, 5, 5), BLACK, 2) - assert_image_equal( - img, expected, "line straight horizontal inverted 2px wide failed" - ) - with Image.open( - os.path.join(IMAGES_PATH, "line_horizontal_w3px.png") - ) as expected: - expected.load() - img, draw = self.create_base_image_draw((20, 20)) - draw.line((5, 5, 14, 5), BLACK, 3) - assert_image_equal( - img, expected, "line straight horizontal normal 3px wide failed" - ) - img, draw = self.create_base_image_draw((20, 20)) - draw.line((14, 5, 5, 5), BLACK, 3) - assert_image_equal( - img, expected, "line straight horizontal inverted 3px wide failed" - ) - with Image.open( - os.path.join(IMAGES_PATH, "line_horizontal_w101px.png") - ) as expected: - expected.load() - img, draw = self.create_base_image_draw((200, 110)) - draw.line((5, 55, 195, 55), BLACK, 101) - assert_image_equal( - img, expected, "line straight horizontal 101px wide failed" - ) +def test_line_vertical(): + with Image.open( + os.path.join(IMAGES_PATH, "line_vertical_w2px_normal.png") + ) as expected: + expected.load() + img, draw = create_base_image_draw((20, 20)) + draw.line((5, 5, 5, 14), BLACK, 2) + assert_image_equal( + img, expected, "line straight vertical normal 2px wide failed" + ) + with Image.open( + os.path.join(IMAGES_PATH, "line_vertical_w2px_inverted.png") + ) as expected: + expected.load() + img, draw = create_base_image_draw((20, 20)) + draw.line((5, 14, 5, 5), BLACK, 2) + assert_image_equal( + img, expected, "line straight vertical inverted 2px wide failed" + ) + with Image.open(os.path.join(IMAGES_PATH, "line_vertical_w3px.png")) as expected: + expected.load() + img, draw = create_base_image_draw((20, 20)) + draw.line((5, 5, 5, 14), BLACK, 3) + assert_image_equal( + img, expected, "line straight vertical normal 3px wide failed" + ) + img, draw = create_base_image_draw((20, 20)) + draw.line((5, 14, 5, 5), BLACK, 3) + assert_image_equal( + img, expected, "line straight vertical inverted 3px wide failed" + ) + with Image.open(os.path.join(IMAGES_PATH, "line_vertical_w101px.png")) as expected: + expected.load() + img, draw = create_base_image_draw((110, 200)) + draw.line((55, 5, 55, 195), BLACK, 101) + assert_image_equal(img, expected, "line straight vertical 101px wide failed") + with Image.open( + os.path.join(IMAGES_PATH, "line_vertical_slope1px_w2px.png") + ) as expected: + expected.load() + img, draw = create_base_image_draw((20, 20)) + draw.line((5, 5, 6, 14), BLACK, 2) + assert_image_equal(img, expected, "line vertical 1px slope 2px wide failed") - def test_line_h_s1_w2(self): - self.skipTest("failing") - with Image.open( - os.path.join(IMAGES_PATH, "line_horizontal_slope1px_w2px.png") - ) as expected: - expected.load() - img, draw = self.create_base_image_draw((20, 20)) - draw.line((5, 5, 14, 6), BLACK, 2) - assert_image_equal( - img, expected, "line horizontal 1px slope 2px wide failed" - ) - def test_line_vertical(self): - with Image.open( - os.path.join(IMAGES_PATH, "line_vertical_w2px_normal.png") - ) as expected: - expected.load() - img, draw = self.create_base_image_draw((20, 20)) - draw.line((5, 5, 5, 14), BLACK, 2) - assert_image_equal( - img, expected, "line straight vertical normal 2px wide failed" - ) - with Image.open( - os.path.join(IMAGES_PATH, "line_vertical_w2px_inverted.png") - ) as expected: - expected.load() - img, draw = self.create_base_image_draw((20, 20)) - draw.line((5, 14, 5, 5), BLACK, 2) - assert_image_equal( - img, expected, "line straight vertical inverted 2px wide failed" - ) - with Image.open( - os.path.join(IMAGES_PATH, "line_vertical_w3px.png") - ) as expected: - expected.load() - img, draw = self.create_base_image_draw((20, 20)) - draw.line((5, 5, 5, 14), BLACK, 3) - assert_image_equal( - img, expected, "line straight vertical normal 3px wide failed" - ) - img, draw = self.create_base_image_draw((20, 20)) - draw.line((5, 14, 5, 5), BLACK, 3) - assert_image_equal( - img, expected, "line straight vertical inverted 3px wide failed" - ) - with Image.open( - os.path.join(IMAGES_PATH, "line_vertical_w101px.png") - ) as expected: - expected.load() - img, draw = self.create_base_image_draw((110, 200)) - draw.line((55, 5, 55, 195), BLACK, 101) - assert_image_equal( - img, expected, "line straight vertical 101px wide failed" - ) - with Image.open( - os.path.join(IMAGES_PATH, "line_vertical_slope1px_w2px.png") - ) as expected: - expected.load() - img, draw = self.create_base_image_draw((20, 20)) - draw.line((5, 5, 6, 14), BLACK, 2) - assert_image_equal(img, expected, "line vertical 1px slope 2px wide failed") +def test_line_oblique_45(): + with Image.open( + os.path.join(IMAGES_PATH, "line_oblique_45_w3px_a.png") + ) as expected: + expected.load() + img, draw = create_base_image_draw((20, 20)) + draw.line((5, 5, 14, 14), BLACK, 3) + assert_image_equal(img, expected, "line oblique 45 normal 3px wide A failed") + img, draw = create_base_image_draw((20, 20)) + draw.line((14, 14, 5, 5), BLACK, 3) + assert_image_equal(img, expected, "line oblique 45 inverted 3px wide A failed") + with Image.open( + os.path.join(IMAGES_PATH, "line_oblique_45_w3px_b.png") + ) as expected: + expected.load() + img, draw = create_base_image_draw((20, 20)) + draw.line((14, 5, 5, 14), BLACK, 3) + assert_image_equal(img, expected, "line oblique 45 normal 3px wide B failed") + img, draw = create_base_image_draw((20, 20)) + draw.line((5, 14, 14, 5), BLACK, 3) + assert_image_equal(img, expected, "line oblique 45 inverted 3px wide B failed") - def test_line_oblique_45(self): - with Image.open( - os.path.join(IMAGES_PATH, "line_oblique_45_w3px_a.png") - ) as expected: - expected.load() - img, draw = self.create_base_image_draw((20, 20)) - draw.line((5, 5, 14, 14), BLACK, 3) - assert_image_equal( - img, expected, "line oblique 45 normal 3px wide A failed" - ) - img, draw = self.create_base_image_draw((20, 20)) - draw.line((14, 14, 5, 5), BLACK, 3) - assert_image_equal( - img, expected, "line oblique 45 inverted 3px wide A failed" - ) - with Image.open( - os.path.join(IMAGES_PATH, "line_oblique_45_w3px_b.png") - ) as expected: - expected.load() - img, draw = self.create_base_image_draw((20, 20)) - draw.line((14, 5, 5, 14), BLACK, 3) - assert_image_equal( - img, expected, "line oblique 45 normal 3px wide B failed" - ) - img, draw = self.create_base_image_draw((20, 20)) - draw.line((5, 14, 14, 5), BLACK, 3) - assert_image_equal( - img, expected, "line oblique 45 inverted 3px wide B failed" - ) - def test_wide_line_dot(self): - # Test drawing a wide "line" from one point to another just draws - # a single point - # Arrange - im = Image.new("RGB", (W, H)) - draw = ImageDraw.Draw(im) - expected = "Tests/images/imagedraw_wide_line_dot.png" +def test_wide_line_dot(): + # Test drawing a wide "line" from one point to another just draws a single point + # Arrange + im = Image.new("RGB", (W, H)) + draw = ImageDraw.Draw(im) - # Act - draw.line([(50, 50), (50, 50)], width=3) + # Act + draw.line([(50, 50), (50, 50)], width=3) - # Assert - assert_image_similar(im, Image.open(expected), 1) + # Assert + assert_image_similar_tofile(im, "Tests/images/imagedraw_wide_line_dot.png", 1) - def test_line_joint(self): - im = Image.new("RGB", (500, 325)) - draw = ImageDraw.Draw(im) - expected = "Tests/images/imagedraw_line_joint_curve.png" - # Act - xy = [ +def test_wide_line_larger_than_int(): + # Arrange + im = Image.new("RGB", (W, H)) + draw = ImageDraw.Draw(im) + expected = "Tests/images/imagedraw_wide_line_larger_than_int.png" + + # Act + draw.line([(0, 0), (32768, 32768)], width=3) + + # Assert + assert_image_similar_tofile(im, expected, 1) + + +@pytest.mark.parametrize( + "xy", + [ + [ (400, 280), (380, 280), (450, 280), @@ -794,107 +986,289 @@ class TestImageDraw(PillowTestCase): (50, 200), (150, 50), (250, 100), - ] - draw.line(xy, GRAY, 50, "curve") + ], + ( + 400, + 280, + 380, + 280, + 450, + 280, + 440, + 120, + 350, + 200, + 310, + 280, + 300, + 280, + 250, + 280, + 250, + 200, + 150, + 200, + 150, + 260, + 50, + 200, + 150, + 50, + 250, + 100, + ), + [ + 400, + 280, + 380, + 280, + 450, + 280, + 440, + 120, + 350, + 200, + 310, + 280, + 300, + 280, + 250, + 280, + 250, + 200, + 150, + 200, + 150, + 260, + 50, + 200, + 150, + 50, + 250, + 100, + ], + ], +) +def test_line_joint(xy): + im = Image.new("RGB", (500, 325)) + draw = ImageDraw.Draw(im) - # Assert - assert_image_similar(im, Image.open(expected), 3) + # Act + draw.line(xy, GRAY, 50, "curve") - def test_textsize_empty_string(self): - # https://github.com/python-pillow/Pillow/issues/2783 + # Assert + assert_image_similar_tofile(im, "Tests/images/imagedraw_line_joint_curve.png", 3) + + +def test_textsize_empty_string(): + # https://github.com/python-pillow/Pillow/issues/2783 + # Arrange + im = Image.new("RGB", (W, H)) + draw = ImageDraw.Draw(im) + + # Act + # Should not cause 'SystemError: returned NULL without setting an error' + draw.textsize("") + draw.textsize("\n") + draw.textsize("test\n") + + +@skip_unless_feature("freetype2") +def test_textsize_stroke(): + # Arrange + im = Image.new("RGB", (W, H)) + draw = ImageDraw.Draw(im) + font = ImageFont.truetype("Tests/fonts/FreeMono.ttf", 20) + + # Act / Assert + assert draw.textsize("A", font, stroke_width=2) == (16, 20) + assert draw.multiline_textsize("ABC\nAaaa", font, stroke_width=2) == (52, 44) + + +@skip_unless_feature("freetype2") +def test_stroke(): + for suffix, stroke_fill in {"same": None, "different": "#0f0"}.items(): # Arrange - im = Image.new("RGB", (W, H)) - draw = ImageDraw.Draw(im) - - # Act - # Should not cause 'SystemError: returned NULL without setting an error' - draw.textsize("") - draw.textsize("\n") - draw.textsize("test\n") - - @unittest.skipUnless(HAS_FREETYPE, "ImageFont not available") - def test_textsize_stroke(self): - # Arrange - im = Image.new("RGB", (W, H)) - draw = ImageDraw.Draw(im) - font = ImageFont.truetype("Tests/fonts/FreeMono.ttf", 20) - - # Act / Assert - self.assertEqual(draw.textsize("A", font, stroke_width=2), (16, 20)) - self.assertEqual( - draw.multiline_textsize("ABC\nAaaa", font, stroke_width=2), (52, 44) - ) - - @unittest.skipUnless(HAS_FREETYPE, "ImageFont not available") - def test_stroke(self): - for suffix, stroke_fill in {"same": None, "different": "#0f0"}.items(): - # Arrange - im = Image.new("RGB", (120, 130)) - draw = ImageDraw.Draw(im) - font = ImageFont.truetype("Tests/fonts/FreeMono.ttf", 120) - - # Act - draw.text( - (10, 10), "A", "#f00", font, stroke_width=2, stroke_fill=stroke_fill - ) - - # Assert - assert_image_similar( - im, Image.open("Tests/images/imagedraw_stroke_" + suffix + ".png"), 3.1 - ) - - @unittest.skipUnless(HAS_FREETYPE, "ImageFont not available") - def test_stroke_multiline(self): - # Arrange - im = Image.new("RGB", (100, 250)) + im = Image.new("RGB", (120, 130)) draw = ImageDraw.Draw(im) font = ImageFont.truetype("Tests/fonts/FreeMono.ttf", 120) # Act - draw.multiline_text( - (10, 10), "A\nB", "#f00", font, stroke_width=2, stroke_fill="#0f0" - ) + draw.text((12, 12), "A", "#f00", font, stroke_width=2, stroke_fill=stroke_fill) # Assert - assert_image_similar( - im, Image.open("Tests/images/imagedraw_stroke_multiline.png"), 3.3 + assert_image_similar_tofile( + im, "Tests/images/imagedraw_stroke_" + suffix + ".png", 3.1 ) - def test_same_color_outline(self): - # Prepare shape - x0, y0 = 5, 5 - x1, y1 = 5, 50 - x2, y2 = 95, 50 - x3, y3 = 95, 5 - s = ImageDraw.Outline() - s.move(x0, y0) - s.curve(x1, y1, x2, y2, x3, y3) - s.line(x0, y0) +@skip_unless_feature("freetype2") +def test_stroke_descender(): + # Arrange + im = Image.new("RGB", (120, 130)) + draw = ImageDraw.Draw(im) + font = ImageFont.truetype("Tests/fonts/FreeMono.ttf", 120) - # Begin - for mode in ["RGB", "L"]: - for fill, outline in [["red", None], ["red", "red"], ["red", "#f00"]]: - for operation, args in { - "chord": [BBOX1, 0, 180], - "ellipse": [BBOX1], - "shape": [s], - "pieslice": [BBOX1, -90, 45], - "polygon": [[(18, 30), (85, 30), (60, 72)]], - "rectangle": [BBOX1], - }.items(): - # Arrange - im = Image.new(mode, (W, H)) - draw = ImageDraw.Draw(im) + # Act + draw.text((12, 2), "y", "#f00", font, stroke_width=2, stroke_fill="#0f0") - # Act - draw_method = getattr(draw, operation) - args += [fill, outline] - draw_method(*args) + # Assert + assert_image_similar_tofile(im, "Tests/images/imagedraw_stroke_descender.png", 6.76) - # Assert - expected = "Tests/images/imagedraw_outline_{}_{}.png".format( - operation, mode - ) - assert_image_similar(im, Image.open(expected), 1) + +@skip_unless_feature("freetype2") +def test_stroke_multiline(): + # Arrange + im = Image.new("RGB", (100, 250)) + draw = ImageDraw.Draw(im) + font = ImageFont.truetype("Tests/fonts/FreeMono.ttf", 120) + + # Act + draw.multiline_text( + (12, 12), "A\nB", "#f00", font, stroke_width=2, stroke_fill="#0f0" + ) + + # Assert + assert_image_similar_tofile(im, "Tests/images/imagedraw_stroke_multiline.png", 3.3) + + +def test_same_color_outline(): + # Prepare shape + x0, y0 = 5, 5 + x1, y1 = 5, 50 + x2, y2 = 95, 50 + x3, y3 = 95, 5 + + s = ImageDraw.Outline() + s.move(x0, y0) + s.curve(x1, y1, x2, y2, x3, y3) + s.line(x0, y0) + + # Begin + for mode in ["RGB", "L"]: + for fill, outline in [["red", None], ["red", "red"], ["red", "#f00"]]: + for operation, args in { + "chord": [BBOX1, 0, 180], + "ellipse": [BBOX1], + "shape": [s], + "pieslice": [BBOX1, -90, 45], + "polygon": [[(18, 30), (85, 30), (60, 72)]], + "rectangle": [BBOX1], + }.items(): + # Arrange + im = Image.new(mode, (W, H)) + draw = ImageDraw.Draw(im) + + # Act + draw_method = getattr(draw, operation) + args += [fill, outline] + draw_method(*args) + + # Assert + expected = f"Tests/images/imagedraw_outline_{operation}_{mode}.png" + assert_image_similar_tofile(im, expected, 1) + + +@pytest.mark.parametrize( + "n_sides, rotation, polygon_name", + [(4, 0, "square"), (8, 0, "regular_octagon"), (4, 45, "square")], +) +def test_draw_regular_polygon(n_sides, rotation, polygon_name): + im = Image.new("RGBA", size=(W, H), color=(255, 0, 0, 0)) + filename_base = f"Tests/images/imagedraw_{polygon_name}" + filename = ( + f"{filename_base}.png" + if rotation == 0 + else f"{filename_base}_rotate_{rotation}.png" + ) + draw = ImageDraw.Draw(im) + bounding_circle = ((W // 2, H // 2), 25) + draw.regular_polygon(bounding_circle, n_sides, rotation=rotation, fill="red") + assert_image_equal(im, Image.open(filename)) + + +@pytest.mark.parametrize( + "n_sides, expected_vertices", + [ + (3, [(28.35, 62.5), (71.65, 62.5), (50.0, 25.0)]), + (4, [(32.32, 67.68), (67.68, 67.68), (67.68, 32.32), (32.32, 32.32)]), + ( + 5, + [ + (35.31, 70.23), + (64.69, 70.23), + (73.78, 42.27), + (50.0, 25.0), + (26.22, 42.27), + ], + ), + ( + 6, + [ + (37.5, 71.65), + (62.5, 71.65), + (75.0, 50.0), + (62.5, 28.35), + (37.5, 28.35), + (25.0, 50.0), + ], + ), + ], +) +def test_compute_regular_polygon_vertices(n_sides, expected_vertices): + bounding_circle = (W // 2, H // 2, 25) + vertices = ImageDraw._compute_regular_polygon_vertices(bounding_circle, n_sides, 0) + assert vertices == expected_vertices + + +@pytest.mark.parametrize( + "n_sides, bounding_circle, rotation, expected_error, error_message", + [ + (None, (50, 50, 25), 0, TypeError, "n_sides should be an int"), + (1, (50, 50, 25), 0, ValueError, "n_sides should be an int > 2"), + (3, 50, 0, TypeError, "bounding_circle should be a tuple"), + ( + 3, + (50, 50, 100, 100), + 0, + ValueError, + "bounding_circle should contain 2D coordinates " + "and a radius (e.g. (x, y, r) or ((x, y), r) )", + ), + ( + 3, + (50, 50, "25"), + 0, + ValueError, + "bounding_circle should only contain numeric data", + ), + ( + 3, + ((50, 50, 50), 25), + 0, + ValueError, + "bounding_circle centre should contain 2D coordinates (e.g. (x, y))", + ), + ( + 3, + (50, 50, 0), + 0, + ValueError, + "bounding_circle radius should be > 0", + ), + ( + 3, + (50, 50, 25), + "0", + ValueError, + "rotation should be an int or float", + ), + ], +) +def test_compute_regular_polygon_vertices_input_error_handling( + n_sides, bounding_circle, rotation, expected_error, error_message +): + with pytest.raises(expected_error) as e: + ImageDraw._compute_regular_polygon_vertices(bounding_circle, n_sides, rotation) + assert str(e.value) == error_message diff --git a/Tests/test_imagedraw2.py b/Tests/test_imagedraw2.py index 80bafcb96..b78dfe85b 100644 --- a/Tests/test_imagedraw2.py +++ b/Tests/test_imagedraw2.py @@ -1,9 +1,13 @@ import os.path -import unittest -from PIL import Image, ImageDraw2, features +from PIL import Image, ImageDraw, ImageDraw2 -from .helper import PillowTestCase, assert_image_equal, assert_image_similar, hopper +from .helper import ( + assert_image_equal, + assert_image_similar, + hopper, + skip_unless_feature, +) BLACK = (0, 0, 0) WHITE = (255, 255, 255) @@ -30,194 +34,207 @@ POINTS2 = [10, 10, 20, 40, 30, 30] KITE_POINTS = [(10, 50), (70, 10), (90, 50), (70, 90), (10, 50)] -HAS_FREETYPE = features.check("freetype2") FONT_PATH = "Tests/fonts/FreeMono.ttf" -class TestImageDraw(PillowTestCase): - def test_sanity(self): - im = hopper("RGB").copy() +def test_sanity(): + im = hopper("RGB").copy() - draw = ImageDraw2.Draw(im) - pen = ImageDraw2.Pen("blue", width=7) - draw.line(list(range(10)), pen) + draw = ImageDraw2.Draw(im) + pen = ImageDraw2.Pen("blue", width=7) + draw.line(list(range(10)), pen) - from PIL import ImageDraw + draw, handler = ImageDraw.getdraw(im) + pen = ImageDraw2.Pen("blue", width=7) + draw.line(list(range(10)), pen) - draw, handler = ImageDraw.getdraw(im) - pen = ImageDraw2.Pen("blue", width=7) - draw.line(list(range(10)), pen) - def helper_ellipse(self, mode, bbox): - # Arrange - im = Image.new("RGB", (W, H)) - draw = ImageDraw2.Draw(im) - pen = ImageDraw2.Pen("blue", width=2) - brush = ImageDraw2.Brush("green") - expected = "Tests/images/imagedraw_ellipse_{}.png".format(mode) +def helper_ellipse(mode, bbox): + # Arrange + im = Image.new("RGB", (W, H)) + draw = ImageDraw2.Draw(im) + pen = ImageDraw2.Pen("blue", width=2) + brush = ImageDraw2.Brush("green") + expected = f"Tests/images/imagedraw_ellipse_{mode}.png" - # Act - draw.ellipse(bbox, pen, brush) + # Act + draw.ellipse(bbox, pen, brush) - # Assert - assert_image_similar(im, Image.open(expected), 1) + # Assert + assert_image_similar(im, Image.open(expected), 1) - def test_ellipse1(self): - self.helper_ellipse("RGB", BBOX1) - def test_ellipse2(self): - self.helper_ellipse("RGB", BBOX2) +def test_ellipse1(): + helper_ellipse("RGB", BBOX1) - def test_ellipse_edge(self): - # Arrange - im = Image.new("RGB", (W, H)) - draw = ImageDraw2.Draw(im) - brush = ImageDraw2.Brush("white") - # Act - draw.ellipse(((0, 0), (W - 1, H)), brush) +def test_ellipse2(): + helper_ellipse("RGB", BBOX2) - # Assert - assert_image_similar( - im, Image.open("Tests/images/imagedraw_ellipse_edge.png"), 1 - ) - def helper_line(self, points): - # Arrange - im = Image.new("RGB", (W, H)) - draw = ImageDraw2.Draw(im) - pen = ImageDraw2.Pen("yellow", width=2) +def test_ellipse_edge(): + # Arrange + im = Image.new("RGB", (W, H)) + draw = ImageDraw2.Draw(im) + brush = ImageDraw2.Brush("white") - # Act - draw.line(points, pen) + # Act + draw.ellipse(((0, 0), (W - 1, H - 1)), brush) - # Assert - assert_image_equal(im, Image.open("Tests/images/imagedraw_line.png")) + # Assert + assert_image_similar(im, Image.open("Tests/images/imagedraw_ellipse_edge.png"), 1) - def test_line1_pen(self): - self.helper_line(POINTS1) - def test_line2_pen(self): - self.helper_line(POINTS2) +def helper_line(points): + # Arrange + im = Image.new("RGB", (W, H)) + draw = ImageDraw2.Draw(im) + pen = ImageDraw2.Pen("yellow", width=2) - def test_line_pen_as_brush(self): - # Arrange - im = Image.new("RGB", (W, H)) - draw = ImageDraw2.Draw(im) - pen = None - brush = ImageDraw2.Pen("yellow", width=2) + # Act + draw.line(points, pen) - # Act - # Pass in the pen as the brush parameter - draw.line(POINTS1, pen, brush) + # Assert + assert_image_equal(im, Image.open("Tests/images/imagedraw_line.png")) - # Assert - assert_image_equal(im, Image.open("Tests/images/imagedraw_line.png")) - def helper_polygon(self, points): - # Arrange - im = Image.new("RGB", (W, H)) - draw = ImageDraw2.Draw(im) - pen = ImageDraw2.Pen("blue", width=2) - brush = ImageDraw2.Brush("red") +def test_line1_pen(): + helper_line(POINTS1) - # Act - draw.polygon(points, pen, brush) - # Assert - assert_image_equal(im, Image.open("Tests/images/imagedraw_polygon.png")) +def test_line2_pen(): + helper_line(POINTS2) - def test_polygon1(self): - self.helper_polygon(POINTS1) - def test_polygon2(self): - self.helper_polygon(POINTS2) +def test_line_pen_as_brush(): + # Arrange + im = Image.new("RGB", (W, H)) + draw = ImageDraw2.Draw(im) + pen = None + brush = ImageDraw2.Pen("yellow", width=2) - def helper_rectangle(self, bbox): - # Arrange - im = Image.new("RGB", (W, H)) - draw = ImageDraw2.Draw(im) - pen = ImageDraw2.Pen("green", width=2) - brush = ImageDraw2.Brush("black") + # Act + # Pass in the pen as the brush parameter + draw.line(POINTS1, pen, brush) - # Act - draw.rectangle(bbox, pen, brush) + # Assert + assert_image_equal(im, Image.open("Tests/images/imagedraw_line.png")) - # Assert - assert_image_equal(im, Image.open("Tests/images/imagedraw_rectangle.png")) - def test_rectangle1(self): - self.helper_rectangle(BBOX1) +def helper_polygon(points): + # Arrange + im = Image.new("RGB", (W, H)) + draw = ImageDraw2.Draw(im) + pen = ImageDraw2.Pen("blue", width=2) + brush = ImageDraw2.Brush("red") - def test_rectangle2(self): - self.helper_rectangle(BBOX2) + # Act + draw.polygon(points, pen, brush) - def test_big_rectangle(self): - # Test drawing a rectangle bigger than the image - # Arrange - im = Image.new("RGB", (W, H)) - bbox = [(-1, -1), (W + 1, H + 1)] - brush = ImageDraw2.Brush("orange") - draw = ImageDraw2.Draw(im) - expected = "Tests/images/imagedraw_big_rectangle.png" + # Assert + assert_image_equal(im, Image.open("Tests/images/imagedraw_polygon.png")) - # Act - draw.rectangle(bbox, brush) - # Assert - assert_image_similar(im, Image.open(expected), 1) +def test_polygon1(): + helper_polygon(POINTS1) - @unittest.skipUnless(HAS_FREETYPE, "ImageFont not available") - def test_text(self): - # Arrange - im = Image.new("RGB", (W, H)) - draw = ImageDraw2.Draw(im) - font = ImageDraw2.Font("white", FONT_PATH) - expected = "Tests/images/imagedraw2_text.png" - # Act - draw.text((5, 5), "ImageDraw2", font) +def test_polygon2(): + helper_polygon(POINTS2) - # Assert - assert_image_similar(im, Image.open(expected), 13) - @unittest.skipUnless(HAS_FREETYPE, "ImageFont not available") - def test_textsize(self): - # Arrange - im = Image.new("RGB", (W, H)) - draw = ImageDraw2.Draw(im) - font = ImageDraw2.Font("white", FONT_PATH) +def helper_rectangle(bbox): + # Arrange + im = Image.new("RGB", (W, H)) + draw = ImageDraw2.Draw(im) + pen = ImageDraw2.Pen("green", width=2) + brush = ImageDraw2.Brush("black") - # Act - size = draw.textsize("ImageDraw2", font) + # Act + draw.rectangle(bbox, pen, brush) - # Assert - self.assertEqual(size[1], 12) + # Assert + assert_image_equal(im, Image.open("Tests/images/imagedraw_rectangle.png")) - @unittest.skipUnless(HAS_FREETYPE, "ImageFont not available") - def test_textsize_empty_string(self): - # Arrange - im = Image.new("RGB", (W, H)) - draw = ImageDraw2.Draw(im) - font = ImageDraw2.Font("white", FONT_PATH) - # Act - # Should not cause 'SystemError: returned NULL without setting an error' - draw.textsize("", font) - draw.textsize("\n", font) - draw.textsize("test\n", font) +def test_rectangle1(): + helper_rectangle(BBOX1) - @unittest.skipUnless(HAS_FREETYPE, "ImageFont not available") - def test_flush(self): - # Arrange - im = Image.new("RGB", (W, H)) - draw = ImageDraw2.Draw(im) - font = ImageDraw2.Font("white", FONT_PATH) - # Act - draw.text((5, 5), "ImageDraw2", font) - im2 = draw.flush() +def test_rectangle2(): + helper_rectangle(BBOX2) - # Assert - assert_image_equal(im, im2) + +def test_big_rectangle(): + # Test drawing a rectangle bigger than the image + # Arrange + im = Image.new("RGB", (W, H)) + bbox = [(-1, -1), (W + 1, H + 1)] + brush = ImageDraw2.Brush("orange") + draw = ImageDraw2.Draw(im) + expected = "Tests/images/imagedraw_big_rectangle.png" + + # Act + draw.rectangle(bbox, brush) + + # Assert + assert_image_similar(im, Image.open(expected), 1) + + +@skip_unless_feature("freetype2") +def test_text(): + # Arrange + im = Image.new("RGB", (W, H)) + draw = ImageDraw2.Draw(im) + font = ImageDraw2.Font("white", FONT_PATH) + expected = "Tests/images/imagedraw2_text.png" + + # Act + draw.text((5, 5), "ImageDraw2", font) + + # Assert + assert_image_similar(im, Image.open(expected), 13) + + +@skip_unless_feature("freetype2") +def test_textsize(): + # Arrange + im = Image.new("RGB", (W, H)) + draw = ImageDraw2.Draw(im) + font = ImageDraw2.Font("white", FONT_PATH) + + # Act + size = draw.textsize("ImageDraw2", font) + + # Assert + assert size[1] == 12 + + +@skip_unless_feature("freetype2") +def test_textsize_empty_string(): + # Arrange + im = Image.new("RGB", (W, H)) + draw = ImageDraw2.Draw(im) + font = ImageDraw2.Font("white", FONT_PATH) + + # Act + # Should not cause 'SystemError: returned NULL without setting an error' + draw.textsize("", font) + draw.textsize("\n", font) + draw.textsize("test\n", font) + + +@skip_unless_feature("freetype2") +def test_flush(): + # Arrange + im = Image.new("RGB", (W, H)) + draw = ImageDraw2.Draw(im) + font = ImageDraw2.Font("white", FONT_PATH) + + # Act + draw.text((5, 5), "ImageDraw2", font) + im2 = draw.flush() + + # Assert + assert_image_equal(im, im2) diff --git a/Tests/test_imageenhance.py b/Tests/test_imageenhance.py index 9524d2fcc..8bc94401e 100644 --- a/Tests/test_imageenhance.py +++ b/Tests/test_imageenhance.py @@ -1,54 +1,55 @@ from PIL import Image, ImageEnhance -from .helper import PillowTestCase, assert_image_equal, hopper +from .helper import assert_image_equal, hopper -class TestImageEnhance(PillowTestCase): - def test_sanity(self): +def test_sanity(): + # FIXME: assert_image + # Implicit asserts no exception: + ImageEnhance.Color(hopper()).enhance(0.5) + ImageEnhance.Contrast(hopper()).enhance(0.5) + ImageEnhance.Brightness(hopper()).enhance(0.5) + ImageEnhance.Sharpness(hopper()).enhance(0.5) - # FIXME: assert_image - # Implicit asserts no exception: - ImageEnhance.Color(hopper()).enhance(0.5) - ImageEnhance.Contrast(hopper()).enhance(0.5) - ImageEnhance.Brightness(hopper()).enhance(0.5) - ImageEnhance.Sharpness(hopper()).enhance(0.5) - def test_crash(self): +def test_crash(): + # crashes on small images + im = Image.new("RGB", (1, 1)) + ImageEnhance.Sharpness(im).enhance(0.5) - # crashes on small images - im = Image.new("RGB", (1, 1)) - ImageEnhance.Sharpness(im).enhance(0.5) - def _half_transparent_image(self): - # returns an image, half transparent, half solid - im = hopper("RGB") +def _half_transparent_image(): + # returns an image, half transparent, half solid + im = hopper("RGB") - transparent = Image.new("L", im.size, 0) - solid = Image.new("L", (im.size[0] // 2, im.size[1]), 255) - transparent.paste(solid, (0, 0)) - im.putalpha(transparent) + transparent = Image.new("L", im.size, 0) + solid = Image.new("L", (im.size[0] // 2, im.size[1]), 255) + transparent.paste(solid, (0, 0)) + im.putalpha(transparent) - return im + return im - def _check_alpha(self, im, original, op, amount): - self.assertEqual(im.getbands(), original.getbands()) - assert_image_equal( - im.getchannel("A"), - original.getchannel("A"), - "Diff on {}: {}".format(op, amount), - ) - def test_alpha(self): - # Issue https://github.com/python-pillow/Pillow/issues/899 - # Is alpha preserved through image enhancement? +def _check_alpha(im, original, op, amount): + assert im.getbands() == original.getbands() + assert_image_equal( + im.getchannel("A"), + original.getchannel("A"), + f"Diff on {op}: {amount}", + ) - original = self._half_transparent_image() - for op in ["Color", "Brightness", "Contrast", "Sharpness"]: - for amount in [0, 0.5, 1.0]: - self._check_alpha( - getattr(ImageEnhance, op)(original).enhance(amount), - original, - op, - amount, - ) +def test_alpha(): + # Issue https://github.com/python-pillow/Pillow/issues/899 + # Is alpha preserved through image enhancement? + + original = _half_transparent_image() + + for op in ["Color", "Brightness", "Contrast", "Sharpness"]: + for amount in [0, 0.5, 1.0]: + _check_alpha( + getattr(ImageEnhance, op)(original).enhance(amount), + original, + op, + amount, + ) diff --git a/Tests/test_imagefile.py b/Tests/test_imagefile.py index 13fe3e192..b4107e8e3 100644 --- a/Tests/test_imagefile.py +++ b/Tests/test_imagefile.py @@ -1,34 +1,25 @@ -import unittest from io import BytesIO -from PIL import EpsImagePlugin, Image, ImageFile +import pytest + +from PIL import EpsImagePlugin, Image, ImageFile, features from .helper import ( - PillowTestCase, assert_image, assert_image_equal, assert_image_similar, fromstring, hopper, + skip_unless_feature, tostring, ) -try: - from PIL import _webp - - HAVE_WEBP = True -except ImportError: - HAVE_WEBP = False - - -codecs = dir(Image.core) - # save original block sizes MAXBLOCK = ImageFile.MAXBLOCK SAFEBLOCK = ImageFile.SAFEBLOCK -class TestImageFile(PillowTestCase): +class TestImageFile: def test_parser(self): def roundtrip(format): @@ -53,7 +44,7 @@ class TestImageFile(PillowTestCase): assert_image_similar(im1.convert("P"), im2, 1) assert_image_equal(*roundtrip("IM")) assert_image_equal(*roundtrip("MSP")) - if "zip_encoder" in codecs: + if features.check("zlib"): try: # force multiple blocks in PNG driver ImageFile.MAXBLOCK = 8192 @@ -77,23 +68,22 @@ class TestImageFile(PillowTestCase): # EPS comes back in RGB: assert_image_similar(im1, im2.convert("L"), 20) - if "jpeg_encoder" in codecs: + if features.check("jpg"): im1, im2 = roundtrip("JPEG") # lossy compression assert_image(im1, im2.mode, im2.size) - self.assertRaises(IOError, roundtrip, "PDF") + with pytest.raises(OSError): + roundtrip("PDF") def test_ico(self): with open("Tests/images/python.ico", "rb") as f: data = f.read() with ImageFile.Parser() as p: p.feed(data) - self.assertEqual((48, 48), p.image.size) + assert (48, 48) == p.image.size + @skip_unless_feature("zlib") def test_safeblock(self): - if "zip_encoder" not in codecs: - self.skipTest("PNG (zlib) encoder not available") - im1 = hopper() try: @@ -105,10 +95,17 @@ class TestImageFile(PillowTestCase): assert_image_equal(im1, im2) def test_raise_ioerror(self): - self.assertRaises(IOError, ImageFile.raise_ioerror, 1) + with pytest.raises(IOError): + with pytest.warns(DeprecationWarning) as record: + ImageFile.raise_ioerror(1) + assert len(record) == 1 + + def test_raise_oserror(self): + with pytest.raises(OSError): + ImageFile.raise_oserror(1) def test_raise_typeerror(self): - with self.assertRaises(TypeError): + with pytest.raises(TypeError): parser = ImageFile.Parser() parser.feed(1) @@ -117,25 +114,21 @@ class TestImageFile(PillowTestCase): input = f.read() p = ImageFile.Parser() p.feed(input) - with self.assertRaises(IOError): + with pytest.raises(OSError): p.close() + @skip_unless_feature("zlib") def test_truncated_with_errors(self): - if "zip_encoder" not in codecs: - self.skipTest("PNG (zlib) encoder not available") - with Image.open("Tests/images/truncated_image.png") as im: - with self.assertRaises(IOError): + with pytest.raises(OSError): im.load() # Test that the error is raised if loaded a second time - with self.assertRaises(IOError): + with pytest.raises(OSError): im.load() + @skip_unless_feature("zlib") def test_truncated_without_errors(self): - if "zip_encoder" not in codecs: - self.skipTest("PNG (zlib) encoder not available") - with Image.open("Tests/images/truncated_image.png") as im: ImageFile.LOAD_TRUNCATED_IMAGES = True try: @@ -143,18 +136,14 @@ class TestImageFile(PillowTestCase): finally: ImageFile.LOAD_TRUNCATED_IMAGES = False + @skip_unless_feature("zlib") def test_broken_datastream_with_errors(self): - if "zip_encoder" not in codecs: - self.skipTest("PNG (zlib) encoder not available") - with Image.open("Tests/images/broken_data_stream.png") as im: - with self.assertRaises(IOError): + with pytest.raises(OSError): im.load() + @skip_unless_feature("zlib") def test_broken_datastream_without_errors(self): - if "zip_encoder" not in codecs: - self.skipTest("PNG (zlib) encoder not available") - with Image.open("Tests/images/broken_data_stream.png") as im: ImageFile.LOAD_TRUNCATED_IMAGES = True try: @@ -180,7 +169,7 @@ class MockImageFile(ImageFile.ImageFile): self.tile = [("MOCK", (xoff, yoff, xoff + xsize, yoff + ysize), 32, None)] -class TestPyDecoder(PillowTestCase): +class TestPyDecoder: def get_decoder(self): decoder = MockPyDecoder(None) @@ -199,12 +188,13 @@ class TestPyDecoder(PillowTestCase): im.load() - self.assertEqual(d.state.xoff, xoff) - self.assertEqual(d.state.yoff, yoff) - self.assertEqual(d.state.xsize, xsize) - self.assertEqual(d.state.ysize, ysize) + assert d.state.xoff == xoff + assert d.state.yoff == yoff + assert d.state.xsize == xsize + assert d.state.ysize == ysize - self.assertRaises(ValueError, d.set_as_raw, b"\x00") + with pytest.raises(ValueError): + d.set_as_raw(b"\x00") def test_extents_none(self): buf = BytesIO(b"\x00" * 255) @@ -215,10 +205,10 @@ class TestPyDecoder(PillowTestCase): im.load() - self.assertEqual(d.state.xoff, 0) - self.assertEqual(d.state.yoff, 0) - self.assertEqual(d.state.xsize, 200) - self.assertEqual(d.state.ysize, 200) + assert d.state.xoff == 0 + assert d.state.yoff == 0 + assert d.state.xsize == 200 + assert d.state.ysize == 200 def test_negsize(self): buf = BytesIO(b"\x00" * 255) @@ -227,10 +217,12 @@ class TestPyDecoder(PillowTestCase): im.tile = [("MOCK", (xoff, yoff, -10, yoff + ysize), 32, None)] self.get_decoder() - self.assertRaises(ValueError, im.load) + with pytest.raises(ValueError): + im.load() im.tile = [("MOCK", (xoff, yoff, xoff + xsize, -10), 32, None)] - self.assertRaises(ValueError, im.load) + with pytest.raises(ValueError): + im.load() def test_oversize(self): buf = BytesIO(b"\x00" * 255) @@ -239,104 +231,21 @@ class TestPyDecoder(PillowTestCase): im.tile = [("MOCK", (xoff, yoff, xoff + xsize + 100, yoff + ysize), 32, None)] self.get_decoder() - self.assertRaises(ValueError, im.load) + with pytest.raises(ValueError): + im.load() im.tile = [("MOCK", (xoff, yoff, xoff + xsize, yoff + ysize + 100), 32, None)] - self.assertRaises(ValueError, im.load) + with pytest.raises(ValueError): + im.load() def test_no_format(self): buf = BytesIO(b"\x00" * 255) im = MockImageFile(buf) - self.assertIsNone(im.format) - self.assertIsNone(im.get_format_mimetype()) + assert im.format is None + assert im.get_format_mimetype() is None - def test_exif_jpeg(self): - with Image.open("Tests/images/exif-72dpi-int.jpg") as im: # Little endian - exif = im.getexif() - self.assertNotIn(258, exif) - self.assertIn(40960, exif) - self.assertEqual(exif[40963], 450) - self.assertEqual(exif[11], "gThumb 3.0.1") - - out = self.tempfile("temp.jpg") - exif[258] = 8 - del exif[40960] - exif[40963] = 455 - exif[11] = "Pillow test" - im.save(out, exif=exif) - with Image.open(out) as reloaded: - reloaded_exif = reloaded.getexif() - self.assertEqual(reloaded_exif[258], 8) - self.assertNotIn(40960, exif) - self.assertEqual(reloaded_exif[40963], 455) - self.assertEqual(exif[11], "Pillow test") - - with Image.open("Tests/images/no-dpi-in-exif.jpg") as im: # Big endian - exif = im.getexif() - self.assertNotIn(258, exif) - self.assertIn(40962, exif) - self.assertEqual(exif[40963], 200) - self.assertEqual(exif[305], "Adobe Photoshop CC 2017 (Macintosh)") - - out = self.tempfile("temp.jpg") - exif[258] = 8 - del exif[34665] - exif[40963] = 455 - exif[305] = "Pillow test" - im.save(out, exif=exif) - with Image.open(out) as reloaded: - reloaded_exif = reloaded.getexif() - self.assertEqual(reloaded_exif[258], 8) - self.assertNotIn(40960, exif) - self.assertEqual(reloaded_exif[40963], 455) - self.assertEqual(exif[305], "Pillow test") - - @unittest.skipIf( - not HAVE_WEBP or not _webp.HAVE_WEBPANIM, - "WebP support not installed with animation", - ) - def test_exif_webp(self): - with Image.open("Tests/images/hopper.webp") as im: - exif = im.getexif() - self.assertEqual(exif, {}) - - out = self.tempfile("temp.webp") - exif[258] = 8 - exif[40963] = 455 - exif[305] = "Pillow test" - - def check_exif(): - with Image.open(out) as reloaded: - reloaded_exif = reloaded.getexif() - self.assertEqual(reloaded_exif[258], 8) - self.assertEqual(reloaded_exif[40963], 455) - self.assertEqual(exif[305], "Pillow test") - - im.save(out, exif=exif) - check_exif() - im.save(out, exif=exif, save_all=True) - check_exif() - - def test_exif_png(self): - with Image.open("Tests/images/exif.png") as im: - exif = im.getexif() - self.assertEqual(exif, {274: 1}) - - out = self.tempfile("temp.png") - exif[258] = 8 - del exif[274] - exif[40963] = 455 - exif[305] = "Pillow test" - im.save(out, exif=exif) - - with Image.open(out) as reloaded: - reloaded_exif = reloaded.getexif() - self.assertEqual(reloaded_exif, {258: 8, 40963: 455, 305: "Pillow test"}) - - def test_exif_interop(self): - with Image.open("Tests/images/flower.jpg") as im: - exif = im.getexif() - self.assertEqual( - exif.get_ifd(0xA005), {1: "R98", 2: b"0100", 4097: 2272, 4098: 1704} - ) + def test_oserror(self): + im = Image.new("RGB", (1, 1)) + with pytest.raises(OSError): + im.save(BytesIO(), "JPEG2000") diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index 12e6ea55a..0c219fed1 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -1,21 +1,23 @@ import copy -import distutils.version import os import re import shutil import sys -import unittest from io import BytesIO +import pytest +from packaging.version import parse as parse_version + from PIL import Image, ImageDraw, ImageFont, features from .helper import ( - PillowTestCase, assert_image_equal, + assert_image_equal_tofile, assert_image_similar, assert_image_similar_tofile, - is_pypy, is_win32, + skip_unless_feature, + skip_unless_feature_version, ) FONT_PATH = "Tests/fonts/FreeMono.ttf" @@ -23,93 +25,36 @@ FONT_SIZE = 20 TEST_TEXT = "hey you\nyou are awesome\nthis looks awkward" -HAS_FREETYPE = features.check("freetype2") -HAS_RAQM = features.check("raqm") + +pytestmark = skip_unless_feature("freetype2") -class SimplePatcher: - def __init__(self, parent_obj, attr_name, value): - self._parent_obj = parent_obj - self._attr_name = attr_name - self._saved = None - self._is_saved = False - self._value = value - - def __enter__(self): - # Patch the attr on the object - if hasattr(self._parent_obj, self._attr_name): - self._saved = getattr(self._parent_obj, self._attr_name) - setattr(self._parent_obj, self._attr_name, self._value) - self._is_saved = True - else: - setattr(self._parent_obj, self._attr_name, self._value) - self._is_saved = False - - def __exit__(self, type, value, traceback): - # Restore the original value - if self._is_saved: - setattr(self._parent_obj, self._attr_name, self._saved) - else: - delattr(self._parent_obj, self._attr_name) - - -@unittest.skipUnless(HAS_FREETYPE, "ImageFont not available") -class TestImageFont(PillowTestCase): +class TestImageFont: LAYOUT_ENGINE = ImageFont.LAYOUT_BASIC - # Freetype has different metrics depending on the version. - # (and, other things, but first things first) - METRICS = { - (">=2.3", "<2.4"): {"multiline": 30, "textsize": 12, "getters": (13, 16)}, - (">=2.7",): {"multiline": 6.2, "textsize": 2.5, "getters": (12, 16)}, - "Default": {"multiline": 0.5, "textsize": 0.5, "getters": (12, 16)}, - } - - def setUp(self): - freetype = distutils.version.StrictVersion(ImageFont.core.freetype2_version) - - self.metrics = self.METRICS["Default"] - for conditions, metrics in self.METRICS.items(): - if not isinstance(conditions, tuple): - continue - - for condition in conditions: - version = re.sub("[<=>]", "", condition) - if (condition.startswith(">=") and freetype >= version) or ( - condition.startswith("<") and freetype < version - ): - # Condition was met - continue - - # Condition failed - break - else: - # All conditions were met - self.metrics = metrics - def get_font(self): return ImageFont.truetype( FONT_PATH, FONT_SIZE, layout_engine=self.LAYOUT_ENGINE ) def test_sanity(self): - self.assertRegex(ImageFont.core.freetype2_version, r"\d+\.\d+\.\d+$") + assert re.search(r"\d+\.\d+\.\d+$", features.version_module("freetype2")) def test_font_properties(self): ttf = self.get_font() - self.assertEqual(ttf.path, FONT_PATH) - self.assertEqual(ttf.size, FONT_SIZE) + assert ttf.path == FONT_PATH + assert ttf.size == FONT_SIZE ttf_copy = ttf.font_variant() - self.assertEqual(ttf_copy.path, FONT_PATH) - self.assertEqual(ttf_copy.size, FONT_SIZE) + assert ttf_copy.path == FONT_PATH + assert ttf_copy.size == FONT_SIZE ttf_copy = ttf.font_variant(size=FONT_SIZE + 1) - self.assertEqual(ttf_copy.size, FONT_SIZE + 1) + assert ttf_copy.size == FONT_SIZE + 1 second_font_path = "Tests/fonts/DejaVuSans.ttf" ttf_copy = ttf.font_variant(font=second_font_path) - self.assertEqual(ttf_copy.path, second_font_path) + assert ttf_copy.path == second_font_path def test_font_with_name(self): self.get_font() @@ -128,18 +73,19 @@ class TestImageFont(PillowTestCase): # Usage note: making two fonts from the same buffer fails. # shared_bytes = self._font_as_bytes() # self._render(shared_bytes) - # self.assertRaises(Exception, _render, shared_bytes) + # with pytest.raises(Exception): + # _render(shared_bytes) def test_font_with_open_file(self): with open(FONT_PATH, "rb") as f: self._render(f) - def test_non_unicode_path(self): + def test_non_ascii_path(self, tmp_path): + tempfile = str(tmp_path / ("temp_" + chr(128) + ".ttf")) try: - tempfile = self.tempfile("temp_" + chr(128) + ".ttf") + shutil.copy(FONT_PATH, tempfile) except UnicodeEncodeError: - self.skipTest("Unicode path could not be created") - shutil.copy(FONT_PATH, tempfile) + pytest.skip("Non-ASCII path could not be created") ImageFont.truetype(tempfile, FONT_SIZE) @@ -154,7 +100,7 @@ class TestImageFont(PillowTestCase): finally: ImageFont.core.HAVE_RAQM = have_raqm - self.assertEqual(ttf.layout_engine, ImageFont.LAYOUT_BASIC) + assert ttf.layout_engine == ImageFont.LAYOUT_BASIC def _render(self, font): txt = "Hello World!" @@ -175,6 +121,18 @@ class TestImageFont(PillowTestCase): assert_image_equal(img_path, img_filelike) + def test_transparent_background(self): + im = Image.new(mode="RGBA", size=(300, 100)) + draw = ImageDraw.Draw(im) + ttf = self.get_font() + + txt = "Hello World!" + draw.text((10, 10), txt, font=ttf) + + target = "Tests/images/transparent_background_text.png" + with Image.open(target) as target_img: + assert_image_similar(im, target_img, 4.09) + def test_textsize_equal(self): im = Image.new(mode="RGB", size=(300, 100)) draw = ImageDraw.Draw(im) @@ -189,7 +147,38 @@ class TestImageFont(PillowTestCase): with Image.open(target) as target_img: # Epsilon ~.5 fails with FreeType 2.7 - assert_image_similar(im, target_img, self.metrics["textsize"]) + assert_image_similar(im, target_img, 2.5) + + @pytest.mark.parametrize( + "text, mode, font, size, length_basic, length_raqm", + ( + # basic test + ("text", "L", "FreeMono.ttf", 15, 36, 36), + ("text", "1", "FreeMono.ttf", 15, 36, 36), + # issue 4177 + ("rrr", "L", "DejaVuSans.ttf", 18, 21, 22.21875), + ("rrr", "1", "DejaVuSans.ttf", 18, 24, 22.21875), + # test 'l' not including extra margin + # using exact value 2047 / 64 for raqm, checked with debugger + ("ill", "L", "OpenSansCondensed-LightItalic.ttf", 63, 33, 31.984375), + ("ill", "1", "OpenSansCondensed-LightItalic.ttf", 63, 33, 31.984375), + ), + ) + def test_getlength(self, text, mode, font, size, length_basic, length_raqm): + f = ImageFont.truetype( + "Tests/fonts/" + font, size, layout_engine=self.LAYOUT_ENGINE + ) + + im = Image.new(mode, (1, 1), 0) + d = ImageDraw.Draw(im) + + if self.LAYOUT_ENGINE == ImageFont.LAYOUT_BASIC: + length = d.textlength(text, f) + assert length == length_basic + else: + # disable kerning, kerning metrics changed + length = d.textlength(text, f, features=["-kern"]) + assert length == length_raqm def test_render_multiline(self): im = Image.new(mode="RGB", size=(300, 100)) @@ -208,7 +197,7 @@ class TestImageFont(PillowTestCase): # some versions of freetype have different horizontal spacing. # setting a tight epsilon, I'm showing the original test failure # at epsilon = ~38. - assert_image_similar(im, target_img, self.metrics["multiline"]) + assert_image_similar(im, target_img, 6.2) def test_render_multiline_text(self): ttf = self.get_font() @@ -223,7 +212,7 @@ class TestImageFont(PillowTestCase): with Image.open(target) as target_img: # Epsilon ~.5 fails with FreeType 2.7 - assert_image_similar(im, target_img, self.metrics["multiline"]) + assert_image_similar(im, target_img, 6.2) # Test that text() can pass on additional arguments # to multiline_text() @@ -242,7 +231,7 @@ class TestImageFont(PillowTestCase): with Image.open(target) as target_img: # Epsilon ~.5 fails with FreeType 2.7 - assert_image_similar(im, target_img, self.metrics["multiline"]) + assert_image_similar(im, target_img, 6.2) def test_unknown_align(self): im = Image.new(mode="RGB", size=(300, 100)) @@ -250,14 +239,8 @@ class TestImageFont(PillowTestCase): ttf = self.get_font() # Act/Assert - self.assertRaises( - ValueError, - draw.multiline_text, - (0, 0), - TEST_TEXT, - font=ttf, - align="unknown", - ) + with pytest.raises(ValueError): + draw.multiline_text((0, 0), TEST_TEXT, font=ttf, align="unknown") def test_draw_align(self): im = Image.new("RGB", (300, 100), "white") @@ -272,14 +255,13 @@ class TestImageFont(PillowTestCase): draw = ImageDraw.Draw(im) # Test that textsize() correctly connects to multiline_textsize() - self.assertEqual( - draw.textsize(TEST_TEXT, font=ttf), - draw.multiline_textsize(TEST_TEXT, font=ttf), + assert draw.textsize(TEST_TEXT, font=ttf) == draw.multiline_textsize( + TEST_TEXT, font=ttf ) # Test that multiline_textsize corresponds to ImageFont.textsize() # for single line text - self.assertEqual(ttf.getsize("A"), draw.multiline_textsize("A", font=ttf)) + assert ttf.getsize("A") == draw.multiline_textsize("A", font=ttf) # Test that textsize() can pass on additional arguments # to multiline_textsize() @@ -291,9 +273,9 @@ class TestImageFont(PillowTestCase): im = Image.new(mode="RGB", size=(300, 100)) draw = ImageDraw.Draw(im) - self.assertEqual( - draw.textsize("longest line", font=ttf)[0], - draw.multiline_textsize("longest line\nline", font=ttf)[0], + assert ( + draw.textsize("longest line", font=ttf)[0] + == draw.multiline_textsize("longest line\nline", font=ttf)[0] ) def test_multiline_spacing(self): @@ -307,7 +289,7 @@ class TestImageFont(PillowTestCase): with Image.open(target) as target_img: # Epsilon ~.5 fails with FreeType 2.7 - assert_image_similar(im, target_img, self.metrics["multiline"]) + assert_image_similar(im, target_img, 6.2) def test_rotated_transposed_font(self): img_grey = Image.new("L", (100, 100)) @@ -327,8 +309,8 @@ class TestImageFont(PillowTestCase): box_size_b = draw.textsize(word) # Check (w,h) of box a is (h,w) of box b - self.assertEqual(box_size_a[0], box_size_b[1]) - self.assertEqual(box_size_a[1], box_size_b[0]) + assert box_size_a[0] == box_size_b[1] + assert box_size_a[1] == box_size_b[0] def test_unrotated_transposed_font(self): img_grey = Image.new("L", (100, 100)) @@ -348,7 +330,7 @@ class TestImageFont(PillowTestCase): box_size_b = draw.textsize(word) # Check boxes a and b are same size - self.assertEqual(box_size_a, box_size_b) + assert box_size_a == box_size_b def test_rotated_transposed_font_get_mask(self): # Arrange @@ -361,7 +343,7 @@ class TestImageFont(PillowTestCase): mask = transposed_font.getmask(text) # Assert - self.assertEqual(mask.size, (13, 108)) + assert mask.size == (13, 108) def test_unrotated_transposed_font_get_mask(self): # Arrange @@ -374,7 +356,7 @@ class TestImageFont(PillowTestCase): mask = transposed_font.getmask(text) # Assert - self.assertEqual(mask.size, (108, 13)) + assert mask.size == (108, 13) def test_free_type_font_get_name(self): # Arrange @@ -384,7 +366,7 @@ class TestImageFont(PillowTestCase): name = font.getname() # Assert - self.assertEqual(("FreeMono", "Regular"), name) + assert ("FreeMono", "Regular") == name def test_free_type_font_get_metrics(self): # Arrange @@ -394,9 +376,9 @@ class TestImageFont(PillowTestCase): ascent, descent = font.getmetrics() # Assert - self.assertIsInstance(ascent, int) - self.assertIsInstance(descent, int) - self.assertEqual((ascent, descent), (16, 4)) # too exact check? + assert isinstance(ascent, int) + assert isinstance(descent, int) + assert (ascent, descent) == (16, 4) # too exact check? def test_free_type_font_get_offset(self): # Arrange @@ -407,7 +389,7 @@ class TestImageFont(PillowTestCase): offset = font.getoffset(text) # Assert - self.assertEqual(offset, (0, 3)) + assert offset == (0, 3) def test_free_type_font_get_mask(self): # Arrange @@ -418,19 +400,22 @@ class TestImageFont(PillowTestCase): mask = font.getmask(text) # Assert - self.assertEqual(mask.size, (108, 13)) + assert mask.size == (108, 13) def test_load_path_not_found(self): # Arrange filename = "somefilenamethatdoesntexist.ttf" # Act/Assert - self.assertRaises(IOError, ImageFont.load_path, filename) - self.assertRaises(IOError, ImageFont.truetype, filename) + with pytest.raises(OSError): + ImageFont.load_path(filename) + with pytest.raises(OSError): + ImageFont.truetype(filename) def test_load_non_font_bytes(self): with open("Tests/images/hopper.jpg", "rb") as f: - self.assertRaises(IOError, ImageFont.truetype, f) + with pytest.raises(OSError): + ImageFont.truetype(f) def test_default_font(self): # Arrange @@ -452,7 +437,7 @@ class TestImageFont(PillowTestCase): # issue #2614 font = self.get_font() # should not crash. - self.assertEqual((0, 0), font.getsize("")) + assert (0, 0) == font.getsize("") def test_render_empty(self): # issue 2666 @@ -468,10 +453,9 @@ class TestImageFont(PillowTestCase): # should not segfault, should return UnicodeDecodeError # issue #2826 font = ImageFont.load_default() - with self.assertRaises(UnicodeEncodeError): + with pytest.raises(UnicodeEncodeError): font.getsize("’") - @unittest.skipIf(is_pypy(), "failing on PyPy") def test_unicode_extended(self): # issue #3777 text = "A\u278A\U0001F12B" @@ -486,12 +470,14 @@ class TestImageFont(PillowTestCase): d = ImageDraw.Draw(img) d.text((10, 10), text, font=ttf) - assert_image_similar_tofile(img, target, self.metrics["multiline"]) + # fails with 14.7 + assert_image_similar_tofile(img, target, 6.2) - def _test_fake_loading_font(self, path_to_fake, fontname): + def _test_fake_loading_font(self, monkeypatch, path_to_fake, fontname): # Make a copy of FreeTypeFont so we can patch the original free_type_font = copy.deepcopy(ImageFont.FreeTypeFont) - with SimplePatcher(ImageFont, "_FreeTypeFont", free_type_font): + with monkeypatch.context() as m: + m.setattr(ImageFont, "_FreeTypeFont", free_type_font, raising=False) def loadable_font(filepath, size, index, encoding, *args, **kwargs): if filepath == path_to_fake: @@ -502,112 +488,108 @@ class TestImageFont(PillowTestCase): filepath, size, index, encoding, *args, **kwargs ) - with SimplePatcher(ImageFont, "FreeTypeFont", loadable_font): - font = ImageFont.truetype(fontname) - # Make sure it's loaded - name = font.getname() - self.assertEqual(("FreeMono", "Regular"), name) + m.setattr(ImageFont, "FreeTypeFont", loadable_font) + font = ImageFont.truetype(fontname) + # Make sure it's loaded + name = font.getname() + assert ("FreeMono", "Regular") == name - @unittest.skipIf(is_win32(), "requires Unix or macOS") - def test_find_linux_font(self): + @pytest.mark.skipif(is_win32(), reason="requires Unix or macOS") + def test_find_linux_font(self, monkeypatch): # A lot of mocking here - this is more for hitting code and # catching syntax like errors font_directory = "/usr/local/share/fonts" - with SimplePatcher(sys, "platform", "linux"): - patched_env = copy.deepcopy(os.environ) - patched_env["XDG_DATA_DIRS"] = "/usr/share/:/usr/local/share/" - with SimplePatcher(os, "environ", patched_env): + monkeypatch.setattr(sys, "platform", "linux") + monkeypatch.setenv("XDG_DATA_DIRS", "/usr/share/:/usr/local/share/") - def fake_walker(path): - if path == font_directory: - return [ - ( - path, - [], - [ - "Arial.ttf", - "Single.otf", - "Duplicate.otf", - "Duplicate.ttf", - ], - ) - ] - return [(path, [], ["some_random_font.ttf"])] - - with SimplePatcher(os, "walk", fake_walker): - # Test that the font loads both with and without the - # extension - self._test_fake_loading_font( - font_directory + "/Arial.ttf", "Arial.ttf" + def fake_walker(path): + if path == font_directory: + return [ + ( + path, + [], + ["Arial.ttf", "Single.otf", "Duplicate.otf", "Duplicate.ttf"], ) - self._test_fake_loading_font(font_directory + "/Arial.ttf", "Arial") + ] + return [(path, [], ["some_random_font.ttf"])] - # Test that non-ttf fonts can be found without the - # extension - self._test_fake_loading_font( - font_directory + "/Single.otf", "Single" - ) + monkeypatch.setattr(os, "walk", fake_walker) + # Test that the font loads both with and without the + # extension + self._test_fake_loading_font( + monkeypatch, font_directory + "/Arial.ttf", "Arial.ttf" + ) + self._test_fake_loading_font( + monkeypatch, font_directory + "/Arial.ttf", "Arial" + ) - # Test that ttf fonts are preferred if the extension is - # not specified - self._test_fake_loading_font( - font_directory + "/Duplicate.ttf", "Duplicate" - ) + # Test that non-ttf fonts can be found without the + # extension + self._test_fake_loading_font( + monkeypatch, font_directory + "/Single.otf", "Single" + ) - @unittest.skipIf(is_win32(), "requires Unix or macOS") - def test_find_macos_font(self): + # Test that ttf fonts are preferred if the extension is + # not specified + self._test_fake_loading_font( + monkeypatch, font_directory + "/Duplicate.ttf", "Duplicate" + ) + + @pytest.mark.skipif(is_win32(), reason="requires Unix or macOS") + def test_find_macos_font(self, monkeypatch): # Like the linux test, more cover hitting code rather than testing # correctness. font_directory = "/System/Library/Fonts" - with SimplePatcher(sys, "platform", "darwin"): + monkeypatch.setattr(sys, "platform", "darwin") - def fake_walker(path): - if path == font_directory: - return [ - ( - path, - [], - [ - "Arial.ttf", - "Single.otf", - "Duplicate.otf", - "Duplicate.ttf", - ], - ) - ] - return [(path, [], ["some_random_font.ttf"])] + def fake_walker(path): + if path == font_directory: + return [ + ( + path, + [], + ["Arial.ttf", "Single.otf", "Duplicate.otf", "Duplicate.ttf"], + ) + ] + return [(path, [], ["some_random_font.ttf"])] - with SimplePatcher(os, "walk", fake_walker): - self._test_fake_loading_font(font_directory + "/Arial.ttf", "Arial.ttf") - self._test_fake_loading_font(font_directory + "/Arial.ttf", "Arial") - self._test_fake_loading_font(font_directory + "/Single.otf", "Single") - self._test_fake_loading_font( - font_directory + "/Duplicate.ttf", "Duplicate" - ) + monkeypatch.setattr(os, "walk", fake_walker) + self._test_fake_loading_font( + monkeypatch, font_directory + "/Arial.ttf", "Arial.ttf" + ) + self._test_fake_loading_font( + monkeypatch, font_directory + "/Arial.ttf", "Arial" + ) + self._test_fake_loading_font( + monkeypatch, font_directory + "/Single.otf", "Single" + ) + self._test_fake_loading_font( + monkeypatch, font_directory + "/Duplicate.ttf", "Duplicate" + ) def test_imagefont_getters(self): # Arrange t = self.get_font() # Act / Assert - self.assertEqual(t.getmetrics(), (16, 4)) - self.assertEqual(t.font.ascent, 16) - self.assertEqual(t.font.descent, 4) - self.assertEqual(t.font.height, 20) - self.assertEqual(t.font.x_ppem, 20) - self.assertEqual(t.font.y_ppem, 20) - self.assertEqual(t.font.glyphs, 4177) - self.assertEqual(t.getsize("A"), (12, 16)) - self.assertEqual(t.getsize("AB"), (24, 16)) - self.assertEqual(t.getsize("M"), self.metrics["getters"]) - self.assertEqual(t.getsize("y"), (12, 20)) - self.assertEqual(t.getsize("a"), (12, 16)) - self.assertEqual(t.getsize_multiline("A"), (12, 16)) - self.assertEqual(t.getsize_multiline("AB"), (24, 16)) - self.assertEqual(t.getsize_multiline("a"), (12, 16)) - self.assertEqual(t.getsize_multiline("ABC\n"), (36, 36)) - self.assertEqual(t.getsize_multiline("ABC\nA"), (36, 36)) - self.assertEqual(t.getsize_multiline("ABC\nAaaa"), (48, 36)) + assert t.getmetrics() == (16, 4) + assert t.font.ascent == 16 + assert t.font.descent == 4 + assert t.font.height == 20 + assert t.font.x_ppem == 20 + assert t.font.y_ppem == 20 + assert t.font.glyphs == 4177 + assert t.getsize("A") == (12, 16) + assert t.getsize("AB") == (24, 16) + assert t.getsize("M") == (12, 16) + assert t.getsize("y") == (12, 20) + assert t.getsize("a") == (12, 16) + assert t.getsize_multiline("A") == (12, 16) + assert t.getsize_multiline("AB") == (24, 16) + assert t.getsize_multiline("a") == (12, 16) + assert t.getsize_multiline("ABC\n") == (36, 36) + assert t.getsize_multiline("ABC\nA") == (36, 36) + assert t.getsize_multiline("ABC\nAaaa") == (48, 36) def test_getsize_stroke(self): # Arrange @@ -615,13 +597,13 @@ class TestImageFont(PillowTestCase): # Act / Assert for stroke_width in [0, 2]: - self.assertEqual( - t.getsize("A", stroke_width=stroke_width), - (12 + stroke_width * 2, 16 + stroke_width * 2), + assert t.getsize("A", stroke_width=stroke_width) == ( + 12 + stroke_width * 2, + 16 + stroke_width * 2, ) - self.assertEqual( - t.getsize_multiline("ABC\nAaaa", stroke_width=stroke_width), - (48 + stroke_width * 2, 36 + stroke_width * 4), + assert t.getsize_multiline("ABC\nAaaa", stroke_width=stroke_width) == ( + 48 + stroke_width * 2, + 36 + stroke_width * 4, ) def test_complex_font_settings(self): @@ -629,129 +611,372 @@ class TestImageFont(PillowTestCase): t = self.get_font() # Act / Assert if t.layout_engine == ImageFont.LAYOUT_BASIC: - self.assertRaises(KeyError, t.getmask, "абвг", direction="rtl") - self.assertRaises(KeyError, t.getmask, "абвг", features=["-kern"]) - self.assertRaises(KeyError, t.getmask, "абвг", language="sr") + with pytest.raises(KeyError): + t.getmask("абвг", direction="rtl") + with pytest.raises(KeyError): + t.getmask("абвг", features=["-kern"]) + with pytest.raises(KeyError): + t.getmask("абвг", language="sr") def test_variation_get(self): font = self.get_font() - freetype = distutils.version.StrictVersion(ImageFont.core.freetype2_version) - if freetype < "2.9.1": - self.assertRaises(NotImplementedError, font.get_variation_names) - self.assertRaises(NotImplementedError, font.get_variation_axes) + freetype = parse_version(features.version_module("freetype2")) + if freetype < parse_version("2.9.1"): + with pytest.raises(NotImplementedError): + font.get_variation_names() + with pytest.raises(NotImplementedError): + font.get_variation_axes() return - self.assertRaises(IOError, font.get_variation_names) - self.assertRaises(IOError, font.get_variation_axes) + with pytest.raises(OSError): + font.get_variation_names() + with pytest.raises(OSError): + font.get_variation_axes() font = ImageFont.truetype("Tests/fonts/AdobeVFPrototype.ttf") - self.assertEqual( - font.get_variation_names(), - [ - b"ExtraLight", - b"Light", - b"Regular", - b"Semibold", - b"Bold", - b"Black", - b"Black Medium Contrast", - b"Black High Contrast", - b"Default", - ], - ) - self.assertEqual( - font.get_variation_axes(), - [ - {"name": b"Weight", "minimum": 200, "maximum": 900, "default": 389}, - {"name": b"Contrast", "minimum": 0, "maximum": 100, "default": 0}, - ], - ) + assert font.get_variation_names(), [ + b"ExtraLight", + b"Light", + b"Regular", + b"Semibold", + b"Bold", + b"Black", + b"Black Medium Contrast", + b"Black High Contrast", + b"Default", + ] + assert font.get_variation_axes() == [ + {"name": b"Weight", "minimum": 200, "maximum": 900, "default": 389}, + {"name": b"Contrast", "minimum": 0, "maximum": 100, "default": 0}, + ] font = ImageFont.truetype("Tests/fonts/TINY5x3GX.ttf") - self.assertEqual( - font.get_variation_names(), - [ - b"20", - b"40", - b"60", - b"80", - b"100", - b"120", - b"140", - b"160", - b"180", - b"200", - b"220", - b"240", - b"260", - b"280", - b"300", - b"Regular", - ], - ) - self.assertEqual( - font.get_variation_axes(), - [{"name": b"Size", "minimum": 0, "maximum": 300, "default": 0}], - ) + assert font.get_variation_names() == [ + b"20", + b"40", + b"60", + b"80", + b"100", + b"120", + b"140", + b"160", + b"180", + b"200", + b"220", + b"240", + b"260", + b"280", + b"300", + b"Regular", + ] + assert font.get_variation_axes() == [ + {"name": b"Size", "minimum": 0, "maximum": 300, "default": 0} + ] + + def _check_text(self, font, path, epsilon): + im = Image.new("RGB", (100, 75), "white") + d = ImageDraw.Draw(im) + d.text((10, 10), "Text", font=font, fill="black") + + try: + with Image.open(path) as expected: + assert_image_similar(im, expected, epsilon) + except AssertionError: + if "_adobe" in path: + path = path.replace("_adobe", "_adobe_older_harfbuzz") + with Image.open(path) as expected: + assert_image_similar(im, expected, epsilon) + else: + raise def test_variation_set_by_name(self): font = self.get_font() - freetype = distutils.version.StrictVersion(ImageFont.core.freetype2_version) - if freetype < "2.9.1": - self.assertRaises(NotImplementedError, font.set_variation_by_name, "Bold") + freetype = parse_version(features.version_module("freetype2")) + if freetype < parse_version("2.9.1"): + with pytest.raises(NotImplementedError): + font.set_variation_by_name("Bold") return - self.assertRaises(IOError, font.set_variation_by_name, "Bold") - - def _check_text(font, path, epsilon): - im = Image.new("RGB", (100, 75), "white") - d = ImageDraw.Draw(im) - d.text((10, 10), "Text", font=font, fill="black") - - with Image.open(path) as expected: - assert_image_similar(im, expected, epsilon) + with pytest.raises(OSError): + font.set_variation_by_name("Bold") font = ImageFont.truetype("Tests/fonts/AdobeVFPrototype.ttf", 36) - _check_text(font, "Tests/images/variation_adobe.png", 11) + self._check_text(font, "Tests/images/variation_adobe.png", 11) for name in ["Bold", b"Bold"]: font.set_variation_by_name(name) - _check_text(font, "Tests/images/variation_adobe_name.png", 11) + self._check_text(font, "Tests/images/variation_adobe_name.png", 11) font = ImageFont.truetype("Tests/fonts/TINY5x3GX.ttf", 36) - _check_text(font, "Tests/images/variation_tiny.png", 40) + self._check_text(font, "Tests/images/variation_tiny.png", 40) for name in ["200", b"200"]: font.set_variation_by_name(name) - _check_text(font, "Tests/images/variation_tiny_name.png", 40) + self._check_text(font, "Tests/images/variation_tiny_name.png", 40) def test_variation_set_by_axes(self): font = self.get_font() - freetype = distutils.version.StrictVersion(ImageFont.core.freetype2_version) - if freetype < "2.9.1": - self.assertRaises(NotImplementedError, font.set_variation_by_axes, [100]) + freetype = parse_version(features.version_module("freetype2")) + if freetype < parse_version("2.9.1"): + with pytest.raises(NotImplementedError): + font.set_variation_by_axes([100]) return - self.assertRaises(IOError, font.set_variation_by_axes, [500, 50]) - - def _check_text(font, path, epsilon): - im = Image.new("RGB", (100, 75), "white") - d = ImageDraw.Draw(im) - d.text((10, 10), "Text", font=font, fill="black") - - with Image.open(path) as expected: - assert_image_similar(im, expected, epsilon) + with pytest.raises(OSError): + font.set_variation_by_axes([500, 50]) font = ImageFont.truetype("Tests/fonts/AdobeVFPrototype.ttf", 36) font.set_variation_by_axes([500, 50]) - _check_text(font, "Tests/images/variation_adobe_axes.png", 5.1) + self._check_text(font, "Tests/images/variation_adobe_axes.png", 11.05) font = ImageFont.truetype("Tests/fonts/TINY5x3GX.ttf", 36) font.set_variation_by_axes([100]) - _check_text(font, "Tests/images/variation_tiny_axes.png", 32.5) + self._check_text(font, "Tests/images/variation_tiny_axes.png", 32.5) + + @pytest.mark.parametrize( + "anchor, left, left_old, top", + ( + # test horizontal anchors + ("ls", 0, 0, -36), + ("ms", -64, -65, -36), + ("rs", -128, -129, -36), + # test vertical anchors + ("ma", -64, -65, 16), + ("mt", -64, -65, 0), + ("mm", -64, -65, -17), + ("mb", -64, -65, -44), + ("md", -64, -65, -51), + ), + ids=("ls", "ms", "rs", "ma", "mt", "mm", "mb", "md"), + ) + def test_anchor(self, anchor, left, left_old, top): + name, text = "quick", "Quick" + path = f"Tests/images/test_anchor_{name}_{anchor}.png" + + freetype = parse_version(features.version_module("freetype2")) + if freetype < parse_version("2.4"): + width, height = (129, 44) + left = left_old + elif self.LAYOUT_ENGINE == ImageFont.LAYOUT_RAQM: + width, height = (129, 44) + else: + width, height = (128, 44) + + bbox_expected = (left, top, left + width, top + height) + + f = ImageFont.truetype( + "Tests/fonts/NotoSans-Regular.ttf", 48, layout_engine=self.LAYOUT_ENGINE + ) + + im = Image.new("RGB", (200, 200), "white") + d = ImageDraw.Draw(im) + d.line(((0, 100), (200, 100)), "gray") + d.line(((100, 0), (100, 200)), "gray") + d.text((100, 100), text, fill="black", anchor=anchor, font=f) + + assert d.textbbox((0, 0), text, f, anchor=anchor) == bbox_expected + + with Image.open(path) as expected: + assert_image_similar(im, expected, 7) + + @pytest.mark.parametrize( + "anchor, align", + ( + # test horizontal anchors + ("lm", "left"), + ("lm", "center"), + ("lm", "right"), + ("mm", "left"), + ("mm", "center"), + ("mm", "right"), + ("rm", "left"), + ("rm", "center"), + ("rm", "right"), + # test vertical anchors + ("ma", "center"), + # ("mm", "center"), # duplicate + ("md", "center"), + ), + ) + def test_anchor_multiline(self, anchor, align): + target = f"Tests/images/test_anchor_multiline_{anchor}_{align}.png" + text = "a\nlong\ntext sample" + + f = ImageFont.truetype( + "Tests/fonts/NotoSans-Regular.ttf", 48, layout_engine=self.LAYOUT_ENGINE + ) + + # test render + im = Image.new("RGB", (600, 400), "white") + d = ImageDraw.Draw(im) + d.line(((0, 200), (600, 200)), "gray") + d.line(((300, 0), (300, 400)), "gray") + d.multiline_text( + (300, 200), text, fill="black", anchor=anchor, font=f, align=align + ) + + with Image.open(target) as expected: + assert_image_similar(im, expected, 4) + + def test_anchor_invalid(self): + font = self.get_font() + im = Image.new("RGB", (100, 100), "white") + d = ImageDraw.Draw(im) + d.font = font + + for anchor in ["", "l", "a", "lax", "sa", "xa", "lx"]: + pytest.raises(ValueError, lambda: font.getmask2("hello", anchor=anchor)) + pytest.raises(ValueError, lambda: font.getbbox("hello", anchor=anchor)) + pytest.raises(ValueError, lambda: d.text((0, 0), "hello", anchor=anchor)) + pytest.raises( + ValueError, lambda: d.textbbox((0, 0), "hello", anchor=anchor) + ) + pytest.raises( + ValueError, lambda: d.multiline_text((0, 0), "foo\nbar", anchor=anchor) + ) + pytest.raises( + ValueError, + lambda: d.multiline_textbbox((0, 0), "foo\nbar", anchor=anchor), + ) + for anchor in ["lt", "lb"]: + pytest.raises( + ValueError, lambda: d.multiline_text((0, 0), "foo\nbar", anchor=anchor) + ) + pytest.raises( + ValueError, + lambda: d.multiline_textbbox((0, 0), "foo\nbar", anchor=anchor), + ) + + @skip_unless_feature("freetype2") + @pytest.mark.parametrize("bpp", (1, 2, 4, 8)) + def test_bitmap_font(self, bpp): + text = "Bitmap Font" + layout_name = ["basic", "raqm"][self.LAYOUT_ENGINE] + target = f"Tests/images/bitmap_font_{bpp}_{layout_name}.png" + font = ImageFont.truetype( + f"Tests/fonts/DejaVuSans-24-{bpp}-stripped.ttf", + 24, + layout_engine=self.LAYOUT_ENGINE, + ) + + im = Image.new("RGB", (160, 35), "white") + draw = ImageDraw.Draw(im) + draw.text((2, 2), text, "black", font) + + assert_image_equal_tofile(im, target) + + def test_standard_embedded_color(self): + txt = "Hello World!" + ttf = ImageFont.truetype(FONT_PATH, 40, layout_engine=self.LAYOUT_ENGINE) + ttf.getsize(txt) + + im = Image.new("RGB", (300, 64), "white") + d = ImageDraw.Draw(im) + d.text((10, 10), txt, font=ttf, fill="#fa6", embedded_color=True) + + with Image.open("Tests/images/standard_embedded.png") as expected: + assert_image_similar(im, expected, 6.2) + + @skip_unless_feature_version("freetype2", "2.5.0") + def test_cbdt(self): + try: + font = ImageFont.truetype( + "Tests/fonts/NotoColorEmoji.ttf", + size=109, + layout_engine=self.LAYOUT_ENGINE, + ) + + im = Image.new("RGB", (150, 150), "white") + d = ImageDraw.Draw(im) + + d.text((10, 10), "\U0001f469", embedded_color=True, font=font) + + with Image.open("Tests/images/cbdt_notocoloremoji.png") as expected: + assert_image_similar(im, expected, 6.2) + except IOError as e: + assert str(e) in ("unimplemented feature", "unknown file format") + pytest.skip("freetype compiled without libpng or unsupported") + + @skip_unless_feature_version("freetype2", "2.5.0") + def test_cbdt_mask(self): + try: + font = ImageFont.truetype( + "Tests/fonts/NotoColorEmoji.ttf", + size=109, + layout_engine=self.LAYOUT_ENGINE, + ) + + im = Image.new("RGB", (150, 150), "white") + d = ImageDraw.Draw(im) + + d.text((10, 10), "\U0001f469", "black", font=font) + + with Image.open("Tests/images/cbdt_notocoloremoji_mask.png") as expected: + assert_image_similar(im, expected, 6.2) + except IOError as e: + assert str(e) in ("unimplemented feature", "unknown file format") + pytest.skip("freetype compiled without libpng or unsupported") + + @skip_unless_feature_version("freetype2", "2.10.0") + def test_colr(self): + font = ImageFont.truetype( + "Tests/fonts/BungeeColor-Regular_colr_Windows.ttf", + size=64, + layout_engine=self.LAYOUT_ENGINE, + ) + + im = Image.new("RGB", (300, 75), "white") + d = ImageDraw.Draw(im) + + d.text((15, 5), "Bungee", embedded_color=True, font=font) + + with Image.open("Tests/images/colr_bungee.png") as expected: + assert_image_similar(im, expected, 21) + + @skip_unless_feature_version("freetype2", "2.10.0") + def test_colr_mask(self): + font = ImageFont.truetype( + "Tests/fonts/BungeeColor-Regular_colr_Windows.ttf", + size=64, + layout_engine=self.LAYOUT_ENGINE, + ) + + im = Image.new("RGB", (300, 75), "white") + d = ImageDraw.Draw(im) + + d.text((15, 5), "Bungee", "black", font=font) + + with Image.open("Tests/images/colr_bungee_mask.png") as expected: + assert_image_similar(im, expected, 22) -@unittest.skipUnless(HAS_RAQM, "Raqm not Available") +@skip_unless_feature("raqm") class TestImageFont_RaqmLayout(TestImageFont): LAYOUT_ENGINE = ImageFont.LAYOUT_RAQM + + +@skip_unless_feature_version("freetype2", "2.4", "Different metrics") +def test_render_mono_size(): + # issue 4177 + + im = Image.new("P", (100, 30), "white") + draw = ImageDraw.Draw(im) + ttf = ImageFont.truetype( + "Tests/fonts/DejaVuSans.ttf", 18, layout_engine=ImageFont.LAYOUT_BASIC + ) + + draw.text((10, 10), "r" * 10, "black", ttf) + assert_image_equal_tofile(im, "Tests/images/text_mono.gif") + + +def test_freetype_deprecation(monkeypatch): + # Arrange: mock features.version_module to return fake FreeType version + def fake_version_module(module): + return "2.7" + + monkeypatch.setattr(features, "version_module", fake_version_module) + + # Act / Assert + with pytest.warns(DeprecationWarning): + ImageFont.truetype(FONT_PATH, FONT_SIZE) diff --git a/Tests/test_imagefont_bitmap.py b/Tests/test_imagefont_bitmap.py deleted file mode 100644 index 437bd4bf6..000000000 --- a/Tests/test_imagefont_bitmap.py +++ /dev/null @@ -1,45 +0,0 @@ -import unittest - -from PIL import Image, ImageDraw, ImageFont - -from .helper import PillowTestCase, assert_image_similar - -image_font_installed = True -try: - ImageFont.core.getfont -except ImportError: - image_font_installed = False - - -@unittest.skipUnless(image_font_installed, "image font not installed") -class TestImageFontBitmap(PillowTestCase): - def test_similar(self): - text = "EmbeddedBitmap" - font_outline = ImageFont.truetype(font="Tests/fonts/DejaVuSans.ttf", size=24) - font_bitmap = ImageFont.truetype( - font="Tests/fonts/DejaVuSans-bitmap.ttf", size=24 - ) - size_outline = font_outline.getsize(text) - size_bitmap = font_bitmap.getsize(text) - size_final = ( - max(size_outline[0], size_bitmap[0]), - max(size_outline[1], size_bitmap[1]), - ) - im_bitmap = Image.new("RGB", size_final, (255, 255, 255)) - im_outline = im_bitmap.copy() - draw_bitmap = ImageDraw.Draw(im_bitmap) - draw_outline = ImageDraw.Draw(im_outline) - - # Metrics are different on the bitmap and ttf fonts, - # more so on some platforms and versions of freetype than others. - # Mac has a 1px difference, linux doesn't. - draw_bitmap.text( - (0, size_final[1] - size_bitmap[1]), text, fill=(0, 0, 0), font=font_bitmap - ) - draw_outline.text( - (0, size_final[1] - size_outline[1]), - text, - fill=(0, 0, 0), - font=font_outline, - ) - assert_image_similar(im_bitmap, im_outline, 20) diff --git a/Tests/test_imagefontctl.py b/Tests/test_imagefontctl.py index 796059e5d..82e2b4ebc 100644 --- a/Tests/test_imagefontctl.py +++ b/Tests/test_imagefontctl.py @@ -1,195 +1,431 @@ -import unittest +import pytest +from packaging.version import parse as parse_version from PIL import Image, ImageDraw, ImageFont, features -from .helper import PillowTestCase, assert_image_similar +from .helper import ( + assert_image_similar, + skip_unless_feature, + skip_unless_feature_version, +) FONT_SIZE = 20 FONT_PATH = "Tests/fonts/DejaVuSans.ttf" +pytestmark = skip_unless_feature("raqm") -@unittest.skipUnless(features.check("raqm"), "Raqm Library is not installed.") -class TestImagecomplextext(PillowTestCase): - def test_english(self): - # smoke test, this should not fail - ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE) - im = Image.new(mode="RGB", size=(300, 100)) - draw = ImageDraw.Draw(im) - draw.text((0, 0), "TEST", font=ttf, fill=500, direction="ltr") - def test_complex_text(self): - ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE) +def test_english(): + # smoke test, this should not fail + ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE) + im = Image.new(mode="RGB", size=(300, 100)) + draw = ImageDraw.Draw(im) + draw.text((0, 0), "TEST", font=ttf, fill=500, direction="ltr") - im = Image.new(mode="RGB", size=(300, 100)) - draw = ImageDraw.Draw(im) - draw.text((0, 0), "اهلا عمان", font=ttf, fill=500) - target = "Tests/images/test_text.png" - with Image.open(target) as target_img: - assert_image_similar(im, target_img, 0.5) +def test_complex_text(): + ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE) - def test_y_offset(self): - ttf = ImageFont.truetype("Tests/fonts/NotoNastaliqUrdu-Regular.ttf", FONT_SIZE) + im = Image.new(mode="RGB", size=(300, 100)) + draw = ImageDraw.Draw(im) + draw.text((0, 0), "اهلا عمان", font=ttf, fill=500) - im = Image.new(mode="RGB", size=(300, 100)) - draw = ImageDraw.Draw(im) - draw.text((0, 0), "العالم العربي", font=ttf, fill=500) + target = "Tests/images/test_text.png" + with Image.open(target) as target_img: + assert_image_similar(im, target_img, 0.5) - target = "Tests/images/test_y_offset.png" - with Image.open(target) as target_img: - assert_image_similar(im, target_img, 1.7) - def test_complex_unicode_text(self): - ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE) +def test_y_offset(): + ttf = ImageFont.truetype("Tests/fonts/NotoNastaliqUrdu-Regular.ttf", FONT_SIZE) - im = Image.new(mode="RGB", size=(300, 100)) - draw = ImageDraw.Draw(im) - draw.text((0, 0), "السلام عليكم", font=ttf, fill=500) + im = Image.new(mode="RGB", size=(300, 100)) + draw = ImageDraw.Draw(im) + draw.text((0, 0), "العالم العربي", font=ttf, fill=500) - target = "Tests/images/test_complex_unicode_text.png" - with Image.open(target) as target_img: - assert_image_similar(im, target_img, 0.5) + target = "Tests/images/test_y_offset.png" + with Image.open(target) as target_img: + assert_image_similar(im, target_img, 1.7) - ttf = ImageFont.truetype("Tests/fonts/KhmerOSBattambang-Regular.ttf", FONT_SIZE) - im = Image.new(mode="RGB", size=(300, 100)) - draw = ImageDraw.Draw(im) - draw.text((0, 0), "លោកុប្បត្តិ", font=ttf, fill=500) +def test_complex_unicode_text(): + ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE) - target = "Tests/images/test_complex_unicode_text2.png" - with Image.open(target) as target_img: - assert_image_similar(im, target_img, 2.3) + im = Image.new(mode="RGB", size=(300, 100)) + draw = ImageDraw.Draw(im) + draw.text((0, 0), "السلام عليكم", font=ttf, fill=500) - def test_text_direction_rtl(self): - ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE) + target = "Tests/images/test_complex_unicode_text.png" + with Image.open(target) as target_img: + assert_image_similar(im, target_img, 0.5) - im = Image.new(mode="RGB", size=(300, 100)) - draw = ImageDraw.Draw(im) - draw.text((0, 0), "English عربي", font=ttf, fill=500, direction="rtl") + ttf = ImageFont.truetype("Tests/fonts/KhmerOSBattambang-Regular.ttf", FONT_SIZE) - target = "Tests/images/test_direction_rtl.png" - with Image.open(target) as target_img: - assert_image_similar(im, target_img, 0.5) + im = Image.new(mode="RGB", size=(300, 100)) + draw = ImageDraw.Draw(im) + draw.text((0, 0), "លោកុប្បត្តិ", font=ttf, fill=500) - def test_text_direction_ltr(self): - ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE) + target = "Tests/images/test_complex_unicode_text2.png" + with Image.open(target) as target_img: + assert_image_similar(im, target_img, 2.33) - im = Image.new(mode="RGB", size=(300, 100)) - draw = ImageDraw.Draw(im) - draw.text((0, 0), "سلطنة عمان Oman", font=ttf, fill=500, direction="ltr") - target = "Tests/images/test_direction_ltr.png" - with Image.open(target) as target_img: - assert_image_similar(im, target_img, 0.5) +def test_text_direction_rtl(): + ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE) - def test_text_direction_rtl2(self): - ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE) + im = Image.new(mode="RGB", size=(300, 100)) + draw = ImageDraw.Draw(im) + draw.text((0, 0), "English عربي", font=ttf, fill=500, direction="rtl") - im = Image.new(mode="RGB", size=(300, 100)) - draw = ImageDraw.Draw(im) - draw.text((0, 0), "Oman سلطنة عمان", font=ttf, fill=500, direction="rtl") + target = "Tests/images/test_direction_rtl.png" + with Image.open(target) as target_img: + assert_image_similar(im, target_img, 0.5) - target = "Tests/images/test_direction_ltr.png" - with Image.open(target) as target_img: - assert_image_similar(im, target_img, 0.5) - def test_text_direction_ttb(self): - ttf = ImageFont.truetype("Tests/fonts/NotoSansJP-Regular.otf", FONT_SIZE) +def test_text_direction_ltr(): + ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE) - im = Image.new(mode="RGB", size=(100, 300)) - draw = ImageDraw.Draw(im) - try: - draw.text((0, 0), "English あい", font=ttf, fill=500, direction="ttb") - except ValueError as ex: - if str(ex) == "libraqm 0.7 or greater required for 'ttb' direction": - self.skipTest("libraqm 0.7 or greater not available") + im = Image.new(mode="RGB", size=(300, 100)) + draw = ImageDraw.Draw(im) + draw.text((0, 0), "سلطنة عمان Oman", font=ttf, fill=500, direction="ltr") - target = "Tests/images/test_direction_ttb.png" - with Image.open(target) as target_img: - assert_image_similar(im, target_img, 1.15) + target = "Tests/images/test_direction_ltr.png" + with Image.open(target) as target_img: + assert_image_similar(im, target_img, 0.5) - def test_text_direction_ttb_stroke(self): - ttf = ImageFont.truetype("Tests/fonts/NotoSansJP-Regular.otf", 50) - im = Image.new(mode="RGB", size=(100, 300)) - draw = ImageDraw.Draw(im) - try: - draw.text( - (25, 25), - "あい", - font=ttf, - fill=500, - direction="ttb", - stroke_width=2, - stroke_fill="#0f0", - ) - except ValueError as ex: - if str(ex) == "libraqm 0.7 or greater required for 'ttb' direction": - self.skipTest("libraqm 0.7 or greater not available") +def test_text_direction_rtl2(): + ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE) - target = "Tests/images/test_direction_ttb_stroke.png" - with Image.open(target) as target_img: - assert_image_similar(im, target_img, 12.4) + im = Image.new(mode="RGB", size=(300, 100)) + draw = ImageDraw.Draw(im) + draw.text((0, 0), "Oman سلطنة عمان", font=ttf, fill=500, direction="rtl") - def test_ligature_features(self): - ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE) + target = "Tests/images/test_direction_ltr.png" + with Image.open(target) as target_img: + assert_image_similar(im, target_img, 0.5) - im = Image.new(mode="RGB", size=(300, 100)) - draw = ImageDraw.Draw(im) - draw.text((0, 0), "filling", font=ttf, fill=500, features=["-liga"]) - target = "Tests/images/test_ligature_features.png" - with Image.open(target) as target_img: - assert_image_similar(im, target_img, 0.5) - liga_size = ttf.getsize("fi", features=["-liga"]) - self.assertEqual(liga_size, (13, 19)) +def test_text_direction_ttb(): + ttf = ImageFont.truetype("Tests/fonts/NotoSansJP-Regular.otf", FONT_SIZE) - def test_kerning_features(self): - ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE) + im = Image.new(mode="RGB", size=(100, 300)) + draw = ImageDraw.Draw(im) + try: + draw.text((0, 0), "English あい", font=ttf, fill=500, direction="ttb") + except ValueError as ex: + if str(ex) == "libraqm 0.7 or greater required for 'ttb' direction": + pytest.skip("libraqm 0.7 or greater not available") - im = Image.new(mode="RGB", size=(300, 100)) - draw = ImageDraw.Draw(im) - draw.text((0, 0), "TeToAV", font=ttf, fill=500, features=["-kern"]) + target = "Tests/images/test_direction_ttb.png" + with Image.open(target) as target_img: + assert_image_similar(im, target_img, 2.8) - target = "Tests/images/test_kerning_features.png" - with Image.open(target) as target_img: - assert_image_similar(im, target_img, 0.5) - def test_arabictext_features(self): - ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE) +def test_text_direction_ttb_stroke(): + ttf = ImageFont.truetype("Tests/fonts/NotoSansJP-Regular.otf", 50) - im = Image.new(mode="RGB", size=(300, 100)) - draw = ImageDraw.Draw(im) + im = Image.new(mode="RGB", size=(100, 300)) + draw = ImageDraw.Draw(im) + try: draw.text( - (0, 0), - "اللغة العربية", + (27, 27), + "あい", font=ttf, fill=500, - features=["-fina", "-init", "-medi"], + direction="ttb", + stroke_width=2, + stroke_fill="#0f0", ) + except ValueError as ex: + if str(ex) == "libraqm 0.7 or greater required for 'ttb' direction": + pytest.skip("libraqm 0.7 or greater not available") - target = "Tests/images/test_arabictext_features.png" - with Image.open(target) as target_img: - assert_image_similar(im, target_img, 0.5) + target = "Tests/images/test_direction_ttb_stroke.png" + with Image.open(target) as target_img: + assert_image_similar(im, target_img, 19.4) - def test_x_max_and_y_offset(self): - ttf = ImageFont.truetype("Tests/fonts/ArefRuqaa-Regular.ttf", 40) - im = Image.new(mode="RGB", size=(50, 100)) - draw = ImageDraw.Draw(im) - draw.text((0, 0), "لح", font=ttf, fill=500) +def test_ligature_features(): + ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE) - target = "Tests/images/test_x_max_and_y_offset.png" - with Image.open(target) as target_img: - assert_image_similar(im, target_img, 0.5) + im = Image.new(mode="RGB", size=(300, 100)) + draw = ImageDraw.Draw(im) + draw.text((0, 0), "filling", font=ttf, fill=500, features=["-liga"]) + target = "Tests/images/test_ligature_features.png" + with Image.open(target) as target_img: + assert_image_similar(im, target_img, 0.5) - def test_language(self): - ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE) + liga_size = ttf.getsize("fi", features=["-liga"]) + assert liga_size == (13, 19) - im = Image.new(mode="RGB", size=(300, 100)) - draw = ImageDraw.Draw(im) - draw.text((0, 0), "абвг", font=ttf, fill=500, language="sr") - target = "Tests/images/test_language.png" - with Image.open(target) as target_img: - assert_image_similar(im, target_img, 0.5) +def test_kerning_features(): + ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE) + + im = Image.new(mode="RGB", size=(300, 100)) + draw = ImageDraw.Draw(im) + draw.text((0, 0), "TeToAV", font=ttf, fill=500, features=["-kern"]) + + target = "Tests/images/test_kerning_features.png" + with Image.open(target) as target_img: + assert_image_similar(im, target_img, 0.5) + + +def test_arabictext_features(): + ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE) + + im = Image.new(mode="RGB", size=(300, 100)) + draw = ImageDraw.Draw(im) + draw.text( + (0, 0), + "اللغة العربية", + font=ttf, + fill=500, + features=["-fina", "-init", "-medi"], + ) + + target = "Tests/images/test_arabictext_features.png" + with Image.open(target) as target_img: + assert_image_similar(im, target_img, 0.5) + + +def test_x_max_and_y_offset(): + ttf = ImageFont.truetype("Tests/fonts/ArefRuqaa-Regular.ttf", 40) + + im = Image.new(mode="RGB", size=(50, 100)) + draw = ImageDraw.Draw(im) + draw.text((0, 0), "لح", font=ttf, fill=500) + + target = "Tests/images/test_x_max_and_y_offset.png" + with Image.open(target) as target_img: + assert_image_similar(im, target_img, 0.5) + + +def test_language(): + ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE) + + im = Image.new(mode="RGB", size=(300, 100)) + draw = ImageDraw.Draw(im) + draw.text((0, 0), "абвг", font=ttf, fill=500, language="sr") + + target = "Tests/images/test_language.png" + with Image.open(target) as target_img: + assert_image_similar(im, target_img, 0.5) + + +@pytest.mark.parametrize("mode", ("L", "1")) +@pytest.mark.parametrize( + "text, direction, expected", + ( + ("سلطنة عمان Oman", None, 173.703125), + ("سلطنة عمان Oman", "ltr", 173.703125), + ("Oman سلطنة عمان", "rtl", 173.703125), + ("English عربي", "rtl", 123.796875), + ("test", "ttb", 80.0), + ), + ids=("None", "ltr", "rtl2", "rtl", "ttb"), +) +def test_getlength(mode, text, direction, expected): + ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE) + im = Image.new(mode, (1, 1), 0) + d = ImageDraw.Draw(im) + + try: + assert d.textlength(text, ttf, direction) == expected + except ValueError as ex: + if ( + direction == "ttb" + and str(ex) == "libraqm 0.7 or greater required for 'ttb' direction" + ): + pytest.skip("libraqm 0.7 or greater not available") + + +@pytest.mark.parametrize("mode", ("L", "1")) +@pytest.mark.parametrize("direction", ("ltr", "ttb")) +@pytest.mark.parametrize( + "text", + ("i" + ("\u030C" * 15) + "i", "i" + "\u032C" * 15 + "i", "\u035Cii", "i\u0305i"), + ids=("caron-above", "caron-below", "double-breve", "overline"), +) +def test_getlength_combine(mode, direction, text): + if text == "i\u0305i" and direction == "ttb": + pytest.skip("fails with this font") + + ttf = ImageFont.truetype("Tests/fonts/NotoSans-Regular.ttf", 48) + + try: + target = ttf.getlength("ii", mode, direction) + actual = ttf.getlength(text, mode, direction) + + assert actual == target + except ValueError as ex: + if ( + direction == "ttb" + and str(ex) == "libraqm 0.7 or greater required for 'ttb' direction" + ): + pytest.skip("libraqm 0.7 or greater not available") + + +# FreeType 2.5.1 README: Miscellaneous Changes: +# Improved computation of emulated vertical metrics for TrueType fonts. +@skip_unless_feature_version( + "freetype2", "2.5.1", "FreeType <2.5.1 has incompatible ttb metrics" +) +@pytest.mark.parametrize("anchor", ("lt", "mm", "rb", "sm")) +def test_anchor_ttb(anchor): + text = "f" + path = f"Tests/images/test_anchor_ttb_{text}_{anchor}.png" + f = ImageFont.truetype("Tests/fonts/NotoSans-Regular.ttf", 120) + + im = Image.new("RGB", (200, 400), "white") + d = ImageDraw.Draw(im) + d.line(((0, 200), (200, 200)), "gray") + d.line(((100, 0), (100, 400)), "gray") + try: + d.text((100, 200), text, fill="black", anchor=anchor, direction="ttb", font=f) + except ValueError as ex: + if str(ex) == "libraqm 0.7 or greater required for 'ttb' direction": + pytest.skip("libraqm 0.7 or greater not available") + + with Image.open(path) as expected: + assert_image_similar(im, expected, 1) # fails at 5 + + +combine_tests = ( + # extends above (e.g. issue #4553) + ("caron", "a\u030C\u030C\u030C\u030C\u030Cb", None, None, 0.08), + ("caron_la", "a\u030C\u030C\u030C\u030C\u030Cb", "la", None, 0.08), + ("caron_lt", "a\u030C\u030C\u030C\u030C\u030Cb", "lt", None, 0.08), + ("caron_ls", "a\u030C\u030C\u030C\u030C\u030Cb", "ls", None, 0.08), + ("caron_ttb", "ca" + ("\u030C" * 15) + "b", None, "ttb", 0.3), + ("caron_ttb_lt", "ca" + ("\u030C" * 15) + "b", "lt", "ttb", 0.3), + # extends below + ("caron_below", "a\u032C\u032C\u032C\u032C\u032Cb", None, None, 0.02), + ("caron_below_ld", "a\u032C\u032C\u032C\u032C\u032Cb", "ld", None, 0.02), + ("caron_below_lb", "a\u032C\u032C\u032C\u032C\u032Cb", "lb", None, 0.02), + ("caron_below_ls", "a\u032C\u032C\u032C\u032C\u032Cb", "ls", None, 0.02), + ("caron_below_ttb", "a" + ("\u032C" * 15) + "b", None, "ttb", 0.03), + ("caron_below_ttb_lb", "a" + ("\u032C" * 15) + "b", "lb", "ttb", 0.03), + # extends to the right (e.g. issue #3745) + ("double_breve_below", "a\u035Ci", None, None, 0.02), + ("double_breve_below_ma", "a\u035Ci", "ma", None, 0.02), + ("double_breve_below_ra", "a\u035Ci", "ra", None, 0.02), + ("double_breve_below_ttb", "a\u035Cb", None, "ttb", 0.02), + ("double_breve_below_ttb_rt", "a\u035Cb", "rt", "ttb", 0.02), + ("double_breve_below_ttb_mt", "a\u035Cb", "mt", "ttb", 0.02), + ("double_breve_below_ttb_st", "a\u035Cb", "st", "ttb", 0.02), + # extends to the left (fail=0.064) + ("overline", "i\u0305", None, None, 0.02), + ("overline_la", "i\u0305", "la", None, 0.02), + ("overline_ra", "i\u0305", "ra", None, 0.02), + ("overline_ttb", "i\u0305", None, "ttb", 0.02), + ("overline_ttb_rt", "i\u0305", "rt", "ttb", 0.02), + ("overline_ttb_mt", "i\u0305", "mt", "ttb", 0.02), + ("overline_ttb_st", "i\u0305", "st", "ttb", 0.02), +) + + +# this tests various combining characters for anchor alignment and clipping +@pytest.mark.parametrize( + "name, text, anchor, dir, epsilon", combine_tests, ids=[r[0] for r in combine_tests] +) +def test_combine(name, text, dir, anchor, epsilon): + if ( + parse_version(features.version_module("freetype2")) < parse_version("2.5.1") + and dir == "ttb" + ): + # FreeType 2.5.1 README: Miscellaneous Changes: + # Improved computation of emulated vertical metrics for TrueType fonts. + pytest.skip("FreeType <2.5.1 has incompatible ttb metrics") + + path = f"Tests/images/test_combine_{name}.png" + f = ImageFont.truetype("Tests/fonts/NotoSans-Regular.ttf", 48) + + im = Image.new("RGB", (400, 400), "white") + d = ImageDraw.Draw(im) + d.line(((0, 200), (400, 200)), "gray") + d.line(((200, 0), (200, 400)), "gray") + try: + d.text((200, 200), text, fill="black", anchor=anchor, direction=dir, font=f) + except ValueError as ex: + if str(ex) == "libraqm 0.7 or greater required for 'ttb' direction": + pytest.skip("libraqm 0.7 or greater not available") + + with Image.open(path) as expected: + assert_image_similar(im, expected, epsilon) + + +@pytest.mark.parametrize( + "anchor, align", + ( + ("lm", "left"), # pass with getsize + ("lm", "center"), # fail at 2.12 + ("lm", "right"), # fail at 2.57 + ("mm", "left"), # fail at 2.12 + ("mm", "center"), # pass with getsize + ("mm", "right"), # fail at 2.12 + ("rm", "left"), # fail at 2.57 + ("rm", "center"), # fail at 2.12 + ("rm", "right"), # pass with getsize + ), +) +def test_combine_multiline(anchor, align): + # test that multiline text uses getlength, not getsize or getbbox + + path = f"Tests/images/test_combine_multiline_{anchor}_{align}.png" + f = ImageFont.truetype("Tests/fonts/NotoSans-Regular.ttf", 48) + text = "i\u0305\u035C\ntext" # i with overline and double breve, and a word + + im = Image.new("RGB", (400, 400), "white") + d = ImageDraw.Draw(im) + d.line(((0, 200), (400, 200)), "gray") + d.line(((200, 0), (200, 400)), "gray") + bbox = d.multiline_textbbox((200, 200), text, anchor=anchor, font=f, align=align) + d.rectangle(bbox, outline="red") + d.multiline_text((200, 200), text, fill="black", anchor=anchor, font=f, align=align) + + with Image.open(path) as expected: + assert_image_similar(im, expected, 0.015) + + +def test_anchor_invalid_ttb(): + font = ImageFont.truetype(FONT_PATH, FONT_SIZE) + im = Image.new("RGB", (100, 100), "white") + d = ImageDraw.Draw(im) + d.font = font + + for anchor in ["", "l", "a", "lax", "xa", "la", "ls", "ld", "lx"]: + pytest.raises( + ValueError, lambda: font.getmask2("hello", anchor=anchor, direction="ttb") + ) + pytest.raises( + ValueError, lambda: font.getbbox("hello", anchor=anchor, direction="ttb") + ) + pytest.raises( + ValueError, lambda: d.text((0, 0), "hello", anchor=anchor, direction="ttb") + ) + pytest.raises( + ValueError, + lambda: d.textbbox((0, 0), "hello", anchor=anchor, direction="ttb"), + ) + pytest.raises( + ValueError, + lambda: d.multiline_text( + (0, 0), "foo\nbar", anchor=anchor, direction="ttb" + ), + ) + pytest.raises( + ValueError, + lambda: d.multiline_textbbox( + (0, 0), "foo\nbar", anchor=anchor, direction="ttb" + ), + ) + # ttb multiline text does not support anchors at all + pytest.raises( + ValueError, + lambda: d.multiline_text((0, 0), "foo\nbar", anchor="mm", direction="ttb"), + ) + pytest.raises( + ValueError, + lambda: d.multiline_textbbox((0, 0), "foo\nbar", anchor="mm", direction="ttb"), + ) diff --git a/Tests/test_imagegrab.py b/Tests/test_imagegrab.py index efa2b54b9..c36285451 100644 --- a/Tests/test_imagegrab.py +++ b/Tests/test_imagegrab.py @@ -1,65 +1,99 @@ +import os import subprocess import sys -from .helper import PillowTestCase, assert_image +import pytest -try: - from PIL import ImageGrab +from PIL import Image, ImageGrab - class TestImageGrab(PillowTestCase): - def test_grab(self): - for im in [ - ImageGrab.grab(), - ImageGrab.grab(include_layered_windows=True), - ImageGrab.grab(all_screens=True), - ]: +from .helper import assert_image, assert_image_equal_tofile, skip_unless_feature + + +class TestImageGrab: + @pytest.mark.skipif( + sys.platform not in ("win32", "darwin"), reason="requires Windows or macOS" + ) + def test_grab(self): + for im in [ + ImageGrab.grab(), + ImageGrab.grab(include_layered_windows=True), + ImageGrab.grab(all_screens=True), + ]: + assert_image(im, im.mode, im.size) + + im = ImageGrab.grab(bbox=(10, 20, 50, 80)) + assert_image(im, im.mode, (40, 60)) + + @skip_unless_feature("xcb") + def test_grab_x11(self): + try: + if sys.platform not in ("win32", "darwin"): + im = ImageGrab.grab() assert_image(im, im.mode, im.size) - im = ImageGrab.grab(bbox=(10, 20, 50, 80)) - assert_image(im, im.mode, (40, 60)) + im2 = ImageGrab.grab(xdisplay="") + assert_image(im2, im2.mode, im2.size) + except OSError as e: + pytest.skip(str(e)) - def test_grabclipboard(self): - if sys.platform == "darwin": - subprocess.call(["screencapture", "-cx"]) - else: - p = subprocess.Popen( - ["powershell", "-command", "-"], stdin=subprocess.PIPE - ) - p.stdin.write( - b"""[Reflection.Assembly]::LoadWithPartialName("System.Drawing") + @pytest.mark.skipif(Image.core.HAVE_XCB, reason="tests missing XCB") + def test_grab_no_xcb(self): + if sys.platform not in ("win32", "darwin"): + with pytest.raises(OSError) as e: + ImageGrab.grab() + assert str(e.value).startswith("Pillow was built without XCB support") + + with pytest.raises(OSError) as e: + ImageGrab.grab(xdisplay="") + assert str(e.value).startswith("Pillow was built without XCB support") + + @skip_unless_feature("xcb") + def test_grab_invalid_xdisplay(self): + with pytest.raises(OSError) as e: + ImageGrab.grab(xdisplay="error.test:0.0") + assert str(e.value).startswith("X connection failed") + + def test_grabclipboard(self): + if sys.platform == "darwin": + subprocess.call(["screencapture", "-cx"]) + elif sys.platform == "win32": + p = subprocess.Popen(["powershell", "-command", "-"], stdin=subprocess.PIPE) + p.stdin.write( + b"""[Reflection.Assembly]::LoadWithPartialName("System.Drawing") [Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") $bmp = New-Object Drawing.Bitmap 200, 200 [Windows.Forms.Clipboard]::SetImage($bmp)""" - ) - p.communicate() - - im = ImageGrab.grabclipboard() - assert_image(im, im.mode, im.size) - - -except ImportError: - - class TestImageGrab(PillowTestCase): - def test_skip(self): - self.skipTest("ImportError") - - -class TestImageGrabImport(PillowTestCase): - def test_import(self): - # Arrange - exception = None - - # Act - try: - from PIL import ImageGrab - - ImageGrab.__name__ # dummy to prevent Pyflakes warning - except Exception as e: - exception = e - - # Assert - if sys.platform in ["win32", "darwin"]: - self.assertIsNone(exception) + ) + p.communicate() else: - self.assertIsInstance(exception, ImportError) - self.assertEqual(str(exception), "ImageGrab is macOS and Windows only") + with pytest.raises(NotImplementedError) as e: + ImageGrab.grabclipboard() + assert str(e.value) == "ImageGrab.grabclipboard() is macOS and Windows only" + return + + im = ImageGrab.grabclipboard() + assert_image(im, im.mode, im.size) + + @pytest.mark.skipif(sys.platform != "win32", reason="Windows only") + def test_grabclipboard_file(self): + p = subprocess.Popen(["powershell", "-command", "-"], stdin=subprocess.PIPE) + p.stdin.write(rb'Set-Clipboard -Path "Tests\images\hopper.gif"') + p.communicate() + + im = ImageGrab.grabclipboard() + assert len(im) == 1 + assert os.path.samefile(im[0], "Tests/images/hopper.gif") + + @pytest.mark.skipif(sys.platform != "win32", reason="Windows only") + def test_grabclipboard_png(self): + p = subprocess.Popen(["powershell", "-command", "-"], stdin=subprocess.PIPE) + p.stdin.write( + rb"""$bytes = [System.IO.File]::ReadAllBytes("Tests\images\hopper.png") +$ms = new-object System.IO.MemoryStream(, $bytes) +[Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") +[Windows.Forms.Clipboard]::SetData("PNG", $ms)""" + ) + p.communicate() + + im = ImageGrab.grabclipboard() + assert_image_equal_tofile(im, "Tests/images/hopper.png") diff --git a/Tests/test_imagemath.py b/Tests/test_imagemath.py index bc4f1af28..239806796 100644 --- a/Tests/test_imagemath.py +++ b/Tests/test_imagemath.py @@ -3,7 +3,7 @@ from PIL import Image, ImageMath def pixel(im): if hasattr(im, "im"): - return "{} {!r}".format(im.mode, im.getpixel((0, 0))) + return "{} {}".format(im.mode, repr(im.getpixel((0, 0)))) else: if isinstance(im, int): return int(im) # hack to deal with booleans diff --git a/Tests/test_imagemorph.py b/Tests/test_imagemorph.py index db9e7b647..087c39e01 100644 --- a/Tests/test_imagemorph.py +++ b/Tests/test_imagemorph.py @@ -1,328 +1,342 @@ # Test the ImageMorphology functionality +import pytest + from PIL import Image, ImageMorph, _imagingmorph -from .helper import PillowTestCase, assert_image_equal, hopper +from .helper import assert_image_equal, hopper -class MorphTests(PillowTestCase): - def setUp(self): - self.A = self.string_to_img( - """ - ....... - ....... - ..111.. - ..111.. - ..111.. - ....... - ....... - """ - ) +def string_to_img(image_string): + """Turn a string image representation into a binary image""" + rows = [s for s in image_string.replace(" ", "").split("\n") if len(s)] + height = len(rows) + width = len(rows[0]) + im = Image.new("L", (width, height)) + for i in range(width): + for j in range(height): + c = rows[j][i] + v = c in "X1" + im.putpixel((i, j), v) - def img_to_string(self, im): - """Turn a (small) binary image into a string representation""" - chars = ".1" - width, height = im.size - return "\n".join( - "".join(chars[im.getpixel((c, r)) > 0] for c in range(width)) - for r in range(height) - ) + return im - def string_to_img(self, image_string): - """Turn a string image representation into a binary image""" - rows = [s for s in image_string.replace(" ", "").split("\n") if len(s)] - height = len(rows) - width = len(rows[0]) - im = Image.new("L", (width, height)) - for i in range(width): - for j in range(height): - c = rows[j][i] - v = c in "X1" - im.putpixel((i, j), v) - return im +A = string_to_img( + """ + ....... + ....... + ..111.. + ..111.. + ..111.. + ....... + ....... + """ +) - def img_string_normalize(self, im): - return self.img_to_string(self.string_to_img(im)) - def assert_img_equal(self, A, B): - self.assertEqual(self.img_to_string(A), self.img_to_string(B)) +def img_to_string(im): + """Turn a (small) binary image into a string representation""" + chars = ".1" + width, height = im.size + return "\n".join( + "".join(chars[im.getpixel((c, r)) > 0] for c in range(width)) + for r in range(height) + ) - def assert_img_equal_img_string(self, A, Bstring): - self.assertEqual(self.img_to_string(A), self.img_string_normalize(Bstring)) - def test_str_to_img(self): - with Image.open("Tests/images/morph_a.png") as im: - assert_image_equal(self.A, im) +def img_string_normalize(im): + return img_to_string(string_to_img(im)) - def create_lut(self): - for op in ("corner", "dilation4", "dilation8", "erosion4", "erosion8", "edge"): - lb = ImageMorph.LutBuilder(op_name=op) - lut = lb.build_lut() - with open("Tests/images/%s.lut" % op, "wb") as f: - f.write(lut) - # create_lut() - def test_lut(self): - for op in ("corner", "dilation4", "dilation8", "erosion4", "erosion8", "edge"): - lb = ImageMorph.LutBuilder(op_name=op) - self.assertIsNone(lb.get_lut()) +def assert_img_equal(A, B): + assert img_to_string(A) == img_to_string(B) - lut = lb.build_lut() - with open("Tests/images/%s.lut" % op, "rb") as f: - self.assertEqual(lut, bytearray(f.read())) - def test_no_operator_loaded(self): - mop = ImageMorph.MorphOp() - with self.assertRaises(Exception) as e: - mop.apply(None) - self.assertEqual(str(e.exception), "No operator loaded") - with self.assertRaises(Exception) as e: - mop.match(None) - self.assertEqual(str(e.exception), "No operator loaded") - with self.assertRaises(Exception) as e: - mop.save_lut(None) - self.assertEqual(str(e.exception), "No operator loaded") +def assert_img_equal_img_string(A, Bstring): + assert img_to_string(A) == img_string_normalize(Bstring) - # Test the named patterns - def test_erosion8(self): - # erosion8 - mop = ImageMorph.MorphOp(op_name="erosion8") - count, Aout = mop.apply(self.A) - self.assertEqual(count, 8) - self.assert_img_equal_img_string( - Aout, - """ - ....... - ....... - ....... - ...1... - ....... - ....... - ....... - """, - ) - def test_dialation8(self): - # dialation8 - mop = ImageMorph.MorphOp(op_name="dilation8") - count, Aout = mop.apply(self.A) - self.assertEqual(count, 16) - self.assert_img_equal_img_string( - Aout, - """ - ....... - .11111. - .11111. - .11111. - .11111. - .11111. - ....... - """, - ) +def test_str_to_img(): + with Image.open("Tests/images/morph_a.png") as im: + assert_image_equal(A, im) - def test_erosion4(self): - # erosion4 - mop = ImageMorph.MorphOp(op_name="dilation4") - count, Aout = mop.apply(self.A) - self.assertEqual(count, 12) - self.assert_img_equal_img_string( - Aout, - """ - ....... - ..111.. - .11111. - .11111. - .11111. - ..111.. - ....... - """, - ) - def test_edge(self): - # edge - mop = ImageMorph.MorphOp(op_name="edge") - count, Aout = mop.apply(self.A) - self.assertEqual(count, 1) - self.assert_img_equal_img_string( - Aout, - """ - ....... - ....... - ..111.. - ..1.1.. - ..111.. - ....... - ....... - """, - ) - - def test_corner(self): - # Create a corner detector pattern - mop = ImageMorph.MorphOp(patterns=["1:(... ... ...)->0", "4:(00. 01. ...)->1"]) - count, Aout = mop.apply(self.A) - self.assertEqual(count, 5) - self.assert_img_equal_img_string( - Aout, - """ - ....... - ....... - ..1.1.. - ....... - ..1.1.. - ....... - ....... - """, - ) - - # Test the coordinate counting with the same operator - coords = mop.match(self.A) - self.assertEqual(len(coords), 4) - self.assertEqual(tuple(coords), ((2, 2), (4, 2), (2, 4), (4, 4))) - - coords = mop.get_on_pixels(Aout) - self.assertEqual(len(coords), 4) - self.assertEqual(tuple(coords), ((2, 2), (4, 2), (2, 4), (4, 4))) - - def test_mirroring(self): - # Test 'M' for mirroring - mop = ImageMorph.MorphOp(patterns=["1:(... ... ...)->0", "M:(00. 01. ...)->1"]) - count, Aout = mop.apply(self.A) - self.assertEqual(count, 7) - self.assert_img_equal_img_string( - Aout, - """ - ....... - ....... - ..1.1.. - ....... - ....... - ....... - ....... - """, - ) - - def test_negate(self): - # Test 'N' for negate - mop = ImageMorph.MorphOp(patterns=["1:(... ... ...)->0", "N:(00. 01. ...)->1"]) - count, Aout = mop.apply(self.A) - self.assertEqual(count, 8) - self.assert_img_equal_img_string( - Aout, - """ - ....... - ....... - ..1.... - ....... - ....... - ....... - ....... - """, - ) - - def test_non_binary_images(self): - im = hopper("RGB") - mop = ImageMorph.MorphOp(op_name="erosion8") - - with self.assertRaises(Exception) as e: - mop.apply(im) - self.assertEqual( - str(e.exception), "Image must be binary, meaning it must use mode L" - ) - with self.assertRaises(Exception) as e: - mop.match(im) - self.assertEqual( - str(e.exception), "Image must be binary, meaning it must use mode L" - ) - with self.assertRaises(Exception) as e: - mop.get_on_pixels(im) - self.assertEqual( - str(e.exception), "Image must be binary, meaning it must use mode L" - ) - - def test_add_patterns(self): - # Arrange - lb = ImageMorph.LutBuilder(op_name="corner") - self.assertEqual(lb.patterns, ["1:(... ... ...)->0", "4:(00. 01. ...)->1"]) - new_patterns = ["M:(00. 01. ...)->1", "N:(00. 01. ...)->1"] - - # Act - lb.add_patterns(new_patterns) - - # Assert - self.assertEqual( - lb.patterns, - [ - "1:(... ... ...)->0", - "4:(00. 01. ...)->1", - "M:(00. 01. ...)->1", - "N:(00. 01. ...)->1", - ], - ) - - def test_unknown_pattern(self): - self.assertRaises(Exception, ImageMorph.LutBuilder, op_name="unknown") - - def test_pattern_syntax_error(self): - # Arrange - lb = ImageMorph.LutBuilder(op_name="corner") - new_patterns = ["a pattern with a syntax error"] - lb.add_patterns(new_patterns) - - # Act / Assert - with self.assertRaises(Exception) as e: - lb.build_lut() - self.assertEqual( - str(e.exception), 'Syntax error in pattern "a pattern with a syntax error"' - ) - - def test_load_invalid_mrl(self): - # Arrange - invalid_mrl = "Tests/images/hopper.png" - mop = ImageMorph.MorphOp() - - # Act / Assert - with self.assertRaises(Exception) as e: - mop.load_lut(invalid_mrl) - self.assertEqual(str(e.exception), "Wrong size operator file!") - - def test_roundtrip_mrl(self): - # Arrange - tempfile = self.tempfile("temp.mrl") - mop = ImageMorph.MorphOp(op_name="corner") - initial_lut = mop.lut - - # Act - mop.save_lut(tempfile) - mop.load_lut(tempfile) - - # Act / Assert - self.assertEqual(mop.lut, initial_lut) - - def test_set_lut(self): - # Arrange - lb = ImageMorph.LutBuilder(op_name="corner") +def create_lut(): + for op in ("corner", "dilation4", "dilation8", "erosion4", "erosion8", "edge"): + lb = ImageMorph.LutBuilder(op_name=op) lut = lb.build_lut() - mop = ImageMorph.MorphOp() + with open(f"Tests/images/{op}.lut", "wb") as f: + f.write(lut) - # Act - mop.set_lut(lut) - # Assert - self.assertEqual(mop.lut, lut) +# create_lut() +def test_lut(): + for op in ("corner", "dilation4", "dilation8", "erosion4", "erosion8", "edge"): + lb = ImageMorph.LutBuilder(op_name=op) + assert lb.get_lut() is None - def test_wrong_mode(self): - lut = ImageMorph.LutBuilder(op_name="corner").build_lut() - imrgb = Image.new("RGB", (10, 10)) - iml = Image.new("L", (10, 10)) + lut = lb.build_lut() + with open(f"Tests/images/{op}.lut", "rb") as f: + assert lut == bytearray(f.read()) - with self.assertRaises(RuntimeError): - _imagingmorph.apply(bytes(lut), imrgb.im.id, iml.im.id) - with self.assertRaises(RuntimeError): - _imagingmorph.apply(bytes(lut), iml.im.id, imrgb.im.id) +def test_no_operator_loaded(): + mop = ImageMorph.MorphOp() + with pytest.raises(Exception) as e: + mop.apply(None) + assert str(e.value) == "No operator loaded" + with pytest.raises(Exception) as e: + mop.match(None) + assert str(e.value) == "No operator loaded" + with pytest.raises(Exception) as e: + mop.save_lut(None) + assert str(e.value) == "No operator loaded" - with self.assertRaises(RuntimeError): - _imagingmorph.match(bytes(lut), imrgb.im.id) - # Should not raise - _imagingmorph.match(bytes(lut), iml.im.id) +# Test the named patterns +def test_erosion8(): + # erosion8 + mop = ImageMorph.MorphOp(op_name="erosion8") + count, Aout = mop.apply(A) + assert count == 8 + assert_img_equal_img_string( + Aout, + """ + ....... + ....... + ....... + ...1... + ....... + ....... + ....... + """, + ) + + +def test_dialation8(): + # dialation8 + mop = ImageMorph.MorphOp(op_name="dilation8") + count, Aout = mop.apply(A) + assert count == 16 + assert_img_equal_img_string( + Aout, + """ + ....... + .11111. + .11111. + .11111. + .11111. + .11111. + ....... + """, + ) + + +def test_erosion4(): + # erosion4 + mop = ImageMorph.MorphOp(op_name="dilation4") + count, Aout = mop.apply(A) + assert count == 12 + assert_img_equal_img_string( + Aout, + """ + ....... + ..111.. + .11111. + .11111. + .11111. + ..111.. + ....... + """, + ) + + +def test_edge(): + # edge + mop = ImageMorph.MorphOp(op_name="edge") + count, Aout = mop.apply(A) + assert count == 1 + assert_img_equal_img_string( + Aout, + """ + ....... + ....... + ..111.. + ..1.1.. + ..111.. + ....... + ....... + """, + ) + + +def test_corner(): + # Create a corner detector pattern + mop = ImageMorph.MorphOp(patterns=["1:(... ... ...)->0", "4:(00. 01. ...)->1"]) + count, Aout = mop.apply(A) + assert count == 5 + assert_img_equal_img_string( + Aout, + """ + ....... + ....... + ..1.1.. + ....... + ..1.1.. + ....... + ....... + """, + ) + + # Test the coordinate counting with the same operator + coords = mop.match(A) + assert len(coords) == 4 + assert tuple(coords) == ((2, 2), (4, 2), (2, 4), (4, 4)) + + coords = mop.get_on_pixels(Aout) + assert len(coords) == 4 + assert tuple(coords) == ((2, 2), (4, 2), (2, 4), (4, 4)) + + +def test_mirroring(): + # Test 'M' for mirroring + mop = ImageMorph.MorphOp(patterns=["1:(... ... ...)->0", "M:(00. 01. ...)->1"]) + count, Aout = mop.apply(A) + assert count == 7 + assert_img_equal_img_string( + Aout, + """ + ....... + ....... + ..1.1.. + ....... + ....... + ....... + ....... + """, + ) + + +def test_negate(): + # Test 'N' for negate + mop = ImageMorph.MorphOp(patterns=["1:(... ... ...)->0", "N:(00. 01. ...)->1"]) + count, Aout = mop.apply(A) + assert count == 8 + assert_img_equal_img_string( + Aout, + """ + ....... + ....... + ..1.... + ....... + ....... + ....... + ....... + """, + ) + + +def test_non_binary_images(): + im = hopper("RGB") + mop = ImageMorph.MorphOp(op_name="erosion8") + + with pytest.raises(Exception) as e: + mop.apply(im) + assert str(e.value) == "Image must be binary, meaning it must use mode L" + with pytest.raises(Exception) as e: + mop.match(im) + assert str(e.value) == "Image must be binary, meaning it must use mode L" + with pytest.raises(Exception) as e: + mop.get_on_pixels(im) + assert str(e.value) == "Image must be binary, meaning it must use mode L" + + +def test_add_patterns(): + # Arrange + lb = ImageMorph.LutBuilder(op_name="corner") + assert lb.patterns == ["1:(... ... ...)->0", "4:(00. 01. ...)->1"] + new_patterns = ["M:(00. 01. ...)->1", "N:(00. 01. ...)->1"] + + # Act + lb.add_patterns(new_patterns) + + # Assert + assert lb.patterns == [ + "1:(... ... ...)->0", + "4:(00. 01. ...)->1", + "M:(00. 01. ...)->1", + "N:(00. 01. ...)->1", + ] + + +def test_unknown_pattern(): + with pytest.raises(Exception): + ImageMorph.LutBuilder(op_name="unknown") + + +def test_pattern_syntax_error(): + # Arrange + lb = ImageMorph.LutBuilder(op_name="corner") + new_patterns = ["a pattern with a syntax error"] + lb.add_patterns(new_patterns) + + # Act / Assert + with pytest.raises(Exception) as e: + lb.build_lut() + assert str(e.value) == 'Syntax error in pattern "a pattern with a syntax error"' + + +def test_load_invalid_mrl(): + # Arrange + invalid_mrl = "Tests/images/hopper.png" + mop = ImageMorph.MorphOp() + + # Act / Assert + with pytest.raises(Exception) as e: + mop.load_lut(invalid_mrl) + assert str(e.value) == "Wrong size operator file!" + + +def test_roundtrip_mrl(tmp_path): + # Arrange + tempfile = str(tmp_path / "temp.mrl") + mop = ImageMorph.MorphOp(op_name="corner") + initial_lut = mop.lut + + # Act + mop.save_lut(tempfile) + mop.load_lut(tempfile) + + # Act / Assert + assert mop.lut == initial_lut + + +def test_set_lut(): + # Arrange + lb = ImageMorph.LutBuilder(op_name="corner") + lut = lb.build_lut() + mop = ImageMorph.MorphOp() + + # Act + mop.set_lut(lut) + + # Assert + assert mop.lut == lut + + +def test_wrong_mode(): + lut = ImageMorph.LutBuilder(op_name="corner").build_lut() + imrgb = Image.new("RGB", (10, 10)) + iml = Image.new("L", (10, 10)) + + with pytest.raises(RuntimeError): + _imagingmorph.apply(bytes(lut), imrgb.im.id, iml.im.id) + + with pytest.raises(RuntimeError): + _imagingmorph.apply(bytes(lut), iml.im.id, imrgb.im.id) + + with pytest.raises(RuntimeError): + _imagingmorph.match(bytes(lut), imrgb.im.id) + + # Should not raise + _imagingmorph.match(bytes(lut), iml.im.id) diff --git a/Tests/test_imageops.py b/Tests/test_imageops.py index 166a2605d..f17bfdd2f 100644 --- a/Tests/test_imageops.py +++ b/Tests/test_imageops.py @@ -1,302 +1,364 @@ -from PIL import Image, ImageOps +import pytest + +from PIL import Image, ImageDraw, ImageOps, ImageStat, features from .helper import ( - PillowTestCase, assert_image_equal, assert_image_similar, assert_tuple_approx_equal, hopper, ) -try: - from PIL import _webp - HAVE_WEBP = True -except ImportError: - HAVE_WEBP = False +class Deformer: + def getmesh(self, im): + x, y = im.size + return [((0, 0, x, y), (0, 0, x, 0, x, y, y, 0))] -class TestImageOps(PillowTestCase): - class Deformer: - def getmesh(self, im): - x, y = im.size - return [((0, 0, x, y), (0, 0, x, 0, x, y, y, 0))] +deformer = Deformer() - deformer = Deformer() - def test_sanity(self): +def test_sanity(): - ImageOps.autocontrast(hopper("L")) - ImageOps.autocontrast(hopper("RGB")) + ImageOps.autocontrast(hopper("L")) + ImageOps.autocontrast(hopper("RGB")) - ImageOps.autocontrast(hopper("L"), cutoff=10) - ImageOps.autocontrast(hopper("L"), ignore=[0, 255]) + ImageOps.autocontrast(hopper("L"), cutoff=10) + ImageOps.autocontrast(hopper("L"), cutoff=(2, 10)) + ImageOps.autocontrast(hopper("L"), ignore=[0, 255]) + ImageOps.autocontrast(hopper("L"), mask=hopper("L")) - ImageOps.colorize(hopper("L"), (0, 0, 0), (255, 255, 255)) - ImageOps.colorize(hopper("L"), "black", "white") + ImageOps.colorize(hopper("L"), (0, 0, 0), (255, 255, 255)) + ImageOps.colorize(hopper("L"), "black", "white") - ImageOps.pad(hopper("L"), (128, 128)) - ImageOps.pad(hopper("RGB"), (128, 128)) + ImageOps.pad(hopper("L"), (128, 128)) + ImageOps.pad(hopper("RGB"), (128, 128)) - ImageOps.crop(hopper("L"), 1) - ImageOps.crop(hopper("RGB"), 1) + ImageOps.crop(hopper("L"), 1) + ImageOps.crop(hopper("RGB"), 1) - ImageOps.deform(hopper("L"), self.deformer) - ImageOps.deform(hopper("RGB"), self.deformer) + ImageOps.deform(hopper("L"), deformer) + ImageOps.deform(hopper("RGB"), deformer) - ImageOps.equalize(hopper("L")) - ImageOps.equalize(hopper("RGB")) + ImageOps.equalize(hopper("L")) + ImageOps.equalize(hopper("RGB")) - ImageOps.expand(hopper("L"), 1) - ImageOps.expand(hopper("RGB"), 1) - ImageOps.expand(hopper("L"), 2, "blue") - ImageOps.expand(hopper("RGB"), 2, "blue") + ImageOps.expand(hopper("L"), 1) + ImageOps.expand(hopper("RGB"), 1) + ImageOps.expand(hopper("L"), 2, "blue") + ImageOps.expand(hopper("RGB"), 2, "blue") - ImageOps.fit(hopper("L"), (128, 128)) - ImageOps.fit(hopper("RGB"), (128, 128)) + ImageOps.fit(hopper("L"), (128, 128)) + ImageOps.fit(hopper("RGB"), (128, 128)) - ImageOps.flip(hopper("L")) - ImageOps.flip(hopper("RGB")) + ImageOps.flip(hopper("L")) + ImageOps.flip(hopper("RGB")) - ImageOps.grayscale(hopper("L")) - ImageOps.grayscale(hopper("RGB")) + ImageOps.grayscale(hopper("L")) + ImageOps.grayscale(hopper("RGB")) - ImageOps.invert(hopper("L")) - ImageOps.invert(hopper("RGB")) + ImageOps.invert(hopper("L")) + ImageOps.invert(hopper("RGB")) - ImageOps.mirror(hopper("L")) - ImageOps.mirror(hopper("RGB")) + ImageOps.mirror(hopper("L")) + ImageOps.mirror(hopper("RGB")) - ImageOps.posterize(hopper("L"), 4) - ImageOps.posterize(hopper("RGB"), 4) + ImageOps.posterize(hopper("L"), 4) + ImageOps.posterize(hopper("RGB"), 4) - ImageOps.solarize(hopper("L")) - ImageOps.solarize(hopper("RGB")) + ImageOps.solarize(hopper("L")) + ImageOps.solarize(hopper("RGB")) - ImageOps.exif_transpose(hopper("L")) - ImageOps.exif_transpose(hopper("RGB")) + ImageOps.exif_transpose(hopper("L")) + ImageOps.exif_transpose(hopper("RGB")) - def test_1pxfit(self): - # Division by zero in equalize if image is 1 pixel high - newimg = ImageOps.fit(hopper("RGB").resize((1, 1)), (35, 35)) - self.assertEqual(newimg.size, (35, 35)) - newimg = ImageOps.fit(hopper("RGB").resize((1, 100)), (35, 35)) - self.assertEqual(newimg.size, (35, 35)) +def test_1pxfit(): + # Division by zero in equalize if image is 1 pixel high + newimg = ImageOps.fit(hopper("RGB").resize((1, 1)), (35, 35)) + assert newimg.size == (35, 35) - newimg = ImageOps.fit(hopper("RGB").resize((100, 1)), (35, 35)) - self.assertEqual(newimg.size, (35, 35)) + newimg = ImageOps.fit(hopper("RGB").resize((1, 100)), (35, 35)) + assert newimg.size == (35, 35) - def test_fit_same_ratio(self): - # The ratio for this image is 1000.0 / 755 = 1.3245033112582782 - # If the ratios are not acknowledged to be the same, - # and Pillow attempts to adjust the width to - # 1.3245033112582782 * 755 = 1000.0000000000001 - # then centering this greater width causes a negative x offset when cropping - with Image.new("RGB", (1000, 755)) as im: - new_im = ImageOps.fit(im, (1000, 755)) - self.assertEqual(new_im.size, (1000, 755)) + newimg = ImageOps.fit(hopper("RGB").resize((100, 1)), (35, 35)) + assert newimg.size == (35, 35) - def test_pad(self): - # Same ratio - im = hopper() - new_size = (im.width * 2, im.height * 2) - new_im = ImageOps.pad(im, new_size) - self.assertEqual(new_im.size, new_size) - for label, color, new_size in [ - ("h", None, (im.width * 4, im.height * 2)), - ("v", "#f00", (im.width * 2, im.height * 4)), - ]: - for i, centering in enumerate([(0, 0), (0.5, 0.5), (1, 1)]): - new_im = ImageOps.pad(im, new_size, color=color, centering=centering) - self.assertEqual(new_im.size, new_size) +def test_fit_same_ratio(): + # The ratio for this image is 1000.0 / 755 = 1.3245033112582782 + # If the ratios are not acknowledged to be the same, + # and Pillow attempts to adjust the width to + # 1.3245033112582782 * 755 = 1000.0000000000001 + # then centering this greater width causes a negative x offset when cropping + with Image.new("RGB", (1000, 755)) as im: + new_im = ImageOps.fit(im, (1000, 755)) + assert new_im.size == (1000, 755) + +def test_pad(): + # Same ratio + im = hopper() + new_size = (im.width * 2, im.height * 2) + new_im = ImageOps.pad(im, new_size) + assert new_im.size == new_size + + for label, color, new_size in [ + ("h", None, (im.width * 4, im.height * 2)), + ("v", "#f00", (im.width * 2, im.height * 4)), + ]: + for i, centering in enumerate([(0, 0), (0.5, 0.5), (1, 1)]): + new_im = ImageOps.pad(im, new_size, color=color, centering=centering) + assert new_im.size == new_size + + with Image.open( + "Tests/images/imageops_pad_" + label + "_" + str(i) + ".jpg" + ) as target: + assert_image_similar(new_im, target, 6) + + +def test_pil163(): + # Division by zero in equalize if < 255 pixels in image (@PIL163) + + i = hopper("RGB").resize((15, 16)) + + ImageOps.equalize(i.convert("L")) + ImageOps.equalize(i.convert("P")) + ImageOps.equalize(i.convert("RGB")) + + +def test_scale(): + # Test the scaling function + i = hopper("L").resize((50, 50)) + + with pytest.raises(ValueError): + ImageOps.scale(i, -1) + + newimg = ImageOps.scale(i, 1) + assert newimg.size == (50, 50) + + newimg = ImageOps.scale(i, 2) + assert newimg.size == (100, 100) + + newimg = ImageOps.scale(i, 0.5) + assert newimg.size == (25, 25) + + +def test_colorize_2color(): + # Test the colorizing function with 2-color functionality + + # Open test image (256px by 10px, black to white) + with Image.open("Tests/images/bw_gradient.png") as im: + im = im.convert("L") + + # Create image with original 2-color functionality + im_test = ImageOps.colorize(im, "red", "green") + + # Test output image (2-color) + left = (0, 1) + middle = (127, 1) + right = (255, 1) + assert_tuple_approx_equal( + im_test.getpixel(left), + (255, 0, 0), + threshold=1, + msg="black test pixel incorrect", + ) + assert_tuple_approx_equal( + im_test.getpixel(middle), + (127, 63, 0), + threshold=1, + msg="mid test pixel incorrect", + ) + assert_tuple_approx_equal( + im_test.getpixel(right), + (0, 127, 0), + threshold=1, + msg="white test pixel incorrect", + ) + + +def test_colorize_2color_offset(): + # Test the colorizing function with 2-color functionality and offset + + # Open test image (256px by 10px, black to white) + with Image.open("Tests/images/bw_gradient.png") as im: + im = im.convert("L") + + # Create image with original 2-color functionality with offsets + im_test = ImageOps.colorize( + im, black="red", white="green", blackpoint=50, whitepoint=100 + ) + + # Test output image (2-color) with offsets + left = (25, 1) + middle = (75, 1) + right = (125, 1) + assert_tuple_approx_equal( + im_test.getpixel(left), + (255, 0, 0), + threshold=1, + msg="black test pixel incorrect", + ) + assert_tuple_approx_equal( + im_test.getpixel(middle), + (127, 63, 0), + threshold=1, + msg="mid test pixel incorrect", + ) + assert_tuple_approx_equal( + im_test.getpixel(right), + (0, 127, 0), + threshold=1, + msg="white test pixel incorrect", + ) + + +def test_colorize_3color_offset(): + # Test the colorizing function with 3-color functionality and offset + + # Open test image (256px by 10px, black to white) + with Image.open("Tests/images/bw_gradient.png") as im: + im = im.convert("L") + + # Create image with new three color functionality with offsets + im_test = ImageOps.colorize( + im, + black="red", + white="green", + mid="blue", + blackpoint=50, + whitepoint=200, + midpoint=100, + ) + + # Test output image (3-color) with offsets + left = (25, 1) + left_middle = (75, 1) + middle = (100, 1) + right_middle = (150, 1) + right = (225, 1) + assert_tuple_approx_equal( + im_test.getpixel(left), + (255, 0, 0), + threshold=1, + msg="black test pixel incorrect", + ) + assert_tuple_approx_equal( + im_test.getpixel(left_middle), + (127, 0, 127), + threshold=1, + msg="low-mid test pixel incorrect", + ) + assert_tuple_approx_equal( + im_test.getpixel(middle), (0, 0, 255), threshold=1, msg="mid incorrect" + ) + assert_tuple_approx_equal( + im_test.getpixel(right_middle), + (0, 63, 127), + threshold=1, + msg="high-mid test pixel incorrect", + ) + assert_tuple_approx_equal( + im_test.getpixel(right), + (0, 127, 0), + threshold=1, + msg="white test pixel incorrect", + ) + + +def test_exif_transpose(): + exts = [".jpg"] + if features.check("webp") and features.check("webp_anim"): + exts.append(".webp") + for ext in exts: + with Image.open("Tests/images/hopper" + ext) as base_im: + + def check(orientation_im): + for im in [ + orientation_im, + orientation_im.copy(), + ]: # ImageFile # Image + if orientation_im is base_im: + assert "exif" not in im.info + else: + original_exif = im.info["exif"] + transposed_im = ImageOps.exif_transpose(im) + assert_image_similar(base_im, transposed_im, 17) + if orientation_im is base_im: + assert "exif" not in im.info + else: + assert transposed_im.info["exif"] != original_exif + + assert 0x0112 not in transposed_im.getexif() + + # Repeat the operation to test that it does not keep transposing + transposed_im2 = ImageOps.exif_transpose(transposed_im) + assert_image_equal(transposed_im2, transposed_im) + + check(base_im) + for i in range(2, 9): with Image.open( - "Tests/images/imageops_pad_" + label + "_" + str(i) + ".jpg" - ) as target: - assert_image_similar(new_im, target, 6) + "Tests/images/hopper_orientation_" + str(i) + ext + ) as orientation_im: + check(orientation_im) - def test_pil163(self): - # Division by zero in equalize if < 255 pixels in image (@PIL163) - i = hopper("RGB").resize((15, 16)) +def test_autocontrast_cutoff(): + # Test the cutoff argument of autocontrast + with Image.open("Tests/images/bw_gradient.png") as img: - ImageOps.equalize(i.convert("L")) - ImageOps.equalize(i.convert("P")) - ImageOps.equalize(i.convert("RGB")) + def autocontrast(cutoff): + return ImageOps.autocontrast(img, cutoff).histogram() - def test_scale(self): - # Test the scaling function - i = hopper("L").resize((50, 50)) + assert autocontrast(10) == autocontrast((10, 10)) + assert autocontrast(10) != autocontrast((1, 10)) - with self.assertRaises(ValueError): - ImageOps.scale(i, -1) - newimg = ImageOps.scale(i, 1) - self.assertEqual(newimg.size, (50, 50)) +def test_autocontrast_mask_toy_input(): + # Test the mask argument of autocontrast + with Image.open("Tests/images/bw_gradient.png") as img: - newimg = ImageOps.scale(i, 2) - self.assertEqual(newimg.size, (100, 100)) + rect_mask = Image.new("L", img.size, 0) + draw = ImageDraw.Draw(rect_mask) + x0 = img.size[0] // 4 + y0 = img.size[1] // 4 + x1 = 3 * img.size[0] // 4 + y1 = 3 * img.size[1] // 4 + draw.rectangle((x0, y0, x1, y1), fill=255) - newimg = ImageOps.scale(i, 0.5) - self.assertEqual(newimg.size, (25, 25)) + result = ImageOps.autocontrast(img, mask=rect_mask) + result_nomask = ImageOps.autocontrast(img) - def test_colorize_2color(self): - # Test the colorizing function with 2-color functionality + assert result != result_nomask + assert ImageStat.Stat(result, mask=rect_mask).median == [127] + assert ImageStat.Stat(result_nomask).median == [128] - # Open test image (256px by 10px, black to white) - with Image.open("Tests/images/bw_gradient.png") as im: - im = im.convert("L") - # Create image with original 2-color functionality - im_test = ImageOps.colorize(im, "red", "green") +def test_auto_contrast_mask_real_input(): + # Test the autocontrast with a rectangular mask + with Image.open("Tests/images/iptc.jpg") as img: - # Test output image (2-color) - left = (0, 1) - middle = (127, 1) - right = (255, 1) + rect_mask = Image.new("L", img.size, 0) + draw = ImageDraw.Draw(rect_mask) + x0, y0 = img.size[0] // 2, img.size[1] // 2 + x1, y1 = img.size[0] - 40, img.size[1] + draw.rectangle((x0, y0, x1, y1), fill=255) + + result = ImageOps.autocontrast(img, mask=rect_mask) + result_nomask = ImageOps.autocontrast(img) + + assert result_nomask != result assert_tuple_approx_equal( - im_test.getpixel(left), - (255, 0, 0), - threshold=1, - msg="black test pixel incorrect", + ImageStat.Stat(result, mask=rect_mask).median, + [195, 202, 184], + threshold=2, + msg="autocontrast with mask pixel incorrect", ) assert_tuple_approx_equal( - im_test.getpixel(middle), - (127, 63, 0), - threshold=1, - msg="mid test pixel incorrect", + ImageStat.Stat(result_nomask).median, + [119, 106, 79], + threshold=2, + msg="autocontrast without mask pixel incorrect", ) - assert_tuple_approx_equal( - im_test.getpixel(right), - (0, 127, 0), - threshold=1, - msg="white test pixel incorrect", - ) - - def test_colorize_2color_offset(self): - # Test the colorizing function with 2-color functionality and offset - - # Open test image (256px by 10px, black to white) - with Image.open("Tests/images/bw_gradient.png") as im: - im = im.convert("L") - - # Create image with original 2-color functionality with offsets - im_test = ImageOps.colorize( - im, black="red", white="green", blackpoint=50, whitepoint=100 - ) - - # Test output image (2-color) with offsets - left = (25, 1) - middle = (75, 1) - right = (125, 1) - assert_tuple_approx_equal( - im_test.getpixel(left), - (255, 0, 0), - threshold=1, - msg="black test pixel incorrect", - ) - assert_tuple_approx_equal( - im_test.getpixel(middle), - (127, 63, 0), - threshold=1, - msg="mid test pixel incorrect", - ) - assert_tuple_approx_equal( - im_test.getpixel(right), - (0, 127, 0), - threshold=1, - msg="white test pixel incorrect", - ) - - def test_colorize_3color_offset(self): - # Test the colorizing function with 3-color functionality and offset - - # Open test image (256px by 10px, black to white) - with Image.open("Tests/images/bw_gradient.png") as im: - im = im.convert("L") - - # Create image with new three color functionality with offsets - im_test = ImageOps.colorize( - im, - black="red", - white="green", - mid="blue", - blackpoint=50, - whitepoint=200, - midpoint=100, - ) - - # Test output image (3-color) with offsets - left = (25, 1) - left_middle = (75, 1) - middle = (100, 1) - right_middle = (150, 1) - right = (225, 1) - assert_tuple_approx_equal( - im_test.getpixel(left), - (255, 0, 0), - threshold=1, - msg="black test pixel incorrect", - ) - assert_tuple_approx_equal( - im_test.getpixel(left_middle), - (127, 0, 127), - threshold=1, - msg="low-mid test pixel incorrect", - ) - assert_tuple_approx_equal( - im_test.getpixel(middle), (0, 0, 255), threshold=1, msg="mid incorrect" - ) - assert_tuple_approx_equal( - im_test.getpixel(right_middle), - (0, 63, 127), - threshold=1, - msg="high-mid test pixel incorrect", - ) - assert_tuple_approx_equal( - im_test.getpixel(right), - (0, 127, 0), - threshold=1, - msg="white test pixel incorrect", - ) - - def test_exif_transpose(self): - exts = [".jpg"] - if HAVE_WEBP and _webp.HAVE_WEBPANIM: - exts.append(".webp") - for ext in exts: - with Image.open("Tests/images/hopper" + ext) as base_im: - - def check(orientation_im): - for im in [ - orientation_im, - orientation_im.copy(), - ]: # ImageFile # Image - if orientation_im is base_im: - self.assertNotIn("exif", im.info) - else: - original_exif = im.info["exif"] - transposed_im = ImageOps.exif_transpose(im) - assert_image_similar(base_im, transposed_im, 17) - if orientation_im is base_im: - self.assertNotIn("exif", im.info) - else: - self.assertNotEqual( - transposed_im.info["exif"], original_exif - ) - - self.assertNotIn(0x0112, transposed_im.getexif()) - - # Repeat the operation - # to test that it does not keep transposing - transposed_im2 = ImageOps.exif_transpose(transposed_im) - assert_image_equal(transposed_im2, transposed_im) - - check(base_im) - for i in range(2, 9): - with Image.open( - "Tests/images/hopper_orientation_" + str(i) + ext - ) as orientation_im: - check(orientation_im) diff --git a/Tests/test_imageops_usm.py b/Tests/test_imageops_usm.py index 71e858681..8837ed2a2 100644 --- a/Tests/test_imageops_usm.py +++ b/Tests/test_imageops_usm.py @@ -1,89 +1,111 @@ +import pytest + from PIL import Image, ImageFilter -from .helper import PillowTestCase + +@pytest.fixture +def test_images(): + ims = { + "im": Image.open("Tests/images/hopper.ppm"), + "snakes": Image.open("Tests/images/color_snakes.png"), + } + try: + yield ims + finally: + for im in ims.values(): + im.close() -class TestImageOpsUsm(PillowTestCase): - def setUp(self): - super().setUp() - self.im = Image.open("Tests/images/hopper.ppm") - self.addCleanup(self.im.close) - self.snakes = Image.open("Tests/images/color_snakes.png") - self.addCleanup(self.snakes.close) +def test_filter_api(test_images): + im = test_images["im"] - def test_filter_api(self): + test_filter = ImageFilter.GaussianBlur(2.0) + i = im.filter(test_filter) + assert i.mode == "RGB" + assert i.size == (128, 128) - test_filter = ImageFilter.GaussianBlur(2.0) - i = self.im.filter(test_filter) - self.assertEqual(i.mode, "RGB") - self.assertEqual(i.size, (128, 128)) + test_filter = ImageFilter.UnsharpMask(2.0, 125, 8) + i = im.filter(test_filter) + assert i.mode == "RGB" + assert i.size == (128, 128) - test_filter = ImageFilter.UnsharpMask(2.0, 125, 8) - i = self.im.filter(test_filter) - self.assertEqual(i.mode, "RGB") - self.assertEqual(i.size, (128, 128)) - def test_usm_formats(self): +def test_usm_formats(test_images): + im = test_images["im"] - usm = ImageFilter.UnsharpMask - self.assertRaises(ValueError, self.im.convert("1").filter, usm) - self.im.convert("L").filter(usm) - self.assertRaises(ValueError, self.im.convert("I").filter, usm) - self.assertRaises(ValueError, self.im.convert("F").filter, usm) - self.im.convert("RGB").filter(usm) - self.im.convert("RGBA").filter(usm) - self.im.convert("CMYK").filter(usm) - self.assertRaises(ValueError, self.im.convert("YCbCr").filter, usm) + usm = ImageFilter.UnsharpMask + with pytest.raises(ValueError): + im.convert("1").filter(usm) + im.convert("L").filter(usm) + with pytest.raises(ValueError): + im.convert("I").filter(usm) + with pytest.raises(ValueError): + im.convert("F").filter(usm) + im.convert("RGB").filter(usm) + im.convert("RGBA").filter(usm) + im.convert("CMYK").filter(usm) + with pytest.raises(ValueError): + im.convert("YCbCr").filter(usm) - def test_blur_formats(self): - blur = ImageFilter.GaussianBlur - self.assertRaises(ValueError, self.im.convert("1").filter, blur) - blur(self.im.convert("L")) - self.assertRaises(ValueError, self.im.convert("I").filter, blur) - self.assertRaises(ValueError, self.im.convert("F").filter, blur) - self.im.convert("RGB").filter(blur) - self.im.convert("RGBA").filter(blur) - self.im.convert("CMYK").filter(blur) - self.assertRaises(ValueError, self.im.convert("YCbCr").filter, blur) +def test_blur_formats(test_images): + im = test_images["im"] - def test_usm_accuracy(self): + blur = ImageFilter.GaussianBlur + with pytest.raises(ValueError): + im.convert("1").filter(blur) + blur(im.convert("L")) + with pytest.raises(ValueError): + im.convert("I").filter(blur) + with pytest.raises(ValueError): + im.convert("F").filter(blur) + im.convert("RGB").filter(blur) + im.convert("RGBA").filter(blur) + im.convert("CMYK").filter(blur) + with pytest.raises(ValueError): + im.convert("YCbCr").filter(blur) - src = self.snakes.convert("RGB") - i = src.filter(ImageFilter.UnsharpMask(5, 1024, 0)) - # Image should not be changed because it have only 0 and 255 levels. - self.assertEqual(i.tobytes(), src.tobytes()) - def test_blur_accuracy(self): +def test_usm_accuracy(test_images): + snakes = test_images["snakes"] - i = self.snakes.filter(ImageFilter.GaussianBlur(0.4)) - # These pixels surrounded with pixels with 255 intensity. - # They must be very close to 255. - for x, y, c in [ - (1, 0, 1), - (2, 0, 1), - (7, 8, 1), - (8, 8, 1), - (2, 9, 1), - (7, 3, 0), - (8, 3, 0), - (5, 8, 0), - (5, 9, 0), - (1, 3, 0), - (4, 3, 2), - (4, 2, 2), - ]: - self.assertGreaterEqual(i.im.getpixel((x, y))[c], 250) - # Fuzzy match. + src = snakes.convert("RGB") + i = src.filter(ImageFilter.UnsharpMask(5, 1024, 0)) + # Image should not be changed because it have only 0 and 255 levels. + assert i.tobytes() == src.tobytes() - def gp(x, y): - return i.im.getpixel((x, y)) - self.assertTrue(236 <= gp(7, 4)[0] <= 239) - self.assertTrue(236 <= gp(7, 5)[2] <= 239) - self.assertTrue(236 <= gp(7, 6)[2] <= 239) - self.assertTrue(236 <= gp(7, 7)[1] <= 239) - self.assertTrue(236 <= gp(8, 4)[0] <= 239) - self.assertTrue(236 <= gp(8, 5)[2] <= 239) - self.assertTrue(236 <= gp(8, 6)[2] <= 239) - self.assertTrue(236 <= gp(8, 7)[1] <= 239) +def test_blur_accuracy(test_images): + snakes = test_images["snakes"] + + i = snakes.filter(ImageFilter.GaussianBlur(0.4)) + # These pixels surrounded with pixels with 255 intensity. + # They must be very close to 255. + for x, y, c in [ + (1, 0, 1), + (2, 0, 1), + (7, 8, 1), + (8, 8, 1), + (2, 9, 1), + (7, 3, 0), + (8, 3, 0), + (5, 8, 0), + (5, 9, 0), + (1, 3, 0), + (4, 3, 2), + (4, 2, 2), + ]: + assert i.im.getpixel((x, y))[c] >= 250 + # Fuzzy match. + + def gp(x, y): + return i.im.getpixel((x, y)) + + assert 236 <= gp(7, 4)[0] <= 239 + assert 236 <= gp(7, 5)[2] <= 239 + assert 236 <= gp(7, 6)[2] <= 239 + assert 236 <= gp(7, 7)[1] <= 239 + assert 236 <= gp(8, 4)[0] <= 239 + assert 236 <= gp(8, 5)[2] <= 239 + assert 236 <= gp(8, 6)[2] <= 239 + assert 236 <= gp(8, 7)[1] <= 239 diff --git a/Tests/test_imagepalette.py b/Tests/test_imagepalette.py index 03a4fd3a0..a2b0d2b02 100644 --- a/Tests/test_imagepalette.py +++ b/Tests/test_imagepalette.py @@ -1,135 +1,150 @@ +import pytest + from PIL import Image, ImagePalette -from .helper import PillowTestCase, assert_image_equal +from .helper import assert_image_equal -class TestImagePalette(PillowTestCase): - def test_sanity(self): +def test_sanity(): - ImagePalette.ImagePalette("RGB", list(range(256)) * 3) - self.assertRaises( - ValueError, ImagePalette.ImagePalette, "RGB", list(range(256)) * 2 - ) + ImagePalette.ImagePalette("RGB", list(range(256)) * 3) + with pytest.raises(ValueError): + ImagePalette.ImagePalette("RGB", list(range(256)) * 2) - def test_getcolor(self): - palette = ImagePalette.ImagePalette() +def test_getcolor(): - test_map = {} - for i in range(256): - test_map[palette.getcolor((i, i, i))] = i + palette = ImagePalette.ImagePalette() - self.assertEqual(len(test_map), 256) - self.assertRaises(ValueError, palette.getcolor, (1, 2, 3)) + test_map = {} + for i in range(256): + test_map[palette.getcolor((i, i, i))] = i - # Test unknown color specifier - self.assertRaises(ValueError, palette.getcolor, "unknown") + assert len(test_map) == 256 + with pytest.raises(ValueError): + palette.getcolor((1, 2, 3)) - def test_file(self): + # Test unknown color specifier + with pytest.raises(ValueError): + palette.getcolor("unknown") - palette = ImagePalette.ImagePalette("RGB", list(range(256)) * 3) - f = self.tempfile("temp.lut") +def test_file(tmp_path): + palette = ImagePalette.ImagePalette("RGB", list(range(256)) * 3) + + f = str(tmp_path / "temp.lut") + + palette.save(f) + + p = ImagePalette.load(f) + + # load returns raw palette information + assert len(p[0]) == 768 + assert p[1] == "RGB" + + p = ImagePalette.raw(p[1], p[0]) + assert isinstance(p, ImagePalette.ImagePalette) + assert p.palette == palette.tobytes() + + +def test_make_linear_lut(): + # Arrange + black = 0 + white = 255 + + # Act + lut = ImagePalette.make_linear_lut(black, white) + + # Assert + assert isinstance(lut, list) + assert len(lut) == 256 + # Check values + for i in range(0, len(lut)): + assert lut[i] == i + + +def test_make_linear_lut_not_yet_implemented(): + # Update after FIXME + # Arrange + black = 1 + white = 255 + + # Act + with pytest.raises(NotImplementedError): + ImagePalette.make_linear_lut(black, white) + + +def test_make_gamma_lut(): + # Arrange + exp = 5 + + # Act + lut = ImagePalette.make_gamma_lut(exp) + + # Assert + assert isinstance(lut, list) + assert len(lut) == 256 + # Check a few values + assert lut[0] == 0 + assert lut[63] == 0 + assert lut[127] == 8 + assert lut[191] == 60 + assert lut[255] == 255 + + +def test_rawmode_valueerrors(tmp_path): + # Arrange + palette = ImagePalette.raw("RGB", list(range(256)) * 3) + + # Act / Assert + with pytest.raises(ValueError): + palette.tobytes() + with pytest.raises(ValueError): + palette.getcolor((1, 2, 3)) + f = str(tmp_path / "temp.lut") + with pytest.raises(ValueError): palette.save(f) - p = ImagePalette.load(f) - # load returns raw palette information - self.assertEqual(len(p[0]), 768) - self.assertEqual(p[1], "RGB") +def test_getdata(): + # Arrange + data_in = list(range(256)) * 3 + palette = ImagePalette.ImagePalette("RGB", data_in) - p = ImagePalette.raw(p[1], p[0]) - self.assertIsInstance(p, ImagePalette.ImagePalette) - self.assertEqual(p.palette, palette.tobytes()) + # Act + mode, data_out = palette.getdata() - def test_make_linear_lut(self): - # Arrange - black = 0 - white = 255 + # Assert + assert mode == "RGB;L" - # Act - lut = ImagePalette.make_linear_lut(black, white) - # Assert - self.assertIsInstance(lut, list) - self.assertEqual(len(lut), 256) - # Check values - for i in range(0, len(lut)): - self.assertEqual(lut[i], i) +def test_rawmode_getdata(): + # Arrange + data_in = list(range(256)) * 3 + palette = ImagePalette.raw("RGB", data_in) - def test_make_linear_lut_not_yet_implemented(self): - # Update after FIXME - # Arrange - black = 1 - white = 255 + # Act + rawmode, data_out = palette.getdata() - # Act - self.assertRaises( - NotImplementedError, ImagePalette.make_linear_lut, black, white - ) + # Assert + assert rawmode == "RGB" + assert data_in == data_out - def test_make_gamma_lut(self): - # Arrange - exp = 5 - # Act - lut = ImagePalette.make_gamma_lut(exp) +def test_2bit_palette(tmp_path): + # issue #2258, 2 bit palettes are corrupted. + outfile = str(tmp_path / "temp.png") - # Assert - self.assertIsInstance(lut, list) - self.assertEqual(len(lut), 256) - # Check a few values - self.assertEqual(lut[0], 0) - self.assertEqual(lut[63], 0) - self.assertEqual(lut[127], 8) - self.assertEqual(lut[191], 60) - self.assertEqual(lut[255], 255) + rgb = b"\x00" * 2 + b"\x01" * 2 + b"\x02" * 2 + img = Image.frombytes("P", (6, 1), rgb) + img.putpalette(b"\xFF\x00\x00\x00\xFF\x00\x00\x00\xFF") # RGB + img.save(outfile, format="PNG") - def test_rawmode_valueerrors(self): - # Arrange - palette = ImagePalette.raw("RGB", list(range(256)) * 3) + with Image.open(outfile) as reloaded: + assert_image_equal(img, reloaded) - # Act / Assert - self.assertRaises(ValueError, palette.tobytes) - self.assertRaises(ValueError, palette.getcolor, (1, 2, 3)) - f = self.tempfile("temp.lut") - self.assertRaises(ValueError, palette.save, f) - def test_getdata(self): - # Arrange - data_in = list(range(256)) * 3 - palette = ImagePalette.ImagePalette("RGB", data_in) - - # Act - mode, data_out = palette.getdata() - - # Assert - self.assertEqual(mode, "RGB;L") - - def test_rawmode_getdata(self): - # Arrange - data_in = list(range(256)) * 3 - palette = ImagePalette.raw("RGB", data_in) - - # Act - rawmode, data_out = palette.getdata() - - # Assert - self.assertEqual(rawmode, "RGB") - self.assertEqual(data_in, data_out) - - def test_2bit_palette(self): - # issue #2258, 2 bit palettes are corrupted. - outfile = self.tempfile("temp.png") - - rgb = b"\x00" * 2 + b"\x01" * 2 + b"\x02" * 2 - img = Image.frombytes("P", (6, 1), rgb) - img.putpalette(b"\xFF\x00\x00\x00\xFF\x00\x00\x00\xFF") # RGB - img.save(outfile, format="PNG") - - with Image.open(outfile) as reloaded: - assert_image_equal(img, reloaded) - - def test_invalid_palette(self): - self.assertRaises(IOError, ImagePalette.load, "Tests/images/hopper.jpg") +def test_invalid_palette(): + with pytest.raises(OSError): + ImagePalette.load("Tests/images/hopper.jpg") diff --git a/Tests/test_imagepath.py b/Tests/test_imagepath.py index 35c78cd3b..0835fdb43 100644 --- a/Tests/test_imagepath.py +++ b/Tests/test_imagepath.py @@ -1,80 +1,181 @@ import array +import math import struct +import pytest + from PIL import Image, ImagePath -from .helper import PillowTestCase + +def test_path(): + + p = ImagePath.Path(list(range(10))) + + # sequence interface + assert len(p) == 5 + assert p[0] == (0.0, 1.0) + assert p[-1] == (8.0, 9.0) + assert list(p[:1]) == [(0.0, 1.0)] + with pytest.raises(TypeError) as cm: + p["foo"] + assert str(cm.value) == "Path indices must be integers, not str" + assert list(p) == [(0.0, 1.0), (2.0, 3.0), (4.0, 5.0), (6.0, 7.0), (8.0, 9.0)] + + # method sanity check + assert p.tolist() == [ + (0.0, 1.0), + (2.0, 3.0), + (4.0, 5.0), + (6.0, 7.0), + (8.0, 9.0), + ] + assert p.tolist(1) == [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0] + + assert p.getbbox() == (0.0, 1.0, 8.0, 9.0) + + assert p.compact(5) == 2 + assert list(p) == [(0.0, 1.0), (4.0, 5.0), (8.0, 9.0)] + + p.transform((1, 0, 1, 0, 1, 1)) + assert list(p) == [(1.0, 2.0), (5.0, 6.0), (9.0, 10.0)] + + # alternative constructors + p = ImagePath.Path([0, 1]) + assert list(p) == [(0.0, 1.0)] + p = ImagePath.Path([0.0, 1.0]) + assert list(p) == [(0.0, 1.0)] + p = ImagePath.Path([0, 1]) + assert list(p) == [(0.0, 1.0)] + p = ImagePath.Path([(0, 1)]) + assert list(p) == [(0.0, 1.0)] + p = ImagePath.Path(p) + assert list(p) == [(0.0, 1.0)] + p = ImagePath.Path(p.tolist(0)) + assert list(p) == [(0.0, 1.0)] + p = ImagePath.Path(p.tolist(1)) + assert list(p) == [(0.0, 1.0)] + p = ImagePath.Path(array.array("f", [0, 1])) + assert list(p) == [(0.0, 1.0)] + + arr = array.array("f", [0, 1]) + if hasattr(arr, "tobytes"): + p = ImagePath.Path(arr.tobytes()) + else: + p = ImagePath.Path(arr.tostring()) + assert list(p) == [(0.0, 1.0)] -class TestImagePath(PillowTestCase): - def test_path(self): +def test_invalid_coords(): + # Arrange + coords = ["a", "b"] - p = ImagePath.Path(list(range(10))) + # Act / Assert + with pytest.raises(SystemError): + ImagePath.Path(coords) - # sequence interface - self.assertEqual(len(p), 5) - self.assertEqual(p[0], (0.0, 1.0)) - self.assertEqual(p[-1], (8.0, 9.0)) - self.assertEqual(list(p[:1]), [(0.0, 1.0)]) - with self.assertRaises(TypeError) as cm: - p["foo"] - self.assertEqual(str(cm.exception), "Path indices must be integers, not str") - self.assertEqual( - list(p), [(0.0, 1.0), (2.0, 3.0), (4.0, 5.0), (6.0, 7.0), (8.0, 9.0)] - ) - # method sanity check - self.assertEqual( - p.tolist(), [(0.0, 1.0), (2.0, 3.0), (4.0, 5.0), (6.0, 7.0), (8.0, 9.0)] - ) - self.assertEqual( - p.tolist(1), [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0] - ) +def test_path_odd_number_of_coordinates(): + # Arrange + coords = [0] - self.assertEqual(p.getbbox(), (0.0, 1.0, 8.0, 9.0)) + # Act / Assert + with pytest.raises(ValueError) as e: + ImagePath.Path(coords) - self.assertEqual(p.compact(5), 2) - self.assertEqual(list(p), [(0.0, 1.0), (4.0, 5.0), (8.0, 9.0)]) + assert str(e.value) == "wrong number of coordinates" - p.transform((1, 0, 1, 0, 1, 1)) - self.assertEqual(list(p), [(1.0, 2.0), (5.0, 6.0), (9.0, 10.0)]) - # alternative constructors - p = ImagePath.Path([0, 1]) - self.assertEqual(list(p), [(0.0, 1.0)]) - p = ImagePath.Path([0.0, 1.0]) - self.assertEqual(list(p), [(0.0, 1.0)]) - p = ImagePath.Path([0, 1]) - self.assertEqual(list(p), [(0.0, 1.0)]) - p = ImagePath.Path([(0, 1)]) - self.assertEqual(list(p), [(0.0, 1.0)]) - p = ImagePath.Path(p) - self.assertEqual(list(p), [(0.0, 1.0)]) - p = ImagePath.Path(p.tolist(0)) - self.assertEqual(list(p), [(0.0, 1.0)]) - p = ImagePath.Path(p.tolist(1)) - self.assertEqual(list(p), [(0.0, 1.0)]) - p = ImagePath.Path(array.array("f", [0, 1])) - self.assertEqual(list(p), [(0.0, 1.0)]) +@pytest.mark.parametrize( + "coords, expected", + [ + ([0, 1, 2, 3], (0.0, 1.0, 2.0, 3.0)), + ([3, 2, 1, 0], (1.0, 0.0, 3.0, 2.0)), + ], +) +def test_getbbox(coords, expected): + # Arrange + p = ImagePath.Path(coords) - arr = array.array("f", [0, 1]) - if hasattr(arr, "tobytes"): - p = ImagePath.Path(arr.tobytes()) - else: - p = ImagePath.Path(arr.tostring()) - self.assertEqual(list(p), [(0.0, 1.0)]) + # Act / Assert + assert p.getbbox() == expected - def test_overflow_segfault(self): - # Some Pythons fail getting the argument as an integer, and it falls - # through to the sequence. Seeing this on 32-bit Windows. - with self.assertRaises((TypeError, MemoryError)): - # post patch, this fails with a memory error - x = evil() - # This fails due to the invalid malloc above, - # and segfaults - for i in range(200000): - x[i] = b"0" * 16 +def test_getbbox_no_args(): + # Arrange + p = ImagePath.Path([0, 1, 2, 3]) + + # Act / Assert + with pytest.raises(TypeError): + p.getbbox(1) + + +@pytest.mark.parametrize( + "coords, expected", + [ + (0, []), + (list(range(6)), [(0.0, 3.0), (4.0, 9.0), (8.0, 15.0)]), + ], +) +def test_map(coords, expected): + # Arrange + p = ImagePath.Path(coords) + + # Act + # Modifies the path in-place + p.map(lambda x, y: (x * 2, y * 3)) + + # Assert + assert list(p) == expected + + +def test_transform(): + # Arrange + p = ImagePath.Path([0, 1, 2, 3]) + theta = math.pi / 15 + + # Act + # Affine transform, in-place + p.transform( + (math.cos(theta), math.sin(theta), 20, -math.sin(theta), math.cos(theta), 20), + ) + + # Assert + assert p.tolist() == [ + (20.20791169081776, 20.978147600733806), + (22.58003027392089, 22.518619420565898), + ] + + +def test_transform_with_wrap(): + # Arrange + p = ImagePath.Path([0, 1, 2, 3]) + theta = math.pi / 15 + + # Act + # Affine transform, in-place, with wrap parameter + p.transform( + (math.cos(theta), math.sin(theta), 20, -math.sin(theta), math.cos(theta), 20), + 1.0, + ) + + # Assert + assert p.tolist() == [ + (0.20791169081775962, 20.978147600733806), + (0.5800302739208902, 22.518619420565898), + ] + + +def test_overflow_segfault(): + # Some Pythons fail getting the argument as an integer, and it falls + # through to the sequence. Seeing this on 32-bit Windows. + with pytest.raises((TypeError, MemoryError)): + # post patch, this fails with a memory error + x = evil() + + # This fails due to the invalid malloc above, + # and segfaults + for i in range(200000): + x[i] = b"0" * 16 class evil: diff --git a/Tests/test_imageqt.py b/Tests/test_imageqt.py index f69a21d2a..cf4aba982 100644 --- a/Tests/test_imageqt.py +++ b/Tests/test_imageqt.py @@ -1,71 +1,42 @@ +import pytest + from PIL import ImageQt -from .helper import PillowTestCase, hopper +from .helper import hopper if ImageQt.qt_is_installed: from PIL.ImageQt import qRgba - def skip_if_qt_is_not_installed(_): - pass + +@pytest.mark.skipif(not ImageQt.qt_is_installed, reason="Qt bindings are not installed") +def test_rgb(): + # from https://doc.qt.io/archives/qt-4.8/qcolor.html + # typedef QRgb + # An ARGB quadruplet on the format #AARRGGBB, + # equivalent to an unsigned int. + if ImageQt.qt_version == "side6": + from PySide6.QtGui import qRgb + elif ImageQt.qt_version == "5": + from PyQt5.QtGui import qRgb + elif ImageQt.qt_version == "side2": + from PySide2.QtGui import qRgb + + assert qRgb(0, 0, 0) == qRgba(0, 0, 0, 255) + + def checkrgb(r, g, b): + val = ImageQt.rgb(r, g, b) + val = val % 2 ** 24 # drop the alpha + assert val >> 16 == r + assert ((val >> 8) % 2 ** 8) == g + assert val % 2 ** 8 == b + + checkrgb(0, 0, 0) + checkrgb(255, 0, 0) + checkrgb(0, 255, 0) + checkrgb(0, 0, 255) -else: - - def skip_if_qt_is_not_installed(test_case): - test_case.skipTest("Qt bindings are not installed") - - -class PillowQtTestCase: - def setUp(self): - skip_if_qt_is_not_installed(self) - - def tearDown(self): - pass - - -class PillowQPixmapTestCase(PillowQtTestCase): - def setUp(self): - super().setUp() - try: - if ImageQt.qt_version == "5": - from PyQt5.QtGui import QGuiApplication - elif ImageQt.qt_version == "side2": - from PySide2.QtGui import QGuiApplication - except ImportError: - self.skipTest("QGuiApplication not installed") - - self.app = QGuiApplication([]) - - def tearDown(self): - super().tearDown() - self.app.quit() - - -class TestImageQt(PillowQtTestCase, PillowTestCase): - def test_rgb(self): - # from https://doc.qt.io/archives/qt-4.8/qcolor.html - # typedef QRgb - # An ARGB quadruplet on the format #AARRGGBB, - # equivalent to an unsigned int. - if ImageQt.qt_version == "5": - from PyQt5.QtGui import qRgb - elif ImageQt.qt_version == "side2": - from PySide2.QtGui import qRgb - - self.assertEqual(qRgb(0, 0, 0), qRgba(0, 0, 0, 255)) - - def checkrgb(r, g, b): - val = ImageQt.rgb(r, g, b) - val = val % 2 ** 24 # drop the alpha - self.assertEqual(val >> 16, r) - self.assertEqual(((val >> 8) % 2 ** 8), g) - self.assertEqual(val % 2 ** 8, b) - - checkrgb(0, 0, 0) - checkrgb(255, 0, 0) - checkrgb(0, 255, 0) - checkrgb(0, 0, 255) - - def test_image(self): - for mode in ("1", "RGB", "RGBA", "L", "P"): - ImageQt.ImageQt(hopper(mode)) +@pytest.mark.skipif(not ImageQt.qt_is_installed, reason="Qt bindings are not installed") +def test_image(): + for mode in ("1", "RGB", "RGBA", "L", "P"): + ImageQt.ImageQt(hopper(mode)) diff --git a/Tests/test_imagesequence.py b/Tests/test_imagesequence.py index 741d71a1c..7cf237b46 100644 --- a/Tests/test_imagesequence.py +++ b/Tests/test_imagesequence.py @@ -1,98 +1,106 @@ +import pytest + from PIL import Image, ImageSequence, TiffImagePlugin -from .helper import PillowTestCase, assert_image_equal, hopper +from .helper import assert_image_equal, hopper, skip_unless_feature -class TestImageSequence(PillowTestCase): - def test_sanity(self): +def test_sanity(tmp_path): - test_file = self.tempfile("temp.im") + test_file = str(tmp_path / "temp.im") - im = hopper("RGB") - im.save(test_file) + im = hopper("RGB") + im.save(test_file) - seq = ImageSequence.Iterator(im) + seq = ImageSequence.Iterator(im) - index = 0 - for frame in seq: - assert_image_equal(im, frame) - self.assertEqual(im.tell(), index) - index += 1 + index = 0 + for frame in seq: + assert_image_equal(im, frame) + assert im.tell() == index + index += 1 - self.assertEqual(index, 1) + assert index == 1 - self.assertRaises(AttributeError, ImageSequence.Iterator, 0) + with pytest.raises(AttributeError): + ImageSequence.Iterator(0) - def test_iterator(self): - with Image.open("Tests/images/multipage.tiff") as im: - i = ImageSequence.Iterator(im) - for index in range(0, im.n_frames): - self.assertEqual(i[index], next(i)) - self.assertRaises(IndexError, lambda: i[index + 1]) - self.assertRaises(StopIteration, next, i) - def test_iterator_min_frame(self): - with Image.open("Tests/images/hopper.psd") as im: - i = ImageSequence.Iterator(im) - for index in range(1, im.n_frames): - self.assertEqual(i[index], next(i)) +def test_iterator(): + with Image.open("Tests/images/multipage.tiff") as im: + i = ImageSequence.Iterator(im) + for index in range(0, im.n_frames): + assert i[index] == next(i) + with pytest.raises(IndexError): + i[index + 1] + with pytest.raises(StopIteration): + next(i) - def _test_multipage_tiff(self): - with Image.open("Tests/images/multipage.tiff") as im: - for index, frame in enumerate(ImageSequence.Iterator(im)): - frame.load() - self.assertEqual(index, im.tell()) - frame.convert("RGB") - def test_tiff(self): - self._test_multipage_tiff() +def test_iterator_min_frame(): + with Image.open("Tests/images/hopper.psd") as im: + i = ImageSequence.Iterator(im) + for index in range(1, im.n_frames): + assert i[index] == next(i) - def test_libtiff(self): - codecs = dir(Image.core) - if "libtiff_encoder" not in codecs or "libtiff_decoder" not in codecs: - self.skipTest("tiff support not available") +def _test_multipage_tiff(): + with Image.open("Tests/images/multipage.tiff") as im: + for index, frame in enumerate(ImageSequence.Iterator(im)): + frame.load() + assert index == im.tell() + frame.convert("RGB") - TiffImagePlugin.READ_LIBTIFF = True - self._test_multipage_tiff() - TiffImagePlugin.READ_LIBTIFF = False - def test_consecutive(self): - with Image.open("Tests/images/multipage.tiff") as im: - firstFrame = None - for frame in ImageSequence.Iterator(im): - if firstFrame is None: - firstFrame = frame.copy() - for frame in ImageSequence.Iterator(im): - assert_image_equal(frame, firstFrame) - break +def test_tiff(): + _test_multipage_tiff() - def test_palette_mmap(self): - # Using mmap in ImageFile can require to reload the palette. - with Image.open("Tests/images/multipage-mmap.tiff") as im: - color1 = im.getpalette()[0:3] - im.seek(0) - color2 = im.getpalette()[0:3] - self.assertEqual(color1, color2) - def test_all_frames(self): - # Test a single image - with Image.open("Tests/images/iss634.gif") as im: - ims = ImageSequence.all_frames(im) +@skip_unless_feature("libtiff") +def test_libtiff(): + TiffImagePlugin.READ_LIBTIFF = True + _test_multipage_tiff() + TiffImagePlugin.READ_LIBTIFF = False - self.assertEqual(len(ims), 42) - for i, im_frame in enumerate(ims): - self.assertFalse(im_frame is im) - im.seek(i) - assert_image_equal(im, im_frame) +def test_consecutive(): + with Image.open("Tests/images/multipage.tiff") as im: + firstFrame = None + for frame in ImageSequence.Iterator(im): + if firstFrame is None: + firstFrame = frame.copy() + for frame in ImageSequence.Iterator(im): + assert_image_equal(frame, firstFrame) + break - # Test a series of images - ims = ImageSequence.all_frames([im, hopper(), im]) - self.assertEqual(len(ims), 85) - # Test an operation - ims = ImageSequence.all_frames(im, lambda im_frame: im_frame.rotate(90)) - for i, im_frame in enumerate(ims): - im.seek(i) - assert_image_equal(im.rotate(90), im_frame) +def test_palette_mmap(): + # Using mmap in ImageFile can require to reload the palette. + with Image.open("Tests/images/multipage-mmap.tiff") as im: + color1 = im.getpalette()[0:3] + im.seek(0) + color2 = im.getpalette()[0:3] + assert color1 == color2 + + +def test_all_frames(): + # Test a single image + with Image.open("Tests/images/iss634.gif") as im: + ims = ImageSequence.all_frames(im) + + assert len(ims) == 42 + for i, im_frame in enumerate(ims): + assert im_frame is not im + + im.seek(i) + assert_image_equal(im, im_frame) + + # Test a series of images + ims = ImageSequence.all_frames([im, hopper(), im]) + assert len(ims) == 85 + + # Test an operation + ims = ImageSequence.all_frames(im, lambda im_frame: im_frame.rotate(90)) + for i, im_frame in enumerate(ims): + im.seek(i) + assert_image_equal(im.rotate(90), im_frame) diff --git a/Tests/test_imageshow.py b/Tests/test_imageshow.py index 0d513a47d..78e80f521 100644 --- a/Tests/test_imageshow.py +++ b/Tests/test_imageshow.py @@ -1,7 +1,8 @@ import pytest + from PIL import Image, ImageShow -from .helper import hopper, is_win32, on_ci, on_github_actions +from .helper import hopper, is_win32, on_ci def test_sanity(): @@ -17,19 +18,22 @@ def test_register(): ImageShow._viewers.pop() -def test_viewer_show(): +@pytest.mark.parametrize( + "order", + [-1, 0], +) +def test_viewer_show(order): class TestViewer(ImageShow.Viewer): - methodCalled = False - def show_image(self, image, **options): self.methodCalled = True return True viewer = TestViewer() - ImageShow.register(viewer, -1) + ImageShow.register(viewer, order) for mode in ("1", "I;16", "LA", "RGB", "RGBA"): - with hopper() as im: + viewer.methodCalled = False + with hopper(mode) as im: assert ImageShow.show(im) assert viewer.methodCalled @@ -38,8 +42,8 @@ def test_viewer_show(): @pytest.mark.skipif( - not on_ci() or (is_win32() and on_github_actions()), - reason="Only run on CIs; hangs on Windows on GitHub Actions", + not on_ci() or is_win32(), + reason="Only run on CIs; hangs on Windows CIs", ) def test_show(): for mode in ("1", "I;16", "LA", "RGB", "RGBA"): diff --git a/Tests/test_imagestat.py b/Tests/test_imagestat.py index 6c70193ce..9474ff6f9 100644 --- a/Tests/test_imagestat.py +++ b/Tests/test_imagestat.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image, ImageStat from .helper import hopper diff --git a/Tests/test_imagetk.py b/Tests/test_imagetk.py index aba378714..928b8cbd1 100644 --- a/Tests/test_imagetk.py +++ b/Tests/test_imagetk.py @@ -1,87 +1,92 @@ -import unittest +import pytest from PIL import Image -from .helper import PillowTestCase, assert_image_equal, hopper +from .helper import assert_image_equal, hopper try: - from PIL import ImageTk - import tkinter as tk + from PIL import ImageTk + dir(ImageTk) HAS_TK = True except (OSError, ImportError): - # Skipped via setUp() + # Skipped via pytestmark HAS_TK = False TK_MODES = ("1", "L", "P", "RGB", "RGBA") -@unittest.skipUnless(HAS_TK, "Tk not installed") -class TestImageTk(PillowTestCase): - def setUp(self): - try: - # setup tk - tk.Frame() - # root = tk.Tk() - except tk.TclError as v: - self.skipTest("TCL Error: %s" % v) +pytestmark = pytest.mark.skipif(not HAS_TK, reason="Tk not installed") - def test_kw(self): - TEST_JPG = "Tests/images/hopper.jpg" - TEST_PNG = "Tests/images/hopper.png" - with Image.open(TEST_JPG) as im1: - with Image.open(TEST_PNG) as im2: - with open(TEST_PNG, "rb") as fp: - data = fp.read() - kw = {"file": TEST_JPG, "data": data} - # Test "file" - im = ImageTk._get_image_from_kw(kw) - assert_image_equal(im, im1) +def setup_module(): + try: + # setup tk + tk.Frame() + # root = tk.Tk() + except tk.TclError as v: + pytest.skip(f"TCL Error: {v}") - # Test "data" - im = ImageTk._get_image_from_kw(kw) - assert_image_equal(im, im2) - # Test no relevant entry - im = ImageTk._get_image_from_kw(kw) - self.assertIsNone(im) +def test_kw(): + TEST_JPG = "Tests/images/hopper.jpg" + TEST_PNG = "Tests/images/hopper.png" + with Image.open(TEST_JPG) as im1: + with Image.open(TEST_PNG) as im2: + with open(TEST_PNG, "rb") as fp: + data = fp.read() + kw = {"file": TEST_JPG, "data": data} - def test_photoimage(self): - for mode in TK_MODES: - # test as image: - im = hopper(mode) + # Test "file" + im = ImageTk._get_image_from_kw(kw) + assert_image_equal(im, im1) - # this should not crash - im_tk = ImageTk.PhotoImage(im) + # Test "data" + im = ImageTk._get_image_from_kw(kw) + assert_image_equal(im, im2) - self.assertEqual(im_tk.width(), im.width) - self.assertEqual(im_tk.height(), im.height) + # Test no relevant entry + im = ImageTk._get_image_from_kw(kw) + assert im is None - reloaded = ImageTk.getimage(im_tk) - assert_image_equal(reloaded, im.convert("RGBA")) - def test_photoimage_blank(self): - # test a image using mode/size: - for mode in TK_MODES: - im_tk = ImageTk.PhotoImage(mode, (100, 100)) - - self.assertEqual(im_tk.width(), 100) - self.assertEqual(im_tk.height(), 100) - - # reloaded = ImageTk.getimage(im_tk) - # assert_image_equal(reloaded, im) - - def test_bitmapimage(self): - im = hopper("1") +def test_photoimage(): + for mode in TK_MODES: + # test as image: + im = hopper(mode) # this should not crash - im_tk = ImageTk.BitmapImage(im) + im_tk = ImageTk.PhotoImage(im) - self.assertEqual(im_tk.width(), im.width) - self.assertEqual(im_tk.height(), im.height) + assert im_tk.width() == im.width + assert im_tk.height() == im.height + + reloaded = ImageTk.getimage(im_tk) + assert_image_equal(reloaded, im.convert("RGBA")) + + +def test_photoimage_blank(): + # test a image using mode/size: + for mode in TK_MODES: + im_tk = ImageTk.PhotoImage(mode, (100, 100)) + + assert im_tk.width() == 100 + assert im_tk.height() == 100 # reloaded = ImageTk.getimage(im_tk) # assert_image_equal(reloaded, im) + + +def test_bitmapimage(): + im = hopper("1") + + # this should not crash + im_tk = ImageTk.BitmapImage(im) + + assert im_tk.width() == im.width + assert im_tk.height() == im.height + + # reloaded = ImageTk.getimage(im_tk) + # assert_image_equal(reloaded, im) diff --git a/Tests/test_imagewin.py b/Tests/test_imagewin.py index 1cd8c674e..9d64d17a3 100644 --- a/Tests/test_imagewin.py +++ b/Tests/test_imagewin.py @@ -1,11 +1,11 @@ -import unittest +import pytest from PIL import ImageWin -from .helper import PillowTestCase, hopper, is_win32 +from .helper import hopper, is_win32 -class TestImageWin(PillowTestCase): +class TestImageWin: def test_sanity(self): dir(ImageWin) @@ -18,7 +18,7 @@ class TestImageWin(PillowTestCase): dc2 = int(hdc) # Assert - self.assertEqual(dc2, 50) + assert dc2 == 50 def test_hwnd(self): # Arrange @@ -29,11 +29,11 @@ class TestImageWin(PillowTestCase): wnd2 = int(hwnd) # Assert - self.assertEqual(wnd2, 50) + assert wnd2 == 50 -@unittest.skipUnless(is_win32(), "Windows only") -class TestImageWinDib(PillowTestCase): +@pytest.mark.skipif(not is_win32(), reason="Windows only") +class TestImageWinDib: def test_dib_image(self): # Arrange im = hopper() @@ -42,7 +42,7 @@ class TestImageWinDib(PillowTestCase): dib = ImageWin.Dib(im) # Assert - self.assertEqual(dib.size, im.size) + assert dib.size == im.size def test_dib_mode_string(self): # Arrange @@ -53,7 +53,7 @@ class TestImageWinDib(PillowTestCase): dib = ImageWin.Dib(mode, size) # Assert - self.assertEqual(dib.size, (128, 128)) + assert dib.size == (128, 128) def test_dib_paste(self): # Arrange @@ -67,7 +67,7 @@ class TestImageWinDib(PillowTestCase): dib.paste(im) # Assert - self.assertEqual(dib.size, (128, 128)) + assert dib.size == (128, 128) def test_dib_paste_bbox(self): # Arrange @@ -82,7 +82,7 @@ class TestImageWinDib(PillowTestCase): dib.paste(im, bbox) # Assert - self.assertEqual(dib.size, (128, 128)) + assert dib.size == (128, 128) def test_dib_frombytes_tobytes_roundtrip(self): # Arrange @@ -95,7 +95,7 @@ class TestImageWinDib(PillowTestCase): dib2 = ImageWin.Dib(mode, size) # Confirm they're different - self.assertNotEqual(dib1.tobytes(), dib2.tobytes()) + assert dib1.tobytes() != dib2.tobytes() # Act # Make one the same as the using tobytes()/frombytes() @@ -104,4 +104,4 @@ class TestImageWinDib(PillowTestCase): # Assert # Confirm they're the same - self.assertEqual(dib1.tobytes(), dib2.tobytes()) + assert dib1.tobytes() == dib2.tobytes() diff --git a/Tests/test_lib_image.py b/Tests/test_lib_image.py index 7115e62ad..37ed3659d 100644 --- a/Tests/test_lib_image.py +++ b/Tests/test_lib_image.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image diff --git a/Tests/test_lib_pack.py b/Tests/test_lib_pack.py index f9e6e7c4c..8a1460346 100644 --- a/Tests/test_lib_pack.py +++ b/Tests/test_lib_pack.py @@ -1,13 +1,13 @@ import sys -from PIL import Image +import pytest -from .helper import PillowTestCase +from PIL import Image X = 255 -class TestLibPack(PillowTestCase): +class TestLibPack: def assert_pack(self, mode, rawmode, data, *pixels): """ data - either raw bytes with data or just number of bytes in rawmode. @@ -20,7 +20,7 @@ class TestLibPack(PillowTestCase): data_len = data * len(pixels) data = bytes(range(1, data_len + 1)) - self.assertEqual(data, im.tobytes("raw", rawmode)) + assert data == im.tobytes("raw", rawmode) def test_1(self): self.assert_pack("1", "1", b"\x01", 0, 0, 0, 0, 0, 0, 0, X) @@ -222,7 +222,7 @@ class TestLibPack(PillowTestCase): ) -class TestLibUnpack(PillowTestCase): +class TestLibUnpack: def assert_unpack(self, mode, rawmode, data, *pixels): """ data - either raw bytes with data or just number of bytes in rawmode. @@ -234,7 +234,7 @@ class TestLibUnpack(PillowTestCase): im = Image.frombytes(mode, (len(pixels), 1), data, "raw", rawmode, 0, 1) for x, pixel in enumerate(pixels): - self.assertEqual(pixel, im.getpixel((x, 0))) + assert pixel == im.getpixel((x, 0)) def test_1(self): self.assert_unpack("1", "1", b"\x01", 0, 0, 0, 0, 0, 0, 0, X) @@ -719,6 +719,9 @@ class TestLibUnpack(PillowTestCase): self.assert_unpack("CMYK", "CMYK;16N", 8, (1, 3, 5, 7), (9, 11, 13, 15)) def test_value_error(self): - self.assertRaises(ValueError, self.assert_unpack, "L", "L", 0, 0) - self.assertRaises(ValueError, self.assert_unpack, "RGB", "RGB", 2, 0) - self.assertRaises(ValueError, self.assert_unpack, "CMYK", "CMYK", 2, 0) + with pytest.raises(ValueError): + self.assert_unpack("L", "L", 0, 0) + with pytest.raises(ValueError): + self.assert_unpack("RGB", "RGB", 2, 0) + with pytest.raises(ValueError): + self.assert_unpack("CMYK", "CMYK", 2, 0) diff --git a/Tests/test_locale.py b/Tests/test_locale.py index c5e54883d..7a07fbbe0 100644 --- a/Tests/test_locale.py +++ b/Tests/test_locale.py @@ -1,6 +1,7 @@ import locale import pytest + from PIL import Image # ref https://github.com/python-pillow/Pillow/issues/272 diff --git a/Tests/test_map.py b/Tests/test_map.py index bdc3a7e2c..2b65fb3f9 100644 --- a/Tests/test_map.py +++ b/Tests/test_map.py @@ -1,38 +1,36 @@ import sys -import unittest + +import pytest from PIL import Image -from .helper import PillowTestCase, is_win32 +from .helper import is_win32 -try: - import numpy -except ImportError: - numpy = None +pytestmark = pytest.mark.skipif(is_win32(), reason="Win32 does not call map_buffer") -@unittest.skipIf(is_win32(), "Win32 does not call map_buffer") -class TestMap(PillowTestCase): - def test_overflow(self): - # There is the potential to overflow comparisons in map.c - # if there are > SIZE_MAX bytes in the image or if - # the file encodes an offset that makes - # (offset + size(bytes)) > SIZE_MAX +def test_overflow(): + # There is the potential to overflow comparisons in map.c + # if there are > SIZE_MAX bytes in the image or if + # the file encodes an offset that makes + # (offset + size(bytes)) > SIZE_MAX - # Note that this image triggers the decompression bomb warning: - max_pixels = Image.MAX_IMAGE_PIXELS - Image.MAX_IMAGE_PIXELS = None + # Note that this image triggers the decompression bomb warning: + max_pixels = Image.MAX_IMAGE_PIXELS + Image.MAX_IMAGE_PIXELS = None - # This image hits the offset test. - with Image.open("Tests/images/l2rgb_read.bmp") as im: - with self.assertRaises((ValueError, MemoryError, IOError)): - im.load() + # This image hits the offset test. + with Image.open("Tests/images/l2rgb_read.bmp") as im: + with pytest.raises((ValueError, MemoryError, OSError)): + im.load() - Image.MAX_IMAGE_PIXELS = max_pixels + Image.MAX_IMAGE_PIXELS = max_pixels - @unittest.skipIf(sys.maxsize <= 2 ** 32, "requires 64-bit system") - @unittest.skipIf(numpy is None, "Numpy is not installed") - def test_ysize(self): - # Should not raise 'Integer overflow in ysize' - arr = numpy.zeros((46341, 46341), dtype=numpy.uint8) - Image.fromarray(arr) + +@pytest.mark.skipif(sys.maxsize <= 2 ** 32, reason="Requires 64-bit system") +def test_ysize(): + numpy = pytest.importorskip("numpy", reason="NumPy not installed") + + # Should not raise 'Integer overflow in ysize' + arr = numpy.zeros((46341, 46341), dtype=numpy.uint8) + Image.fromarray(arr) diff --git a/Tests/test_mode_i16.py b/Tests/test_mode_i16.py index fcd45b3c1..0571aabf4 100644 --- a/Tests/test_mode_i16.py +++ b/Tests/test_mode_i16.py @@ -1,111 +1,106 @@ from PIL import Image -from .helper import PillowTestCase, hopper +from .helper import hopper + +original = hopper().resize((32, 32)).convert("I") -class TestModeI16(PillowTestCase): +def verify(im1): + im2 = original.copy() + assert im1.size == im2.size + pix1 = im1.load() + pix2 = im2.load() + for y in range(im1.size[1]): + for x in range(im1.size[0]): + xy = x, y + p1 = pix1[xy] + p2 = pix2[xy] + assert ( + p1 == p2 + ), f"got {repr(p1)} from mode {im1.mode} at {xy}, expected {repr(p2)}" - original = hopper().resize((32, 32)).convert("I") - def verify(self, im1): - im2 = self.original.copy() - self.assertEqual(im1.size, im2.size) - pix1 = im1.load() - pix2 = im2.load() - for y in range(im1.size[1]): - for x in range(im1.size[0]): - xy = x, y - p1 = pix1[xy] - p2 = pix2[xy] - self.assertEqual( - p1, - p2, - ( - "got {!r} from mode {} at {}, expected {!r}".format( - p1, im1.mode, xy, p2 - ) - ), - ) +def test_basic(tmp_path): + # PIL 1.1 has limited support for 16-bit image data. Check that + # create/copy/transform and save works as expected. - def test_basic(self): - # PIL 1.1 has limited support for 16-bit image data. Check that - # create/copy/transform and save works as expected. + def basic(mode): - def basic(mode): + imIn = original.convert(mode) + verify(imIn) - imIn = self.original.convert(mode) - self.verify(imIn) + w, h = imIn.size - w, h = imIn.size + imOut = imIn.copy() + verify(imOut) # copy - imOut = imIn.copy() - self.verify(imOut) # copy + imOut = imIn.transform((w, h), Image.EXTENT, (0, 0, w, h)) + verify(imOut) # transform - imOut = imIn.transform((w, h), Image.EXTENT, (0, 0, w, h)) - self.verify(imOut) # transform + filename = str(tmp_path / "temp.im") + imIn.save(filename) - filename = self.tempfile("temp.im") - imIn.save(filename) + with Image.open(filename) as imOut: - with Image.open(filename) as imOut: + verify(imIn) + verify(imOut) - self.verify(imIn) - self.verify(imOut) + imOut = imIn.crop((0, 0, w, h)) + verify(imOut) - imOut = imIn.crop((0, 0, w, h)) - self.verify(imOut) + imOut = Image.new(mode, (w, h), None) + imOut.paste(imIn.crop((0, 0, w // 2, h)), (0, 0)) + imOut.paste(imIn.crop((w // 2, 0, w, h)), (w // 2, 0)) - imOut = Image.new(mode, (w, h), None) - imOut.paste(imIn.crop((0, 0, w // 2, h)), (0, 0)) - imOut.paste(imIn.crop((w // 2, 0, w, h)), (w // 2, 0)) + verify(imIn) + verify(imOut) - self.verify(imIn) - self.verify(imOut) + imIn = Image.new(mode, (1, 1), 1) + assert imIn.getpixel((0, 0)) == 1 - imIn = Image.new(mode, (1, 1), 1) - self.assertEqual(imIn.getpixel((0, 0)), 1) + imIn.putpixel((0, 0), 2) + assert imIn.getpixel((0, 0)) == 2 - imIn.putpixel((0, 0), 2) - self.assertEqual(imIn.getpixel((0, 0)), 2) + if mode == "L": + maximum = 255 + else: + maximum = 32767 - if mode == "L": - maximum = 255 - else: - maximum = 32767 + imIn = Image.new(mode, (1, 1), 256) + assert imIn.getpixel((0, 0)) == min(256, maximum) - imIn = Image.new(mode, (1, 1), 256) - self.assertEqual(imIn.getpixel((0, 0)), min(256, maximum)) + imIn.putpixel((0, 0), 512) + assert imIn.getpixel((0, 0)) == min(512, maximum) - imIn.putpixel((0, 0), 512) - self.assertEqual(imIn.getpixel((0, 0)), min(512, maximum)) + basic("L") - basic("L") + basic("I;16") + basic("I;16B") + basic("I;16L") - basic("I;16") - basic("I;16B") - basic("I;16L") + basic("I") - basic("I") - def test_tobytes(self): - def tobytes(mode): - return Image.new(mode, (1, 1), 1).tobytes() +def test_tobytes(): + def tobytes(mode): + return Image.new(mode, (1, 1), 1).tobytes() - order = 1 if Image._ENDIAN == "<" else -1 + order = 1 if Image._ENDIAN == "<" else -1 - self.assertEqual(tobytes("L"), b"\x01") - self.assertEqual(tobytes("I;16"), b"\x01\x00") - self.assertEqual(tobytes("I;16B"), b"\x00\x01") - self.assertEqual(tobytes("I"), b"\x01\x00\x00\x00"[::order]) + assert tobytes("L") == b"\x01" + assert tobytes("I;16") == b"\x01\x00" + assert tobytes("I;16B") == b"\x00\x01" + assert tobytes("I") == b"\x01\x00\x00\x00"[::order] - def test_convert(self): - im = self.original.copy() +def test_convert(): - self.verify(im.convert("I;16")) - self.verify(im.convert("I;16").convert("L")) - self.verify(im.convert("I;16").convert("I")) + im = original.copy() - self.verify(im.convert("I;16B")) - self.verify(im.convert("I;16B").convert("L")) - self.verify(im.convert("I;16B").convert("I")) + verify(im.convert("I;16")) + verify(im.convert("I;16").convert("L")) + verify(im.convert("I;16").convert("I")) + + verify(im.convert("I;16B")) + verify(im.convert("I;16B").convert("L")) + verify(im.convert("I;16B").convert("I")) diff --git a/Tests/test_numpy.py b/Tests/test_numpy.py index 45fabee78..da367fa46 100644 --- a/Tests/test_numpy.py +++ b/Tests/test_numpy.py @@ -1,224 +1,237 @@ -import unittest - import pytest + from PIL import Image -from .helper import PillowTestCase, assert_deep_equal, assert_image, hopper - -try: - import numpy -except ImportError: - numpy = None +from .helper import assert_deep_equal, assert_image, hopper +numpy = pytest.importorskip("numpy", reason="NumPy not installed") TEST_IMAGE_SIZE = (10, 10) -@unittest.skipIf(numpy is None, "Numpy is not installed") -class TestNumpy(PillowTestCase): - def test_numpy_to_image(self): - def to_image(dtype, bands=1, boolean=0): - if bands == 1: - if boolean: - data = [0, 255] * 50 - else: - data = list(range(100)) - a = numpy.array(data, dtype=dtype) - a.shape = TEST_IMAGE_SIZE - i = Image.fromarray(a) - if list(i.getdata()) != data: - print("data mismatch for", dtype) +def test_numpy_to_image(): + def to_image(dtype, bands=1, boolean=0): + if bands == 1: + if boolean: + data = [0, 255] * 50 else: data = list(range(100)) - a = numpy.array([[x] * bands for x in data], dtype=dtype) - a.shape = TEST_IMAGE_SIZE[0], TEST_IMAGE_SIZE[1], bands - i = Image.fromarray(a) - if list(i.getchannel(0).getdata()) != list(range(100)): - print("data mismatch for", dtype) - return i - - # Check supported 1-bit integer formats - assert_image(to_image(numpy.bool, 1, 1), "1", TEST_IMAGE_SIZE) - assert_image(to_image(numpy.bool8, 1, 1), "1", TEST_IMAGE_SIZE) - - # Check supported 8-bit integer formats - assert_image(to_image(numpy.uint8), "L", TEST_IMAGE_SIZE) - assert_image(to_image(numpy.uint8, 3), "RGB", TEST_IMAGE_SIZE) - assert_image(to_image(numpy.uint8, 4), "RGBA", TEST_IMAGE_SIZE) - assert_image(to_image(numpy.int8), "I", TEST_IMAGE_SIZE) - - # Check non-fixed-size integer types - # These may fail, depending on the platform, since we have no native - # 64 bit int image types. - # assert_image(to_image(numpy.uint), "I", TEST_IMAGE_SIZE) - # assert_image(to_image(numpy.int), "I", TEST_IMAGE_SIZE) - - # Check 16-bit integer formats - if Image._ENDIAN == "<": - assert_image(to_image(numpy.uint16), "I;16", TEST_IMAGE_SIZE) + a = numpy.array(data, dtype=dtype) + a.shape = TEST_IMAGE_SIZE + i = Image.fromarray(a) + if list(i.getdata()) != data: + print("data mismatch for", dtype) else: - assert_image(to_image(numpy.uint16), "I;16B", TEST_IMAGE_SIZE) + data = list(range(100)) + a = numpy.array([[x] * bands for x in data], dtype=dtype) + a.shape = TEST_IMAGE_SIZE[0], TEST_IMAGE_SIZE[1], bands + i = Image.fromarray(a) + if list(i.getchannel(0).getdata()) != list(range(100)): + print("data mismatch for", dtype) + return i - assert_image(to_image(numpy.int16), "I", TEST_IMAGE_SIZE) + # Check supported 1-bit integer formats + assert_image(to_image(numpy.bool, 1, 1), "1", TEST_IMAGE_SIZE) + assert_image(to_image(numpy.bool8, 1, 1), "1", TEST_IMAGE_SIZE) - # Check 32-bit integer formats - assert_image(to_image(numpy.uint32), "I", TEST_IMAGE_SIZE) - assert_image(to_image(numpy.int32), "I", TEST_IMAGE_SIZE) + # Check supported 8-bit integer formats + assert_image(to_image(numpy.uint8), "L", TEST_IMAGE_SIZE) + assert_image(to_image(numpy.uint8, 3), "RGB", TEST_IMAGE_SIZE) + assert_image(to_image(numpy.uint8, 4), "RGBA", TEST_IMAGE_SIZE) + assert_image(to_image(numpy.int8), "I", TEST_IMAGE_SIZE) - # Check 64-bit integer formats - self.assertRaises(TypeError, to_image, numpy.uint64) - self.assertRaises(TypeError, to_image, numpy.int64) + # Check non-fixed-size integer types + # These may fail, depending on the platform, since we have no native + # 64-bit int image types. + # assert_image(to_image(numpy.uint), "I", TEST_IMAGE_SIZE) + # assert_image(to_image(numpy.int), "I", TEST_IMAGE_SIZE) - # Check floating-point formats - assert_image(to_image(numpy.float), "F", TEST_IMAGE_SIZE) - self.assertRaises(TypeError, to_image, numpy.float16) - assert_image(to_image(numpy.float32), "F", TEST_IMAGE_SIZE) - assert_image(to_image(numpy.float64), "F", TEST_IMAGE_SIZE) + # Check 16-bit integer formats + if Image._ENDIAN == "<": + assert_image(to_image(numpy.uint16), "I;16", TEST_IMAGE_SIZE) + else: + assert_image(to_image(numpy.uint16), "I;16B", TEST_IMAGE_SIZE) - assert_image(to_image(numpy.uint8, 2), "LA", (10, 10)) - assert_image(to_image(numpy.uint8, 3), "RGB", (10, 10)) - assert_image(to_image(numpy.uint8, 4), "RGBA", (10, 10)) + assert_image(to_image(numpy.int16), "I", TEST_IMAGE_SIZE) - # based on an erring example at - # https://stackoverflow.com/questions/10854903/what-is-causing-dimension-dependent-attributeerror-in-pil-fromarray-function - def test_3d_array(self): - size = (5, TEST_IMAGE_SIZE[0], TEST_IMAGE_SIZE[1]) - a = numpy.ones(size, dtype=numpy.uint8) - assert_image(Image.fromarray(a[1, :, :]), "L", TEST_IMAGE_SIZE) - size = (TEST_IMAGE_SIZE[0], 5, TEST_IMAGE_SIZE[1]) - a = numpy.ones(size, dtype=numpy.uint8) - assert_image(Image.fromarray(a[:, 1, :]), "L", TEST_IMAGE_SIZE) - size = (TEST_IMAGE_SIZE[0], TEST_IMAGE_SIZE[1], 5) - a = numpy.ones(size, dtype=numpy.uint8) - assert_image(Image.fromarray(a[:, :, 1]), "L", TEST_IMAGE_SIZE) + # Check 32-bit integer formats + assert_image(to_image(numpy.uint32), "I", TEST_IMAGE_SIZE) + assert_image(to_image(numpy.int32), "I", TEST_IMAGE_SIZE) - def _test_img_equals_nparray(self, img, np): - self.assertGreaterEqual(len(np.shape), 2) - np_size = np.shape[1], np.shape[0] - self.assertEqual(img.size, np_size) - px = img.load() - for x in range(0, img.size[0], int(img.size[0] / 10)): - for y in range(0, img.size[1], int(img.size[1] / 10)): - assert_deep_equal(px[x, y], np[y, x]) + # Check 64-bit integer formats + with pytest.raises(TypeError): + to_image(numpy.uint64) + with pytest.raises(TypeError): + to_image(numpy.int64) - def test_16bit(self): - with Image.open("Tests/images/16bit.cropped.tif") as img: - np_img = numpy.array(img) - self._test_img_equals_nparray(img, np_img) - self.assertEqual(np_img.dtype, numpy.dtype("u2"), - ("I;16L", "= 2 + np_size = np.shape[1], np.shape[0] + assert img.size == np_size + px = img.load() + for x in range(0, img.size[0], int(img.size[0] / 10)): + for y in range(0, img.size[1], int(img.size[1] / 10)): + assert_deep_equal(px[x, y], np[y, x]) - for mode in modes: - _to_array(*mode) - def test_point_lut(self): - # see https://github.com/python-pillow/Pillow/issues/439 +def test_16bit(): + with Image.open("Tests/images/16bit.cropped.tif") as img: + np_img = numpy.array(img) + _test_img_equals_nparray(img, np_img) + assert np_img.dtype == numpy.dtype("u2"), + ("I;16L", "", 0), (b"\x90\x1F\xA3", 8)) - self.assertEqual( - PdfParser.get_value(b"asd < 9 0 1 f A > qwe", 3), (b"\x90\x1F\xA0", 17) - ) - self.assertEqual(PdfParser.get_value(b"(asd)", 0), (b"asd", 5)) - self.assertEqual( - PdfParser.get_value(b"(asd(qwe)zxc)zzz(aaa)", 0), (b"asd(qwe)zxc", 13) - ) - self.assertEqual( - PdfParser.get_value(b"(Two \\\nwords.)", 0), (b"Two words.", 14) - ) - self.assertEqual(PdfParser.get_value(b"(Two\nlines.)", 0), (b"Two\nlines.", 12)) - self.assertEqual( - PdfParser.get_value(b"(Two\r\nlines.)", 0), (b"Two\nlines.", 13) - ) - self.assertEqual( - PdfParser.get_value(b"(Two\\nlines.)", 0), (b"Two\nlines.", 13) - ) - self.assertEqual(PdfParser.get_value(b"(One\\(paren).", 0), (b"One(paren", 12)) - self.assertEqual(PdfParser.get_value(b"(One\\)paren).", 0), (b"One)paren", 12)) - self.assertEqual(PdfParser.get_value(b"(\\0053)", 0), (b"\x053", 7)) - self.assertEqual(PdfParser.get_value(b"(\\053)", 0), (b"\x2B", 6)) - self.assertEqual(PdfParser.get_value(b"(\\53)", 0), (b"\x2B", 5)) - self.assertEqual(PdfParser.get_value(b"(\\53a)", 0), (b"\x2Ba", 6)) - self.assertEqual(PdfParser.get_value(b"(\\1111)", 0), (b"\x491", 7)) - self.assertEqual(PdfParser.get_value(b" 123 (", 0), (123, 4)) - self.assertAlmostEqual(PdfParser.get_value(b" 123.4 %", 0)[0], 123.4) - self.assertEqual(PdfParser.get_value(b" 123.4 %", 0)[1], 6) - self.assertRaises(PdfFormatError, PdfParser.get_value, b"]", 0) - d = PdfParser.get_value(b"<>", 0)[0] - self.assertIsInstance(d, PdfDict) - self.assertEqual(len(d), 2) - self.assertEqual(d.Name, "value") - self.assertEqual(d[b"Name"], b"value") - self.assertEqual(d.N, PdfName("V")) - a = PdfParser.get_value(b"[/Name (value) /N /V]", 0)[0] - self.assertIsInstance(a, list) - self.assertEqual(len(a), 4) - self.assertEqual(a[0], PdfName("Name")) - s = PdfParser.get_value( - b"<>\nstream\nabcde\nendstream<<...", 0 - )[0] - self.assertIsInstance(s, PdfStream) - self.assertEqual(s.dictionary.Name, "value") - self.assertEqual(s.decode(), b"abcde") - for name in ["CreationDate", "ModDate"]: - for date, value in { - b"20180729214124": "20180729214124", - b"D:20180729214124": "20180729214124", - b"D:2018072921": "20180729210000", - b"D:20180729214124Z": "20180729214124", - b"D:20180729214124+08'00'": "20180729134124", - b"D:20180729214124-05'00'": "20180730024124", - }.items(): - d = PdfParser.get_value( - b"<>", 0 - )[0] - self.assertEqual(time.strftime("%Y%m%d%H%M%S", getattr(d, name)), value) +def test_parsing(): + assert PdfParser.interpret_name(b"Name#23Hash") == b"Name#Hash" + assert PdfParser.interpret_name(b"Name#23Hash", as_text=True) == "Name#Hash" + assert PdfParser.get_value(b"1 2 R ", 0) == (IndirectReference(1, 2), 5) + assert PdfParser.get_value(b"true[", 0) == (True, 4) + assert PdfParser.get_value(b"false%", 0) == (False, 5) + assert PdfParser.get_value(b"null<", 0) == (None, 4) + assert PdfParser.get_value(b"%cmt\n %cmt\n 123\n", 0) == (123, 15) + assert PdfParser.get_value(b"<901FA3>", 0) == (b"\x90\x1F\xA3", 8) + assert PdfParser.get_value(b"asd < 9 0 1 f A > qwe", 3) == (b"\x90\x1F\xA0", 17) + assert PdfParser.get_value(b"(asd)", 0) == (b"asd", 5) + assert PdfParser.get_value(b"(asd(qwe)zxc)zzz(aaa)", 0) == (b"asd(qwe)zxc", 13) + assert PdfParser.get_value(b"(Two \\\nwords.)", 0) == (b"Two words.", 14) + assert PdfParser.get_value(b"(Two\nlines.)", 0) == (b"Two\nlines.", 12) + assert PdfParser.get_value(b"(Two\r\nlines.)", 0) == (b"Two\nlines.", 13) + assert PdfParser.get_value(b"(Two\\nlines.)", 0) == (b"Two\nlines.", 13) + assert PdfParser.get_value(b"(One\\(paren).", 0) == (b"One(paren", 12) + assert PdfParser.get_value(b"(One\\)paren).", 0) == (b"One)paren", 12) + assert PdfParser.get_value(b"(\\0053)", 0) == (b"\x053", 7) + assert PdfParser.get_value(b"(\\053)", 0) == (b"\x2B", 6) + assert PdfParser.get_value(b"(\\53)", 0) == (b"\x2B", 5) + assert PdfParser.get_value(b"(\\53a)", 0) == (b"\x2Ba", 6) + assert PdfParser.get_value(b"(\\1111)", 0) == (b"\x491", 7) + assert PdfParser.get_value(b" 123 (", 0) == (123, 4) + assert round(abs(PdfParser.get_value(b" 123.4 %", 0)[0] - 123.4), 7) == 0 + assert PdfParser.get_value(b" 123.4 %", 0)[1] == 6 + with pytest.raises(PdfFormatError): + PdfParser.get_value(b"]", 0) + d = PdfParser.get_value(b"<>", 0)[0] + assert isinstance(d, PdfDict) + assert len(d) == 2 + assert d.Name == "value" + assert d[b"Name"] == b"value" + assert d.N == PdfName("V") + a = PdfParser.get_value(b"[/Name (value) /N /V]", 0)[0] + assert isinstance(a, list) + assert len(a) == 4 + assert a[0] == PdfName("Name") + s = PdfParser.get_value( + b"<>\nstream\nabcde\nendstream<<...", 0 + )[0] + assert isinstance(s, PdfStream) + assert s.dictionary.Name == "value" + assert s.decode() == b"abcde" + for name in ["CreationDate", "ModDate"]: + for date, value in { + b"20180729214124": "20180729214124", + b"D:20180729214124": "20180729214124", + b"D:2018072921": "20180729210000", + b"D:20180729214124Z": "20180729214124", + b"D:20180729214124+08'00'": "20180729134124", + b"D:20180729214124-05'00'": "20180730024124", + }.items(): + d = PdfParser.get_value(b"<>", 0)[ + 0 + ] + assert time.strftime("%Y%m%d%H%M%S", getattr(d, name)) == value - def test_pdf_repr(self): - self.assertEqual(bytes(IndirectReference(1, 2)), b"1 2 R") - self.assertEqual(bytes(IndirectObjectDef(*IndirectReference(1, 2))), b"1 2 obj") - self.assertEqual(bytes(PdfName(b"Name#Hash")), b"/Name#23Hash") - self.assertEqual(bytes(PdfName("Name#Hash")), b"/Name#23Hash") - self.assertEqual( - bytes(PdfDict({b"Name": IndirectReference(1, 2)})), b"<<\n/Name 1 2 R\n>>" - ) - self.assertEqual( - bytes(PdfDict({"Name": IndirectReference(1, 2)})), b"<<\n/Name 1 2 R\n>>" - ) - self.assertEqual(pdf_repr(IndirectReference(1, 2)), b"1 2 R") - self.assertEqual( - pdf_repr(IndirectObjectDef(*IndirectReference(1, 2))), b"1 2 obj" - ) - self.assertEqual(pdf_repr(PdfName(b"Name#Hash")), b"/Name#23Hash") - self.assertEqual(pdf_repr(PdfName("Name#Hash")), b"/Name#23Hash") - self.assertEqual( - pdf_repr(PdfDict({b"Name": IndirectReference(1, 2)})), - b"<<\n/Name 1 2 R\n>>", - ) - self.assertEqual( - pdf_repr(PdfDict({"Name": IndirectReference(1, 2)})), b"<<\n/Name 1 2 R\n>>" - ) - self.assertEqual(pdf_repr(123), b"123") - self.assertEqual(pdf_repr(True), b"true") - self.assertEqual(pdf_repr(False), b"false") - self.assertEqual(pdf_repr(None), b"null") - self.assertEqual(pdf_repr(b"a)/b\\(c"), br"(a\)/b\\\(c)") - self.assertEqual( - pdf_repr([123, True, {"a": PdfName(b"b")}]), b"[ 123 true <<\n/a /b\n>> ]" - ) - self.assertEqual(pdf_repr(PdfBinary(b"\x90\x1F\xA0")), b"<901FA0>") + +def test_pdf_repr(): + assert bytes(IndirectReference(1, 2)) == b"1 2 R" + assert bytes(IndirectObjectDef(*IndirectReference(1, 2))) == b"1 2 obj" + assert bytes(PdfName(b"Name#Hash")) == b"/Name#23Hash" + assert bytes(PdfName("Name#Hash")) == b"/Name#23Hash" + assert bytes(PdfDict({b"Name": IndirectReference(1, 2)})) == b"<<\n/Name 1 2 R\n>>" + assert bytes(PdfDict({"Name": IndirectReference(1, 2)})) == b"<<\n/Name 1 2 R\n>>" + assert pdf_repr(IndirectReference(1, 2)) == b"1 2 R" + assert pdf_repr(IndirectObjectDef(*IndirectReference(1, 2))) == b"1 2 obj" + assert pdf_repr(PdfName(b"Name#Hash")) == b"/Name#23Hash" + assert pdf_repr(PdfName("Name#Hash")) == b"/Name#23Hash" + assert ( + pdf_repr(PdfDict({b"Name": IndirectReference(1, 2)})) == b"<<\n/Name 1 2 R\n>>" + ) + assert ( + pdf_repr(PdfDict({"Name": IndirectReference(1, 2)})) == b"<<\n/Name 1 2 R\n>>" + ) + assert pdf_repr(123) == b"123" + assert pdf_repr(True) == b"true" + assert pdf_repr(False) == b"false" + assert pdf_repr(None) == b"null" + assert pdf_repr(b"a)/b\\(c") == br"(a\)/b\\\(c)" + assert pdf_repr([123, True, {"a": PdfName(b"b")}]) == b"[ 123 true <<\n/a /b\n>> ]" + assert pdf_repr(PdfBinary(b"\x90\x1F\xA0")) == b"<901FA0>" diff --git a/Tests/test_pickle.py b/Tests/test_pickle.py index 5ceee33d6..a10dcec8c 100644 --- a/Tests/test_pickle.py +++ b/Tests/test_pickle.py @@ -1,125 +1,94 @@ +import pickle + +import pytest + from PIL import Image -from .helper import PillowTestCase +from .helper import skip_unless_feature -class TestPickle(PillowTestCase): - def helper_pickle_file(self, pickle, protocol=0, mode=None): - # Arrange - with Image.open("Tests/images/hopper.jpg") as im: - filename = self.tempfile("temp.pkl") - if mode: - im = im.convert(mode) +def helper_pickle_file(tmp_path, pickle, protocol, test_file, mode): + # Arrange + with Image.open(test_file) as im: + filename = str(tmp_path / "temp.pkl") + if mode: + im = im.convert(mode) - # Act - with open(filename, "wb") as f: - pickle.dump(im, f, protocol) - with open(filename, "rb") as f: - loaded_im = pickle.load(f) + # Act + with open(filename, "wb") as f: + pickle.dump(im, f, protocol) + with open(filename, "rb") as f: + loaded_im = pickle.load(f) - # Assert - self.assertEqual(im, loaded_im) + # Assert + assert im == loaded_im - def helper_pickle_string( - self, pickle, protocol=0, test_file="Tests/images/hopper.jpg", mode=None - ): - with Image.open(test_file) as im: - if mode: - im = im.convert(mode) - # Act - dumped_string = pickle.dumps(im, protocol) - loaded_im = pickle.loads(dumped_string) +def helper_pickle_string(pickle, protocol, test_file, mode): + with Image.open(test_file) as im: + if mode: + im = im.convert(mode) - # Assert - self.assertEqual(im, loaded_im) + # Act + dumped_string = pickle.dumps(im, protocol) + loaded_im = pickle.loads(dumped_string) - def test_pickle_image(self): - # Arrange - import pickle + # Assert + assert im == loaded_im - # Act / Assert - for protocol in range(0, pickle.HIGHEST_PROTOCOL + 1): - self.helper_pickle_string(pickle, protocol) - self.helper_pickle_file(pickle, protocol) - def test_cpickle_image(self): - # Arrange - try: - import cPickle - except ImportError: - return +@pytest.mark.parametrize( + ("test_file", "test_mode"), + [ + ("Tests/images/hopper.jpg", None), + ("Tests/images/hopper.jpg", "L"), + ("Tests/images/hopper.jpg", "PA"), + pytest.param( + "Tests/images/hopper.webp", None, marks=skip_unless_feature("webp") + ), + ("Tests/images/hopper.tif", None), + ("Tests/images/test-card.png", None), + ("Tests/images/zero_bb.png", None), + ("Tests/images/zero_bb_scale2.png", None), + ("Tests/images/non_zero_bb.png", None), + ("Tests/images/non_zero_bb_scale2.png", None), + ("Tests/images/p_trns_single.png", None), + ("Tests/images/pil123p.png", None), + ("Tests/images/itxt_chunks.png", None), + ], +) +def test_pickle_image(tmp_path, test_file, test_mode): + # Act / Assert + for protocol in range(0, pickle.HIGHEST_PROTOCOL + 1): + helper_pickle_string(pickle, protocol, test_file, test_mode) + helper_pickle_file(tmp_path, pickle, protocol, test_file, test_mode) - # Act / Assert - for protocol in range(0, cPickle.HIGHEST_PROTOCOL + 1): - self.helper_pickle_string(cPickle, protocol) - self.helper_pickle_file(cPickle, protocol) - def test_pickle_p_mode(self): - # Arrange - import pickle +def test_pickle_la_mode_with_palette(tmp_path): + # Arrange + filename = str(tmp_path / "temp.pkl") + with Image.open("Tests/images/hopper.jpg") as im: + im = im.convert("PA") - # Act / Assert - for test_file in [ - "Tests/images/test-card.png", - "Tests/images/zero_bb.png", - "Tests/images/zero_bb_scale2.png", - "Tests/images/non_zero_bb.png", - "Tests/images/non_zero_bb_scale2.png", - "Tests/images/p_trns_single.png", - "Tests/images/pil123p.png", - "Tests/images/itxt_chunks.png", - ]: - for protocol in range(0, pickle.HIGHEST_PROTOCOL + 1): - self.helper_pickle_string( - pickle, protocol=protocol, test_file=test_file - ) + # Act / Assert + for protocol in range(0, pickle.HIGHEST_PROTOCOL + 1): + im.mode = "LA" + with open(filename, "wb") as f: + pickle.dump(im, f, protocol) + with open(filename, "rb") as f: + loaded_im = pickle.load(f) - def test_pickle_pa_mode(self): - # Arrange - import pickle + im.mode = "PA" + assert im == loaded_im - # Act / Assert - for protocol in range(0, pickle.HIGHEST_PROTOCOL + 1): - self.helper_pickle_string(pickle, protocol, mode="PA") - self.helper_pickle_file(pickle, protocol, mode="PA") - def test_pickle_l_mode(self): - # Arrange - import pickle +@skip_unless_feature("webp") +def test_pickle_tell(): + # Arrange + image = Image.open("Tests/images/hopper.webp") - # Act / Assert - for protocol in range(0, pickle.HIGHEST_PROTOCOL + 1): - self.helper_pickle_string(pickle, protocol, mode="L") - self.helper_pickle_file(pickle, protocol, mode="L") + # Act: roundtrip + unpickled_image = pickle.loads(pickle.dumps(image)) - def test_pickle_la_mode_with_palette(self): - # Arrange - import pickle - - filename = self.tempfile("temp.pkl") - with Image.open("Tests/images/hopper.jpg") as im: - im = im.convert("PA") - - # Act / Assert - for protocol in range(0, pickle.HIGHEST_PROTOCOL + 1): - im.mode = "LA" - with open(filename, "wb") as f: - pickle.dump(im, f, protocol) - with open(filename, "rb") as f: - loaded_im = pickle.load(f) - - im.mode = "PA" - self.assertEqual(im, loaded_im) - - def test_cpickle_l_mode(self): - # Arrange - try: - import cPickle - except ImportError: - return - - # Act / Assert - for protocol in range(0, cPickle.HIGHEST_PROTOCOL + 1): - self.helper_pickle_string(cPickle, protocol, mode="L") - self.helper_pickle_file(cPickle, protocol, mode="L") + # Assert + assert unpickled_image.tell() == 0 diff --git a/Tests/test_pyroma.py b/Tests/test_pyroma.py index 7fc9e87cb..aa05c2cfd 100644 --- a/Tests/test_pyroma.py +++ b/Tests/test_pyroma.py @@ -1,13 +1,10 @@ import pytest + from PIL import __version__ -try: - import pyroma -except ImportError: - pyroma = None +pyroma = pytest.importorskip("pyroma", reason="Pyroma not installed") -@pytest.mark.skipif(pyroma is None, reason="Pyroma is not installed") def test_pyroma(): # Arrange data = pyroma.projectdata.get_data(".") diff --git a/Tests/test_qt_image_fromqpixmap.py b/Tests/test_qt_image_fromqpixmap.py deleted file mode 100644 index 1c7184376..000000000 --- a/Tests/test_qt_image_fromqpixmap.py +++ /dev/null @@ -1,15 +0,0 @@ -from PIL import ImageQt - -from .helper import PillowTestCase, assert_image_equal, hopper -from .test_imageqt import PillowQPixmapTestCase - - -class TestFromQPixmap(PillowQPixmapTestCase, PillowTestCase): - def roundtrip(self, expected): - result = ImageQt.fromqpixmap(ImageQt.toqpixmap(expected)) - # Qt saves all pixmaps as rgb - assert_image_equal(result, expected.convert("RGB")) - - def test_sanity(self): - for mode in ("1", "RGB", "RGBA", "L", "P"): - self.roundtrip(hopper(mode)) diff --git a/Tests/test_qt_image_qapplication.py b/Tests/test_qt_image_qapplication.py new file mode 100644 index 000000000..06bd27c00 --- /dev/null +++ b/Tests/test_qt_image_qapplication.py @@ -0,0 +1,63 @@ +import pytest + +from PIL import ImageQt + +from .helper import assert_image_equal, hopper + +if ImageQt.qt_is_installed: + from PIL.ImageQt import QPixmap + + if ImageQt.qt_version == "side6": + from PySide6.QtWidgets import QApplication, QHBoxLayout, QLabel, QWidget + elif ImageQt.qt_version == "5": + from PyQt5.QtWidgets import QApplication, QHBoxLayout, QLabel, QWidget + elif ImageQt.qt_version == "side2": + from PySide2.QtWidgets import QApplication, QHBoxLayout, QLabel, QWidget + + class Example(QWidget): + def __init__(self): + super().__init__() + + img = hopper().resize((1000, 1000)) + + qimage = ImageQt.ImageQt(img) + + pixmap1 = ImageQt.QPixmap.fromImage(qimage) + + QHBoxLayout(self) # hbox + + lbl = QLabel(self) + # Segfault in the problem + lbl.setPixmap(pixmap1.copy()) + + +def roundtrip(expected): + result = ImageQt.fromqpixmap(ImageQt.toqpixmap(expected)) + # Qt saves all pixmaps as rgb + assert_image_equal(result, expected.convert("RGB")) + + +@pytest.mark.skipif(not ImageQt.qt_is_installed, reason="Qt bindings are not installed") +def test_sanity(tmp_path): + # Segfault test + app = QApplication([]) + ex = Example() + assert app # Silence warning + assert ex # Silence warning + + for mode in ("1", "RGB", "RGBA", "L", "P"): + # to QPixmap + data = ImageQt.toqpixmap(hopper(mode)) + + assert isinstance(data, QPixmap) + assert not data.isNull() + + # Test saving the file + tempfile = str(tmp_path / f"temp_{mode}.png") + data.save(tempfile) + + # from QPixmap + roundtrip(hopper(mode)) + + app.quit() + app = None diff --git a/Tests/test_qt_image_toqimage.py b/Tests/test_qt_image_toqimage.py index 3733789f1..1a2bfd71e 100644 --- a/Tests/test_qt_image_toqimage.py +++ b/Tests/test_qt_image_toqimage.py @@ -1,72 +1,44 @@ +import pytest + from PIL import Image, ImageQt -from .helper import PillowTestCase, assert_image_equal, hopper -from .test_imageqt import PillowQtTestCase +from .helper import assert_image_equal, hopper + +pytestmark = pytest.mark.skipif( + not ImageQt.qt_is_installed, reason="Qt bindings are not installed" +) if ImageQt.qt_is_installed: from PIL.ImageQt import QImage - try: - from PyQt5 import QtGui - from PyQt5.QtWidgets import QWidget, QHBoxLayout, QLabel, QApplication - except (ImportError, RuntimeError): - from PySide2 import QtGui - from PySide2.QtWidgets import QWidget, QHBoxLayout, QLabel, QApplication +def test_sanity(tmp_path): + for mode in ("RGB", "RGBA", "L", "P", "1"): + src = hopper(mode) + data = ImageQt.toqimage(src) -class TestToQImage(PillowQtTestCase, PillowTestCase): - def test_sanity(self): - for mode in ("RGB", "RGBA", "L", "P", "1"): - src = hopper(mode) - data = ImageQt.toqimage(src) + assert isinstance(data, QImage) + assert not data.isNull() - self.assertIsInstance(data, QImage) - self.assertFalse(data.isNull()) + # reload directly from the qimage + rt = ImageQt.fromqimage(data) + if mode in ("L", "P", "1"): + assert_image_equal(rt, src.convert("RGB")) + else: + assert_image_equal(rt, src) - # reload directly from the qimage - rt = ImageQt.fromqimage(data) - if mode in ("L", "P", "1"): - assert_image_equal(rt, src.convert("RGB")) - else: - assert_image_equal(rt, src) + if mode == "1": + # BW appears to not save correctly on QT4 and QT5 + # kicks out errors on console: + # libpng warning: Invalid color type/bit depth combination + # in IHDR + # libpng error: Invalid IHDR data + continue - if mode == "1": - # BW appears to not save correctly on QT4 and QT5 - # kicks out errors on console: - # libpng warning: Invalid color type/bit depth combination - # in IHDR - # libpng error: Invalid IHDR data - continue + # Test saving the file + tempfile = str(tmp_path / f"temp_{mode}.png") + data.save(tempfile) - # Test saving the file - tempfile = self.tempfile("temp_{}.png".format(mode)) - data.save(tempfile) - - # Check that it actually worked. - with Image.open(tempfile) as reloaded: - assert_image_equal(reloaded, src) - - def test_segfault(self): - app = QApplication([]) - ex = Example() - assert app # Silence warning - assert ex # Silence warning - - -if ImageQt.qt_is_installed: - - class Example(QWidget): - def __init__(self): - super().__init__() - - img = hopper().resize((1000, 1000)) - - qimage = ImageQt.ImageQt(img) - - pixmap1 = QtGui.QPixmap.fromImage(qimage) - - QHBoxLayout(self) # hbox - - lbl = QLabel(self) - # Segfault in the problem - lbl.setPixmap(pixmap1.copy()) + # Check that it actually worked. + with Image.open(tempfile) as reloaded: + assert_image_equal(reloaded, src) diff --git a/Tests/test_qt_image_toqpixmap.py b/Tests/test_qt_image_toqpixmap.py deleted file mode 100644 index 2c07f1bf5..000000000 --- a/Tests/test_qt_image_toqpixmap.py +++ /dev/null @@ -1,20 +0,0 @@ -from PIL import ImageQt - -from .helper import PillowTestCase, hopper -from .test_imageqt import PillowQPixmapTestCase - -if ImageQt.qt_is_installed: - from PIL.ImageQt import QPixmap - - -class TestToQPixmap(PillowQPixmapTestCase, PillowTestCase): - def test_sanity(self): - for mode in ("1", "RGB", "RGBA", "L", "P"): - data = ImageQt.toqpixmap(hopper(mode)) - - self.assertIsInstance(data, QPixmap) - self.assertFalse(data.isNull()) - - # Test saving the file - tempfile = self.tempfile("temp_{}.png".format(mode)) - data.save(tempfile) diff --git a/Tests/test_sgi_crash.py b/Tests/test_sgi_crash.py new file mode 100644 index 000000000..ac304aab4 --- /dev/null +++ b/Tests/test_sgi_crash.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python +import pytest + +from PIL import Image + + +@pytest.mark.parametrize( + "test_file", + [ + "Tests/images/sgi_overrun_expandrowF04.bin", + "Tests/images/sgi_crash.bin", + "Tests/images/crash-6b7f2244da6d0ae297ee0754a424213444e92778.sgi", + "Tests/images/ossfuzz-5730089102868480.sgi", + ], +) +def test_crashes(test_file): + with open(test_file, "rb") as f: + im = Image.open(f) + with pytest.raises(OSError): + im.load() diff --git a/Tests/test_shell_injection.py b/Tests/test_shell_injection.py index 179bd17ac..d25d42dfc 100644 --- a/Tests/test_shell_injection.py +++ b/Tests/test_shell_injection.py @@ -1,15 +1,10 @@ import shutil +import pytest + from PIL import GifImagePlugin, Image, JpegImagePlugin -from .helper import ( - PillowTestCase, - cjpeg_available, - djpeg_available, - is_win32, - netpbm_available, - unittest, -) +from .helper import cjpeg_available, djpeg_available, is_win32, netpbm_available TEST_JPG = "Tests/images/hopper.jpg" TEST_GIF = "Tests/images/hopper.gif" @@ -17,38 +12,38 @@ TEST_GIF = "Tests/images/hopper.gif" test_filenames = ("temp_';", 'temp_";', "temp_'\"|", "temp_'\"||", "temp_'\"&&") -@unittest.skipIf(is_win32(), "requires Unix or macOS") -class TestShellInjection(PillowTestCase): - def assert_save_filename_check(self, src_img, save_func): +@pytest.mark.skipif(is_win32(), reason="Requires Unix or macOS") +class TestShellInjection: + def assert_save_filename_check(self, tmp_path, src_img, save_func): for filename in test_filenames: - dest_file = self.tempfile(filename) + dest_file = str(tmp_path / filename) save_func(src_img, 0, dest_file) # If file can't be opened, shell injection probably occurred with Image.open(dest_file) as im: im.load() - @unittest.skipUnless(djpeg_available(), "djpeg not available") - def test_load_djpeg_filename(self): + @pytest.mark.skipif(not djpeg_available(), reason="djpeg not available") + def test_load_djpeg_filename(self, tmp_path): for filename in test_filenames: - src_file = self.tempfile(filename) + src_file = str(tmp_path / filename) shutil.copy(TEST_JPG, src_file) with Image.open(src_file) as im: im.load_djpeg() - @unittest.skipUnless(cjpeg_available(), "cjpeg not available") - def test_save_cjpeg_filename(self): + @pytest.mark.skipif(not cjpeg_available(), reason="cjpeg not available") + def test_save_cjpeg_filename(self, tmp_path): with Image.open(TEST_JPG) as im: - self.assert_save_filename_check(im, JpegImagePlugin._save_cjpeg) + self.assert_save_filename_check(tmp_path, im, JpegImagePlugin._save_cjpeg) - @unittest.skipUnless(netpbm_available(), "netpbm not available") - def test_save_netpbm_filename_bmp_mode(self): + @pytest.mark.skipif(not netpbm_available(), reason="Netpbm not available") + def test_save_netpbm_filename_bmp_mode(self, tmp_path): with Image.open(TEST_GIF) as im: im = im.convert("RGB") - self.assert_save_filename_check(im, GifImagePlugin._save_netpbm) + self.assert_save_filename_check(tmp_path, im, GifImagePlugin._save_netpbm) - @unittest.skipUnless(netpbm_available(), "netpbm not available") - def test_save_netpbm_filename_l_mode(self): + @pytest.mark.skipif(not netpbm_available(), reason="Netpbm not available") + def test_save_netpbm_filename_l_mode(self, tmp_path): with Image.open(TEST_GIF) as im: im = im.convert("L") - self.assert_save_filename_check(im, GifImagePlugin._save_netpbm) + self.assert_save_filename_check(tmp_path, im, GifImagePlugin._save_netpbm) diff --git a/Tests/test_tiff_crashes.py b/Tests/test_tiff_crashes.py new file mode 100644 index 000000000..d0de4b305 --- /dev/null +++ b/Tests/test_tiff_crashes.py @@ -0,0 +1,41 @@ +# Reproductions/tests for crashes/read errors in TiffDecode.c + +# When run in Python, all of these images should fail for +# one reason or another, either as a buffer overrun, +# unrecognized datastream, or truncated image file. +# There shouldn't be any segfaults. +# +# if run like +# `valgrind --tool=memcheck pytest test_tiff_crashes.py 2>&1 | grep TiffDecode.c` +# the output should be empty. There may be Python issues +# in the valgrind especially if run in a debug Python +# version. + +import pytest + +from PIL import Image + +from .helper import on_ci + + +@pytest.mark.parametrize( + "test_file", + [ + "Tests/images/crash_1.tif", + "Tests/images/crash_2.tif", + "Tests/images/crash-2020-10-test.tif", + ], +) +@pytest.mark.filterwarnings("ignore:Possibly corrupt EXIF data") +@pytest.mark.filterwarnings("ignore:Metadata warning") +def test_tiff_crashes(test_file): + try: + with Image.open(test_file) as im: + im.load() + except FileNotFoundError: + if not on_ci(): + pytest.skip("test image not found") + return + raise + except OSError: + pass diff --git a/Tests/test_tiff_ifdrational.py b/Tests/test_tiff_ifdrational.py index dedbbfe6d..1697a8d49 100644 --- a/Tests/test_tiff_ifdrational.py +++ b/Tests/test_tiff_ifdrational.py @@ -1,60 +1,66 @@ from fractions import Fraction -from PIL import Image, TiffImagePlugin +from PIL import Image, TiffImagePlugin, features from PIL.TiffImagePlugin import IFDRational -from .helper import PillowTestCase, hopper +from .helper import hopper -class Test_IFDRational(PillowTestCase): - def _test_equal(self, num, denom, target): +def _test_equal(num, denom, target): - t = IFDRational(num, denom) + t = IFDRational(num, denom) - self.assertEqual(target, t) - self.assertEqual(t, target) + assert target == t + assert t == target - def test_sanity(self): - self._test_equal(1, 1, 1) - self._test_equal(1, 1, Fraction(1, 1)) +def test_sanity(): - self._test_equal(2, 2, 1) - self._test_equal(1.0, 1, Fraction(1, 1)) + _test_equal(1, 1, 1) + _test_equal(1, 1, Fraction(1, 1)) - self._test_equal(Fraction(1, 1), 1, Fraction(1, 1)) - self._test_equal(IFDRational(1, 1), 1, 1) + _test_equal(2, 2, 1) + _test_equal(1.0, 1, Fraction(1, 1)) - self._test_equal(1, 2, Fraction(1, 2)) - self._test_equal(1, 2, IFDRational(1, 2)) + _test_equal(Fraction(1, 1), 1, Fraction(1, 1)) + _test_equal(IFDRational(1, 1), 1, 1) - def test_nonetype(self): - # Fails if the _delegate function doesn't return a valid function + _test_equal(1, 2, Fraction(1, 2)) + _test_equal(1, 2, IFDRational(1, 2)) - xres = IFDRational(72) - yres = IFDRational(72) - self.assertIsNotNone(xres._val) - self.assertIsNotNone(xres.numerator) - self.assertIsNotNone(xres.denominator) - self.assertIsNotNone(yres._val) - self.assertTrue(xres and 1) - self.assertTrue(xres and yres) +def test_ranges(): + for num in range(1, 10): + for denom in range(1, 10): + assert IFDRational(num, denom) == IFDRational(num, denom) - def test_ifd_rational_save(self): - methods = (True, False) - if "libtiff_encoder" not in dir(Image.core): - methods = (False,) - for libtiff in methods: - TiffImagePlugin.WRITE_LIBTIFF = libtiff +def test_nonetype(): + # Fails if the _delegate function doesn't return a valid function - im = hopper() - out = self.tempfile("temp.tiff") - res = IFDRational(301, 1) - im.save(out, dpi=(res, res), compression="raw") + xres = IFDRational(72) + yres = IFDRational(72) + assert xres._val is not None + assert xres.numerator is not None + assert xres.denominator is not None + assert yres._val is not None - with Image.open(out) as reloaded: - self.assertEqual( - float(IFDRational(301, 1)), float(reloaded.tag_v2[282]) - ) + assert xres and 1 + assert xres and yres + + +def test_ifd_rational_save(tmp_path): + methods = (True, False) + if not features.check("libtiff"): + methods = (False,) + + for libtiff in methods: + TiffImagePlugin.WRITE_LIBTIFF = libtiff + + im = hopper() + out = str(tmp_path / "temp.tiff") + res = IFDRational(301, 1) + im.save(out, dpi=(res, res), compression="raw") + + with Image.open(out) as reloaded: + assert float(IFDRational(301, 1)) == float(reloaded.tag_v2[282]) diff --git a/Tests/test_uploader.py b/Tests/test_uploader.py index 24e7bebc8..720926e53 100644 --- a/Tests/test_uploader.py +++ b/Tests/test_uploader.py @@ -1,13 +1,13 @@ -from .helper import PillowTestCase, assert_image_equal, assert_image_similar, hopper +from .helper import assert_image_equal, assert_image_similar, hopper -class TestUploader(PillowTestCase): - def check_upload_equal(self): - result = hopper("P").convert("RGB") - target = hopper("RGB") - assert_image_equal(result, target) +def check_upload_equal(): + result = hopper("P").convert("RGB") + target = hopper("RGB") + assert_image_equal(result, target) - def check_upload_similar(self): - result = hopper("P").convert("RGB") - target = hopper("RGB") - assert_image_similar(result, target, 0) + +def check_upload_similar(): + result = hopper("P").convert("RGB") + target = hopper("RGB") + assert_image_similar(result, target, 0) diff --git a/Tests/test_util.py b/Tests/test_util.py index 3d88b9472..b5bfca012 100644 --- a/Tests/test_util.py +++ b/Tests/test_util.py @@ -1,4 +1,5 @@ import pytest + from PIL import _util @@ -13,7 +14,6 @@ def test_is_path(): assert it_is -@pytest.mark.skipif(not _util.py36, reason="os.path support for Paths added in 3.6") def test_path_obj_is_path(): # Arrange from pathlib import Path diff --git a/Tests/test_webp_leaks.py b/Tests/test_webp_leaks.py index 713fc161e..34197c14f 100644 --- a/Tests/test_webp_leaks.py +++ b/Tests/test_webp_leaks.py @@ -1,14 +1,13 @@ -import unittest from io import BytesIO -from PIL import Image, features +from PIL import Image -from .helper import PillowLeakTestCase +from .helper import PillowLeakTestCase, skip_unless_feature test_file = "Tests/images/hopper.webp" -@unittest.skipUnless(features.check("webp"), "WebP is not installed") +@skip_unless_feature("webp") class TestWebPLeaks(PillowLeakTestCase): mem_limit = 3 * 1024 # kb diff --git a/codecov.yml b/codecov.yml index e93896692..f3afccc1c 100644 --- a/codecov.yml +++ b/codecov.yml @@ -8,6 +8,12 @@ codecov: comment: false +coverage: + status: + project: + default: + threshold: 0.01% + # Matches 'omit:' in .coveragerc ignore: - "Tests/32bit_segfault_check.py" diff --git a/depends/README.rst b/depends/README.rst index ce88fa47b..b69c9dcbf 100644 --- a/depends/README.rst +++ b/depends/README.rst @@ -3,7 +3,7 @@ Depends ``install_openjpeg.sh``, ``install_webp.sh``, ``install_imagequant.sh``, ``install_raqm.sh`` and ``install_raqm_cmake.sh`` can be used to download, -build & install non-packaged dependencies; useful for testing with Travis CI. +build & install non-packaged dependencies; useful for testing on CI. ``install_extra_test_images.sh`` can be used to install additional test images -that are used for Travis CI and AppVeyor. +that are used by CI. diff --git a/depends/diffcover-install.sh b/depends/diffcover-install.sh deleted file mode 100755 index a0b462b56..000000000 --- a/depends/diffcover-install.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env bash -# Fetch the remote master branch before running diff-cover on Travis CI. -# https://github.com/Bachmann1234/diff-cover#troubleshooting -git fetch origin master:refs/remotes/origin/master - -# CFLAGS=-O0 means build with no optimisation. -# Makes build much quicker for lxml and other dependencies. -time CFLAGS=-O0 pip install diff_cover diff --git a/depends/diffcover-run.sh b/depends/diffcover-run.sh deleted file mode 100755 index b007494e9..000000000 --- a/depends/diffcover-run.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env bash -coverage xml -diff-cover coverage.xml -diff-quality --violation=pyflakes -diff-quality --violation=pycodestyle diff --git a/depends/install_extra_test_images.sh b/depends/install_extra_test_images.sh index 36af34b54..02da12d61 100755 --- a/depends/install_extra_test_images.sh +++ b/depends/install_extra_test_images.sh @@ -4,12 +4,12 @@ # Use SVN to just fetch a single Git subdirectory svn_export() { - if [ ! -z $1 ]; then - echo "" - echo "Retrying svn export..." - echo "" - fi + if [ ! -z $1 ]; then + echo "" + echo "Retrying svn export..." + echo "" + fi - svn export --force https://github.com/python-pillow/pillow-depends/trunk/test_images ../Tests/images + svn export --force https://github.com/python-pillow/pillow-depends/trunk/test_images ../Tests/images } svn_export || svn_export retry || svn_export retry || svn_export retry diff --git a/depends/install_imagequant.sh b/depends/install_imagequant.sh index 1f2b677fd..ed438f904 100755 --- a/depends/install_imagequant.sh +++ b/depends/install_imagequant.sh @@ -1,7 +1,7 @@ #!/bin/bash # install libimagequant -archive=libimagequant-2.12.6 +archive=libimagequant-2.13.1 ./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/master/$archive.tar.gz diff --git a/depends/install_openjpeg.sh b/depends/install_openjpeg.sh index a93498282..7321b80f0 100755 --- a/depends/install_openjpeg.sh +++ b/depends/install_openjpeg.sh @@ -1,7 +1,7 @@ #!/bin/bash # install openjpeg -archive=openjpeg-2.3.1 +archive=openjpeg-2.4.0 ./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/master/$archive.tar.gz diff --git a/depends/install_raqm.sh b/depends/install_raqm.sh index 38a5bfd52..a7ce16792 100755 --- a/depends/install_raqm.sh +++ b/depends/install_raqm.sh @@ -2,7 +2,7 @@ # install raqm -archive=raqm-0.7.0 +archive=raqm-0.7.1 ./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/master/$archive.tar.gz diff --git a/docs/COPYING b/docs/COPYING index ec2a5d8cb..f2466d659 100644 --- a/docs/COPYING +++ b/docs/COPYING @@ -5,7 +5,7 @@ The Python Imaging Library (PIL) is Pillow is the friendly PIL fork. It is - Copyright © 2010-2020 by Alex Clark and contributors + Copyright © 2010-2021 by Alex Clark and contributors Like PIL, Pillow is licensed under the open source PIL Software License: diff --git a/docs/Makefile b/docs/Makefile index d7c20bca4..686f0119e 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -156,4 +156,4 @@ livehtml: html livereload $(BUILDDIR)/html -p 33233 serve: - cd $(BUILDDIR)/html; python -m SimpleHTTPServer + cd $(BUILDDIR)/html; python3 -m http.server diff --git a/docs/PIL.rst b/docs/PIL.rst index fe69fed62..fa036b9cc 100644 --- a/docs/PIL.rst +++ b/docs/PIL.rst @@ -4,106 +4,97 @@ PIL Package (autodoc of remaining modules) Reference for modules whose documentation has not yet been ported or written can be found here. -:mod:`BdfFontFile` Module -------------------------- +:mod:`PIL` Module +----------------- + +.. py:module:: PIL + +.. autoexception:: UnidentifiedImageError + :show-inheritance: + +:mod:`~PIL.BdfFontFile` Module +------------------------------ .. automodule:: PIL.BdfFontFile :members: :undoc-members: :show-inheritance: -:mod:`ContainerIO` Module -------------------------- +:mod:`~PIL.ContainerIO` Module +------------------------------ .. automodule:: PIL.ContainerIO :members: :undoc-members: :show-inheritance: -:mod:`FontFile` Module ----------------------- +:mod:`~PIL.FontFile` Module +--------------------------- .. automodule:: PIL.FontFile :members: :undoc-members: :show-inheritance: -:mod:`GdImageFile` Module -------------------------- +:mod:`~PIL.GdImageFile` Module +------------------------------ .. automodule:: PIL.GdImageFile :members: :undoc-members: :show-inheritance: -:mod:`GimpGradientFile` Module ------------------------------- +:mod:`~PIL.GimpGradientFile` Module +----------------------------------- .. automodule:: PIL.GimpGradientFile :members: :undoc-members: :show-inheritance: -:mod:`GimpPaletteFile` Module ------------------------------ +:mod:`~PIL.GimpPaletteFile` Module +---------------------------------- .. automodule:: PIL.GimpPaletteFile :members: :undoc-members: :show-inheritance: -.. intentionally skipped documenting this because it's not documented anywhere - -:mod:`ImageDraw2` Module ------------------------- +:mod:`~PIL.ImageDraw2` Module +----------------------------- .. automodule:: PIL.ImageDraw2 :members: + :member-order: bysource :undoc-members: :show-inheritance: -:mod:`ImageShow` Module ------------------------ - -.. automodule:: PIL.ImageShow - :members: - :undoc-members: - :show-inheritance: - -:mod:`ImageTransform` Module ----------------------------- +:mod:`~PIL.ImageTransform` Module +--------------------------------- .. automodule:: PIL.ImageTransform :members: :undoc-members: :show-inheritance: -:mod:`JpegPresets` Module -------------------------- - -.. automodule:: PIL.JpegPresets - :members: - :undoc-members: - :show-inheritance: - -:mod:`PaletteFile` Module -------------------------- +:mod:`~PIL.PaletteFile` Module +------------------------------ .. automodule:: PIL.PaletteFile :members: :undoc-members: :show-inheritance: -:mod:`PcfFontFile` Module -------------------------- +:mod:`~PIL.PcfFontFile` Module +------------------------------ .. automodule:: PIL.PcfFontFile :members: :undoc-members: :show-inheritance: -:class:`PngImagePlugin.iTXt` Class ----------------------------------- +:class:`.PngImagePlugin.iTXt` Class +----------------------------------- .. autoclass:: PIL.PngImagePlugin.iTXt :members: @@ -116,8 +107,8 @@ can be found here. :param lang: language code :param tkey: UTF-8 version of the key name -:class:`PngImagePlugin.PngInfo` Class -------------------------------------- +:class:`.PngImagePlugin.PngInfo` Class +-------------------------------------- .. autoclass:: PIL.PngImagePlugin.PngInfo :members: @@ -125,27 +116,18 @@ can be found here. :show-inheritance: -:mod:`TarIO` Module -------------------- +:mod:`~PIL.TarIO` Module +------------------------ .. automodule:: PIL.TarIO :members: :undoc-members: :show-inheritance: -:mod:`WalImageFile` Module --------------------------- +:mod:`~PIL.WalImageFile` Module +------------------------------- .. automodule:: PIL.WalImageFile :members: :undoc-members: :show-inheritance: - -:mod:`_binary` Module ---------------------- - -.. automodule:: PIL._binary - :members: - :undoc-members: - :show-inheritance: - diff --git a/docs/about.rst b/docs/about.rst index ce6537e14..51b583ea0 100644 --- a/docs/about.rst +++ b/docs/about.rst @@ -6,20 +6,20 @@ Goals The fork author's goal is to foster and support active development of PIL through: -- Continuous integration testing via `Travis CI`_, `AppVeyor`_ and `GitHub Actions`_ +- Continuous integration testing via `GitHub Actions`_, `AppVeyor`_ and `Travis CI`_ - Publicized development activity on `GitHub`_ - Regular releases to the `Python Package Index`_ -.. _Travis CI: https://travis-ci.org/python-pillow/Pillow -.. _AppVeyor: https://ci.appveyor.com/project/Python-pillow/pillow .. _GitHub Actions: https://github.com/python-pillow/Pillow/actions +.. _AppVeyor: https://ci.appveyor.com/project/Python-pillow/pillow +.. _Travis CI: https://travis-ci.com/github/python-pillow/pillow-wheels .. _GitHub: https://github.com/python-pillow/Pillow .. _Python Package Index: https://pypi.org/project/Pillow/ License ------- -Like PIL, Pillow is `licensed under the open source PIL Software License `_ +Like PIL, Pillow is `licensed under the open source HPND License `_ Why a fork? ----------- @@ -36,10 +36,4 @@ What about PIL? Prior to Pillow 2.0.0, very few image code changes were made. Pillow 2.0.0 added Python 3 support and includes many bug fixes from many contributors. -As more time passes since the last PIL release (1.1.7 in 2009), the likelihood of a new PIL release decreases. However, we've yet to hear an official "PIL is dead" announcement. So if you still want to support PIL, please `report issues here first`_, then `open corresponding Pillow tickets here`_. - -.. _report issues here first: https://bitbucket.org/effbot/pil-2009-raclette/issues - -.. _open corresponding Pillow tickets here: https://github.com/python-pillow/Pillow/issues - -Please provide a link to the first ticket so we can track the issue(s) upstream. +As more time passes since the last PIL release (1.1.7 in 2009), the likelihood of a new PIL release decreases. However, we've yet to hear an official "PIL is dead" announcement. diff --git a/docs/conf.py b/docs/conf.py index d34318100..4fb9d1f8f 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -16,18 +16,27 @@ # documentation root, use os.path.abspath to make it absolute, like shown here. # sys.path.insert(0, os.path.abspath('.')) -import PIL import sphinx_rtd_theme +import PIL + # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. -# needs_sphinx = '1.0' +needs_sphinx = "2.4" # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. -extensions = ["sphinx.ext.autodoc", "sphinx.ext.viewcode", "sphinx.ext.intersphinx"] +extensions = [ + "sphinx.ext.autodoc", + "sphinx.ext.intersphinx", + "sphinx.ext.viewcode", + "sphinx_issues", + "sphinx_removed_in", +] + +intersphinx_mapping = {"python": ("https://docs.python.org/3", None)} # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: @@ -42,7 +51,7 @@ master_doc = "index" # General information about the project. project = "Pillow (PIL Fork)" -copyright = "1995-2011 Fredrik Lundh, 2010-2020 Alex Clark and Contributors" +copyright = "1995-2011 Fredrik Lundh, 2010-2021 Alex Clark and Contributors" author = "Fredrik Lundh, Alex Clark and Contributors" # The version info for the project you're documenting, acts as replacement for @@ -69,7 +78,7 @@ language = None # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. -exclude_patterns = ["_build"] +exclude_patterns = ["_build", "releasenotes/template.rst"] # The reST default role (used for this markup: `text`) to use for all # documents. @@ -98,6 +107,17 @@ pygments_style = "sphinx" # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = False +# If true, Sphinx will warn about all references where the target cannot be found. +# Default is False. You can activate this mode temporarily using the -n command-line +# switch. +nitpicky = True + +# A list of (type, target) tuples (by default empty) that should be ignored when +# generating warnings in “nitpicky mode”. Note that type should include the domain name +# if present. Example entries would be ('py:func', 'int') or +# ('envvar', 'LD_LIBRARY_PATH'). +# nitpick_ignore = [] + # -- Options for HTML output ---------------------------------------------- @@ -124,7 +144,7 @@ html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] # The name of an image file (relative to this directory) to place at the top # of the sidebar. -# html_logo = None +html_logo = "resources/pillow-logo.png" # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 @@ -289,4 +309,6 @@ texinfo_documents = [ def setup(app): - app.add_javascript("js/script.js") + app.add_js_file("js/script.js") + app.add_css_file("css/dark.css") + app.add_css_file("css/light.css") diff --git a/docs/deprecations.rst b/docs/deprecations.rst index bcabb8186..44aa2a795 100644 --- a/docs/deprecations.rst +++ b/docs/deprecations.rst @@ -12,25 +12,55 @@ Deprecated features Below are features which are considered deprecated. Where appropriate, a ``DeprecationWarning`` is issued. -ImageCms.CmsProfile attributes -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +FreeType 2.7 +~~~~~~~~~~~~ -.. deprecated:: 3.2.0 +.. deprecated:: 8.1.0 -Some attributes in ``ImageCms.CmsProfile`` are deprecated. From 6.0.0, they issue a -``DeprecationWarning``: +Support for FreeType 2.7 is deprecated and will be removed in Pillow 9.0.0 (2022-01-02), +when FreeType 2.8 will be the minimum supported. -======================== =============================== -Deprecated Use instead -======================== =============================== -``color_space`` Padded ``xcolor_space`` -``pcs`` Padded ``connection_space`` -``product_copyright`` Unicode ``copyright`` -``product_desc`` Unicode ``profile_description`` -``product_description`` Unicode ``profile_description`` -``product_manufacturer`` Unicode ``manufacturer`` -``product_model`` Unicode ``model`` -======================== =============================== +We recommend upgrading to at least FreeType `2.10.4`_, which fixed a severe +vulnerability introduced in FreeType 2.6 (:cve:`CVE-2020-15999`). + +.. _2.10.4: https://sourceforge.net/projects/freetype/files/freetype2/2.10.4/ + +Image.show command parameter +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. deprecated:: 7.2.0 + +The ``command`` parameter will be removed in Pillow 9.0.0 (2022-01-02). +Use a subclass of :py:class:`.ImageShow.Viewer` instead. + +Image._showxv +~~~~~~~~~~~~~ + +.. deprecated:: 7.2.0 + +``Image._showxv`` will be removed in Pillow 9.0.0 (2022-01-02). +Use :py:meth:`.Image.Image.show` instead. If custom behaviour is required, use +:py:func:`.ImageShow.register` to add a custom :py:class:`.ImageShow.Viewer` class. + +ImageFile.raise_ioerror +~~~~~~~~~~~~~~~~~~~~~~~ + +.. deprecated:: 7.2.0 + +``IOError`` was merged into ``OSError`` in Python 3.3. +So, ``ImageFile.raise_ioerror`` will be removed in Pillow 9.0.0 (2022-01-02). +Use ``ImageFile.raise_oserror`` instead. + +PILLOW_VERSION constant +~~~~~~~~~~~~~~~~~~~~~~~ + +.. deprecated:: 5.2.0 + +``PILLOW_VERSION`` will be removed in Pillow 9.0.0 (2022-01-02). +Use ``__version__`` instead. + +It was initially removed in Pillow 7.0.0, but brought back in 7.1.0 to give projects +more time to upgrade. Removed features ---------------- @@ -38,10 +68,60 @@ Removed features Deprecated features are only removed in major releases after an appropriate period of deprecation has passed. +im.offset +~~~~~~~~~ + +.. deprecated:: 1.1.2 +.. versionremoved:: 8.0.0 + +``im.offset()`` has been removed, call :py:func:`.ImageChops.offset()` instead. + +It was documented as deprecated in PIL 1.1.2, +raised a ``DeprecationWarning`` since 1.1.5, +an ``Exception`` since Pillow 3.0.0 +and ``NotImplementedError`` since 3.3.0. + +Image.fromstring, im.fromstring and im.tostring +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. deprecated:: 2.0.0 +.. versionremoved:: 8.0.0 + +* ``Image.fromstring()`` has been removed, call :py:func:`.Image.frombytes()` instead. +* ``im.fromstring()`` has been removed, call :py:meth:`~PIL.Image.Image.frombytes()` instead. +* ``im.tostring()`` has been removed, call :py:meth:`~PIL.Image.Image.tobytes()` instead. + +They issued a ``DeprecationWarning`` since 2.0.0, +an ``Exception`` since 3.0.0 +and ``NotImplementedError`` since 3.3.0. + +ImageCms.CmsProfile attributes +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. deprecated:: 3.2.0 +.. versionremoved:: 8.0.0 + +Some attributes in :py:class:`PIL.ImageCms.CmsProfile` have been removed. From 6.0.0, +they issued a ``DeprecationWarning``: + +======================== =================================================== + +Removed Use instead +======================== =================================================== +``color_space`` Padded :py:attr:`~.CmsProfile.xcolor_space` +``pcs`` Padded :py:attr:`~.CmsProfile.connection_space` +``product_copyright`` Unicode :py:attr:`~.CmsProfile.copyright` +``product_desc`` Unicode :py:attr:`~.CmsProfile.profile_description` +``product_description`` Unicode :py:attr:`~.CmsProfile.profile_description` +``product_manufacturer`` Unicode :py:attr:`~.CmsProfile.manufacturer` +``product_model`` Unicode :py:attr:`~.CmsProfile.model` +======================== =================================================== + Python 2.7 ~~~~~~~~~~ -*Removed in version 7.0.0.* +.. deprecated:: 6.0.0 +.. versionremoved:: 7.0.0 Python 2.7 reached end-of-life on 2020-01-01. Pillow 6.x was the last series to support Python 2. @@ -49,7 +129,8 @@ support Python 2. Image.__del__ ~~~~~~~~~~~~~ -*Removed in version 7.0.0.* +.. deprecated:: 6.1.0 +.. versionremoved:: 7.0.0 Implicitly closing the image's underlying file in ``Image.__del__`` has been removed. Use a context manager or call ``Image.close()`` instead to close the file in a @@ -69,17 +150,11 @@ Use instead: with Image.open("hopper.png") as im: im.save("out.jpg") -PILLOW_VERSION constant -~~~~~~~~~~~~~~~~~~~~~~~ - -*Removed in version 7.0.0.* - -``PILLOW_VERSION`` has been removed. Use ``__version__`` instead. - PIL.*ImagePlugin.__version__ attributes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -*Removed in version 7.0.0.* +.. deprecated:: 6.0.0 +.. versionremoved:: 7.0.0 The version constants of individual plugins have been removed. Use ``PIL.__version__`` instead. @@ -104,7 +179,8 @@ Removed Removed Removed PyQt4 and PySide ~~~~~~~~~~~~~~~~ -*Removed in version 7.0.0.* +.. deprecated:: 6.0.0 +.. versionremoved:: 7.0.0 Qt 4 reached end-of-life on 2015-12-19. Its Python bindings are also EOL: PyQt4 since 2018-08-31 and PySide since 2015-10-14. @@ -115,7 +191,8 @@ or PySide2. Setting the size of TIFF images ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -*Removed in version 7.0.0.* +.. deprecated:: 5.3.0 +.. versionremoved:: 7.0.0 Setting the size of a TIFF image directly (eg. ``im.size = (256, 256)``) throws an error. Use ``Image.resize`` instead. @@ -123,7 +200,8 @@ an error. Use ``Image.resize`` instead. VERSION constant ~~~~~~~~~~~~~~~~ -*Removed in version 6.0.0.* +.. deprecated:: 5.2.0 +.. versionremoved:: 6.0.0 ``VERSION`` (the old PIL version, always 1.1.7) has been removed. Use ``__version__`` instead. @@ -131,7 +209,8 @@ VERSION constant Undocumented ImageOps functions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -*Removed in version 6.0.0.* +.. deprecated:: 4.3.0 +.. versionremoved:: 6.0.0 Several undocumented functions in ``ImageOps`` have been removed. Use the equivalents in ``ImageFilter`` instead: @@ -149,9 +228,10 @@ Removed Use instead PIL.OleFileIO ~~~~~~~~~~~~~ -*Removed in version 6.0.0.* +.. deprecated:: 4.0.0 +.. versionremoved:: 6.0.0 PIL.OleFileIO was removed as a vendored file and in Pillow 4.0.0 (2017-01) in favour of the upstream olefile Python package, and replaced with an ``ImportError`` in 5.0.0 (2018-01). The deprecated file has now been removed from Pillow. If needed, install from -PyPI (eg. ``pip install olefile``). +PyPI (eg. ``python3 -m pip install olefile``). diff --git a/docs/example/DdsImagePlugin.py b/docs/example/DdsImagePlugin.py index 45f63f164..78aa3ce72 100644 --- a/docs/example/DdsImagePlugin.py +++ b/docs/example/DdsImagePlugin.py @@ -212,10 +212,10 @@ class DdsImageFile(ImageFile.ImageFile): def _open(self): magic, header_size = struct.unpack("`_ for details. + library before building the Python Imaging Library. See the + :doc:`installation documentation <../installation>` for details. + +.. _apng-sequences: + +APNG sequences +~~~~~~~~~~~~~~ + +The PNG loader includes limited support for reading and writing Animated Portable +Network Graphics (APNG) files. +When an APNG file is loaded, :py:meth:`~PIL.ImageFile.ImageFile.get_format_mimetype` +will return ``"image/apng"``. The value of the :py:attr:`~PIL.Image.Image.is_animated` +property will be ``True`` when the :py:attr:`~PIL.Image.Image.n_frames` property is +greater than 1. For APNG files, the ``n_frames`` property depends on both the animation +frame count as well as the presence or absence of a default image. See the +``default_image`` property documentation below for more details. +The :py:meth:`~PIL.Image.Image.seek` and :py:meth:`~PIL.Image.Image.tell` methods +are supported. + +``im.seek()`` raises an :py:exc:`EOFError` if you try to seek after the last frame. + +These :py:attr:`~PIL.Image.Image.info` properties will be set for APNG frames, +where applicable: + +**default_image** + Specifies whether or not this APNG file contains a separate default image, + which is not a part of the actual APNG animation. + + When an APNG file contains a default image, the initially loaded image (i.e. + the result of ``seek(0)``) will be the default image. + To account for the presence of the default image, the + :py:attr:`~PIL.Image.Image.n_frames` property will be set to ``frame_count + 1``, + where ``frame_count`` is the actual APNG animation frame count. + To load the first APNG animation frame, ``seek(1)`` must be called. + + * ``True`` - The APNG contains default image, which is not an animation frame. + * ``False`` - The APNG does not contain a default image. The ``n_frames`` property + will be set to the actual APNG animation frame count. + The initially loaded image (i.e. ``seek(0)``) will be the first APNG animation + frame. + +**loop** + The number of times to loop this APNG, 0 indicates infinite looping. + +**duration** + The time to display this APNG frame (in milliseconds). + +.. note:: + + The APNG loader returns images the same size as the APNG file's logical screen size. + The returned image contains the pixel data for a given frame, after applying + any APNG frame disposal and frame blend operations (i.e. it contains what a web + browser would render for this frame - the composite of all previous frames and this + frame). + + Any APNG file containing sequence errors is treated as an invalid image. The APNG + loader will not attempt to repair and reorder files containing sequence errors. + +.. _apng-saving: + +Saving +~~~~~~ + +When calling :py:meth:`~PIL.Image.Image.save`, by default only a single frame PNG file +will be saved. To save an APNG file (including a single frame APNG), the ``save_all`` +parameter must be set to ``True``. The following parameters can also be set: + +**default_image** + Boolean value, specifying whether or not the base image is a default image. + If ``True``, the base image will be used as the default image, and the first image + from the ``append_images`` sequence will be the first APNG animation frame. + If ``False``, the base image will be used as the first APNG animation frame. + Defaults to ``False``. + +**append_images** + A list or tuple of images to append as additional frames. Each of the + images in the list can be single or multiframe images. The size of each frame + should match the size of the base image. Also note that if a frame's mode does + not match that of the base image, the frame will be converted to the base image + mode. + +**loop** + Integer number of times to loop this APNG, 0 indicates infinite looping. + Defaults to 0. + +**duration** + Integer (or list or tuple of integers) length of time to display this APNG frame + (in milliseconds). + Defaults to 0. + +**disposal** + An integer (or list or tuple of integers) specifying the APNG disposal + operation to be used for this frame before rendering the next frame. + Defaults to 0. + + * 0 (:py:data:`~PIL.PngImagePlugin.APNG_DISPOSE_OP_NONE`, default) - + No disposal is done on this frame before rendering the next frame. + * 1 (:py:data:`PIL.PngImagePlugin.APNG_DISPOSE_OP_BACKGROUND`) - + This frame's modified region is cleared to fully transparent black before + rendering the next frame. + * 2 (:py:data:`~PIL.PngImagePlugin.APNG_DISPOSE_OP_PREVIOUS`) - + This frame's modified region is reverted to the previous frame's contents before + rendering the next frame. + +**blend** + An integer (or list or tuple of integers) specifying the APNG blend + operation to be used for this frame before rendering the next frame. + Defaults to 0. + + * 0 (:py:data:`~PIL.PngImagePlugin.APNG_BLEND_OP_SOURCE`) - + All color components of this frame, including alpha, overwrite the previous output + image contents. + * 1 (:py:data:`~PIL.PngImagePlugin.APNG_BLEND_OP_OVER`) - + This frame should be alpha composited with the previous output image contents. + +.. note:: + + The ``duration``, ``disposal`` and ``blend`` parameters can be set to lists or tuples to + specify values for each individual frame in the animation. The length of the list or tuple + must be identical to the total number of actual frames in the APNG animation. + If the APNG contains a default image (i.e. ``default_image`` is set to ``True``), + these list or tuple parameters should not include an entry for the default image. + PPM ^^^ @@ -573,7 +716,7 @@ Pillow also reads SPIDER stack files containing sequences of SPIDER images. The :py:meth:`~PIL.Image.Image.seek` and :py:meth:`~PIL.Image.Image.tell` methods are supported, and random access is allowed. -The :py:meth:`~PIL.Image.Image.open` method sets the following attributes: +The :py:meth:`~PIL.Image.open` method sets the following attributes: **format** Set to ``SPIDER`` @@ -584,8 +727,8 @@ The :py:meth:`~PIL.Image.Image.open` method sets the following attributes: **n_frames** Set to the number of images in the stack. -A convenience method, :py:meth:`~PIL.Image.Image.convert2byte`, is provided for -converting floating point data to byte data (mode ``L``):: +A convenience method, :py:meth:`~PIL.SpiderImagePlugin.SpiderImageFile.convert2byte`, +is provided for converting floating point data to byte data (mode ``L``):: im = Image.open('image001.spi').convert2byte() @@ -626,7 +769,7 @@ uncompressed files. support for reading Packbits, LZW and JPEG compressed TIFFs without using libtiff. -The :py:meth:`~PIL.Image.Image.open` method sets the following +The :py:meth:`~PIL.Image.open` method sets the following :py:attr:`~PIL.Image.Image.info` properties: **compression** @@ -636,8 +779,8 @@ The :py:meth:`~PIL.Image.Image.open` method sets the following **dpi** Image resolution as an ``(xdpi, ydpi)`` tuple, where applicable. You can use - the :py:attr:`~PIL.Image.Image.tag` attribute to get more detailed - information about the image resolution. + the :py:attr:`~PIL.TiffImagePlugin.TiffImageFile.tag` attribute to get more + detailed information about the image resolution. .. versionadded:: 1.1.5 @@ -648,9 +791,9 @@ The :py:meth:`~PIL.Image.Image.open` method sets the following .. versionadded:: 1.1.5 -The :py:attr:`~PIL.Image.Image.tag_v2` attribute contains a dictionary -of TIFF metadata. The keys are numerical indexes from -:py:attr:`~PIL.TiffTags.TAGS_V2`. Values are strings or numbers for single +The :py:attr:`~PIL.TiffImagePlugin.TiffImageFile.tag_v2` attribute contains a +dictionary of TIFF metadata. The keys are numerical indexes from +:py:data:`.TiffTags.TAGS_V2`. Values are strings or numbers for single items, multiple values are returned in a tuple of values. Rational numbers are returned as a :py:class:`~PIL.TiffImagePlugin.IFDRational` object. @@ -658,8 +801,8 @@ object. .. versionadded:: 3.0.0 For compatibility with legacy code, the -:py:attr:`~PIL.Image.Image.tag` attribute contains a dictionary of -decoded TIFF fields as returned prior to version 3.0.0. Values are +:py:attr:`~PIL.TiffImagePlugin.TiffImageFile.tag` attribute contains a dictionary +of decoded TIFF fields as returned prior to version 3.0.0. Values are returned as either strings or tuples of numeric values. Rational numbers are returned as a tuple of ``(numerator, denominator)``. @@ -703,7 +846,7 @@ The :py:meth:`~PIL.Image.Image.save` method can take the following keyword argum object and setting the type in :py:attr:`~PIL.TiffImagePlugin.ImageFileDirectory_v2.tagtype` with the appropriate numerical value from - ``TiffTags.TYPES``. + :py:data:`.TiffTags.TYPES`. .. versionadded:: 2.3.0 @@ -720,7 +863,7 @@ The :py:meth:`~PIL.Image.Image.save` method can take the following keyword argum Previous versions only supported some tags when writing using libtiff. The supported list is found in - :py:attr:`~PIL:TiffTags.LIBTIFF_CORE`. + :py:data:`.TiffTags.LIBTIFF_CORE`. .. versionadded:: 6.1.0 @@ -733,7 +876,7 @@ The :py:meth:`~PIL.Image.Image.save` method can take the following keyword argum **compression** A string containing the desired compression method for the file. (valid only with libtiff installed) Valid compression - methods are: ``None``, ``"tiff_ccitt"``, ``"group3"``, + methods are: :data:`None`, ``"tiff_ccitt"``, ``"group3"``, ``"group4"``, ``"tiff_jpeg"``, ``"tiff_adobe_deflate"``, ``"tiff_thunderscan"``, ``"tiff_deflate"``, ``"tiff_sgilog"``, ``"tiff_sgilog24"``, ``"tiff_raw_16"`` @@ -813,9 +956,10 @@ Saving sequences library is v0.5.0 or later. You can check webp animation support at runtime by calling ``features.check("webp_anim")``. -When calling :py:meth:`~PIL.Image.Image.save` to write a WebP file, the -following options are available when the ``save_all`` argument is present and -true. +When calling :py:meth:`~PIL.Image.Image.save` to write a WebP file, by default +only the first frame of a multiframe image will be saved. If the ``save_all`` +argument is present and true, then all frames will be saved, and the following +options will also be available. **append_images** A list of images to append as additional frames. Each of the @@ -897,7 +1041,7 @@ FLI, FLC Pillow reads Autodesk FLI and FLC animations. -The :py:meth:`~PIL.Image.Image.open` method sets the following +The :py:meth:`~PIL.Image.open` method sets the following :py:attr:`~PIL.Image.Image.info` properties: **duration** @@ -930,7 +1074,7 @@ GBR The GBR decoder reads GIMP brush files, version 1 and 2. -The :py:meth:`~PIL.Image.Image.open` method sets the following +The :py:meth:`~PIL.Image.open` method sets the following :py:attr:`~PIL.Image.Image.info` properties: **comment** @@ -945,7 +1089,7 @@ GD Pillow reads uncompressed GD2 files. Note that you must use :py:func:`PIL.GdImageFile.open` to read such a file. -The :py:meth:`~PIL.Image.Image.open` method sets the following +The :py:meth:`~PIL.Image.open` method sets the following :py:attr:`~PIL.Image.Image.info` properties: **transparency** @@ -1061,7 +1205,7 @@ XPM Pillow reads X pixmap files (mode ``P``) with 256 colors or less. -The :py:meth:`~PIL.Image.Image.open` method sets the following +The :py:meth:`~PIL.Image.open` method sets the following :py:attr:`~PIL.Image.Image.info` properties: **transparency** @@ -1102,7 +1246,7 @@ The :py:meth:`~PIL.Image.Image.save` method can take the following keyword argum **append** Set to True to append pages to an existing PDF file. If the file doesn't - exist, an :py:exc:`IOError` will be raised. + exist, an :py:exc:`OSError` will be raised. .. versionadded:: 5.1.0 diff --git a/docs/handbook/text-anchors.rst b/docs/handbook/text-anchors.rst new file mode 100644 index 000000000..0aecd3483 --- /dev/null +++ b/docs/handbook/text-anchors.rst @@ -0,0 +1,140 @@ + +.. _text-anchors: + +Text anchors +============ + +The ``anchor`` parameter determines the alignment of drawn text relative to the ``xy`` parameter. +The default alignment is top left, specifically ``la`` (left-ascender) for horizontal text +and ``lt`` (left-top) for vertical text. + +This parameter is only supported by OpenType/TrueType fonts. +Other fonts may ignore the parameter and use the default (top left) alignment. + +Specifying an anchor +^^^^^^^^^^^^^^^^^^^^ + +An anchor is specified with a two-character string. The first character is the +horizontal alignment, the second character is the vertical alignment. +For example, the default value of ``la`` for horizontal text means left-ascender +aligned text. + +When drawing text with :py:meth:`PIL.ImageDraw.ImageDraw.text` with a specific anchor, +the text will be placed such that the specified anchor point is at the ``xy`` coordinates. + +For example, in the following image, the text is ``ms`` (middle-baseline) aligned, with +``xy`` at the intersection of the two lines: + +.. image:: ../../Tests/images/test_anchor_quick_ms.png + :alt: ms (middle-baseline) aligned text. + :align: left + +.. code-block:: python + + from PIL import Image, ImageDraw, ImageFont + + font = ImageFont.truetype("Tests/fonts/NotoSans-Regular.ttf", 48) + im = Image.new("RGB", (200, 200), "white") + d = ImageDraw.Draw(im) + d.line(((0, 100), (200, 100)), "gray") + d.line(((100, 0), (100, 200)), "gray") + d.text((100, 100), "Quick", fill="black", anchor="ms", font=font) + +.. container:: clearer + + | + +.. only: comment + The container above prevents the image alignment from affecting the following text. + +Quick reference +^^^^^^^^^^^^^^^ + +.. image:: ../resources/anchor_horizontal.svg + :alt: Horizontal text + :align: center + +.. image:: ../resources/anchor_vertical.svg + :alt: Vertical text + :align: center + +Horizontal anchor alignment +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +``l`` --- left + Anchor is to the left of the text. + + For *horizontal* text this is the origin of the first glyph, as shown in the `FreeType tutorial`_. + +``m`` --- middle + Anchor is horizontally centered with the text. + + For *vertical* text it is recommended to use ``s`` (baseline) alignment instead, + as it does not change based on the specific glyphs of the given text. + +``r`` --- right + Anchor is to the right of the text. + + For *horizontal* text this is the advanced origin of the last glyph, as shown in the `FreeType tutorial`_. + +``s`` --- baseline *(vertical text only)* + Anchor is at the baseline (middle) of the text. The exact alignment depends on the font. + + For *vertical* text this is the recommended alignment, + as it does not change based on the specific glyphs of the given text + (see image for vertical text above). + +Vertical anchor alignment +^^^^^^^^^^^^^^^^^^^^^^^^^ + +``a`` --- ascender / top *(horizontal text only)* + Anchor is at the ascender line (top) of the first line of text, as defined by the font. + + See `Font metrics on Wikipedia`_ for more information. + +``t`` --- top *(single-line text only)* + Anchor is at the top of the text. + + For *vertical* text this is the origin of the first glyph, as shown in the `FreeType tutorial`_. + + For *horizontal* text it is recommended to use ``a`` (ascender) alignment instead, + as it does not change based on the specific glyphs of the given text. + +``m`` --- middle + Anchor is vertically centered with the text. + + For *horizontal* text this is the midpoint of the first ascender line and the last descender line. + +``s`` --- baseline *(horizontal text only)* + Anchor is at the baseline (bottom) of the first line of text, only descenders extend below the anchor. + + See `Font metrics on Wikipedia`_ for more information. + +``b`` --- bottom *(single-line text only)* + Anchor is at the bottom of the text. + + For *vertical* text this is the advanced origin of the last glyph, as shown in the `FreeType tutorial`_. + + For *horizontal* text it is recommended to use ``d`` (descender) alignment instead, + as it does not change based on the specific glyphs of the given text. + +``d`` --- descender / bottom *(horizontal text only)* + Anchor is at the descender line (bottom) of the last line of text, as defined by the font. + + See `Font metrics on Wikipedia`_ for more information. + +Examples +^^^^^^^^ + +The following image shows several examples of anchors for horizontal text. +In each section the ``xy`` parameter was set to the center shown by the intersection +of the two lines. + +.. comment: Image generated with ../example/anchors.py + +.. image:: ../example/anchors.png + :alt: Text anchor examples + :align: center + +.. _Font metrics on Wikipedia: https://en.wikipedia.org/wiki/Typeface#Font_metrics +.. _FreeType tutorial: https://freetype.org/freetype2/docs/tutorial/step2.html diff --git a/docs/handbook/tutorial.rst b/docs/handbook/tutorial.rst index acb61291c..6b68a0562 100644 --- a/docs/handbook/tutorial.rst +++ b/docs/handbook/tutorial.rst @@ -29,7 +29,7 @@ bands in the image, and also the pixel type and depth. Common modes are “L” (luminance) for greyscale images, “RGB” for true color images, and “CMYK” for pre-press images. -If the file cannot be opened, an :py:exc:`IOError` exception is raised. +If the file cannot be opened, an :py:exc:`OSError` exception is raised. Once you have an instance of the :py:class:`~PIL.Image.Image` class, you can use the methods defined by this class to process and manipulate the image. For @@ -74,8 +74,9 @@ Convert files to JPEG outfile = f + ".jpg" if infile != outfile: try: - Image.open(infile).save(outfile) - except IOError: + with Image.open(infile) as im: + im.save(outfile) + except OSError: print("cannot convert", infile) A second argument can be supplied to the :py:meth:`~PIL.Image.Image.save` @@ -99,7 +100,7 @@ Create JPEG thumbnails with Image.open(infile) as im: im.thumbnail(size) im.save(outfile, "JPEG") - except IOError: + except OSError: print("cannot create thumbnail for", infile) It is important to note that the library doesn’t decode or load the raster data @@ -123,8 +124,8 @@ Identify Image Files for infile in sys.argv[1:]: try: with Image.open(infile) as im: - print(infile, im.format, "%dx%d" % im.size, im.mode) - except IOError: + print(infile, im.format, f"{im.size}x{im.mode}") + except OSError: pass Cutting, pasting, and merging images @@ -405,13 +406,13 @@ Using the ImageSequence Iterator class # ...do something to frame... -Postscript printing +PostScript printing ------------------- The Python Imaging Library includes functions to print images, text and -graphics on Postscript printers. Here’s a simple example: +graphics on PostScript printers. Here’s a simple example: -Drawing Postscript +Drawing PostScript ^^^^^^^^^^^^^^^^^^ :: @@ -449,11 +450,11 @@ context manager:: ... If everything goes well, the result is an :py:class:`PIL.Image.Image` object. -Otherwise, an :exc:`IOError` exception is raised. +Otherwise, an :exc:`OSError` exception is raised. You can use a file-like object instead of the filename. The object must -implement :py:meth:`~file.read`, :py:meth:`~file.seek` and -:py:meth:`~file.tell` methods, and be opened in binary mode. +implement ``file.read``, ``file.seek`` and ``file.tell`` methods, +and be opened in binary mode. Reading from an open file ^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/docs/handbook/writing-your-own-file-decoder.rst b/docs/handbook/writing-your-own-file-decoder.rst index 58e2bccc5..03b4ca601 100644 --- a/docs/handbook/writing-your-own-file-decoder.rst +++ b/docs/handbook/writing-your-own-file-decoder.rst @@ -3,9 +3,9 @@ Writing Your Own Image Plugin ============================= -The Pillow uses a plug-in model which allows you to add your own +Pillow uses a plugin model which allows you to add your own decoders to the library, without any changes to the library -itself. Such plug-ins usually have names like +itself. Such plugins usually have names like :file:`XxxImagePlugin.py`, where ``Xxx`` is a unique format name (usually an abbreviation). @@ -14,11 +14,11 @@ itself. Such plug-ins usually have names like :file:`ImagePlugin.py`. You will need to import your image plugin manually. -Pillow decodes files in 2 stages: +Pillow decodes files in two stages: 1. It loops over the available image plugins in the loaded order, and - calls the plugin's ``accept`` function with the first 16 bytes of - the file. If the ``accept`` function returns true, the plugin's + calls the plugin's ``_accept`` function with the first 16 bytes of + the file. If the ``_accept`` function returns true, the plugin's ``_open`` method is called to set up the image metadata and image tiles. The ``_open`` method is not for decoding the actual image data. @@ -26,24 +26,24 @@ Pillow decodes files in 2 stages: called, which sets up a decoder for each tile and feeds the data to it. -An image plug-in should contain a format handler derived from the +An image plugin should contain a format handler derived from the :py:class:`PIL.ImageFile.ImageFile` base class. This class should -provide an :py:meth:`_open` method, which reads the file header and +provide an ``_open`` method, which reads the file header and sets up at least the :py:attr:`~PIL.Image.Image.mode` and :py:attr:`~PIL.Image.Image.size` attributes. To be able to load the -file, the method must also create a list of :py:attr:`tile` -descriptors, which contain a decoder name, extents of the tile, and +file, the method must also create a list of ``tile`` descriptors, +which contain a decoder name, extents of the tile, and any decoder-specific data. The format handler class must be explicitly registered, via a call to the :py:mod:`~PIL.Image` module. .. note:: For performance reasons, it is important that the - :py:meth:`_open` method quickly rejects files that do not have the + ``_open`` method quickly rejects files that do not have the appropriate contents. Example ------- -The following plug-in supports a simple format, which has a 128-byte header +The following plugin supports a simple format, which has a 128-byte header consisting of the words “SPAM” followed by the width, height, and pixel size in bits. The header fields are separated by spaces. The image data follows directly after the header, and can be either bi-level, greyscale, or 24-bit @@ -53,6 +53,11 @@ true color. from PIL import Image, ImageFile + + def _accept(prefix): + return prefix[:4] == b"SPAM" + + class SpamImageFile(ImageFile.ImageFile): format = "SPAM" @@ -60,12 +65,7 @@ true color. def _open(self): - # check header - header = self.fp.read(128) - if header[:4] != b"SPAM": - raise SyntaxError("not a SPAM file") - - header = header.split() + header = self.fp.read(128).split() # size in pixels (width, height) self._size = int(header[1]), int(header[2]) @@ -82,14 +82,16 @@ true color. raise SyntaxError("unknown number of bits") # data descriptor - self.tile = [ - ("raw", (0, 0) + self.size, 128, (self.mode, 0, 1)) - ] + self.tile = [("raw", (0, 0) + self.size, 128, (self.mode, 0, 1))] - Image.register_open(SpamImageFile.format, SpamImageFile) - Image.register_extension(SpamImageFile.format, ".spam") - Image.register_extension(SpamImageFile.format, ".spa") # dos version + Image.register_open(SpamImageFile.format, SpamImageFile, _accept) + + Image.register_extensions(SpamImageFile.format, [ + ".spam", + ".spa", # DOS version + ]) + The format handler must always set the :py:attr:`~PIL.Image.Image.size` and :py:attr:`~PIL.Image.Image.mode` @@ -103,10 +105,19 @@ Note that the image plugin must be explicitly registered using :py:func:`PIL.Image.register_open`. Although not required, it is also a good idea to register any extensions used by this format. -The :py:attr:`tile` attribute ------------------------------ +Once the plugin has been imported, it can be used: -To be able to read the file as well as just identifying it, the :py:attr:`tile` +.. code-block:: python + + from PIL import Image + import SpamImagePlugin + with Image.open("hopper.spam") as im: + pass + +The ``tile`` attribute +---------------------- + +To be able to read the file as well as just identifying it, the ``tile`` attribute must also be set. This attribute consists of a list of tile descriptors, where each descriptor specifies how data should be loaded to a given region in the image. In most cases, only a single descriptor is used, @@ -132,9 +143,9 @@ The fields are used as follows: **parameters** Parameters to the decoder. The contents of this field depends on the decoder specified by the first field in the tile descriptor tuple. If the - decoder doesn’t need any parameters, use None for this field. + decoder doesn’t need any parameters, use :data:`None` for this field. -Note that the :py:attr:`tile` attribute contains a list of tile descriptors, +Note that the ``tile`` attribute contains a list of tile descriptors, not just a single descriptor. Decoders @@ -146,7 +157,9 @@ The raw decoder The ``raw`` decoder is used to read uncompressed data from an image file. It can be used with most uncompressed file formats, such as PPM, BMP, uncompressed TIFF, and many others. To use the raw decoder with the -:py:func:`PIL.Image.frombytes` function, use the following syntax:: +:py:func:`PIL.Image.frombytes` function, use the following syntax: + +.. code-block:: python image = Image.frombytes( mode, size, data, "raw", @@ -175,11 +188,11 @@ The fields are used as follows: The **raw mode** field is used to determine how the data should be unpacked to match PIL’s internal pixel layout. PIL supports a large set of raw modes; for a -complete list, see the table in the :py:mod:`Unpack.c` module. The following +complete list, see the table in the :file:`Unpack.c` module. The following table describes some commonly used **raw modes**: +-----------+-----------------------------------------------------------------+ -| mode | description | +| mode | description | +===========+=================================================================+ | ``1`` | 1-bit bilevel, stored with the leftmost pixel in the most | | | significant bit. 0 means black, 1 means white. | @@ -211,7 +224,7 @@ Note that for the most common cases, the raw mode is simply the same as the mode The Python Imaging Library supports many other decoders, including JPEG, PNG, and PackBits. For details, see the :file:`decode.c` source file, and the -standard plug-in implementations provided with the library. +standard plugin implementations provided with the library. Decoding floating point data ---------------------------- @@ -223,7 +236,7 @@ You can use the ``raw`` decoder to read images where data is packed in any standard machine data type, using one of the following raw modes: ============ ======================================= -mode description +mode description ============ ======================================= ``F`` 32-bit native floating point. ``F;8`` 8-bit unsigned integer. @@ -258,6 +271,8 @@ image memory. To use the bit decoder with the :py:func:`PIL.Image.frombytes` function, use the following syntax:: +.. code-block:: python + image = Image.frombytes( mode, size, data, "bit", bits, pad, fill, sign, orientation @@ -350,7 +365,7 @@ interest in this object are: The target image, will be set by Pillow. **state** - An ImagingCodecStateInstance, will be set by Pillow. The **context** + An ImagingCodecStateInstance, will be set by Pillow. The ``context`` member is an opaque struct that can be used by the decoder to store any format specific state or options. @@ -414,4 +429,3 @@ Python-based file decoder: 3. Cleanup: The decoder instance's ``cleanup`` method is called. - diff --git a/docs/index.rst b/docs/index.rst index ab4c638bc..d2aca4bc4 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -3,27 +3,48 @@ Pillow Pillow is the friendly PIL fork by `Alex Clark and Contributors `_. PIL is the Python Imaging Library by Fredrik Lundh and Contributors. -Pillow for enterprise is available via the Tidelift Subscription. `Learn more `_. - -.. image:: https://zenodo.org/badge/17549/python-pillow/Pillow.svg - :target: https://zenodo.org/badge/latestdoi/17549/python-pillow/Pillow +Pillow for enterprise is available via the Tidelift Subscription. `Learn more `_. .. image:: https://readthedocs.org/projects/pillow/badge/?version=latest :target: https://pillow.readthedocs.io/?badge=latest :alt: Documentation Status -.. image:: https://img.shields.io/travis/python-pillow/Pillow/master.svg?label=Linux%20build - :target: https://travis-ci.org/python-pillow/Pillow - :alt: Travis CI build status (Linux) +.. image:: https://github.com/python-pillow/Pillow/workflows/Lint/badge.svg + :target: https://github.com/python-pillow/Pillow/actions?query=workflow%3ALint + :alt: GitHub Actions build status (Lint) -.. image:: https://img.shields.io/travis/python-pillow/pillow-wheels/master.svg?label=macOS%20build - :target: https://travis-ci.org/python-pillow/pillow-wheels - :alt: Travis CI build status (macOS) +.. image:: https://github.com/python-pillow/Pillow/workflows/Test%20Docker/badge.svg + :target: https://github.com/python-pillow/Pillow/actions?query=workflow%3A%22Test+Docker%22 + :alt: GitHub Actions build status (Test Docker) + +.. image:: https://github.com/python-pillow/Pillow/workflows/Test/badge.svg + :target: https://github.com/python-pillow/Pillow/actions?query=workflow%3ATest + :alt: GitHub Actions build status (Test Linux and macOS) + +.. image:: https://github.com/python-pillow/Pillow/workflows/Test%20Windows/badge.svg + :target: https://github.com/python-pillow/Pillow/actions?query=workflow%3A%22Test+Windows%22 + :alt: GitHub Actions build status (Test Windows) .. image:: https://img.shields.io/appveyor/build/python-pillow/Pillow/master.svg?label=Windows%20build :target: https://ci.appveyor.com/project/python-pillow/Pillow :alt: AppVeyor CI build status (Windows) +.. image:: https://img.shields.io/travis/com/python-pillow/pillow-wheels/master.svg?label=macOS%20build + :target: https://travis-ci.com/github/python-pillow/pillow-wheels + :alt: Travis CI build status (macOS) + +.. image:: https://codecov.io/gh/python-pillow/Pillow/branch/master/graph/badge.svg + :target: https://codecov.io/gh/python-pillow/Pillow + :alt: Code coverage + +.. image:: https://zenodo.org/badge/17549/python-pillow/Pillow.svg + :target: https://zenodo.org/badge/latestdoi/17549/python-pillow/Pillow + :alt: Zenodo + +.. image:: https://tidelift.com/badges/package/pypi/Pillow?style=flat + :target: https://tidelift.com/subscription/pkg/pypi-pillow?utm_source=pypi-pillow&utm_medium=badge + :alt: Tidelift + .. image:: https://img.shields.io/pypi/v/pillow.svg :target: https://pypi.org/project/Pillow/ :alt: Latest PyPI version @@ -32,9 +53,14 @@ Pillow for enterprise is available via the Tidelift Subscription. `Learn more = 2.1.0 no longer supports "import _imaging". Please use "from PIL.Image import core as _imaging" instead. -Notes ------ +Python Support +-------------- -.. note:: Pillow is supported on the following Python versions +Pillow supports these Python versions. - -+--------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ -|**Python** |**3.8**|**3.7**|**3.6**|**3.5**|**3.4**|**3.3**|**3.2**|**2.7**|**2.6**|**2.5**|**2.4**| -+--------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ -|Pillow >= 7 | Yes | Yes | Yes | Yes | | | | | | | | -+--------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ -|Pillow 6.2.1 - 6.2.2| Yes | Yes | Yes | Yes | | | | Yes | | | | -+--------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ -|Pillow 6.0 - 6.2.0 | | Yes | Yes | Yes | | | | Yes | | | | -+--------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ -|Pillow 5.2 - 5.4 | | Yes | Yes | Yes | Yes | | | Yes | | | | -+--------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ -|Pillow 5.0 - 5.1 | | | Yes | Yes | Yes | | | Yes | | | | -+--------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ -|Pillow 4 | | | Yes | Yes | Yes | Yes | | Yes | | | | -+--------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ -|Pillow 2 - 3 | | | | Yes | Yes | Yes | Yes | Yes | Yes | | | -+--------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ -|Pillow < 2 | | | | | | | | Yes | Yes | Yes | Yes | -+--------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ ++----------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ +| **Python** |**3.9**|**3.8**|**3.7**|**3.6**|**3.5**|**3.4**|**3.3**|**3.2**|**2.7**|**2.6**|**2.5**|**2.4**| ++----------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ +| Pillow >= 8.0 | Yes | Yes | Yes | Yes | | | | | | | | | ++----------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ +| Pillow 7.0 - 7.2 | | Yes | Yes | Yes | Yes | | | | | | | | ++----------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ +| Pillow 6.2.1 - 6.2.2 | | Yes | Yes | Yes | Yes | | | | Yes | | | | ++----------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ +| Pillow 6.0 - 6.2.0 | | | Yes | Yes | Yes | | | | Yes | | | | ++----------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ +| Pillow 5.2 - 5.4 | | | Yes | Yes | Yes | Yes | | | Yes | | | | ++----------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ +| Pillow 5.0 - 5.1 | | | | Yes | Yes | Yes | | | Yes | | | | ++----------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ +| Pillow 4 | | | | Yes | Yes | Yes | Yes | | Yes | | | | ++----------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ +| Pillow 2 - 3 | | | | | Yes | Yes | Yes | Yes | Yes | Yes | | | ++----------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ +| Pillow < 2 | | | | | | | | | Yes | Yes | Yes | Yes | ++----------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ Basic Installation ------------------ @@ -47,20 +48,22 @@ Basic Installation Install Pillow with :command:`pip`:: - python -m pip install pip - python -m pip install Pillow + python3 -m pip install --upgrade pip + python3 -m pip install --upgrade Pillow Windows Installation ^^^^^^^^^^^^^^^^^^^^ We provide Pillow binaries for Windows compiled for the matrix of -supported Pythons in both 32 and 64-bit versions in wheel, egg, and -executable installers. These binaries have all of the optional -libraries included except for raqm and libimagequant:: +supported Pythons in both 32 and 64-bit versions in the wheel format. +These binaries have all of the optional libraries included except +for raqm, libimagequant, and libxcb:: - python -m pip install pip - python -m pip install Pillow + python3 -m pip install --upgrade pip + python3 -m pip install --upgrade Pillow + +To install Pillow in MSYS2, see `Building on Windows using MSYS2/MinGW`_. macOS Installation @@ -68,11 +71,11 @@ macOS Installation We provide binaries for macOS for each of the supported Python versions in the wheel format. These include support for all optional -libraries except libimagequant. Raqm support requires libraqm, -fribidi, and harfbuzz to be installed separately:: +libraries except libimagequant and libxcb. Raqm support requires +libraqm, fribidi, and harfbuzz to be installed separately:: - python -m pip install pip - python -m pip install Pillow + python3 -m pip install --upgrade pip + python3 -m pip install --upgrade Pillow Linux Installation ^^^^^^^^^^^^^^^^^^ @@ -82,8 +85,8 @@ versions in the manylinux wheel format. These include support for all optional libraries except libimagequant. Raqm support requires libraqm, fribidi, and harfbuzz to be installed separately:: - python -m pip install pip - python -m pip install Pillow + python3 -m pip install --upgrade pip + python3 -m pip install --upgrade Pillow Most major Linux distributions, including Fedora, Debian/Ubuntu and ArchLinux also include Pillow in packages that previously contained @@ -156,7 +159,7 @@ Many of Pillow's features require external libraries: * **littlecms** provides color management * Pillow version 2.2.1 and below uses liblcms1, Pillow 2.3.0 and - above uses liblcms2. Tested with **1.19** and **2.7-2.9**. + above uses liblcms2. Tested with **1.19** and **2.7-2.11**. * **libwebp** provides the WebP format. @@ -168,13 +171,13 @@ Many of Pillow's features require external libraries: * **openjpeg** provides JPEG 2000 functionality. - * Pillow has been tested with openjpeg **2.0.0**, **2.1.0** and **2.3.1**. + * Pillow has been tested with openjpeg **2.0.0**, **2.1.0**, **2.3.1** and **2.4.0**. * Pillow does **not** support the earlier **1.5** series which ships with Debian Jessie. * **libimagequant** provides improved color quantization - * Pillow has been tested with libimagequant **2.6-2.12.6** + * Pillow has been tested with libimagequant **2.6-2.13.1** * Libimagequant is licensed GPLv3, which is more restrictive than the Pillow license, therefore we will not be distributing binaries with libimagequant support enabled. @@ -193,10 +196,12 @@ Many of Pillow's features require external libraries: is available if all the libraries are installed. * Windows support: Raqm is not included in prebuilt wheels +* **libxcb** provides X11 screengrab support. + Once you have installed the prerequisites, run:: - python -m pip install pip - python -m pip install Pillow + python3 -m pip install --upgrade pip + python3 -m pip install --upgrade Pillow If the prerequisites are installed in the standard library locations for your machine (e.g. :file:`/usr` or :file:`/usr/local`), no @@ -206,7 +211,7 @@ those locations by editing :file:`setup.py` or :file:`setup.cfg`, or by adding environment variables on the command line:: - CFLAGS="-I/usr/pkg/include" python -m pip install pillow + CFLAGS="-I/usr/pkg/include" python3 -m pip install --upgrade Pillow If Pillow has been previously built without the required prerequisites, it may be necessary to manually clear the pip cache or @@ -226,14 +231,14 @@ Build Options * Build flags: ``--disable-zlib``, ``--disable-jpeg``, ``--disable-tiff``, ``--disable-freetype``, ``--disable-lcms``, ``--disable-webp``, ``--disable-webpmux``, ``--disable-jpeg2000``, - ``--disable-imagequant``. + ``--disable-imagequant``, ``--disable-xcb``. Disable building the corresponding feature even if the development libraries are present on the building machine. * Build flags: ``--enable-zlib``, ``--enable-jpeg``, ``--enable-tiff``, ``--enable-freetype``, ``--enable-lcms``, ``--enable-webp``, ``--enable-webpmux``, ``--enable-jpeg2000``, - ``--enable-imagequant``. + ``--enable-imagequant``, ``--enable-xcb``. Require that the corresponding feature is built. The build will raise an exception if the libraries are not found. Webpmux (WebP metadata) relies on WebP support. Tcl and Tk also must be used together. @@ -250,11 +255,11 @@ Build Options Sample usage:: - MAX_CONCURRENCY=1 python setup.py build_ext --enable-[feature] install + MAX_CONCURRENCY=1 python3 setup.py build_ext --enable-[feature] install or using pip:: - python -m pip install pillow --global-option="build_ext" --global-option="--enable-[feature]" + python3 -m pip install --upgrade Pillow --global-option="build_ext" --global-option="--enable-[feature]" Building on macOS @@ -280,19 +285,56 @@ Then see ``depends/install_raqm_cmake.sh`` to install libraqm. Now install Pillow with:: - python -m pip install pip - python -m pip install Pillow + python3 -m pip install --upgrade pip + python3 -m pip install --upgrade Pillow or from within the uncompressed source directory:: - python setup.py install + python3 setup.py install Building on Windows ^^^^^^^^^^^^^^^^^^^ -We don't recommend trying to build on Windows. It is a maze of twisty -passages, mostly dead ends. There are build scripts and notes for the -Windows build in the ``winbuild`` directory. +We recommend you use prebuilt wheels from PyPI. +If you wish to compile Pillow manually, you can use the build scripts +in the ``winbuild`` directory used for CI testing and development. +These scripts require Visual Studio 2017 or newer and NASM. + +Building on Windows using MSYS2/MinGW +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +To build Pillow using MSYS2, make sure you run the **MSYS2 MinGW 32-bit** or +**MSYS2 MinGW 64-bit** console, *not* **MSYS2** directly. + +The following instructions target the 64-bit build, for 32-bit +replace all occurrences of ``mingw-w64-x86_64-`` with ``mingw-w64-i686-``. + +Make sure you have Python and GCC installed:: + + pacman -S \ + mingw-w64-x86_64-gcc \ + mingw-w64-x86_64-python3 \ + mingw-w64-x86_64-python3-pip \ + mingw-w64-x86_64-python3-setuptools + +Prerequisites are installed on **MSYS2 MinGW 64-bit** with:: + + pacman -S \ + mingw-w64-x86_64-libjpeg-turbo \ + mingw-w64-x86_64-zlib \ + mingw-w64-x86_64-libtiff \ + mingw-w64-x86_64-freetype \ + mingw-w64-x86_64-lcms2 \ + mingw-w64-x86_64-libwebp \ + mingw-w64-x86_64-openjpeg2 \ + mingw-w64-x86_64-libimagequant \ + mingw-w64-x86_64-libraqm + +Now install Pillow with:: + + python3 -m pip install --upgrade pip + python3 -m pip install --upgrade Pillow + Building on FreeBSD ^^^^^^^^^^^^^^^^^^^ @@ -305,7 +347,7 @@ Make sure you have Python's development libraries installed:: Prerequisites are installed on **FreeBSD 10 or 11** with:: - sudo pkg install jpeg-turbo tiff webp lcms2 freetype2 openjpeg harfbuzz fribidi + sudo pkg install jpeg-turbo tiff webp lcms2 freetype2 openjpeg harfbuzz fribidi libxcb Then see ``depends/install_raqm_cmake.sh`` to install libraqm. @@ -326,21 +368,21 @@ In Fedora, the command is:: .. Note:: ``redhat-rpm-config`` is required on Fedora 23, but not earlier versions. -Prerequisites are installed on **Ubuntu 16.04 LTS** with:: +Prerequisites for **Ubuntu 16.04 LTS - 20.04 LTS** are installed with:: sudo apt-get install libtiff5-dev libjpeg8-dev libopenjp2-7-dev zlib1g-dev \ libfreetype6-dev liblcms2-dev libwebp-dev tcl8.6-dev tk8.6-dev python3-tk \ - libharfbuzz-dev libfribidi-dev + libharfbuzz-dev libfribidi-dev libxcb1-dev Then see ``depends/install_raqm.sh`` to install libraqm. -Prerequisites are installed on recent **RedHat** **Centos** or **Fedora** with:: +Prerequisites are installed on recent **Red Hat**, **CentOS** or **Fedora** with:: sudo dnf install libtiff-devel libjpeg-devel openjpeg2-devel zlib-devel \ freetype-devel lcms2-devel libwebp-devel tcl-devel tk-devel \ - harfbuzz-devel fribidi-devel libraqm-devel libimagequant-devel + harfbuzz-devel fribidi-devel libraqm-devel libimagequant-devel libxcb-devel -Note that the package manager may be yum or dnf, depending on the +Note that the package manager may be yum or DNF, depending on the exact distribution. See also the ``Dockerfile``\s in the Test Infrastructure repo @@ -376,39 +418,37 @@ These platforms are built and tested for every change. +----------------------------------+--------------------------+-----------------------+ |**Operating system** |**Tested Python versions**|**Tested architecture**| +----------------------------------+--------------------------+-----------------------+ -| Alpine | 3.7 |x86-64 | +| Alpine | 3.8 |x86-64 | +----------------------------------+--------------------------+-----------------------+ -| Arch | 3.7 |x86-64 | +| Arch | 3.8 |x86-64 | +----------------------------------+--------------------------+-----------------------+ -| Amazon Linux 1 | 3.6 |x86-64 | -+----------------------------------+--------------------------+-----------------------+ -| Amazon Linux 2 | 3.6 |x86-64 | -+----------------------------------+--------------------------+-----------------------+ -| CentOS 6 | 3.6 |x86-64 | +| Amazon Linux 2 | 3.7 |x86-64 | +----------------------------------+--------------------------+-----------------------+ | CentOS 7 | 3.6 |x86-64 | +----------------------------------+--------------------------+-----------------------+ | CentOS 8 | 3.6 |x86-64 | +----------------------------------+--------------------------+-----------------------+ -| Debian 9 Stretch | 3.5 |x86 | -+----------------------------------+--------------------------+-----------------------+ | Debian 10 Buster | 3.7 |x86 | +----------------------------------+--------------------------+-----------------------+ -| Fedora 30 | 3.7 |x86-64 | +| Fedora 32 | 3.8 |x86-64 | +----------------------------------+--------------------------+-----------------------+ -| Fedora 31 | 3.7 |x86-64 | +| Fedora 33 | 3.9 |x86-64 | +----------------------------------+--------------------------+-----------------------+ -| macOS 10.15 Catalina | 3.5, 3.6, 3.7, 3.8, PyPy3|x86-64 | +| macOS 10.15 Catalina | 3.6, 3.7, 3.8, 3.9, PyPy3|x86-64 | +----------------------------------+--------------------------+-----------------------+ -| Ubuntu Linux 16.04 LTS | 3.5, 3.6, 3.7, 3.8, PyPy3|x86-64 | +| Ubuntu Linux 16.04 LTS (Xenial) | 3.6, 3.7, 3.8, 3.9, PyPy3|x86-64 | +----------------------------------+--------------------------+-----------------------+ -| Windows Server 2012 R2 | 3.5, 3.8 |x86, x86-64 | -| +--------------------------+-----------------------+ -| | PyPy3, 3.7/MinGW |x86 | +| Ubuntu Linux 18.04 LTS (Bionic) | 3.6, 3.7, 3.8, 3.9, PyPy3|x86-64 | +----------------------------------+--------------------------+-----------------------+ -| Windows Server 2019 | 3.5, 3.6, 3.7, 3.8 |x86, x86-64 | +| Ubuntu Linux 20.04 LTS (Focal) | 3.8 |x86-64 | ++----------------------------------+--------------------------+-----------------------+ +| Windows Server 2016 | 3.6 |x86-64 | ++----------------------------------+--------------------------+-----------------------+ +| Windows Server 2019 | 3.6, 3.7, 3.8, 3.9 |x86, x86-64 | | +--------------------------+-----------------------+ | | PyPy3 |x86 | +| +--------------------------+-----------------------+ +| | 3.8/MinGW |x86, x86-64 | +----------------------------------+--------------------------+-----------------------+ @@ -425,9 +465,17 @@ These platforms have been reported to work at the versions mentioned. +----------------------------------+------------------------------+--------------------------------+-----------------------+ |**Operating system** |**Tested Python versions** |**Latest tested Pillow version**|**Tested processors** | +----------------------------------+------------------------------+--------------------------------+-----------------------+ -| macOS 10.15 Catalina | 3.5, 3.6, 3.7, 3.8 | 7.0.0 |x86-64 | +| macOS 11.0 Big Sur | 3.8, 3.9 | 8.0.1 |arm | +| +------------------------------+--------------------------------+-----------------------+ +| | 3.6, 3.7, 3.8, 3.9 | 8.0.1 |x86-64 | +----------------------------------+------------------------------+--------------------------------+-----------------------+ -| macOS 10.14 Mojave | 2.7, 3.5, 3.6, 3.7 | 6.0.0 |x86-64 | +| macOS 10.15 Catalina | 3.6, 3.7, 3.8, 3.9 | 8.0.1 |x86-64 | +| +------------------------------+--------------------------------+ + +| | 3.5 | 7.2.0 | | ++----------------------------------+------------------------------+--------------------------------+-----------------------+ +| macOS 10.14 Mojave | 3.5, 3.6, 3.7, 3.8 | 7.2.0 |x86-64 | +| +------------------------------+--------------------------------+ + +| | 2.7 | 6.0.0 | | | +------------------------------+--------------------------------+ + | | 3.4 | 5.4.1 | | +----------------------------------+------------------------------+--------------------------------+-----------------------+ @@ -449,14 +497,14 @@ These platforms have been reported to work at the versions mentioned. +----------------------------------+------------------------------+--------------------------------+-----------------------+ | Fedora 23 | 2.7, 3.4 | 3.1.0 |x86-64 | +----------------------------------+------------------------------+--------------------------------+-----------------------+ -| Ubuntu Linux 12.04 LTS | 2.6, 3.2, 3.3, 3.4, 3.5 | 3.4.1 |x86,x86-64 | +| Ubuntu Linux 12.04 LTS (Precise) | 2.6, 3.2, 3.3, 3.4, 3.5 | 3.4.1 |x86,x86-64 | | | PyPy5.3.1, PyPy3 v2.4.0 | | | | +------------------------------+--------------------------------+-----------------------+ | | 2.7 | 4.3.0 |x86-64 | | +------------------------------+--------------------------------+-----------------------+ | | 2.7, 3.2 | 3.4.1 |ppc | +----------------------------------+------------------------------+--------------------------------+-----------------------+ -| Ubuntu Linux 10.04 LTS | 2.6 | 2.3.0 |x86,x86-64 | +| Ubuntu Linux 10.04 LTS (Lucid) | 2.6 | 2.3.0 |x86,x86-64 | +----------------------------------+------------------------------+--------------------------------+-----------------------+ | Debian 8.2 Jessie | 2.7, 3.4 | 3.1.0 |x86-64 | +----------------------------------+------------------------------+--------------------------------+-----------------------+ @@ -472,11 +520,13 @@ These platforms have been reported to work at the versions mentioned. +----------------------------------+------------------------------+--------------------------------+-----------------------+ | FreeBSD 10.2 | 2.7, 3.4 | 3.1.0 |x86-64 | +----------------------------------+------------------------------+--------------------------------+-----------------------+ +| Windows 10 | 3.7 | 7.1.0 |x86-64 | ++----------------------------------+------------------------------+--------------------------------+-----------------------+ | Windows 8.1 Pro | 2.6, 2.7, 3.2, 3.3, 3.4 | 2.4.0 |x86,x86-64 | +----------------------------------+------------------------------+--------------------------------+-----------------------+ | Windows 8 Pro | 2.6, 2.7, 3.2, 3.3, 3.4a3 | 2.2.0 |x86,x86-64 | +----------------------------------+------------------------------+--------------------------------+-----------------------+ -| Windows 7 Pro | 2.7, 3.2, 3.3 | 3.4.1 |x86-64 | +| Windows 7 Professional | 3.7 | 7.0.0 |x86,x86-64 | +----------------------------------+------------------------------+--------------------------------+-----------------------+ | Windows Server 2008 R2 Enterprise| 3.3 | |x86-64 | +----------------------------------+------------------------------+--------------------------------+-----------------------+ diff --git a/docs/porting.rst b/docs/porting.rst index 3b14cde9d..2943d72fd 100644 --- a/docs/porting.rst +++ b/docs/porting.rst @@ -19,7 +19,8 @@ to this:: from PIL import Image -The :py:mod:`_imaging` module has been moved. You can now import it like this:: +The :py:mod:`PIL._imaging` module has been moved to :py:mod:`PIL.Image.core`. +You can now import it like this:: from PIL.Image import core as _imaging diff --git a/docs/reference/ExifTags.rst b/docs/reference/ExifTags.rst index 9fc7cd13b..4567d4d3e 100644 --- a/docs/reference/ExifTags.rst +++ b/docs/reference/ExifTags.rst @@ -1,13 +1,14 @@ .. py:module:: PIL.ExifTags .. py:currentmodule:: PIL.ExifTags -:py:mod:`ExifTags` Module -========================== +:py:mod:`~PIL.ExifTags` Module +============================== -The :py:mod:`ExifTags` module exposes two dictionaries which +The :py:mod:`~PIL.ExifTags` module exposes two dictionaries which provide constants and clear-text names for various well-known EXIF tags. -.. py:class:: PIL.ExifTags.TAGS +.. py:data:: TAGS + :type: dict The TAG dictionary maps 16-bit integer EXIF tag enumerations to descriptive string names. For instance: @@ -16,7 +17,8 @@ provide constants and clear-text names for various well-known EXIF tags. >>> TAGS[0x010e] 'ImageDescription' -.. py:class:: PIL.ExifTags.GPSTAGS +.. py:data:: GPSTAGS + :type: dict The GPSTAGS dictionary maps 8-bit integer EXIF gps enumerations to descriptive string names. For instance: diff --git a/docs/reference/Image.rst b/docs/reference/Image.rst index 8af56f6c1..8d63c173b 100644 --- a/docs/reference/Image.rst +++ b/docs/reference/Image.rst @@ -1,8 +1,8 @@ .. py:module:: PIL.Image .. py:currentmodule:: PIL.Image -:py:mod:`Image` Module -====================== +:py:mod:`~PIL.Image` Module +=========================== The :py:mod:`~PIL.Image` module provides a class with the same name which is used to represent a PIL image. The module also provides a number of factory @@ -52,14 +52,22 @@ Functions .. warning:: To protect against potential DOS attacks caused by "`decompression bombs`_" (i.e. malicious files which decompress into a huge amount of data and are designed to crash or cause disruption by using up - a lot of memory), Pillow will issue a ``DecompressionBombWarning`` if the image is over a certain - limit. If desired, the warning can be turned into an error with - ``warnings.simplefilter('error', Image.DecompressionBombWarning)`` or suppressed entirely with - ``warnings.simplefilter('ignore', Image.DecompressionBombWarning)``. See also `the logging - documentation`_ to have warnings output to the logging facility instead of stderr. + a lot of memory), Pillow will issue a ``DecompressionBombWarning`` if the number of pixels in an + image is over a certain limit, :py:data:`PIL.Image.MAX_IMAGE_PIXELS`. - .. _decompression bombs: https://en.wikipedia.org/wiki/Zip_bomb - .. _the logging documentation: https://docs.python.org/3/library/logging.html#integration-with-the-warnings-module + This threshold can be changed by setting :py:data:`PIL.Image.MAX_IMAGE_PIXELS`. It can be disabled + by setting ``Image.MAX_IMAGE_PIXELS = None``. + + If desired, the warning can be turned into an error with + ``warnings.simplefilter('error', Image.DecompressionBombWarning)`` or suppressed entirely with + ``warnings.simplefilter('ignore', Image.DecompressionBombWarning)``. See also + `the logging documentation`_ to have warnings output to the logging facility instead of stderr. + + If the number of pixels is greater than twice :py:data:`PIL.Image.MAX_IMAGE_PIXELS`, then a + ``DecompressionBombError`` will be raised instead. + + .. _decompression bombs: https://en.wikipedia.org/wiki/Zip_bomb + .. _the logging documentation: https://docs.python.org/3/library/logging.html#integration-with-the-warnings-module Image processing ^^^^^^^^^^^^^^^^ @@ -76,9 +84,16 @@ Constructing images .. autofunction:: new .. autofunction:: fromarray .. autofunction:: frombytes -.. autofunction:: fromstring .. autofunction:: frombuffer +Generating images +^^^^^^^^^^^^^^^^^ + +.. autofunction:: effect_mandelbrot +.. autofunction:: effect_noise +.. autofunction:: linear_gradient +.. autofunction:: radial_gradient + Registering plugins ^^^^^^^^^^^^^^^^^^^ @@ -88,12 +103,14 @@ Registering plugins ignore them. .. autofunction:: register_open -.. autofunction:: register_decoder .. autofunction:: register_mime .. autofunction:: register_save -.. autofunction:: register_encoder +.. autofunction:: register_save_all .. autofunction:: register_extension - +.. autofunction:: register_extensions +.. autofunction:: registered_extensions +.. autofunction:: register_decoder +.. autofunction:: register_encoder The Image Class --------------- @@ -140,7 +157,10 @@ This crops the input image with the provided coordinates: .. automethod:: PIL.Image.Image.draft +.. automethod:: PIL.Image.Image.effect_spread +.. automethod:: PIL.Image.Image.entropy .. automethod:: PIL.Image.Image.filter +.. automethod:: PIL.Image.Image.frombytes This blurs the input image using a filter from the ``ImageFilter`` module: @@ -176,13 +196,15 @@ This helps to get the bounding box coordinates of the input image: print(im.getbbox()) # Returns four coordinates in the format (left, upper, right, lower) +.. automethod:: PIL.Image.Image.getchannel .. automethod:: PIL.Image.Image.getcolors .. automethod:: PIL.Image.Image.getdata +.. automethod:: PIL.Image.Image.getexif .. automethod:: PIL.Image.Image.getextrema .. automethod:: PIL.Image.Image.getpalette .. automethod:: PIL.Image.Image.getpixel +.. automethod:: PIL.Image.Image.getprojection .. automethod:: PIL.Image.Image.histogram -.. automethod:: PIL.Image.Image.offset .. automethod:: PIL.Image.Image.paste .. automethod:: PIL.Image.Image.point .. automethod:: PIL.Image.Image.putalpha @@ -190,6 +212,8 @@ This helps to get the bounding box coordinates of the input image: .. automethod:: PIL.Image.Image.putpalette .. automethod:: PIL.Image.Image.putpixel .. automethod:: PIL.Image.Image.quantize +.. automethod:: PIL.Image.Image.reduce +.. automethod:: PIL.Image.Image.remap_palette .. automethod:: PIL.Image.Image.resize This resizes the given image from ``(width, height)`` to ``(width/2, height/2)``: @@ -204,7 +228,6 @@ This resizes the given image from ``(width, height)`` to ``(width/2, height/2)`` (width, height) = (im.width // 2, im.height // 2) im_resized = im.resize((width, height)) -.. automethod:: PIL.Image.Image.remap_palette .. automethod:: PIL.Image.Image.rotate This rotates the input image by ``theta`` degrees counter clockwise: @@ -224,16 +247,14 @@ This rotates the input image by ``theta`` degrees counter clockwise: .. automethod:: PIL.Image.Image.seek .. automethod:: PIL.Image.Image.show .. automethod:: PIL.Image.Image.split -.. automethod:: PIL.Image.Image.getchannel .. automethod:: PIL.Image.Image.tell .. automethod:: PIL.Image.Image.thumbnail .. automethod:: PIL.Image.Image.tobitmap .. automethod:: PIL.Image.Image.tobytes -.. automethod:: PIL.Image.Image.tostring .. automethod:: PIL.Image.Image.transform .. automethod:: PIL.Image.Image.transpose -This flips the input image by using the ``Image.FLIP_LEFT_RIGHT`` method. +This flips the input image by using the :data:`FLIP_LEFT_RIGHT` method. .. code-block:: python @@ -249,67 +270,59 @@ This flips the input image by using the ``Image.FLIP_LEFT_RIGHT`` method. .. automethod:: PIL.Image.Image.verify -.. automethod:: PIL.Image.Image.fromstring - .. automethod:: PIL.Image.Image.load .. automethod:: PIL.Image.Image.close -Attributes ----------- +Image Attributes +---------------- Instances of the :py:class:`Image` class have the following attributes: -.. py:attribute:: filename +.. py:attribute:: Image.filename + :type: str The filename or path of the source file. Only images created with the factory function ``open`` have a filename attribute. If the input is a file like object, the filename attribute is set to an empty string. - :type: :py:class:`string` - -.. py:attribute:: format +.. py:attribute:: Image.format + :type: Optional[str] The file format of the source file. For images created by the library itself (via a factory function, or by running a method on an existing - image), this attribute is set to ``None``. + image), this attribute is set to :data:`None`. - :type: :py:class:`string` or ``None`` - -.. py:attribute:: mode +.. py:attribute:: Image.mode + :type: str Image mode. This is a string specifying the pixel format used by the image. Typical values are “1”, “L”, “RGB”, or “CMYK.” See :ref:`concept-modes` for a full list. - :type: :py:class:`string` - -.. py:attribute:: size +.. py:attribute:: Image.size + :type: tuple[int] Image size, in pixels. The size is given as a 2-tuple (width, height). - :type: ``(width, height)`` - -.. py:attribute:: width +.. py:attribute:: Image.width + :type: int Image width, in pixels. - :type: :py:class:`int` - -.. py:attribute:: height +.. py:attribute:: Image.height + :type: int Image height, in pixels. - :type: :py:class:`int` - -.. py:attribute:: palette +.. py:attribute:: Image.palette + :type: Optional[PIL.ImagePalette.ImagePalette] Colour palette table, if any. If mode is "P" or "PA", this should be an instance of the :py:class:`~PIL.ImagePalette.ImagePalette` class. - Otherwise, it should be set to ``None``. + Otherwise, it should be set to :data:`None`. - :type: :py:class:`~PIL.ImagePalette.ImagePalette` or ``None`` - -.. py:attribute:: info +.. py:attribute:: Image.info + :type: dict A dictionary holding data associated with the image. This dictionary is used by file handlers to pass on various non-image information read from @@ -322,4 +335,177 @@ Instances of the :py:class:`Image` class have the following attributes: Unless noted elsewhere, this dictionary does not affect saving files. - :type: :py:class:`dict` +.. py:attribute:: Image.is_animated + :type: bool + + ``True`` if this image has more than one frame, or ``False`` otherwise. + + This attribute is only defined by image plugins that support animated images. + Plugins may leave this attribute undefined if they don't support loading + animated images, even if the given format supports animated images. + + Given that this attribute is not present for all images use + ``getattr(image, "is_animated", False)`` to check if Pillow is aware of multiple + frames in an image regardless of its format. + + .. seealso:: :attr:`~Image.n_frames`, :func:`~Image.seek` and :func:`~Image.tell` + +.. py:attribute:: Image.n_frames + :type: int + + The number of frames in this image. + + This attribute is only defined by image plugins that support animated images. + Plugins may leave this attribute undefined if they don't support loading + animated images, even if the given format supports animated images. + + Given that this attribute is not present for all images use + ``getattr(image, "n_frames", 1)`` to check the number of frames that Pillow is + aware of in an image regardless of its format. + + .. seealso:: :attr:`~Image.is_animated`, :func:`~Image.seek` and :func:`~Image.tell` + +Classes +------- + +.. autoclass:: PIL.Image.Exif + :members: + :undoc-members: + :show-inheritance: +.. autoclass:: PIL.Image.ImagePointHandler +.. autoclass:: PIL.Image.ImageTransformHandler + +Constants +--------- + +.. data:: NONE +.. data:: MAX_IMAGE_PIXELS + + Set to 89,478,485, approximately 0.25GB for a 24-bit (3 bpp) image. + See :py:meth:`~PIL.Image.open` for more information about how this is used. + +Transpose methods +^^^^^^^^^^^^^^^^^ + +Used to specify the :meth:`Image.transpose` method to use. + +.. data:: FLIP_LEFT_RIGHT +.. data:: FLIP_TOP_BOTTOM +.. data:: ROTATE_90 +.. data:: ROTATE_180 +.. data:: ROTATE_270 +.. data:: TRANSPOSE +.. data:: TRANSVERSE + +Transform methods +^^^^^^^^^^^^^^^^^ + +Used to specify the :meth:`Image.transform` method to use. + +.. data:: AFFINE + + Affine transform + +.. data:: EXTENT + + Cut out a rectangular subregion + +.. data:: PERSPECTIVE + + Perspective transform + +.. data:: QUAD + + Map a quadrilateral to a rectangle + +.. data:: MESH + + Map a number of source quadrilaterals in one operation + +Resampling filters +^^^^^^^^^^^^^^^^^^ + +See :ref:`concept-filters` for details. + +.. data:: NEAREST + :noindex: +.. data:: BOX + :noindex: +.. data:: BILINEAR + :noindex: +.. data:: HAMMING + :noindex: +.. data:: BICUBIC + :noindex: +.. data:: LANCZOS + :noindex: + +Some filters are also available under the following names for backwards compatibility: + +.. data:: NONE + :noindex: + :value: NEAREST +.. data:: LINEAR + :value: BILINEAR +.. data:: CUBIC + :value: BICUBIC +.. data:: ANTIALIAS + :value: LANCZOS + +Dither modes +^^^^^^^^^^^^ + +Used to specify the dithering method to use for the +:meth:`~Image.convert` and :meth:`~Image.quantize` methods. + +.. data:: NONE + :noindex: + + No dither + +.. comment: (not implemented) + .. data:: ORDERED + .. data:: RASTERIZE + +.. data:: FLOYDSTEINBERG + + Floyd-Steinberg dither + +Palettes +^^^^^^^^ + +Used to specify the pallete to use for the :meth:`~Image.convert` method. + +.. data:: WEB +.. data:: ADAPTIVE + +Quantization methods +^^^^^^^^^^^^^^^^^^^^ + +Used to specify the quantization method to use for the :meth:`~Image.quantize` method. + +.. data:: MEDIANCUT + + Median cut + +.. data:: MAXCOVERAGE + + Maximum coverage + +.. data:: FASTOCTREE + + Fast octree + +.. data:: LIBIMAGEQUANT + + libimagequant + + Check support using :py:func:`PIL.features.check_feature` + with ``feature="libimagequant"``. + +.. comment: These are not referenced anywhere? + Categories + ^^^^^^^^^^ + .. data:: NORMAL + .. data:: SEQUENCE + .. data:: CONTAINER diff --git a/docs/reference/ImageChops.rst b/docs/reference/ImageChops.rst index 6c8f11253..9519361a7 100644 --- a/docs/reference/ImageChops.rst +++ b/docs/reference/ImageChops.rst @@ -1,15 +1,15 @@ .. py:module:: PIL.ImageChops .. py:currentmodule:: PIL.ImageChops -:py:mod:`ImageChops` ("Channel Operations") Module -================================================== +:py:mod:`~PIL.ImageChops` ("Channel Operations") Module +======================================================= -The :py:mod:`ImageChops` module contains a number of arithmetical image +The :py:mod:`~PIL.ImageChops` module contains a number of arithmetical image operations, called channel operations (“chops”). These can be used for various purposes, including special effects, image compositions, algorithmic painting, and more. -For more pre-made operations, see :py:mod:`ImageOps`. +For more pre-made operations, see :py:mod:`~PIL.ImageOps`. At this time, most channel operations are only implemented for 8-bit images (e.g. “L” and “RGB”). @@ -36,12 +36,10 @@ operations in this module). .. autofunction:: PIL.ImageChops.logical_or .. autofunction:: PIL.ImageChops.logical_xor .. autofunction:: PIL.ImageChops.multiply -.. py:method:: PIL.ImageChops.offset(image, xoffset, yoffset=None) - - Returns a copy of the image where data has been offset by the given - distances. Data wraps around the edges. If **yoffset** is omitted, it - is assumed to be equal to **xoffset**. - +.. autofunction:: PIL.ImageChops.soft_light +.. autofunction:: PIL.ImageChops.hard_light +.. autofunction:: PIL.ImageChops.overlay +.. autofunction:: PIL.ImageChops.offset .. autofunction:: PIL.ImageChops.screen .. autofunction:: PIL.ImageChops.subtract .. autofunction:: PIL.ImageChops.subtract_modulo diff --git a/docs/reference/ImageCms.rst b/docs/reference/ImageCms.rst index 922e1685a..f938e63a0 100644 --- a/docs/reference/ImageCms.rst +++ b/docs/reference/ImageCms.rst @@ -1,16 +1,37 @@ .. py:module:: PIL.ImageCms .. py:currentmodule:: PIL.ImageCms -:py:mod:`ImageCms` Module -========================= +:py:mod:`~PIL.ImageCms` Module +============================== -The :py:mod:`ImageCms` module provides color profile management +The :py:mod:`~PIL.ImageCms` module provides color profile management support using the LittleCMS2 color management engine, based on Kevin Cazabon's PyCMS library. -.. automodule:: PIL.ImageCms - :members: - :noindex: +.. autoclass:: ImageCmsTransform +.. autoexception:: PyCMSError + +Functions +--------- + +.. autofunction:: applyTransform +.. autofunction:: buildProofTransform +.. autofunction:: buildProofTransformFromOpenProfiles +.. autofunction:: buildTransform +.. autofunction:: buildTransformFromOpenProfiles +.. autofunction:: createProfile +.. autofunction:: getDefaultIntent +.. autofunction:: getOpenProfile +.. autofunction:: getProfileCopyright +.. autofunction:: getProfileDescription +.. autofunction:: getProfileInfo +.. autofunction:: getProfileManufacturer +.. autofunction:: getProfileModel +.. autofunction:: getProfileName +.. autofunction:: get_display_profile +.. autofunction:: isIntentSupported +.. autofunction:: profileToProfile +.. autofunction:: versions CmsProfile ---------- @@ -25,87 +46,73 @@ can be easily displayed in a chromaticity diagram, for example). .. py:class:: CmsProfile .. py:attribute:: creation_date + :type: Optional[datetime.datetime] Date and time this profile was first created (see 7.2.1 of ICC.1:2010). - :type: :py:class:`datetime.datetime` or ``None`` - .. py:attribute:: version + :type: float The version number of the ICC standard that this profile follows (e.g. ``2.0``). - :type: :py:class:`float` - .. py:attribute:: icc_version + :type: int Same as ``version``, but in encoded format (see 7.2.4 of ICC.1:2010). .. py:attribute:: device_class + :type: str 4-character string identifying the profile class. One of ``scnr``, ``mntr``, ``prtr``, ``link``, ``spac``, ``abst``, ``nmcl`` (see 7.2.5 of ICC.1:2010 for details). - :type: :py:class:`string` - .. py:attribute:: xcolor_space + :type: str 4-character string (padded with whitespace) identifying the color space, e.g. ``XYZ␣``, ``RGB␣`` or ``CMYK`` (see 7.2.6 of ICC.1:2010 for details). - Note that the deprecated attribute ``color_space`` contains an - interpreted (non-padded) variant of this (but can be empty on - unknown input). - - :type: :py:class:`string` - .. py:attribute:: connection_space + :type: str 4-character string (padded with whitespace) identifying the color space on the B-side of the transform (see 7.2.7 of ICC.1:2010 for details). - Note that the deprecated attribute ``pcs`` contains an interpreted - (non-padded) variant of this (but can be empty on unknown input). - - :type: :py:class:`string` - .. py:attribute:: header_flags + :type: int The encoded header flags of the profile (see 7.2.11 of ICC.1:2010 for details). - :type: :py:class:`int` - .. py:attribute:: header_manufacturer + :type: str 4-character string (padded with whitespace) identifying the device manufacturer, which shall match the signature contained in the appropriate section of the ICC signature registry found at www.color.org (see 7.2.12 of ICC.1:2010). - :type: :py:class:`string` - .. py:attribute:: header_model + :type: str 4-character string (padded with whitespace) identifying the device model, which shall match the signature contained in the appropriate section of the ICC signature registry found at www.color.org (see 7.2.13 of ICC.1:2010). - :type: :py:class:`string` - .. py:attribute:: attributes + :type: int Flags used to identify attributes unique to the particular device setup for which the profile is applicable (see 7.2.14 of ICC.1:2010 for details). - :type: :py:class:`int` - .. py:attribute:: rendering_intent + :type: int The rendering intent to use when combining this profile with another profile (usually overridden at run-time, but provided here @@ -114,143 +121,135 @@ can be easily displayed in a chromaticity diagram, for example). One of ``ImageCms.INTENT_ABSOLUTE_COLORIMETRIC``, ``ImageCms.INTENT_PERCEPTUAL``, ``ImageCms.INTENT_RELATIVE_COLORIMETRIC`` and ``ImageCms.INTENT_SATURATION``. - :type: :py:class:`int` - .. py:attribute:: profile_id + :type: bytes A sequence of 16 bytes identifying the profile (via a specially constructed MD5 sum), or 16 binary zeroes if the profile ID has not been calculated (see 7.2.18 of ICC.1:2010). - :type: :py:class:`bytes` - .. py:attribute:: copyright + :type: Optional[str] The text copyright information for the profile (see 9.2.21 of ICC.1:2010). - :type: :py:class:`unicode` or ``None`` - .. py:attribute:: manufacturer + :type: Optional[str] The (English) display string for the device manufacturer (see 9.2.22 of ICC.1:2010). - :type: :py:class:`unicode` or ``None`` - .. py:attribute:: model + :type: Optional[str] The (English) display string for the device model of the device for which this profile is created (see 9.2.23 of ICC.1:2010). - :type: :py:class:`unicode` or ``None`` - .. py:attribute:: profile_description + :type: Optional[str] The (English) display string for the profile description (see 9.2.41 of ICC.1:2010). - :type: :py:class:`unicode` or ``None`` - .. py:attribute:: target + :type: Optional[str] The name of the registered characterization data set, or the measurement data for a characterization target (see 9.2.14 of ICC.1:2010). - :type: :py:class:`unicode` or ``None`` - .. py:attribute:: red_colorant + :type: Optional[tuple[tuple[float]]] The first column in the matrix used in matrix/TRC transforms (see 9.2.44 of ICC.1:2010). - :type: ``((X, Y, Z), (x, y, Y))`` or ``None`` + The value is in the format ``((X, Y, Z), (x, y, Y))``, if available. .. py:attribute:: green_colorant + :type: Optional[tuple[tuple[float]]] The second column in the matrix used in matrix/TRC transforms (see 9.2.30 of ICC.1:2010). - :type: ``((X, Y, Z), (x, y, Y))`` or ``None`` + The value is in the format ``((X, Y, Z), (x, y, Y))``, if available. .. py:attribute:: blue_colorant + :type: Optional[tuple[tuple[float]]] The third column in the matrix used in matrix/TRC transforms (see 9.2.4 of ICC.1:2010). - :type: ``((X, Y, Z), (x, y, Y))`` or ``None`` + The value is in the format ``((X, Y, Z), (x, y, Y))``, if available. .. py:attribute:: luminance + :type: Optional[tuple[tuple[float]]] The absolute luminance of emissive devices in candelas per square metre as described by the Y channel (see 9.2.32 of ICC.1:2010). - :type: ``((X, Y, Z), (x, y, Y))`` or ``None`` + The value is in the format ``((X, Y, Z), (x, y, Y))``, if available. .. py:attribute:: chromaticity + :type: Optional[tuple[tuple[float]]] The data of the phosphor/colorant chromaticity set used (red, green and blue channels, see 9.2.16 of ICC.1:2010). - :type: ``((x, y, Y), (x, y, Y), (x, y, Y))`` or ``None`` + The value is in the format ``((x, y, Y), (x, y, Y), (x, y, Y))``, if available. .. py:attribute:: chromatic_adaption + :type: tuple[tuple[float]] The chromatic adaption matrix converts a color measured using the actual illumination conditions and relative to the actual adopted - white, to an color relative to the PCS adopted white, with + white, to a color relative to the PCS adopted white, with complete adaptation from the actual adopted white chromaticity to the PCS adopted white chromaticity (see 9.2.15 of ICC.1:2010). - Two matrices are returned, one in (X, Y, Z) space and one in (x, y, Y) space. - - :type: 2-tuple of 3-tuple, the first with (X, Y, Z) and the second with (x, y, Y) values + Two 3-tuples of floats are returned in a 2-tuple, + one in (X, Y, Z) space and one in (x, y, Y) space. .. py:attribute:: colorant_table + :type: list[str] This tag identifies the colorants used in the profile by a unique name and set of PCSXYZ or PCSLAB values (see 9.2.19 of ICC.1:2010). - :type: list of strings - .. py:attribute:: colorant_table_out + :type: list[str] This tag identifies the colorants used in the profile by a unique name and set of PCSLAB values (for DeviceLink profiles only, see 9.2.19 of ICC.1:2010). - :type: list of strings - .. py:attribute:: colorimetric_intent + :type: Optional[str] 4-character string (padded with whitespace) identifying the image state of PCS colorimetry produced using the colorimetric intent transforms (see 9.2.20 of ICC.1:2010 for details). - :type: :py:class:`string` or ``None`` - .. py:attribute:: perceptual_rendering_intent_gamut + :type: Optional[str] 4-character string (padded with whitespace) identifying the (one) standard reference medium gamut (see 9.2.37 of ICC.1:2010 for details). - :type: :py:class:`string` or ``None`` - .. py:attribute:: saturation_rendering_intent_gamut + :type: Optional[str] 4-character string (padded with whitespace) identifying the (one) standard reference medium gamut (see 9.2.37 of ICC.1:2010 for details). - :type: :py:class:`string` or ``None`` - .. py:attribute:: technology + :type: Optional[str] 4-character string (padded with whitespace) identifying the device technology (see 9.2.47 of ICC.1:2010 for details). - :type: :py:class:`string` or ``None`` - .. py:attribute:: media_black_point + :type: Optional[tuple[tuple[float]]] This tag specifies the media black point and is used for generating absolute colorimetry. @@ -258,57 +257,57 @@ can be easily displayed in a chromaticity diagram, for example). This tag was available in ICC 3.2, but it is removed from version 4. - :type: ``((X, Y, Z), (x, y, Y))`` or ``None`` + The value is in the format ``((X, Y, Z), (x, y, Y))``, if available. .. py:attribute:: media_white_point_temperature + :type: Optional[float] Calculates the white point temperature (see the LCMS documentation for more information). - :type: :py:class:`float` or ``None`` - .. py:attribute:: viewing_condition + :type: Optional[str] The (English) display string for the viewing conditions (see 9.2.48 of ICC.1:2010). - :type: :py:class:`unicode` or ``None`` - .. py:attribute:: screening_description + :type: Optional[str] The (English) display string for the screening conditions. This tag was available in ICC 3.2, but it is removed from version 4. - :type: :py:class:`unicode` or ``None`` - .. py:attribute:: red_primary + :type: Optional[tuple[tuple[float]]] The XYZ-transformed of the RGB primary color red (1, 0, 0). - :type: ``((X, Y, Z), (x, y, Y))`` or ``None`` + The value is in the format ``((X, Y, Z), (x, y, Y))``, if available. .. py:attribute:: green_primary + :type: Optional[tuple[tuple[float]]] The XYZ-transformed of the RGB primary color green (0, 1, 0). - :type: ``((X, Y, Z), (x, y, Y))`` or ``None`` + The value is in the format ``((X, Y, Z), (x, y, Y))``, if available. .. py:attribute:: blue_primary + :type: Optional[tuple[tuple[float]]] The XYZ-transformed of the RGB primary color blue (0, 0, 1). - :type: ``((X, Y, Z), (x, y, Y))`` or ``None`` + The value is in the format ``((X, Y, Z), (x, y, Y))``, if available. .. py:attribute:: is_matrix_shaper + :type: bool True if this profile is implemented as a matrix shaper (see documentation on LCMS). - :type: :py:class:`bool` - .. py:attribute:: clut + :type: dict[tuple[bool]] Returns a dictionary of all supported intents and directions for the CLUT model. @@ -326,9 +325,8 @@ can be easily displayed in a chromaticity diagram, for example). The elements of the tuple are booleans. If the value is ``True``, that intent is supported for that direction. - :type: :py:class:`dict` of boolean 3-tuples - .. py:attribute:: intent_supported + :type: dict[tuple[bool]] Returns a dictionary of all supported intents and directions. @@ -345,64 +343,6 @@ can be easily displayed in a chromaticity diagram, for example). The elements of the tuple are booleans. If the value is ``True``, that intent is supported for that direction. - :type: :py:class:`dict` of boolean 3-tuples - - .. py:attribute:: color_space - - Deprecated but retained for backwards compatibility. - Interpreted value of :py:attr:`.xcolor_space`. May be the - empty string if value could not be decoded. - - :type: :py:class:`string` - - .. py:attribute:: pcs - - Deprecated but retained for backwards compatibility. - Interpreted value of :py:attr:`.connection_space`. May be - the empty string if value could not be decoded. - - :type: :py:class:`string` - - .. py:attribute:: product_model - - Deprecated but retained for backwards compatibility. - ASCII-encoded value of :py:attr:`.model`. - - :type: :py:class:`string` - - .. py:attribute:: product_manufacturer - - Deprecated but retained for backwards compatibility. - ASCII-encoded value of :py:attr:`.manufacturer`. - - :type: :py:class:`string` - - .. py:attribute:: product_copyright - - Deprecated but retained for backwards compatibility. - ASCII-encoded value of :py:attr:`.copyright`. - - :type: :py:class:`string` - - .. py:attribute:: product_description - - Deprecated but retained for backwards compatibility. - ASCII-encoded value of :py:attr:`.profile_description`. - - :type: :py:class:`string` - - .. py:attribute:: product_desc - - Deprecated but retained for backwards compatibility. - ASCII-encoded value of :py:attr:`.profile_description`. - - This alias of :py:attr:`.product_description` used to - contain a derived informative string about the profile, - depending on the value of the description, copyright, - manufacturer and model fields). - - :type: :py:class:`string` - There is one function defined on the class: .. py:method:: is_intent_supported(intent, direction) @@ -413,10 +353,10 @@ can be easily displayed in a chromaticity diagram, for example). with :py:attr:`.intent_supported`. :param intent: One of ``ImageCms.INTENT_ABSOLUTE_COLORIMETRIC``, - ``ImageCms.INTENT_PERCEPTUAL``, - ``ImageCms.INTENT_RELATIVE_COLORIMETRIC`` - and ``ImageCms.INTENT_SATURATION``. + ``ImageCms.INTENT_PERCEPTUAL``, + ``ImageCms.INTENT_RELATIVE_COLORIMETRIC`` + and ``ImageCms.INTENT_SATURATION``. :param direction: One of ``ImageCms.DIRECTION_INPUT``, - ``ImageCms.DIRECTION_OUTPUT`` - and ``ImageCms.DIRECTION_PROOF`` + ``ImageCms.DIRECTION_OUTPUT`` + and ``ImageCms.DIRECTION_PROOF`` :return: Boolean if the intent and direction is supported. diff --git a/docs/reference/ImageColor.rst b/docs/reference/ImageColor.rst index 187306f1b..e32a77b54 100644 --- a/docs/reference/ImageColor.rst +++ b/docs/reference/ImageColor.rst @@ -1,10 +1,10 @@ .. py:module:: PIL.ImageColor .. py:currentmodule:: PIL.ImageColor -:py:mod:`ImageColor` Module -=========================== +:py:mod:`~PIL.ImageColor` Module +================================ -The :py:mod:`ImageColor` module contains color tables and converters from +The :py:mod:`~PIL.ImageColor` module contains color tables and converters from CSS3-style color specifiers to RGB tuples. This module is used by :py:meth:`PIL.Image.new` and the :py:mod:`~PIL.ImageDraw` module, among others. diff --git a/docs/reference/ImageDraw.rst b/docs/reference/ImageDraw.rst index ad0fe4abe..57d1c2dda 100644 --- a/docs/reference/ImageDraw.rst +++ b/docs/reference/ImageDraw.rst @@ -1,10 +1,10 @@ .. py:module:: PIL.ImageDraw .. py:currentmodule:: PIL.ImageDraw -:py:mod:`ImageDraw` Module -========================== +:py:mod:`~PIL.ImageDraw` Module +=============================== -The :py:mod:`ImageDraw` module provides simple 2D graphics for +The :py:mod:`~PIL.ImageDraw` module provides simple 2D graphics for :py:class:`~PIL.Image.Image` objects. You can use this module to create new images, annotate or retouch existing images, and to generate graphics on the fly for web use. @@ -20,14 +20,14 @@ Example: Draw a gray cross over an image from PIL import Image, ImageDraw - im = Image.open("hopper.jpg") + with Image.open("hopper.jpg") as im: - draw = ImageDraw.Draw(im) - draw.line((0, 0) + im.size, fill=128) - draw.line((0, im.size[1], im.size[0], 0), fill=128) + draw = ImageDraw.Draw(im) + draw.line((0, 0) + im.size, fill=128) + draw.line((0, im.size[1], im.size[0], 0), fill=128) - # write to stdout - im.save(sys.stdout, "PNG") + # write to stdout + im.save(sys.stdout, "PNG") Concepts @@ -81,13 +81,13 @@ Example: Draw Partial Opacity Text from PIL import Image, ImageDraw, ImageFont # get an image - base = Image.open('Pillow/Tests/images/hopper.png').convert('RGBA') + base = Image.open("Pillow/Tests/images/hopper.png").convert("RGBA") # make a blank image for the text, initialized to transparent text color - txt = Image.new('RGBA', base.size, (255,255,255,0)) + txt = Image.new("RGBA", base.size, (255,255,255,0)) # get a font - fnt = ImageFont.truetype('Pillow/Tests/fonts/FreeMono.ttf', 40) + fnt = ImageFont.truetype("Pillow/Tests/fonts/FreeMono.ttf", 40) # get a drawing context d = ImageDraw.Draw(txt) @@ -100,12 +100,31 @@ Example: Draw Partial Opacity Text out.show() +Example: Draw Multiline Text +---------------------------- + +.. code-block:: python + + from PIL import Image, ImageDraw, ImageFont + + # create an image + out = Image.new("RGB", (150, 100), (255, 255, 255)) + + # get a font + fnt = ImageFont.truetype("Pillow/Tests/fonts/FreeMono.ttf", 40) + # get a drawing context + d = ImageDraw.Draw(out) + + # draw multiline text + d.multiline_text((10,10), "Hello\nWorld", font=fnt, fill=(0, 0, 0)) + + out.show() Functions --------- -.. py:class:: PIL.ImageDraw.Draw(im, mode=None) +.. py:method:: Draw(im, mode=None) Creates an object that can be used to draw in the given image. @@ -121,13 +140,13 @@ Functions Methods ------- -.. py:method:: PIL.ImageDraw.ImageDraw.getfont() +.. py:method:: ImageDraw.getfont() Get the current default font. :returns: An image font. -.. py:method:: PIL.ImageDraw.ImageDraw.arc(xy, start, end, fill=None, width=0) +.. py:method:: ImageDraw.arc(xy, start, end, fill=None, width=0) Draws an arc (a portion of a circle outline) between the start and end angles, inside the given bounding box. @@ -143,7 +162,7 @@ Methods .. versionadded:: 5.3.0 -.. py:method:: PIL.ImageDraw.ImageDraw.bitmap(xy, bitmap, fill=None) +.. py:method:: ImageDraw.bitmap(xy, bitmap, fill=None) Draws a bitmap (mask) at the given position, using the current fill color for the non-zero portions. The bitmap should be a valid transparency mask @@ -154,7 +173,7 @@ Methods To paste pixel data into an image, use the :py:meth:`~PIL.Image.Image.paste` method on the image itself. -.. py:method:: PIL.ImageDraw.ImageDraw.chord(xy, start, end, fill=None, outline=None, width=0) +.. py:method:: ImageDraw.chord(xy, start, end, fill=None, outline=None, width=1) Same as :py:meth:`~PIL.ImageDraw.ImageDraw.arc`, but connects the end points with a straight line. @@ -168,7 +187,7 @@ Methods .. versionadded:: 5.3.0 -.. py:method:: PIL.ImageDraw.ImageDraw.ellipse(xy, fill=None, outline=None, width=0) +.. py:method:: ImageDraw.ellipse(xy, fill=None, outline=None, width=1) Draws an ellipse inside the given bounding box. @@ -181,9 +200,9 @@ Methods .. versionadded:: 5.3.0 -.. py:method:: PIL.ImageDraw.ImageDraw.line(xy, fill=None, width=0, joint=None) +.. py:method:: ImageDraw.line(xy, fill=None, width=0, joint=None) - Draws a line between the coordinates in the **xy** list. + Draws a line between the coordinates in the ``xy`` list. :param xy: Sequence of either 2-tuples like ``[(x, y), (x, y), ...]`` or numeric values like ``[x, y, x, y, ...]``. @@ -193,12 +212,11 @@ Methods .. versionadded:: 1.1.5 .. note:: This option was broken until version 1.1.6. - :param joint: Joint type between a sequence of lines. It can be "curve", - for rounded edges, or None. + :param joint: Joint type between a sequence of lines. It can be ``"curve"``, for rounded edges, or :data:`None`. .. versionadded:: 5.3.0 -.. py:method:: PIL.ImageDraw.ImageDraw.pieslice(xy, start, end, fill=None, outline=None, width=0) +.. py:method:: ImageDraw.pieslice(xy, start, end, fill=None, outline=None, width=1) Same as arc, but also draws straight lines between the end points and the center of the bounding box. @@ -215,7 +233,7 @@ Methods .. versionadded:: 5.3.0 -.. py:method:: PIL.ImageDraw.ImageDraw.point(xy, fill=None) +.. py:method:: ImageDraw.point(xy, fill=None) Draws points (individual pixels) at the given coordinates. @@ -223,7 +241,7 @@ Methods numeric values like ``[x, y, x, y, ...]``. :param fill: Color to use for the point. -.. py:method:: PIL.ImageDraw.ImageDraw.polygon(xy, fill=None, outline=None) +.. py:method:: ImageDraw.polygon(xy, fill=None, outline=None) Draws a polygon. @@ -236,7 +254,25 @@ Methods :param outline: Color to use for the outline. :param fill: Color to use for the fill. -.. py:method:: PIL.ImageDraw.ImageDraw.rectangle(xy, fill=None, outline=None, width=0) + +.. py:method:: ImageDraw.regular_polygon(bounding_circle, n_sides, rotation=0, fill=None, outline=None) + + Draws a regular polygon inscribed in ``bounding_circle``, + with ``n_sides``, and rotation of ``rotation`` degrees. + + :param bounding_circle: The bounding circle is a tuple defined + by a point and radius. + (e.g. ``bounding_circle=(x, y, r)`` or ``((x, y), r)``). + The polygon is inscribed in this circle. + :param n_sides: Number of sides + (e.g. ``n_sides=3`` for a triangle, ``6`` for a hexagon). + :param rotation: Apply an arbitrary rotation to the polygon + (e.g. ``rotation=90``, applies a 90 degree rotation). + :param fill: Color to use for the fill. + :param outline: Color to use for the outline. + + +.. py:method:: ImageDraw.rectangle(xy, fill=None, outline=None, width=1) Draws a rectangle. @@ -249,27 +285,39 @@ Methods .. versionadded:: 5.3.0 -.. py:method:: PIL.ImageDraw.ImageDraw.shape(shape, fill=None, outline=None) +.. py:method:: ImageDraw.shape(shape, fill=None, outline=None) .. warning:: This method is experimental. Draw a shape. -.. py:method:: PIL.ImageDraw.ImageDraw.text(xy, text, fill=None, font=None, anchor=None, spacing=4, align="left", direction=None, features=None, language=None, stroke_width=0, stroke_fill=None) +.. py:method:: ImageDraw.text(xy, text, fill=None, font=None, anchor=None, spacing=4, align="left", direction=None, features=None, language=None, stroke_width=0, stroke_fill=None, embedded_color=False) Draws the string at the given position. - :param xy: Top left corner of the text. - :param text: Text to be drawn. If it contains any newline characters, - the text is passed on to multiline_text() + :param xy: The anchor coordinates of the text. + :param text: String to be drawn. If it contains any newline characters, + the text is passed on to + :py:meth:`~PIL.ImageDraw.ImageDraw.multiline_text`. :param fill: Color to use for the text. :param font: An :py:class:`~PIL.ImageFont.ImageFont` instance. - :param spacing: If the text is passed on to multiline_text(), + :param anchor: The text anchor alignment. Determines the relative location of + the anchor to the text. The default alignment is top left. + See :ref:`text-anchors` for valid values. This parameter is + ignored for non-TrueType fonts. + + .. note:: This parameter was present in earlier versions + of Pillow, but implemented only in version 8.0.0. + + :param spacing: If the text is passed on to + :py:meth:`~PIL.ImageDraw.ImageDraw.multiline_text`, the number of pixels between lines. - :param align: If the text is passed on to multiline_text(), - "left", "center" or "right". - :param direction: Direction of the text. It can be 'rtl' (right to - left), 'ltr' (left to right) or 'ttb' (top to bottom). + :param align: If the text is passed on to + :py:meth:`~PIL.ImageDraw.ImageDraw.multiline_text`, + ``"left"``, ``"center"`` or ``"right"``. Determines the relative alignment of lines. + Use the ``anchor`` parameter to specify the alignment to ``xy``. + :param direction: Direction of the text. It can be ``"rtl"`` (right to + left), ``"ltr"`` (left to right) or ``"ttb"`` (top to bottom). Requires libraqm. .. versionadded:: 4.2.0 @@ -277,12 +325,11 @@ Methods :param features: A list of OpenType font features to be used during text layout. This is usually used to turn on optional font features that are not enabled by default, - for example 'dlig' or 'ss01', but can be also - used to turn off default font features for - example '-liga' to disable ligatures or '-kern' + for example ``"dlig"`` or ``"ss01"``, but can be also + used to turn off default font features, for + example ``"-liga"`` to disable ligatures or ``"-kern"`` to disable kerning. To get all supported - features, see - https://docs.microsoft.com/en-us/typography/opentype/spec/featurelist + features, see `OpenType docs`_. Requires libraqm. .. versionadded:: 4.2.0 @@ -291,8 +338,68 @@ Methods different glyph shapes or ligatures. This parameter tells the font which language the text is in, and to apply the correct substitutions as appropriate, if available. - It should be a `BCP 47 language code - ` + It should be a `BCP 47 language code`_. + Requires libraqm. + + .. versionadded:: 6.0.0 + + :param stroke_width: The width of the text stroke. + + .. versionadded:: 6.2.0 + + :param stroke_fill: Color to use for the text stroke. If not given, will default to + the ``fill`` parameter. + + .. versionadded:: 6.2.0 + + :param embedded_color: Whether to use font embedded color glyphs (COLR or CBDT). + + .. versionadded:: 8.0.0 + + +.. py:method:: ImageDraw.multiline_text(xy, text, fill=None, font=None, anchor=None, spacing=4, align="left", direction=None, features=None, language=None, stroke_width=0, stroke_fill=None, embedded_color=False) + + Draws the string at the given position. + + :param xy: The anchor coordinates of the text. + :param text: String to be drawn. + :param fill: Color to use for the text. + :param font: An :py:class:`~PIL.ImageFont.ImageFont` instance. + + :param anchor: The text anchor alignment. Determines the relative location of + the anchor to the text. The default alignment is top left. + See :ref:`text-anchors` for valid values. This parameter is + ignored for non-TrueType fonts. + + .. note:: This parameter was present in earlier versions + of Pillow, but implemented only in version 8.0.0. + + :param spacing: The number of pixels between lines. + :param align: ``"left"``, ``"center"`` or ``"right"``. Determines the relative alignment of lines. + Use the ``anchor`` parameter to specify the alignment to ``xy``. + :param direction: Direction of the text. It can be ``"rtl"`` (right to + left), ``"ltr"`` (left to right) or ``"ttb"`` (top to bottom). + Requires libraqm. + + .. versionadded:: 4.2.0 + + :param features: A list of OpenType font features to be used during text + layout. This is usually used to turn on optional + font features that are not enabled by default, + for example ``"dlig"`` or ``"ss01"``, but can be also + used to turn off default font features, for + example ``"-liga"`` to disable ligatures or ``"-kern"`` + to disable kerning. To get all supported + features, see `OpenType docs`_. + Requires libraqm. + + .. versionadded:: 4.2.0 + + :param language: Language of the text. Different languages may use + different glyph shapes or ligatures. This parameter tells + the font which language the text is in, and to apply the + correct substitutions as appropriate, if available. + It should be a `BCP 47 language code`_. Requires libraqm. .. versionadded:: 6.0.0 @@ -306,68 +413,42 @@ Methods .. versionadded:: 6.2.0 -.. py:method:: PIL.ImageDraw.ImageDraw.multiline_text(xy, text, fill=None, font=None, anchor=None, spacing=4, align="left", direction=None, features=None, language=None) + :param embedded_color: Whether to use font embedded color glyphs (COLR or CBDT). - Draws the string at the given position. + .. versionadded:: 8.0.0 - :param xy: Top left corner of the text. - :param text: Text to be drawn. - :param fill: Color to use for the text. - :param font: An :py:class:`~PIL.ImageFont.ImageFont` instance. - :param spacing: The number of pixels between lines. - :param align: "left", "center" or "right". - :param direction: Direction of the text. It can be 'rtl' (right to - left), 'ltr' (left to right) or 'ttb' (top to bottom). - Requires libraqm. - - .. versionadded:: 4.2.0 - - :param features: A list of OpenType font features to be used during text - layout. This is usually used to turn on optional - font features that are not enabled by default, - for example 'dlig' or 'ss01', but can be also - used to turn off default font features for - example '-liga' to disable ligatures or '-kern' - to disable kerning. To get all supported - features, see - https://docs.microsoft.com/en-us/typography/opentype/spec/featurelist - Requires libraqm. - - .. versionadded:: 4.2.0 - - :param language: Language of the text. Different languages may use - different glyph shapes or ligatures. This parameter tells - the font which language the text is in, and to apply the - correct substitutions as appropriate, if available. - It should be a `BCP 47 language code - ` - Requires libraqm. - - .. versionadded:: 6.0.0 - -.. py:method:: PIL.ImageDraw.ImageDraw.textsize(text, font=None, spacing=4, direction=None, features=None, language=None, stroke_width=0) +.. py:method:: ImageDraw.textsize(text, font=None, spacing=4, direction=None, features=None, language=None, stroke_width=0) Return the size of the given string, in pixels. + Use :py:meth:`textlength()` to measure the offset of following text with + 1/64 pixel precision. + Use :py:meth:`textbbox()` to get the exact bounding box based on an anchor. + + .. note:: For historical reasons this function measures text height from + the ascender line instead of the top, see :ref:`text-anchors`. + If you wish to measure text height from the top, it is recommended + to use :meth:`textbbox` with ``anchor='lt'`` instead. + :param text: Text to be measured. If it contains any newline characters, - the text is passed on to multiline_textsize() + the text is passed on to :py:meth:`~PIL.ImageDraw.ImageDraw.multiline_textsize`. :param font: An :py:class:`~PIL.ImageFont.ImageFont` instance. - :param spacing: If the text is passed on to multiline_textsize(), + :param spacing: If the text is passed on to + :py:meth:`~PIL.ImageDraw.ImageDraw.multiline_textsize`, the number of pixels between lines. - :param direction: Direction of the text. It can be 'rtl' (right to - left), 'ltr' (left to right) or 'ttb' (top to bottom). + :param direction: Direction of the text. It can be ``"rtl"`` (right to + left), ``"ltr"`` (left to right) or ``"ttb"`` (top to bottom). Requires libraqm. .. versionadded:: 4.2.0 :param features: A list of OpenType font features to be used during text layout. This is usually used to turn on optional font features that are not enabled by default, - for example 'dlig' or 'ss01', but can be also - used to turn off default font features for - example '-liga' to disable ligatures or '-kern' + for example ``"dlig"`` or ``"ss01"``, but can be also + used to turn off default font features, for + example ``"-liga"`` to disable ligatures or ``"-kern"`` to disable kerning. To get all supported - features, see - https://docs.microsoft.com/en-us/typography/opentype/spec/featurelist + features, see `OpenType docs`_. Requires libraqm. .. versionadded:: 4.2.0 @@ -375,8 +456,7 @@ Methods different glyph shapes or ligatures. This parameter tells the font which language the text is in, and to apply the correct substitutions as appropriate, if available. - It should be a `BCP 47 language code - ` + It should be a `BCP 47 language code`_. Requires libraqm. .. versionadded:: 6.0.0 @@ -385,15 +465,25 @@ Methods .. versionadded:: 6.2.0 -.. py:method:: PIL.ImageDraw.ImageDraw.multiline_textsize(text, font=None, spacing=4, direction=None, features=None, language=None, stroke_width=0) +.. py:method:: ImageDraw.multiline_textsize(text, font=None, spacing=4, direction=None, features=None, language=None, stroke_width=0) Return the size of the given string, in pixels. + Use :py:meth:`textlength()` to measure the offset of following text with + 1/64 pixel precision. + Use :py:meth:`textbbox()` to get the exact bounding box based on an anchor. + + .. note:: For historical reasons this function measures text height as the + distance between the top ascender line and bottom descender line, + not the top and bottom of the text, see :ref:`text-anchors`. + If you wish to measure text height from the top to the bottom of text, + it is recommended to use :meth:`multiline_textbbox` instead. + :param text: Text to be measured. :param font: An :py:class:`~PIL.ImageFont.ImageFont` instance. :param spacing: The number of pixels between lines. - :param direction: Direction of the text. It can be 'rtl' (right to - left), 'ltr' (left to right) or 'ttb' (top to bottom). + :param direction: Direction of the text. It can be ``"rtl"`` (right to + left), ``"ltr"`` (left to right) or ``"ttb"`` (top to bottom). Requires libraqm. .. versionadded:: 4.2.0 @@ -401,12 +491,11 @@ Methods :param features: A list of OpenType font features to be used during text layout. This is usually used to turn on optional font features that are not enabled by default, - for example 'dlig' or 'ss01', but can be also - used to turn off default font features for - example '-liga' to disable ligatures or '-kern' + for example ``"dlig"`` or ``"ss01"``, but can be also + used to turn off default font features, for + example ``"-liga"`` to disable ligatures or ``"-kern"`` to disable kerning. To get all supported - features, see - https://docs.microsoft.com/en-us/typography/opentype/spec/featurelist + features, see `OpenType docs`_. Requires libraqm. .. versionadded:: 4.2.0 @@ -415,8 +504,7 @@ Methods different glyph shapes or ligatures. This parameter tells the font which language the text is in, and to apply the correct substitutions as appropriate, if available. - It should be a `BCP 47 language code - ` + It should be a `BCP 47 language code`_. Requires libraqm. .. versionadded:: 6.0.0 @@ -425,7 +513,165 @@ Methods .. versionadded:: 6.2.0 -.. py:method:: PIL.ImageDraw.getdraw(im=None, hints=None) +.. py:method:: ImageDraw.textlength(text, font=None, direction=None, features=None, language=None, embedded_color=False) + + Returns length (in pixels with 1/64 precision) of given text when rendered + in font with provided direction, features, and language. + + This is the amount by which following text should be offset. + Text bounding box may extend past the length in some fonts, + e.g. when using italics or accents. + + The result is returned as a float; it is a whole number if using basic layout. + + Note that the sum of two lengths may not equal the length of a concatenated + string due to kerning. If you need to adjust for kerning, include the following + character and subtract its length. + + For example, instead of + + .. code-block:: python + + hello = draw.textlength("Hello", font) + world = draw.textlength("World", font) + hello_world = hello + world # not adjusted for kerning + assert hello_world == draw.textlength("HelloWorld", font) # may fail + + use + + .. code-block:: python + + hello = draw.textlength("HelloW", font) - draw.textlength("W", font) # adjusted for kerning + world = draw.textlength("World", font) + hello_world = hello + world # adjusted for kerning + assert hello_world == draw.textlength("HelloWorld", font) # True + + or disable kerning with (requires libraqm) + + .. code-block:: python + + hello = draw.textlength("Hello", font, features=["-kern"]) + world = draw.textlength("World", font, features=["-kern"]) + hello_world = hello + world # kerning is disabled, no need to adjust + assert hello_world == draw.textlength("HelloWorld", font, features=["-kern"]) # True + + .. versionadded:: 8.0.0 + + :param text: Text to be measured. May not contain any newline characters. + :param font: An :py:class:`~PIL.ImageFont.ImageFont` instance. + :param direction: Direction of the text. It can be ``"rtl"`` (right to + left), ``"ltr"`` (left to right) or ``"ttb"`` (top to bottom). + Requires libraqm. + :param features: A list of OpenType font features to be used during text + layout. This is usually used to turn on optional + font features that are not enabled by default, + for example ``"dlig"`` or ``"ss01"``, but can be also + used to turn off default font features, for + example ``"-liga"`` to disable ligatures or ``"-kern"`` + to disable kerning. To get all supported + features, see `OpenType docs`_. + Requires libraqm. + :param language: Language of the text. Different languages may use + different glyph shapes or ligatures. This parameter tells + the font which language the text is in, and to apply the + correct substitutions as appropriate, if available. + It should be a `BCP 47 language code`_. + Requires libraqm. + :param embedded_color: Whether to use font embedded color glyphs (COLR or CBDT). + +.. py:method:: ImageDraw.textbbox(xy, text, font=None, anchor=None, spacing=4, align="left", direction=None, features=None, language=None, stroke_width=0, embedded_color=False) + + Returns bounding box (in pixels) of given text relative to given anchor + when rendered in font with provided direction, features, and language. + Only supported for TrueType fonts. + + Use :py:meth:`textlength` to get the offset of following text with + 1/64 pixel precision. The bounding box includes extra margins for + some fonts, e.g. italics or accents. + + .. versionadded:: 8.0.0 + + :param xy: The anchor coordinates of the text. + :param text: Text to be measured. If it contains any newline characters, + the text is passed on to + :py:meth:`~PIL.ImageDraw.ImageDraw.multiline_textbbox`. + :param font: A :py:class:`~PIL.ImageFont.FreeTypeFont` instance. + :param anchor: The text anchor alignment. Determines the relative location of + the anchor to the text. The default alignment is top left. + See :ref:`text-anchors` for valid values. This parameter is + ignored for non-TrueType fonts. + :param spacing: If the text is passed on to + :py:meth:`~PIL.ImageDraw.ImageDraw.multiline_textbbox`, + the number of pixels between lines. + :param align: If the text is passed on to + :py:meth:`~PIL.ImageDraw.ImageDraw.multiline_textbbox`, + ``"left"``, ``"center"`` or ``"right"``. Determines the relative alignment of lines. + Use the ``anchor`` parameter to specify the alignment to ``xy``. + :param direction: Direction of the text. It can be ``"rtl"`` (right to + left), ``"ltr"`` (left to right) or ``"ttb"`` (top to bottom). + Requires libraqm. + :param features: A list of OpenType font features to be used during text + layout. This is usually used to turn on optional + font features that are not enabled by default, + for example ``"dlig"`` or ``"ss01"``, but can be also + used to turn off default font features, for + example ``"-liga"`` to disable ligatures or ``"-kern"`` + to disable kerning. To get all supported + features, see `OpenType docs`_. + Requires libraqm. + :param language: Language of the text. Different languages may use + different glyph shapes or ligatures. This parameter tells + the font which language the text is in, and to apply the + correct substitutions as appropriate, if available. + It should be a `BCP 47 language code`_. + Requires libraqm. + :param stroke_width: The width of the text stroke. + :param embedded_color: Whether to use font embedded color glyphs (COLR or CBDT). + +.. py:method:: ImageDraw.multiline_textbbox(xy, text, font=None, anchor=None, spacing=4, align="left", direction=None, features=None, language=None, stroke_width=0, embedded_color=False) + + Returns bounding box (in pixels) of given text relative to given anchor + when rendered in font with provided direction, features, and language. + Only supported for TrueType fonts. + + Use :py:meth:`textlength` to get the offset of following text with + 1/64 pixel precision. The bounding box includes extra margins for + some fonts, e.g. italics or accents. + + .. versionadded:: 8.0.0 + + :param xy: The anchor coordinates of the text. + :param text: Text to be measured. + :param font: A :py:class:`~PIL.ImageFont.FreeTypeFont` instance. + :param anchor: The text anchor alignment. Determines the relative location of + the anchor to the text. The default alignment is top left. + See :ref:`text-anchors` for valid values. This parameter is + ignored for non-TrueType fonts. + :param spacing: The number of pixels between lines. + :param align: ``"left"``, ``"center"`` or ``"right"``. Determines the relative alignment of lines. + Use the ``anchor`` parameter to specify the alignment to ``xy``. + :param direction: Direction of the text. It can be ``"rtl"`` (right to + left), ``"ltr"`` (left to right) or ``"ttb"`` (top to bottom). + Requires libraqm. + :param features: A list of OpenType font features to be used during text + layout. This is usually used to turn on optional + font features that are not enabled by default, + for example ``"dlig"`` or ``"ss01"``, but can be also + used to turn off default font features, for + example ``"-liga"`` to disable ligatures or ``"-kern"`` + to disable kerning. To get all supported + features, see `OpenType docs`_. + Requires libraqm. + :param language: Language of the text. Different languages may use + different glyph shapes or ligatures. This parameter tells + the font which language the text is in, and to apply the + correct substitutions as appropriate, if available. + It should be a `BCP 47 language code`_. + Requires libraqm. + :param stroke_width: The width of the text stroke. + :param embedded_color: Whether to use font embedded color glyphs (COLR or CBDT). + +.. py:method:: getdraw(im=None, hints=None) .. warning:: This method is experimental. @@ -436,7 +682,7 @@ Methods :param hints: An optional list of hints. :returns: A (drawing context, drawing resource factory) tuple. -.. py:method:: PIL.ImageDraw.floodfill(image, xy, value, border=None, thresh=0) +.. py:method:: floodfill(image, xy, value, border=None, thresh=0) .. warning:: This method is experimental. @@ -453,3 +699,6 @@ Methods tolerable difference of a pixel value from the 'background' in order for it to be replaced. Useful for filling regions of non- homogeneous, but similar, colors. + +.. _BCP 47 language code: https://www.w3.org/International/articles/language-tags/ +.. _OpenType docs: https://docs.microsoft.com/en-us/typography/opentype/spec/featurelist diff --git a/docs/reference/ImageEnhance.rst b/docs/reference/ImageEnhance.rst index b172054b2..29ceee314 100644 --- a/docs/reference/ImageEnhance.rst +++ b/docs/reference/ImageEnhance.rst @@ -1,10 +1,10 @@ .. py:module:: PIL.ImageEnhance .. py:currentmodule:: PIL.ImageEnhance -:py:mod:`ImageEnhance` Module -============================= +:py:mod:`~PIL.ImageEnhance` Module +================================== -The :py:mod:`ImageEnhance` module contains a number of classes that can be used +The :py:mod:`~PIL.ImageEnhance` module contains a number of classes that can be used for image enhancement. Example: Vary the sharpness of an image @@ -18,7 +18,7 @@ Example: Vary the sharpness of an image for i in range(8): factor = i / 4.0 - enhancer.enhance(factor).show("Sharpness %f" % factor) + enhancer.enhance(factor).show(f"Sharpness {factor:f}") Also see the :file:`enhancer.py` demo program in the :file:`Scripts/` directory. @@ -29,7 +29,8 @@ Classes All enhancement classes implement a common interface, containing a single method: -.. py:class:: PIL.ImageEnhance._Enhance +.. py:class:: _Enhance + .. py:method:: enhance(factor) Returns an enhanced image. @@ -40,7 +41,7 @@ method: etc), and higher values more. There are no restrictions on this value. -.. py:class:: PIL.ImageEnhance.Color(image) +.. py:class:: Color(image) Adjust image color balance. @@ -49,7 +50,7 @@ method: factor of 0.0 gives a black and white image. A factor of 1.0 gives the original image. -.. py:class:: PIL.ImageEnhance.Contrast(image) +.. py:class:: Contrast(image) Adjust image contrast. @@ -57,7 +58,7 @@ method: to the contrast control on a TV set. An enhancement factor of 0.0 gives a solid grey image. A factor of 1.0 gives the original image. -.. py:class:: PIL.ImageEnhance.Brightness(image) +.. py:class:: Brightness(image) Adjust image brightness. @@ -65,7 +66,7 @@ method: enhancement factor of 0.0 gives a black image. A factor of 1.0 gives the original image. -.. py:class:: PIL.ImageEnhance.Sharpness(image) +.. py:class:: Sharpness(image) Adjust image sharpness. diff --git a/docs/reference/ImageFile.rst b/docs/reference/ImageFile.rst index d93dfb3a3..e0ce389e8 100644 --- a/docs/reference/ImageFile.rst +++ b/docs/reference/ImageFile.rst @@ -1,10 +1,10 @@ .. py:module:: PIL.ImageFile .. py:currentmodule:: PIL.ImageFile -:py:mod:`ImageFile` Module -========================== +:py:mod:`~PIL.ImageFile` Module +=============================== -The :py:mod:`ImageFile` module provides support functions for the image open +The :py:mod:`~PIL.ImageFile` module provides support functions for the image open and save functions. In addition, it provides a :py:class:`Parser` class which can be used to decode @@ -34,14 +34,28 @@ Example: Parse an image im.save("copy.jpg") -:py:class:`~PIL.ImageFile.Parser` ---------------------------------- +Classes +------- .. autoclass:: PIL.ImageFile.Parser() :members: -:py:class:`~PIL.ImageFile.PyDecoder` ------------------------------------- - .. autoclass:: PIL.ImageFile.PyDecoder() :members: + +.. autoclass:: PIL.ImageFile.ImageFile() + :member-order: bysource + :members: + :undoc-members: + :show-inheritance: + +.. autoclass:: PIL.ImageFile.StubImageFile() + :members: + :show-inheritance: + +Constants +--------- + +.. autodata:: PIL.ImageFile.LOAD_TRUNCATED_IMAGES +.. autodata:: PIL.ImageFile.ERRORS + :annotation: diff --git a/docs/reference/ImageFilter.rst b/docs/reference/ImageFilter.rst index 52a7d7500..c85da4fb5 100644 --- a/docs/reference/ImageFilter.rst +++ b/docs/reference/ImageFilter.rst @@ -1,10 +1,10 @@ .. py:module:: PIL.ImageFilter .. py:currentmodule:: PIL.ImageFilter -:py:mod:`ImageFilter` Module -============================ +:py:mod:`~PIL.ImageFilter` Module +================================= -The :py:mod:`ImageFilter` module contains definitions for a pre-defined set of +The :py:mod:`~PIL.ImageFilter` module contains definitions for a pre-defined set of filters, which can be be used with the :py:meth:`Image.filter() ` method. @@ -66,3 +66,29 @@ image enhancement filters: .. autoclass:: PIL.ImageFilter.ModeFilter :members: + +.. class:: Filter + + An abstract mixin used for filtering images + (for use with :py:meth:`~PIL.Image.Image.filter`). + + Implementors must provide the following method: + + .. method:: filter(self, image) + + Applies a filter to a single-band image, or a single band of an image. + + :returns: A filtered copy of the image. + +.. class:: MultibandFilter + + An abstract mixin used for filtering multi-band images + (for use with :py:meth:`~PIL.Image.Image.filter`). + + Implementors must provide the following method: + + .. method:: filter(self, image) + + Applies a filter to a multi-band image. + + :returns: A filtered copy of the image. diff --git a/docs/reference/ImageFont.rst b/docs/reference/ImageFont.rst index bb7538096..813d325e0 100644 --- a/docs/reference/ImageFont.rst +++ b/docs/reference/ImageFont.rst @@ -1,21 +1,22 @@ .. py:module:: PIL.ImageFont .. py:currentmodule:: PIL.ImageFont -:py:mod:`ImageFont` Module -========================== +:py:mod:`~PIL.ImageFont` Module +=============================== -The :py:mod:`ImageFont` module defines a class with the same name. Instances of +The :py:mod:`~PIL.ImageFont` module defines a class with the same name. Instances of this class store bitmap fonts, and are used with the -:py:meth:`PIL.ImageDraw.Draw.text` method. +:py:meth:`PIL.ImageDraw.ImageDraw.text` method. -PIL uses its own font file format to store bitmap fonts. You can use the -:command:`pilfont` utility to convert BDF and PCF font descriptors (X window -font formats) to this format. +PIL uses its own font file format to store bitmap fonts, limited to 256 characters. You can use +`pilfont.py `_ +from `pillow-scripts `_ to convert BDF and +PCF font descriptors (X window font formats) to this format. Starting with version 1.1.4, PIL can be configured to support TrueType and OpenType fonts (as well as other font formats supported by the FreeType library). For earlier versions, TrueType support is only available as part of -the imToolkit package +the imToolkit package. Example ------- @@ -55,3 +56,19 @@ Methods .. autoclass:: PIL.ImageFont.TransposedFont :members: + +Constants +--------- + +.. data:: PIL.ImageFont.LAYOUT_BASIC + + Use basic text layout for TrueType font. + Advanced features such as text direction are not supported. + +.. data:: PIL.ImageFont.LAYOUT_RAQM + + Use Raqm text layout for TrueType font. + Advanced features are supported. + + Requires Raqm, you can check support using + :py:func:`PIL.features.check_feature` with ``feature="raqm"``. diff --git a/docs/reference/ImageGrab.rst b/docs/reference/ImageGrab.rst index e94e21cb9..ac83b2255 100644 --- a/docs/reference/ImageGrab.rst +++ b/docs/reference/ImageGrab.rst @@ -1,23 +1,21 @@ .. py:module:: PIL.ImageGrab .. py:currentmodule:: PIL.ImageGrab -:py:mod:`ImageGrab` Module (macOS and Windows only) -=================================================== +:py:mod:`~PIL.ImageGrab` Module +=============================== -The :py:mod:`ImageGrab` module can be used to copy the contents of the screen +The :py:mod:`~PIL.ImageGrab` module can be used to copy the contents of the screen or the clipboard to a PIL image memory. -.. note:: The current version works on macOS and Windows only. - .. versionadded:: 1.1.3 -.. py:function:: PIL.ImageGrab.grab(bbox=None, include_layered_windows=False, all_screens=False) +.. py:function:: grab(bbox=None, include_layered_windows=False, all_screens=False, xdisplay=None) Take a snapshot of the screen. The pixels inside the bounding box are - returned as an "RGB" image on Windows or "RGBA" on macOS. + returned as an "RGBA" on macOS, or an "RGB" image otherwise. If the bounding box is omitted, the entire screen is copied. - .. versionadded:: 1.1.3 (Windows), 3.0.0 (macOS) + .. versionadded:: 1.1.3 (Windows), 3.0.0 (macOS), 7.1.0 (Linux (X11)) :param bbox: What region to copy. Default is the entire screen. Note that on Windows OS, the top-left point may be negative if ``all_screens=True`` is used. @@ -27,11 +25,18 @@ or the clipboard to a PIL image memory. :param all_screens: Capture all monitors. Windows OS only. .. versionadded:: 6.2.0 + + :param xdisplay: + X11 Display address. Pass :data:`None` to grab the default system screen. Pass ``""`` to grab the default X11 screen on Windows or macOS. + + You can check X11 support using :py:func:`PIL.features.check_feature` with ``feature="xcb"``. + + .. versionadded:: 7.1.0 :return: An image -.. py:function:: PIL.ImageGrab.grabclipboard() +.. py:function:: grabclipboard() - Take a snapshot of the clipboard image, if any. + Take a snapshot of the clipboard image, if any. Only macOS and Windows are currently supported. .. versionadded:: 1.1.4 (Windows), 3.3.0 (macOS) diff --git a/docs/reference/ImageMath.rst b/docs/reference/ImageMath.rst index ca30244d1..821f60cf5 100644 --- a/docs/reference/ImageMath.rst +++ b/docs/reference/ImageMath.rst @@ -1,10 +1,10 @@ .. py:module:: PIL.ImageMath .. py:currentmodule:: PIL.ImageMath -:py:mod:`ImageMath` Module -========================== +:py:mod:`~PIL.ImageMath` Module +=============================== -The :py:mod:`ImageMath` module can be used to evaluate “image expressions”. The +The :py:mod:`~PIL.ImageMath` module can be used to evaluate “image expressions”. The module provides a single :py:meth:`~PIL.ImageMath.eval` function, which takes an expression string and one or more images. @@ -60,9 +60,8 @@ point values, as necessary. For example, if you add two 8-bit images, the result will be a 32-bit integer image. If you add a floating point constant to an 8-bit image, the result will be a 32-bit floating point image. -You can force conversion using the :py:func:`~PIL.ImageMath.convert`, -:py:func:`~PIL.ImageMath.float`, and :py:func:`~PIL.ImageMath.int` functions -described below. +You can force conversion using the ``convert()``, ``float()``, and ``int()`` +functions described below. Bitwise Operators ^^^^^^^^^^^^^^^^^ @@ -98,20 +97,24 @@ These functions are applied to each individual pixel. .. py:currentmodule:: None .. py:function:: abs(image) + :noindex: Absolute value. .. py:function:: convert(image, mode) + :noindex: Convert image to the given mode. The mode must be given as a string constant. .. py:function:: float(image) + :noindex: Convert image to 32-bit floating point. This is equivalent to convert(image, “F”). .. py:function:: int(image) + :noindex: Convert image to 32-bit integer. This is equivalent to convert(image, “I”). @@ -119,9 +122,11 @@ These functions are applied to each individual pixel. integers if necessary to get a correct result. .. py:function:: max(image1, image2) + :noindex: Maximum value. .. py:function:: min(image1, image2) + :noindex: Minimum value. diff --git a/docs/reference/ImageMorph.rst b/docs/reference/ImageMorph.rst index be9d59348..d4522a06a 100644 --- a/docs/reference/ImageMorph.rst +++ b/docs/reference/ImageMorph.rst @@ -1,10 +1,10 @@ .. py:module:: PIL.ImageMorph .. py:currentmodule:: PIL.ImageMorph -:py:mod:`ImageMorph` Module -=========================== +:py:mod:`~PIL.ImageMorph` Module +================================ -The :py:mod:`ImageMorph` module provides morphology operations on images. +The :py:mod:`~PIL.ImageMorph` module provides morphology operations on images. .. automodule:: PIL.ImageMorph :members: diff --git a/docs/reference/ImageOps.rst b/docs/reference/ImageOps.rst index 1c86d168f..9a16d6625 100644 --- a/docs/reference/ImageOps.rst +++ b/docs/reference/ImageOps.rst @@ -1,10 +1,10 @@ .. py:module:: PIL.ImageOps .. py:currentmodule:: PIL.ImageOps -:py:mod:`ImageOps` Module -========================== +:py:mod:`~PIL.ImageOps` Module +============================== -The :py:mod:`ImageOps` module contains a number of ‘ready-made’ image +The :py:mod:`~PIL.ImageOps` module contains a number of ‘ready-made’ image processing operations. This module is somewhat experimental, and most operators only work on L and RGB images. diff --git a/docs/reference/ImagePalette.rst b/docs/reference/ImagePalette.rst index 15b8aed8f..f14c1c3a4 100644 --- a/docs/reference/ImagePalette.rst +++ b/docs/reference/ImagePalette.rst @@ -1,10 +1,10 @@ .. py:module:: PIL.ImagePalette .. py:currentmodule:: PIL.ImagePalette -:py:mod:`ImagePalette` Module -============================= +:py:mod:`~PIL.ImagePalette` Module +================================== -The :py:mod:`ImagePalette` module contains a class of the same name to +The :py:mod:`~PIL.ImagePalette` module contains a class of the same name to represent the color palette of palette mapped images. .. note:: diff --git a/docs/reference/ImagePath.rst b/docs/reference/ImagePath.rst index 5ab350ef3..b9bdfc507 100644 --- a/docs/reference/ImagePath.rst +++ b/docs/reference/ImagePath.rst @@ -1,10 +1,10 @@ .. py:module:: PIL.ImagePath .. py:currentmodule:: PIL.ImagePath -:py:mod:`ImagePath` Module -========================== +:py:mod:`~PIL.ImagePath` Module +=============================== -The :py:mod:`ImagePath` module is used to store and manipulate 2-dimensional +The :py:mod:`~PIL.ImagePath` module is used to store and manipulate 2-dimensional vector data. Path objects can be passed to the methods on the :py:mod:`~PIL.ImageDraw` module. @@ -33,7 +33,7 @@ vector data. Path objects can be passed to the methods on the method modifies the path in place, and returns the number of points left in the path. - **distance** is measured as `Manhattan distance`_ and defaults to two + ``distance`` is measured as `Manhattan distance`_ and defaults to two pixels. .. _Manhattan distance: https://en.wikipedia.org/wiki/Manhattan_distance @@ -55,7 +55,7 @@ vector data. Path objects can be passed to the methods on the :param flat: By default, this function returns a list of 2-tuples [(x, y), ...]. If this argument is ``True``, it returns a flat list [x, y, ...] instead. - :return: A list of coordinates. See **flat**. + :return: A list of coordinates. See ``flat``. .. py:method:: PIL.ImagePath.Path.transform(matrix) diff --git a/docs/reference/ImageQt.rst b/docs/reference/ImageQt.rst index 7dd7084db..34d4cef51 100644 --- a/docs/reference/ImageQt.rst +++ b/docs/reference/ImageQt.rst @@ -1,15 +1,15 @@ .. py:module:: PIL.ImageQt .. py:currentmodule:: PIL.ImageQt -:py:mod:`ImageQt` Module -======================== +:py:mod:`~PIL.ImageQt` Module +============================= -The :py:mod:`ImageQt` module contains support for creating PyQt5 or PySide2 QImage +The :py:mod:`~PIL.ImageQt` module contains support for creating PyQt5 or PySide2 QImage objects from PIL images. .. versionadded:: 1.1.6 -.. py:class:: ImageQt.ImageQt(image) +.. py:class:: ImageQt(image) Creates an :py:class:`~PIL.ImageQt.ImageQt` object from a PIL :py:class:`~PIL.Image.Image` object. This class is a subclass of diff --git a/docs/reference/ImageSequence.rst b/docs/reference/ImageSequence.rst index 251ea3a93..1bfb554b6 100644 --- a/docs/reference/ImageSequence.rst +++ b/docs/reference/ImageSequence.rst @@ -1,10 +1,10 @@ .. py:module:: PIL.ImageSequence .. py:currentmodule:: PIL.ImageSequence -:py:mod:`ImageSequence` Module -============================== +:py:mod:`~PIL.ImageSequence` Module +=================================== -The :py:mod:`ImageSequence` module contains a wrapper class that lets you +The :py:mod:`~PIL.ImageSequence` module contains a wrapper class that lets you iterate over the frames of an image sequence. Extracting frames from an animation @@ -14,12 +14,11 @@ Extracting frames from an animation from PIL import Image, ImageSequence - im = Image.open("animation.fli") - - index = 1 - for frame in ImageSequence.Iterator(im): - frame.save("frame%d.png" % index) - index += 1 + with Image.open("animation.fli") as im: + index = 1 + for frame in ImageSequence.Iterator(im): + frame.save(f"frame{index}.png") + index += 1 The :py:class:`~PIL.ImageSequence.Iterator` class ------------------------------------------------- diff --git a/docs/reference/ImageShow.rst b/docs/reference/ImageShow.rst new file mode 100644 index 000000000..a30a6caed --- /dev/null +++ b/docs/reference/ImageShow.rst @@ -0,0 +1,27 @@ +.. py:module:: PIL.ImageShow +.. py:currentmodule:: PIL.ImageShow + +:py:mod:`~PIL.ImageShow` Module +=============================== + +The :py:mod:`~PIL.ImageShow` Module is used to display images. +All default viewers convert the image to be shown to PNG format. + +.. autofunction:: PIL.ImageShow.show + +.. autoclass:: WindowsViewer +.. autoclass:: MacViewer + +.. class:: UnixViewer + + The following viewers may be registered on Unix-based systems, if the given command is found: + + .. autoclass:: PIL.ImageShow.DisplayViewer + .. autoclass:: PIL.ImageShow.EogViewer + .. autoclass:: PIL.ImageShow.XVViewer + +.. autofunction:: PIL.ImageShow.register +.. autoclass:: PIL.ImageShow.Viewer + :member-order: bysource + :members: + :undoc-members: diff --git a/docs/reference/ImageStat.rst b/docs/reference/ImageStat.rst index 32f5917c1..5bb735296 100644 --- a/docs/reference/ImageStat.rst +++ b/docs/reference/ImageStat.rst @@ -1,13 +1,13 @@ .. py:module:: PIL.ImageStat .. py:currentmodule:: PIL.ImageStat -:py:mod:`ImageStat` Module -========================== +:py:mod:`~PIL.ImageStat` Module +=============================== -The :py:mod:`ImageStat` module calculates global statistics for an image, or +The :py:mod:`~PIL.ImageStat` module calculates global statistics for an image, or for a region of an image. -.. py:class:: PIL.ImageStat.Stat(image_or_list, mask=None) +.. py:class:: Stat(image_or_list, mask=None) Calculate statistics for the given image. If a mask is included, only the regions covered by that mask are included in the @@ -22,13 +22,13 @@ for a region of an image. .. note:: - This relies on the :py:meth:`~PIL.Image.histogram` method, and + This relies on the :py:meth:`~PIL.Image.Image.histogram` method, and simply returns the low and high bins used. This is correct for images with 8 bits per channel, but fails for other modes such as - ``I`` or ``F``. Instead, use :py:meth:`~PIL.Image.getextrema` to + ``I`` or ``F``. Instead, use :py:meth:`~PIL.Image.Image.getextrema` to return per-band extrema for the image. This is more correct and efficient because, for non-8-bit modes, the histogram method uses - :py:meth:`~PIL.Image.getextrema` to determine the bins used. + :py:meth:`~PIL.Image.Image.getextrema` to determine the bins used. .. py:attribute:: count diff --git a/docs/reference/ImageTk.rst b/docs/reference/ImageTk.rst index 7ee4af029..134ef5651 100644 --- a/docs/reference/ImageTk.rst +++ b/docs/reference/ImageTk.rst @@ -1,10 +1,10 @@ .. py:module:: PIL.ImageTk .. py:currentmodule:: PIL.ImageTk -:py:mod:`ImageTk` Module -======================== +:py:mod:`~PIL.ImageTk` Module +============================= -The :py:mod:`ImageTk` module contains support to create and modify Tkinter +The :py:mod:`~PIL.ImageTk` module contains support to create and modify Tkinter BitmapImage and PhotoImage objects from PIL images. For examples, see the demo programs in the Scripts directory. diff --git a/docs/reference/ImageWin.rst b/docs/reference/ImageWin.rst index ff3d6a7fc..2ee3cadb7 100644 --- a/docs/reference/ImageWin.rst +++ b/docs/reference/ImageWin.rst @@ -1,10 +1,10 @@ .. py:module:: PIL.ImageWin .. py:currentmodule:: PIL.ImageWin -:py:mod:`ImageWin` Module (Windows-only) -======================================== +:py:mod:`~PIL.ImageWin` Module (Windows-only) +============================================= -The :py:mod:`ImageWin` module contains support to create and display images on +The :py:mod:`~PIL.ImageWin` module contains support to create and display images on Windows. ImageWin can be used with PythonWin and other user interface toolkits that diff --git a/docs/reference/JpegPresets.rst b/docs/reference/JpegPresets.rst new file mode 100644 index 000000000..aafae44cf --- /dev/null +++ b/docs/reference/JpegPresets.rst @@ -0,0 +1,11 @@ +.. py:currentmodule:: PIL.JpegPresets + +:py:mod:`~PIL.JpegPresets` Module +================================= + +.. automodule:: PIL.JpegPresets + + .. data:: presets + :type: dict + + A dictionary of all supported presets. diff --git a/docs/reference/PSDraw.rst b/docs/reference/PSDraw.rst index 2b5b9b340..3e8512e7a 100644 --- a/docs/reference/PSDraw.rst +++ b/docs/reference/PSDraw.rst @@ -1,10 +1,10 @@ .. py:module:: PIL.PSDraw .. py:currentmodule:: PIL.PSDraw -:py:mod:`PSDraw` Module -======================= +:py:mod:`~PIL.PSDraw` Module +============================ -The :py:mod:`PSDraw` module provides simple print support for Postscript +The :py:mod:`~PIL.PSDraw` module provides simple print support for PostScript printers. You can print text, graphics and images through this module. .. autoclass:: PIL.PSDraw.PSDraw diff --git a/docs/reference/PixelAccess.rst b/docs/reference/PixelAccess.rst index 8a8569922..f28e58f86 100644 --- a/docs/reference/PixelAccess.rst +++ b/docs/reference/PixelAccess.rst @@ -17,8 +17,8 @@ changes it. .. code-block:: python from PIL import Image - im = Image.open('hopper.jpg') - px = im.load() + with Image.open('hopper.jpg') as im: + px = im.load() print (px[4,4]) px[4,4] = (0,0,0) print (px[4,4]) diff --git a/docs/reference/PyAccess.rst b/docs/reference/PyAccess.rst index 6a492cd86..486c9fc21 100644 --- a/docs/reference/PyAccess.rst +++ b/docs/reference/PyAccess.rst @@ -1,10 +1,10 @@ .. py:module:: PIL.PyAccess .. py:currentmodule:: PIL.PyAccess -:py:mod:`PyAccess` Module -========================= +:py:mod:`~PIL.PyAccess` Module +============================== -The :py:mod:`PyAccess` module provides a CFFI/Python implementation of the :ref:`PixelAccess`. This implementation is far faster on PyPy than the PixelAccess version. +The :py:mod:`~PIL.PyAccess` module provides a CFFI/Python implementation of the :ref:`PixelAccess`. This implementation is far faster on PyPy than the PixelAccess version. .. note:: Accessing individual pixels is fairly slow. If you are looping over all of the pixels in an image, there is likely @@ -18,8 +18,8 @@ The following script loads an image, accesses one pixel from it, then changes it .. code-block:: python from PIL import Image - im = Image.open('hopper.jpg') - px = im.load() + with Image.open('hopper.jpg') as im: + px = im.load() print (px[4,4]) px[4,4] = (0,0,0) print (px[4,4]) diff --git a/docs/reference/TiffTags.rst b/docs/reference/TiffTags.rst index 3b261625a..1441185dd 100644 --- a/docs/reference/TiffTags.rst +++ b/docs/reference/TiffTags.rst @@ -1,17 +1,17 @@ .. py:module:: PIL.TiffTags .. py:currentmodule:: PIL.TiffTags -:py:mod:`TiffTags` Module -========================= +:py:mod:`~PIL.TiffTags` Module +============================== -The :py:mod:`TiffTags` module exposes many of the standard TIFF +The :py:mod:`~PIL.TiffTags` module exposes many of the standard TIFF metadata tag numbers, names, and type information. .. method:: lookup(tag) :param tag: Integer tag number - :returns: Taginfo namedtuple, From the ``TAGS_V2`` info if possible, - otherwise just populating the value and name from ``TAGS``. + :returns: Taginfo namedtuple, From the :py:data:`~PIL.TiffTags.TAGS_V2` info if possible, + otherwise just populating the value and name from :py:data:`~PIL.TiffTags.TAGS`. If the tag is not recognized, "unknown" is returned for the name .. versionadded:: 3.1.0 @@ -22,7 +22,7 @@ metadata tag numbers, names, and type information. :param value: Integer Tag Number :param name: Tag Name - :param type: Integer type from :py:attr:`PIL.TiffTags.TYPES` + :param type: Integer type from :py:data:`PIL.TiffTags.TYPES` :param length: Array length: 0 == variable, 1 == single value, n = fixed :param enum: Dict of name:integer value options for an enumeration @@ -33,15 +33,17 @@ metadata tag numbers, names, and type information. .. versionadded:: 3.0.0 -.. py:attribute:: PIL.TiffTags.TAGS_V2 +.. py:data:: PIL.TiffTags.TAGS_V2 + :type: dict The ``TAGS_V2`` dictionary maps 16-bit integer tag numbers to - :py:class:`PIL.TagTypes.TagInfo` tuples for metadata fields defined in the TIFF + :py:class:`PIL.TiffTags.TagInfo` tuples for metadata fields defined in the TIFF spec. .. versionadded:: 3.0.0 -.. py:attribute:: PIL.TiffTags.TAGS +.. py:data:: PIL.TiffTags.TAGS + :type: dict The ``TAGS`` dictionary maps 16-bit integer TIFF tag number to descriptive string names. For instance: @@ -50,10 +52,16 @@ metadata tag numbers, names, and type information. >>> TAGS[0x010e] 'ImageDescription' - This dictionary contains a superset of the tags in TAGS_V2, common + This dictionary contains a superset of the tags in :py:data:`~PIL.TiffTags.TAGS_V2`, common EXIF tags, and other well known metadata tags. -.. py:attribute:: PIL.TiffTags.TYPES +.. py:data:: PIL.TiffTags.TYPES + :type: dict The ``TYPES`` dictionary maps the TIFF type short integer to a human readable type name. + +.. py:data:: PIL.TiffTags.LIBTIFF_CORE + :type: list + + A list of supported tag IDs when writing using LibTIFF. diff --git a/docs/reference/block_allocator.rst b/docs/reference/block_allocator.rst index 400f236dc..1abe5280f 100644 --- a/docs/reference/block_allocator.rst +++ b/docs/reference/block_allocator.rst @@ -40,7 +40,7 @@ variables: * ``PILLOW_BLOCK_SIZE``, in bytes, K, or M. Specifies the maximum block size for ``ImagingAllocateArray``. Valid values are - integers, with an optional `k` or `m` suffix. Defaults to 16M. + integers, with an optional ``k`` or ``m`` suffix. Defaults to 16M. * ``PILLOW_BLOCKS_MAX`` Specifies the number of freed blocks to retain to fill future memory requests. Any freed blocks over this diff --git a/docs/reference/features.rst b/docs/reference/features.rst new file mode 100644 index 000000000..dd218fa0e --- /dev/null +++ b/docs/reference/features.rst @@ -0,0 +1,66 @@ +.. py:module:: PIL.features +.. py:currentmodule:: PIL.features + +:py:mod:`~PIL.features` Module +============================== + +The :py:mod:`PIL.features` module can be used to detect which Pillow features are available on your system. + +.. autofunction:: PIL.features.pilinfo +.. autofunction:: PIL.features.check +.. autofunction:: PIL.features.version +.. autofunction:: PIL.features.get_supported + +Modules +------- + +Support for the following modules can be checked: + +* ``pil``: The Pillow core module, required for all functionality. +* ``tkinter``: Tkinter support. Version number not available. +* ``freetype2``: FreeType font support via :py:func:`PIL.ImageFont.truetype`. +* ``littlecms2``: LittleCMS 2 support via :py:mod:`PIL.ImageCms`. +* ``webp``: WebP image support. + +.. autofunction:: PIL.features.check_module +.. autofunction:: PIL.features.version_module +.. autofunction:: PIL.features.get_supported_modules + +Codecs +------ + +Support for these is only checked during Pillow compilation. +If the required library was uninstalled from the system, the ``pil`` core module may fail to load instead. +Except for ``jpg``, the version number is checked at run-time. + +Support for the following codecs can be checked: + +* ``jpg``: (compile time) Libjpeg support, required for JPEG based image formats. Only compile time version number is available. +* ``jpg_2000``: (compile time) OpenJPEG support, required for JPEG 2000 image formats. +* ``zlib``: (compile time) Zlib support, required for zlib compressed formats, such as PNG. +* ``libtiff``: (compile time) LibTIFF support, required for TIFF based image formats. + +.. autofunction:: PIL.features.check_codec +.. autofunction:: PIL.features.version_codec +.. autofunction:: PIL.features.get_supported_codecs + +Features +-------- + +Some of these are only checked during Pillow compilation. +If the required library was uninstalled from the system, the relevant module may fail to load instead. +Feature version numbers are available only where stated. + +Support for the following features can be checked: + +* ``libjpeg_turbo``: (compile time) Whether Pillow was compiled against the libjpeg-turbo version of libjpeg. Compile-time version number is available. +* ``transp_webp``: Support for transparency in WebP images. +* ``webp_mux``: (compile time) Support for EXIF data in WebP images. +* ``webp_anim``: (compile time) Support for animated WebP images. +* ``raqm``: Raqm library, required for ``ImageFont.LAYOUT_RAQM`` in :py:func:`PIL.ImageFont.truetype`. Run-time version number is available for Raqm 0.7.0 or newer. +* ``libimagequant``: (compile time) ImageQuant quantization support in :py:func:`PIL.Image.Image.quantize`. Run-time version number is available. +* ``xcb``: (compile time) Support for X11 in :py:func:`PIL.ImageGrab.grab` via the XCB library. + +.. autofunction:: PIL.features.check_feature +.. autofunction:: PIL.features.version_feature +.. autofunction:: PIL.features.get_supported_features diff --git a/docs/reference/index.rst b/docs/reference/index.rst index 8c09e7b67..5d6affa94 100644 --- a/docs/reference/index.rst +++ b/docs/reference/index.rst @@ -7,8 +7,8 @@ Reference Image ImageChops - ImageColor ImageCms + ImageColor ImageDraw ImageEnhance ImageFile @@ -22,14 +22,17 @@ Reference ImagePath ImageQt ImageSequence + ImageShow ImageStat ImageTk ImageWin ExifTags TiffTags + JpegPresets PSDraw PixelAccess PyAccess + features ../PIL plugins internal_design diff --git a/docs/reference/internal_design.rst b/docs/reference/internal_design.rst index bbc9050cf..5f911db51 100644 --- a/docs/reference/internal_design.rst +++ b/docs/reference/internal_design.rst @@ -7,4 +7,4 @@ Internal Reference Docs open_files limits block_allocator - + internal_modules diff --git a/docs/reference/internal_modules.rst b/docs/reference/internal_modules.rst new file mode 100644 index 000000000..1105ff76e --- /dev/null +++ b/docs/reference/internal_modules.rst @@ -0,0 +1,47 @@ +Internal Modules +================ + +:mod:`~PIL._binary` Module +-------------------------- + +.. automodule:: PIL._binary + :members: + :undoc-members: + :show-inheritance: + +:mod:`~PIL._tkinter_finder` Module +---------------------------------- + +.. automodule:: PIL._tkinter_finder + :members: + :undoc-members: + :show-inheritance: + +:mod:`~PIL._util` Module +------------------------ + +.. automodule:: PIL._util + :members: + :undoc-members: + :show-inheritance: + +:mod:`~PIL._version` Module +--------------------------- + +.. module:: PIL._version + +.. data:: __version__ + :annotation: + :type: str + + This is the master version number for Pillow, + all other uses reference this module. + +:mod:`PIL.Image.core` Module +---------------------------- + +.. module:: PIL._imaging +.. module:: PIL.Image.core + +An internal interface module previously known as :mod:`~PIL._imaging`, +implemented in :file:`_imaging.c`. diff --git a/docs/reference/plugins.rst b/docs/reference/plugins.rst index 46f657fce..7094f8784 100644 --- a/docs/reference/plugins.rst +++ b/docs/reference/plugins.rst @@ -1,340 +1,332 @@ Plugin reference ================ -:mod:`BmpImagePlugin` Module ----------------------------- +:mod:`~PIL.BmpImagePlugin` Module +--------------------------------- .. automodule:: PIL.BmpImagePlugin :members: :undoc-members: :show-inheritance: -:mod:`BufrStubImagePlugin` Module ---------------------------------- +:mod:`~PIL.BufrStubImagePlugin` Module +-------------------------------------- .. automodule:: PIL.BufrStubImagePlugin :members: :undoc-members: :show-inheritance: -:mod:`CurImagePlugin` Module ----------------------------- +:mod:`~PIL.CurImagePlugin` Module +--------------------------------- .. automodule:: PIL.CurImagePlugin :members: :undoc-members: :show-inheritance: -:mod:`DcxImagePlugin` Module ----------------------------- +:mod:`~PIL.DcxImagePlugin` Module +--------------------------------- .. automodule:: PIL.DcxImagePlugin :members: :undoc-members: :show-inheritance: -:mod:`EpsImagePlugin` Module ----------------------------- +:mod:`~PIL.EpsImagePlugin` Module +--------------------------------- .. automodule:: PIL.EpsImagePlugin :members: :undoc-members: :show-inheritance: -:mod:`FitsStubImagePlugin` Module ---------------------------------- +:mod:`~PIL.FitsStubImagePlugin` Module +-------------------------------------- .. automodule:: PIL.FitsStubImagePlugin :members: :undoc-members: :show-inheritance: -:mod:`FliImagePlugin` Module ----------------------------- +:mod:`~PIL.FliImagePlugin` Module +--------------------------------- .. automodule:: PIL.FliImagePlugin :members: :undoc-members: :show-inheritance: -:mod:`FpxImagePlugin` Module ----------------------------- +:mod:`~PIL.FpxImagePlugin` Module +--------------------------------- .. automodule:: PIL.FpxImagePlugin :members: :undoc-members: :show-inheritance: -:mod:`GbrImagePlugin` Module ----------------------------- +:mod:`~PIL.GbrImagePlugin` Module +--------------------------------- .. automodule:: PIL.GbrImagePlugin :members: :undoc-members: :show-inheritance: -:mod:`GifImagePlugin` Module ----------------------------- +:mod:`~PIL.GifImagePlugin` Module +--------------------------------- .. automodule:: PIL.GifImagePlugin :members: :undoc-members: :show-inheritance: -:mod:`GribStubImagePlugin` Module ---------------------------------- +:mod:`~PIL.GribStubImagePlugin` Module +-------------------------------------- .. automodule:: PIL.GribStubImagePlugin :members: :undoc-members: :show-inheritance: -:mod:`Hdf5StubImagePlugin` Module ---------------------------------- +:mod:`~PIL.Hdf5StubImagePlugin` Module +-------------------------------------- .. automodule:: PIL.Hdf5StubImagePlugin :members: :undoc-members: :show-inheritance: -:mod:`IcnsImagePlugin` Module ------------------------------ +:mod:`~PIL.IcnsImagePlugin` Module +---------------------------------- .. automodule:: PIL.IcnsImagePlugin :members: :undoc-members: :show-inheritance: -:mod:`IcoImagePlugin` Module ----------------------------- +:mod:`~PIL.IcoImagePlugin` Module +--------------------------------- .. automodule:: PIL.IcoImagePlugin :members: :undoc-members: :show-inheritance: -:mod:`ImImagePlugin` Module ---------------------------- +:mod:`~PIL.ImImagePlugin` Module +-------------------------------- .. automodule:: PIL.ImImagePlugin :members: :undoc-members: :show-inheritance: -:mod:`ImtImagePlugin` Module ----------------------------- +:mod:`~PIL.ImtImagePlugin` Module +--------------------------------- .. automodule:: PIL.ImtImagePlugin :members: :undoc-members: :show-inheritance: -:mod:`IptcImagePlugin` Module ------------------------------ +:mod:`~PIL.IptcImagePlugin` Module +---------------------------------- .. automodule:: PIL.IptcImagePlugin :members: :undoc-members: :show-inheritance: -:mod:`JpegImagePlugin` Module ------------------------------ +:mod:`~PIL.JpegImagePlugin` Module +---------------------------------- .. automodule:: PIL.JpegImagePlugin :members: :undoc-members: :show-inheritance: -:mod:`Jpeg2KImagePlugin` Module -------------------------------- +:mod:`~PIL.Jpeg2KImagePlugin` Module +------------------------------------ .. automodule:: PIL.Jpeg2KImagePlugin :members: :undoc-members: :show-inheritance: -:mod:`McIdasImagePlugin` Module -------------------------------- +:mod:`~PIL.McIdasImagePlugin` Module +------------------------------------ .. automodule:: PIL.McIdasImagePlugin :members: :undoc-members: :show-inheritance: -:mod:`MicImagePlugin` Module ----------------------------- +:mod:`~PIL.MicImagePlugin` Module +--------------------------------- .. automodule:: PIL.MicImagePlugin :members: :undoc-members: :show-inheritance: -:mod:`MpegImagePlugin` Module ------------------------------ +:mod:`~PIL.MpegImagePlugin` Module +---------------------------------- .. automodule:: PIL.MpegImagePlugin :members: :undoc-members: :show-inheritance: -:mod:`MspImagePlugin` Module ----------------------------- +:mod:`~PIL.MspImagePlugin` Module +--------------------------------- .. automodule:: PIL.MspImagePlugin :members: :undoc-members: :show-inheritance: -:mod:`PalmImagePlugin` Module ------------------------------ +:mod:`~PIL.PalmImagePlugin` Module +---------------------------------- .. automodule:: PIL.PalmImagePlugin :members: :undoc-members: :show-inheritance: -:mod:`PcdImagePlugin` Module ----------------------------- +:mod:`~PIL.PcdImagePlugin` Module +--------------------------------- .. automodule:: PIL.PcdImagePlugin :members: :undoc-members: :show-inheritance: -:mod:`PcxImagePlugin` Module ----------------------------- +:mod:`~PIL.PcxImagePlugin` Module +--------------------------------- .. automodule:: PIL.PcxImagePlugin :members: :undoc-members: :show-inheritance: -:mod:`PdfImagePlugin` Module ----------------------------- +:mod:`~PIL.PdfImagePlugin` Module +--------------------------------- .. automodule:: PIL.PdfImagePlugin :members: :undoc-members: :show-inheritance: -:mod:`PixarImagePlugin` Module ------------------------------- +:mod:`~PIL.PixarImagePlugin` Module +----------------------------------- .. automodule:: PIL.PixarImagePlugin :members: :undoc-members: :show-inheritance: -:mod:`PngImagePlugin` Module ----------------------------- +:mod:`~PIL.PngImagePlugin` Module +--------------------------------- .. automodule:: PIL.PngImagePlugin - :members: ChunkStream, PngImageFile, PngStream, getchunks, is_cid, putchunk - :show-inheritance: -.. autoclass:: PIL.PngImagePlugin.ChunkStream - :members: - :undoc-members: - :show-inheritance: -.. autoclass:: PIL.PngImagePlugin.PngImageFile - :members: - :undoc-members: - :show-inheritance: -.. autoclass:: PIL.PngImagePlugin.PngStream - :members: + :members: ChunkStream, PngImageFile, PngStream, getchunks, is_cid, putchunk, + MAX_TEXT_CHUNK, MAX_TEXT_MEMORY, APNG_BLEND_OP_SOURCE, APNG_BLEND_OP_OVER, + APNG_DISPOSE_OP_NONE, APNG_DISPOSE_OP_BACKGROUND, APNG_DISPOSE_OP_PREVIOUS :undoc-members: :show-inheritance: + :member-order: groupwise -:mod:`PpmImagePlugin` Module ----------------------------- +:mod:`~PIL.PpmImagePlugin` Module +--------------------------------- .. automodule:: PIL.PpmImagePlugin :members: :undoc-members: :show-inheritance: -:mod:`PsdImagePlugin` Module ----------------------------- +:mod:`~PIL.PsdImagePlugin` Module +--------------------------------- .. automodule:: PIL.PsdImagePlugin :members: :undoc-members: :show-inheritance: -:mod:`SgiImagePlugin` Module ----------------------------- +:mod:`~PIL.SgiImagePlugin` Module +--------------------------------- .. automodule:: PIL.SgiImagePlugin :members: :undoc-members: :show-inheritance: -:mod:`SpiderImagePlugin` Module -------------------------------- +:mod:`~PIL.SpiderImagePlugin` Module +------------------------------------ .. automodule:: PIL.SpiderImagePlugin :members: :undoc-members: :show-inheritance: -:mod:`SunImagePlugin` Module ----------------------------- +:mod:`~PIL.SunImagePlugin` Module +--------------------------------- .. automodule:: PIL.SunImagePlugin :members: :undoc-members: :show-inheritance: -:mod:`TgaImagePlugin` Module ----------------------------- +:mod:`~PIL.TgaImagePlugin` Module +--------------------------------- .. automodule:: PIL.TgaImagePlugin :members: :undoc-members: :show-inheritance: -:mod:`TiffImagePlugin` Module ------------------------------ +:mod:`~PIL.TiffImagePlugin` Module +---------------------------------- .. automodule:: PIL.TiffImagePlugin :members: :undoc-members: :show-inheritance: -:mod:`WebPImagePlugin` Module ------------------------------ +:mod:`~PIL.WebPImagePlugin` Module +---------------------------------- .. automodule:: PIL.WebPImagePlugin :members: :undoc-members: :show-inheritance: -:mod:`WmfImagePlugin` Module ----------------------------- +:mod:`~PIL.WmfImagePlugin` Module +--------------------------------- .. automodule:: PIL.WmfImagePlugin :members: :undoc-members: :show-inheritance: -:mod:`XVThumbImagePlugin` Module --------------------------------- +:mod:`~PIL.XVThumbImagePlugin` Module +------------------------------------- .. automodule:: PIL.XVThumbImagePlugin :members: :undoc-members: :show-inheritance: -:mod:`XbmImagePlugin` Module ----------------------------- +:mod:`~PIL.XbmImagePlugin` Module +--------------------------------- .. automodule:: PIL.XbmImagePlugin :members: :undoc-members: :show-inheritance: -:mod:`XpmImagePlugin` Module ----------------------------- +:mod:`~PIL.XpmImagePlugin` Module +--------------------------------- .. automodule:: PIL.XpmImagePlugin :members: diff --git a/docs/releasenotes/2.7.0.rst b/docs/releasenotes/2.7.0.rst index 931f9fd1e..03000528f 100644 --- a/docs/releasenotes/2.7.0.rst +++ b/docs/releasenotes/2.7.0.rst @@ -29,53 +29,53 @@ Image resizing filters Image resizing methods :py:meth:`~PIL.Image.Image.resize` and :py:meth:`~PIL.Image.Image.thumbnail` take a ``resample`` argument, which tells which filter should be used for resampling. Possible values are: -:py:attr:`PIL.Image.NEAREST`, :py:attr:`PIL.Image.BILINEAR`, -:py:attr:`PIL.Image.BICUBIC` and :py:attr:`PIL.Image.ANTIALIAS`. +:py:data:`PIL.Image.NEAREST`, :py:data:`PIL.Image.BILINEAR`, +:py:data:`PIL.Image.BICUBIC` and :py:data:`PIL.Image.ANTIALIAS`. Almost all of them were changed in this version. Bicubic and bilinear downscaling ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -From the beginning :py:attr:`~PIL.Image.BILINEAR` and -:py:attr:`~PIL.Image.BICUBIC` filters were based on affine transformations +From the beginning :py:data:`~PIL.Image.BILINEAR` and +:py:data:`~PIL.Image.BICUBIC` filters were based on affine transformations and used a fixed number of pixels from the source image for every destination -pixel (2x2 pixels for :py:attr:`~PIL.Image.BILINEAR` and 4x4 for -:py:attr:`~PIL.Image.BICUBIC`). This gave an unsatisfactory result for +pixel (2x2 pixels for :py:data:`~PIL.Image.BILINEAR` and 4x4 for +:py:data:`~PIL.Image.BICUBIC`). This gave an unsatisfactory result for downscaling. At the same time, a high quality convolutions-based algorithm with -flexible kernel was used for :py:attr:`~PIL.Image.ANTIALIAS` filter. +flexible kernel was used for :py:data:`~PIL.Image.ANTIALIAS` filter. Starting from Pillow 2.7.0, a high quality convolutions-based algorithm is used for all of these three filters. If you have previously used any tricks to maintain quality when downscaling with -:py:attr:`~PIL.Image.BILINEAR` and :py:attr:`~PIL.Image.BICUBIC` filters +:py:data:`~PIL.Image.BILINEAR` and :py:data:`~PIL.Image.BICUBIC` filters (for example, reducing within several steps), they are unnecessary now. Antialias renamed to Lanczos ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -A new :py:attr:`PIL.Image.LANCZOS` constant was added instead of -:py:attr:`~PIL.Image.ANTIALIAS`. +A new :py:data:`PIL.Image.LANCZOS` constant was added instead of +:py:data:`~PIL.Image.ANTIALIAS`. -When :py:attr:`~PIL.Image.ANTIALIAS` was initially added, it was the only +When :py:data:`~PIL.Image.ANTIALIAS` was initially added, it was the only high-quality filter based on convolutions. It's name was supposed to reflect this. Starting from Pillow 2.7.0 all resize method are based on convolutions. All of them are antialias from now on. And the real name of the -:py:attr:`~PIL.Image.ANTIALIAS` filter is Lanczos filter. +:py:data:`~PIL.Image.ANTIALIAS` filter is Lanczos filter. -The :py:attr:`~PIL.Image.ANTIALIAS` constant is left for backward compatibility -and is an alias for :py:attr:`~PIL.Image.LANCZOS`. +The :py:data:`~PIL.Image.ANTIALIAS` constant is left for backward compatibility +and is an alias for :py:data:`~PIL.Image.LANCZOS`. Lanczos upscaling quality ^^^^^^^^^^^^^^^^^^^^^^^^^ -The image upscaling quality with :py:attr:`~PIL.Image.LANCZOS` filter was -almost the same as :py:attr:`~PIL.Image.BILINEAR` due to bug. This has been fixed. +The image upscaling quality with :py:data:`~PIL.Image.LANCZOS` filter was +almost the same as :py:data:`~PIL.Image.BILINEAR` due to bug. This has been fixed. Bicubic upscaling quality ^^^^^^^^^^^^^^^^^^^^^^^^^ -The :py:attr:`~PIL.Image.BICUBIC` filter for affine transformations produced +The :py:data:`~PIL.Image.BICUBIC` filter for affine transformations produced sharp, slightly pixelated image for upscaling. Bicubic for convolutions is more soft. @@ -84,42 +84,42 @@ Resize performance In most cases, convolution is more a expensive algorithm for downscaling because it takes into account all the pixels of source image. Therefore -:py:attr:`~PIL.Image.BILINEAR` and :py:attr:`~PIL.Image.BICUBIC` filters' +:py:data:`~PIL.Image.BILINEAR` and :py:data:`~PIL.Image.BICUBIC` filters' performance can be lower than before. On the other hand the quality of -:py:attr:`~PIL.Image.BILINEAR` and :py:attr:`~PIL.Image.BICUBIC` was close to -:py:attr:`~PIL.Image.NEAREST`. So if such quality is suitable for your tasks -you can switch to :py:attr:`~PIL.Image.NEAREST` filter for downscaling, +:py:data:`~PIL.Image.BILINEAR` and :py:data:`~PIL.Image.BICUBIC` was close to +:py:data:`~PIL.Image.NEAREST`. So if such quality is suitable for your tasks +you can switch to :py:data:`~PIL.Image.NEAREST` filter for downscaling, which will give a huge improvement in performance. At the same time performance of convolution resampling for downscaling has been improved by around a factor of two compared to the previous version. -The upscaling performance of the :py:attr:`~PIL.Image.LANCZOS` filter has -remained the same. For :py:attr:`~PIL.Image.BILINEAR` filter it has improved by -1.5 times and for :py:attr:`~PIL.Image.BICUBIC` by four times. +The upscaling performance of the :py:data:`~PIL.Image.LANCZOS` filter has +remained the same. For :py:data:`~PIL.Image.BILINEAR` filter it has improved by +1.5 times and for :py:data:`~PIL.Image.BICUBIC` by four times. Default filter for thumbnails ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ In Pillow 2.5 the default filter for :py:meth:`~PIL.Image.Image.thumbnail` was -changed from :py:attr:`~PIL.Image.NEAREST` to :py:attr:`~PIL.Image.ANTIALIAS`. +changed from :py:data:`~PIL.Image.NEAREST` to :py:data:`~PIL.Image.ANTIALIAS`. Antialias was chosen because all the other filters gave poor quality for -reduction. Starting from Pillow 2.7.0, :py:attr:`~PIL.Image.ANTIALIAS` has been -replaced with :py:attr:`~PIL.Image.BICUBIC`, because it's faster and -:py:attr:`~PIL.Image.ANTIALIAS` doesn't give any advantages after +reduction. Starting from Pillow 2.7.0, :py:data:`~PIL.Image.ANTIALIAS` has been +replaced with :py:data:`~PIL.Image.BICUBIC`, because it's faster and +:py:data:`~PIL.Image.ANTIALIAS` doesn't give any advantages after downscaling with libjpeg, which uses supersampling internally, not convolutions. Image transposition ------------------- -A new method :py:attr:`PIL.Image.TRANSPOSE` has been added for the +A new method :py:data:`PIL.Image.TRANSPOSE` has been added for the :py:meth:`~PIL.Image.Image.transpose` operation in addition to -:py:attr:`~PIL.Image.FLIP_LEFT_RIGHT`, :py:attr:`~PIL.Image.FLIP_TOP_BOTTOM`, -:py:attr:`~PIL.Image.ROTATE_90`, :py:attr:`~PIL.Image.ROTATE_180`, -:py:attr:`~PIL.Image.ROTATE_270`. :py:attr:`~PIL.Image.TRANSPOSE` is an algebra +:py:data:`~PIL.Image.FLIP_LEFT_RIGHT`, :py:data:`~PIL.Image.FLIP_TOP_BOTTOM`, +:py:data:`~PIL.Image.ROTATE_90`, :py:data:`~PIL.Image.ROTATE_180`, +:py:data:`~PIL.Image.ROTATE_270`. :py:data:`~PIL.Image.TRANSPOSE` is an algebra transpose, with an image reflected across its main diagonal. -The speed of :py:attr:`~PIL.Image.ROTATE_90`, :py:attr:`~PIL.Image.ROTATE_270` -and :py:attr:`~PIL.Image.TRANSPOSE` has been significantly improved for large +The speed of :py:data:`~PIL.Image.ROTATE_90`, :py:data:`~PIL.Image.ROTATE_270` +and :py:data:`~PIL.Image.TRANSPOSE` has been significantly improved for large images which don't fit in the processor cache. Gaussian blur and unsharp mask diff --git a/docs/releasenotes/3.1.1.rst b/docs/releasenotes/3.1.1.rst index 8c32a43e7..38118ea39 100644 --- a/docs/releasenotes/3.1.1.rst +++ b/docs/releasenotes/3.1.1.rst @@ -6,7 +6,7 @@ CVE-2016-0740 -- Buffer overflow in TiffDecode.c ------------------------------------------------ Pillow 3.1.0 and earlier when linked against libtiff >= 4.0.0 on x64 -may overflow a buffer when reading a specially crafted tiff file. +may overflow a buffer when reading a specially crafted tiff file (:cve:`CVE-2016-0740`). Specifically, libtiff >= 4.0.0 changed the return type of ``TIFFScanlineSize`` from ``int32`` to machine dependent @@ -24,9 +24,11 @@ CVE-2016-0775 -- Buffer overflow in FliDecode.c ----------------------------------------------- In all versions of Pillow, dating back at least to the last PIL 1.1.7 -release, FliDecode.c has a buffer overflow error. +release, FliDecode.c has a buffer overflow error (:cve:`CVE-2016-0775`). -Around line 192:: +Around line 192: + +.. code-block:: c case 16: /* COPY chunk */ @@ -45,13 +47,13 @@ is a set of row pointers to segments of memory that are the size of the row. At the max ``y``, this will write the contents of the line off the end of the memory buffer, causing a segfault. -This issue was found by Alyssa Besseling at Atlassian +This issue was found by Alyssa Besseling at Atlassian. CVE-2016-2533 -- Buffer overflow in PcdDecode.c ----------------------------------------------- In all versions of Pillow, dating back at least to the last PIL 1.1.7 -release, ``PcdDecode.c`` has a buffer overflow error. +release, ``PcdDecode.c`` has a buffer overflow error (:cve:`CVE-2016-2533`). The ``state.buffer`` for ``PcdDecode.c`` is allocated based on a 3 bytes per pixel sizing, where ``PcdDecode.c`` wrote into the buffer @@ -63,14 +65,16 @@ Integer overflow in Resample.c ------------------------------ If a large value was passed into the new size for an image, it is -possible to overflow an int32 value passed into malloc. +possible to overflow an ``int32`` value passed into malloc. - kk = malloc(xsize * kmax * sizeof(float)); - ... - xbounds = malloc(xsize * 2 * sizeof(int)); +.. code-block:: c + + kk = malloc(xsize * kmax * sizeof(float)); + ... + xbounds = malloc(xsize * 2 * sizeof(int)); ``xsize`` is trusted user input. These multiplications can overflow, -leading the malloc'd buffer to be undersized. These allocations are +leading the ``malloc``'d buffer to be undersized. These allocations are followed by a loop that writes out of bounds. This can lead to corruption on the heap of the Python process with attacker controlled float data. diff --git a/docs/releasenotes/3.1.2.rst b/docs/releasenotes/3.1.2.rst index ddb6a2ada..b5f7cfe99 100644 --- a/docs/releasenotes/3.1.2.rst +++ b/docs/releasenotes/3.1.2.rst @@ -7,9 +7,11 @@ CVE-2016-3076 -- Buffer overflow in Jpeg2KEncode.c Pillow between 2.5.0 and 3.1.1 may overflow a buffer when writing large Jpeg2000 files, allowing for code execution or other memory -corruption. +corruption (:cve:`CVE-2016-3076`). -This occurs specifically in the function ``j2k_encode_entry``, at the line:: +This occurs specifically in the function ``j2k_encode_entry``, at the line: + +.. code-block:: c state->buffer = malloc (tile_width * tile_height * components * prec / 8); diff --git a/docs/releasenotes/4.1.0.rst b/docs/releasenotes/4.1.0.rst index dc5d73479..4d6598d8e 100644 --- a/docs/releasenotes/4.1.0.rst +++ b/docs/releasenotes/4.1.0.rst @@ -10,9 +10,9 @@ Several deprecated items have been removed. resolution', 'resolution unit', and 'date time' has been removed. Underscores should be used instead. -* The methods :py:meth:`PIL.ImageDraw.ImageDraw.setink`, - :py:meth:`PIL.ImageDraw.ImageDraw.setfill`, and - :py:meth:`PIL.ImageDraw.ImageDraw.setfont` have been removed. +* The methods ``PIL.ImageDraw.ImageDraw.setink``, + ``PIL.ImageDraw.ImageDraw.setfill``, and + ``PIL.ImageDraw.ImageDraw.setfont`` have been removed. Closing Files When Opening Images diff --git a/docs/releasenotes/4.2.0.rst b/docs/releasenotes/4.2.0.rst index e07fd9071..1e9637f1e 100644 --- a/docs/releasenotes/4.2.0.rst +++ b/docs/releasenotes/4.2.0.rst @@ -6,10 +6,9 @@ Added Complex Text Rendering Pillow now supports complex text rendering for scripts requiring glyph composition and bidirectional flow. This optional feature adds three -dependencies: harfbuzz, fribidi, and raqm. See the `install -documentation <../installation.html>`_ for further details. This feature is -tested and works on Unix and Mac, but has not yet been built on Windows -platforms. +dependencies: harfbuzz, fribidi, and raqm. See the :doc:`install documentation +<../installation>` for further details. This feature is tested and works on +Unix and Mac, but has not yet been built on Windows platforms. New Optional Parameters ======================= @@ -27,16 +26,16 @@ New DecompressionBomb Warning :py:meth:`PIL.Image.Image.crop` now may raise a DecompressionBomb warning if the crop region enlarges the image over the threshold -specified by :py:attr:`PIL.Image.MAX_PIXELS`. +specified by :py:data:`PIL.Image.MAX_IMAGE_PIXELS`. Removed Deprecated Items ======================== Several deprecated items have been removed. -* The methods :py:meth:`PIL.ImageWin.Dib.fromstring`, - :py:meth:`PIL.ImageWin.Dib.tostring` and - :py:meth:`PIL.TiffImagePlugin.ImageFileDirectory_v2.as_dict` have +* The methods ``PIL.ImageWin.Dib.fromstring``, + ``PIL.ImageWin.Dib.tostring`` and + ``PIL.TiffImagePlugin.ImageFileDirectory_v2.as_dict`` have been removed. * Before Pillow 4.2.0, attempting to save an RGBA image as JPEG would diff --git a/docs/releasenotes/4.3.0.rst b/docs/releasenotes/4.3.0.rst index 6fa554e23..ea81fc45e 100644 --- a/docs/releasenotes/4.3.0.rst +++ b/docs/releasenotes/4.3.0.rst @@ -124,7 +124,7 @@ This release contains several performance improvements: * ``Image.transpose`` has been accelerated 15% or more by using a cache friendly algorithm. * ImageFilters based on Kernel convolution are significantly faster - due to the new MultibandFilter feature. + due to the new :py:class:`~PIL.ImageFilter.MultibandFilter` feature. * All memory allocation for images is now done in blocks, rather than falling back to an allocation for each scan line for images larger than the block size. diff --git a/docs/releasenotes/5.3.0.rst b/docs/releasenotes/5.3.0.rst index cce671c32..bff56566b 100644 --- a/docs/releasenotes/5.3.0.rst +++ b/docs/releasenotes/5.3.0.rst @@ -34,7 +34,7 @@ Curved joints for line sequences ``ImageDraw.Draw.line`` draws a line, or lines, between points. Previously, when multiple points are given, for a larger ``width``, the joints between these lines looked unsightly. There is now an additional optional argument, -``joint``, defaulting to ``None``. When it is set to ``curved``, the joints +``joint``, defaulting to :data:`None`. When it is set to ``curved``, the joints between the lines will become rounded. ImageOps.colorize diff --git a/docs/releasenotes/6.0.0.rst b/docs/releasenotes/6.0.0.rst index 0145347f2..3e3b945a0 100644 --- a/docs/releasenotes/6.0.0.rst +++ b/docs/releasenotes/6.0.0.rst @@ -17,7 +17,7 @@ Removed deprecated PIL.OleFileIO PIL.OleFileIO was removed as a vendored file and in Pillow 4.0.0 (2017-01) in favour of the upstream olefile Python package, and replaced with an ``ImportError``. The deprecated file has now been removed from Pillow. If needed, install from PyPI (eg. -``pip install olefile``). +``python3 -m pip install olefile``). Removed deprecated ImageOps functions ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/docs/releasenotes/6.2.0.rst b/docs/releasenotes/6.2.0.rst index faf84e6bd..20a009cc1 100644 --- a/docs/releasenotes/6.2.0.rst +++ b/docs/releasenotes/6.2.0.rst @@ -73,7 +73,7 @@ Security ======== This release catches several buffer overruns, as well as addressing -CVE-2019-16865. The CVE is regarding DOS problems, such as consuming large +:cve:`CVE-2019-16865`. The CVE is regarding DOS problems, such as consuming large amounts of memory, or taking a large amount of time to process an image. In RawDecode.c, an error is now thrown if skip is calculated to be less than @@ -96,14 +96,14 @@ Other Changes Removed bdist_wininst .exe installers ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -.exe installers fell out of favour with PEP 527, and will be deprecated in +.exe installers fell out of favour with :pep:`527`, and will be deprecated in Python 3.8. Pillow will no longer be distributing them. Wheels should be used instead. Flags for libwebp in wheels ^^^^^^^^^^^^^^^^^^^^^^^^^^^ -When building libwebp for inclusion in wheels, Pillow now adds the -O3 and --DNDEBUG CFLAGS. These flags would be used by default if building libwebp +When building libwebp for inclusion in wheels, Pillow now adds the ``-O3`` and +``-DNDEBUG`` CFLAGS. These flags would be used by default if building libwebp without debugging, and using them fixes a significant decrease in speed when a wheel-installed copy of Pillow performs libwebp operations. diff --git a/docs/releasenotes/6.2.2.rst b/docs/releasenotes/6.2.2.rst index a138c7d60..79d4b88aa 100644 --- a/docs/releasenotes/6.2.2.rst +++ b/docs/releasenotes/6.2.2.rst @@ -6,12 +6,13 @@ Security This release addresses several security problems. -CVE-2019-19911 is regarding FPX images. If an image reports that it has a large number -of bands, a large amount of resources will be used when trying to process the +:cve:`CVE-2019-19911` is regarding FPX images. If an image reports that it has a large +number of bands, a large amount of resources will be used when trying to process the image. This is fixed by limiting the number of bands to those usable by Pillow. -Buffer overruns were found when processing an SGI (CVE-2020-5311), PCX (CVE-2020-5312) -or FLI image (CVE-2020-5313). Checks have been added to prevent this. +Buffer overruns were found when processing an SGI (:cve:`CVE-2020-5311`), +PCX (:cve:`CVE-2020-5312`) or FLI image (:cve:`CVE-2020-5313`). Checks have been added +to prevent this. -CVE-2020-5310: Overflow checks have been added when calculating the size of a memory -block to be reallocated in the processing of a TIFF image. +:cve:`CVE-2020-5310`: Overflow checks have been added when calculating the size of a +memory block to be reallocated in the processing of a TIFF image. diff --git a/docs/releasenotes/7.0.0.rst b/docs/releasenotes/7.0.0.rst index e0e764342..80002b0ce 100644 --- a/docs/releasenotes/7.0.0.rst +++ b/docs/releasenotes/7.0.0.rst @@ -66,7 +66,7 @@ See :ref:`concept-filters` to learn the difference. In short, Image.draft() return value ^^^^^^^^^^^^^^^^^^^^^^^^^^ -If the :py:meth:`~PIL.Image.Image.draft` method has no effect, it returns ``None``. +If the :py:meth:`~PIL.Image.Image.draft` method has no effect, it returns :data:`None`. If it does have an effect, then it previously returned the image itself. However, unlike other `chain methods`_, :py:meth:`~PIL.Image.Image.draft` does not return a modified version of the image, but modifies it in-place. So instead, if @@ -85,7 +85,7 @@ Custom unidentified image error ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Pillow will now throw a custom ``UnidentifiedImageError`` when an image cannot be -identified. For backwards compatibility, this will inherit from ``IOError``. +identified. For backwards compatibility, this will inherit from ``OSError``. New argument ``reducing_gap`` for Image.resize() and Image.thumbnail() methods ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -95,7 +95,7 @@ the closer the result to the fair resampling. The smaller ``reducing_gap``, the faster resizing. With ``reducing_gap`` greater or equal to 3.0, the result is indistinguishable from fair resampling. -The default value for :py:meth:`~PIL.Image.Image.resize` is ``None``, +The default value for :py:meth:`~PIL.Image.Image.resize` is :data:`None`, which means that the optimization is turned off by default. The default value for :py:meth:`~PIL.Image.Image.thumbnail` is 2.0, diff --git a/docs/releasenotes/7.1.0.rst b/docs/releasenotes/7.1.0.rst new file mode 100644 index 000000000..fd3627e3c --- /dev/null +++ b/docs/releasenotes/7.1.0.rst @@ -0,0 +1,102 @@ +7.1.0 +----- + +API Changes +=========== + +Allow saving of zero quality JPEG images +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If no quality was specified when saving a JPEG, Pillow internally used a value +of zero to indicate that the default quality should be used. However, this +removed the ability to actually save a JPEG with zero quality. This has now +been resolved. + +.. code-block:: python + + from PIL import Image + im = Image.open("hopper.jpg") + im.save("out.jpg", quality=0) + +API Additions +============= + +New channel operations +^^^^^^^^^^^^^^^^^^^^^^ + +Three new channel operations have been added: :py:meth:`~PIL.ImageChops.soft_light`, +:py:meth:`~PIL.ImageChops.hard_light` and :py:meth:`~PIL.ImageChops.overlay`. + +PILLOW_VERSION constant +^^^^^^^^^^^^^^^^^^^^^^^ + +``PILLOW_VERSION`` has been re-added but is deprecated and will be removed in a future +release. Use ``__version__`` instead. + +It was initially removed in Pillow 7.0.0, but brought back in 7.1.0 to give projects +more time to upgrade. + +Reading JPEG comments +^^^^^^^^^^^^^^^^^^^^^ + +When opening a JPEG image, the comment may now be read into +:py:attr:`~PIL.Image.Image.info`. + +Support for different charset encodings in PcfFontFile +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Previously ``PcfFontFile`` output only bitmap PIL fonts with ISO 8859-1 encoding, even +though the PCF format supports Unicode, making it hard to work with Pillow with bitmap +fonts in languages which use different character sets. + +Now it's possible to set a different charset encoding in ``PcfFontFile``'s class +constructor. By default, it generates a PIL font file with ISO 8859-1 as before. The +generated PIL font file still contains up to 256 characters, but the character set is +different depending on the selected encoding. + +To use such a font with ``ImageDraw.text``, call it with a bytes object with the same +encoding as the font file. + +X11 ImageGrab.grab() +^^^^^^^^^^^^^^^^^^^^ +Support has been added for ``ImageGrab.grab()`` on Linux using the X server +with the XCB library. + +An optional ``xdisplay`` parameter has been added to select the X server, +with the default value of :data:`None` using the default X server. + +Passing a different value on Windows or macOS will force taking a snapshot +using the selected X server; pass an empty string to use the default X server. +XCB support is not included in pre-compiled wheels for Windows and macOS. + +Security +======== + +This release includes security fixes. + +* :cve:`CVE-2020-10177` Fix multiple OOB reads in FLI decoding +* :cve:`CVE-2020-10378` Fix bounds overflow in PCX decoding +* :cve:`CVE-2020-10379` Fix two buffer overflows in TIFF decoding +* :cve:`CVE-2020-10994` Fix bounds overflow in JPEG 2000 decoding +* :cve:`CVE-2020-11538` Fix buffer overflow in SGI-RLE decoding + +Other Changes +============= + +If present, only use alpha channel for bounding box +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +When the :py:meth:`~PIL.Image.Image.getbbox` method calculates the bounding +box, for an RGB image it trims black pixels. Similarly, for an RGBA image it +would trim black transparent pixels. This is now changed so that if an image +has an alpha channel (RGBA, RGBa, PA, LA, La), any transparent pixels are +trimmed. + +Improved APNG support +^^^^^^^^^^^^^^^^^^^^^ + +Added support for reading and writing Animated Portable Network Graphics (APNG) images. +The PNG plugin now supports using the :py:meth:`~PIL.Image.Image.seek` method and the +:py:class:`~PIL.ImageSequence.Iterator` class to read APNG frame sequences. +The PNG plugin also now supports using the ``append_images`` argument to write APNG frame +sequences. See :ref:`apng-sequences` for further details. diff --git a/docs/releasenotes/7.1.1.rst b/docs/releasenotes/7.1.1.rst new file mode 100644 index 000000000..2169e6a05 --- /dev/null +++ b/docs/releasenotes/7.1.1.rst @@ -0,0 +1,25 @@ +7.1.1 +----- + +Fix regression seeking PNG files +================================ + +This fixes a regression introduced in 7.1.0 when adding support for APNG files when calling +``seek`` and ``tell``: + +.. code-block:: pycon + + >>> from PIL import Image + >>> with Image.open("Tests/images/hopper.png") as im: + ... im.seek(0) + ... + Traceback (most recent call last): + File "", line 2, in + File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/PIL/PngImagePlugin.py", line 739, in seek + if not self._seek_check(frame): + File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/PIL/ImageFile.py", line 306, in _seek_check + return self.tell() != frame + File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/PIL/PngImagePlugin.py", line 827, in tell + return self.__frame + AttributeError: 'PngImageFile' object has no attribute '_PngImageFile__frame' + >>> diff --git a/docs/releasenotes/7.1.2.rst b/docs/releasenotes/7.1.2.rst new file mode 100644 index 000000000..b12d84e33 --- /dev/null +++ b/docs/releasenotes/7.1.2.rst @@ -0,0 +1,16 @@ +7.1.2 +----- + +Fix another regression seeking PNG files +======================================== + +This fixes a regression introduced in 7.1.0 when adding support for APNG files. + +When calling ``seek(n)`` on a regular PNG where ``n > 0``, it failed to raise an +``EOFError`` as it should have done, resulting in: + +.. code-block:: pycon + + AttributeError: 'NoneType' object has no attribute 'read' + +Pillow 7.1.2 now raises the correct exception. diff --git a/docs/releasenotes/7.2.0.rst b/docs/releasenotes/7.2.0.rst new file mode 100644 index 000000000..ff1b7c9e7 --- /dev/null +++ b/docs/releasenotes/7.2.0.rst @@ -0,0 +1,58 @@ +7.2.0 +----- + +API Changes +=========== + +Replaced TiffImagePlugin DEBUG with logging +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +``TiffImagePlugin.DEBUG = True`` has been a way to print various debugging +information when interacting with TIFF images. This has now been removed +in favour of Python's ``logging`` module, already used in other places in the +Pillow source code. + +Corrected default offset when writing EXIF data +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Previously, the default ``offset`` argument for +:py:meth:`~PIL.Image.Exif.tobytes` was 0, which did not include the magic +header. It is now 8. + +Moved to ImageFileDirectory_v2 in Image.Exif +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Moved from the legacy :py:class:`PIL.TiffImagePlugin.ImageFileDirectory_v1` to +:py:class:`PIL.TiffImagePlugin.ImageFileDirectory_v2` in +:py:class:`PIL.Image.Exif`. This means that Exif RATIONALs and SIGNED_RATIONALs +are now read as :py:class:`PIL.TiffImagePlugin.IFDRational`, instead of as a +tuple with a numerator and a denominator. + +TIFF BYTE tags format +^^^^^^^^^^^^^^^^^^^^^ + +TIFF BYTE tags were previously read as a tuple containing a bytestring. They +are now read as just a single bytestring. + +Deprecations +^^^^^^^^^^^^ + +Image.show command parameter +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The ``command`` parameter was deprecated and will be removed in a future release. +Use a subclass of :py:class:`PIL.ImageShow.Viewer` instead. + +Image._showxv +~~~~~~~~~~~~~ + +``Image._showxv`` has been deprecated. Use :py:meth:`~PIL.Image.Image.show` +instead. If custom behaviour is required, use :py:meth:`~PIL.ImageShow.register` to add +a custom :py:class:`~PIL.ImageShow.Viewer` class. + +ImageFile.raise_ioerror +~~~~~~~~~~~~~~~~~~~~~~~ + +``IOError`` was merged into ``OSError`` in Python 3.3. So, ``ImageFile.raise_ioerror`` +is now deprecated and will be removed in a future release. Use +``ImageFile.raise_oserror`` instead. diff --git a/docs/releasenotes/8.0.0.rst b/docs/releasenotes/8.0.0.rst new file mode 100644 index 000000000..1bef62e00 --- /dev/null +++ b/docs/releasenotes/8.0.0.rst @@ -0,0 +1,179 @@ +8.0.0 +----- + +Backwards Incompatible Changes +============================== + +Python 3.5 +^^^^^^^^^^ + +Pillow has dropped support for Python 3.5, which reached end-of-life on 2020-09-13. + +PyPy 7.1.x +^^^^^^^^^^ + +Pillow has dropped support for PyPy3 7.1.1. +PyPy3 7.2.0, released on 2019-10-14, is now the minimum compatible version. + +im.offset +^^^^^^^^^ + +``im.offset()`` has been removed, call :py:func:`.ImageChops.offset()` instead. + +Image.fromstring, im.fromstring and im.tostring +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +* ``Image.fromstring()`` has been removed, call :py:func:`.Image.frombytes()` instead. +* ``im.fromstring()`` has been removed, call :py:meth:`~PIL.Image.Image.frombytes()` instead. +* ``im.tostring()`` has been removed, call :py:meth:`~PIL.Image.Image.tobytes()` instead. + +ImageCms.CmsProfile attributes +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Some attributes in :py:class:`PIL.ImageCms.CmsProfile` have been removed: + +======================== =================================================== +Removed Use instead +======================== =================================================== +``color_space`` Padded :py:attr:`~.CmsProfile.xcolor_space` +``pcs`` Padded :py:attr:`~.CmsProfile.connection_space` +``product_copyright`` Unicode :py:attr:`~.CmsProfile.copyright` +``product_desc`` Unicode :py:attr:`~.CmsProfile.profile_description` +``product_description`` Unicode :py:attr:`~.CmsProfile.profile_description` +``product_manufacturer`` Unicode :py:attr:`~.CmsProfile.manufacturer` +``product_model`` Unicode :py:attr:`~.CmsProfile.model` +======================== =================================================== + +API Changes +=========== + +ImageDraw.text: stroke_width +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Fixed issue where passing ``stroke_width`` with a non-zero value +to :py:meth:`.ImageDraw.text` would cause the text to be offset by that amount. + +ImageDraw.text: anchor +^^^^^^^^^^^^^^^^^^^^^^ + +The ``anchor`` parameter of :py:meth:`.ImageDraw.text` has been implemented. + +Use this parameter to change the position of text relative to the +specified ``xy`` point. See :ref:`text-anchors` for details. + +Add MIME type to PsdImagePlugin +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +"image/vnd.adobe.photoshop" is now registered as the +:py:class:`.PsdImagePlugin.PsdImageFile` MIME type. + +API Additions +============= + +Image.open: add formats parameter +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Added a new ``formats`` parameter to :py:func:`.Image.open`: + +* A list or tuple of formats to attempt to load the file in. + This can be used to restrict the set of formats checked. + Pass ``None`` to try all supported formats. You can print the set of + available formats by running ``python -m PIL`` or using + the :py:func:`PIL.features.pilinfo` function. + +ImageOps.autocontrast: add mask parameter +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +:py:func:`.ImageOps.autocontrast` can now take a ``mask`` parameter: + +* Histogram used in contrast operation is computed using pixels within the mask. + If no mask is given the entire image is used for histogram computation. + +ImageOps.autocontrast cutoffs +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Previously, the ``cutoff`` parameter of :py:func:`.ImageOps.autocontrast` could only +be a single number, used as the percent to cut off from the histogram on the low and +high ends. + +Now, it can also be a tuple ``(low, high)``. + +ImageDraw.regular_polygon +^^^^^^^^^^^^^^^^^^^^^^^^^ + +A new method :py:meth:`.ImageDraw.regular_polygon`, draws a regular polygon of ``n_sides``, inscribed in a ``bounding_circle``. + +For example ``draw.regular_polygon(((100, 100), 50), 5)`` +draws a pentagon centered at the point ``(100, 100)`` with a polygon radius of ``50``. + +ImageDraw.text: embedded_color +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The methods :py:meth:`.ImageDraw.text` and :py:meth:`.ImageDraw.multiline_text` +now support fonts with embedded color data. + +To render text with embedded color data, use the parameter ``embedded_color=True``. + +Support for CBDT fonts requires FreeType 2.5 compiled with libpng. +Support for COLR fonts requires FreeType 2.10. +SBIX and SVG fonts are not yet supported. + +ImageDraw.textlength +^^^^^^^^^^^^^^^^^^^^ + +Two new methods :py:meth:`.ImageDraw.textlength` and :py:meth:`.FreeTypeFont.getlength` +were added, returning the exact advance length of text with 1/64 pixel precision. + +These can be used for word-wrapping or rendering text in parts. + +ImageDraw.textbbox +^^^^^^^^^^^^^^^^^^ + +Three new methods :py:meth:`.ImageDraw.textbbox`, :py:meth:`.ImageDraw.multiline_textbbox`, +and :py:meth:`.FreeTypeFont.getbbox` return the bounding box of rendered text. + +These functions accept an ``anchor`` parameter, see :ref:`text-anchors` for details. + +Other Changes +============= + +Improved ellipse-drawing algorithm +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The ellipse-drawing algorithm has been changed from drawing a 360-sided polygon to one +which resembles Bresenham's algorithm for circles. It should be faster and produce +smoother curves, especially for smaller ellipses. + +ImageDraw.text and ImageDraw.multiline_text +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Fixed multiple issues in methods :py:meth:`.ImageDraw.text` and :py:meth:`.ImageDraw.multiline_text` +sometimes causing unexpected text alignment issues. + +The ``align`` parameter of :py:meth:`.ImageDraw.multiline_text` now gives better results in some cases. + +TrueType fonts with embedded bitmaps are now supported. + +Added writing of subIFDs +^^^^^^^^^^^^^^^^^^^^^^^^ + +When saving EXIF data, Pillow is now able to write subIFDs, such as the GPS IFD. This +should happen automatically when saving an image using the EXIF data that it was opened +with, such as in :py:meth:`~PIL.ImageOps.exif_transpose`. + +Previously, the code of the first tag of the subIFD was incorrectly written as the +offset. + +Error for large BMP files +^^^^^^^^^^^^^^^^^^^^^^^^^ + +Previously, if a BMP file was too large, an ``OSError`` would be raised. Now, +``DecompressionBombError`` is used instead, as Pillow already uses for other formats. + +Dark theme for docs +^^^^^^^^^^^^^^^^^^^ + +The https://pillow.readthedocs.io documentation will use a dark theme if the the user has requested the system use one. Uses the ``prefers-color-scheme`` CSS media query. + + + diff --git a/docs/releasenotes/8.0.1.rst b/docs/releasenotes/8.0.1.rst new file mode 100644 index 000000000..3584a5d72 --- /dev/null +++ b/docs/releasenotes/8.0.1.rst @@ -0,0 +1,22 @@ +8.0.1 +----- + +Security +======== + +Update FreeType used in binary wheels to `2.10.4`_ to fix :cve:`CVE-2020-15999`: + + - A heap buffer overflow has been found in the handling of embedded PNG bitmaps, + introduced in FreeType version 2.6. + + If you use option ``FT_CONFIG_OPTION_USE_PNG`` you should upgrade immediately. + +We strongly recommend updating to Pillow 8.0.1 if you are using Pillow 8.0.0, which improved support for bitmap fonts. + +In Pillow 7.2.0 and earlier bitmap fonts were disabled with ``FT_LOAD_NO_BITMAP``, but it is not +clear if this prevents the exploit and we recommend updating to Pillow 8.0.1. + +Pillow 8.0.0 and earlier are potentially vulnerable releases, including the last release +to support Python 2.7, namely Pillow 6.2.2. + +.. _2.10.4: https://sourceforge.net/projects/freetype/files/freetype2/2.10.4/ diff --git a/docs/releasenotes/8.1.0.rst b/docs/releasenotes/8.1.0.rst new file mode 100644 index 000000000..90847af81 --- /dev/null +++ b/docs/releasenotes/8.1.0.rst @@ -0,0 +1,89 @@ +8.1.0 +----- + +Deprecations +============ + +FreeType 2.7 +^^^^^^^^^^^^ + +Support for FreeType 2.7 is deprecated and will be removed in Pillow 9.0.0 (2022-01-02), +when FreeType 2.8 will be the minimum supported. + +We recommend upgrading to at least FreeType `2.10.4`_, which fixed a severe +vulnerability introduced in FreeType 2.6 (:cve:`CVE-2020-15999`). + +.. _2.10.4: https://sourceforge.net/projects/freetype/files/freetype2/2.10.4/ + +Makefile +^^^^^^^^ + +The 'install-venv' target has been deprecated. + +API Additions +============= + +Append images to ICO +^^^^^^^^^^^^^^^^^^^^ + +When saving an ICO image, the file may contain versions of the image at different +sizes. By default, Pillow will scale down the main image to create these copies. + +With this release, a list of images can be provided to the ``append_images`` parameter +when saving, to replace the scaled down versions. This is the same functionality that +already exists for the ICNS format. + +Security +======== + +This release includes security fixes. + +* An out-of-bounds read when saving TIFFs with custom metadata through LibTIFF +* An out-of-bounds read when saving a GIF of 1px width +* :cve:`CVE-2020-35653` Buffer read overrun in PCX decoding + +The PCX image decoder used the reported image stride to calculate the row buffer, +rather than calculating it from the image size. This issue dates back to the PIL fork. +Thanks to Google's `OSS-Fuzz`_ project for finding this. + +* :cve:`CVE-2020-35654` Fix TIFF OOB Write error + +OOB Write in TiffDecode.c when reading corrupt YCbCr files in some LibTIFF versions +(4.1.0/Ubuntu 20.04, but not 4.0.9/Ubuntu 18.04). In some cases LibTIFF's +interpretation of the file is different when reading in RGBA mode, leading to an Out of +bounds write in TiffDecode.c. This potentially affects Pillow versions from 6.0.0 to +8.0.1, depending on the version of LibTIFF. This was reported through `Tidelift`_. + +* :cve:`CVE-2020-35655` Fix for SGI Decode buffer overrun + +4 byte read overflow in SGIRleDecode.c, where the code was not correctly checking the +offsets and length tables. Independently reported through `Tidelift`_ and Google's +`OSS-Fuzz`_. This vulnerability covers Pillow versions 4.3.0->8.0.1. + +.. _Tidelift: https://tidelift.com/subscription/pkg/pypi-pillow?utm_source=pillow&utm_medium=referral&utm_campaign=docs +.. _OSS-Fuzz: https://github.com/google/oss-fuzz + +Dependencies +^^^^^^^^^^^^ + +OpenJPEG in the macOS and Linux wheels has been updated from 2.3.1 to 2.4.0, including +security fixes. + +Other Changes +============= + +Makefile +^^^^^^^^ + +The 'co' target has been removed. + +PyPy wheels +^^^^^^^^^^^ + +Wheels have been added for PyPy 3.7. + +PySide6 +^^^^^^^ + +Support has been added for PySide6. If it is installed, it will be used instead of +PyQt5 or PySide2, since it is based on a newer Qt. diff --git a/docs/releasenotes/index.rst b/docs/releasenotes/index.rst index d329257a9..cd73de814 100644 --- a/docs/releasenotes/index.rst +++ b/docs/releasenotes/index.rst @@ -1,11 +1,26 @@ Release Notes ============= +Pillow is released quarterly on January 2nd, April 1st, July 1st and October 15th. +Patch releases are created if the latest release contains severe bugs, or if security +fixes are put together before a scheduled release. See :ref:`versioning` for more +information. + +Please use the latest version of Pillow. Functionality and security fixes should not be +expected to be backported to earlier versions. + .. note:: Contributors please include release notes as needed or appropriate with your bug fixes, feature additions and tests. .. toctree:: :maxdepth: 2 + 8.1.0 + 8.0.1 + 8.0.0 + 7.2.0 + 7.1.2 + 7.1.1 + 7.1.0 7.0.0 6.2.2 6.2.1 @@ -34,3 +49,4 @@ Release Notes 3.0.0 2.8.0 2.7.0 + versioning diff --git a/docs/releasenotes/template.rst b/docs/releasenotes/template.rst new file mode 100644 index 000000000..bf381114e --- /dev/null +++ b/docs/releasenotes/template.rst @@ -0,0 +1,45 @@ +x.y.z +----- + +Backwards Incompatible Changes +============================== + +TODO +^^^^ + +Deprecations +============ + +TODO +^^^^ + +TODO + +API Changes +=========== + +TODO +^^^^ + +TODO + +API Additions +============= + +TODO +^^^^ + +TODO + +Security +======== + +TODO + +Other Changes +============= + +TODO +^^^^ + +TODO diff --git a/docs/releasenotes/versioning.rst b/docs/releasenotes/versioning.rst new file mode 100644 index 000000000..1653bff3c --- /dev/null +++ b/docs/releasenotes/versioning.rst @@ -0,0 +1,30 @@ +.. _versioning: + +Versioning +========== + +Pillow follows [Semantic Versioning](https://semver.org/): + + Given a version number MAJOR.MINOR.PATCH, increment the: + + 1. MAJOR version when you make incompatible API changes, + 2. MINOR version when you add functionality in a backwards compatible manner, and + 3. PATCH version when you make backwards compatible bug fixes. + +Quarterly releases ("`Main Release `_") +bump at least the MINOR version, as new functionality has likely been added in the +prior three months. + +A quarterly release bumps the MAJOR version when incompatible API changes are +made, such as removing deprecated APIs or dropping an EOL Python version. In practice, +these occur every 12-18 months, guided by +`Python's EOL schedule `_, and +any APIs that have been deprecated for at least a year are removed at the same time. + +PATCH versions ("`Point Release `_" +or "`Embargoed Release `_") +are for security, installation or critical bug fixes. These are less common as it is +preferred to stick to quarterly releases. + +Between quarterly releases, ".dev0" is appended to the "master" branch, indicating that +this is not a formally released copy. diff --git a/docs/resources/anchor_horizontal.svg b/docs/resources/anchor_horizontal.svg new file mode 100644 index 000000000..a0648a10c --- /dev/null +++ b/docs/resources/anchor_horizontal.svg @@ -0,0 +1,467 @@ + + + + + Pillow horizontal text anchors + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + Pillow horizontal text anchors + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + (d) descender + (s) baseline + (a) ascender + (m) middle + (t) top + (b) bottom + (l) left + (r) right + (m) middle + + + Horizontal text + + diff --git a/docs/resources/anchor_vertical.svg b/docs/resources/anchor_vertical.svg new file mode 100644 index 000000000..95da30ffd --- /dev/null +++ b/docs/resources/anchor_vertical.svg @@ -0,0 +1,841 @@ + + + + + Pillow vertical text anchors + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + Pillow vertical text anchors + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + (l)left + (s) baseline + (r)right + (t) top + (m) middle + (b) bottom + (m)middle + (l)left + (s) baseline + (r)right + (t) top + (m) middle + (b) bottom + (m)middle + + + Verticaltext + + diff --git a/docs/resources/css/dark.css b/docs/resources/css/dark.css new file mode 100644 index 000000000..cc213d674 --- /dev/null +++ b/docs/resources/css/dark.css @@ -0,0 +1,2001 @@ +@media (prefers-color-scheme: dark) { + html { + background-color: #181a1b !important; + } + + html, body, input, textarea, select, button { + background-color: #181a1b; + } + + html, body, input, textarea, select, button { + border-color: #736b5e; + color: #e8e6e3; + } + + a { + color: #3391ff; + } + + table { + border-color: #545b5e; + } + + ::placeholder { + color: #b2aba1; + } + + input:-webkit-autofill, + textarea:-webkit-autofill, + select:-webkit-autofill { + background-color: #555b00 !important; + color: #e8e6e3 !important; + } + + ::selection { + background-color: #004daa !important; + color: #e8e6e3 !important; + } + + ::-moz-selection { + background-color: #004daa !important; + color: #e8e6e3 !important; + } + + /* Invert Style */ + .jfk-bubble.gtx-bubble, embed[type="application/pdf"] { + filter: invert(100%) hue-rotate(180deg) contrast(90%) !important; + } + + /* Override Style */ + .vimvixen-hint { + background-color: #7b5300 !important; + border-color: #d8b013 !important; + color: #f3e8c8 !important; + } + + ::placeholder { + opacity: 0.5 !important; + } + + /* Variables Style */ + :root { + --darkreader-neutral-background: #181a1b; + --darkreader-neutral-text: #e8e6e3; + --darkreader-selection-background: #004daa; + --darkreader-selection-text: #e8e6e3; + } + + /* Modified CSS */ + a:hover, + a:active { + outline-color: initial; + } + + abbr[title] { + border-bottom-color: initial; + } + + ins { + background-image: initial; + background-color: rgb(112, 112, 0); + color: rgb(232, 230, 227); + text-decoration-color: initial; + } + + mark { + background-image: initial; + background-color: rgb(204, 204, 0); + color: rgb(232, 230, 227); + } + + ul, + ol, + dl { + list-style-image: none; + } + + li { + list-style-image: initial; + } + + img { + border-color: initial; + } + + fieldset { + border-color: initial; + } + + legend { + border-color: initial; + } + + .chromeframe { + background-image: initial; + background-color: rgb(53, 57, 59); + color: rgb(232, 230, 227); + } + + .ir { + border-color: initial; + background-color: transparent; + } + + .visuallyhidden { + border-color: initial; + } + + .fa-border { + border-color: rgb(53, 57, 59); + } + + .fa-inverse { + color: rgb(232, 230, 227); + } + + .sr-only { + border-color: initial; + } + + .fa::before, + .wy-menu-vertical li span.toctree-expand::before, + .wy-menu-vertical li.on a span.toctree-expand::before, + .wy-menu-vertical li.current > a span.toctree-expand::before, + .rst-content .admonition-title::before, + .rst-content h1 .headerlink::before, + .rst-content h2 .headerlink::before, + .rst-content h3 .headerlink::before, + .rst-content h4 .headerlink::before, + .rst-content h5 .headerlink::before, + .rst-content h6 .headerlink::before, + .rst-content dl dt .headerlink::before, + .rst-content p.caption .headerlink::before, + .rst-content table > caption .headerlink::before, + .rst-content .code-block-caption .headerlink::before, + .rst-content tt.download span:first-child::before, + .rst-content code.download span:first-child::before, + .icon::before, + .wy-dropdown .caret::before, + .wy-inline-validate.wy-inline-validate-success .wy-input-context::before, + .wy-inline-validate.wy-inline-validate-danger .wy-input-context::before, + .wy-inline-validate.wy-inline-validate-warning .wy-input-context::before, + .wy-inline-validate.wy-inline-validate-info .wy-input-context::before { + text-decoration-color: inherit; + } + + a .fa, + a .wy-menu-vertical li span.toctree-expand, + .wy-menu-vertical li a span.toctree-expand, + .wy-menu-vertical li.on a span.toctree-expand, + .wy-menu-vertical li.current > a span.toctree-expand, + a .rst-content .admonition-title, + .rst-content a .admonition-title, + a .rst-content h1 .headerlink, + .rst-content h1 a .headerlink, + a .rst-content h2 .headerlink, + .rst-content h2 a .headerlink, + a .rst-content h3 .headerlink, + .rst-content h3 a .headerlink, + a .rst-content h4 .headerlink, + .rst-content h4 a .headerlink, + a .rst-content h5 .headerlink, + .rst-content h5 a .headerlink, + a .rst-content h6 .headerlink, + .rst-content h6 a .headerlink, + a .rst-content dl dt .headerlink, + .rst-content dl dt a .headerlink, + a .rst-content p.caption .headerlink, + .rst-content p.caption a .headerlink, + a .rst-content table > caption .headerlink, + .rst-content table > caption a .headerlink, + a .rst-content .code-block-caption .headerlink, + .rst-content .code-block-caption a .headerlink, + a .rst-content tt.download span:first-child, + .rst-content tt.download a span:first-child, + a .rst-content code.download span:first-child, + .rst-content code.download a span:first-child, + a .icon { + text-decoration-color: inherit; + } + + .wy-alert, + .rst-content .note, + .rst-content .attention, + .rst-content .caution, + .rst-content .danger, + .rst-content .error, + .rst-content .hint, + .rst-content .important, + .rst-content .tip, + .rst-content .warning, + .rst-content .seealso, + .rst-content .admonition-todo, + .rst-content .admonition { + background-image: initial; + background-color: rgb(32, 35, 36); + } + + .wy-alert-title, + .rst-content .admonition-title { + color: rgb(232, 230, 227); + background-image: initial; + background-color: rgb(29, 91, 131); + } + + .wy-alert.wy-alert-danger, + .rst-content .wy-alert-danger.note, + .rst-content .wy-alert-danger.attention, + .rst-content .wy-alert-danger.caution, + .rst-content .danger, + .rst-content .error, + .rst-content .wy-alert-danger.hint, + .rst-content .wy-alert-danger.important, + .rst-content .wy-alert-danger.tip, + .rst-content .wy-alert-danger.warning, + .rst-content .wy-alert-danger.seealso, + .rst-content .wy-alert-danger.admonition-todo, + .rst-content .wy-alert-danger.admonition { + background-image: initial; + background-color: rgb(52, 12, 8); + } + + .wy-alert.wy-alert-danger .wy-alert-title, + .rst-content .wy-alert-danger.note .wy-alert-title, + .rst-content .wy-alert-danger.attention .wy-alert-title, + .rst-content .wy-alert-danger.caution .wy-alert-title, + .rst-content .danger .wy-alert-title, + .rst-content .error .wy-alert-title, + .rst-content .wy-alert-danger.hint .wy-alert-title, + .rst-content .wy-alert-danger.important .wy-alert-title, + .rst-content .wy-alert-danger.tip .wy-alert-title, + .rst-content .wy-alert-danger.warning .wy-alert-title, + .rst-content .wy-alert-danger.seealso .wy-alert-title, + .rst-content .wy-alert-danger.admonition-todo .wy-alert-title, + .rst-content .wy-alert-danger.admonition .wy-alert-title, + .wy-alert.wy-alert-danger .rst-content .admonition-title, + .rst-content .wy-alert.wy-alert-danger .admonition-title, + .rst-content .wy-alert-danger.note .admonition-title, + .rst-content .wy-alert-danger.attention .admonition-title, + .rst-content .wy-alert-danger.caution .admonition-title, + .rst-content .danger .admonition-title, + .rst-content .error .admonition-title, + .rst-content .wy-alert-danger.hint .admonition-title, + .rst-content .wy-alert-danger.important .admonition-title, + .rst-content .wy-alert-danger.tip .admonition-title, + .rst-content .wy-alert-danger.warning .admonition-title, + .rst-content .wy-alert-danger.seealso .admonition-title, + .rst-content .wy-alert-danger.admonition-todo .admonition-title, + .rst-content .wy-alert-danger.admonition .admonition-title { + background-image: initial; + background-color: rgb(108, 22, 13); + } + + .wy-alert.wy-alert-warning, + .rst-content .wy-alert-warning.note, + .rst-content .attention, + .rst-content .caution, + .rst-content .wy-alert-warning.danger, + .rst-content .wy-alert-warning.error, + .rst-content .wy-alert-warning.hint, + .rst-content .wy-alert-warning.important, + .rst-content .wy-alert-warning.tip, + .rst-content .warning, + .rst-content .wy-alert-warning.seealso, + .rst-content .admonition-todo, + .rst-content .wy-alert-warning.admonition { + background-image: initial; + background-color: rgb(82, 53, 0); + } + + .wy-alert.wy-alert-warning .wy-alert-title, + .rst-content .wy-alert-warning.note .wy-alert-title, + .rst-content .attention .wy-alert-title, + .rst-content .caution .wy-alert-title, + .rst-content .wy-alert-warning.danger .wy-alert-title, + .rst-content .wy-alert-warning.error .wy-alert-title, + .rst-content .wy-alert-warning.hint .wy-alert-title, + .rst-content .wy-alert-warning.important .wy-alert-title, + .rst-content .wy-alert-warning.tip .wy-alert-title, + .rst-content .warning .wy-alert-title, + .rst-content .wy-alert-warning.seealso .wy-alert-title, + .rst-content .admonition-todo .wy-alert-title, + .rst-content .wy-alert-warning.admonition .wy-alert-title, + .wy-alert.wy-alert-warning .rst-content .admonition-title, + .rst-content .wy-alert.wy-alert-warning .admonition-title, + .rst-content .wy-alert-warning.note .admonition-title, + .rst-content .attention .admonition-title, + .rst-content .caution .admonition-title, + .rst-content .wy-alert-warning.danger .admonition-title, + .rst-content .wy-alert-warning.error .admonition-title, + .rst-content .wy-alert-warning.hint .admonition-title, + .rst-content .wy-alert-warning.important .admonition-title, + .rst-content .wy-alert-warning.tip .admonition-title, + .rst-content .warning .admonition-title, + .rst-content .wy-alert-warning.seealso .admonition-title, + .rst-content .admonition-todo .admonition-title, + .rst-content .wy-alert-warning.admonition .admonition-title { + background-image: initial; + background-color: rgb(123, 65, 14); + } + + .wy-alert.wy-alert-info, + .rst-content .note, + .rst-content .wy-alert-info.attention, + .rst-content .wy-alert-info.caution, + .rst-content .wy-alert-info.danger, + .rst-content .wy-alert-info.error, + .rst-content .wy-alert-info.hint, + .rst-content .wy-alert-info.important, + .rst-content .wy-alert-info.tip, + .rst-content .wy-alert-info.warning, + .rst-content .seealso, + .rst-content .wy-alert-info.admonition-todo, + .rst-content .wy-alert-info.admonition { + background-image: initial; + background-color: rgb(32, 35, 36); + } + + .wy-alert.wy-alert-info .wy-alert-title, + .rst-content .note .wy-alert-title, + .rst-content .wy-alert-info.attention .wy-alert-title, + .rst-content .wy-alert-info.caution .wy-alert-title, + .rst-content .wy-alert-info.danger .wy-alert-title, + .rst-content .wy-alert-info.error .wy-alert-title, + .rst-content .wy-alert-info.hint .wy-alert-title, + .rst-content .wy-alert-info.important .wy-alert-title, + .rst-content .wy-alert-info.tip .wy-alert-title, + .rst-content .wy-alert-info.warning .wy-alert-title, + .rst-content .seealso .wy-alert-title, + .rst-content .wy-alert-info.admonition-todo .wy-alert-title, + .rst-content .wy-alert-info.admonition .wy-alert-title, + .wy-alert.wy-alert-info .rst-content .admonition-title, + .rst-content .wy-alert.wy-alert-info .admonition-title, + .rst-content .note .admonition-title, + .rst-content .wy-alert-info.attention .admonition-title, + .rst-content .wy-alert-info.caution .admonition-title, + .rst-content .wy-alert-info.danger .admonition-title, + .rst-content .wy-alert-info.error .admonition-title, + .rst-content .wy-alert-info.hint .admonition-title, + .rst-content .wy-alert-info.important .admonition-title, + .rst-content .wy-alert-info.tip .admonition-title, + .rst-content .wy-alert-info.warning .admonition-title, + .rst-content .seealso .admonition-title, + .rst-content .wy-alert-info.admonition-todo .admonition-title, + .rst-content .wy-alert-info.admonition .admonition-title { + background-image: initial; + background-color: rgb(29, 91, 131); + } + + .wy-alert.wy-alert-success, + .rst-content .wy-alert-success.note, + .rst-content .wy-alert-success.attention, + .rst-content .wy-alert-success.caution, + .rst-content .wy-alert-success.danger, + .rst-content .wy-alert-success.error, + .rst-content .hint, + .rst-content .important, + .rst-content .tip, + .rst-content .wy-alert-success.warning, + .rst-content .wy-alert-success.seealso, + .rst-content .wy-alert-success.admonition-todo, + .rst-content .wy-alert-success.admonition { + background-image: initial; + background-color: rgb(9, 66, 58); + } + + .wy-alert.wy-alert-success .wy-alert-title, + .rst-content .wy-alert-success.note .wy-alert-title, + .rst-content .wy-alert-success.attention .wy-alert-title, + .rst-content .wy-alert-success.caution .wy-alert-title, + .rst-content .wy-alert-success.danger .wy-alert-title, + .rst-content .wy-alert-success.error .wy-alert-title, + .rst-content .hint .wy-alert-title, + .rst-content .important .wy-alert-title, + .rst-content .tip .wy-alert-title, + .rst-content .wy-alert-success.warning .wy-alert-title, + .rst-content .wy-alert-success.seealso .wy-alert-title, + .rst-content .wy-alert-success.admonition-todo .wy-alert-title, + .rst-content .wy-alert-success.admonition .wy-alert-title, + .wy-alert.wy-alert-success .rst-content .admonition-title, + .rst-content .wy-alert.wy-alert-success .admonition-title, + .rst-content .wy-alert-success.note .admonition-title, + .rst-content .wy-alert-success.attention .admonition-title, + .rst-content .wy-alert-success.caution .admonition-title, + .rst-content .wy-alert-success.danger .admonition-title, + .rst-content .wy-alert-success.error .admonition-title, + .rst-content .hint .admonition-title, + .rst-content .important .admonition-title, + .rst-content .tip .admonition-title, + .rst-content .wy-alert-success.warning .admonition-title, + .rst-content .wy-alert-success.seealso .admonition-title, + .rst-content .wy-alert-success.admonition-todo .admonition-title, + .rst-content .wy-alert-success.admonition .admonition-title { + background-image: initial; + background-color: rgb(21, 150, 125); + } + + .wy-alert.wy-alert-neutral, + .rst-content .wy-alert-neutral.note, + .rst-content .wy-alert-neutral.attention, + .rst-content .wy-alert-neutral.caution, + .rst-content .wy-alert-neutral.danger, + .rst-content .wy-alert-neutral.error, + .rst-content .wy-alert-neutral.hint, + .rst-content .wy-alert-neutral.important, + .rst-content .wy-alert-neutral.tip, + .rst-content .wy-alert-neutral.warning, + .rst-content .wy-alert-neutral.seealso, + .rst-content .wy-alert-neutral.admonition-todo, + .rst-content .wy-alert-neutral.admonition { + background-image: initial; + background-color: rgb(27, 36, 36); + } + + .wy-alert.wy-alert-neutral .wy-alert-title, + .rst-content .wy-alert-neutral.note .wy-alert-title, + .rst-content .wy-alert-neutral.attention .wy-alert-title, + .rst-content .wy-alert-neutral.caution .wy-alert-title, + .rst-content .wy-alert-neutral.danger .wy-alert-title, + .rst-content .wy-alert-neutral.error .wy-alert-title, + .rst-content .wy-alert-neutral.hint .wy-alert-title, + .rst-content .wy-alert-neutral.important .wy-alert-title, + .rst-content .wy-alert-neutral.tip .wy-alert-title, + .rst-content .wy-alert-neutral.warning .wy-alert-title, + .rst-content .wy-alert-neutral.seealso .wy-alert-title, + .rst-content .wy-alert-neutral.admonition-todo .wy-alert-title, + .rst-content .wy-alert-neutral.admonition .wy-alert-title, + .wy-alert.wy-alert-neutral .rst-content .admonition-title, + .rst-content .wy-alert.wy-alert-neutral .admonition-title, + .rst-content .wy-alert-neutral.note .admonition-title, + .rst-content .wy-alert-neutral.attention .admonition-title, + .rst-content .wy-alert-neutral.caution .admonition-title, + .rst-content .wy-alert-neutral.danger .admonition-title, + .rst-content .wy-alert-neutral.error .admonition-title, + .rst-content .wy-alert-neutral.hint .admonition-title, + .rst-content .wy-alert-neutral.important .admonition-title, + .rst-content .wy-alert-neutral.tip .admonition-title, + .rst-content .wy-alert-neutral.warning .admonition-title, + .rst-content .wy-alert-neutral.seealso .admonition-title, + .rst-content .wy-alert-neutral.admonition-todo .admonition-title, + .rst-content .wy-alert-neutral.admonition .admonition-title { + color: rgb(192, 186, 178); + background-image: initial; + background-color: rgb(40, 43, 45); + } + + .wy-alert.wy-alert-neutral a, + .rst-content .wy-alert-neutral.note a, + .rst-content .wy-alert-neutral.attention a, + .rst-content .wy-alert-neutral.caution a, + .rst-content .wy-alert-neutral.danger a, + .rst-content .wy-alert-neutral.error a, + .rst-content .wy-alert-neutral.hint a, + .rst-content .wy-alert-neutral.important a, + .rst-content .wy-alert-neutral.tip a, + .rst-content .wy-alert-neutral.warning a, + .rst-content .wy-alert-neutral.seealso a, + .rst-content .wy-alert-neutral.admonition-todo a, + .rst-content .wy-alert-neutral.admonition a { + color: rgb(84, 164, 217); + } + + .wy-tray-container li { + background-image: initial; + background-color: transparent; + color: rgb(232, 230, 227); + box-shadow: rgba(0, 0, 0, 0.1) 0px 5px 5px 0px; + } + + .wy-tray-container li.wy-tray-item-success { + background-image: initial; + background-color: rgb(31, 139, 77); + } + + .wy-tray-container li.wy-tray-item-info { + background-image: initial; + background-color: rgb(33, 102, 148); + } + + .wy-tray-container li.wy-tray-item-warning { + background-image: initial; + background-color: rgb(178, 94, 20); + } + + .wy-tray-container li.wy-tray-item-danger { + background-image: initial; + background-color: rgb(162, 33, 20); + } + + .btn { + color: rgb(232, 230, 227); + border-color: rgba(140, 130, 115, 0.1); + background-color: rgb(31, 139, 77); + text-decoration-color: initial; + box-shadow: rgba(24, 26, 27, 0.5) 0px 1px 2px -1px inset, + rgba(0, 0, 0, 0.1) 0px -2px 0px 0px inset; + } + + .btn-hover { + background-image: initial; + background-color: rgb(37, 114, 165); + color: rgb(232, 230, 227); + } + + .btn:hover { + background-image: initial; + background-color: rgb(35, 156, 86); + color: rgb(232, 230, 227); + } + + .btn:focus { + background-image: initial; + background-color: rgb(35, 156, 86); + outline-color: initial; + } + + .btn:active { + box-shadow: rgba(0, 0, 0, 0.05) 0px -1px 0px 0px inset, + rgba(0, 0, 0, 0.1) 0px 2px 0px 0px inset; + } + + .btn:visited { + color: rgb(232, 230, 227); + } + + .btn:disabled { + background-image: none; + box-shadow: none; + } + + .btn-disabled { + background-image: none; + box-shadow: none; + } + + .btn-disabled:hover, + .btn-disabled:focus, + .btn-disabled:active { + background-image: none; + box-shadow: none; + } + + .btn-info { + background-color: rgb(33, 102, 148) !important; + } + + .btn-info:hover { + background-color: rgb(37, 114, 165) !important; + } + + .btn-neutral { + background-color: rgb(27, 36, 36) !important; + color: rgb(192, 186, 178) !important; + } + + .btn-neutral:hover { + color: rgb(192, 186, 178); + background-color: rgb(34, 44, 44) !important; + } + + .btn-neutral:visited { + color: rgb(192, 186, 178) !important; + } + + .btn-success { + background-color: rgb(31, 139, 77) !important; + } + + .btn-success:hover { + background-color: rgb(27, 122, 68) !important; + } + + .btn-danger { + background-color: rgb(162, 33, 20) !important; + } + + .btn-danger:hover { + background-color: rgb(149, 30, 18) !important; + } + + .btn-warning { + background-color: rgb(178, 94, 20) !important; + } + + .btn-warning:hover { + background-color: rgb(165, 87, 18) !important; + } + + .btn-invert { + background-color: rgb(26, 28, 29); + } + + .btn-invert:hover { + background-color: rgb(35, 38, 40) !important; + } + + .btn-link { + color: rgb(84, 164, 217); + box-shadow: none; + background-color: transparent !important; + border-color: transparent !important; + } + + .btn-link:hover { + box-shadow: none; + background-color: transparent !important; + color: rgb(79, 162, 216) !important; + } + + .btn-link:active { + box-shadow: none; + background-color: transparent !important; + color: rgb(79, 162, 216) !important; + } + + .btn-link:visited { + color: rgb(164, 103, 188); + } + + .wy-dropdown-menu { + background-image: initial; + background-color: rgb(26, 28, 29); + border-color: rgb(60, 65, 67); + box-shadow: rgba(0, 0, 0, 0.1) 0px 2px 2px 0px; + } + + .wy-dropdown-menu > dd > a { + color: rgb(192, 186, 178); + } + + .wy-dropdown-menu > dd > a:hover { + background-image: initial; + background-color: rgb(33, 102, 148); + color: rgb(232, 230, 227); + } + + .wy-dropdown-menu > dd.divider { + border-top-color: rgb(60, 65, 67); + } + + .wy-dropdown-menu > dd.call-to-action { + background-image: initial; + background-color: rgb(40, 43, 45); + } + + .wy-dropdown-menu > dd.call-to-action:hover { + background-image: initial; + background-color: rgb(40, 43, 45); + } + + .wy-dropdown-menu > dd.call-to-action .btn { + color: rgb(232, 230, 227); + } + + .wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu { + background-image: initial; + background-color: rgb(26, 28, 29); + } + + .wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a:hover { + background-image: initial; + background-color: rgb(33, 102, 148); + color: rgb(232, 230, 227); + } + + .wy-dropdown-arrow::before { + border-bottom-color: rgb(51, 55, 57); + border-left-color: transparent; + border-right-color: transparent; + } + + fieldset { + border-color: initial; + } + + legend { + border-color: initial; + } + + label { + color: rgb(200, 195, 188); + } + + .wy-control-group.wy-control-group-required > label::after { + color: rgb(233, 88, 73); + } + + .wy-form-message-inline { + color: rgb(168, 160, 149); + } + + .wy-form-message { + color: rgb(168, 160, 149); + } + + input[type="text"], input[type="password"], input[type="email"], input[type="url"], input[type="date"], input[type="month"], input[type="time"], input[type="datetime"], input[type="datetime-local"], input[type="week"], input[type="number"], input[type="search"], input[type="tel"], input[type="color"] { + border-color: rgb(62, 68, 70); + box-shadow: rgb(43, 47, 49) 0px 1px 3px inset; + } + + input[type="text"]:focus, input[type="password"]:focus, input[type="email"]:focus, input[type="url"]:focus, input[type="date"]:focus, input[type="month"]:focus, input[type="time"]:focus, input[type="datetime"]:focus, input[type="datetime-local"]:focus, input[type="week"]:focus, input[type="number"]:focus, input[type="search"]:focus, input[type="tel"]:focus, input[type="color"]:focus { + outline-color: initial; + border-color: rgb(123, 114, 101); + } + + input.no-focus:focus { + border-color: rgb(62, 68, 70) !important; + } + + input[type="file"]:focus, input[type="radio"]:focus, input[type="checkbox"]:focus { + outline-color: rgb(13, 113, 167); + } + + input[type="text"][disabled], input[type="password"][disabled], input[type="email"][disabled], input[type="url"][disabled], input[type="date"][disabled], input[type="month"][disabled], input[type="time"][disabled], input[type="datetime"][disabled], input[type="datetime-local"][disabled], input[type="week"][disabled], input[type="number"][disabled], input[type="search"][disabled], input[type="tel"][disabled], input[type="color"][disabled] { + background-color: rgb(27, 29, 30); + } + + input:focus:invalid, + textarea:focus:invalid, + select:focus:invalid { + color: rgb(233, 88, 73); + border-color: rgb(149, 31, 18); + } + + input:focus:invalid:focus, + textarea:focus:invalid:focus, + select:focus:invalid:focus { + border-color: rgb(149, 31, 18); + } + + input[type="file"]:focus:invalid:focus, input[type="radio"]:focus:invalid:focus, input[type="checkbox"]:focus:invalid:focus { + outline-color: rgb(149, 31, 18); + } + + select, + textarea { + border-color: rgb(62, 68, 70); + box-shadow: rgb(43, 47, 49) 0px 1px 3px inset; + } + + select { + border-color: rgb(62, 68, 70); + background-color: rgb(24, 26, 27); + } + + select:focus, + textarea:focus { + outline-color: initial; + } + + select[disabled], + textarea[disabled], + input[readonly], + select[readonly], + textarea[readonly] { + background-color: rgb(27, 29, 30); + } + + .wy-checkbox, + .wy-radio { + color: rgb(192, 186, 178); + } + + .wy-input-prefix .wy-input-context, + .wy-input-suffix .wy-input-context { + background-color: rgb(27, 36, 36); + border-color: rgb(62, 68, 70); + color: rgb(168, 160, 149); + } + + .wy-input-suffix .wy-input-context { + border-left-color: initial; + } + + .wy-input-prefix .wy-input-context { + border-right-color: initial; + } + + .wy-switch::before { + background-image: initial; + background-color: rgb(53, 57, 59); + } + + .wy-switch::after { + background-image: initial; + background-color: rgb(82, 88, 92); + } + + .wy-switch span { + color: rgb(200, 195, 188); + } + + .wy-switch.active::before { + background-image: initial; + background-color: rgb(24, 106, 58); + } + + .wy-switch.active::after { + background-image: initial; + background-color: rgb(31, 139, 77); + } + + .wy-control-group.wy-control-group-error .wy-form-message, + .wy-control-group.wy-control-group-error > label { + color: rgb(233, 88, 73); + } + + .wy-control-group.wy-control-group-error input[type="text"], .wy-control-group.wy-control-group-error input[type="password"], .wy-control-group.wy-control-group-error input[type="email"], .wy-control-group.wy-control-group-error input[type="url"], .wy-control-group.wy-control-group-error input[type="date"], .wy-control-group.wy-control-group-error input[type="month"], .wy-control-group.wy-control-group-error input[type="time"], .wy-control-group.wy-control-group-error input[type="datetime"], .wy-control-group.wy-control-group-error input[type="datetime-local"], .wy-control-group.wy-control-group-error input[type="week"], .wy-control-group.wy-control-group-error input[type="number"], .wy-control-group.wy-control-group-error input[type="search"], .wy-control-group.wy-control-group-error input[type="tel"], .wy-control-group.wy-control-group-error input[type="color"] { + border-color: rgb(149, 31, 18); + } + + .wy-control-group.wy-control-group-error textarea { + border-color: rgb(149, 31, 18); + } + + .wy-inline-validate.wy-inline-validate-success .wy-input-context { + color: rgb(92, 218, 145); + } + + .wy-inline-validate.wy-inline-validate-danger .wy-input-context { + color: rgb(233, 88, 73); + } + + .wy-inline-validate.wy-inline-validate-warning .wy-input-context { + color: rgb(232, 138, 54); + } + + .wy-inline-validate.wy-inline-validate-info .wy-input-context { + color: rgb(84, 164, 217); + } + + .wy-table caption, + .rst-content table.docutils caption, + .rst-content table.field-list caption { + color: rgb(232, 230, 227); + } + + .wy-table thead, + .rst-content table.docutils thead, + .rst-content table.field-list thead { + color: rgb(232, 230, 227); + } + + .wy-table thead th, + .rst-content table.docutils thead th, + .rst-content table.field-list thead th { + border-bottom-color: rgb(56, 61, 63); + } + + .wy-table td, + .rst-content table.docutils td, + .rst-content table.field-list td { + background-color: transparent; + } + + .wy-table-secondary { + color: rgb(152, 143, 129); + } + + .wy-table-tertiary { + color: rgb(152, 143, 129); + } + + .wy-table-odd td, + .wy-table-striped tr:nth-child(2n-1) td, + .rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td { + background-color: rgb(27, 36, 36); + } + + .wy-table-backed { + background-color: rgb(27, 36, 36); + } + + .wy-table-bordered-all, + .rst-content table.docutils { + border-color: rgb(56, 61, 63); + } + + .wy-table-bordered-all td, + .rst-content table.docutils td { + border-bottom-color: rgb(56, 61, 63); + border-left-color: rgb(56, 61, 63); + } + + .wy-table-bordered { + border-color: rgb(56, 61, 63); + } + + .wy-table-bordered-rows td { + border-bottom-color: rgb(56, 61, 63); + } + + .wy-table-horizontal td, + .wy-table-horizontal th { + border-bottom-color: rgb(56, 61, 63); + } + + a { + color: rgb(84, 164, 217); + text-decoration-color: initial; + } + + a:hover { + color: rgb(68, 156, 214); + } + + a:visited { + color: rgb(164, 103, 188); + } + + body { + color: rgb(192, 186, 178); + background-image: initial; + background-color: rgb(33, 35, 37); + } + + .wy-text-strike { + text-decoration-color: initial; + } + + .wy-text-warning { + color: rgb(232, 138, 54) !important; + } + + a.wy-text-warning:hover { + color: rgb(236, 157, 87) !important; + } + + .wy-text-info { + color: rgb(84, 164, 217) !important; + } + + a.wy-text-info:hover { + color: rgb(79, 162, 216) !important; + } + + .wy-text-success { + color: rgb(92, 218, 145) !important; + } + + a.wy-text-success:hover { + color: rgb(73, 214, 133) !important; + } + + .wy-text-danger { + color: rgb(233, 88, 73) !important; + } + + a.wy-text-danger:hover { + color: rgb(237, 118, 104) !important; + } + + .wy-text-neutral { + color: rgb(192, 186, 178) !important; + } + + a.wy-text-neutral:hover { + color: rgb(176, 169, 159) !important; + } + + hr { + border-right-color: initial; + border-bottom-color: initial; + border-left-color: initial; + border-top-color: rgb(56, 61, 63); + } + + code, + .rst-content tt, + .rst-content code { + background-image: initial; + background-color: rgb(24, 26, 27); + border-color: rgb(56, 61, 63); + color: rgb(233, 88, 73); + } + + .wy-plain-list-disc, + .rst-content .section ul, + .rst-content .toctree-wrapper ul, + article ul { + list-style-image: initial; + } + + .wy-plain-list-disc li, + .rst-content .section ul li, + .rst-content .toctree-wrapper ul li, + article ul li { + list-style-image: initial; + } + + .wy-plain-list-disc li li, + .rst-content .section ul li li, + .rst-content .toctree-wrapper ul li li, + article ul li li { + list-style-image: initial; + } + + .wy-plain-list-disc li li li, + .rst-content .section ul li li li, + .rst-content .toctree-wrapper ul li li li, + article ul li li li { + list-style-image: initial; + } + + .wy-plain-list-disc li ol li, + .rst-content .section ul li ol li, + .rst-content .toctree-wrapper ul li ol li, + article ul li ol li { + list-style-image: initial; + } + + .wy-plain-list-decimal, + .rst-content .section ol, + .rst-content ol.arabic, + article ol { + list-style-image: initial; + } + + .wy-plain-list-decimal li, + .rst-content .section ol li, + .rst-content ol.arabic li, + article ol li { + list-style-image: initial; + } + + .wy-plain-list-decimal li ul li, + .rst-content .section ol li ul li, + .rst-content ol.arabic li ul li, + article ol li ul li { + list-style-image: initial; + } + + .wy-breadcrumbs li code, + .wy-breadcrumbs li .rst-content tt, + .rst-content .wy-breadcrumbs li tt { + border-color: initial; + background-image: none; + background-color: initial; + } + + .wy-breadcrumbs li code.literal, + .wy-breadcrumbs li .rst-content tt.literal, + .rst-content .wy-breadcrumbs li tt.literal { + color: rgb(192, 186, 178); + } + + .wy-breadcrumbs-extra { + color: rgb(184, 178, 169); + } + + .wy-menu a:hover { + text-decoration-color: initial; + } + + .wy-menu-horiz li:hover { + background-image: initial; + background-color: rgba(24, 26, 27, 0.1); + } + + .wy-menu-horiz li.divide-left { + border-left-color: rgb(119, 110, 98); + } + + .wy-menu-horiz li.divide-right { + border-right-color: rgb(119, 110, 98); + } + + .wy-menu-vertical header, + .wy-menu-vertical p.caption { + color: rgb(99, 161, 201); + } + + .wy-menu-vertical li.divide-top { + border-top-color: rgb(119, 110, 98); + } + + .wy-menu-vertical li.divide-bottom { + border-bottom-color: rgb(119, 110, 98); + } + + .wy-menu-vertical li.current { + background-image: initial; + background-color: rgb(40, 43, 45); + } + + .wy-menu-vertical li.current a { + color: rgb(152, 143, 129); + border-right-color: rgb(63, 69, 71); + } + + .wy-menu-vertical li.current a:hover { + background-image: initial; + background-color: rgb(47, 51, 53); + } + + .wy-menu-vertical li code, + .wy-menu-vertical li .rst-content tt, + .rst-content .wy-menu-vertical li tt { + border-color: initial; + background-image: inherit; + background-color: inherit; + color: inherit; + } + + .wy-menu-vertical li span.toctree-expand { + color: rgb(183, 177, 168); + } + + .wy-menu-vertical li.on a, + .wy-menu-vertical li.current > a { + color: rgb(192, 186, 178); + background-image: initial; + background-color: rgb(26, 28, 29); + border-color: initial; + } + + .wy-menu-vertical li.on a:hover, + .wy-menu-vertical li.current > a:hover { + background-image: initial; + background-color: rgb(26, 28, 29); + } + + .wy-menu-vertical li.on a:hover span.toctree-expand, + .wy-menu-vertical li.current > a:hover span.toctree-expand { + color: rgb(152, 143, 129); + } + + .wy-menu-vertical li.on a span.toctree-expand, + .wy-menu-vertical li.current > a span.toctree-expand { + color: rgb(200, 195, 188); + } + + .wy-menu-vertical li.toctree-l1.current > a { + border-bottom-color: rgb(63, 69, 71); + border-top-color: rgb(63, 69, 71); + } + + .wy-menu-vertical li.toctree-l2 a, + .wy-menu-vertical li.toctree-l3 a, + .wy-menu-vertical li.toctree-l4 a { + color: rgb(192, 186, 178); + } + + .wy-menu-vertical li.toctree-l2.current > a { + background-image: initial; + background-color: rgb(54, 59, 61); + } + + .wy-menu-vertical li.toctree-l2.current li.toctree-l3 > a { + background-image: initial; + background-color: rgb(54, 59, 61); + } + + .wy-menu-vertical li.toctree-l2 a:hover span.toctree-expand { + color: rgb(152, 143, 129); + } + + .wy-menu-vertical li.toctree-l2 span.toctree-expand { + color: rgb(174, 167, 156); + } + + .wy-menu-vertical li.toctree-l3.current > a { + background-image: initial; + background-color: rgb(61, 66, 69); + } + + .wy-menu-vertical li.toctree-l3.current li.toctree-l4 > a { + background-image: initial; + background-color: rgb(61, 66, 69); + } + + .wy-menu-vertical li.toctree-l3 a:hover span.toctree-expand { + color: rgb(152, 143, 129); + } + + .wy-menu-vertical li.toctree-l3 span.toctree-expand { + color: rgb(166, 158, 146); + } + + .wy-menu-vertical li.toctree-l2.current a, + .wy-menu-vertical li.toctree-l3.current a { + background-color: #363636; + } + + .wy-menu-vertical li ul li a { + color: rgb(208, 204, 198); + } + + .wy-menu-vertical a { + color: rgb(208, 204, 198); + } + + .wy-menu-vertical a:hover { + background-color: rgb(57, 62, 64); + } + + .wy-menu-vertical a:hover span.toctree-expand { + color: rgb(208, 204, 198); + } + + .wy-menu-vertical a:active { + background-color: rgb(33, 102, 148); + color: rgb(232, 230, 227); + } + + .wy-menu-vertical a:active span.toctree-expand { + color: rgb(232, 230, 227); + } + + .wy-side-nav-search { + background-color: rgb(33, 102, 148); + color: rgb(230, 228, 225); + } + + .wy-side-nav-search input[type="text"] { + border-color: rgb(35, 111, 160); + } + + .wy-side-nav-search img { + background-color: rgb(33, 102, 148); + } + + .wy-side-nav-search > a, + .wy-side-nav-search .wy-dropdown > a { + color: rgb(230, 228, 225); + } + + .wy-side-nav-search > a:hover, + .wy-side-nav-search .wy-dropdown > a:hover { + background-image: initial; + background-color: rgba(24, 26, 27, 0.1); + } + + .wy-side-nav-search > a img.logo, + .wy-side-nav-search .wy-dropdown > a img.logo { + background-image: initial; + background-color: transparent; + } + + .wy-side-nav-search > div.version { + color: rgba(232, 230, 227, 0.3); + } + + .wy-nav .wy-menu-vertical header { + color: rgb(84, 164, 217); + } + + .wy-nav .wy-menu-vertical a { + color: rgb(184, 178, 169); + } + + .wy-nav .wy-menu-vertical a:hover { + background-color: rgb(33, 102, 148); + color: rgb(232, 230, 227); + } + + .wy-body-for-nav { + background-image: initial; + background-color: rgb(26, 28, 29); + } + + .wy-nav-side { + color: rgb(169, 161, 150); + background-image: initial; + background-color: rgb(38, 41, 43); + } + + .wy-nav-top { + background-image: initial; + background-color: rgb(33, 102, 148); + color: rgb(232, 230, 227); + } + + .wy-nav-top a { + color: rgb(232, 230, 227); + } + + .wy-nav-top img { + background-color: rgb(33, 102, 148); + } + + .wy-nav-content-wrap { + background-image: initial; + background-color: rgb(26, 28, 29); + } + + .wy-body-mask { + background-image: initial; + background-color: rgba(0, 0, 0, 0.2); + } + + footer { + color: rgb(152, 143, 129); + } + + footer span.commit code, + footer span.commit .rst-content tt, + .rst-content footer span.commit tt { + background-image: none; + background-color: initial; + border-color: initial; + color: rgb(152, 143, 129); + } + + #search-results .search li { + border-bottom-color: rgb(56, 61, 63); + } + + #search-results .search li:first-child { + border-top-color: rgb(56, 61, 63); + } + + #search-results .context { + color: rgb(152, 143, 129); + } + + .wy-body-for-nav { + background-image: initial; + background-color: rgb(26, 28, 29); + } + + @media screen and (min-width: 1100px) { + .wy-nav-content-wrap { + background-image: initial; + background-color: rgba(0, 0, 0, 0.05); + } + + .wy-nav-content { + background-image: initial; + background-color: rgb(26, 28, 29); + } + } + .rst-versions { + color: rgb(230, 228, 225); + background-image: initial; + background-color: rgb(23, 24, 25); + } + + .rst-versions a { + color: rgb(84, 164, 217); + text-decoration-color: initial; + } + + .rst-versions .rst-current-version { + background-color: rgb(29, 31, 32); + color: rgb(92, 218, 145); + } + + .rst-versions .rst-current-version .fa, + .rst-versions .rst-current-version .wy-menu-vertical li span.toctree-expand, + .wy-menu-vertical li .rst-versions .rst-current-version span.toctree-expand, + .rst-versions .rst-current-version .rst-content .admonition-title, + .rst-content .rst-versions .rst-current-version .admonition-title, + .rst-versions .rst-current-version .rst-content h1 .headerlink, + .rst-content h1 .rst-versions .rst-current-version .headerlink, + .rst-versions .rst-current-version .rst-content h2 .headerlink, + .rst-content h2 .rst-versions .rst-current-version .headerlink, + .rst-versions .rst-current-version .rst-content h3 .headerlink, + .rst-content h3 .rst-versions .rst-current-version .headerlink, + .rst-versions .rst-current-version .rst-content h4 .headerlink, + .rst-content h4 .rst-versions .rst-current-version .headerlink, + .rst-versions .rst-current-version .rst-content h5 .headerlink, + .rst-content h5 .rst-versions .rst-current-version .headerlink, + .rst-versions .rst-current-version .rst-content h6 .headerlink, + .rst-content h6 .rst-versions .rst-current-version .headerlink, + .rst-versions .rst-current-version .rst-content dl dt .headerlink, + .rst-content dl dt .rst-versions .rst-current-version .headerlink, + .rst-versions .rst-current-version .rst-content p.caption .headerlink, + .rst-content p.caption .rst-versions .rst-current-version .headerlink, + .rst-versions .rst-current-version .rst-content table > caption .headerlink, + .rst-content table > caption .rst-versions .rst-current-version .headerlink, + .rst-versions .rst-current-version .rst-content .code-block-caption .headerlink, + .rst-content .code-block-caption .rst-versions .rst-current-version .headerlink, + .rst-versions .rst-current-version .rst-content tt.download span:first-child, + .rst-content tt.download .rst-versions .rst-current-version span:first-child, + .rst-versions .rst-current-version .rst-content code.download span:first-child, + .rst-content code.download .rst-versions .rst-current-version span:first-child, + .rst-versions .rst-current-version .icon { + color: rgb(230, 228, 225); + } + + .rst-versions .rst-current-version.rst-out-of-date { + background-color: rgb(162, 33, 20); + color: rgb(232, 230, 227); + } + + .rst-versions .rst-current-version.rst-active-old-version { + background-color: rgb(192, 156, 11); + color: rgb(232, 230, 227); + } + + .rst-versions .rst-other-versions { + color: rgb(152, 143, 129); + } + + .rst-versions .rst-other-versions hr { + border-right-color: initial; + border-bottom-color: initial; + border-left-color: initial; + border-top-color: rgb(119, 111, 98); + } + + .rst-versions .rst-other-versions dd a { + color: rgb(230, 228, 225); + } + + .rst-versions.rst-badge { + border-color: initial; + } + + .rst-content abbr[title] { + text-decoration-color: initial; + } + + .rst-content.style-external-links a.reference.external::after { + color: rgb(184, 178, 169); + } + + .rst-content pre.literal-block, .rst-content div[class^="highlight"] { + border-color: rgb(56, 61, 63); + } + + .rst-content pre.literal-block div[class^="highlight"], .rst-content div[class^="highlight"] div[class^="highlight"] { + border-color: initial; + } + + .rst-content .linenodiv pre { + border-right-color: rgb(54, 59, 61); + } + + .rst-content .admonition table { + border-color: rgba(140, 130, 115, 0.1); + } + + .rst-content .admonition table td, + .rst-content .admonition table th { + background-image: initial !important; + background-color: transparent !important; + border-color: rgba(140, 130, 115, 0.1) !important; + } + + .rst-content .section ol.loweralpha, + .rst-content .section ol.loweralpha li { + list-style-image: initial; + } + + .rst-content .section ol.upperalpha, + .rst-content .section ol.upperalpha li { + list-style-image: initial; + } + + .rst-content .toc-backref { + color: rgb(192, 186, 178); + } + + .rst-content .sidebar { + background-image: initial; + background-color: rgb(27, 36, 36); + border-color: rgb(56, 61, 63); + } + + .rst-content .sidebar .sidebar-title { + background-image: initial; + background-color: rgb(40, 43, 45); + } + + .rst-content .highlighted { + background-image: initial; + background-color: rgb(192, 156, 11); + } + + .rst-content table.docutils.citation, + .rst-content table.docutils.footnote { + background-image: none; + background-color: initial; + border-color: initial; + color: rgb(152, 143, 129); + } + + .rst-content table.docutils.citation td, + .rst-content table.docutils.citation tr, + .rst-content table.docutils.footnote td, + .rst-content table.docutils.footnote tr { + border-color: initial; + background-color: transparent !important; + } + + .rst-content table.docutils.citation tt, + .rst-content table.docutils.citation code, + .rst-content table.docutils.footnote tt, + .rst-content table.docutils.footnote code { + color: rgb(178, 172, 162); + } + + .rst-content table.docutils th { + border-color: rgb(56, 61, 63); + } + + .rst-content table.field-list { + border-color: initial; + } + + .rst-content table.field-list td { + border-color: initial; + } + + .rst-content tt, + .rst-content tt, + .rst-content code { + color: rgb(232, 230, 227); + } + + .rst-content tt.literal, + .rst-content tt.literal, + .rst-content code.literal { + color: rgb(233, 88, 73); + } + + .rst-content tt.xref, + a .rst-content tt, + .rst-content tt.xref, + .rst-content code.xref, + a .rst-content tt, + a .rst-content code { + color: rgb(192, 186, 178); + } + + .rst-content a tt, + .rst-content a tt, + .rst-content a code { + color: rgb(84, 164, 217); + } + + .rst-content dl:not(.docutils) dt { + background-image: initial; + background-color: rgb(32, 35, 36); + color: rgb(84, 164, 217); + border-top-color: rgb(28, 89, 128); + } + + .rst-content dl:not(.docutils) dt::before { + color: rgb(109, 178, 223); + } + + .rst-content dl:not(.docutils) dt .headerlink { + color: rgb(192, 186, 178); + } + + .rst-content dl:not(.docutils) dl dt { + border-top-color: initial; + border-right-color: initial; + border-bottom-color: initial; + border-left-color: rgb(62, 68, 70); + background-image: initial; + background-color: rgb(32, 35, 37); + color: rgb(178, 172, 162); + } + + .rst-content dl:not(.docutils) dl dt .headerlink { + color: rgb(192, 186, 178); + } + + .rst-content dl:not(.docutils) tt.descname, + .rst-content dl:not(.docutils) tt.descclassname, + .rst-content dl:not(.docutils) tt.descname, + .rst-content dl:not(.docutils) code.descname, + .rst-content dl:not(.docutils) tt.descclassname, + .rst-content dl:not(.docutils) code.descclassname { + background-color: transparent; + border-color: initial; + } + + .rst-content dl:not(.docutils) .optional { + color: rgb(232, 230, 227); + } + + .rst-content .viewcode-link, + .rst-content .viewcode-back { + color: rgb(92, 218, 145); + } + + .rst-content tt.download, + .rst-content code.download { + background-image: inherit; + background-color: inherit; + color: inherit; + border-color: inherit; + } + + .rst-content .guilabel { + border-color: rgb(27, 84, 122); + background-image: initial; + background-color: rgb(32, 35, 36); + } + + span[id*="MathJax-Span"] { + color: rgb(192, 186, 178); + } + + .highlight .hll { + background-color: rgb(82, 82, 0); + } + + .highlight { + background-image: initial; + background-color: rgb(61, 82, 0); + } + + .highlight .c { + color: rgb(119, 179, 195); + } + + .highlight .err { + border-color: rgb(179, 0, 0); + } + + .highlight .k { + color: rgb(126, 255, 163); + } + + .highlight .o { + color: rgb(168, 160, 149); + } + + .highlight .ch { + color: rgb(119, 179, 195); + } + + .highlight .cm { + color: rgb(119, 179, 195); + } + + .highlight .cp { + color: rgb(126, 255, 163); + } + + .highlight .cpf { + color: rgb(119, 179, 195); + } + + .highlight .c1 { + color: rgb(119, 179, 195); + } + + .highlight .cs { + color: rgb(119, 179, 195); + background-color: rgb(60, 0, 0); + } + + .highlight .gd { + color: rgb(255, 92, 92); + } + + .highlight .gr { + color: rgb(255, 26, 26); + } + + .highlight .gh { + color: rgb(127, 174, 255); + } + + .highlight .gi { + color: rgb(92, 255, 92); + } + + .highlight .go { + color: rgb(200, 195, 188); + } + + .highlight .gp { + color: rgb(246, 147, 68); + } + + .highlight .gu { + color: rgb(255, 114, 255); + } + + .highlight .gt { + color: rgb(71, 160, 255); + } + + .highlight .kc { + color: rgb(126, 255, 163); + } + + .highlight .kd { + color: rgb(126, 255, 163); + } + + .highlight .kn { + color: rgb(126, 255, 163); + } + + .highlight .kp { + color: rgb(126, 255, 163); + } + + .highlight .kr { + color: rgb(126, 255, 163); + } + + .highlight .kt { + color: rgb(255, 137, 103); + } + + .highlight .m { + color: rgb(125, 222, 174); + } + + .highlight .s { + color: rgb(123, 166, 202); + } + + .highlight .na { + color: rgb(123, 166, 202); + } + + .highlight .nb { + color: rgb(126, 255, 163); + } + + .highlight .nc { + color: rgb(81, 194, 242); + } + + .highlight .no { + color: rgb(103, 177, 215); + } + + .highlight .nd { + color: rgb(178, 172, 162); + } + + .highlight .ni { + color: rgb(217, 100, 73); + } + + .highlight .ne { + color: rgb(126, 255, 163); + } + + .highlight .nf { + color: rgb(131, 186, 249); + } + + .highlight .nl { + color: rgb(137, 193, 255); + } + + .highlight .nn { + color: rgb(81, 194, 242); + } + + .highlight .nt { + color: rgb(138, 191, 249); + } + + .highlight .nv { + color: rgb(190, 103, 215); + } + + .highlight .ow { + color: rgb(126, 255, 163); + } + + .highlight .w { + color: rgb(189, 183, 175); + } + + .highlight .mb { + color: rgb(125, 222, 174); + } + + .highlight .mf { + color: rgb(125, 222, 174); + } + + .highlight .mh { + color: rgb(125, 222, 174); + } + + .highlight .mi { + color: rgb(125, 222, 174); + } + + .highlight .mo { + color: rgb(125, 222, 174); + } + + .highlight .sa { + color: rgb(123, 166, 202); + } + + .highlight .sb { + color: rgb(123, 166, 202); + } + + .highlight .sc { + color: rgb(123, 166, 202); + } + + .highlight .dl { + color: rgb(123, 166, 202); + } + + .highlight .sd { + color: rgb(123, 166, 202); + } + + .highlight .s2 { + color: rgb(123, 166, 202); + } + + .highlight .se { + color: rgb(123, 166, 202); + } + + .highlight .sh { + color: rgb(123, 166, 202); + } + + .highlight .si { + color: rgb(117, 168, 209); + } + + .highlight .sx { + color: rgb(246, 147, 68); + } + + .highlight .sr { + color: rgb(133, 182, 224); + } + + .highlight .s1 { + color: rgb(123, 166, 202); + } + + .highlight .ss { + color: rgb(188, 230, 128); + } + + .highlight .bp { + color: rgb(126, 255, 163); + } + + .highlight .fm { + color: rgb(131, 186, 249); + } + + .highlight .vc { + color: rgb(190, 103, 215); + } + + .highlight .vg { + color: rgb(190, 103, 215); + } + + .highlight .vi { + color: rgb(190, 103, 215); + } + + .highlight .vm { + color: rgb(190, 103, 215); + } + + .highlight .il { + color: rgb(125, 222, 174); + } + + .rst-other-versions a { + border-color: initial; + } + + .ethical-sidebar .ethical-image-link, + .ethical-footer .ethical-image-link { + border-color: initial; + } + + .ethical-sidebar, + .ethical-footer { + background-color: rgb(34, 36, 38); + border-color: rgb(62, 68, 70); + color: rgb(226, 223, 219); + } + + .ethical-sidebar ul { + list-style-image: initial; + } + + .ethical-sidebar ul li { + background-color: rgb(5, 77, 121); + color: rgb(232, 230, 227); + } + + .ethical-sidebar a, + .ethical-sidebar a:visited, + .ethical-sidebar a:hover, + .ethical-sidebar a:active, + .ethical-footer a, + .ethical-footer a:visited, + .ethical-footer a:hover, + .ethical-footer a:active { + color: rgb(226, 223, 219); + text-decoration-color: initial !important; + border-bottom-color: initial !important; + } + + .ethical-callout a { + color: rgb(161, 153, 141) !important; + text-decoration-color: initial !important; + } + + .ethical-fixedfooter { + background-color: rgb(34, 36, 38); + border-top-color: rgb(66, 72, 74); + color: rgb(192, 186, 178); + } + + .ethical-fixedfooter .ethical-text::before { + background-color: rgb(61, 140, 64); + color: rgb(232, 230, 227); + } + + .ethical-fixedfooter .ethical-callout { + color: rgb(168, 160, 149); + } + + .ethical-fixedfooter a, + .ethical-fixedfooter a:hover, + .ethical-fixedfooter a:active, + .ethical-fixedfooter a:visited { + color: rgb(192, 186, 178); + text-decoration-color: initial; + } + + .ethical-rtd .ethical-sidebar { + color: rgb(184, 178, 169); + } + + .ethical-alabaster a.ethical-image-link { + border-color: initial !important; + } + + .ethical-dark-theme .ethical-sidebar { + background-color: rgb(58, 62, 65); + border-color: rgb(75, 81, 84); + color: rgb(193, 188, 180) !important; + } + + .ethical-dark-theme a, + .ethical-dark-theme a:visited { + color: rgb(216, 213, 208) !important; + border-bottom-color: initial !important; + } + + .ethical-dark-theme .ethical-callout a { + color: rgb(184, 178, 169) !important; + } + + .keep-us-sustainable { + border-color: rgb(87, 133, 38); + } + + .keep-us-sustainable a, + .keep-us-sustainable a:hover, + .keep-us-sustainable a:visited { + text-decoration-color: initial; + } + + .wy-body-for-nav .keep-us-sustainable { + color: rgb(184, 178, 169); + } + + .wy-body-for-nav .keep-us-sustainable a { + color: rgb(222, 219, 215); + } + + /* For black-on-white/transparent images at handbook/text-anchors.html */ + #text-anchors img { + filter: invert(1) brightness(0.85) hue-rotate(-60deg); + } +} diff --git a/docs/resources/css/light.css b/docs/resources/css/light.css new file mode 100644 index 000000000..04edd7b16 --- /dev/null +++ b/docs/resources/css/light.css @@ -0,0 +1,8 @@ +@media (prefers-color-scheme: light) { + + .wy-menu-vertical li.toctree-l2.current a, + .wy-menu-vertical li.toctree-l3.current a { + background-color: #c9c9c9; + } + +} diff --git a/docs/resources/js/script.js b/docs/resources/js/script.js index 3bc216c2d..5cb6494ea 100644 --- a/docs/resources/js/script.js +++ b/docs/resources/js/script.js @@ -24,13 +24,11 @@ jQuery(document).ready(function ($) { var $upperA = $sidebarItem.parent().children('a'); var $upperAParent = $upperA.parent(); if ($upperAParent.hasClass('toctree-l2')) { - $a.css('background-color', '#c9c9c9'); $a.css('padding-left', '4em'); } else if ($upperAParent.hasClass('toctree-l3')) { if (!$upperA.find('.toctree-expand').length) { $upperA.prepend($('').addClass('toctree-expand')); } - $a.css('background-color', '#c9c9c9'); $a.css('padding-left', '5em'); } else { $a.css('background-color', '#bdbdbd'); diff --git a/docs/resources/pillow-logo.png b/docs/resources/pillow-logo.png new file mode 100644 index 000000000..1cc2006a6 Binary files /dev/null and b/docs/resources/pillow-logo.png differ diff --git a/requirements.txt b/requirements.txt index 14f934c9c..1ed1356f9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,12 +1,14 @@ # Development, documentation & testing requirements. -black; python_version >= '3.6' +black check-manifest coverage -jarn.viewdoc +markdown2 olefile -pycodestyle -pyflakes +packaging pyroma pytest pytest-cov +sphinx>=2.4 +sphinx-issues +sphinx-removed-in sphinx-rtd-theme diff --git a/selftest.py b/selftest.py index ea52256f7..a9a02ef71 100755 --- a/selftest.py +++ b/selftest.py @@ -47,7 +47,7 @@ def testimage(): ('PPM', 'RGB', (128, 128)) >>> try: ... _info(Image.open("Tests/images/hopper.jpg")) - ... except IOError as v: + ... except OSError as v: ... print(v) ('JPEG', 'RGB', (128, 128)) diff --git a/setup.cfg b/setup.cfg index 17e85bd21..129adeee7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,12 +1,10 @@ [flake8] -extend-ignore = E203, W503 +extend-ignore = E203 max-line-length = 88 [isort] -combine_as_imports = True -include_trailing_comma = True -line_length = 88 -multi_line_output = 3 +profile = black [tool:pytest] -addopts = -rs +addopts = -ra --color=yes +testpaths = Tests diff --git a/setup.py b/setup.py index ea549277a..cbc2641c5 100755 --- a/setup.py +++ b/setup.py @@ -14,15 +14,14 @@ import struct import subprocess import sys import warnings -from distutils import ccompiler, sysconfig -from distutils.command.build_ext import build_ext from setuptools import Extension, setup +from setuptools.command.build_ext import build_ext def get_version(): version_file = "src/PIL/_version.py" - with open(version_file, "r") as f: + with open(version_file) as f: exec(compile(f.read(), version_file, "exec")) return locals()["__version__"] @@ -38,13 +37,17 @@ TIFF_ROOT = None ZLIB_ROOT = None -if sys.platform == "win32" and sys.version_info >= (3, 9): - warnings.warn( - "Pillow {} does not support Python {}.{} and does not provide prebuilt " - "Windows binaries. We do not recommend building from source on Windows.".format( - PILLOW_VERSION, sys.version_info.major, sys.version_info.minor - ), - RuntimeWarning, +if sys.platform == "win32" and sys.version_info >= (3, 10): + import atexit + + atexit.register( + lambda: warnings.warn( + f"Pillow {PILLOW_VERSION} does not support Python " + f"{sys.version_info.major}.{sys.version_info.minor} and does not provide " + "prebuilt Windows binaries. We do not recommend building from source on " + "Windows.", + RuntimeWarning, + ) ) @@ -131,21 +134,9 @@ class RequiredDependencyException(Exception): pass -PLATFORM_MINGW = "mingw" in ccompiler.get_default_compiler() +PLATFORM_MINGW = os.name == "nt" and "GCC" in sys.version PLATFORM_PYPY = hasattr(sys, "pypy_version_info") -if sys.platform == "win32" and PLATFORM_MINGW: - from distutils import cygwinccompiler - - cygwin_versions = cygwinccompiler.get_versions() - if cygwin_versions[1] is None: - # ld version is None - # distutils cygwinccompiler might fetch the ld path from gcc - # Try the normal path instead - cygwin_versions = list(cygwin_versions) - cygwin_versions[1] = cygwinccompiler._find_exe_version("ld -v") - cygwinccompiler.get_versions = lambda: tuple(cygwin_versions) - def _dbg(s, tp=None): if DEBUG: @@ -175,7 +166,7 @@ def _find_library_dirs_ldconfig(): # Assuming GLIBC's ldconfig (with option -p) # Alpine Linux uses musl that can't print cache args = ["/sbin/ldconfig", "-p"] - expr = r".*\(%s.*\) => (.*)" % abi_type + expr = fr".*\({abi_type}.*\) => (.*)" env = dict(os.environ) env["LC_ALL"] = "C" env["LANG"] = "C" @@ -244,11 +235,6 @@ def _cmd_exists(cmd): ) -def _read(file): - with open(file, "rb") as fp: - return fp.read() - - def _pkg_config(name): try: command = os.environ.get("PKG_CONFIG", "pkg-config") @@ -286,6 +272,7 @@ class pil_build_ext(build_ext): "webpmux", "jpeg2000", "imagequant", + "xcb", ] required = {"jpeg", "zlib"} @@ -307,8 +294,8 @@ class pil_build_ext(build_ext): user_options = ( build_ext.user_options - + [("disable-%s" % x, None, "Disable support for %s" % x) for x in feature] - + [("enable-%s" % x, None, "Enable support for %s" % x) for x in feature] + + [(f"disable-{x}", None, f"Disable support for {x}") for x in feature] + + [(f"enable-{x}", None, f"Enable support for {x}") for x in feature] + [ ("disable-platform-guessing", None, "Disable platform guessing on Linux"), ("debug", None, "Debug logging"), @@ -321,8 +308,8 @@ class pil_build_ext(build_ext): self.add_imaging_libs = "" build_ext.initialize_options(self) for x in self.feature: - setattr(self, "disable_%s" % x, None) - setattr(self, "enable_%s" % x, None) + setattr(self, f"disable_{x}", None) + setattr(self, f"enable_{x}", None) def finalize_options(self): build_ext.finalize_options(self) @@ -339,25 +326,39 @@ class pil_build_ext(build_ext): except TypeError: self.parallel = None for x in self.feature: - if getattr(self, "disable_%s" % x): + if getattr(self, f"disable_{x}"): setattr(self.feature, x, False) self.feature.required.discard(x) _dbg("Disabling %s", x) - if getattr(self, "enable_%s" % x): + if getattr(self, f"enable_{x}"): raise ValueError( - "Conflicting options: --enable-{} and --disable-{}".format(x, x) + f"Conflicting options: --enable-{x} and --disable-{x}" ) - if getattr(self, "enable_%s" % x): + if getattr(self, f"enable_{x}"): _dbg("Requiring %s", x) self.feature.required.add(x) + def _update_extension(self, name, libraries, define_macros=None, include_dirs=None): + for extension in self.extensions: + if extension.name == name: + extension.libraries += libraries + if define_macros is not None: + extension.define_macros += define_macros + if include_dirs is not None: + extension.include_dirs += include_dirs + break + + def _remove_extension(self, name): + for extension in self.extensions: + if extension.name == name: + self.extensions.remove(extension) + break + def build_extensions(self): library_dirs = [] include_dirs = [] - _add_directory(include_dirs, "src/libImaging") - pkg_config = None if _cmd_exists(os.environ.get("PKG_CONFIG", "pkg-config")): pkg_config = _pkg_config @@ -382,12 +383,12 @@ class pil_build_ext(build_ext): if root is None and pkg_config: if isinstance(lib_name, tuple): for lib_name2 in lib_name: - _dbg("Looking for `%s` using pkg-config." % lib_name2) + _dbg(f"Looking for `{lib_name2}` using pkg-config.") root = pkg_config(lib_name2) if root: break else: - _dbg("Looking for `%s` using pkg-config." % lib_name) + _dbg(f"Looking for `{lib_name}` using pkg-config.") root = pkg_config(lib_name) if isinstance(root, tuple): @@ -417,10 +418,8 @@ class pil_build_ext(build_ext): for d in os.environ[k].split(os.path.pathsep): _add_directory(library_dirs, d) - prefix = sysconfig.get_config_var("prefix") - if prefix: - _add_directory(library_dirs, os.path.join(prefix, "lib")) - _add_directory(include_dirs, os.path.join(prefix, "include")) + _add_directory(library_dirs, os.path.join(sys.prefix, "lib")) + _add_directory(include_dirs, os.path.join(sys.prefix, "include")) # # add platform directories @@ -465,6 +464,9 @@ class pil_build_ext(build_ext): # add Homebrew's include and lib directories _add_directory(library_dirs, os.path.join(prefix, "lib")) _add_directory(include_dirs, os.path.join(prefix, "include")) + _add_directory( + include_dirs, os.path.join(prefix, "opt", "zlib", "include") + ) ft_prefix = os.path.join(prefix, "opt", "freetype") if ft_prefix and os.path.isdir(ft_prefix): @@ -477,6 +479,18 @@ class pil_build_ext(build_ext): _add_directory(library_dirs, "/usr/X11/lib") _add_directory(include_dirs, "/usr/X11/include") + # SDK install path + try: + sdk_path = ( + subprocess.check_output(["xcrun", "--show-sdk-path"]) + .strip() + .decode("latin1") + ) + except Exception: + sdk_path = None + if sdk_path: + _add_directory(library_dirs, os.path.join(sdk_path, "usr", "lib")) + _add_directory(include_dirs, os.path.join(sdk_path, "usr", "include")) elif ( sys.platform.startswith("linux") or sys.platform.startswith("gnu") @@ -516,11 +530,6 @@ class pil_build_ext(build_ext): _add_directory(library_dirs, "/lib") if sys.platform == "win32": - if PLATFORM_MINGW: - _add_directory( - include_dirs, "C:\\msys64\\mingw32\\include\\libimagequant" - ) - # on Windows, look for the OpenJPEG libraries in the location that # the official installer puts them program_files = os.environ.get("ProgramFiles", "") @@ -578,7 +587,7 @@ class pil_build_ext(build_ext): try: listdir = os.listdir(directory) except Exception: - # WindowsError, FileNotFoundError + # OSError, FileNotFoundError continue for name in listdir: if name.startswith("openjpeg-") and os.path.isfile( @@ -681,6 +690,12 @@ class pil_build_ext(build_ext): ): feature.webpmux = "libwebpmux" + if feature.want("xcb"): + _dbg("Looking for xcb") + if _find_include_file(self, "xcb/xcb.h"): + if _find_library_file(self, "xcb"): + feature.xcb = "xcb" + for f in feature: if not getattr(feature, f) and feature.require(f): if f in ("jpeg", "zlib"): @@ -690,12 +705,6 @@ class pil_build_ext(build_ext): # # core library - files = ["src/_imaging.c"] - for src_file in _IMAGING: - files.append("src/" + src_file + ".c") - for src_file in _LIB_IMAGING: - files.append(os.path.join("src/libImaging", src_file + ".c")) - libs = self.add_imaging_libs.split() defs = [] if feature.jpeg: @@ -704,7 +713,7 @@ class pil_build_ext(build_ext): if feature.jpeg2000: libs.append(feature.jpeg2000) defs.append(("HAVE_OPENJPEG", None)) - if sys.platform == "win32": + if sys.platform == "win32" and not PLATFORM_MINGW: defs.append(("OPJ_STATIC", None)) if feature.zlib: libs.append(feature.zlib) @@ -715,17 +724,28 @@ class pil_build_ext(build_ext): if feature.tiff: libs.append(feature.tiff) defs.append(("HAVE_LIBTIFF", None)) + # FIXME the following define should be detected automatically + # based on system libtiff, see #4237 + if PLATFORM_MINGW: + defs.append(("USE_WIN32_FILEIO", None)) + if feature.xcb: + libs.append(feature.xcb) + defs.append(("HAVE_XCB", None)) if sys.platform == "win32": libs.extend(["kernel32", "user32", "gdi32"]) if struct.unpack("h", b"\0\1")[0] == 1: defs.append(("WORDS_BIGENDIAN", None)) - if sys.platform == "win32" and not (PLATFORM_PYPY or PLATFORM_MINGW): - defs.append(("PILLOW_VERSION", '"\\"%s\\""' % PILLOW_VERSION)) + if ( + sys.platform == "win32" + and sys.version_info < (3, 9) + and not (PLATFORM_PYPY or PLATFORM_MINGW) + ): + defs.append(("PILLOW_VERSION", f'"\\"{PILLOW_VERSION}\\""')) else: - defs.append(("PILLOW_VERSION", '"%s"' % PILLOW_VERSION)) + defs.append(("PILLOW_VERSION", f'"{PILLOW_VERSION}"')) - exts = [(Extension("PIL._imaging", files, libraries=libs, define_macros=defs))] + self._update_extension("PIL._imaging", libs, defs) # # additional libraries @@ -733,26 +753,17 @@ class pil_build_ext(build_ext): if feature.freetype: libs = ["freetype"] defs = [] - exts.append( - Extension( - "PIL._imagingft", - ["src/_imagingft.c"], - libraries=libs, - define_macros=defs, - ) - ) + self._update_extension("PIL._imagingft", libs, defs) + else: + self._remove_extension("PIL._imagingft") if feature.lcms: extra = [] if sys.platform == "win32": extra.extend(["user32", "gdi32"]) - exts.append( - Extension( - "PIL._imagingcms", - ["src/_imagingcms.c"], - libraries=[feature.lcms] + extra, - ) - ) + self._update_extension("PIL._imagingcms", [feature.lcms] + extra) + else: + self._remove_extension("PIL._imagingcms") if feature.webp: libs = [feature.webp] @@ -763,26 +774,12 @@ class pil_build_ext(build_ext): libs.append(feature.webpmux) libs.append(feature.webpmux.replace("pmux", "pdemux")) - exts.append( - Extension( - "PIL._webp", ["src/_webp.c"], libraries=libs, define_macros=defs - ) - ) + self._update_extension("PIL._webp", libs, defs) + else: + self._remove_extension("PIL._webp") tk_libs = ["psapi"] if sys.platform == "win32" else [] - exts.append( - Extension( - "PIL._imagingtk", - ["src/_imagingtk.c", "src/Tk/tkImaging.c"], - include_dirs=["src/Tk"], - libraries=tk_libs, - ) - ) - - exts.append(Extension("PIL._imagingmath", ["src/_imagingmath.c"])) - exts.append(Extension("PIL._imagingmorph", ["src/_imagingmorph.c"])) - - self.extensions[:] = exts + self._update_extension("PIL._imagingtk", tk_libs) build_ext.build_extensions(self) @@ -796,11 +793,11 @@ class pil_build_ext(build_ext): print("-" * 68) print("PIL SETUP SUMMARY") print("-" * 68) - print("version Pillow %s" % PILLOW_VERSION) + print(f"version Pillow {PILLOW_VERSION}") v = sys.version.split("[") - print("platform {} {}".format(sys.platform, v[0].strip())) + print(f"platform {sys.platform} {v[0].strip()}") for v in v[1:]: - print(" [%s" % v.strip()) + print(f" [{v.strip()}") print("-" * 68) options = [ @@ -813,6 +810,7 @@ class pil_build_ext(build_ext): (feature.lcms, "LITTLECMS2"), (feature.webp, "WEBP"), (feature.webpmux, "WEBPMUX"), + (feature.xcb, "XCB (X protocol)"), ] all = 1 @@ -820,10 +818,10 @@ class pil_build_ext(build_ext): if option[0]: version = "" if len(option) >= 3 and option[2]: - version = " (%s)" % option[2] - print("--- {} support available{}".format(option[1], version)) + version = f" ({option[2]})" + print(f"--- {option[1]} support available{version}") else: - print("*** %s support not available" % option[1]) + print(f"*** {option[1]} support not available") all = 0 print("-" * 68) @@ -845,12 +843,31 @@ def debug_build(): return hasattr(sys, "gettotalrefcount") +files = ["src/_imaging.c"] +for src_file in _IMAGING: + files.append("src/" + src_file + ".c") +for src_file in _LIB_IMAGING: + files.append(os.path.join("src/libImaging", src_file + ".c")) +ext_modules = [ + Extension("PIL._imaging", files), + Extension("PIL._imagingft", ["src/_imagingft.c"]), + Extension("PIL._imagingcms", ["src/_imagingcms.c"]), + Extension("PIL._webp", ["src/_webp.c"]), + Extension("PIL._imagingtk", ["src/_imagingtk.c", "src/Tk/tkImaging.c"]), + Extension("PIL._imagingmath", ["src/_imagingmath.c"]), + Extension("PIL._imagingmorph", ["src/_imagingmorph.c"]), +] + +with open("README.md") as f: + long_description = f.read() + try: setup( name=NAME, version=PILLOW_VERSION, description="Python Imaging Library (Fork)", - long_description=_read("README.rst").decode("utf-8"), + long_description=long_description, + long_description_content_type="text/markdown", license="HPND", author="Alex Clark (PIL Fork Author)", author_email="aclark@python-pillow.org", @@ -858,16 +875,21 @@ try: project_urls={ "Documentation": "https://pillow.readthedocs.io", "Source": "https://github.com/python-pillow/Pillow", - "Funding": "https://tidelift.com/subscription/pkg/pypi-pillow", + "Funding": "https://tidelift.com/subscription/pkg/pypi-pillow?" + "utm_source=pypi-pillow&utm_medium=pypi", + "Release notes": "https://pillow.readthedocs.io/en/stable/releasenotes/" + "index.html", + "Changelog": "https://github.com/python-pillow/Pillow/blob/master/" + "CHANGES.rst", }, classifiers=[ "Development Status :: 6 - Mature", "License :: OSI Approved :: Historical Permission Notice and Disclaimer (HPND)", # noqa: E501 "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", @@ -877,9 +899,9 @@ try: "Topic :: Multimedia :: Graphics :: Graphics Conversion", "Topic :: Multimedia :: Graphics :: Viewers", ], - python_requires=">=3.5", + python_requires=">=3.6", cmdclass={"build_ext": pil_build_ext}, - ext_modules=[Extension("PIL._imaging", ["_imaging.c"])], + ext_modules=ext_modules, include_package_data=True, packages=["PIL"], package_dir={"": "src"}, @@ -887,28 +909,23 @@ try: zip_safe=not (debug_build() or PLATFORM_MINGW), ) except RequiredDependencyException as err: - msg = """ + msg = f""" -The headers or library files could not be found for %s, +The headers or library files could not be found for {str(err)}, a required dependency when compiling Pillow from source. Please see the install instructions at: https://pillow.readthedocs.io/en/latest/installation.html -""" % ( - str(err) - ) +""" sys.stderr.write(msg) raise RequiredDependencyException(msg) except DependencyException as err: - msg = """ + msg = f""" -The headers or library files could not be found for %s, -which was requested by the option flag --enable-%s +The headers or library files could not be found for {str(err)}, +which was requested by the option flag --enable-{str(err)} -""" % ( - str(err), - str(err), - ) +""" sys.stderr.write(msg) raise DependencyException(msg) diff --git a/src/PIL/BdfFontFile.py b/src/PIL/BdfFontFile.py index 7a485cf80..102b72e1d 100644 --- a/src/PIL/BdfFontFile.py +++ b/src/PIL/BdfFontFile.py @@ -17,13 +17,13 @@ # See the README file for information on usage and redistribution. # +""" +Parse X Bitmap Distribution Format (BDF) +""" + from . import FontFile, Image -# -------------------------------------------------------------------- -# parse X Bitmap Distribution Format (BDF) -# -------------------------------------------------------------------- - bdf_slant = { "R": "Roman", "I": "Italic", @@ -78,11 +78,9 @@ def bdf_char(f): return id, int(props["ENCODING"]), bbox, im -## -# Font file plugin for the X11 BDF format. - - class BdfFontFile(FontFile.FontFile): + """Font file plugin for the X11 BDF format.""" + def __init__(self, fp): super().__init__() diff --git a/src/PIL/BlpImagePlugin.py b/src/PIL/BlpImagePlugin.py index 5ccba37db..d5d7c0e05 100644 --- a/src/PIL/BlpImagePlugin.py +++ b/src/PIL/BlpImagePlugin.py @@ -250,7 +250,7 @@ class BlpImageFile(ImageFile.ImageFile): decoder = "BLP2" self.mode = "RGBA" if self._blp_alpha_depth else "RGB" else: - raise BLPFormatError("Bad BLP magic %r" % (self.magic)) + raise BLPFormatError(f"Bad BLP magic {repr(self.magic)}") self.tile = [(decoder, (0, 0) + self.size, 0, (self.mode, 0, 1))] @@ -282,8 +282,8 @@ class _BLPBaseDecoder(ImageFile.PyDecoder): self.magic = self.fd.read(4) self._read_blp_header() self._load() - except struct.error: - raise OSError("Truncated Blp file") + except struct.error as e: + raise OSError("Truncated Blp file") from e return 0, 0 def _read_palette(self): @@ -336,11 +336,11 @@ class BLP1Decoder(_BLPBaseDecoder): self.set_as_raw(bytes(data)) else: raise BLPFormatError( - "Unsupported BLP encoding %r" % (self._blp_encoding) + f"Unsupported BLP encoding {repr(self._blp_encoding)}" ) else: raise BLPFormatError( - "Unsupported BLP compression %r" % (self._blp_encoding) + f"Unsupported BLP compression {repr(self._blp_encoding)}" ) def _decode_jpeg_stream(self): @@ -400,13 +400,15 @@ class BLP2Decoder(_BLPBaseDecoder): data += d else: raise BLPFormatError( - "Unsupported alpha encoding %r" % (self._blp_alpha_encoding) + f"Unsupported alpha encoding {repr(self._blp_alpha_encoding)}" ) else: - raise BLPFormatError("Unknown BLP encoding %r" % (self._blp_encoding)) + raise BLPFormatError(f"Unknown BLP encoding {repr(self._blp_encoding)}") else: - raise BLPFormatError("Unknown BLP compression %r" % (self._blp_compression)) + raise BLPFormatError( + f"Unknown BLP compression {repr(self._blp_compression)}" + ) self.set_as_raw(bytes(data)) diff --git a/src/PIL/BmpImagePlugin.py b/src/PIL/BmpImagePlugin.py index 85e2350c5..98685be0b 100644 --- a/src/PIL/BmpImagePlugin.py +++ b/src/PIL/BmpImagePlugin.py @@ -25,7 +25,11 @@ from . import Image, ImageFile, ImagePalette -from ._binary import i8, i16le as i16, i32le as i32, o8, o16le as o16, o32le as o32 +from ._binary import i16le as i16 +from ._binary import i32le as i32 +from ._binary import o8 +from ._binary import o16le as o16 +from ._binary import o32le as o32 # # -------------------------------------------------------------------- @@ -47,7 +51,7 @@ def _accept(prefix): def _dib_accept(prefix): - return i32(prefix[:4]) in [12, 40, 64, 108, 124] + return i32(prefix) in [12, 40, 64, 108, 124] # ============================================================================= @@ -82,34 +86,34 @@ class BmpImageFile(ImageFile.ImageFile): # -------------------------------------------------- IBM OS/2 Bitmap v1 # ----- This format has different offsets because of width/height types if file_info["header_size"] == 12: - file_info["width"] = i16(header_data[0:2]) - file_info["height"] = i16(header_data[2:4]) - file_info["planes"] = i16(header_data[4:6]) - file_info["bits"] = i16(header_data[6:8]) + file_info["width"] = i16(header_data, 0) + file_info["height"] = i16(header_data, 2) + file_info["planes"] = i16(header_data, 4) + file_info["bits"] = i16(header_data, 6) file_info["compression"] = self.RAW file_info["palette_padding"] = 3 # --------------------------------------------- Windows Bitmap v2 to v5 # v3, OS/2 v2, v4, v5 elif file_info["header_size"] in (40, 64, 108, 124): - file_info["y_flip"] = i8(header_data[7]) == 0xFF + file_info["y_flip"] = header_data[7] == 0xFF file_info["direction"] = 1 if file_info["y_flip"] else -1 - file_info["width"] = i32(header_data[0:4]) + file_info["width"] = i32(header_data, 0) file_info["height"] = ( - i32(header_data[4:8]) + i32(header_data, 4) if not file_info["y_flip"] - else 2 ** 32 - i32(header_data[4:8]) + else 2 ** 32 - i32(header_data, 4) ) - file_info["planes"] = i16(header_data[8:10]) - file_info["bits"] = i16(header_data[10:12]) - file_info["compression"] = i32(header_data[12:16]) + file_info["planes"] = i16(header_data, 8) + file_info["bits"] = i16(header_data, 10) + file_info["compression"] = i32(header_data, 12) # byte size of pixel data - file_info["data_size"] = i32(header_data[16:20]) + file_info["data_size"] = i32(header_data, 16) file_info["pixels_per_meter"] = ( - i32(header_data[20:24]), - i32(header_data[24:28]), + i32(header_data, 20), + i32(header_data, 24), ) - file_info["colors"] = i32(header_data[28:32]) + file_info["colors"] = i32(header_data, 28) file_info["palette_padding"] = 4 self.info["dpi"] = tuple( int(x / 39.3701 + 0.5) for x in file_info["pixels_per_meter"] @@ -119,7 +123,7 @@ class BmpImageFile(ImageFile.ImageFile): for idx, mask in enumerate( ["r_mask", "g_mask", "b_mask", "a_mask"] ): - file_info[mask] = i32(header_data[36 + idx * 4 : 40 + idx * 4]) + file_info[mask] = i32(header_data, 36 + idx * 4) else: # 40 byte headers only have the three components in the # bitfields masks, ref: @@ -144,7 +148,7 @@ class BmpImageFile(ImageFile.ImageFile): file_info["a_mask"], ) else: - raise OSError("Unsupported BMP header type (%d)" % file_info["header_size"]) + raise OSError(f"Unsupported BMP header type ({file_info['header_size']})") # ------------------ Special case : header is reported 40, which # ---------------------- is shorter than real size for bpp >= 16 @@ -157,14 +161,10 @@ class BmpImageFile(ImageFile.ImageFile): else (1 << file_info["bits"]) ) - # ------------------------------- Check abnormal values for DOS attacks - if file_info["width"] * file_info["height"] > 2 ** 31: - raise OSError("Unsupported BMP Size: (%dx%d)" % self.size) - # ---------------------- Check bit depth for unusual unsupported values self.mode, raw_mode = BIT2MODE.get(file_info["bits"], (None, None)) if self.mode is None: - raise OSError("Unsupported BMP pixel depth (%d)" % file_info["bits"]) + raise OSError(f"Unsupported BMP pixel depth ({file_info['bits']})") # ---------------- Process BMP with Bitfields compression (not palette) if file_info["compression"] == self.BITFIELDS: @@ -209,14 +209,14 @@ class BmpImageFile(ImageFile.ImageFile): if file_info["bits"] == 32 and header == 22: # 32-bit .cur offset raw_mode, self.mode = "BGRA", "RGBA" else: - raise OSError("Unsupported BMP compression (%d)" % file_info["compression"]) + raise OSError(f"Unsupported BMP compression ({file_info['compression']})") # --------------- Once the header is processed, process the palette/LUT if self.mode == "P": # Paletted for 1, 4 and 8 bit images # ---------------------------------------------------- 1-bit images if not (0 < file_info["colors"] <= 65536): - raise OSError("Unsupported BMP Palette size (%d)" % file_info["colors"]) + raise OSError(f"Unsupported BMP Palette size ({file_info['colors']})") else: padding = file_info["palette_padding"] palette = read(padding * file_info["colors"]) @@ -263,10 +263,10 @@ class BmpImageFile(ImageFile.ImageFile): # read 14 bytes: magic number, filesize, reserved, header final offset head_data = self.fp.read(14) # choke if the file does not have the required magic bytes - if head_data[0:2] != b"BM": + if not _accept(head_data): raise SyntaxError("Not a BMP file") # read the start position of the BMP image data (u32) - offset = i32(head_data[10:14]) + offset = i32(head_data, 10) # load bitmap information (offset=raster info) self._bitmap(offset=offset) @@ -304,8 +304,8 @@ def _dib_save(im, fp, filename): def _save(im, fp, filename, bitmap_header=True): try: rawmode, bits, colors = SAVE[im.mode] - except KeyError: - raise OSError("cannot write mode %s as BMP" % im.mode) + except KeyError as e: + raise OSError(f"cannot write mode {im.mode} as BMP") from e info = im.encoderinfo diff --git a/src/PIL/ContainerIO.py b/src/PIL/ContainerIO.py index 9727601ab..45e80b39a 100644 --- a/src/PIL/ContainerIO.py +++ b/src/PIL/ContainerIO.py @@ -14,14 +14,16 @@ # See the README file for information on usage and redistribution. # -## -# A file object that provides read access to a part of an existing -# file (for example a TAR file). import io class ContainerIO: + """ + A file object that provides read access to a part of an existing + file (for example a TAR file). + """ + def __init__(self, file, offset, length): """ Create file object. @@ -82,7 +84,7 @@ class ContainerIO: else: n = self.length - self.pos if not n: # EOF - return "" + return b"" if "b" in self.fh.mode else "" self.pos = self.pos + n return self.fh.read(n) @@ -92,13 +94,14 @@ class ContainerIO: :returns: An 8-bit string. """ - s = "" + s = b"" if "b" in self.fh.mode else "" + newline_character = b"\n" if "b" in self.fh.mode else "\n" while True: c = self.read(1) if not c: break s = s + c - if c == "\n": + if c == newline_character: break return s diff --git a/src/PIL/CurImagePlugin.py b/src/PIL/CurImagePlugin.py index 3a1b6d2e5..42af5cafc 100644 --- a/src/PIL/CurImagePlugin.py +++ b/src/PIL/CurImagePlugin.py @@ -16,7 +16,8 @@ # See the README file for information on usage and redistribution. # from . import BmpImagePlugin, Image -from ._binary import i8, i16le as i16, i32le as i32 +from ._binary import i16le as i16 +from ._binary import i32le as i32 # # -------------------------------------------------------------------- @@ -46,17 +47,17 @@ class CurImageFile(BmpImagePlugin.BmpImageFile): # pick the largest cursor in the file m = b"" - for i in range(i16(s[4:])): + for i in range(i16(s, 4)): s = self.fp.read(16) if not m: m = s - elif i8(s[0]) > i8(m[0]) and i8(s[1]) > i8(m[1]): + elif s[0] > m[0] and s[1] > m[1]: m = s if not m: raise TypeError("No cursors were found") # load as bitmap - self._bitmap(i32(m[12:]) + offset) + self._bitmap(i32(m, 12) + offset) # patch up the bitmap height self._size = self.size[0], self.size[1] // 2 diff --git a/src/PIL/DcxImagePlugin.py b/src/PIL/DcxImagePlugin.py index 7d2aff325..de21db8f0 100644 --- a/src/PIL/DcxImagePlugin.py +++ b/src/PIL/DcxImagePlugin.py @@ -46,7 +46,7 @@ class DcxImageFile(PcxImageFile): # Header s = self.fp.read(4) - if i32(s) != MAGIC: + if not _accept(s): raise SyntaxError("not a DCX file") # Component directory @@ -59,16 +59,10 @@ class DcxImageFile(PcxImageFile): self.__fp = self.fp self.frame = None + self.n_frames = len(self._offset) + self.is_animated = self.n_frames > 1 self.seek(0) - @property - def n_frames(self): - return len(self._offset) - - @property - def is_animated(self): - return len(self._offset) > 1 - def seek(self, frame): if not self._seek_check(frame): return diff --git a/src/PIL/DdsImagePlugin.py b/src/PIL/DdsImagePlugin.py index 9ba6e0ff8..df2d0060c 100644 --- a/src/PIL/DdsImagePlugin.py +++ b/src/PIL/DdsImagePlugin.py @@ -94,6 +94,9 @@ DXT5_FOURCC = 0x35545844 # dxgiformat.h +DXGI_FORMAT_R8G8B8A8_TYPELESS = 27 +DXGI_FORMAT_R8G8B8A8_UNORM = 28 +DXGI_FORMAT_R8G8B8A8_UNORM_SRGB = 29 DXGI_FORMAT_BC7_TYPELESS = 97 DXGI_FORMAT_BC7_UNORM = 98 DXGI_FORMAT_BC7_UNORM_SRGB = 99 @@ -106,10 +109,10 @@ class DdsImageFile(ImageFile.ImageFile): def _open(self): magic, header_size = struct.unpack("= 6 and i16(prefix[4:6]) in [0xAF11, 0xAF12] + return len(prefix) >= 6 and i16(prefix, 4) in [0xAF11, 0xAF12] ## @@ -42,23 +44,24 @@ class FliImageFile(ImageFile.ImageFile): # HEAD s = self.fp.read(128) - magic = i16(s[4:6]) if not ( - magic in [0xAF11, 0xAF12] - and i16(s[14:16]) in [0, 3] # flags + _accept(s) + and i16(s, 14) in [0, 3] # flags and s[20:22] == b"\x00\x00" # reserved ): raise SyntaxError("not an FLI/FLC file") # frames - self.__framecount = i16(s[6:8]) + self.n_frames = i16(s, 6) + self.is_animated = self.n_frames > 1 # image characteristics self.mode = "P" - self._size = i16(s[8:10]), i16(s[10:12]) + self._size = i16(s, 8), i16(s, 10) # animation speed - duration = i32(s[16:20]) + duration = i32(s, 16) + magic = i16(s, 4) if magic == 0xAF11: duration = (duration * 1000) // 70 self.info["duration"] = duration @@ -70,17 +73,17 @@ class FliImageFile(ImageFile.ImageFile): self.__offset = 128 - if i16(s[4:6]) == 0xF100: + if i16(s, 4) == 0xF100: # prefix chunk; ignore it self.__offset = self.__offset + i32(s) s = self.fp.read(16) - if i16(s[4:6]) == 0xF1FA: + if i16(s, 4) == 0xF1FA: # look for palette chunk s = self.fp.read(6) - if i16(s[4:6]) == 11: + if i16(s, 4) == 11: self._palette(palette, 2) - elif i16(s[4:6]) == 4: + elif i16(s, 4) == 4: self._palette(palette, 0) palette = [o8(r) + o8(g) + o8(b) for (r, g, b) in palette] @@ -98,26 +101,18 @@ class FliImageFile(ImageFile.ImageFile): i = 0 for e in range(i16(self.fp.read(2))): s = self.fp.read(2) - i = i + i8(s[0]) - n = i8(s[1]) + i = i + s[0] + n = s[1] if n == 0: n = 256 s = self.fp.read(n * 3) for n in range(0, len(s), 3): - r = i8(s[n]) << shift - g = i8(s[n + 1]) << shift - b = i8(s[n + 2]) << shift + r = s[n] << shift + g = s[n + 1] << shift + b = s[n + 2] << shift palette[i] = (r, g, b) i += 1 - @property - def n_frames(self): - return self.__framecount - - @property - def is_animated(self): - return self.__framecount > 1 - def seek(self, frame): if not self._seek_check(frame): return @@ -137,7 +132,7 @@ class FliImageFile(ImageFile.ImageFile): self.load() if frame != self.__frame + 1: - raise ValueError("cannot seek to frame %d" % frame) + raise ValueError(f"cannot seek to frame {frame}") self.__frame = frame # move to next frame diff --git a/src/PIL/FontFile.py b/src/PIL/FontFile.py index 979a1e33c..c5fc80b37 100644 --- a/src/PIL/FontFile.py +++ b/src/PIL/FontFile.py @@ -23,18 +23,15 @@ WIDTH = 800 def puti16(fp, values): - # write network order (big-endian) 16-bit sequence + """Write network order (big-endian) 16-bit sequence""" for v in values: if v < 0: v += 65536 fp.write(_binary.o16be(v)) -## -# Base class for raster font file handlers. - - class FontFile: + """Base class for raster font file handlers.""" bitmap = None @@ -104,7 +101,7 @@ class FontFile: # font metrics with open(os.path.splitext(filename)[0] + ".pil", "wb") as fp: fp.write(b"PILfont\n") - fp.write((";;;;;;%d;\n" % self.ysize).encode("ascii")) # HACK!!! + fp.write(f";;;;;;{self.ysize};\n".encode("ascii")) # HACK!!! fp.write(b"DATA\n") for id in range(256): m = self.metrics[id] diff --git a/src/PIL/FpxImagePlugin.py b/src/PIL/FpxImagePlugin.py index 8d252c79c..5e385469f 100644 --- a/src/PIL/FpxImagePlugin.py +++ b/src/PIL/FpxImagePlugin.py @@ -17,7 +17,7 @@ import olefile from . import Image, ImageFile -from ._binary import i8, i32le as i32 +from ._binary import i32le as i32 # we map from colour field tuples to (mode, rawmode) descriptors MODES = { @@ -59,8 +59,8 @@ class FpxImageFile(ImageFile.ImageFile): try: self.ole = olefile.OleFileIO(self.fp) - except OSError: - raise SyntaxError("not an FPX file; invalid OLE file") + except OSError as e: + raise SyntaxError("not an FPX file; invalid OLE file") from e if self.ole.root.clsid != "56616700-C154-11CE-8553-00AA00A1F95B": raise SyntaxError("not an FPX file; bad root CLSID") @@ -72,7 +72,7 @@ class FpxImageFile(ImageFile.ImageFile): # get the Image Contents Property Set prop = self.ole.getproperties( - ["Data Object Store %06d" % index, "\005Image Contents"] + [f"Data Object Store {index:06d}", "\005Image Contents"] ) # size (highest resolution) @@ -99,7 +99,7 @@ class FpxImageFile(ImageFile.ImageFile): colors = [] bands = i32(s, 4) if bands > 4: - raise IOError("Invalid number of bands") + raise OSError("Invalid number of bands") for i in range(bands): # note: for now, we ignore the "uncalibrated" flag colors.append(i32(s, 8 + i * 4) & 0x7FFFFFFF) @@ -120,8 +120,8 @@ class FpxImageFile(ImageFile.ImageFile): # setup tile descriptors for a given subimage stream = [ - "Data Object Store %06d" % index, - "Resolution %04d" % subimage, + f"Data Object Store {index:06d}", + f"Resolution {subimage:04d}", "Subimage 0000 Header", ] @@ -180,8 +180,8 @@ class FpxImageFile(ImageFile.ImageFile): elif compression == 2: - internal_color_conversion = i8(s[14]) - jpeg_tables = i8(s[15]) + internal_color_conversion = s[14] + jpeg_tables = s[15] rawmode = self.rawmode if internal_color_conversion: diff --git a/src/PIL/FtexImagePlugin.py b/src/PIL/FtexImagePlugin.py index 096ccacac..900661238 100644 --- a/src/PIL/FtexImagePlugin.py +++ b/src/PIL/FtexImagePlugin.py @@ -89,7 +89,7 @@ class FtexImageFile(ImageFile.ImageFile): elif format == FORMAT_UNCOMPRESSED: self.tile = [("raw", (0, 0) + self.size, 0, ("RGB", 0, 1))] else: - raise ValueError("Invalid texture compression format: %r" % (format)) + raise ValueError(f"Invalid texture compression format: {repr(format)}") self.fp.close() self.fp = BytesIO(data) diff --git a/src/PIL/GbrImagePlugin.py b/src/PIL/GbrImagePlugin.py index 292de435c..0f230602d 100644 --- a/src/PIL/GbrImagePlugin.py +++ b/src/PIL/GbrImagePlugin.py @@ -29,7 +29,7 @@ from ._binary import i32be as i32 def _accept(prefix): - return len(prefix) >= 8 and i32(prefix[:4]) >= 20 and i32(prefix[4:8]) in (1, 2) + return len(prefix) >= 8 and i32(prefix, 0) >= 20 and i32(prefix, 4) in (1, 2) ## @@ -47,7 +47,7 @@ class GbrImageFile(ImageFile.ImageFile): if header_size < 20: raise SyntaxError("not a GIMP brush") if version not in (1, 2): - raise SyntaxError("Unsupported GIMP brush version: %s" % version) + raise SyntaxError(f"Unsupported GIMP brush version: {version}") width = i32(self.fp.read(4)) height = i32(self.fp.read(4)) @@ -55,7 +55,7 @@ class GbrImageFile(ImageFile.ImageFile): if width <= 0 or height <= 0: raise SyntaxError("not a GIMP brush") if color_depth not in (1, 4): - raise SyntaxError("Unsupported GIMP brush color depth: %s" % color_depth) + raise SyntaxError(f"Unsupported GIMP brush color depth: {color_depth}") if version == 1: comment_length = header_size - 20 @@ -84,6 +84,10 @@ class GbrImageFile(ImageFile.ImageFile): self._data_size = width * height * color_depth def load(self): + if self.im: + # Already loaded + return + self.im = Image.core.new(self.mode, self.size) self.frombytes(self.fp.read(self._data_size)) diff --git a/src/PIL/GdImageFile.py b/src/PIL/GdImageFile.py index 54c88712c..9c34adaa6 100644 --- a/src/PIL/GdImageFile.py +++ b/src/PIL/GdImageFile.py @@ -14,26 +14,31 @@ # -# NOTE: This format cannot be automatically recognized, so the -# class is not registered for use with Image.open(). To open a -# gd file, use the GdImageFile.open() function instead. +""" +.. note:: + This format cannot be automatically recognized, so the + class is not registered for use with :py:func:`PIL.Image.open()`. To open a + gd file, use the :py:func:`PIL.GdImageFile.open()` function instead. -# THE GD FORMAT IS NOT DESIGNED FOR DATA INTERCHANGE. This -# implementation is provided for convenience and demonstrational -# purposes only. +.. warning:: + THE GD FORMAT IS NOT DESIGNED FOR DATA INTERCHANGE. This + implementation is provided for convenience and demonstrational + purposes only. +""" from . import ImageFile, ImagePalette, UnidentifiedImageError -from ._binary import i8, i16be as i16, i32be as i32 - -## -# Image plugin for the GD uncompressed format. Note that this format -# is not supported by the standard Image.open function. To use -# this plugin, you have to import the GdImageFile module and -# use the GdImageFile.open function. +from ._binary import i16be as i16 +from ._binary import i32be as i32 class GdImageFile(ImageFile.ImageFile): + """ + Image plugin for the GD uncompressed format. Note that this format + is not supported by the standard :py:func:`PIL.Image.open()` function. To use + this plugin, you have to import the :py:mod:`PIL.GdImageFile` module and + use the :py:func:`PIL.GdImageFile.open()` function. + """ format = "GD" format_description = "GD uncompressed images" @@ -43,17 +48,17 @@ class GdImageFile(ImageFile.ImageFile): # Header s = self.fp.read(1037) - if not i16(s[:2]) in [65534, 65535]: + if not i16(s) in [65534, 65535]: raise SyntaxError("Not a valid GD 2.x .gd file") self.mode = "L" # FIXME: "P" - self._size = i16(s[2:4]), i16(s[4:6]) + self._size = i16(s, 2), i16(s, 4) - trueColor = i8(s[6]) + trueColor = s[6] trueColorOffset = 2 if trueColor else 0 # transparency index - tindex = i32(s[7 + trueColorOffset : 7 + trueColorOffset + 4]) + tindex = i32(s, 7 + trueColorOffset) if tindex < 256: self.info["transparency"] = tindex @@ -74,12 +79,12 @@ def open(fp, mode="r"): :param mode: Optional mode. In this version, if the mode argument is given, it must be "r". :returns: An image instance. - :raises IOError: If the image could not be read. + :raises OSError: If the image could not be read. """ if mode != "r": raise ValueError("bad mode") try: return GdImageFile(fp) - except SyntaxError: - raise UnidentifiedImageError("cannot identify this image file") + except SyntaxError as e: + raise UnidentifiedImageError("cannot identify this image file") from e diff --git a/src/PIL/GifImagePlugin.py b/src/PIL/GifImagePlugin.py index 1d94fc7c7..7c083bd8b 100644 --- a/src/PIL/GifImagePlugin.py +++ b/src/PIL/GifImagePlugin.py @@ -30,7 +30,9 @@ import os import subprocess from . import Image, ImageChops, ImageFile, ImagePalette, ImageSequence -from ._binary import i8, i16le as i16, o8, o16le as o16 +from ._binary import i16le as i16 +from ._binary import o8 +from ._binary import o16le as o16 # -------------------------------------------------------------------- # Identify/read GIF files @@ -55,30 +57,30 @@ class GifImageFile(ImageFile.ImageFile): def data(self): s = self.fp.read(1) - if s and i8(s): - return self.fp.read(i8(s)) + if s and s[0]: + return self.fp.read(s[0]) return None def _open(self): # Screen s = self.fp.read(13) - if s[:6] not in [b"GIF87a", b"GIF89a"]: + if not _accept(s): raise SyntaxError("not a GIF file") self.info["version"] = s[:6] - self._size = i16(s[6:]), i16(s[8:]) + self._size = i16(s, 6), i16(s, 8) self.tile = [] - flags = i8(s[10]) + flags = s[10] bits = (flags & 7) + 1 if flags & 128: # get global palette - self.info["background"] = i8(s[11]) + self.info["background"] = s[11] # check if palette contains colour indices p = self.fp.read(3 << bits) for i in range(0, len(p), 3): - if not (i // 3 == i8(p[i]) == i8(p[i + 1]) == i8(p[i + 2])): + if not (i // 3 == p[i] == p[i + 1] == p[i + 2]): p = ImagePalette.raw("RGB", p) self.global_palette = self.palette = p break @@ -130,9 +132,9 @@ class GifImageFile(ImageFile.ImageFile): for f in range(self.__frame + 1, frame + 1): try: self._seek(f) - except EOFError: + except EOFError as e: self.seek(last_frame) - raise EOFError("no more images in GIF file") + raise EOFError("no more images in GIF file") from e def _seek(self, frame): @@ -151,7 +153,7 @@ class GifImageFile(ImageFile.ImageFile): self.load() if frame != self.__frame + 1: - raise ValueError("cannot seek to frame %d" % frame) + raise ValueError(f"cannot seek to frame {frame}") self.__frame = frame self.tile = [] @@ -184,14 +186,14 @@ class GifImageFile(ImageFile.ImageFile): # s = self.fp.read(1) block = self.data() - if i8(s) == 249: + if s[0] == 249: # # graphic control extension # - flags = i8(block[0]) + flags = block[0] if flags & 1: - info["transparency"] = i8(block[3]) - info["duration"] = i16(block[1:3]) * 10 + info["transparency"] = block[3] + info["duration"] = i16(block, 1) * 10 # disposal method - find the value of bits 4 - 6 dispose_bits = 0b00011100 & flags @@ -202,7 +204,7 @@ class GifImageFile(ImageFile.ImageFile): # correct, but it seems to prevent the last # frame from looking odd for some animations self.disposal_method = dispose_bits - elif i8(s) == 254: + elif s[0] == 254: # # comment extension # @@ -213,15 +215,15 @@ class GifImageFile(ImageFile.ImageFile): info["comment"] = block block = self.data() continue - elif i8(s) == 255: + elif s[0] == 255: # # application extension # info["extension"] = block, self.fp.tell() if block[:11] == b"NETSCAPE2.0": block = self.data() - if len(block) >= 3 and i8(block[0]) == 1: - info["loop"] = i16(block[1:3]) + if len(block) >= 3 and block[0] == 1: + info["loop"] = i16(block, 1) while self.data(): pass @@ -232,12 +234,12 @@ class GifImageFile(ImageFile.ImageFile): s = self.fp.read(9) # extent - x0, y0 = i16(s[0:]), i16(s[2:]) - x1, y1 = x0 + i16(s[4:]), y0 + i16(s[6:]) + x0, y0 = i16(s, 0), i16(s, 2) + x1, y1 = x0 + i16(s, 4), y0 + i16(s, 6) if x1 > self.size[0] or y1 > self.size[1]: self._size = max(x1, self.size[0]), max(y1, self.size[1]) self.dispose_extent = x0, y0, x1, y1 - flags = i8(s[8]) + flags = s[8] interlace = (flags & 64) != 0 @@ -246,7 +248,7 @@ class GifImageFile(ImageFile.ImageFile): self.palette = ImagePalette.raw("RGB", self.fp.read(3 << bits)) # image data - bits = i8(self.fp.read(1)) + bits = self.fp.read(1)[0] self.__offset = self.fp.tell() self.tile = [ ("gif", (x0, y0, x1, y1), self.__offset, (bits, interlace)) @@ -255,7 +257,7 @@ class GifImageFile(ImageFile.ImageFile): else: pass - # raise IOError, "illegal GIF tag `%x`" % i8(s) + # raise OSError, "illegal GIF tag `%x`" % s[0] try: if self.disposal_method < 2: @@ -298,13 +300,14 @@ class GifImageFile(ImageFile.ImageFile): # if the disposal method is 'do not dispose', transparent # pixels should show the content of the previous frame - if self._prev_im and self.disposal_method == 1: + if self._prev_im and self._prev_disposal_method == 1: # we do this by pasting the updated area onto the previous # frame which we then use as the current image content updated = self._crop(self.im, self.dispose_extent) self._prev_im.paste(updated, self.dispose_extent, updated.convert("RGBA")) self.im = self._prev_im self._prev_im = self.im.copy() + self._prev_disposal_method = self.disposal_method def _close__fp(self): try: diff --git a/src/PIL/GimpGradientFile.py b/src/PIL/GimpGradientFile.py index 1cacf5718..7ab7f9990 100644 --- a/src/PIL/GimpGradientFile.py +++ b/src/PIL/GimpGradientFile.py @@ -13,17 +13,19 @@ # See the README file for information on usage and redistribution. # +""" +Stuff to translate curve segments to palette values (derived from +the corresponding code in GIMP, written by Federico Mena Quintero. +See the GIMP distribution for more information.) +""" + + from math import log, pi, sin, sqrt from ._binary import o8 -# -------------------------------------------------------------------- -# Stuff to translate curve segments to palette values (derived from -# the corresponding code in GIMP, written by Federico Mena Quintero. -# See the GIMP distribution for more information.) -# - EPSILON = 1e-10 +"""""" # Enable auto-doc for data member def linear(middle, pos): @@ -58,6 +60,7 @@ def sphere_decreasing(middle, pos): SEGMENTS = [linear, curved, sine, sphere_increasing, sphere_decreasing] +"""""" # Enable auto-doc for data member class GradientFile: @@ -98,11 +101,9 @@ class GradientFile: return b"".join(palette), "RGBA" -## -# File handler for GIMP's gradient format. - - class GimpGradientFile(GradientFile): + """File handler for GIMP's gradient format.""" + def __init__(self, fp): if fp.readline()[:13] != b"GIMP Gradient": diff --git a/src/PIL/GimpPaletteFile.py b/src/PIL/GimpPaletteFile.py index e3060ab8a..10fd3ad81 100644 --- a/src/PIL/GimpPaletteFile.py +++ b/src/PIL/GimpPaletteFile.py @@ -18,11 +18,9 @@ import re from ._binary import o8 -## -# File handler for GIMP's palette format. - class GimpPaletteFile: + """File handler for GIMP's palette format.""" rawmode = "RGB" diff --git a/src/PIL/GribStubImagePlugin.py b/src/PIL/GribStubImagePlugin.py index 515c272f7..b9bdd16e3 100644 --- a/src/PIL/GribStubImagePlugin.py +++ b/src/PIL/GribStubImagePlugin.py @@ -10,7 +10,6 @@ # from . import Image, ImageFile -from ._binary import i8 _handler = None @@ -30,7 +29,7 @@ def register_handler(handler): def _accept(prefix): - return prefix[0:4] == b"GRIB" and i8(prefix[7]) == 1 + return prefix[0:4] == b"GRIB" and prefix[7] == 1 class GribStubImageFile(ImageFile.StubImageFile): diff --git a/src/PIL/IcnsImagePlugin.py b/src/PIL/IcnsImagePlugin.py index 18e8403d1..2a63d75cb 100644 --- a/src/PIL/IcnsImagePlugin.py +++ b/src/PIL/IcnsImagePlugin.py @@ -23,10 +23,9 @@ import subprocess import sys import tempfile -from PIL import Image, ImageFile, PngImagePlugin -from PIL._binary import i8 +from PIL import Image, ImageFile, PngImagePlugin, features -enable_jpeg2k = hasattr(Image.core, "jp2klib_version") +enable_jpeg2k = features.check_codec("jpg_2000") if enable_jpeg2k: from PIL import Jpeg2KImagePlugin @@ -70,7 +69,7 @@ def read_32(fobj, start_length, size): byte = fobj.read(1) if not byte: break - byte = i8(byte) + byte = byte[0] if byte & 0x80: blocksize = byte - 125 byte = fobj.read(1) @@ -83,7 +82,7 @@ def read_32(fobj, start_length, size): if bytesleft <= 0: break if bytesleft != 0: - raise SyntaxError("Error reading channel [%r left]" % bytesleft) + raise SyntaxError(f"Error reading channel [{repr(bytesleft)} left]") band = Image.frombuffer("L", pixel_size, b"".join(data), "raw", "L", 0, 1) im.im.putband(band.im, band_ix) return {"RGB": im} @@ -321,7 +320,7 @@ def _save(im, fp, filename): last_w = None second_path = None for w in [16, 32, 128, 256, 512]: - prefix = "icon_{}x{}".format(w, w) + prefix = f"icon_{w}x{w}" first_path = os.path.join(iconset, prefix + ".png") if last_w == w: @@ -337,6 +336,10 @@ def _save(im, fp, filename): # iconutil -c icns -o {} {} + fp_only = not filename + if fp_only: + f, filename = tempfile.mkstemp(".icns") + os.close(f) convert_cmd = ["iconutil", "-c", "icns", "-o", filename, iconset] convert_proc = subprocess.Popen( convert_cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL @@ -349,6 +352,10 @@ def _save(im, fp, filename): if retcode: raise subprocess.CalledProcessError(retcode, convert_cmd) + if fp_only: + with open(filename, "rb") as f: + fp.write(f.read()) + Image.register_open(IcnsImageFile.format, IcnsImageFile, lambda x: x[:4] == b"icns") Image.register_extension(IcnsImageFile.format, ".icns") @@ -365,13 +372,12 @@ if __name__ == "__main__": print("Syntax: python IcnsImagePlugin.py [file]") sys.exit() - imf = IcnsImageFile(open(sys.argv[1], "rb")) - for size in imf.info["sizes"]: - imf.size = size - imf.load() - im = imf.im - im.save("out-%s-%s-%s.png" % size) - im = Image.open(sys.argv[1]) - im.save("out.png") - if sys.platform == "windows": - os.startfile("out.png") + with open(sys.argv[1], "rb") as fp: + imf = IcnsImageFile(fp) + for size in imf.info["sizes"]: + imf.size = size + imf.save("out-%s-%s-%s.png" % size) + with Image.open(sys.argv[1]) as im: + im.save("out.png") + if sys.platform == "windows": + os.startfile("out.png") diff --git a/src/PIL/IcoImagePlugin.py b/src/PIL/IcoImagePlugin.py index e4a74321b..e1bfa7a59 100644 --- a/src/PIL/IcoImagePlugin.py +++ b/src/PIL/IcoImagePlugin.py @@ -28,7 +28,8 @@ from io import BytesIO from math import ceil, log from . import BmpImagePlugin, Image, ImageFile, PngImagePlugin -from ._binary import i8, i16le as i16, i32le as i32 +from ._binary import i16le as i16 +from ._binary import i32le as i32 # # -------------------------------------------------------------------- @@ -52,6 +53,7 @@ def _save(im, fp, filename): sizes = list(sizes) fp.write(struct.pack("=8bpp) - "reserved": i8(s[3]), - "planes": i16(s[4:]), - "bpp": i16(s[6:]), - "size": i32(s[8:]), - "offset": i32(s[12:]), + "width": s[0], + "height": s[1], + "nb_color": s[2], # No. of colors in image (0 if >=8bpp) + "reserved": s[3], + "planes": i16(s, 4), + "bpp": i16(s, 6), + "size": i32(s, 8), + "offset": i32(s, 12), } # See Wikipedia diff --git a/src/PIL/ImImagePlugin.py b/src/PIL/ImImagePlugin.py index 12c9237f0..1dfc808c4 100644 --- a/src/PIL/ImImagePlugin.py +++ b/src/PIL/ImImagePlugin.py @@ -26,10 +26,10 @@ # +import os import re from . import Image, ImageFile, ImagePalette -from ._binary import i8 # -------------------------------------------------------------------- # Standard tags @@ -85,16 +85,16 @@ OPEN = { # ifunc95 extensions for i in ["8", "8S", "16", "16S", "32", "32F"]: - OPEN["L %s image" % i] = ("F", "F;%s" % i) - OPEN["L*%s image" % i] = ("F", "F;%s" % i) + OPEN[f"L {i} image"] = ("F", f"F;{i}") + OPEN[f"L*{i} image"] = ("F", f"F;{i}") for i in ["16", "16L", "16B"]: - OPEN["L %s image" % i] = ("I;%s" % i, "I;%s" % i) - OPEN["L*%s image" % i] = ("I;%s" % i, "I;%s" % i) + OPEN[f"L {i} image"] = (f"I;{i}", f"I;{i}") + OPEN[f"L*{i} image"] = (f"I;{i}", f"I;{i}") for i in ["32S"]: - OPEN["L %s image" % i] = ("I", "I;%s" % i) - OPEN["L*%s image" % i] = ("I", "I;%s" % i) + OPEN[f"L {i} image"] = ("I", f"I;{i}") + OPEN[f"L*{i} image"] = ("I", f"I;{i}") for i in range(2, 33): - OPEN["L*%s image" % i] = ("F", "F;%s" % i) + OPEN[f"L*{i} image"] = ("F", f"F;{i}") # -------------------------------------------------------------------- @@ -162,8 +162,8 @@ class ImImageFile(ImageFile.ImageFile): try: m = split.match(s) - except re.error: - raise SyntaxError("not an IM file") + except re.error as e: + raise SyntaxError("not an IM file") from e if m: @@ -222,14 +222,14 @@ class ImImageFile(ImageFile.ImageFile): linear = 1 # linear greyscale palette for i in range(256): if palette[i] == palette[i + 256] == palette[i + 512]: - if i8(palette[i]) != i: + if palette[i] != i: linear = 0 else: greyscale = 0 if self.mode in ["L", "LA", "P", "PA"]: if greyscale: if not linear: - self.lut = [i8(c) for c in palette[:256]] + self.lut = list(palette[:256]) else: if self.mode in ["L", "P"]: self.mode = self.rawmode = "P" @@ -239,7 +239,7 @@ class ImImageFile(ImageFile.ImageFile): self.palette = ImagePalette.raw("RGB;L", palette) elif self.mode == "RGB": if not greyscale or not linear: - self.lut = [i8(c) for c in palette] + self.lut = list(palette) self.frame = 0 @@ -340,16 +340,23 @@ def _save(im, fp, filename): try: image_type, rawmode = SAVE[im.mode] - except KeyError: - raise ValueError("Cannot save %s images as IM" % im.mode) + except KeyError as e: + raise ValueError(f"Cannot save {im.mode} images as IM") from e frames = im.encoderinfo.get("frames", 1) - fp.write(("Image type: %s image\r\n" % image_type).encode("ascii")) + fp.write(f"Image type: {image_type} image\r\n".encode("ascii")) if filename: - fp.write(("Name: %s\r\n" % filename).encode("ascii")) + # Each line must be 100 characters or less, + # or: SyntaxError("not an IM file") + # 8 characters are used for "Name: " and "\r\n" + # Keep just the filename, ditch the potentially overlong path + name, ext = os.path.splitext(os.path.basename(filename)) + name = "".join([name[: 92 - len(ext)], ext]) + + fp.write(f"Name: {name}\r\n".encode("ascii")) fp.write(("Image size (x*y): %d*%d\r\n" % im.size).encode("ascii")) - fp.write(("File size (no of images): %d\r\n" % frames).encode("ascii")) + fp.write(f"File size (no of images): {frames}\r\n".encode("ascii")) if im.mode in ["P", "PA"]: fp.write(b"Lut: 1\r\n") fp.write(b"\000" * (511 - fp.tell()) + b"\032") diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 2e88b6c6f..3751e48bb 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -35,16 +35,41 @@ import struct import sys import tempfile import warnings +import xml.etree.ElementTree from collections.abc import Callable, MutableMapping from pathlib import Path # VERSION was removed in Pillow 6.0.0. -# PILLOW_VERSION was removed in Pillow 7.0.0. +# PILLOW_VERSION is deprecated and will be removed in a future release. # Use __version__ instead. -from . import ImageMode, TiffTags, UnidentifiedImageError, __version__, _plugins -from ._binary import i8, i32le +from . import ( + ImageMode, + TiffTags, + UnidentifiedImageError, + __version__, + _plugins, + _raise_version_warning, +) +from ._binary import i32le from ._util import deferred_error, isPath +if sys.version_info >= (3, 7): + + def __getattr__(name): + if name == "PILLOW_VERSION": + _raise_version_warning() + return __version__ + raise AttributeError(f"module '{__name__}' has no attribute '{name}'") + + +else: + + from . import PILLOW_VERSION + + # Silence warning + assert PILLOW_VERSION + + logger = logging.getLogger(__name__) @@ -56,7 +81,7 @@ class DecompressionBombError(Exception): pass -# Limit to around a quarter gigabyte for a 24 bit (3 bpp) image +# Limit to around a quarter gigabyte for a 24-bit (3 bpp) image MAX_IMAGE_PIXELS = int(1024 * 1024 * 1024 // 4 // 3) @@ -71,8 +96,8 @@ try: if __version__ != getattr(core, "PILLOW_VERSION", None): raise ImportError( "The _imaging extension was built for another version of Pillow or PIL:\n" - "Core version: %s\n" - "Pillow version: %s" % (getattr(core, "PILLOW_VERSION", None), __version__) + f"Core version: {getattr(core, 'PILLOW_VERSION', None)}\n" + f"Pillow version: {__version__}" ) except ImportError as v: @@ -378,7 +403,7 @@ def init(): for plugin in _plugins: try: logger.debug("Importing %s", plugin) - __import__("PIL.%s" % plugin, globals(), locals(), []) + __import__(f"PIL.{plugin}", globals(), locals(), []) except ImportError as e: logger.debug("Image: failed to import %s: %s", plugin, e) @@ -409,8 +434,8 @@ def _getdecoder(mode, decoder_name, args, extra=()): try: # get decoder decoder = getattr(core, decoder_name + "_decoder") - except AttributeError: - raise OSError("decoder %s not available" % decoder_name) + except AttributeError as e: + raise OSError(f"decoder {decoder_name} not available") from e return decoder(mode, *args + extra) @@ -432,8 +457,8 @@ def _getencoder(mode, encoder_name, args, extra=()): try: # get encoder encoder = getattr(core, encoder_name + "_encoder") - except AttributeError: - raise OSError("encoder %s not available" % encoder_name) + except AttributeError as e: + raise OSError(f"encoder {encoder_name} not available") from e return encoder(mode, *args + extra) @@ -569,7 +594,8 @@ class Image: try: if hasattr(self, "_close__fp"): self._close__fp() - self.fp.close() + if self.fp: + self.fp.close() self.fp = None except Exception as msg: logger.debug("Error closing: %s", msg) @@ -639,12 +665,15 @@ class Image: ) def _repr_png_(self): - """ iPython display hook support + """iPython display hook support :returns: png version of the image as bytes """ b = io.BytesIO() - self.save(b, "PNG") + try: + self.save(b, "PNG") + except Exception as e: + raise ValueError("Could not save to PNG for display") from e return b.getvalue() @property @@ -692,7 +721,7 @@ class Image: :param encoder_name: What encoder to use. The default is to use the standard "raw" encoder. :param args: Extra arguments to the encoder. - :rtype: A bytes object. + :returns: A :py:class:`bytes` object. """ # may pass tuple instead of argument list @@ -717,15 +746,10 @@ class Image: if s: break if s < 0: - raise RuntimeError("encoder error %d in tobytes" % s) + raise RuntimeError(f"encoder error {s} in tobytes") return b"".join(data) - def tostring(self, *args, **kw): - raise NotImplementedError( - "tostring() has been removed. Please call tobytes() instead." - ) - def tobitmap(self, name="image"): """ Returns the image converted to an X11 bitmap. @@ -743,9 +767,9 @@ class Image: data = self.tobytes("xbm") return b"".join( [ - ("#define %s_width %d\n" % (name, self.size[0])).encode("ascii"), - ("#define %s_height %d\n" % (name, self.size[1])).encode("ascii"), - ("static char %s_bits[] = {\n" % name).encode("ascii"), + f"#define {name}_width {self.size[0]}\n".encode("ascii"), + f"#define {name}_height {self.size[1]}\n".encode("ascii"), + f"static char {name}_bits[] = {{\n".encode("ascii"), data, b"};", ] @@ -777,11 +801,6 @@ class Image: if s[1] != 0: raise ValueError("cannot decode image data") - def fromstring(self, *args, **kw): - raise NotImplementedError( - "fromstring() has been removed. Please call frombytes() instead." - ) - def load(self): """ Allocates storage for the image and loads the pixel data. In @@ -799,9 +818,15 @@ class Image: """ if self.im and self.palette and self.palette.dirty: # realize palette - self.im.putpalette(*self.palette.getdata()) + mode, arr = self.palette.getdata() + if mode == "RGBA": + mode = "RGB" + self.info["transparency"] = arr[3::4] + arr = bytes( + value for (index, value) in enumerate(arr) if index % 4 != 3 + ) + self.im.putpalette(mode, arr) self.palette.dirty = 0 - self.palette.mode = "RGB" self.palette.rawmode = None if "transparency" in self.info: if isinstance(self.info["transparency"], int): @@ -809,6 +834,8 @@ class Image: else: self.im.putpalettealphas(self.info["transparency"]) self.palette.mode = "RGBA" + else: + self.palette.mode = "RGB" if self.im: if cffi and USE_CFFI_ACCESS: @@ -840,7 +867,7 @@ class Image: and the palette can be represented without a palette. The current version supports all possible conversions between - "L", "RGB" and "CMYK." The **matrix** argument only supports "L" + "L", "RGB" and "CMYK." The ``matrix`` argument only supports "L" and "RGB". When translating a color image to greyscale (mode "L"), @@ -851,24 +878,24 @@ class Image: The default method of converting a greyscale ("L") or "RGB" image into a bilevel (mode "1") image uses Floyd-Steinberg dither to approximate the original image luminosity levels. If - dither is NONE, all values larger than 128 are set to 255 (white), + dither is :data:`NONE`, all values larger than 128 are set to 255 (white), all other values to 0 (black). To use other thresholds, use the :py:meth:`~PIL.Image.Image.point` method. - When converting from "RGBA" to "P" without a **matrix** argument, + When converting from "RGBA" to "P" without a ``matrix`` argument, this passes the operation to :py:meth:`~PIL.Image.Image.quantize`, - and **dither** and **palette** are ignored. + and ``dither`` and ``palette`` are ignored. :param mode: The requested mode. See: :ref:`concept-modes`. :param matrix: An optional conversion matrix. If given, this should be 4- or 12-tuple containing floating point values. :param dither: Dithering method, used when converting from mode "RGB" to "P" or from "RGB" or "L" to "1". - Available methods are NONE or FLOYDSTEINBERG (default). - Note that this is not used when **matrix** is supplied. + Available methods are :data:`NONE` or :data:`FLOYDSTEINBERG` (default). + Note that this is not used when ``matrix`` is supplied. :param palette: Palette to use when converting from mode "RGB" - to "P". Available palettes are WEB or ADAPTIVE. - :param colors: Number of colors to use for the ADAPTIVE palette. + to "P". Available palettes are :data:`WEB` or :data:`ADAPTIVE`. + :param colors: Number of colors to use for the :data:`ADAPTIVE` palette. Defaults to 256. :rtype: :py:class:`~PIL.Image.Image` :returns: An :py:class:`~PIL.Image.Image` object. @@ -946,10 +973,10 @@ class Image: if isinstance(t, tuple): try: t = trns_im.palette.getcolor(t) - except Exception: + except Exception as e: raise ValueError( "Couldn't allocate a palette color for transparency" - ) + ) from e trns_im.putpixel((0, 0), t) if mode in ("L", "RGB"): @@ -1002,8 +1029,8 @@ class Image: # normalize source image and try again im = self.im.convert(getmodebase(self.mode)) im = im.convert(mode, dither) - except KeyError: - raise ValueError("illegal conversion") + except KeyError as e: + raise ValueError("illegal conversion") from e new_im = self._new(im) if delete_trns: @@ -1026,16 +1053,18 @@ class Image: of colors. :param colors: The desired number of colors, <= 256 - :param method: 0 = median cut - 1 = maximum coverage - 2 = fast octree - 3 = libimagequant + :param method: :data:`MEDIANCUT` (median cut), + :data:`MAXCOVERAGE` (maximum coverage), + :data:`FASTOCTREE` (fast octree), + :data:`LIBIMAGEQUANT` (libimagequant; check support using + :py:func:`PIL.features.check_feature` + with ``feature="libimagequant"``). :param kmeans: Integer :param palette: Quantize to the palette of given :py:class:`PIL.Image.Image`. :param dither: Dithering method, used when converting from mode "RGB" to "P" or from "RGB" or "L" to "1". - Available methods are NONE or FLOYDSTEINBERG (default). + Available methods are :data:`NONE` or :data:`FLOYDSTEINBERG` (default). Default: 1 (legacy setting) :returns: A new image @@ -1163,7 +1192,7 @@ class Image: available filters, see the :py:mod:`~PIL.ImageFilter` module. :param filter: Filter kernel. - :returns: An :py:class:`~PIL.Image.Image` object. """ + :returns: An :py:class:`~PIL.Image.Image` object.""" from . import ImageFilter @@ -1188,7 +1217,7 @@ class Image: def getbands(self): """ Returns a tuple containing the name of each band in this image. - For example, **getbands** on an RGB image returns ("R", "G", "B"). + For example, ``getbands`` on an RGB image returns ("R", "G", "B"). :returns: A tuple containing band names. :rtype: tuple @@ -1242,7 +1271,7 @@ class Image: Note that the sequence object returned by this method is an internal PIL data type, which only supports certain sequence operations. To convert it to an ordinary sequence (e.g. for - printing), use **list(im.getdata())**. + printing), use ``list(im.getdata())``. :param band: What band to return. The default is to return all bands. To return a single band, pass in the index @@ -1276,7 +1305,28 @@ class Image: def getexif(self): if self._exif is None: self._exif = Exif() - self._exif.load(self.info.get("exif")) + + exif_info = self.info.get("exif") + if exif_info is None and "Raw profile type exif" in self.info: + exif_info = bytes.fromhex( + "".join(self.info["Raw profile type exif"].split("\n")[3:]) + ) + self._exif.load(exif_info) + + # XMP tags + if 0x0112 not in self._exif: + xmp_tags = self.info.get("XML:com.adobe.xmp") + if xmp_tags: + root = xml.etree.ElementTree.fromstring(xmp_tags) + for elem in root.iter(): + if elem.tag.endswith("}Description"): + orientation = elem.attrib.get( + "{http://ns.adobe.com/tiff/1.0/}Orientation" + ) + if orientation: + self._exif[0x0112] = int(orientation) + break + return self._exif def getim(self): @@ -1328,7 +1378,7 @@ class Image: self.load() x, y = self.im.getprojection() - return [i8(c) for c in x], [i8(c) for c in y] + return list(x), list(y) def histogram(self, mask=None, extrema=None): """ @@ -1386,11 +1436,6 @@ class Image: return self.im.entropy(extrema) return self.im.entropy() - def offset(self, xoffset, yoffset=None): - raise NotImplementedError( - "offset() has been removed. Please call ImageChops.offset() instead." - ) - def paste(self, im, box=None, mask=None): """ Pastes another image into this image. The box argument is either @@ -1472,7 +1517,7 @@ class Image: self.im.paste(im, box) def alpha_composite(self, im, dest=(0, 0), source=(0, 0)): - """ 'In-place' analog of Image.alpha_composite. Composites an image + """'In-place' analog of Image.alpha_composite. Composites an image onto this image. :param im: image to composite over this one @@ -1529,6 +1574,13 @@ class Image: single argument. The function is called once for each possible pixel value, and the resulting table is applied to all bands of the image. + + It may also be an :py:class:`~PIL.Image.ImagePointHandler` + object:: + + class Example(Image.ImagePointHandler): + def point(self, data): + # Return result :param mode: Output mode (default is same as input). In the current version, this can only be used if the source image has mode "L" or "P", and the output has mode "1" or the @@ -1577,16 +1629,16 @@ class Image: mode = getmodebase(self.mode) + "A" try: self.im.setmode(mode) - except (AttributeError, ValueError): + except (AttributeError, ValueError) as e: # do things the hard way im = self.im.convert(mode) if im.mode not in ("LA", "PA", "RGBA"): - raise ValueError # sanity check + raise ValueError from e # sanity check self.im = im self.pyaccess = None self.mode = self.im.mode - except (KeyError, ValueError): - raise ValueError("illegal image mode") + except KeyError as e: + raise ValueError("illegal image mode") from e if self.mode in ("LA", "PA"): band = 1 @@ -1631,12 +1683,14 @@ class Image: def putpalette(self, data, rawmode="RGB"): """ - Attaches a palette to this image. The image must be a "P", - "PA", "L" or "LA" image, and the palette sequence must contain - 768 integer values, where each group of three values represent - the red, green, and blue values for the corresponding pixel - index. Instead of an integer sequence, you can use an 8-bit - string. + Attaches a palette to this image. The image must be a "P", "PA", "L" + or "LA" image. + + The palette sequence must contain either 768 integer values, or 1024 + integer values if alpha is included. Each group of values represents + the red, green, blue (and alpha if included) values for the + corresponding pixel index. Instead of an integer sequence, you can use + an 8-bit string. :param data: A palette sequence (either a list or a string). :param rawmode: The raw mode of the palette. @@ -1700,7 +1754,7 @@ class Image: Rewrites the image to reorder the palette. :param dest_map: A list of indexes into the original palette. - e.g. [1,0] would swap a two item palette, and list(range(256)) + e.g. ``[1,0]`` would swap a two item palette, and ``list(range(256))`` is the identity transform. :param source_palette: Bytes or None. :returns: An :py:class:`~PIL.Image.Image` object. @@ -1794,12 +1848,12 @@ class Image: :param size: The requested size in pixels, as a 2-tuple: (width, height). :param resample: An optional resampling filter. This can be - one of :py:attr:`PIL.Image.NEAREST`, :py:attr:`PIL.Image.BOX`, - :py:attr:`PIL.Image.BILINEAR`, :py:attr:`PIL.Image.HAMMING`, - :py:attr:`PIL.Image.BICUBIC` or :py:attr:`PIL.Image.LANCZOS`. - Default filter is :py:attr:`PIL.Image.BICUBIC`. + one of :py:data:`PIL.Image.NEAREST`, :py:data:`PIL.Image.BOX`, + :py:data:`PIL.Image.BILINEAR`, :py:data:`PIL.Image.HAMMING`, + :py:data:`PIL.Image.BICUBIC` or :py:data:`PIL.Image.LANCZOS`. + Default filter is :py:data:`PIL.Image.BICUBIC`. If the image has mode "1" or "P", it is - always set to :py:attr:`PIL.Image.NEAREST`. + always set to :py:data:`PIL.Image.NEAREST`. See: :ref:`concept-filters`. :param box: An optional 4-tuple of floats providing the source image region to be scaled. @@ -1821,7 +1875,7 @@ class Image: """ if resample not in (NEAREST, BILINEAR, BICUBIC, LANCZOS, BOX, HAMMING): - message = "Unknown resampling filter ({}).".format(resample) + message = f"Unknown resampling filter ({resample})." filters = [ "{} ({})".format(filter[1], filter[0]) @@ -1866,7 +1920,11 @@ class Image: factor_y = int((box[3] - box[1]) / size[1] / reducing_gap) or 1 if factor_x > 1 or factor_y > 1: reduce_box = self._get_safe_box(size, resample, box) - self = self.reduce((factor_x, factor_y), box=reduce_box) + factor = (factor_x, factor_y) + if callable(self.reduce): + self = self.reduce(factor, box=reduce_box) + else: + self = Image.reduce(self, factor, box=reduce_box) box = ( (box[0] - reduce_box[0]) / factor_x, (box[1] - reduce_box[1]) / factor_y, @@ -1878,16 +1936,16 @@ class Image: def reduce(self, factor, box=None): """ - Returns a copy of the image reduced by `factor` times. - If the size of the image is not dividable by the `factor`, + Returns a copy of the image reduced ``factor`` times. + If the size of the image is not dividable by ``factor``, the resulting size will be rounded up. :param factor: A greater than 0 integer or tuple of two integers for width and height separately. :param box: An optional 4-tuple of ints providing the source image region to be reduced. - The values must be within (0, 0, width, height) rectangle. - If omitted or None, the entire source is used. + The values must be within ``(0, 0, width, height)`` rectangle. + If omitted or ``None``, the entire source is used. """ if not isinstance(factor, (list, tuple)): factor = (factor, factor) @@ -1925,12 +1983,12 @@ class Image: :param angle: In degrees counter clockwise. :param resample: An optional resampling filter. This can be - one of :py:attr:`PIL.Image.NEAREST` (use nearest neighbour), - :py:attr:`PIL.Image.BILINEAR` (linear interpolation in a 2x2 - environment), or :py:attr:`PIL.Image.BICUBIC` + one of :py:data:`PIL.Image.NEAREST` (use nearest neighbour), + :py:data:`PIL.Image.BILINEAR` (linear interpolation in a 2x2 + environment), or :py:data:`PIL.Image.BICUBIC` (cubic spline interpolation in a 4x4 environment). If omitted, or if the image has mode "1" or "P", it is - set to :py:attr:`PIL.Image.NEAREST`. See :ref:`concept-filters`. + set to :py:data:`PIL.Image.NEAREST`. See :ref:`concept-filters`. :param expand: Optional expansion flag. If true, expands the output image to make it large enough to hold the entire rotated image. If false or omitted, make the output image the same size as the @@ -2052,7 +2110,7 @@ class Image: :returns: None :exception ValueError: If the output format could not be determined from the file name. Use the format option to solve this. - :exception IOError: If the file could not be written. The file + :exception OSError: If the file could not be written. The file may have been created, and may contain partial data. """ @@ -2084,8 +2142,8 @@ class Image: init() try: format = EXTENSION[ext] - except KeyError: - raise ValueError("unknown file extension: {}".format(ext)) + except KeyError as e: + raise ValueError(f"unknown file extension: {ext}") from e if format.upper() not in SAVE: init() @@ -2113,11 +2171,14 @@ class Image: """ Seeks to the given frame in this sequence file. If you seek beyond the end of the sequence, the method raises an - **EOFError** exception. When a sequence file is opened, the + ``EOFError`` exception. When a sequence file is opened, the library automatically seeks to frame 0. See :py:meth:`~PIL.Image.Image.tell`. + If defined, :attr:`~PIL.Image.Image.n_frames` refers to the + number of available frames. + :param frame: Frame number, starting at 0. :exception EOFError: If the call attempts to seek beyond the end of the sequence. @@ -2129,8 +2190,10 @@ class Image: def show(self, title=None, command=None): """ - Displays this image. This method is mainly intended for - debugging purposes. + Displays this image. This method is mainly intended for debugging purposes. + + This method calls :py:func:`PIL.ImageShow.show` internally. You can use + :py:func:`PIL.ImageShow.register` to override its default behaviour. The image is first saved to a temporary file. By default, it will be in PNG format. @@ -2142,11 +2205,16 @@ class Image: On Windows, the image is opened with the standard PNG display utility. - :param title: Optional title to use for the image window, - where possible. - :param command: command used to show the image + :param title: Optional title to use for the image window, where possible. """ + if command is not None: + warnings.warn( + "The command parameter is deprecated and will be removed in Pillow 9 " + "(2022-01-02). Use a subclass of ImageShow.Viewer instead.", + DeprecationWarning, + ) + _show(self, title=title, command=command) def split(self): @@ -2186,8 +2254,8 @@ class Image: if isinstance(channel, str): try: channel = self.getbands().index(channel) - except ValueError: - raise ValueError('The image has no channel "{}"'.format(channel)) + except ValueError as e: + raise ValueError(f'The image has no channel "{channel}"') from e return self._new(self.im.getband(channel)) @@ -2195,6 +2263,9 @@ class Image: """ Returns the current frame number. See :py:meth:`~PIL.Image.Image.seek`. + If defined, :attr:`~PIL.Image.Image.n_frames` refers to the + number of available frames. + :returns: Frame number, starting with 0. """ return 0 @@ -2215,10 +2286,12 @@ class Image: :param size: Requested size. :param resample: Optional resampling filter. This can be one - of :py:attr:`PIL.Image.NEAREST`, :py:attr:`PIL.Image.BILINEAR`, - :py:attr:`PIL.Image.BICUBIC`, or :py:attr:`PIL.Image.LANCZOS`. - If omitted, it defaults to :py:attr:`PIL.Image.BICUBIC`. - (was :py:attr:`PIL.Image.NEAREST` prior to version 2.5.0). + of :py:data:`PIL.Image.NEAREST`, :py:data:`PIL.Image.BOX`, + :py:data:`PIL.Image.BILINEAR`, :py:data:`PIL.Image.HAMMING`, + :py:data:`PIL.Image.BICUBIC` or :py:data:`PIL.Image.LANCZOS`. + If omitted, it defaults to :py:data:`PIL.Image.BICUBIC`. + (was :py:data:`PIL.Image.NEAREST` prior to version 2.5.0). + See: :ref:`concept-filters`. :param reducing_gap: Apply optimization by resizing the image in two steps. First, reducing the image by integer times using :py:meth:`~PIL.Image.Image.reduce` or @@ -2236,20 +2309,24 @@ class Image: :returns: None """ - # preserve aspect ratio - x, y = self.size - if x > size[0]: - y = max(round(y * size[0] / x), 1) - x = round(size[0]) - if y > size[1]: - x = max(round(x * size[1] / y), 1) - y = round(size[1]) - size = x, y - box = None - - if size == self.size: + x, y = map(math.floor, size) + if x >= self.width and y >= self.height: return + def round_aspect(number, key): + return max(min(math.floor(number), math.ceil(number), key=key), 1) + + # preserve aspect ratio + aspect = self.width / self.height + if x / y >= aspect: + x = round_aspect(y * aspect, key=lambda n: abs(aspect - n / y)) + else: + y = round_aspect( + x / aspect, key=lambda n: 0 if n == 0 else abs(aspect - x / n) + ) + size = (x, y) + + box = None if reducing_gap is not None: res = self.draft(None, (size[0] * reducing_gap, size[1] * reducing_gap)) if res is not None: @@ -2277,36 +2354,37 @@ class Image: :param size: The output size. :param method: The transformation method. This is one of - :py:attr:`PIL.Image.EXTENT` (cut out a rectangular subregion), - :py:attr:`PIL.Image.AFFINE` (affine transform), - :py:attr:`PIL.Image.PERSPECTIVE` (perspective transform), - :py:attr:`PIL.Image.QUAD` (map a quadrilateral to a rectangle), or - :py:attr:`PIL.Image.MESH` (map a number of source quadrilaterals + :py:data:`PIL.Image.EXTENT` (cut out a rectangular subregion), + :py:data:`PIL.Image.AFFINE` (affine transform), + :py:data:`PIL.Image.PERSPECTIVE` (perspective transform), + :py:data:`PIL.Image.QUAD` (map a quadrilateral to a rectangle), or + :py:data:`PIL.Image.MESH` (map a number of source quadrilaterals in one operation). It may also be an :py:class:`~PIL.Image.ImageTransformHandler` object:: class Example(Image.ImageTransformHandler): - def transform(size, method, data, resample, fill=1): + def transform(self, size, data, resample, fill=1): # Return result - It may also be an object with a :py:meth:`~method.getdata` method - that returns a tuple supplying new **method** and **data** values:: + It may also be an object with a ``method.getdata`` method + that returns a tuple supplying new ``method`` and ``data`` values:: - class Example(object): + class Example: def getdata(self): method = Image.EXTENT data = (0, 0, 100, 100) return method, data :param data: Extra data to the transformation method. :param resample: Optional resampling filter. It can be one of - :py:attr:`PIL.Image.NEAREST` (use nearest neighbour), - :py:attr:`PIL.Image.BILINEAR` (linear interpolation in a 2x2 - environment), or :py:attr:`PIL.Image.BICUBIC` (cubic spline + :py:data:`PIL.Image.NEAREST` (use nearest neighbour), + :py:data:`PIL.Image.BILINEAR` (linear interpolation in a 2x2 + environment), or :py:data:`PIL.Image.BICUBIC` (cubic spline interpolation in a 4x4 environment). If omitted, or if the image - has mode "1" or "P", it is set to :py:attr:`PIL.Image.NEAREST`. - :param fill: If **method** is an + has mode "1" or "P", it is set to :py:data:`PIL.Image.NEAREST`. + See: :ref:`concept-filters`. + :param fill: If ``method`` is an :py:class:`~PIL.Image.ImageTransformHandler` object, this is one of the arguments passed to it. Otherwise, it is unused. :param fillcolor: Optional fill color for the area outside the @@ -2399,9 +2477,9 @@ class Image: BOX: "Image.BOX", HAMMING: "Image.HAMMING", LANCZOS: "Image.LANCZOS/Image.ANTIALIAS", - }[resample] + " ({}) cannot be used.".format(resample) + }[resample] + f" ({resample}) cannot be used." else: - message = "Unknown resampling filter ({}).".format(resample) + message = f"Unknown resampling filter ({resample})." filters = [ "{} ({})".format(filter[1], filter[0]) @@ -2428,10 +2506,10 @@ class Image: """ Transpose image (flip or rotate in 90 degree steps) - :param method: One of :py:attr:`PIL.Image.FLIP_LEFT_RIGHT`, - :py:attr:`PIL.Image.FLIP_TOP_BOTTOM`, :py:attr:`PIL.Image.ROTATE_90`, - :py:attr:`PIL.Image.ROTATE_180`, :py:attr:`PIL.Image.ROTATE_270`, - :py:attr:`PIL.Image.TRANSPOSE` or :py:attr:`PIL.Image.TRANSVERSE`. + :param method: One of :py:data:`PIL.Image.FLIP_LEFT_RIGHT`, + :py:data:`PIL.Image.FLIP_TOP_BOTTOM`, :py:data:`PIL.Image.ROTATE_90`, + :py:data:`PIL.Image.ROTATE_180`, :py:data:`PIL.Image.ROTATE_270`, + :py:data:`PIL.Image.TRANSPOSE` or :py:data:`PIL.Image.TRANSVERSE`. :returns: Returns a flipped or rotated copy of this image. """ @@ -2469,12 +2547,20 @@ class Image: class ImagePointHandler: - # used as a mixin by point transforms (for use with im.point) + """ + Used as a mixin by point transforms + (for use with :py:meth:`~PIL.Image.Image.point`) + """ + pass class ImageTransformHandler: - # used as a mixin by geometry transforms (for use with im.transform) + """ + Used as a mixin by geometry transforms + (for use with :py:meth:`~PIL.Image.Image.transform`) + """ + pass @@ -2586,12 +2672,6 @@ def frombytes(mode, size, data, decoder_name="raw", *args): return im -def fromstring(*args, **kw): - raise NotImplementedError( - "fromstring() has been removed. Please call frombytes() instead." - ) - - def frombuffer(mode, size, data, decoder_name="raw", *args): """ Creates an image memory referencing pixel data in a byte buffer. @@ -2603,7 +2683,7 @@ def frombuffer(mode, size, data, decoder_name="raw", *args): Note that this function decodes pixel data only, not entire images. If you have an entire image file in a string, wrap it in a - **BytesIO** object, and use :py:func:`~PIL.Image.open` to load it. + :py:class:`~io.BytesIO` object, and use :py:func:`~PIL.Image.open` to load it. In the current version, the default parameters used for the "raw" decoder differs from that used for :py:func:`~PIL.Image.frombytes`. This is a @@ -2650,7 +2730,7 @@ def fromarray(obj, mode=None): Creates an image memory from an object exporting the array interface (using the buffer protocol). - If **obj** is not contiguous, then the tobytes method is called + If ``obj`` is not contiguous, then the ``tobytes`` method is called and :py:func:`~PIL.Image.frombuffer` is used. If you have an image in NumPy:: @@ -2678,12 +2758,12 @@ def fromarray(obj, mode=None): if mode is None: try: typekey = (1, 1) + shape[2:], arr["typestr"] - except KeyError: - raise TypeError("Cannot handle this data type") + except KeyError as e: + raise TypeError("Cannot handle this data type") from e try: mode, rawmode = _fromarray_typemap[typekey] - except KeyError: - raise TypeError("Cannot handle this data type: %s, %s" % typekey) + except KeyError as e: + raise TypeError("Cannot handle this data type: %s, %s" % typekey) from e else: rawmode = mode if mode in ["1", "L", "I", "P", "F"]: @@ -2693,9 +2773,9 @@ def fromarray(obj, mode=None): else: ndmax = 4 if ndim > ndmax: - raise ValueError("Too many dimensions: %d > %d." % (ndim, ndmax)) + raise ValueError(f"Too many dimensions: {ndim} > {ndmax}.") - size = shape[1], shape[0] + size = 1 if ndim == 1 else shape[1], shape[0] if strides is not None: if hasattr(obj, "tobytes"): obj = obj.tobytes() @@ -2759,19 +2839,19 @@ def _decompression_bomb_check(size): if pixels > 2 * MAX_IMAGE_PIXELS: raise DecompressionBombError( - "Image size (%d pixels) exceeds limit of %d pixels, " - "could be decompression bomb DOS attack." % (pixels, 2 * MAX_IMAGE_PIXELS) + f"Image size ({pixels} pixels) exceeds limit of {2 * MAX_IMAGE_PIXELS} " + "pixels, could be decompression bomb DOS attack." ) if pixels > MAX_IMAGE_PIXELS: warnings.warn( - "Image size (%d pixels) exceeds limit of %d pixels, " - "could be decompression bomb DOS attack." % (pixels, MAX_IMAGE_PIXELS), + f"Image size ({pixels} pixels) exceeds limit of {MAX_IMAGE_PIXELS} pixels, " + "could be decompression bomb DOS attack.", DecompressionBombWarning, ) -def open(fp, mode="r"): +def open(fp, mode="r", formats=None): """ Opens and identifies the given image file. @@ -2782,26 +2862,37 @@ def open(fp, mode="r"): :py:func:`~PIL.Image.new`. See :ref:`file-handling`. :param fp: A filename (string), pathlib.Path object or a file object. - The file object must implement :py:meth:`~file.read`, - :py:meth:`~file.seek`, and :py:meth:`~file.tell` methods, + The file object must implement ``file.read``, + ``file.seek``, and ``file.tell`` methods, and be opened in binary mode. :param mode: The mode. If given, this argument must be "r". + :param formats: A list or tuple of formats to attempt to load the file in. + This can be used to restrict the set of formats checked. + Pass ``None`` to try all supported formats. You can print the set of + available formats by running ``python -m PIL`` or using + the :py:func:`PIL.features.pilinfo` function. :returns: An :py:class:`~PIL.Image.Image` object. :exception FileNotFoundError: If the file cannot be found. :exception PIL.UnidentifiedImageError: If the image cannot be opened and identified. :exception ValueError: If the ``mode`` is not "r", or if a ``StringIO`` instance is used for ``fp``. + :exception TypeError: If ``formats`` is not ``None``, a list or a tuple. """ if mode != "r": - raise ValueError("bad mode %r" % mode) + raise ValueError(f"bad mode {repr(mode)}") elif isinstance(fp, io.StringIO): raise ValueError( "StringIO cannot be used to open an image. " "Binary data must be used instead." ) + if formats is None: + formats = ID + elif not isinstance(formats, (list, tuple)): + raise TypeError("formats must be a list or tuple") + exclusive_fp = False filename = "" if isinstance(fp, Path): @@ -2825,8 +2916,10 @@ def open(fp, mode="r"): accept_warnings = [] - def _open_core(fp, filename, prefix): - for i in ID: + def _open_core(fp, filename, prefix, formats): + for i in formats: + if i not in OPEN: + init() try: factory, accept = OPEN[i] result = not accept or accept(prefix) @@ -2848,11 +2941,11 @@ def open(fp, mode="r"): raise return None - im = _open_core(fp, filename, prefix) + im = _open_core(fp, filename, prefix, formats) if im is None: if init(): - im = _open_core(fp, filename, prefix) + im = _open_core(fp, filename, prefix, formats) if im: im._exclusive_fp = exclusive_fp @@ -3081,17 +3174,25 @@ def register_encoder(name, encoder): # -------------------------------------------------------------------- -# Simple display support. User code may override this. +# Simple display support. def _show(image, **options): - # override me, as necessary + options["_internal_pillow"] = True _showxv(image, **options) def _showxv(image, title=None, **options): from . import ImageShow + if "_internal_pillow" in options: + del options["_internal_pillow"] + else: + warnings.warn( + "_showxv is deprecated and will be removed in Pillow 9 (2022-01-02). " + "Use Image.show instead.", + DeprecationWarning, + ) ImageShow.show(image, title, **options) @@ -3168,13 +3269,13 @@ def _apply_env_variables(env=None): try: var = int(var) * units except ValueError: - warnings.warn("{} is not int".format(var_name)) + warnings.warn(f"{var_name} is not int") continue try: setter(var) except ValueError as e: - warnings.warn("{}: {}".format(var_name, e)) + warnings.warn(f"{var_name}: {e}") _apply_env_variables() @@ -3192,14 +3293,14 @@ class Exif(MutableMapping): def _fixup(self, value): try: - if len(value) == 1 and not isinstance(value, dict): + if len(value) == 1 and isinstance(value, tuple): return value[0] except Exception: pass return value def _fixup_dict(self, src_dict): - # Helper function for _getexif() + # Helper function # returns a dict with any single item tuples/lists as individual values return {k: self._fixup(v) for k, v in src_dict.items()} @@ -3213,7 +3314,7 @@ class Exif(MutableMapping): else: from . import TiffImagePlugin - info = TiffImagePlugin.ImageFileDirectory_v1(self.head) + info = TiffImagePlugin.ImageFileDirectory_v2(self.head) info.load(self.fp) return self._fixup_dict(info) @@ -3233,12 +3334,14 @@ class Exif(MutableMapping): if not data: return - self.fp = io.BytesIO(data[6:]) + if data.startswith(b"Exif\x00\x00"): + data = data[6:] + self.fp = io.BytesIO(data) self.head = self.fp.read(8) # process dictionary from . import TiffImagePlugin - self._info = TiffImagePlugin.ImageFileDirectory_v1(self.head) + self._info = TiffImagePlugin.ImageFileDirectory_v2(self.head) self.endian = self._info._endian self.fp.seek(self._info.next) self._info.load(self.fp) @@ -3249,7 +3352,7 @@ class Exif(MutableMapping): self._data.update(ifd) self._ifds[0x8769] = ifd - def tobytes(self, offset=0): + def tobytes(self, offset=8): from . import TiffImagePlugin if self.endian == "<": @@ -3271,7 +3374,7 @@ class Exif(MutableMapping): if self[0x927C][:8] == b"FUJIFILM": exif_data = self[0x927C] - ifd_offset = i32le(exif_data[8:12]) + ifd_offset = i32le(exif_data, 8) ifd_data = exif_data[ifd_offset:] makernote = {} @@ -3295,8 +3398,8 @@ class Exif(MutableMapping): if len(data) != size: warnings.warn( "Possibly corrupt EXIF MakerNote data. " - "Expecting to read %d bytes but only got %d." - " Skipping tag %s" % (size, len(data), ifd_tag) + f"Expecting to read {size} bytes but only got " + f"{len(data)}. Skipping tag {ifd_tag}" ) continue @@ -3378,7 +3481,8 @@ class Exif(MutableMapping): def __delitem__(self, tag): if self._info is not None and tag in self._info: del self._info[tag] - del self._data[tag] + else: + del self._data[tag] def __iter__(self): keys = set(self._data) diff --git a/src/PIL/ImageChops.py b/src/PIL/ImageChops.py index b1f71b5e7..61d3a295b 100644 --- a/src/PIL/ImageChops.py +++ b/src/PIL/ImageChops.py @@ -54,7 +54,7 @@ def invert(image): def lighter(image1, image2): """ Compares the two images, pixel by pixel, and returns a new image containing - the lighter values. At least one of the images must have mode "1". + the lighter values. .. code-block:: python @@ -71,7 +71,7 @@ def lighter(image1, image2): def darker(image1, image2): """ Compares the two images, pixel by pixel, and returns a new image containing - the darker values. At least one of the images must have mode "1". + the darker values. .. code-block:: python @@ -88,7 +88,7 @@ def darker(image1, image2): def difference(image1, image2): """ Returns the absolute value of the pixel-by-pixel difference between the two - images. At least one of the images must have mode "1". + images. .. code-block:: python @@ -107,8 +107,7 @@ def multiply(image1, image2): Superimposes two images on top of each other. If you multiply an image with a solid black image, the result is black. If - you multiply with a solid white image, the image is unaffected. At least - one of the images must have mode "1". + you multiply with a solid white image, the image is unaffected. .. code-block:: python @@ -124,8 +123,7 @@ def multiply(image1, image2): def screen(image1, image2): """ - Superimposes two inverted images on top of each other. At least one of the - images must have mode "1". + Superimposes two inverted images on top of each other. .. code-block:: python @@ -139,11 +137,46 @@ def screen(image1, image2): return image1._new(image1.im.chop_screen(image2.im)) +def soft_light(image1, image2): + """ + Superimposes two images on top of each other using the Soft Light algorithm + + :rtype: :py:class:`~PIL.Image.Image` + """ + + image1.load() + image2.load() + return image1._new(image1.im.chop_soft_light(image2.im)) + + +def hard_light(image1, image2): + """ + Superimposes two images on top of each other using the Hard Light algorithm + + :rtype: :py:class:`~PIL.Image.Image` + """ + + image1.load() + image2.load() + return image1._new(image1.im.chop_hard_light(image2.im)) + + +def overlay(image1, image2): + """ + Superimposes two images on top of each other using the Overlay algorithm + + :rtype: :py:class:`~PIL.Image.Image` + """ + + image1.load() + image2.load() + return image1._new(image1.im.chop_overlay(image2.im)) + + def add(image1, image2, scale=1.0, offset=0): """ Adds two images, dividing the result by scale and adding the offset. If omitted, scale defaults to 1.0, and offset to 0.0. - At least one of the images must have mode "1". .. code-block:: python @@ -160,8 +193,7 @@ def add(image1, image2, scale=1.0, offset=0): def subtract(image1, image2, scale=1.0, offset=0): """ Subtracts two images, dividing the result by scale and adding the offset. - If omitted, scale defaults to 1.0, and offset to 0.0. At least one of the - images must have mode "1". + If omitted, scale defaults to 1.0, and offset to 0.0. .. code-block:: python @@ -176,8 +208,7 @@ def subtract(image1, image2, scale=1.0, offset=0): def add_modulo(image1, image2): - """Add two images, without clipping the result. At least one of the images - must have mode "1". + """Add two images, without clipping the result. .. code-block:: python @@ -192,8 +223,7 @@ def add_modulo(image1, image2): def subtract_modulo(image1, image2): - """Subtract two images, without clipping the result. At least one of the - images must have mode "1". + """Subtract two images, without clipping the result. .. code-block:: python @@ -208,8 +238,12 @@ def subtract_modulo(image1, image2): def logical_and(image1, image2): - """Logical AND between two images. At least one of the images must have - mode "1". + """Logical AND between two images. + + Both of the images must have mode "1". If you would like to perform a + logical AND on an image with a mode other than "1", try + :py:meth:`~PIL.ImageChops.multiply` instead, using a black-and-white mask + as the second image. .. code-block:: python @@ -224,8 +258,9 @@ def logical_and(image1, image2): def logical_or(image1, image2): - """Logical OR between two images. At least one of the images must have - mode "1". + """Logical OR between two images. + + Both of the images must have mode "1". .. code-block:: python @@ -240,8 +275,9 @@ def logical_or(image1, image2): def logical_xor(image1, image2): - """Logical XOR between two images. At least one of the images must have - mode "1". + """Logical XOR between two images. + + Both of the images must have mode "1". .. code-block:: python @@ -257,7 +293,7 @@ def logical_xor(image1, image2): def blend(image1, image2, alpha): """Blend images using constant transparency weight. Alias for - :py:meth:`PIL.Image.Image.blend`. + :py:func:`PIL.Image.blend`. :rtype: :py:class:`~PIL.Image.Image` """ @@ -267,7 +303,7 @@ def blend(image1, image2, alpha): def composite(image1, image2, mask): """Create composite using transparency mask. Alias for - :py:meth:`PIL.Image.Image.composite`. + :py:func:`PIL.Image.composite`. :rtype: :py:class:`~PIL.Image.Image` """ @@ -277,8 +313,8 @@ def composite(image1, image2, mask): def offset(image, xoffset, yoffset=None): """Returns a copy of the image where data has been offset by the given - distances. Data wraps around the edges. If **yoffset** is omitted, it - is assumed to be equal to **xoffset**. + distances. Data wraps around the edges. If ``yoffset`` is omitted, it + is assumed to be equal to ``xoffset``. :param xoffset: The horizontal distance. :param yoffset: The vertical distance. If omitted, both diff --git a/src/PIL/ImageCms.py b/src/PIL/ImageCms.py index d05b3102c..8c4740ddc 100644 --- a/src/PIL/ImageCms.py +++ b/src/PIL/ImageCms.py @@ -159,6 +159,14 @@ class ImageCmsProfile: """ if isinstance(profile, str): + if sys.platform == "win32": + profile_bytes_path = profile.encode() + try: + profile_bytes_path.decode("ascii") + except UnicodeDecodeError: + with open(profile, "rb") as f: + self._set(core.profile_frombytes(f.read())) + return self._set(core.profile_open(profile), profile) elif hasattr(profile, "read"): self._set(core.profile_frombytes(profile.read())) @@ -192,9 +200,9 @@ class ImageCmsTransform(Image.ImagePointHandler): """ Transform. This can be used with the procedural API, or with the standard - Image.point() method. + :py:func:`~PIL.Image.Image.point` method. - Will return the output profile in the output.info['icc_profile']. + Will return the output profile in the ``output.info['icc_profile']``. """ def __init__( @@ -250,24 +258,23 @@ class ImageCmsTransform(Image.ImagePointHandler): def get_display_profile(handle=None): - """ (experimental) Fetches the profile for the current display device. - :returns: None if the profile is not known. + """ + (experimental) Fetches the profile for the current display device. + + :returns: ``None`` if the profile is not known. """ - if sys.platform == "win32": - from PIL import ImageWin + if sys.platform != "win32": + return None - if isinstance(handle, ImageWin.HDC): - profile = core.get_display_profile_win32(handle, 1) - else: - profile = core.get_display_profile_win32(handle or 0) + from PIL import ImageWin + + if isinstance(handle, ImageWin.HDC): + profile = core.get_display_profile_win32(handle, 1) else: - try: - get = _imagingcms.get_display_profile - except AttributeError: - return None - else: - profile = get() + profile = core.get_display_profile_win32(handle or 0) + if profile is None: + return None return ImageCmsProfile(profile) @@ -278,8 +285,8 @@ def get_display_profile(handle=None): class PyCMSError(Exception): - """ (pyCMS) Exception class. - This is used for all errors in the pyCMS API. """ + """(pyCMS) Exception class. + This is used for all errors in the pyCMS API.""" pass @@ -295,27 +302,28 @@ def profileToProfile( ): """ (pyCMS) Applies an ICC transformation to a given image, mapping from - inputProfile to outputProfile. + ``inputProfile`` to ``outputProfile``. If the input or output profiles specified are not valid filenames, a - PyCMSError will be raised. If inPlace is True and outputMode != im.mode, - a PyCMSError will be raised. If an error occurs during application of - the profiles, a PyCMSError will be raised. If outputMode is not a mode - supported by the outputProfile (or by pyCMS), a PyCMSError will be - raised. + :exc:`PyCMSError` will be raised. If ``inPlace`` is ``True`` and + ``outputMode != im.mode``, a :exc:`PyCMSError` will be raised. + If an error occurs during application of the profiles, + a :exc:`PyCMSError` will be raised. + If ``outputMode`` is not a mode supported by the ``outputProfile`` (or by pyCMS), + a :exc:`PyCMSError` will be raised. - This function applies an ICC transformation to im from inputProfile's - color space to outputProfile's color space using the specified rendering + This function applies an ICC transformation to im from ``inputProfile``'s + color space to ``outputProfile``'s color space using the specified rendering intent to decide how to handle out-of-gamut colors. - OutputMode can be used to specify that a color mode conversion is to + ``outputMode`` can be used to specify that a color mode conversion is to be done using these profiles, but the specified profiles must be able to handle that mode. I.e., if converting im from RGB to CMYK using profiles, the input profile must handle RGB data, and the output profile must handle CMYK data. - :param im: An open PIL image object (i.e. Image.new(...) or - Image.open(...), etc.) + :param im: An open :py:class:`~PIL.Image.Image` object (i.e. Image.new(...) + or Image.open(...), etc.) :param inputProfile: String, as a valid filename path to the ICC input profile you wish to use for this image, or a profile object :param outputProfile: String, as a valid filename path to the ICC output @@ -335,12 +343,12 @@ def profileToProfile( MUST be the same mode as the input, or omitted completely. If omitted, the outputMode will be the same as the mode of the input image (im.mode) - :param inPlace: Boolean. If True, the original image is modified in-place, - and None is returned. If False (default), a new Image object is - returned with the transform applied. + :param inPlace: Boolean. If ``True``, the original image is modified in-place, + and ``None`` is returned. If ``False`` (default), a new + :py:class:`~PIL.Image.Image` object is returned with the transform applied. :param flags: Integer (0-...) specifying additional flags - :returns: Either None or a new PIL image object, depending on value of - inPlace + :returns: Either None or a new :py:class:`~PIL.Image.Image` object, depending on + the value of ``inPlace`` :exception PyCMSError: """ @@ -372,7 +380,7 @@ def profileToProfile( else: imOut = transform.apply(im) except (OSError, TypeError, ValueError) as v: - raise PyCMSError(v) + raise PyCMSError(v) from v return imOut @@ -384,8 +392,8 @@ def getOpenProfile(profileFilename): The PyCMSProfile object can be passed back into pyCMS for use in creating transforms and such (as in ImageCms.buildTransformFromOpenProfiles()). - If profileFilename is not a valid filename for an ICC profile, a PyCMSError - will be raised. + If ``profileFilename`` is not a valid filename for an ICC profile, + a :exc:`PyCMSError` will be raised. :param profileFilename: String, as a valid filename path to the ICC profile you wish to open, or a file-like object. @@ -396,7 +404,7 @@ def getOpenProfile(profileFilename): try: return ImageCmsProfile(profileFilename) except (OSError, TypeError, ValueError) as v: - raise PyCMSError(v) + raise PyCMSError(v) from v def buildTransform( @@ -408,21 +416,21 @@ def buildTransform( flags=0, ): """ - (pyCMS) Builds an ICC transform mapping from the inputProfile to the - outputProfile. Use applyTransform to apply the transform to a given + (pyCMS) Builds an ICC transform mapping from the ``inputProfile`` to the + ``outputProfile``. Use applyTransform to apply the transform to a given image. If the input or output profiles specified are not valid filenames, a - PyCMSError will be raised. If an error occurs during creation of the - transform, a PyCMSError will be raised. + :exc:`PyCMSError` will be raised. If an error occurs during creation + of the transform, a :exc:`PyCMSError` will be raised. - If inMode or outMode are not a mode supported by the outputProfile (or - by pyCMS), a PyCMSError will be raised. + If ``inMode`` or ``outMode`` are not a mode supported by the ``outputProfile`` + (or by pyCMS), a :exc:`PyCMSError` will be raised. - This function builds and returns an ICC transform from the inputProfile - to the outputProfile using the renderingIntent to determine what to do + This function builds and returns an ICC transform from the ``inputProfile`` + to the ``outputProfile`` using the ``renderingIntent`` to determine what to do with out-of-gamut colors. It will ONLY work for converting images that - are in inMode to images that are in outMode color format (PIL mode, + are in ``inMode`` to images that are in ``outMode`` color format (PIL mode, i.e. "RGB", "RGBA", "CMYK", etc.). Building the transform is a fair part of the overhead in @@ -435,7 +443,7 @@ def buildTransform( The reason pyCMS returns a class object rather than a handle directly to the transform is that it needs to keep track of the PIL input/output modes that the transform is meant for. These attributes are stored in - the "inMode" and "outMode" attributes of the object (which can be + the ``inMode`` and ``outMode`` attributes of the object (which can be manually overridden if you really want to, but I don't know of any time that would be of use, or would even work). @@ -477,7 +485,7 @@ def buildTransform( inputProfile, outputProfile, inMode, outMode, renderingIntent, flags=flags ) except (OSError, TypeError, ValueError) as v: - raise PyCMSError(v) + raise PyCMSError(v) from v def buildProofTransform( @@ -491,25 +499,25 @@ def buildProofTransform( flags=FLAGS["SOFTPROOFING"], ): """ - (pyCMS) Builds an ICC transform mapping from the inputProfile to the - outputProfile, but tries to simulate the result that would be - obtained on the proofProfile device. + (pyCMS) Builds an ICC transform mapping from the ``inputProfile`` to the + ``outputProfile``, but tries to simulate the result that would be + obtained on the ``proofProfile`` device. If the input, output, or proof profiles specified are not valid - filenames, a PyCMSError will be raised. + filenames, a :exc:`PyCMSError` will be raised. - If an error occurs during creation of the transform, a PyCMSError will - be raised. + If an error occurs during creation of the transform, + a :exc:`PyCMSError` will be raised. - If inMode or outMode are not a mode supported by the outputProfile - (or by pyCMS), a PyCMSError will be raised. + If ``inMode`` or ``outMode`` are not a mode supported by the ``outputProfile`` + (or by pyCMS), a :exc:`PyCMSError` will be raised. - This function builds and returns an ICC transform from the inputProfile - to the outputProfile, but tries to simulate the result that would be - obtained on the proofProfile device using renderingIntent and - proofRenderingIntent to determine what to do with out-of-gamut + This function builds and returns an ICC transform from the ``inputProfile`` + to the ``outputProfile``, but tries to simulate the result that would be + obtained on the ``proofProfile`` device using ``renderingIntent`` and + ``proofRenderingIntent`` to determine what to do with out-of-gamut colors. This is known as "soft-proofing". It will ONLY work for - converting images that are in inMode to images that are in outMode + converting images that are in ``inMode`` to images that are in outMode color format (PIL mode, i.e. "RGB", "RGBA", "CMYK", etc.). Usage of the resulting transform object is exactly the same as with @@ -517,7 +525,7 @@ def buildProofTransform( Proof profiling is generally used when using an output device to get a good idea of what the final printed/displayed image would look like on - the proofProfile device when it's quicker and easier to use the + the ``proofProfile`` device when it's quicker and easier to use the output device for judging color. Generally, this means that the output device is a monitor, or a dye-sub printer (etc.), and the simulated device is something more expensive, complicated, or time consuming @@ -588,7 +596,7 @@ def buildProofTransform( flags, ) except (OSError, TypeError, ValueError) as v: - raise PyCMSError(v) + raise PyCMSError(v) from v buildTransformFromOpenProfiles = buildTransform @@ -599,39 +607,40 @@ def applyTransform(im, transform, inPlace=False): """ (pyCMS) Applies a transform to a given image. - If im.mode != transform.inMode, a PyCMSError is raised. + If ``im.mode != transform.inMode``, a :exc:`PyCMSError` is raised. - If inPlace is True and transform.inMode != transform.outMode, a - PyCMSError is raised. + If ``inPlace`` is ``True`` and ``transform.inMode != transform.outMode``, a + :exc:`PyCMSError` is raised. - If im.mode, transform.inMode, or transform.outMode is not supported by - pyCMSdll or the profiles you used for the transform, a PyCMSError is - raised. + If ``im.mode``, ``transform.inMode`` or ``transform.outMode`` is not + supported by pyCMSdll or the profiles you used for the transform, a + :exc:`PyCMSError` is raised. - If an error occurs while the transform is being applied, a PyCMSError - is raised. + If an error occurs while the transform is being applied, + a :exc:`PyCMSError` is raised. This function applies a pre-calculated transform (from ImageCms.buildTransform() or ImageCms.buildTransformFromOpenProfiles()) - to an image. The transform can be used for multiple images, saving + to an image. The transform can be used for multiple images, saving considerable calculation time if doing the same conversion multiple times. If you want to modify im in-place instead of receiving a new image as - the return value, set inPlace to True. This can only be done if - transform.inMode and transform.outMode are the same, because we can't + the return value, set ``inPlace`` to ``True``. This can only be done if + ``transform.inMode`` and ``transform.outMode`` are the same, because we can't change the mode in-place (the buffer sizes for some modes are - different). The default behavior is to return a new Image object of - the same dimensions in mode transform.outMode. + different). The default behavior is to return a new :py:class:`~PIL.Image.Image` + object of the same dimensions in mode ``transform.outMode``. - :param im: A PIL Image object, and im.mode must be the same as the inMode - supported by the transform. + :param im: An :py:class:`~PIL.Image.Image` object, and im.mode must be the same + as the ``inMode`` supported by the transform. :param transform: A valid CmsTransform class object - :param inPlace: Bool. If True, im is modified in place and None is - returned, if False, a new Image object with the transform applied is - returned (and im is not changed). The default is False. - :returns: Either None, or a new PIL Image object, depending on the value of - inPlace. The profile will be returned in the image's - info['icc_profile']. + :param inPlace: Bool. If ``True``, ``im`` is modified in place and ``None`` is + returned, if ``False``, a new :py:class:`~PIL.Image.Image` object with the + transform applied is returned (and ``im`` is not changed). The default is + ``False``. + :returns: Either ``None``, or a new :py:class:`~PIL.Image.Image` object, + depending on the value of ``inPlace``. The profile will be returned in + the image's ``info['icc_profile']``. :exception PyCMSError: """ @@ -642,7 +651,7 @@ def applyTransform(im, transform, inPlace=False): else: imOut = transform.apply(im) except (TypeError, ValueError) as v: - raise PyCMSError(v) + raise PyCMSError(v) from v return imOut @@ -651,11 +660,14 @@ def createProfile(colorSpace, colorTemp=-1): """ (pyCMS) Creates a profile. - If colorSpace not in ["LAB", "XYZ", "sRGB"], a PyCMSError is raised + If colorSpace not in ``["LAB", "XYZ", "sRGB"]``, + a :exc:`PyCMSError` is raised. - If using LAB and colorTemp != a positive integer, a PyCMSError is raised. + If using LAB and ``colorTemp`` is not a positive integer, + a :exc:`PyCMSError` is raised. - If an error occurs while creating the profile, a PyCMSError is raised. + If an error occurs while creating the profile, + a :exc:`PyCMSError` is raised. Use this function to create common profiles on-the-fly instead of having to supply a profile on disk and knowing the path to it. It @@ -676,22 +688,21 @@ def createProfile(colorSpace, colorTemp=-1): if colorSpace not in ["LAB", "XYZ", "sRGB"]: raise PyCMSError( - "Color space not supported for on-the-fly profile creation (%s)" - % colorSpace + f"Color space not supported for on-the-fly profile creation ({colorSpace})" ) if colorSpace == "LAB": try: colorTemp = float(colorTemp) - except (TypeError, ValueError): + except (TypeError, ValueError) as e: raise PyCMSError( - 'Color temperature must be numeric, "%s" not valid' % colorTemp - ) + f'Color temperature must be numeric, "{colorTemp}" not valid' + ) from e try: return core.createProfile(colorSpace, colorTemp) except (TypeError, ValueError) as v: - raise PyCMSError(v) + raise PyCMSError(v) from v def getProfileName(profile): @@ -699,9 +710,9 @@ def getProfileName(profile): (pyCMS) Gets the internal product name for the given profile. - If profile isn't a valid CmsProfile object or filename to a profile, - a PyCMSError is raised If an error occurs while trying to obtain the - name tag, a PyCMSError is raised. + If ``profile`` isn't a valid CmsProfile object or filename to a profile, + a :exc:`PyCMSError` is raised If an error occurs while trying + to obtain the name tag, a :exc:`PyCMSError` is raised. Use this function to obtain the INTERNAL name of the profile (stored in an ICC tag in the profile itself), usually the one used when the @@ -730,21 +741,21 @@ def getProfileName(profile): return (profile.profile.profile_description or "") + "\n" if not manufacturer or len(model) > 30: return model + "\n" - return "{} - {}\n".format(model, manufacturer) + return f"{model} - {manufacturer}\n" except (AttributeError, OSError, TypeError, ValueError) as v: - raise PyCMSError(v) + raise PyCMSError(v) from v def getProfileInfo(profile): """ (pyCMS) Gets the internal product information for the given profile. - If profile isn't a valid CmsProfile object or filename to a profile, - a PyCMSError is raised. + If ``profile`` isn't a valid CmsProfile object or filename to a profile, + a :exc:`PyCMSError` is raised. - If an error occurs while trying to obtain the info tag, a PyCMSError - is raised + If an error occurs while trying to obtain the info tag, + a :exc:`PyCMSError` is raised. Use this function to obtain the information stored in the profile's info tag. This often contains details about the profile, and how it @@ -773,18 +784,18 @@ def getProfileInfo(profile): return "\r\n\r\n".join(arr) + "\r\n\r\n" except (AttributeError, OSError, TypeError, ValueError) as v: - raise PyCMSError(v) + raise PyCMSError(v) from v def getProfileCopyright(profile): """ (pyCMS) Gets the copyright for the given profile. - If profile isn't a valid CmsProfile object or filename to a profile, - a PyCMSError is raised. + If ``profile`` isn't a valid CmsProfile object or filename to a profile, a + :exc:`PyCMSError` is raised. - If an error occurs while trying to obtain the copyright tag, a PyCMSError - is raised + If an error occurs while trying to obtain the copyright tag, + a :exc:`PyCMSError` is raised. Use this function to obtain the information stored in the profile's copyright tag. @@ -801,18 +812,18 @@ def getProfileCopyright(profile): profile = ImageCmsProfile(profile) return (profile.profile.copyright or "") + "\n" except (AttributeError, OSError, TypeError, ValueError) as v: - raise PyCMSError(v) + raise PyCMSError(v) from v def getProfileManufacturer(profile): """ (pyCMS) Gets the manufacturer for the given profile. - If profile isn't a valid CmsProfile object or filename to a profile, - a PyCMSError is raised. + If ``profile`` isn't a valid CmsProfile object or filename to a profile, a + :exc:`PyCMSError` is raised. If an error occurs while trying to obtain the manufacturer tag, a - PyCMSError is raised + :exc:`PyCMSError` is raised. Use this function to obtain the information stored in the profile's manufacturer tag. @@ -829,18 +840,18 @@ def getProfileManufacturer(profile): profile = ImageCmsProfile(profile) return (profile.profile.manufacturer or "") + "\n" except (AttributeError, OSError, TypeError, ValueError) as v: - raise PyCMSError(v) + raise PyCMSError(v) from v def getProfileModel(profile): """ (pyCMS) Gets the model for the given profile. - If profile isn't a valid CmsProfile object or filename to a profile, - a PyCMSError is raised. + If ``profile`` isn't a valid CmsProfile object or filename to a profile, a + :exc:`PyCMSError` is raised. - If an error occurs while trying to obtain the model tag, a PyCMSError - is raised + If an error occurs while trying to obtain the model tag, + a :exc:`PyCMSError` is raised. Use this function to obtain the information stored in the profile's model tag. @@ -858,18 +869,18 @@ def getProfileModel(profile): profile = ImageCmsProfile(profile) return (profile.profile.model or "") + "\n" except (AttributeError, OSError, TypeError, ValueError) as v: - raise PyCMSError(v) + raise PyCMSError(v) from v def getProfileDescription(profile): """ (pyCMS) Gets the description for the given profile. - If profile isn't a valid CmsProfile object or filename to a profile, - a PyCMSError is raised. + If ``profile`` isn't a valid CmsProfile object or filename to a profile, a + :exc:`PyCMSError` is raised. - If an error occurs while trying to obtain the description tag, a PyCMSError - is raised + If an error occurs while trying to obtain the description tag, + a :exc:`PyCMSError` is raised. Use this function to obtain the information stored in the profile's description tag. @@ -887,18 +898,18 @@ def getProfileDescription(profile): profile = ImageCmsProfile(profile) return (profile.profile.profile_description or "") + "\n" except (AttributeError, OSError, TypeError, ValueError) as v: - raise PyCMSError(v) + raise PyCMSError(v) from v def getDefaultIntent(profile): """ (pyCMS) Gets the default intent name for the given profile. - If profile isn't a valid CmsProfile object or filename to a profile, - a PyCMSError is raised. + If ``profile`` isn't a valid CmsProfile object or filename to a profile, a + :exc:`PyCMSError` is raised. If an error occurs while trying to obtain the default intent, a - PyCMSError is raised. + :exc:`PyCMSError` is raised. Use this function to determine the default (and usually best optimized) rendering intent for this profile. Most profiles support multiple @@ -926,7 +937,7 @@ def getDefaultIntent(profile): profile = ImageCmsProfile(profile) return profile.profile.rendering_intent except (AttributeError, OSError, TypeError, ValueError) as v: - raise PyCMSError(v) + raise PyCMSError(v) from v def isIntentSupported(profile, intent, direction): @@ -934,15 +945,15 @@ def isIntentSupported(profile, intent, direction): (pyCMS) Checks if a given intent is supported. Use this function to verify that you can use your desired - renderingIntent with profile, and that profile can be used for the + ``intent`` with ``profile``, and that ``profile`` can be used for the input/output/proof profile as you desire. Some profiles are created specifically for one "direction", can cannot - be used for others. Some profiles can only be used for certain - rendering intents... so it's best to either verify this before trying + be used for others. Some profiles can only be used for certain + rendering intents, so it's best to either verify this before trying to create a transform with them (using this function), or catch the - potential PyCMSError that will occur if they don't support the modes - you select. + potential :exc:`PyCMSError` that will occur if they don't + support the modes you select. :param profile: EITHER a valid CmsProfile object, OR a string of the filename of an ICC profile. @@ -977,7 +988,7 @@ def isIntentSupported(profile, intent, direction): else: return -1 except (AttributeError, OSError, TypeError, ValueError) as v: - raise PyCMSError(v) + raise PyCMSError(v) from v def versions(): diff --git a/src/PIL/ImageColor.py b/src/PIL/ImageColor.py index 9cf7a9912..909117449 100644 --- a/src/PIL/ImageColor.py +++ b/src/PIL/ImageColor.py @@ -113,7 +113,7 @@ def getrgb(color): m = re.match(r"rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$", color) if m: return (int(m.group(1)), int(m.group(2)), int(m.group(3)), int(m.group(4))) - raise ValueError("unknown color specifier: %r" % color) + raise ValueError(f"unknown color specifier: {repr(color)}") def getcolor(color, mode): diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py index c6e12150e..b823be9a2 100644 --- a/src/PIL/ImageDraw.py +++ b/src/PIL/ImageDraw.py @@ -35,7 +35,6 @@ import numbers from . import Image, ImageColor - """ A simple 2D drawing interface for PIL images.

@@ -119,7 +118,7 @@ class ImageDraw: fill = self.draw.draw_ink(fill) return ink, fill - def arc(self, xy, start, end, fill=None, width=0): + def arc(self, xy, start, end, fill=None, width=1): """Draw an arc.""" ink, fill = self._getink(fill) if ink is not None: @@ -134,20 +133,20 @@ class ImageDraw: if ink is not None: self.draw.draw_bitmap(xy, bitmap.im, ink) - def chord(self, xy, start, end, fill=None, outline=None, width=0): + def chord(self, xy, start, end, fill=None, outline=None, width=1): """Draw a chord.""" ink, fill = self._getink(outline, fill) if fill is not None: self.draw.draw_chord(xy, start, end, fill, 1) - if ink is not None and ink != fill: + if ink is not None and ink != fill and width != 0: self.draw.draw_chord(xy, start, end, ink, 0, width) - def ellipse(self, xy, fill=None, outline=None, width=0): + def ellipse(self, xy, fill=None, outline=None, width=1): """Draw an ellipse.""" ink, fill = self._getink(outline, fill) if fill is not None: self.draw.draw_ellipse(xy, fill, 1) - if ink is not None and ink != fill: + if ink is not None and ink != fill and width != 0: self.draw.draw_ellipse(xy, ink, 0, width) def line(self, xy, fill=None, width=0, joint=None): @@ -156,6 +155,8 @@ class ImageDraw: if ink is not None: self.draw.draw_lines(xy, ink, width) if joint == "curve" and width > 4: + if not isinstance(xy[0], (list, tuple)): + xy = [tuple(xy[i : i + 2]) for i in range(0, len(xy), 2)] for i in range(1, len(xy) - 1): point = xy[i] angles = [ @@ -219,12 +220,12 @@ class ImageDraw: if ink is not None and ink != fill: self.draw.draw_outline(shape, ink, 0) - def pieslice(self, xy, start, end, fill=None, outline=None, width=0): + def pieslice(self, xy, start, end, fill=None, outline=None, width=1): """Draw a pieslice.""" ink, fill = self._getink(outline, fill) if fill is not None: self.draw.draw_pieslice(xy, start, end, fill, 1) - if ink is not None and ink != fill: + if ink is not None and ink != fill and width != 0: self.draw.draw_pieslice(xy, start, end, ink, 0, width) def point(self, xy, fill=None): @@ -241,12 +242,19 @@ class ImageDraw: if ink is not None and ink != fill: self.draw.draw_polygon(xy, ink, 0) - def rectangle(self, xy, fill=None, outline=None, width=0): + def regular_polygon( + self, bounding_circle, n_sides, rotation=0, fill=None, outline=None + ): + """Draw a regular polygon.""" + xy = _compute_regular_polygon_vertices(bounding_circle, n_sides, rotation) + self.polygon(xy, fill, outline) + + def rectangle(self, xy, fill=None, outline=None, width=1): """Draw a rectangle.""" ink, fill = self._getink(outline, fill) if fill is not None: self.draw.draw_rectangle(xy, fill, 1) - if ink is not None and ink != fill: + if ink is not None and ink != fill and width != 0: self.draw.draw_rectangle(xy, ink, 0, width) def _multiline_check(self, text): @@ -274,8 +282,9 @@ class ImageDraw: language=None, stroke_width=0, stroke_fill=None, + embedded_color=False, *args, - **kwargs + **kwargs, ): if self._multiline_check(text): return self.multiline_text( @@ -291,8 +300,12 @@ class ImageDraw: language, stroke_width, stroke_fill, + embedded_color, ) + if embedded_color and self.mode not in ("RGB", "RGBA"): + raise ValueError("Embedded color supported only in RGB and RGBA modes") + if font is None: font = self.getfont() @@ -303,15 +316,20 @@ class ImageDraw: return ink def draw_text(ink, stroke_width=0, stroke_offset=None): + mode = self.fontmode + if stroke_width == 0 and embedded_color: + mode = "RGBA" coord = xy try: mask, offset = font.getmask2( text, - self.fontmode, + mode, direction=direction, features=features, language=language, stroke_width=stroke_width, + anchor=anchor, + ink=ink, *args, **kwargs, ) @@ -320,11 +338,13 @@ class ImageDraw: try: mask = font.getmask( text, - self.fontmode, + mode, direction, features, language, stroke_width, + anchor, + ink, *args, **kwargs, ) @@ -332,7 +352,15 @@ class ImageDraw: mask = font.getmask(text) if stroke_offset: coord = coord[0] + stroke_offset[0], coord[1] + stroke_offset[1] - self.draw.draw_bitmap(coord, mask, ink) + if mode == "RGBA": + # font.getmask2(mode="RGBA") returns color in RGB bands and mask in A + # extract mask and set text alpha + color, mask = mask, mask.getband(3) + color.fillband(3, (ink >> 24) & 0xFF) + coord2 = coord[0] + mask.size[0], coord[1] + mask.size[1] + self.im.paste(color, coord + coord2, mask) + else: + self.draw.draw_bitmap(coord, mask, ink) ink = getink(fill) if ink is not None: @@ -345,7 +373,7 @@ class ImageDraw: draw_text(stroke_ink, stroke_width) # Draw normal text - draw_text(ink, 0, (stroke_width, stroke_width)) + draw_text(ink, 0) else: # Only draw normal text draw_text(ink) @@ -364,7 +392,18 @@ class ImageDraw: language=None, stroke_width=0, stroke_fill=None, + embedded_color=False, ): + if direction == "ttb": + raise ValueError("ttb direction is unsupported for multiline text") + + if anchor is None: + anchor = "la" + elif len(anchor) != 2: + raise ValueError("anchor must be a 2 character string") + elif anchor[1] in "tb": + raise ValueError("anchor not supported for multiline text") + widths = [] max_width = 0 lines = self._multiline_split(text) @@ -372,26 +411,38 @@ class ImageDraw: self.textsize("A", font=font, stroke_width=stroke_width)[1] + spacing ) for line in lines: - line_width, line_height = self.textsize( - line, - font, - direction=direction, - features=features, - language=language, - stroke_width=stroke_width, + line_width = self.textlength( + line, font, direction=direction, features=features, language=language ) widths.append(line_width) max_width = max(max_width, line_width) - left, top = xy + + top = xy[1] + if anchor[1] == "m": + top -= (len(lines) - 1) * line_spacing / 2.0 + elif anchor[1] == "d": + top -= (len(lines) - 1) * line_spacing + for idx, line in enumerate(lines): + left = xy[0] + width_difference = max_width - widths[idx] + + # first align left by anchor + if anchor[0] == "m": + left -= width_difference / 2.0 + elif anchor[0] == "r": + left -= width_difference + + # then align by align parameter if align == "left": - pass # left = x + pass elif align == "center": - left += (max_width - widths[idx]) / 2.0 + left += width_difference / 2.0 elif align == "right": - left += max_width - widths[idx] + left += width_difference else: raise ValueError('align must be "left", "center" or "right"') + self.text( (left, top), line, @@ -403,9 +454,9 @@ class ImageDraw: language=language, stroke_width=stroke_width, stroke_fill=stroke_fill, + embedded_color=embedded_color, ) top += line_spacing - left = xy[0] def textsize( self, @@ -449,6 +500,172 @@ class ImageDraw: max_width = max(max_width, line_width) return max_width, len(lines) * line_spacing - spacing + def textlength( + self, + text, + font=None, + direction=None, + features=None, + language=None, + embedded_color=False, + ): + """Get the length of a given string, in pixels with 1/64 precision.""" + if self._multiline_check(text): + raise ValueError("can't measure length of multiline text") + if embedded_color and self.mode not in ("RGB", "RGBA"): + raise ValueError("Embedded color supported only in RGB and RGBA modes") + + if font is None: + font = self.getfont() + mode = "RGBA" if embedded_color else self.fontmode + try: + return font.getlength(text, mode, direction, features, language) + except AttributeError: + size = self.textsize( + text, font, direction=direction, features=features, language=language + ) + if direction == "ttb": + return size[1] + return size[0] + + def textbbox( + self, + xy, + text, + font=None, + anchor=None, + spacing=4, + align="left", + direction=None, + features=None, + language=None, + stroke_width=0, + embedded_color=False, + ): + """Get the bounding box of a given string, in pixels.""" + if embedded_color and self.mode not in ("RGB", "RGBA"): + raise ValueError("Embedded color supported only in RGB and RGBA modes") + + if self._multiline_check(text): + return self.multiline_textbbox( + xy, + text, + font, + anchor, + spacing, + align, + direction, + features, + language, + stroke_width, + embedded_color, + ) + + if font is None: + font = self.getfont() + mode = "RGBA" if embedded_color else self.fontmode + bbox = font.getbbox( + text, mode, direction, features, language, stroke_width, anchor + ) + return bbox[0] + xy[0], bbox[1] + xy[1], bbox[2] + xy[0], bbox[3] + xy[1] + + def multiline_textbbox( + self, + xy, + text, + font=None, + anchor=None, + spacing=4, + align="left", + direction=None, + features=None, + language=None, + stroke_width=0, + embedded_color=False, + ): + if direction == "ttb": + raise ValueError("ttb direction is unsupported for multiline text") + + if anchor is None: + anchor = "la" + elif len(anchor) != 2: + raise ValueError("anchor must be a 2 character string") + elif anchor[1] in "tb": + raise ValueError("anchor not supported for multiline text") + + widths = [] + max_width = 0 + lines = self._multiline_split(text) + line_spacing = ( + self.textsize("A", font=font, stroke_width=stroke_width)[1] + spacing + ) + for line in lines: + line_width = self.textlength( + line, + font, + direction=direction, + features=features, + language=language, + embedded_color=embedded_color, + ) + widths.append(line_width) + max_width = max(max_width, line_width) + + top = xy[1] + if anchor[1] == "m": + top -= (len(lines) - 1) * line_spacing / 2.0 + elif anchor[1] == "d": + top -= (len(lines) - 1) * line_spacing + + bbox = None + + for idx, line in enumerate(lines): + left = xy[0] + width_difference = max_width - widths[idx] + + # first align left by anchor + if anchor[0] == "m": + left -= width_difference / 2.0 + elif anchor[0] == "r": + left -= width_difference + + # then align by align parameter + if align == "left": + pass + elif align == "center": + left += width_difference / 2.0 + elif align == "right": + left += width_difference + else: + raise ValueError('align must be "left", "center" or "right"') + + bbox_line = self.textbbox( + (left, top), + line, + font, + anchor, + direction=direction, + features=features, + language=language, + stroke_width=stroke_width, + embedded_color=embedded_color, + ) + if bbox is None: + bbox = bbox_line + else: + bbox = ( + min(bbox[0], bbox_line[0]), + min(bbox[1], bbox_line[1]), + max(bbox[2], bbox_line[2]), + max(bbox[3], bbox_line[3]), + ) + + top += line_spacing + + if bbox is None: + return xy[0], xy[1], xy[0], xy[1] + return bbox + def Draw(im, mode=None): """ @@ -554,6 +771,123 @@ def floodfill(image, xy, value, border=None, thresh=0): edge = new_edge +def _compute_regular_polygon_vertices(bounding_circle, n_sides, rotation): + """ + Generate a list of vertices for a 2D regular polygon. + + :param bounding_circle: The bounding circle is a tuple defined + by a point and radius. The polygon is inscribed in this circle. + (e.g. ``bounding_circle=(x, y, r)`` or ``((x, y), r)``) + :param n_sides: Number of sides + (e.g. ``n_sides=3`` for a triangle, ``6`` for a hexagon) + :param rotation: Apply an arbitrary rotation to the polygon + (e.g. ``rotation=90``, applies a 90 degree rotation) + :return: List of regular polygon vertices + (e.g. ``[(25, 50), (50, 50), (50, 25), (25, 25)]``) + + How are the vertices computed? + 1. Compute the following variables + - theta: Angle between the apothem & the nearest polygon vertex + - side_length: Length of each polygon edge + - centroid: Center of bounding circle (1st, 2nd elements of bounding_circle) + - polygon_radius: Polygon radius (last element of bounding_circle) + - angles: Location of each polygon vertex in polar grid + (e.g. A square with 0 degree rotation => [225.0, 315.0, 45.0, 135.0]) + + 2. For each angle in angles, get the polygon vertex at that angle + The vertex is computed using the equation below. + X= xcos(φ) + ysin(φ) + Y= −xsin(φ) + ycos(φ) + + Note: + φ = angle in degrees + x = 0 + y = polygon_radius + + The formula above assumes rotation around the origin. + In our case, we are rotating around the centroid. + To account for this, we use the formula below + X = xcos(φ) + ysin(φ) + centroid_x + Y = −xsin(φ) + ycos(φ) + centroid_y + """ + # 1. Error Handling + # 1.1 Check `n_sides` has an appropriate value + if not isinstance(n_sides, int): + raise TypeError("n_sides should be an int") + if n_sides < 3: + raise ValueError("n_sides should be an int > 2") + + # 1.2 Check `bounding_circle` has an appropriate value + if not isinstance(bounding_circle, (list, tuple)): + raise TypeError("bounding_circle should be a tuple") + + if len(bounding_circle) == 3: + *centroid, polygon_radius = bounding_circle + elif len(bounding_circle) == 2: + centroid, polygon_radius = bounding_circle + else: + raise ValueError( + "bounding_circle should contain 2D coordinates " + "and a radius (e.g. (x, y, r) or ((x, y), r) )" + ) + + if not all(isinstance(i, (int, float)) for i in (*centroid, polygon_radius)): + raise ValueError("bounding_circle should only contain numeric data") + + if not len(centroid) == 2: + raise ValueError( + "bounding_circle centre should contain 2D coordinates (e.g. (x, y))" + ) + + if polygon_radius <= 0: + raise ValueError("bounding_circle radius should be > 0") + + # 1.3 Check `rotation` has an appropriate value + if not isinstance(rotation, (int, float)): + raise ValueError("rotation should be an int or float") + + # 2. Define Helper Functions + def _apply_rotation(point, degrees, centroid): + return ( + round( + point[0] * math.cos(math.radians(360 - degrees)) + - point[1] * math.sin(math.radians(360 - degrees)) + + centroid[0], + 2, + ), + round( + point[1] * math.cos(math.radians(360 - degrees)) + + point[0] * math.sin(math.radians(360 - degrees)) + + centroid[1], + 2, + ), + ) + + def _compute_polygon_vertex(centroid, polygon_radius, angle): + start_point = [polygon_radius, 0] + return _apply_rotation(start_point, angle, centroid) + + def _get_angles(n_sides, rotation): + angles = [] + degrees = 360 / n_sides + # Start with the bottom left polygon vertex + current_angle = (270 - 0.5 * degrees) + rotation + for _ in range(0, n_sides): + angles.append(current_angle) + current_angle += degrees + if current_angle > 360: + current_angle -= 360 + return angles + + # 3. Variable Declarations + angles = _get_angles(n_sides, rotation) + + # 4. Compute Vertices + return [ + _compute_polygon_vertex(centroid, polygon_radius, angle) for angle in angles + ] + + def _color_diff(color1, color2): """ Uses 1-norm distance to calculate difference between two values. diff --git a/src/PIL/ImageDraw2.py b/src/PIL/ImageDraw2.py index 20b5fe4c4..1f63110fd 100644 --- a/src/PIL/ImageDraw2.py +++ b/src/PIL/ImageDraw2.py @@ -16,21 +16,35 @@ # See the README file for information on usage and redistribution. # + +""" +(Experimental) WCK-style drawing interface operations + +.. seealso:: :py:mod:`PIL.ImageDraw` +""" + + from . import Image, ImageColor, ImageDraw, ImageFont, ImagePath class Pen: + """Stores an outline color and width.""" + def __init__(self, color, width=1, opacity=255): self.color = ImageColor.getrgb(color) self.width = width class Brush: + """Stores a fill color""" + def __init__(self, color, opacity=255): self.color = ImageColor.getrgb(color) class Font: + """Stores a TrueType font and color""" + def __init__(self, color, file, size=12): # FIXME: add support for bitmap fonts self.color = ImageColor.getrgb(color) @@ -38,6 +52,10 @@ class Font: class Draw: + """ + (Experimental) WCK-style drawing interface + """ + def __init__(self, image, size=None, color=None): if not hasattr(image, "im"): image = Image.new(image, size, color) @@ -73,35 +91,89 @@ class Draw: getattr(self.draw, op)(xy, fill=fill, outline=outline) def settransform(self, offset): + """Sets a transformation offset.""" (xoffset, yoffset) = offset self.transform = (1, 0, xoffset, 0, 1, yoffset) def arc(self, xy, start, end, *options): + """ + Draws an arc (a portion of a circle outline) between the start and end + angles, inside the given bounding box. + + .. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.arc` + """ self.render("arc", xy, start, end, *options) def chord(self, xy, start, end, *options): + """ + Same as :py:meth:`~PIL.ImageDraw2.Draw.arc`, but connects the end points + with a straight line. + + .. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.chord` + """ self.render("chord", xy, start, end, *options) def ellipse(self, xy, *options): + """ + Draws an ellipse inside the given bounding box. + + .. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.ellipse` + """ self.render("ellipse", xy, *options) def line(self, xy, *options): + """ + Draws a line between the coordinates in the ``xy`` list. + + .. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.line` + """ self.render("line", xy, *options) def pieslice(self, xy, start, end, *options): + """ + Same as arc, but also draws straight lines between the end points and the + center of the bounding box. + + .. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.pieslice` + """ self.render("pieslice", xy, start, end, *options) def polygon(self, xy, *options): + """ + Draws a polygon. + + The polygon outline consists of straight lines between the given + coordinates, plus a straight line between the last and the first + coordinate. + + + .. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.polygon` + """ self.render("polygon", xy, *options) def rectangle(self, xy, *options): + """ + Draws a rectangle. + + .. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.rectangle` + """ self.render("rectangle", xy, *options) def text(self, xy, text, font): + """ + Draws the string at the given position. + + .. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.text` + """ if self.transform: xy = ImagePath.Path(xy) xy.transform(self.transform) self.draw.text(xy, text, font=font.font, fill=font.color) def textsize(self, text, font): + """ + Return the size of the given string, in pixels. + + .. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.textsize` + """ return self.draw.textsize(text, font=font.font) diff --git a/src/PIL/ImageFile.py b/src/PIL/ImageFile.py index 1fb83f2d7..8c62b3c81 100644 --- a/src/PIL/ImageFile.py +++ b/src/PIL/ImageFile.py @@ -30,6 +30,7 @@ import io import struct import sys +import warnings from . import Image from ._util import isPath @@ -39,6 +40,7 @@ MAXBLOCK = 65536 SAFEBLOCK = 1024 * 1024 LOAD_TRUNCATED_IMAGES = False +"""Whether or not to load truncated image files. User code may change this.""" ERRORS = { -1: "image buffer overrun error", @@ -47,16 +49,7 @@ ERRORS = { -8: "bad configuration", -9: "out of memory error", } - - -def raise_ioerror(error): - try: - message = Image.core.getcodecstatus(error) - except AttributeError: - message = ERRORS.get(error) - if not message: - message = "decoder error %d" % error - raise OSError(message + " when reading image file") +"""Dict of known error codes returned from :meth:`.PyDecoder.decode`.""" # @@ -64,6 +57,25 @@ def raise_ioerror(error): # Helpers +def raise_oserror(error): + try: + message = Image.core.getcodecstatus(error) + except AttributeError: + message = ERRORS.get(error) + if not message: + message = f"decoder error {error}" + raise OSError(message + " when reading image file") + + +def raise_ioerror(error): + warnings.warn( + "raise_ioerror is deprecated and will be removed in Pillow 9 (2022-01-02). " + "Use raise_oserror instead.", + DeprecationWarning, + ) + return raise_oserror(error) + + def _tilesort(t): # sort on offset return t[2] @@ -75,7 +87,7 @@ def _tilesort(t): class ImageFile(Image.Image): - "Base class for image file format handlers." + """Base class for image file format handlers.""" def __init__(self, fp=None, filename=None): super().__init__() @@ -85,6 +97,8 @@ class ImageFile(Image.Image): self.custom_mimetype = None self.tile = None + """ A list of tile descriptors, or ``None`` """ + self.readonly = 1 # until we know better self.decoderconfig = () @@ -112,7 +126,7 @@ class ImageFile(Image.Image): EOFError, # got header but not the first frame struct.error, ) as v: - raise SyntaxError(v) + raise SyntaxError(v) from v if not self.mode or self.size[0] <= 0: raise SyntaxError("not identified by this driver") @@ -140,10 +154,10 @@ class ImageFile(Image.Image): def load(self): """Load image data based on tile list""" - pixel = Image.Image.load(self) - if self.tile is None: raise OSError("cannot load this image") + + pixel = Image.Image.load(self) if not self.tile: return pixel @@ -189,7 +203,7 @@ class ImageFile(Image.Image): # use mmap, if possible import mmap - with open(self.filename, "r") as fp: + with open(self.filename) as fp: self.map = mmap.mmap( fp.fileno(), 0, access=mmap.ACCESS_READ ) @@ -231,12 +245,12 @@ class ImageFile(Image.Image): while True: try: s = read(self.decodermaxblock) - except (IndexError, struct.error): + except (IndexError, struct.error) as e: # truncated png/gif if LOAD_TRUNCATED_IMAGES: break else: - raise OSError("image file is truncated") + raise OSError("image file is truncated") from e if not s: # truncated jpeg if LOAD_TRUNCATED_IMAGES: @@ -244,7 +258,7 @@ class ImageFile(Image.Image): else: raise OSError( "image file is truncated " - "(%d bytes not processed)" % len(b) + f"({len(b)} bytes not processed)" ) b = b + s @@ -267,7 +281,7 @@ class ImageFile(Image.Image): if not self.map and not LOAD_TRUNCATED_IMAGES and err_code < 0: # still raised if decoder fails to return anything - raise_ioerror(err_code) + raise_oserror(err_code) return Image.Image.load(self) @@ -320,7 +334,7 @@ class StubImageFile(ImageFile): def load(self): loader = self._load() if loader is None: - raise OSError("cannot find loader for this %s file" % self.format) + raise OSError(f"cannot find loader for this {self.format} file") image = loader.load(self) assert image is not None # become the other object (!) @@ -358,7 +372,7 @@ class Parser: (Consumer) Feed data to the parser. :param data: A string buffer. - :exception IOError: If the parser failed to parse the image file. + :exception OSError: If the parser failed to parse the image file. """ # collect data @@ -390,7 +404,7 @@ class Parser: if e < 0: # decoding error self.image = None - raise_ioerror(e) + raise_oserror(e) else: # end of image return @@ -444,7 +458,7 @@ class Parser: (Consumer) Close the stream. :returns: An image object. - :exception IOError: If the parser failed to parse the image file either + :exception OSError: If the parser failed to parse the image file either because it cannot be identified or cannot be decoded. """ @@ -495,7 +509,7 @@ def _save(im, fp, tile, bufsize=0): try: fh = fp.fileno() fp.flush() - except (AttributeError, io.UnsupportedOperation): + except (AttributeError, io.UnsupportedOperation) as exc: # compress to Python file-compatible object for e, b, o, a in tile: e = Image._getencoder(im.mode, e, a, im.encoderconfig) @@ -512,7 +526,7 @@ def _save(im, fp, tile, bufsize=0): if s: break if s < 0: - raise OSError("encoder error %d when writing image file" % s) + raise OSError(f"encoder error {s} when writing image file") from exc e.cleanup() else: # slight speedup: compress to real file object @@ -527,7 +541,7 @@ def _save(im, fp, tile, bufsize=0): else: s = e.encode_to_file(fh, bufsize) if s < 0: - raise OSError("encoder error %d when writing image file" % s) + raise OSError(f"encoder error {s} when writing image file") e.cleanup() if hasattr(fp, "flush"): fp.flush() @@ -571,7 +585,7 @@ class PyCodecState: class PyDecoder: """ Python implementation of a format decoder. Override this class and - add the decoding logic in the `decode` method. + add the decoding logic in the :meth:`decode` method. See :ref:`Writing Your Own File Decoder in Python` """ @@ -603,9 +617,9 @@ class PyDecoder: Override to perform the decoding process. :param buffer: A bytes object with the data to be decoded. - :returns: A tuple of (bytes consumed, errcode). + :returns: A tuple of ``(bytes consumed, errcode)``. If finished with decoding return <0 for the bytes consumed. - Err codes are from `ERRORS` + Err codes are from :data:`.ImageFile.ERRORS`. """ raise NotImplementedError() diff --git a/src/PIL/ImageFilter.py b/src/PIL/ImageFilter.py index 6b0f5eb37..9ca17d9ad 100644 --- a/src/PIL/ImageFilter.py +++ b/src/PIL/ImageFilter.py @@ -49,7 +49,7 @@ class Kernel(BuiltinFilter): version, this must be (3,3) or (5,5). :param kernel: A sequence containing kernel weights. :param scale: Scale factor. If given, the result for each pixel is - divided by this value. the default is the sum of the + divided by this value. The default is the sum of the kernel weights. :param offset: Offset. If given, this value is added to the result, after it has been divided by the scale factor. @@ -69,7 +69,7 @@ class Kernel(BuiltinFilter): class RankFilter(Filter): """ Create a rank filter. The rank filter sorts all pixels in - a window of the given size, and returns the **rank**'th value. + a window of the given size, and returns the ``rank``'th value. :param size: The kernel size, in pixels. :param rank: What pixel value to pick. Use 0 for a min filter, @@ -401,9 +401,8 @@ class Color3DLUT(MultibandFilter): raise ValueError( "The table should have either channels * size**3 float items " "or size**3 items of channels-sized tuples with floats. " - "Table should be: {}x{}x{}x{}. Actual length: {}".format( - channels, size[0], size[1], size[2], len(table) - ) + f"Table should be: {channels}x{size[0]}x{size[1]}x{size[2]}. " + f"Actual length: {len(table)}" ) self.table = table @@ -411,10 +410,10 @@ class Color3DLUT(MultibandFilter): def _check_size(size): try: _, _, _ = size - except ValueError: + except ValueError as e: raise ValueError( "Size should be either an integer or a tuple of three integers." - ) + ) from e except TypeError: size = (size, size, size) size = [int(x) for x in size] @@ -513,12 +512,12 @@ class Color3DLUT(MultibandFilter): def __repr__(self): r = [ - "{} from {}".format(self.__class__.__name__, self.table.__class__.__name__), + f"{self.__class__.__name__} from {self.table.__class__.__name__}", "size={:d}x{:d}x{:d}".format(*self.size), - "channels={:d}".format(self.channels), + f"channels={self.channels:d}", ] if self.mode: - r.append("target_mode={}".format(self.mode)) + r.append(f"target_mode={self.mode}") return "<{}>".format(" ".join(r)) def filter(self, image): diff --git a/src/PIL/ImageFont.py b/src/PIL/ImageFont.py index 619800829..c48d89835 100644 --- a/src/PIL/ImageFont.py +++ b/src/PIL/ImageFont.py @@ -28,9 +28,10 @@ import base64 import os import sys +import warnings from io import BytesIO -from . import Image +from . import Image, features from ._util import isDirectory, isPath LAYOUT_BASIC = 0 @@ -71,7 +72,10 @@ class ImageFont: def _load_pilfont(self, filename): with open(filename, "rb") as fp: + image = None for ext in (".png", ".gif", ".pbm"): + if image: + image.close() try: fullname = os.path.splitext(filename)[0] + ext image = Image.open(fullname) @@ -81,11 +85,14 @@ class ImageFont: if image and image.mode in ("1", "L"): break else: + if image: + image.close() raise OSError("cannot find glyph data file") self.file = fullname - return self._load_pilfont_data(fp, image) + self._load_pilfont_data(fp, image) + image.close() def _load_pilfont_data(self, file, image): @@ -158,6 +165,21 @@ class FreeTypeFont: self.index = index self.encoding = encoding + try: + from packaging.version import parse as parse_version + except ImportError: + pass + else: + freetype_version = parse_version(features.version_module("freetype2")) + if freetype_version < parse_version("2.8"): + warnings.warn( + "Support for FreeType 2.7 is deprecated and will be removed" + " in Pillow 9 (2022-01-02). Please upgrade to FreeType 2.8 " + "or newer, preferably FreeType 2.10.4 which fixes " + "CVE-2020-15999.", + DeprecationWarning, + ) + if layout_engine not in (LAYOUT_BASIC, LAYOUT_RAQM): layout_engine = LAYOUT_BASIC if core.HAVE_RAQM: @@ -209,6 +231,147 @@ class FreeTypeFont: """ return self.font.ascent, self.font.descent + def getlength(self, text, mode="", direction=None, features=None, language=None): + """ + Returns length (in pixels with 1/64 precision) of given text when rendered + in font with provided direction, features, and language. + + This is the amount by which following text should be offset. + Text bounding box may extend past the length in some fonts, + e.g. when using italics or accents. + + The result is returned as a float; it is a whole number if using basic layout. + + Note that the sum of two lengths may not equal the length of a concatenated + string due to kerning. If you need to adjust for kerning, include the following + character and subtract its length. + + For example, instead of + + .. code-block:: python + + hello = font.getlength("Hello") + world = font.getlength("World") + hello_world = hello + world # not adjusted for kerning + assert hello_world == font.getlength("HelloWorld") # may fail + + use + + .. code-block:: python + + hello = font.getlength("HelloW") - font.getlength("W") # adjusted for kerning + world = font.getlength("World") + hello_world = hello + world # adjusted for kerning + assert hello_world == font.getlength("HelloWorld") # True + + or disable kerning with (requires libraqm) + + .. code-block:: python + + hello = draw.textlength("Hello", font, features=["-kern"]) + world = draw.textlength("World", font, features=["-kern"]) + hello_world = hello + world # kerning is disabled, no need to adjust + assert hello_world == draw.textlength("HelloWorld", font, features=["-kern"]) + + .. versionadded:: 8.0.0 + + :param text: Text to measure. + :param mode: Used by some graphics drivers to indicate what mode the + driver prefers; if empty, the renderer may return either + mode. Note that the mode is always a string, to simplify + C-level implementations. + + :param direction: Direction of the text. It can be 'rtl' (right to + left), 'ltr' (left to right) or 'ttb' (top to bottom). + Requires libraqm. + + :param features: A list of OpenType font features to be used during text + layout. This is usually used to turn on optional + font features that are not enabled by default, + for example 'dlig' or 'ss01', but can be also + used to turn off default font features for + example '-liga' to disable ligatures or '-kern' + to disable kerning. To get all supported + features, see + https://docs.microsoft.com/en-us/typography/opentype/spec/featurelist + Requires libraqm. + + :param language: Language of the text. Different languages may use + different glyph shapes or ligatures. This parameter tells + the font which language the text is in, and to apply the + correct substitutions as appropriate, if available. + It should be a `BCP 47 language code + `_ + Requires libraqm. + + :return: Width for horizontal, height for vertical text. + """ + return self.font.getlength(text, mode, direction, features, language) / 64 + + def getbbox( + self, + text, + mode="", + direction=None, + features=None, + language=None, + stroke_width=0, + anchor=None, + ): + """ + Returns bounding box (in pixels) of given text relative to given anchor + when rendered in font with provided direction, features, and language. + + Use :py:meth:`getlength()` to get the offset of following text with + 1/64 pixel precision. The bounding box includes extra margins for + some fonts, e.g. italics or accents. + + .. versionadded:: 8.0.0 + + :param text: Text to render. + :param mode: Used by some graphics drivers to indicate what mode the + driver prefers; if empty, the renderer may return either + mode. Note that the mode is always a string, to simplify + C-level implementations. + + :param direction: Direction of the text. It can be 'rtl' (right to + left), 'ltr' (left to right) or 'ttb' (top to bottom). + Requires libraqm. + + :param features: A list of OpenType font features to be used during text + layout. This is usually used to turn on optional + font features that are not enabled by default, + for example 'dlig' or 'ss01', but can be also + used to turn off default font features for + example '-liga' to disable ligatures or '-kern' + to disable kerning. To get all supported + features, see + https://docs.microsoft.com/en-us/typography/opentype/spec/featurelist + Requires libraqm. + + :param language: Language of the text. Different languages may use + different glyph shapes or ligatures. This parameter tells + the font which language the text is in, and to apply the + correct substitutions as appropriate, if available. + It should be a `BCP 47 language code + `_ + Requires libraqm. + + :param stroke_width: The width of the text stroke. + + :param anchor: The text anchor alignment. Determines the relative location of + the anchor to the text. The default alignment is top left. + See :ref:`text-anchors` for valid values. + + :return: ``(left, top, right, bottom)`` bounding box + """ + size, offset = self.font.getsize( + text, mode, direction, features, language, anchor + ) + left, top = offset[0] - stroke_width, offset[1] - stroke_width + width, height = size[0] + 2 * stroke_width, size[1] + 2 * stroke_width + return left, top, left + width, top + height + def getsize( self, text, direction=None, features=None, language=None, stroke_width=0 ): @@ -216,6 +379,15 @@ class FreeTypeFont: Returns width and height (in pixels) of given text if rendered in font with provided direction, features, and language. + Use :py:meth:`getlength()` to measure the offset of following text with + 1/64 pixel precision. + Use :py:meth:`getbbox()` to get the exact bounding box based on an anchor. + + .. note:: For historical reasons this function measures text height from + the ascender line instead of the top, see :ref:`text-anchors`. + If you wish to measure text height from the top, it is recommended + to use the bottom value of :meth:`getbbox` with ``anchor='lt'`` instead. + :param text: Text to measure. :param direction: Direction of the text. It can be 'rtl' (right to @@ -242,7 +414,7 @@ class FreeTypeFont: the font which language the text is in, and to apply the correct substitutions as appropriate, if available. It should be a `BCP 47 language code - ` + `_ Requires libraqm. .. versionadded:: 6.0.0 @@ -253,9 +425,11 @@ class FreeTypeFont: :return: (width, height) """ - size, offset = self.font.getsize(text, direction, features, language) + # vertical offset is added for historical reasons + # see https://github.com/python-pillow/Pillow/pull/4910#discussion_r486682929 + size, offset = self.font.getsize(text, "L", direction, features, language) return ( - size[0] + stroke_width * 2 + offset[0], + size[0] + stroke_width * 2, size[1] + stroke_width * 2 + offset[1], ) @@ -297,7 +471,7 @@ class FreeTypeFont: the font which language the text is in, and to apply the correct substitutions as appropriate, if available. It should be a `BCP 47 language code - ` + `_ Requires libraqm. .. versionadded:: 6.0.0 @@ -339,12 +513,15 @@ class FreeTypeFont: features=None, language=None, stroke_width=0, + anchor=None, + ink=0, ): """ Create a bitmap for the text. If the font uses antialiasing, the bitmap should have mode ``L`` and use a - maximum value of 255. Otherwise, it should have mode ``1``. + maximum value of 255. If the font has embedded color data, the bitmap + should have mode ``RGBA``. Otherwise, it should have mode ``1``. :param text: Text to render. :param mode: Used by some graphics drivers to indicate what mode the @@ -378,7 +555,7 @@ class FreeTypeFont: the font which language the text is in, and to apply the correct substitutions as appropriate, if available. It should be a `BCP 47 language code - ` + `_ Requires libraqm. .. versionadded:: 6.0.0 @@ -387,6 +564,16 @@ class FreeTypeFont: .. versionadded:: 6.2.0 + :param anchor: The text anchor alignment. Determines the relative location of + the anchor to the text. The default alignment is top left. + See :ref:`text-anchors` for valid values. + + .. versionadded:: 8.0.0 + + :param ink: Foreground ink for rendering in RGBA mode. + + .. versionadded:: 8.0.0 + :return: An internal PIL storage memory instance as defined by the :py:mod:`PIL.Image.core` interface module. """ @@ -397,6 +584,8 @@ class FreeTypeFont: features=features, language=language, stroke_width=stroke_width, + anchor=anchor, + ink=ink, )[0] def getmask2( @@ -408,14 +597,17 @@ class FreeTypeFont: features=None, language=None, stroke_width=0, + anchor=None, + ink=0, *args, - **kwargs + **kwargs, ): """ Create a bitmap for the text. If the font uses antialiasing, the bitmap should have mode ``L`` and use a - maximum value of 255. Otherwise, it should have mode ``1``. + maximum value of 255. If the font has embedded color data, the bitmap + should have mode ``RGBA``. Otherwise, it should have mode ``1``. :param text: Text to render. :param mode: Used by some graphics drivers to indicate what mode the @@ -449,7 +641,7 @@ class FreeTypeFont: the font which language the text is in, and to apply the correct substitutions as appropriate, if available. It should be a `BCP 47 language code - ` + `_ Requires libraqm. .. versionadded:: 6.0.0 @@ -458,15 +650,28 @@ class FreeTypeFont: .. versionadded:: 6.2.0 + :param anchor: The text anchor alignment. Determines the relative location of + the anchor to the text. The default alignment is top left. + See :ref:`text-anchors` for valid values. + + .. versionadded:: 8.0.0 + + :param ink: Foreground ink for rendering in RGBA mode. + + .. versionadded:: 8.0.0 + :return: A tuple of an internal PIL storage memory instance as defined by the :py:mod:`PIL.Image.core` interface module, and the text offset, the gap between the starting coordinate and the first marking """ - size, offset = self.font.getsize(text, direction, features, language) + size, offset = self.font.getsize( + text, mode, direction, features, language, anchor + ) size = size[0] + stroke_width * 2, size[1] + stroke_width * 2 - im = fill("L", size, 0) + offset = offset[0] - stroke_width, offset[1] - stroke_width + im = fill("RGBA" if mode == "RGBA" else "L", size, 0) self.font.render( - text, im.id, mode == "1", direction, features, language, stroke_width + text, im.id, mode, direction, features, language, stroke_width, ink ) return im, offset @@ -493,18 +698,18 @@ class FreeTypeFont: def get_variation_names(self): """ :returns: A list of the named styles in a variation font. - :exception IOError: If the font is not a variation font. + :exception OSError: If the font is not a variation font. """ try: names = self.font.getvarnames() - except AttributeError: - raise NotImplementedError("FreeType 2.9.1 or greater is required") + except AttributeError as e: + raise NotImplementedError("FreeType 2.9.1 or greater is required") from e return [name.replace(b"\x00", b"") for name in names] def set_variation_by_name(self, name): """ :param name: The name of the style. - :exception IOError: If the font is not a variation font. + :exception OSError: If the font is not a variation font. """ names = self.get_variation_names() if not isinstance(name, bytes): @@ -523,12 +728,12 @@ class FreeTypeFont: def get_variation_axes(self): """ :returns: A list of the axes in a variation font. - :exception IOError: If the font is not a variation font. + :exception OSError: If the font is not a variation font. """ try: axes = self.font.getvaraxes() - except AttributeError: - raise NotImplementedError("FreeType 2.9.1 or greater is required") + except AttributeError as e: + raise NotImplementedError("FreeType 2.9.1 or greater is required") from e for axis in axes: axis["name"] = axis["name"].replace(b"\x00", b"") return axes @@ -536,12 +741,12 @@ class FreeTypeFont: def set_variation_by_axes(self, axes): """ :param axes: A list of values for each axis. - :exception IOError: If the font is not a variation font. + :exception OSError: If the font is not a variation font. """ try: self.font.setvaraxes(axes) - except AttributeError: - raise NotImplementedError("FreeType 2.9.1 or greater is required") + except AttributeError as e: + raise NotImplementedError("FreeType 2.9.1 or greater is required") from e class TransposedFont: @@ -580,7 +785,7 @@ def load(filename): :param filename: Name of font file. :return: A font object. - :exception IOError: If the file could not be read. + :exception OSError: If the file could not be read. """ f = ImageFont() f._load_pilfont(filename) @@ -630,9 +835,14 @@ def truetype(font=None, size=10, index=0, encoding="", layout_engine=None): This specifies the character set to use. It does not alter the encoding of any text provided in subsequent operations. :param layout_engine: Which layout engine to use, if available: - `ImageFont.LAYOUT_BASIC` or `ImageFont.LAYOUT_RAQM`. + :data:`.ImageFont.LAYOUT_BASIC` or :data:`.ImageFont.LAYOUT_RAQM`. + + You can check support for Raqm layout using + :py:func:`PIL.features.check_feature` with ``feature="raqm"``. + + .. versionadded:: 4.2.0 :return: A font object. - :exception IOError: If the file could not be read. + :exception OSError: If the file could not be read. """ def freetype(font): @@ -692,7 +902,7 @@ def load_path(filename): :param filename: Name of font file. :return: A font object. - :exception IOError: If the file could not be read. + :exception OSError: If the file could not be read. """ for directory in sys.path: if isDirectory(directory): diff --git a/src/PIL/ImageGrab.py b/src/PIL/ImageGrab.py index e587d942d..b93ec3f2a 100644 --- a/src/PIL/ImageGrab.py +++ b/src/PIL/ImageGrab.py @@ -2,7 +2,7 @@ # The Python Imaging Library # $Id$ # -# screen grabber (macOS and Windows only) +# screen grabber # # History: # 2001-04-26 fl created @@ -15,43 +15,56 @@ # See the README file for information on usage and redistribution. # -import os -import subprocess import sys -import tempfile from . import Image -if sys.platform not in ["win32", "darwin"]: - raise ImportError("ImageGrab is macOS and Windows only") +if sys.platform == "darwin": + import os + import subprocess + import tempfile -def grab(bbox=None, include_layered_windows=False, all_screens=False): - if sys.platform == "darwin": - fh, filepath = tempfile.mkstemp(".png") - os.close(fh) - subprocess.call(["screencapture", "-x", filepath]) - im = Image.open(filepath) - im.load() - os.unlink(filepath) - if bbox: - im = im.crop(bbox) - else: - offset, size, data = Image.core.grabscreen(include_layered_windows, all_screens) - im = Image.frombytes( - "RGB", - size, - data, - # RGB, 32-bit line padding, origin lower left corner - "raw", - "BGR", - (size[0] * 3 + 3) & -4, - -1, - ) - if bbox: - x0, y0 = offset - left, top, right, bottom = bbox - im = im.crop((left - x0, top - y0, right - x0, bottom - y0)) +def grab(bbox=None, include_layered_windows=False, all_screens=False, xdisplay=None): + if xdisplay is None: + if sys.platform == "darwin": + fh, filepath = tempfile.mkstemp(".png") + os.close(fh) + subprocess.call(["screencapture", "-x", filepath]) + im = Image.open(filepath) + im.load() + os.unlink(filepath) + if bbox: + im_cropped = im.crop(bbox) + im.close() + return im_cropped + return im + elif sys.platform == "win32": + offset, size, data = Image.core.grabscreen_win32( + include_layered_windows, all_screens + ) + im = Image.frombytes( + "RGB", + size, + data, + # RGB, 32-bit line padding, origin lower left corner + "raw", + "BGR", + (size[0] * 3 + 3) & -4, + -1, + ) + if bbox: + x0, y0 = offset + left, top, right, bottom = bbox + im = im.crop((left - x0, top - y0, right - x0, bottom - y0)) + return im + # use xdisplay=None for default display on non-win32/macOS systems + if not Image.core.HAVE_XCB: + raise OSError("Pillow was built without XCB support") + size, data = Image.core.grabscreen_x11(xdisplay) + im = Image.frombytes("RGB", size, data, "raw", "BGRX", size[0] * 4, 1) + if bbox: + im = im.crop(bbox) return im @@ -79,11 +92,29 @@ def grabclipboard(): im.load() os.unlink(filepath) return im - else: - data = Image.core.grabclipboard() + elif sys.platform == "win32": + fmt, data = Image.core.grabclipboard_win32() + if fmt == "file": # CF_HDROP + import struct + + o = struct.unpack_from("I", data)[0] + if data[16] != 0: + files = data[o:].decode("utf-16le").split("\0") + else: + files = data[o:].decode("mbcs").split("\0") + return files[: files.index("")] if isinstance(data, bytes): - from . import BmpImagePlugin import io - return BmpImagePlugin.DibImageFile(io.BytesIO(data)) - return data + data = io.BytesIO(data) + if fmt == "png": + from . import PngImagePlugin + + return PngImagePlugin.PngImageFile(data) + elif fmt == "DIB": + from . import BmpImagePlugin + + return BmpImagePlugin.DibImageFile(data) + return None + else: + raise NotImplementedError("ImageGrab.grabclipboard() is macOS and Windows only") diff --git a/src/PIL/ImageMath.py b/src/PIL/ImageMath.py index adbb94000..7f9c88e14 100644 --- a/src/PIL/ImageMath.py +++ b/src/PIL/ImageMath.py @@ -41,7 +41,7 @@ class _Operand: elif im1.im.mode in ("I", "F"): return im1.im else: - raise ValueError("unsupported mode: %s" % im1.im.mode) + raise ValueError(f"unsupported mode: {im1.im.mode}") else: # argument was a constant if _isconstant(im1) and self.im.mode in ("1", "L", "I"): @@ -57,8 +57,8 @@ class _Operand: im1.load() try: op = getattr(_imagingmath, op + "_" + im1.mode) - except AttributeError: - raise TypeError("bad operand type for '%s'" % op) + except AttributeError as e: + raise TypeError(f"bad operand type for '{op}'") from e _imagingmath.unop(op, out.im.id, im1.im.id) else: # binary operation @@ -85,8 +85,8 @@ class _Operand: im2.load() try: op = getattr(_imagingmath, op + "_" + im1.mode) - except AttributeError: - raise TypeError("bad operand type for '%s'" % op) + except AttributeError as e: + raise TypeError(f"bad operand type for '{op}'") from e _imagingmath.binop(op, out.im.id, im1.im.id, im2.im.id) return _Operand(out) diff --git a/src/PIL/ImageMorph.py b/src/PIL/ImageMorph.py index d1ec09eac..b76dfa01f 100644 --- a/src/PIL/ImageMorph.py +++ b/src/PIL/ImageMorph.py @@ -28,36 +28,36 @@ MIRROR_MATRIX = [ class LutBuilder: """A class for building a MorphLut from a descriptive language - The input patterns is a list of a strings sequences like these:: + The input patterns is a list of a strings sequences like these:: - 4:(... - .1. - 111)->1 + 4:(... + .1. + 111)->1 - (whitespaces including linebreaks are ignored). The option 4 - describes a series of symmetry operations (in this case a - 4-rotation), the pattern is described by: + (whitespaces including linebreaks are ignored). The option 4 + describes a series of symmetry operations (in this case a + 4-rotation), the pattern is described by: - - . or X - Ignore - - 1 - Pixel is on - - 0 - Pixel is off + - . or X - Ignore + - 1 - Pixel is on + - 0 - Pixel is off - The result of the operation is described after "->" string. + The result of the operation is described after "->" string. - The default is to return the current pixel value, which is - returned if no other match is found. + The default is to return the current pixel value, which is + returned if no other match is found. - Operations: + Operations: - - 4 - 4 way rotation - - N - Negate - - 1 - Dummy op for no other operation (an op must always be given) - - M - Mirroring + - 4 - 4 way rotation + - N - Negate + - 1 - Dummy op for no other operation (an op must always be given) + - M - Mirroring - Example:: + Example:: - lb = LutBuilder(patterns = ["4:(... .1. 111)->1"]) - lut = lb.build_lut() + lb = LutBuilder(patterns = ["4:(... .1. 111)->1"]) + lut = lb.build_lut() """ diff --git a/src/PIL/ImageOps.py b/src/PIL/ImageOps.py index e4e0840b8..14602a5c8 100644 --- a/src/PIL/ImageOps.py +++ b/src/PIL/ImageOps.py @@ -61,20 +61,25 @@ def _lut(image, lut): # actions -def autocontrast(image, cutoff=0, ignore=None): +def autocontrast(image, cutoff=0, ignore=None, mask=None): """ Maximize (normalize) image contrast. This function calculates a - histogram of the input image, removes **cutoff** percent of the + histogram of the input image (or mask region), removes ``cutoff`` percent of the lightest and darkest pixels from the histogram, and remaps the image so that the darkest pixel becomes black (0), and the lightest becomes white (255). :param image: The image to process. - :param cutoff: How many percent to cut off from the histogram. + :param cutoff: The percent to cut off from the histogram on the low and + high ends. Either a tuple of (low, high), or a single + number for both. :param ignore: The background pixel value (use None for no background). + :param mask: Histogram used in contrast operation is computed using pixels + within the mask. If no mask is given the entire image is used + for histogram computation. :return: An image. """ - histogram = image.histogram() + histogram = image.histogram(mask) lut = [] for layer in range(0, len(histogram), 256): h = histogram[layer : layer + 256] @@ -88,12 +93,14 @@ def autocontrast(image, cutoff=0, ignore=None): h[ix] = 0 if cutoff: # cut off pixels from both ends of the histogram + if not isinstance(cutoff, tuple): + cutoff = (cutoff, cutoff) # get number of pixels n = 0 for ix in range(256): n = n + h[ix] # remove cutoff% pixels from the low end - cut = n * cutoff // 100 + cut = n * cutoff[0] // 100 for lo in range(256): if cut > h[lo]: cut = cut - h[lo] @@ -103,8 +110,8 @@ def autocontrast(image, cutoff=0, ignore=None): cut = 0 if cut <= 0: break - # remove cutoff% samples from the hi end - cut = n * cutoff // 100 + # remove cutoff% samples from the high end + cut = n * cutoff[1] // 100 for hi in range(255, -1, -1): if cut > h[hi]: cut = cut - h[hi] @@ -142,14 +149,14 @@ def colorize(image, black, white, mid=None, blackpoint=0, whitepoint=255, midpoi Colorize grayscale image. This function calculates a color wedge which maps all black pixels in the source image to the first color and all white pixels to the - second color. If **mid** is specified, it uses three-color mapping. - The **black** and **white** arguments should be RGB tuples or color names; - optionally you can use three-color mapping by also specifying **mid**. + second color. If ``mid`` is specified, it uses three-color mapping. + The ``black`` and ``white`` arguments should be RGB tuples or color names; + optionally you can use three-color mapping by also specifying ``mid``. Mapping positions for any of the colors can be specified - (e.g. **blackpoint**), where these parameters are the integer + (e.g. ``blackpoint``), where these parameters are the integer value corresponding to where the corresponding color should be mapped. These parameters must have logical order, such that - **blackpoint** <= **midpoint** <= **whitepoint** (if **mid** is specified). + ``blackpoint <= midpoint <= whitepoint`` (if ``mid`` is specified). :param image: The image to colorize. :param black: The color to use for black input pixels. @@ -308,7 +315,7 @@ def deform(image, deformer, resample=Image.BILINEAR): :param image: The image to deform. :param deformer: A deformer object. Any object that implements a - **getmesh** method can be used. + ``getmesh`` method can be used. :param resample: An optional resampling filter. Same values possible as in the PIL.Image.transform function. :return: An image. diff --git a/src/PIL/ImagePalette.py b/src/PIL/ImagePalette.py index e0d439c98..d0604112f 100644 --- a/src/PIL/ImagePalette.py +++ b/src/PIL/ImagePalette.py @@ -97,13 +97,13 @@ class ImagePalette: if isinstance(color, tuple): try: return self.colors[color] - except KeyError: + except KeyError as e: # allocate new color slot if isinstance(self.palette, bytes): self.palette = bytearray(self.palette) index = len(self.colors) if index >= 256: - raise ValueError("cannot allocate more than 256 colors") + raise ValueError("cannot allocate more than 256 colors") from e self.colors[color] = index self.palette[index] = color[0] self.palette[index + 256] = color[1] @@ -111,7 +111,7 @@ class ImagePalette: self.dirty = 1 return index else: - raise ValueError("unknown color specifier: %r" % color) + raise ValueError(f"unknown color specifier: {repr(color)}") def save(self, fp): """Save palette to text file. @@ -123,12 +123,12 @@ class ImagePalette: if isinstance(fp, str): fp = open(fp, "w") fp.write("# Palette\n") - fp.write("# Mode: %s\n" % self.mode) + fp.write(f"# Mode: {self.mode}\n") for i in range(256): - fp.write("%d" % i) + fp.write(f"{i}") for j in range(i * len(self.mode), (i + 1) * len(self.mode)): try: - fp.write(" %d" % self.palette[j]) + fp.write(f" {self.palette[j]}") except IndexError: fp.write(" 0") fp.write("\n") diff --git a/src/PIL/ImageQt.py b/src/PIL/ImageQt.py index dfe2f80bd..64f07be11 100644 --- a/src/PIL/ImageQt.py +++ b/src/PIL/ImageQt.py @@ -22,18 +22,25 @@ from io import BytesIO from . import Image from ._util import isPath -qt_versions = [["5", "PyQt5"], ["side2", "PySide2"]] +qt_versions = [ + ["side6", "PySide6"], + ["5", "PyQt5"], + ["side2", "PySide2"], +] # If a version has already been imported, attempt it first qt_versions.sort(key=lambda qt_version: qt_version[1] in sys.modules, reverse=True) for qt_version, qt_module in qt_versions: try: - if qt_module == "PyQt5": - from PyQt5.QtGui import QImage, qRgba, QPixmap + if qt_module == "PySide6": + from PySide6.QtCore import QBuffer, QIODevice + from PySide6.QtGui import QImage, QPixmap, qRgba + elif qt_module == "PyQt5": from PyQt5.QtCore import QBuffer, QIODevice + from PyQt5.QtGui import QImage, QPixmap, qRgba elif qt_module == "PySide2": - from PySide2.QtGui import QImage, qRgba, QPixmap from PySide2.QtCore import QBuffer, QIODevice + from PySide2.QtGui import QImage, QPixmap, qRgba except (ImportError, RuntimeError): continue qt_is_installed = True @@ -142,15 +149,10 @@ def _toqclass_helper(im): data = im.tobytes("raw", "BGRX") format = QImage.Format_RGB32 elif im.mode == "RGBA": - try: - data = im.tobytes("raw", "BGRA") - except SystemError: - # workaround for earlier versions - r, g, b, a = im.split() - im = Image.merge("RGBA", (b, g, r, a)) + data = im.tobytes("raw", "BGRA") format = QImage.Format_ARGB32 else: - raise ValueError("unsupported image mode %r" % im.mode) + raise ValueError(f"unsupported image mode {repr(im.mode)}") __data = data or align8to32(im.tobytes(), im.size[0], im.mode) return {"data": __data, "im": im, "format": format, "colortable": colortable} diff --git a/src/PIL/ImageSequence.py b/src/PIL/ImageSequence.py index 4e9f5c210..9df910a43 100644 --- a/src/PIL/ImageSequence.py +++ b/src/PIL/ImageSequence.py @@ -38,8 +38,8 @@ class Iterator: try: self.im.seek(ix) return self.im - except EOFError: - raise IndexError # end of sequence + except EOFError as e: + raise IndexError from e # end of sequence def __iter__(self): return self @@ -49,8 +49,8 @@ class Iterator: self.im.seek(self.position) self.position += 1 return self.im - except EOFError: - raise StopIteration + except EOFError as e: + raise StopIteration from e def all_frames(im, func=None): diff --git a/src/PIL/ImageShow.py b/src/PIL/ImageShow.py index f7e809279..1ada8252c 100644 --- a/src/PIL/ImageShow.py +++ b/src/PIL/ImageShow.py @@ -24,6 +24,14 @@ _viewers = [] def register(viewer, order=1): + """ + The :py:func:`register` function is used to register additional viewers. + + :param viewer: The viewer to be registered. + :param order: + Zero or a negative integer to prepend this viewer to the list, + a positive integer to append it. + """ try: if issubclass(viewer, Viewer): viewer = viewer() @@ -31,7 +39,7 @@ def register(viewer, order=1): pass # raised if viewer wasn't a class if order > 0: _viewers.append(viewer) - elif order < 0: + else: _viewers.insert(0, viewer) @@ -40,9 +48,9 @@ def show(image, title=None, **options): Display a given image. :param image: An image object. - :param title: Optional title. Not all viewers can display the title. + :param title: Optional title. Not all viewers can display the title. :param \**options: Additional viewer options. - :returns: True if a suitable viewer was found, false otherwise. + :returns: ``True`` if a suitable viewer was found, ``False`` otherwise. """ for viewer in _viewers: if viewer.show(image, title=title, **options): @@ -56,10 +64,15 @@ class Viewer: # main api def show(self, image, **options): + """ + The main function for displaying an image. + Converts the given image to the target format and displays it. + """ # save temporary image to disk if not ( - image.mode in ("1", "RGBA") or (self.format == "PNG" and image.mode == "LA") + image.mode in ("1", "RGBA") + or (self.format == "PNG" and image.mode in ("I;16", "LA")) ): base = Image.getmodebase(image.mode) if image.mode != base: @@ -70,25 +83,31 @@ class Viewer: # hook methods format = None + """The format to convert the image into.""" options = {} + """Additional options used to convert the image.""" def get_format(self, image): - """Return format name, or None to save as PGM/PPM""" + """Return format name, or ``None`` to save as PGM/PPM.""" return self.format def get_command(self, file, **options): + """ + Returns the command used to display the file. + Not implemented in the base class. + """ raise NotImplementedError def save_image(self, image): - """Save to temporary file, and return filename""" + """Save to temporary file and return filename.""" return image._dump(format=self.get_format(image), **self.options) def show_image(self, image, **options): - """Display given image""" + """Display the given image.""" return self.show_file(self.save_image(image), **options) def show_file(self, file, **options): - """Display given file""" + """Display the given file.""" os.system(self.get_command(file, **options)) return 1 @@ -96,104 +115,114 @@ class Viewer: # -------------------------------------------------------------------- +class WindowsViewer(Viewer): + """The default viewer on Windows is the default system application for PNG files.""" + + format = "PNG" + options = {"compress_level": 1} + + def get_command(self, file, **options): + return ( + f'start "Pillow" /WAIT "{file}" ' + "&& ping -n 2 127.0.0.1 >NUL " + f'&& del /f "{file}"' + ) + + if sys.platform == "win32": - - class WindowsViewer(Viewer): - format = "PNG" - options = {"compress_level": 1} - - def get_command(self, file, **options): - return ( - 'start "Pillow" /WAIT "%s" ' - "&& ping -n 2 127.0.0.1 >NUL " - '&& del /f "%s"' % (file, file) - ) - register(WindowsViewer) -elif sys.platform == "darwin": - class MacViewer(Viewer): - format = "PNG" - options = {"compress_level": 1} +class MacViewer(Viewer): + """The default viewer on MacOS using ``Preview.app``.""" - def get_command(self, file, **options): - # on darwin open returns immediately resulting in the temp - # file removal while app is opening - command = "open -a Preview.app" - command = "({} {}; sleep 20; rm -f {})&".format( - command, quote(file), quote(file) + format = "PNG" + options = {"compress_level": 1} + + def get_command(self, file, **options): + # on darwin open returns immediately resulting in the temp + # file removal while app is opening + command = "open -a Preview.app" + command = f"({command} {quote(file)}; sleep 20; rm -f {quote(file)})&" + return command + + def show_file(self, file, **options): + """Display given file""" + fd, path = tempfile.mkstemp() + with os.fdopen(fd, "w") as f: + f.write(file) + with open(path) as f: + subprocess.Popen( + ["im=$(cat); open -a Preview.app $im; sleep 20; rm -f $im"], + shell=True, + stdin=f, ) - return command + os.remove(path) + return 1 - def show_file(self, file, **options): - """Display given file""" - fd, path = tempfile.mkstemp() - with os.fdopen(fd, "w") as f: - f.write(file) - with open(path, "r") as f: - subprocess.Popen( - ["im=$(cat); open -a Preview.app $im; sleep 20; rm -f $im"], - shell=True, - stdin=f, - ) - os.remove(path) - return 1 +if sys.platform == "darwin": register(MacViewer) -else: - # unixoids +class UnixViewer(Viewer): + format = "PNG" + options = {"compress_level": 1} - class UnixViewer(Viewer): - format = "PNG" - options = {"compress_level": 1} + def get_command(self, file, **options): + command = self.get_command_ex(file, **options)[0] + return f"({command} {quote(file)}; rm -f {quote(file)})&" - def get_command(self, file, **options): + def show_file(self, file, **options): + """Display given file""" + fd, path = tempfile.mkstemp() + with os.fdopen(fd, "w") as f: + f.write(file) + with open(path) as f: command = self.get_command_ex(file, **options)[0] - return "({} {}; rm -f {})&".format(command, quote(file), quote(file)) + subprocess.Popen( + ["im=$(cat);" + command + " $im; rm -f $im"], shell=True, stdin=f + ) + os.remove(path) + return 1 - def show_file(self, file, **options): - """Display given file""" - fd, path = tempfile.mkstemp() - with os.fdopen(fd, "w") as f: - f.write(file) - with open(path, "r") as f: - command = self.get_command_ex(file, **options)[0] - subprocess.Popen( - ["im=$(cat);" + command + " $im; rm -f $im"], shell=True, stdin=f - ) - os.remove(path) - return 1 - # implementations +class DisplayViewer(UnixViewer): + """The ImageMagick ``display`` command.""" - class DisplayViewer(UnixViewer): - def get_command_ex(self, file, **options): - command = executable = "display" - return command, executable + def get_command_ex(self, file, **options): + command = executable = "display" + return command, executable + +class EogViewer(UnixViewer): + """The GNOME Image Viewer ``eog`` command.""" + + def get_command_ex(self, file, **options): + command = executable = "eog" + return command, executable + + +class XVViewer(UnixViewer): + """ + The X Viewer ``xv`` command. + This viewer supports the ``title`` parameter. + """ + + def get_command_ex(self, file, title=None, **options): + # note: xv is pretty outdated. most modern systems have + # imagemagick's display command instead. + command = executable = "xv" + if title: + command += f" -name {quote(title)}" + return command, executable + + +if sys.platform not in ("win32", "darwin"): # unixoids if shutil.which("display"): register(DisplayViewer) - - class EogViewer(UnixViewer): - def get_command_ex(self, file, **options): - command = executable = "eog" - return command, executable - if shutil.which("eog"): register(EogViewer) - - class XVViewer(UnixViewer): - def get_command_ex(self, file, title=None, **options): - # note: xv is pretty outdated. most modern systems have - # imagemagick's display command instead. - command = executable = "xv" - if title: - command += " -name %s" % quote(title) - return command, executable - if shutil.which("xv"): register(XVViewer) @@ -203,4 +232,5 @@ if __name__ == "__main__": print("Syntax: python ImageShow.py imagefile [title]") sys.exit() - print(show(Image.open(sys.argv[1]), *sys.argv[2:])) + with Image.open(sys.argv[1]) as im: + print(show(im, *sys.argv[2:])) diff --git a/src/PIL/ImageTk.py b/src/PIL/ImageTk.py index ee707cffb..62db7a717 100644 --- a/src/PIL/ImageTk.py +++ b/src/PIL/ImageTk.py @@ -41,7 +41,7 @@ def _pilbitmap_check(): if _pilbitmap_ok is None: try: im = Image.new("1", (1, 1)) - tkinter.BitmapImage(data="PIL:%d" % im.im.id) + tkinter.BitmapImage(data=f"PIL:{im.im.id}") _pilbitmap_ok = 1 except tkinter.TclError: _pilbitmap_ok = 0 @@ -69,7 +69,7 @@ class PhotoImage: image, pixels having alpha 0 are treated as transparent. The constructor takes either a PIL image, or a mode and a size. - Alternatively, you can use the **file** or **data** options to initialize + Alternatively, you can use the ``file`` or ``data`` options to initialize the photo image object. :param image: Either a PIL image, or a mode string. If a mode string is @@ -210,7 +210,7 @@ class BitmapImage: The given image must have mode "1". Pixels having value 0 are treated as transparent. Options, if any, are passed on to Tkinter. The most commonly - used option is **foreground**, which is used to specify the color for the + used option is ``foreground``, which is used to specify the color for the non-transparent parts. See the Tkinter documentation for information on how to specify colours. @@ -229,7 +229,7 @@ class BitmapImage: if _pilbitmap_check(): # fast way (requires the pilbitmap booster patch) image.load() - kw["data"] = "PIL:%d" % image.im.id + kw["data"] = f"PIL:{image.im.id}" self.__im = image # must keep a reference else: # slow but safe way diff --git a/src/PIL/ImageWin.py b/src/PIL/ImageWin.py index 927b1694b..ca9b14c8a 100644 --- a/src/PIL/ImageWin.py +++ b/src/PIL/ImageWin.py @@ -59,7 +59,7 @@ class Dib: with 20 greylevels. To make sure that palettes work properly under Windows, you must call the - **palette** method upon certain events from Windows. + ``palette`` method upon certain events from Windows. :param image: Either a PIL image, or a mode string. If a mode string is used, a size must also be given. The mode can be one of "1", @@ -88,8 +88,8 @@ class Dib: Copy the bitmap contents to a device context. :param handle: Device context (HDC), cast to a Python integer, or an - HDC or HWND instance. In PythonWin, you can use the - :py:meth:`CDC.GetHandleAttrib` to get a suitable handle. + HDC or HWND instance. In PythonWin, you can use + ``CDC.GetHandleAttrib()`` to get a suitable handle. """ if isinstance(handle, HWND): dc = self.image.getdc(handle) @@ -173,7 +173,7 @@ class Dib: Load display memory contents from byte data. :param buffer: A buffer containing display data (usually - data returned from tobytes) + data returned from :py:func:`~PIL.ImageWin.Dib.tobytes`) """ return self.image.frombytes(buffer) diff --git a/src/PIL/IptcImagePlugin.py b/src/PIL/IptcImagePlugin.py index 86a7ee8cb..0bbe50668 100644 --- a/src/PIL/IptcImagePlugin.py +++ b/src/PIL/IptcImagePlugin.py @@ -18,7 +18,10 @@ import os import tempfile from . import Image, ImageFile -from ._binary import i8, i16be as i16, i32be as i32, o8 +from ._binary import i8 +from ._binary import i16be as i16 +from ._binary import i32be as i32 +from ._binary import o8 COMPRESSION = {1: "raw", 5: "jpeg"} @@ -59,14 +62,14 @@ class IptcImageFile(ImageFile.ImageFile): if not len(s): return None, 0 - tag = i8(s[1]), i8(s[2]) + tag = s[1], s[2] # syntax - if i8(s[0]) != 0x1C or tag[0] < 1 or tag[0] > 9: + if s[0] != 0x1C or tag[0] < 1 or tag[0] > 9: raise SyntaxError("invalid IPTC/NAA file") # field size - size = i8(s[3]) + size = s[3] if size > 132: raise OSError("illegal field length in IPTC/NAA file") elif size == 128: @@ -74,7 +77,7 @@ class IptcImageFile(ImageFile.ImageFile): elif size > 128: size = i(self.fp.read(size - 128)) else: - size = i16(s[3:]) + size = i16(s, 3) return tag, size @@ -118,8 +121,8 @@ class IptcImageFile(ImageFile.ImageFile): # compression try: compression = COMPRESSION[self.getint((3, 120))] - except KeyError: - raise OSError("Unknown IPTC image compression") + except KeyError as e: + raise OSError("Unknown IPTC image compression") from e # tile if tag == (8, 10): @@ -158,9 +161,9 @@ class IptcImageFile(ImageFile.ImageFile): o.close() try: - _im = Image.open(outfile) - _im.load() - self.im = _im.im + with Image.open(outfile) as _im: + _im.load() + self.im = _im.im finally: try: os.unlink(outfile) @@ -181,9 +184,10 @@ def getiptcinfo(im): :returns: A dictionary containing IPTC information, or None if no IPTC information block was found. """ - from . import TiffImagePlugin, JpegImagePlugin import io + from . import JpegImagePlugin, TiffImagePlugin + data = None if isinstance(im, IptcImageFile): diff --git a/src/PIL/Jpeg2KImagePlugin.py b/src/PIL/Jpeg2KImagePlugin.py index 2c51d3678..0b0d433db 100644 --- a/src/PIL/Jpeg2KImagePlugin.py +++ b/src/PIL/Jpeg2KImagePlugin.py @@ -176,7 +176,7 @@ class Jpeg2KImageFile(ImageFile.ImageFile): if self.size is None or self.mode is None: raise SyntaxError("unable to determine size/mode") - self.reduce = 0 + self._reduce = 0 self.layers = 0 fd = -1 @@ -200,23 +200,33 @@ class Jpeg2KImageFile(ImageFile.ImageFile): "jpeg2k", (0, 0) + self.size, 0, - (self.codec, self.reduce, self.layers, fd, length), + (self.codec, self._reduce, self.layers, fd, length), ) ] + @property + def reduce(self): + # https://github.com/python-pillow/Pillow/issues/4343 found that the + # new Image 'reduce' method was shadowed by this plugin's 'reduce' + # property. This attempts to allow for both scenarios + return self._reduce or super().reduce + + @reduce.setter + def reduce(self, value): + self._reduce = value + def load(self): - if self.reduce: - power = 1 << self.reduce + if self.tile and self._reduce: + power = 1 << self._reduce adjust = power >> 1 self._size = ( int((self.size[0] + adjust) / power), int((self.size[1] + adjust) / power), ) - if self.tile: # Update the reduce and layers settings t = self.tile[0] - t3 = (t[3][0], self.reduce, self.layers, t[3][3], t[3][4]) + t3 = (t[3][0], self._reduce, self.layers, t[3][3], t[3][4]) self.tile = [(t[0], (0, 0) + self.size, t[2], t3)] return ImageFile.ImageFile.load(self) diff --git a/src/PIL/JpegImagePlugin.py b/src/PIL/JpegImagePlugin.py index 9cba544de..054495e6f 100644 --- a/src/PIL/JpegImagePlugin.py +++ b/src/PIL/JpegImagePlugin.py @@ -36,11 +36,14 @@ import io import os import struct import subprocess +import sys import tempfile import warnings from . import Image, ImageFile, TiffImagePlugin -from ._binary import i8, i16be as i16, i32be as i32, o8 +from ._binary import i16be as i16 +from ._binary import i32be as i32 +from ._binary import o8 from .JpegPresets import presets # @@ -71,7 +74,7 @@ def APP(self, marker): self.info["jfif_version"] = divmod(version, 256) # extract JFIF properties try: - jfif_unit = i8(s[7]) + jfif_unit = s[7] jfif_density = i16(s, 8), i16(s, 10) except Exception: pass @@ -111,7 +114,7 @@ def APP(self, marker): code = i16(s, offset) offset += 2 # resource name (usually empty) - name_len = i8(s[offset]) + name_len = s[offset] # name = s[offset+1:offset+1+name_len] offset += 1 + name_len offset += offset & 1 # align @@ -121,10 +124,10 @@ def APP(self, marker): data = s[offset : offset + size] if code == 0x03ED: # ResolutionInfo data = { - "XResolution": i32(data[:4]) / 65536, - "DisplayedUnitsX": i16(data[4:8]), - "YResolution": i32(data[8:12]) / 65536, - "DisplayedUnitsY": i16(data[12:]), + "XResolution": i32(data, 0) / 65536, + "DisplayedUnitsX": i16(data, 4), + "YResolution": i32(data, 8) / 65536, + "DisplayedUnitsY": i16(data, 12), } photoshop[code] = data offset += size @@ -136,7 +139,7 @@ def APP(self, marker): self.info["adobe"] = i16(s, 5) # extract Adobe custom properties try: - adobe_transform = i8(s[1]) + adobe_transform = s[1] except Exception: pass else: @@ -176,6 +179,7 @@ def COM(self, marker): n = i16(self.fp.read(2)) - 2 s = ImageFile._safe_read(self.fp, n) + self.info["comment"] = s self.app["COM"] = s # compatibility self.applist.append(("COM", s)) @@ -190,13 +194,13 @@ def SOF(self, marker): n = i16(self.fp.read(2)) - 2 s = ImageFile._safe_read(self.fp, n) - self._size = i16(s[3:]), i16(s[1:]) + self._size = i16(s, 3), i16(s, 1) - self.bits = i8(s[0]) + self.bits = s[0] if self.bits != 8: - raise SyntaxError("cannot handle %d-bit layers" % self.bits) + raise SyntaxError(f"cannot handle {self.bits}-bit layers") - self.layers = i8(s[5]) + self.layers = s[5] if self.layers == 1: self.mode = "L" elif self.layers == 3: @@ -204,7 +208,7 @@ def SOF(self, marker): elif self.layers == 4: self.mode = "CMYK" else: - raise SyntaxError("cannot handle %d-layer images" % self.layers) + raise SyntaxError(f"cannot handle {self.layers}-layer images") if marker in [0xFFC2, 0xFFC6, 0xFFCA, 0xFFCE]: self.info["progressive"] = self.info["progression"] = 1 @@ -212,7 +216,7 @@ def SOF(self, marker): if self.icclist: # fixup icc profile self.icclist.sort() # sort by sequence number - if i8(self.icclist[0][13]) == len(self.icclist): + if self.icclist[0][13] == len(self.icclist): profile = [] for p in self.icclist: profile.append(p[14:]) @@ -220,19 +224,18 @@ def SOF(self, marker): else: icc_profile = None # wrong number of fragments self.info["icc_profile"] = icc_profile - self.icclist = None + self.icclist = [] for i in range(6, len(s), 3): t = s[i : i + 3] # 4-tuples: id, vsamp, hsamp, qtable - self.layer.append((t[0], i8(t[1]) // 16, i8(t[1]) & 15, i8(t[2]))) + self.layer.append((t[0], t[1] // 16, t[1] & 15, t[2])) def DQT(self, marker): # - # Define quantization table. Support baseline 8-bit tables - # only. Note that there might be more than one table in - # each marker. + # Define quantization table. Note that there might be more + # than one table in each marker. # FIXME: The quantization tables can be used to estimate the # compression quality. @@ -240,15 +243,16 @@ def DQT(self, marker): n = i16(self.fp.read(2)) - 2 s = ImageFile._safe_read(self.fp, n) while len(s): - if len(s) < 65: + v = s[0] + precision = 1 if (v // 16 == 0) else 2 # in bytes + qt_length = 1 + precision * 64 + if len(s) < qt_length: raise SyntaxError("bad quantization table marker") - v = i8(s[0]) - if v // 16 == 0: - self.quantization[v & 15] = array.array("B", s[1:65]) - s = s[65:] - else: - return # FIXME: add code to read 16-bit tables! - # raise SyntaxError, "bad quantization table element size" + data = array.array("B" if precision == 1 else "H", s[1:qt_length]) + if sys.byteorder == "little" and precision > 1: + data.byteswap() # the values are always big-endian + self.quantization[v & 15] = data + s = s[qt_length:] # @@ -322,7 +326,8 @@ MARKER = { def _accept(prefix): - return prefix[0:1] == b"\377" + # Magic number was taken from https://en.wikipedia.org/wiki/JPEG + return prefix[0:3] == b"\xFF\xD8\xFF" ## @@ -336,10 +341,11 @@ class JpegImageFile(ImageFile.ImageFile): def _open(self): - s = self.fp.read(1) + s = self.fp.read(3) - if i8(s) != 255: + if not _accept(s): raise SyntaxError("not a JPEG file") + s = b"\xFF" # Create attributes self.bits = self.layers = 0 @@ -355,7 +361,7 @@ class JpegImageFile(ImageFile.ImageFile): while True: - i = i8(s) + i = s[0] if i == 0xFF: s = s + self.fp.read(1) i = i16(s) @@ -448,9 +454,9 @@ class JpegImageFile(ImageFile.ImageFile): raise ValueError("Invalid Filename") try: - _im = Image.open(path) - _im.load() - self.im = _im.im + with Image.open(path) as _im: + _im.load() + self.im = _im.im finally: try: os.unlink(path) @@ -469,13 +475,6 @@ class JpegImageFile(ImageFile.ImageFile): return _getmp(self) -def _fixup_dict(src_dict): - # Helper function for _getexif() - # returns a dict with any single item tuples/lists as individual values - exif = Image.Exif() - return exif._fixup_dict(src_dict) - - def _getexif(self): if "exif" not in self.info: return None @@ -485,7 +484,7 @@ def _getexif(self): def _getmp(self): # Extract MP information. This method was inspired by the "highly # experimental" _getexif version that's been in use for years now, - # itself based on the ImageFileDirectory class in the TIFF plug-in. + # itself based on the ImageFileDirectory class in the TIFF plugin. # The MP record essentially consists of a TIFF file embedded in a JPEG # application marker. @@ -502,20 +501,20 @@ def _getmp(self): file_contents.seek(info.next) info.load(file_contents) mp = dict(info) - except Exception: - raise SyntaxError("malformed MP Index (unreadable directory)") + except Exception as e: + raise SyntaxError("malformed MP Index (unreadable directory)") from e # it's an error not to have a number of images try: quant = mp[0xB001] - except KeyError: - raise SyntaxError("malformed MP Index (no number of images)") + except KeyError as e: + raise SyntaxError("malformed MP Index (no number of images)") from e # get MP entries mpentries = [] try: rawmpentries = mp[0xB002] for entrynum in range(0, quant): unpackedentry = struct.unpack_from( - "{}LLLHH".format(endianness), rawmpentries, entrynum * 16 + f"{endianness}LLLHH", rawmpentries, entrynum * 16 ) labels = ("Attribute", "Size", "DataOffset", "EntryNo1", "EntryNo2") mpentry = dict(zip(labels, unpackedentry)) @@ -544,8 +543,8 @@ def _getmp(self): mpentry["Attribute"] = mpentryattr mpentries.append(mpentry) mp[0xB002] = mpentries - except KeyError: - raise SyntaxError("malformed MP Index (bad MP Entry)") + except KeyError as e: + raise SyntaxError("malformed MP Index (bad MP Entry)") from e # Next we should try and parse the individual image unique ID list; # we don't because I've never seen this actually used in a real MPO # file and so can't test it. @@ -592,9 +591,9 @@ def convert_dict_qtables(qtables): def get_sampling(im): - # There's no subsampling when image have only 1 layer + # There's no subsampling when images have only 1 layer # (grayscale images) or when they are CMYK (4 layers), - # so set subsampling to default value. + # so set subsampling to the default value. # # NOTE: currently Pillow can't encode JPEG to YCCK format. # If YCCK support is added in the future, subsampling code will have @@ -609,24 +608,24 @@ def _save(im, fp, filename): try: rawmode = RAWMODE[im.mode] - except KeyError: - raise OSError("cannot write mode %s as JPEG" % im.mode) + except KeyError as e: + raise OSError(f"cannot write mode {im.mode} as JPEG") from e info = im.encoderinfo dpi = [round(x) for x in info.get("dpi", (0, 0))] - quality = info.get("quality", 0) + quality = info.get("quality", -1) subsampling = info.get("subsampling", -1) qtables = info.get("qtables") if quality == "keep": - quality = 0 + quality = -1 subsampling = "keep" qtables = "keep" elif quality in presets: preset = presets[quality] - quality = 0 + quality = -1 subsampling = preset.get("subsampling", -1) qtables = preset.get("quantization") elif not isinstance(quality, int): @@ -662,8 +661,8 @@ def _save(im, fp, filename): for line in qtables.splitlines() for num in line.split("#", 1)[0].split() ] - except ValueError: - raise ValueError("Invalid quantization table") + except ValueError as e: + raise ValueError("Invalid quantization table") from e else: qtables = [lines[s : s + 64] for s in range(0, len(lines), 64)] if isinstance(qtables, (tuple, list, dict)): @@ -677,9 +676,9 @@ def _save(im, fp, filename): try: if len(table) != 64: raise TypeError - table = array.array("B", table) - except TypeError: - raise ValueError("Invalid quantization table") + table = array.array("H", table) + except TypeError as e: + raise ValueError("Invalid quantization table") from e else: qtables[idx] = list(table) return qtables @@ -749,8 +748,8 @@ def _save(im, fp, filename): # CMYK can be bigger if im.mode == "CMYK": bufsize = 4 * im.size[0] * im.size[1] - # keep sets quality to 0, but the actual value may be high. - elif quality >= 95 or quality == 0: + # keep sets quality to -1, but the actual value may be high. + elif quality >= 95 or quality == -1: bufsize = 2 * im.size[0] * im.size[1] else: bufsize = im.size[0] * im.size[1] diff --git a/src/PIL/JpegPresets.py b/src/PIL/JpegPresets.py index 012bf81b0..79d10ebb2 100644 --- a/src/PIL/JpegPresets.py +++ b/src/PIL/JpegPresets.py @@ -1,9 +1,11 @@ """ JPEG quality settings equivalent to the Photoshop settings. +Can be used when saving JPEG files. -More presets can be added to the presets dict if needed. - -Can be use when saving JPEG file. +The following presets are available by default: +``web_low``, ``web_medium``, ``web_high``, ``web_very_high``, ``web_maximum``, +``low``, ``medium``, ``high``, ``maximum``. +More presets can be added to the :py:data:`presets` dict if needed. To apply the preset, specify:: @@ -21,7 +23,6 @@ Example:: im.save("image_name.jpg", quality="web_high") - Subsampling ----------- @@ -33,7 +34,7 @@ Possible subsampling values are 0, 1 and 2 that correspond to 4:4:4, 4:2:2 and 4:2:0. You can get the subsampling of a JPEG with the -`JpegImagePlugin.get_sampling(im)` function. +:func:`.JpegImagePlugin.get_sampling` function. In JPEG compressed data a JPEG marker is used instead of an EXIF tag. (ref.: https://www.exiv2.org/tags.html) @@ -63,7 +64,7 @@ The tables format between im.quantization and quantization in presets differ in 3. The zigzag order is remove in the preset (needed by libjpeg >= 6a). You can convert the dict format to the preset format with the -`JpegImagePlugin.convert_dict_qtables(dict_qtables)` function. +:func:`.JpegImagePlugin.convert_dict_qtables()` function. Libjpeg ref.: https://web.archive.org/web/20120328125543/http://www.jpegcameras.com/libjpeg/libjpeg-3.html @@ -71,7 +72,7 @@ https://web.archive.org/web/20120328125543/http://www.jpegcameras.com/libjpeg/li """ # fmt: off -presets = { # noqa: E128 +presets = { 'web_low': {'subsampling': 2, # "4:2:0" 'quantization': [ [20, 16, 25, 39, 50, 46, 62, 68, @@ -112,16 +113,16 @@ presets = { # noqa: E128 ]}, 'web_high': {'subsampling': 0, # "4:4:4" 'quantization': [ - [6, 4, 4, 6, 9, 11, 12, 16, - 4, 5, 5, 6, 8, 10, 12, 12, - 4, 5, 5, 6, 10, 12, 14, 19, - 6, 6, 6, 11, 12, 15, 19, 28, - 9, 8, 10, 12, 16, 20, 27, 31, + [6, 4, 4, 6, 9, 11, 12, 16, + 4, 5, 5, 6, 8, 10, 12, 12, + 4, 5, 5, 6, 10, 12, 14, 19, + 6, 6, 6, 11, 12, 15, 19, 28, + 9, 8, 10, 12, 16, 20, 27, 31, 11, 10, 12, 15, 20, 27, 31, 31, 12, 12, 14, 19, 27, 31, 31, 31, 16, 12, 19, 28, 31, 31, 31, 31], - [7, 7, 13, 24, 26, 31, 31, 31, - 7, 12, 16, 21, 31, 31, 31, 31, + [7, 7, 13, 24, 26, 31, 31, 31, + 7, 12, 16, 21, 31, 31, 31, 31, 13, 16, 17, 31, 31, 31, 31, 31, 24, 21, 31, 31, 31, 31, 31, 31, 26, 31, 31, 31, 31, 31, 31, 31, @@ -131,18 +132,18 @@ presets = { # noqa: E128 ]}, 'web_very_high': {'subsampling': 0, # "4:4:4" 'quantization': [ - [2, 2, 2, 2, 3, 4, 5, 6, - 2, 2, 2, 2, 3, 4, 5, 6, - 2, 2, 2, 2, 4, 5, 7, 9, - 2, 2, 2, 4, 5, 7, 9, 12, - 3, 3, 4, 5, 8, 10, 12, 12, - 4, 4, 5, 7, 10, 12, 12, 12, - 5, 5, 7, 9, 12, 12, 12, 12, - 6, 6, 9, 12, 12, 12, 12, 12], - [3, 3, 5, 9, 13, 15, 15, 15, - 3, 4, 6, 11, 14, 12, 12, 12, - 5, 6, 9, 14, 12, 12, 12, 12, - 9, 11, 14, 12, 12, 12, 12, 12, + [2, 2, 2, 2, 3, 4, 5, 6, + 2, 2, 2, 2, 3, 4, 5, 6, + 2, 2, 2, 2, 4, 5, 7, 9, + 2, 2, 2, 4, 5, 7, 9, 12, + 3, 3, 4, 5, 8, 10, 12, 12, + 4, 4, 5, 7, 10, 12, 12, 12, + 5, 5, 7, 9, 12, 12, 12, 12, + 6, 6, 9, 12, 12, 12, 12, 12], + [3, 3, 5, 9, 13, 15, 15, 15, + 3, 4, 6, 11, 14, 12, 12, 12, + 5, 6, 9, 14, 12, 12, 12, 12, + 9, 11, 14, 12, 12, 12, 12, 12, 13, 14, 12, 12, 12, 12, 12, 12, 15, 12, 12, 12, 12, 12, 12, 12, 15, 12, 12, 12, 12, 12, 12, 12, @@ -189,8 +190,8 @@ presets = { # noqa: E128 'medium': {'subsampling': 2, # "4:2:0" 'quantization': [ [12, 8, 8, 12, 17, 21, 24, 17, - 8, 9, 9, 11, 15, 19, 12, 12, - 8, 9, 10, 12, 19, 12, 12, 12, + 8, 9, 9, 11, 15, 19, 12, 12, + 8, 9, 10, 12, 19, 12, 12, 12, 12, 11, 12, 21, 12, 12, 12, 12, 17, 15, 19, 12, 12, 12, 12, 12, 21, 19, 12, 12, 12, 12, 12, 12, @@ -207,16 +208,16 @@ presets = { # noqa: E128 ]}, 'high': {'subsampling': 0, # "4:4:4" 'quantization': [ - [6, 4, 4, 6, 9, 11, 12, 16, - 4, 5, 5, 6, 8, 10, 12, 12, - 4, 5, 5, 6, 10, 12, 12, 12, - 6, 6, 6, 11, 12, 12, 12, 12, - 9, 8, 10, 12, 12, 12, 12, 12, + [6, 4, 4, 6, 9, 11, 12, 16, + 4, 5, 5, 6, 8, 10, 12, 12, + 4, 5, 5, 6, 10, 12, 12, 12, + 6, 6, 6, 11, 12, 12, 12, 12, + 9, 8, 10, 12, 12, 12, 12, 12, 11, 10, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 16, 12, 12, 12, 12, 12, 12, 12], - [7, 7, 13, 24, 20, 20, 17, 17, - 7, 12, 16, 14, 14, 12, 12, 12, + [7, 7, 13, 24, 20, 20, 17, 17, + 7, 12, 16, 14, 14, 12, 12, 12, 13, 16, 14, 14, 12, 12, 12, 12, 24, 14, 14, 12, 12, 12, 12, 12, 20, 14, 12, 12, 12, 12, 12, 12, @@ -226,18 +227,18 @@ presets = { # noqa: E128 ]}, 'maximum': {'subsampling': 0, # "4:4:4" 'quantization': [ - [2, 2, 2, 2, 3, 4, 5, 6, - 2, 2, 2, 2, 3, 4, 5, 6, - 2, 2, 2, 2, 4, 5, 7, 9, - 2, 2, 2, 4, 5, 7, 9, 12, - 3, 3, 4, 5, 8, 10, 12, 12, - 4, 4, 5, 7, 10, 12, 12, 12, - 5, 5, 7, 9, 12, 12, 12, 12, - 6, 6, 9, 12, 12, 12, 12, 12], - [3, 3, 5, 9, 13, 15, 15, 15, - 3, 4, 6, 10, 14, 12, 12, 12, - 5, 6, 9, 14, 12, 12, 12, 12, - 9, 10, 14, 12, 12, 12, 12, 12, + [2, 2, 2, 2, 3, 4, 5, 6, + 2, 2, 2, 2, 3, 4, 5, 6, + 2, 2, 2, 2, 4, 5, 7, 9, + 2, 2, 2, 4, 5, 7, 9, 12, + 3, 3, 4, 5, 8, 10, 12, 12, + 4, 4, 5, 7, 10, 12, 12, 12, + 5, 5, 7, 9, 12, 12, 12, 12, + 6, 6, 9, 12, 12, 12, 12, 12], + [3, 3, 5, 9, 13, 15, 15, 15, + 3, 4, 6, 10, 14, 12, 12, 12, + 5, 6, 9, 14, 12, 12, 12, 12, + 9, 10, 14, 12, 12, 12, 12, 12, 13, 14, 12, 12, 12, 12, 12, 12, 15, 12, 12, 12, 12, 12, 12, 12, 15, 12, 12, 12, 12, 12, 12, 12, diff --git a/src/PIL/MicImagePlugin.py b/src/PIL/MicImagePlugin.py index 8610988fc..2aed26030 100644 --- a/src/PIL/MicImagePlugin.py +++ b/src/PIL/MicImagePlugin.py @@ -46,8 +46,8 @@ class MicImageFile(TiffImagePlugin.TiffImageFile): try: self.ole = olefile.OleFileIO(self.fp) - except OSError: - raise SyntaxError("not an MIC file; invalid OLE file") + except OSError as e: + raise SyntaxError("not an MIC file; invalid OLE file") from e # find ACI subfiles with Image members (maybe not the # best way to identify MIC files, but what the... ;-) @@ -64,27 +64,21 @@ class MicImageFile(TiffImagePlugin.TiffImageFile): self.__fp = self.fp self.frame = None + self._n_frames = len(self.images) + self.is_animated = self._n_frames > 1 if len(self.images) > 1: self.category = Image.CONTAINER self.seek(0) - @property - def n_frames(self): - return len(self.images) - - @property - def is_animated(self): - return len(self.images) > 1 - def seek(self, frame): if not self._seek_check(frame): return try: filename = self.images[frame] - except IndexError: - raise EOFError("no such frame") + except IndexError as e: + raise EOFError("no such frame") from e self.fp = self.ole.openstream(filename) diff --git a/src/PIL/MpoImagePlugin.py b/src/PIL/MpoImagePlugin.py index e97176d57..575cc9c8e 100644 --- a/src/PIL/MpoImagePlugin.py +++ b/src/PIL/MpoImagePlugin.py @@ -48,15 +48,16 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile): def _after_jpeg_open(self, mpheader=None): self.mpinfo = mpheader if mpheader is not None else self._getmp() - self.__framecount = self.mpinfo[0xB001] + self.n_frames = self.mpinfo[0xB001] self.__mpoffsets = [ mpent["DataOffset"] + self.info["mpoffset"] for mpent in self.mpinfo[0xB002] ] self.__mpoffsets[0] = 0 # Note that the following assertion will only be invalid if something # gets broken within JpegImagePlugin. - assert self.__framecount == len(self.__mpoffsets) + assert self.n_frames == len(self.__mpoffsets) del self.info["mpoffset"] # no longer needed + self.is_animated = self.n_frames > 1 self.__fp = self.fp # FIXME: hack self.__fp.seek(self.__mpoffsets[0]) # get ready to read first frame self.__frame = 0 @@ -67,14 +68,6 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile): def load_seek(self, pos): self.__fp.seek(pos) - @property - def n_frames(self): - return self.__framecount - - @property - def is_animated(self): - return self.__framecount > 1 - def seek(self, frame): if not self._seek_check(frame): return diff --git a/src/PIL/MspImagePlugin.py b/src/PIL/MspImagePlugin.py index 2b2937ecf..e1fdc1fdf 100644 --- a/src/PIL/MspImagePlugin.py +++ b/src/PIL/MspImagePlugin.py @@ -27,7 +27,8 @@ import io import struct from . import Image, ImageFile -from ._binary import i8, i16le as i16, o16le as o16 +from ._binary import i16le as i16 +from ._binary import o16le as o16 # # read MSP files @@ -51,18 +52,18 @@ class MspImageFile(ImageFile.ImageFile): # Header s = self.fp.read(32) - if s[:4] not in [b"DanM", b"LinS"]: + if not _accept(s): raise SyntaxError("not an MSP file") # Header checksum checksum = 0 for i in range(0, 32, 2): - checksum = checksum ^ i16(s[i : i + 2]) + checksum = checksum ^ i16(s, i) if checksum != 0: raise SyntaxError("bad MSP checksum") self.mode = "1" - self._size = i16(s[4:]), i16(s[6:]) + self._size = i16(s, 4), i16(s, 6) if s[:4] == b"DanM": self.tile = [("raw", (0, 0) + self.size, 32, ("1", 0, 1))] @@ -114,10 +115,10 @@ class MspDecoder(ImageFile.PyDecoder): try: self.fd.seek(32) rowmap = struct.unpack_from( - "<%dH" % (self.state.ysize), self.fd.read(self.state.ysize * 2) + f"<{self.state.ysize}H", self.fd.read(self.state.ysize * 2) ) - except struct.error: - raise OSError("Truncated MSP file in row map") + except struct.error as e: + raise OSError("Truncated MSP file in row map") from e for x, rowlen in enumerate(rowmap): try: @@ -131,7 +132,7 @@ class MspDecoder(ImageFile.PyDecoder): ) idx = 0 while idx < rowlen: - runtype = i8(row[idx]) + runtype = row[idx] idx += 1 if runtype == 0: (runcount, runval) = struct.unpack_from("Bc", row, idx) @@ -142,8 +143,8 @@ class MspDecoder(ImageFile.PyDecoder): img.write(row[idx : idx + runcount]) idx += runcount - except struct.error: - raise OSError("Corrupted MSP file in row %d" % x) + except struct.error as e: + raise OSError(f"Corrupted MSP file in row {x}") from e self.set_as_raw(img.getvalue(), ("1", 0, 1)) @@ -160,7 +161,7 @@ Image.register_decoder("MSP", MspDecoder) def _save(im, fp, filename): if im.mode != "1": - raise OSError("cannot write mode %s as MSP" % im.mode) + raise OSError(f"cannot write mode {im.mode} as MSP") # create MSP header header = [0] * 16 diff --git a/src/PIL/PSDraw.py b/src/PIL/PSDraw.py index 762d31e88..c1bd933d3 100644 --- a/src/PIL/PSDraw.py +++ b/src/PIL/PSDraw.py @@ -2,7 +2,7 @@ # The Python Imaging Library # $Id$ # -# simple postscript graphics interface +# Simple PostScript graphics interface # # History: # 1996-04-20 fl Created @@ -20,13 +20,13 @@ import sys from . import EpsImagePlugin ## -# Simple Postscript graphics interface. +# Simple PostScript graphics interface. class PSDraw: """ - Sets up printing to the given file. If **fp** is omitted, - :py:attr:`sys.stdout` is assumed. + Sets up printing to the given file. If ``fp`` is omitted, + :py:data:`sys.stdout` is assumed. """ def __init__(self, fp=None): @@ -41,7 +41,7 @@ class PSDraw: self.fp.write(bytes(to_write, "UTF-8")) def begin_document(self, id=None): - """Set up printing of a document. (Write Postscript DSC header.)""" + """Set up printing of a document. (Write PostScript DSC header.)""" # FIXME: incomplete self._fp_write( "%!PS-Adobe-3.0\n" @@ -57,7 +57,7 @@ class PSDraw: self.isofont = {} def end_document(self): - """Ends printing. (Write Postscript DSC footer.)""" + """Ends printing. (Write PostScript DSC footer.)""" self._fp_write("%%EndDocument\nrestore showpage\n%%End\n") if hasattr(self.fp, "flush"): self.fp.flush() @@ -66,24 +66,23 @@ class PSDraw: """ Selects which font to use. - :param font: A Postscript font name + :param font: A PostScript font name :param size: Size in points. """ if font not in self.isofont: # reencode font - self._fp_write("/PSDraw-{} ISOLatin1Encoding /{} E\n".format(font, font)) + self._fp_write(f"/PSDraw-{font} ISOLatin1Encoding /{font} E\n") self.isofont[font] = 1 # rough - self._fp_write("/F0 %d /PSDraw-%s F\n" % (size, font)) + self._fp_write(f"/F0 {size} /PSDraw-{font} F\n") def line(self, xy0, xy1): """ Draws a line between the two points. Coordinates are given in - Postscript point coordinates (72 points per inch, (0, 0) is the lower + PostScript point coordinates (72 points per inch, (0, 0) is the lower left corner of the page). """ - xy = xy0 + xy1 - self._fp_write("%d %d %d %d Vl\n" % xy) + self._fp_write("%d %d %d %d Vl\n" % (*xy0, *xy1)) def rectangle(self, box): """ @@ -107,8 +106,7 @@ class PSDraw: """ text = "\\(".join(text.split("(")) text = "\\)".join(text.split(")")) - xy = xy + (text,) - self._fp_write("%d %d M (%s) S\n" % xy) + self._fp_write(f"{xy[0]} {xy[1]} M ({text}) S\n") def image(self, box, im, dpi=None): """Draw a PIL image, centered in the given box.""" @@ -132,21 +130,21 @@ class PSDraw: y = ymax dx = (xmax - x) / 2 + box[0] dy = (ymax - y) / 2 + box[1] - self._fp_write("gsave\n{:f} {:f} translate\n".format(dx, dy)) + self._fp_write(f"gsave\n{dx:f} {dy:f} translate\n") if (x, y) != im.size: # EpsImagePlugin._save prints the image at (0,0,xsize,ysize) sx = x / im.size[0] sy = y / im.size[1] - self._fp_write("{:f} {:f} scale\n".format(sx, sy)) + self._fp_write(f"{sx:f} {sy:f} scale\n") EpsImagePlugin._save(im, self.fp, None, 0) self._fp_write("\ngrestore\n") # -------------------------------------------------------------------- -# Postscript driver +# PostScript driver # -# EDROFF.PS -- Postscript driver for Edroff 2 +# EDROFF.PS -- PostScript driver for Edroff 2 # # History: # 94-01-25 fl: created (edroff 2.04) @@ -176,7 +174,7 @@ EDROFF_PS = """\ """ # -# VDI.PS -- Postscript driver for VDI meta commands +# VDI.PS -- PostScript driver for VDI meta commands # # History: # 94-01-25 fl: created (edroff 2.04) diff --git a/src/PIL/PaletteFile.py b/src/PIL/PaletteFile.py index 73f1b4b27..6ccaa1f53 100644 --- a/src/PIL/PaletteFile.py +++ b/src/PIL/PaletteFile.py @@ -15,11 +15,9 @@ from ._binary import o8 -## -# File handler for Teragon-style palette files. - class PaletteFile: + """File handler for Teragon-style palette files.""" rawmode = "RGB" diff --git a/src/PIL/PalmImagePlugin.py b/src/PIL/PalmImagePlugin.py index 804ece34a..700f10e3f 100644 --- a/src/PIL/PalmImagePlugin.py +++ b/src/PIL/PalmImagePlugin.py @@ -8,10 +8,11 @@ ## from . import Image, ImageFile -from ._binary import o8, o16be as o16b +from ._binary import o8 +from ._binary import o16be as o16b # fmt: off -_Palm8BitColormapValues = ( # noqa: E131 +_Palm8BitColormapValues = ( (255, 255, 255), (255, 204, 255), (255, 153, 255), (255, 102, 255), (255, 51, 255), (255, 0, 255), (255, 255, 204), (255, 204, 204), (255, 153, 204), (255, 102, 204), (255, 51, 204), (255, 0, 204), @@ -30,15 +31,15 @@ _Palm8BitColormapValues = ( # noqa: E131 (102, 255, 204), (102, 204, 204), (102, 153, 204), (102, 102, 204), (102, 51, 204), (102, 0, 204), (102, 255, 153), (102, 204, 153), (102, 153, 153), (102, 102, 153), (102, 51, 153), (102, 0, 153), - (51, 255, 255), (51, 204, 255), (51, 153, 255), (51, 102, 255), - (51, 51, 255), (51, 0, 255), (51, 255, 204), (51, 204, 204), - (51, 153, 204), (51, 102, 204), (51, 51, 204), (51, 0, 204), - (51, 255, 153), (51, 204, 153), (51, 153, 153), (51, 102, 153), - (51, 51, 153), (51, 0, 153), (0, 255, 255), (0, 204, 255), - (0, 153, 255), (0, 102, 255), (0, 51, 255), (0, 0, 255), - (0, 255, 204), (0, 204, 204), (0, 153, 204), (0, 102, 204), - (0, 51, 204), (0, 0, 204), (0, 255, 153), (0, 204, 153), - (0, 153, 153), (0, 102, 153), (0, 51, 153), (0, 0, 153), + (51, 255, 255), (51, 204, 255), (51, 153, 255), (51, 102, 255), + (51, 51, 255), (51, 0, 255), (51, 255, 204), (51, 204, 204), + (51, 153, 204), (51, 102, 204), (51, 51, 204), (51, 0, 204), + (51, 255, 153), (51, 204, 153), (51, 153, 153), (51, 102, 153), + (51, 51, 153), (51, 0, 153), (0, 255, 255), (0, 204, 255), + (0, 153, 255), (0, 102, 255), (0, 51, 255), (0, 0, 255), + (0, 255, 204), (0, 204, 204), (0, 153, 204), (0, 102, 204), + (0, 51, 204), (0, 0, 204), (0, 255, 153), (0, 204, 153), + (0, 153, 153), (0, 102, 153), (0, 51, 153), (0, 0, 153), (255, 255, 102), (255, 204, 102), (255, 153, 102), (255, 102, 102), (255, 51, 102), (255, 0, 102), (255, 255, 51), (255, 204, 51), (255, 153, 51), (255, 102, 51), (255, 51, 51), (255, 0, 51), @@ -57,25 +58,25 @@ _Palm8BitColormapValues = ( # noqa: E131 (102, 255, 51), (102, 204, 51), (102, 153, 51), (102, 102, 51), (102, 51, 51), (102, 0, 51), (102, 255, 0), (102, 204, 0), (102, 153, 0), (102, 102, 0), (102, 51, 0), (102, 0, 0), - (51, 255, 102), (51, 204, 102), (51, 153, 102), (51, 102, 102), - (51, 51, 102), (51, 0, 102), (51, 255, 51), (51, 204, 51), - (51, 153, 51), (51, 102, 51), (51, 51, 51), (51, 0, 51), - (51, 255, 0), (51, 204, 0), (51, 153, 0), (51, 102, 0), - (51, 51, 0), (51, 0, 0), (0, 255, 102), (0, 204, 102), - (0, 153, 102), (0, 102, 102), (0, 51, 102), (0, 0, 102), - (0, 255, 51), (0, 204, 51), (0, 153, 51), (0, 102, 51), - (0, 51, 51), (0, 0, 51), (0, 255, 0), (0, 204, 0), - (0, 153, 0), (0, 102, 0), (0, 51, 0), (17, 17, 17), - (34, 34, 34), (68, 68, 68), (85, 85, 85), (119, 119, 119), + (51, 255, 102), (51, 204, 102), (51, 153, 102), (51, 102, 102), + (51, 51, 102), (51, 0, 102), (51, 255, 51), (51, 204, 51), + (51, 153, 51), (51, 102, 51), (51, 51, 51), (51, 0, 51), + (51, 255, 0), (51, 204, 0), (51, 153, 0), (51, 102, 0), + (51, 51, 0), (51, 0, 0), (0, 255, 102), (0, 204, 102), + (0, 153, 102), (0, 102, 102), (0, 51, 102), (0, 0, 102), + (0, 255, 51), (0, 204, 51), (0, 153, 51), (0, 102, 51), + (0, 51, 51), (0, 0, 51), (0, 255, 0), (0, 204, 0), + (0, 153, 0), (0, 102, 0), (0, 51, 0), (17, 17, 17), + (34, 34, 34), (68, 68, 68), (85, 85, 85), (119, 119, 119), (136, 136, 136), (170, 170, 170), (187, 187, 187), (221, 221, 221), (238, 238, 238), (192, 192, 192), (128, 0, 0), (128, 0, 128), - (0, 128, 0), (0, 128, 128), (0, 0, 0), (0, 0, 0), - (0, 0, 0), (0, 0, 0), (0, 0, 0), (0, 0, 0), - (0, 0, 0), (0, 0, 0), (0, 0, 0), (0, 0, 0), - (0, 0, 0), (0, 0, 0), (0, 0, 0), (0, 0, 0), - (0, 0, 0), (0, 0, 0), (0, 0, 0), (0, 0, 0), - (0, 0, 0), (0, 0, 0), (0, 0, 0), (0, 0, 0), - (0, 0, 0), (0, 0, 0), (0, 0, 0), (0, 0, 0)) + (0, 128, 0), (0, 128, 128), (0, 0, 0), (0, 0, 0), + (0, 0, 0), (0, 0, 0), (0, 0, 0), (0, 0, 0), + (0, 0, 0), (0, 0, 0), (0, 0, 0), (0, 0, 0), + (0, 0, 0), (0, 0, 0), (0, 0, 0), (0, 0, 0), + (0, 0, 0), (0, 0, 0), (0, 0, 0), (0, 0, 0), + (0, 0, 0), (0, 0, 0), (0, 0, 0), (0, 0, 0), + (0, 0, 0), (0, 0, 0), (0, 0, 0), (0, 0, 0)) # fmt: on @@ -137,7 +138,7 @@ def _save(im, fp, filename): bpp = im.info["bpp"] im = im.point(lambda x, maxval=(1 << bpp) - 1: maxval - (x & maxval)) else: - raise OSError("cannot write mode %s as Palm" % im.mode) + raise OSError(f"cannot write mode {im.mode} as Palm") # we ignore the palette here im.mode = "P" @@ -153,7 +154,7 @@ def _save(im, fp, filename): else: - raise OSError("cannot write mode %s as Palm" % im.mode) + raise OSError(f"cannot write mode {im.mode} as Palm") # # make sure image data is available diff --git a/src/PIL/PcdImagePlugin.py b/src/PIL/PcdImagePlugin.py index 625f55646..38caf5c63 100644 --- a/src/PIL/PcdImagePlugin.py +++ b/src/PIL/PcdImagePlugin.py @@ -16,7 +16,6 @@ from . import Image, ImageFile -from ._binary import i8 ## # Image plugin for PhotoCD images. This plugin only reads the 768x512 @@ -38,7 +37,7 @@ class PcdImageFile(ImageFile.ImageFile): if s[:4] != b"PCD_": raise SyntaxError("not a PCD file") - orientation = i8(s[1538]) & 3 + orientation = s[1538] & 3 self.tile_post_rotate = None if orientation == 1: self.tile_post_rotate = 90 diff --git a/src/PIL/PcfFontFile.py b/src/PIL/PcfFontFile.py index 605cbbdf3..6a4eb22a6 100644 --- a/src/PIL/PcfFontFile.py +++ b/src/PIL/PcfFontFile.py @@ -19,7 +19,11 @@ import io from . import FontFile, Image -from ._binary import i8, i16be as b16, i16le as l16, i32be as b32, i32le as l32 +from ._binary import i8 +from ._binary import i16be as b16 +from ._binary import i16le as l16 +from ._binary import i32be as b32 +from ._binary import i32le as l32 # -------------------------------------------------------------------- # declarations @@ -48,15 +52,14 @@ def sz(s, o): return s[o : s.index(b"\0", o)] -## -# Font file plugin for the X11 PCF format. - - class PcfFontFile(FontFile.FontFile): + """Font file plugin for the X11 PCF format.""" name = "name" - def __init__(self, fp): + def __init__(self, fp, charset_encoding="iso8859-1"): + + self.charset_encoding = charset_encoding magic = l32(fp.read(4)) if magic != PCF_MAGIC: @@ -229,12 +232,17 @@ class PcfFontFile(FontFile.FontFile): nencoding = (lastCol - firstCol + 1) * (lastRow - firstRow + 1) - for i in range(nencoding): - encodingOffset = i16(fp.read(2)) - if encodingOffset != 0xFFFF: - try: - encoding[i + firstCol] = encodingOffset - except IndexError: - break # only load ISO-8859-1 glyphs + encodingOffsets = [i16(fp.read(2)) for _ in range(nencoding)] + + for i in range(firstCol, len(encoding)): + try: + encodingOffset = encodingOffsets[ + ord(bytearray([i]).decode(self.charset_encoding)) + ] + if encodingOffset != 0xFFFF: + encoding[i] = encodingOffset + except UnicodeDecodeError: + # character is not supported in selected encoding + pass return encoding diff --git a/src/PIL/PcxImagePlugin.py b/src/PIL/PcxImagePlugin.py index 6cf10deb3..a24d44b42 100644 --- a/src/PIL/PcxImagePlugin.py +++ b/src/PIL/PcxImagePlugin.py @@ -29,13 +29,15 @@ import io import logging from . import Image, ImageFile, ImagePalette -from ._binary import i8, i16le as i16, o8, o16le as o16 +from ._binary import i16le as i16 +from ._binary import o8 +from ._binary import o16le as o16 logger = logging.getLogger(__name__) def _accept(prefix): - return i8(prefix[0]) == 10 and i8(prefix[1]) in [0, 2, 3, 5] + return prefix[0] == 10 and prefix[1] in [0, 2, 3, 5] ## @@ -61,16 +63,16 @@ class PcxImageFile(ImageFile.ImageFile): logger.debug("BBox: %s %s %s %s", *bbox) # format - version = i8(s[1]) - bits = i8(s[3]) - planes = i8(s[65]) - stride = i16(s, 66) + version = s[1] + bits = s[3] + planes = s[65] + ignored_stride = i16(s, 66) logger.debug( "PCX version %s, bits %s, planes %s, stride %s", version, bits, planes, - stride, + ignored_stride, ) self.info["dpi"] = i16(s, 12), i16(s, 14) @@ -88,7 +90,7 @@ class PcxImageFile(ImageFile.ImageFile): # FIXME: hey, this doesn't work with the incremental loader !!! self.fp.seek(-769, io.SEEK_END) s = self.fp.read(769) - if len(s) == 769 and i8(s[0]) == 12: + if len(s) == 769 and s[0] == 12: # check if the palette is linear greyscale for i in range(256): if s[i * 3 + 1 : i * 3 + 4] != o8(i) * 3: @@ -108,6 +110,11 @@ class PcxImageFile(ImageFile.ImageFile): self.mode = mode self._size = bbox[2] - bbox[0], bbox[3] - bbox[1] + # don't trust the passed in stride. Calculate for ourselves. + # CVE-2020-35655 + stride = (self._size[0] * bits + 7) // 8 + stride += stride % 2 + bbox = (0, 0) + self.size logger.debug("size: %sx%s", *self.size) @@ -131,8 +138,8 @@ def _save(im, fp, filename): try: version, bits, planes, rawmode = SAVE[im.mode] - except KeyError: - raise ValueError("Cannot save %s images as PCX" % im.mode) + except KeyError as e: + raise ValueError(f"Cannot save {im.mode} images as PCX") from e # bytes per plane stride = (im.size[0] * bits + 7) // 8 diff --git a/src/PIL/PdfImagePlugin.py b/src/PIL/PdfImagePlugin.py index 47500baf7..36c8fb849 100644 --- a/src/PIL/PdfImagePlugin.py +++ b/src/PIL/PdfImagePlugin.py @@ -77,7 +77,7 @@ def _save(im, fp, filename, save_all=False): existing_pdf.start_writing() existing_pdf.write_header() - existing_pdf.write_comment("created by Pillow {} PDF driver".format(__version__)) + existing_pdf.write_comment(f"created by Pillow {__version__} PDF driver") # # pages @@ -121,6 +121,7 @@ def _save(im, fp, filename, save_all=False): bits = 8 params = None + decode = None if im.mode == "1": filter = "ASCIIHexDecode" @@ -129,7 +130,7 @@ def _save(im, fp, filename, save_all=False): bits = 1 elif im.mode == "L": filter = "DCTDecode" - # params = "<< /Predictor 15 /Columns %d >>" % (width-2) + # params = f"<< /Predictor 15 /Columns {width-2} >>" colorspace = PdfParser.PdfName("DeviceGray") procset = "ImageB" # grayscale elif im.mode == "P": @@ -150,8 +151,9 @@ def _save(im, fp, filename, save_all=False): filter = "DCTDecode" colorspace = PdfParser.PdfName("DeviceCMYK") procset = "ImageC" # color images + decode = [1, 0, 1, 0, 1, 0, 1, 0] else: - raise ValueError("cannot save mode %s" % im.mode) + raise ValueError(f"cannot save mode {im.mode}") # # image @@ -173,7 +175,7 @@ def _save(im, fp, filename, save_all=False): elif filter == "RunLengthDecode": ImageFile._save(im, op, [("packbits", (0, 0) + im.size, 0, im.mode)]) else: - raise ValueError("unsupported PDF filter (%s)" % filter) + raise ValueError(f"unsupported PDF filter ({filter})") # # Get image characteristics @@ -189,6 +191,7 @@ def _save(im, fp, filename, save_all=False): Height=height, # * 72.0 / resolution, Filter=PdfParser.PdfName(filter), BitsPerComponent=bits, + Decode=decode, DecodeParams=params, ColorSpace=colorspace, ) diff --git a/src/PIL/PdfParser.py b/src/PIL/PdfParser.py index fdb35eded..975905f96 100644 --- a/src/PIL/PdfParser.py +++ b/src/PIL/PdfParser.py @@ -183,8 +183,8 @@ class XrefTable: this_deleted_object_id = deleted_keys.pop(0) check_format_condition( object_id == this_deleted_object_id, - "expected the next deleted object ID to be %s, instead found %s" - % (object_id, this_deleted_object_id), + f"expected the next deleted object ID to be {object_id}, " + f"instead found {this_deleted_object_id}", ) try: next_in_linked_list = deleted_keys[0] @@ -218,7 +218,7 @@ class PdfName: return hash(self.name) def __repr__(self): - return "PdfName(%s)" % repr(self.name) + return f"PdfName({repr(self.name)})" @classmethod def from_pdf_stream(cls, data): @@ -251,8 +251,8 @@ class PdfDict(collections.UserDict): def __getattr__(self, key): try: value = self[key.encode("us-ascii")] - except KeyError: - raise AttributeError(key) + except KeyError as e: + raise AttributeError(key) from e if isinstance(value, bytes): value = decode_text(value) if key.endswith("Date"): @@ -315,7 +315,7 @@ class PdfStream: return zlib.decompress(self.buf, bufsize=int(expected_length)) else: raise NotImplementedError( - "stream filter %s unknown/unsupported" % repr(self.dictionary.Filter) + f"stream filter {repr(self.dictionary.Filter)} unknown/unsupported" ) @@ -423,7 +423,7 @@ class PdfParser: self.f.write(b"%PDF-1.4\n") def write_comment(self, s): - self.f.write(("% {}\n".format(s)).encode("utf-8")) + self.f.write(f"% {s}\n".encode("utf-8")) def write_catalog(self): self.del_root() @@ -811,11 +811,11 @@ class PdfParser: if m: try: stream_len = int(result[b"Length"]) - except (TypeError, KeyError, ValueError): + except (TypeError, KeyError, ValueError) as e: raise PdfFormatError( "bad or missing Length in stream dict (%r)" % result.get(b"Length", None) - ) + ) from e stream_data = data[m.end() : m.end() + stream_len] m = cls.re_stream_end.match(data, m.end() + stream_len) check_format_condition(m, "stream end not found") @@ -966,9 +966,8 @@ class PdfParser: offset, generation = self.xref_table[ref[0]] check_format_condition( generation == ref[1], - "expected to find generation %s for object ID %s in xref table, " - "instead found generation %s at offset %s" - % (ref[1], ref[0], generation, offset), + f"expected to find generation {ref[1]} for object ID {ref[0]} in xref " + f"table, instead found generation {generation} at offset {offset}", ) value = self.get_value( self.buf, diff --git a/src/PIL/PixarImagePlugin.py b/src/PIL/PixarImagePlugin.py index 5ea32ba89..c4860b6c4 100644 --- a/src/PIL/PixarImagePlugin.py +++ b/src/PIL/PixarImagePlugin.py @@ -43,16 +43,16 @@ class PixarImageFile(ImageFile.ImageFile): # assuming a 4-byte magic label s = self.fp.read(4) - if s != b"\200\350\000\000": + if not _accept(s): raise SyntaxError("not a PIXAR file") # read rest of header s = s + self.fp.read(508) - self._size = i16(s[418:420]), i16(s[416:418]) + self._size = i16(s, 418), i16(s, 416) # get channel/depth descriptions - mode = i16(s[424:426]), i16(s[426:428]) + mode = i16(s, 424), i16(s, 426) if mode == (14, 2): self.mode = "RGB" diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py index ee1400d67..2d4ac7606 100644 --- a/src/PIL/PngImagePlugin.py +++ b/src/PIL/PngImagePlugin.py @@ -31,13 +31,19 @@ # See the README file for information on usage and redistribution. # +import itertools import logging import re import struct +import warnings import zlib -from . import Image, ImageFile, ImagePalette -from ._binary import i8, i16be as i16, i32be as i32, o16be as o16, o32be as o32 +from . import Image, ImageChops, ImageFile, ImagePalette, ImageSequence +from ._binary import i16be as i16 +from ._binary import i32be as i32 +from ._binary import o8 +from ._binary import o16be as o16 +from ._binary import o32be as o32 logger = logging.getLogger(__name__) @@ -74,11 +80,50 @@ _MODES = { _simple_palette = re.compile(b"^\xff*\x00\xff*$") -# Maximum decompressed size for a iTXt or zTXt chunk. -# Eliminates decompression bombs where compressed chunks can expand 1000x MAX_TEXT_CHUNK = ImageFile.SAFEBLOCK -# Set the maximum total text chunk size. +""" +Maximum decompressed size for a iTXt or zTXt chunk. +Eliminates decompression bombs where compressed chunks can expand 1000x. +See :ref:`Text in PNG File Format`. +""" MAX_TEXT_MEMORY = 64 * MAX_TEXT_CHUNK +""" +Set the maximum total text chunk size. +See :ref:`Text in PNG File Format`. +""" + + +# APNG frame disposal modes +APNG_DISPOSE_OP_NONE = 0 +""" +No disposal is done on this frame before rendering the next frame. +See :ref:`Saving APNG sequences`. +""" +APNG_DISPOSE_OP_BACKGROUND = 1 +""" +This frame’s modified region is cleared to fully transparent black before rendering +the next frame. +See :ref:`Saving APNG sequences`. +""" +APNG_DISPOSE_OP_PREVIOUS = 2 +""" +This frame’s modified region is reverted to the previous frame’s contents before +rendering the next frame. +See :ref:`Saving APNG sequences`. +""" + +# APNG frame blend modes +APNG_BLEND_OP_SOURCE = 0 +""" +All color components of this frame, including alpha, overwrite the previous output +image contents. +See :ref:`Saving APNG sequences`. +""" +APNG_BLEND_OP_OVER = 1 +""" +This frame should be alpha composited with the previous output image contents. +See :ref:`Saving APNG sequences`. +""" def _safe_zlib_decompress(s): @@ -118,7 +163,7 @@ class ChunkStream: if not is_cid(cid): if not ImageFile.LOAD_TRUNCATED_IMAGES: - raise SyntaxError("broken PNG file (chunk %s)" % repr(cid)) + raise SyntaxError(f"broken PNG file (chunk {repr(cid)})") return cid, pos, length @@ -147,7 +192,7 @@ class ChunkStream: # Skip CRC checks for ancillary chunks if allowed to load truncated # images # 5th byte of first char is 1 [specs, section 5.4] - if ImageFile.LOAD_TRUNCATED_IMAGES and (i8(cid[0]) >> 5 & 1): + if ImageFile.LOAD_TRUNCATED_IMAGES and (cid[0] >> 5 & 1): self.crc_skip(cid, data) return @@ -155,9 +200,13 @@ class ChunkStream: crc1 = _crc32(data, _crc32(cid)) crc2 = i32(self.fp.read(4)) if crc1 != crc2: - raise SyntaxError("broken PNG file (bad header checksum in %r)" % cid) - except struct.error: - raise SyntaxError("broken PNG file (incomplete checksum in %r)" % cid) + raise SyntaxError( + f"broken PNG file (bad header checksum in {repr(cid)})" + ) + except struct.error as e: + raise SyntaxError( + f"broken PNG file (incomplete checksum in {repr(cid)})" + ) from e def crc_skip(self, cid, data): """Read checksum. Used if the C module is not present""" @@ -174,8 +223,8 @@ class ChunkStream: while True: try: cid, pos, length = self.read() - except struct.error: - raise OSError("truncated PNG file") + except struct.error as e: + raise OSError("truncated PNG file") from e if cid == endchunk: break @@ -216,15 +265,20 @@ class PngInfo: def __init__(self): self.chunks = [] - def add(self, cid, data): + def add(self, cid, data, after_idat=False): """Appends an arbitrary chunk. Use with caution. :param cid: a byte string, 4 bytes long. :param data: a byte string of the encoded data + :param after_idat: for use with private chunks. Whether the chunk + should be written after IDAT """ - self.chunks.append((cid, data)) + chunk = [cid, data] + if after_idat: + chunk.append(True) + self.chunks.append(tuple(chunk)) def add_itxt(self, key, value, lang="", tkey="", zip=False): """Appends an iTXt chunk. @@ -298,6 +352,9 @@ class PngStream(ChunkStream): self.im_tile = None self.im_palette = None self.im_custom_mimetype = None + self.im_n_frames = None + self._seq_num = None + self.rewind_state = None self.text_memory = 0 @@ -305,10 +362,22 @@ class PngStream(ChunkStream): self.text_memory += chunklen if self.text_memory > MAX_TEXT_MEMORY: raise ValueError( - "Too much memory used in text chunks: %s>MAX_TEXT_MEMORY" - % self.text_memory + "Too much memory used in text chunks: " + f"{self.text_memory}>MAX_TEXT_MEMORY" ) + def save_rewind(self): + self.rewind_state = { + "info": self.im_info.copy(), + "tile": self.im_tile, + "seq_num": self._seq_num, + } + + def rewind(self): + self.im_info = self.rewind_state["info"] + self.im_tile = self.rewind_state["tile"] + self._seq_num = self.rewind_state["seq_num"] + def chunk_iCCP(self, pos, length): # ICC profile @@ -320,12 +389,10 @@ class PngStream(ChunkStream): # Compressed profile n bytes (zlib with deflate compression) i = s.find(b"\0") logger.debug("iCCP profile name %r", s[:i]) - logger.debug("Compression method %s", i8(s[i])) - comp_method = i8(s[i]) + logger.debug("Compression method %s", s[i]) + comp_method = s[i] if comp_method != 0: - raise SyntaxError( - "Unknown compression method %s in iCCP chunk" % comp_method - ) + raise SyntaxError(f"Unknown compression method {comp_method} in iCCP chunk") try: icc_profile = _safe_zlib_decompress(s[i + 2 :]) except ValueError: @@ -342,21 +409,27 @@ class PngStream(ChunkStream): # image header s = ImageFile._safe_read(self.fp, length) - self.im_size = i32(s), i32(s[4:]) + self.im_size = i32(s, 0), i32(s, 4) try: - self.im_mode, self.im_rawmode = _MODES[(i8(s[8]), i8(s[9]))] + self.im_mode, self.im_rawmode = _MODES[(s[8], s[9])] except Exception: pass - if i8(s[12]): + if s[12]: self.im_info["interlace"] = 1 - if i8(s[11]): + if s[11]: raise SyntaxError("unknown filter category") return s def chunk_IDAT(self, pos, length): # image data - self.im_tile = [("zip", (0, 0) + self.im_size, pos, self.im_rawmode)] + if "bbox" in self.im_info: + tile = [("zip", self.im_info["bbox"], pos, self.im_rawmode)] + else: + if self.im_n_frames is not None: + self.im_info["default_image"] = True + tile = [("zip", (0, 0) + self.im_size, pos, self.im_rawmode)] + self.im_tile = tile self.im_idat = length raise EOFError @@ -391,7 +464,7 @@ class PngStream(ChunkStream): elif self.im_mode in ("1", "L", "I"): self.im_info["transparency"] = i16(s) elif self.im_mode == "RGB": - self.im_info["transparency"] = i16(s), i16(s[2:]), i16(s[4:]) + self.im_info["transparency"] = i16(s), i16(s, 2), i16(s, 4) return s def chunk_gAMA(self, pos, length): @@ -417,15 +490,15 @@ class PngStream(ChunkStream): # 3 absolute colorimetric s = ImageFile._safe_read(self.fp, length) - self.im_info["srgb"] = i8(s) + self.im_info["srgb"] = s[0] return s def chunk_pHYs(self, pos, length): # pixels per unit s = ImageFile._safe_read(self.fp, length) - px, py = i32(s), i32(s[4:]) - unit = i8(s[8]) + px, py = i32(s, 0), i32(s, 4) + unit = s[8] if unit == 1: # meter dpi = int(px * 0.0254 + 0.5), int(py * 0.0254 + 0.5) self.im_info["dpi"] = dpi @@ -445,10 +518,11 @@ class PngStream(ChunkStream): v = b"" if k: k = k.decode("latin-1", "strict") - v = v.decode("latin-1", "replace") + v_str = v.decode("latin-1", "replace") - self.im_info[k] = self.im_text[k] = v - self.check_text_memory(len(v)) + self.im_info[k] = v if k == "exif" else v_str + self.im_text[k] = v_str + self.check_text_memory(len(v_str)) return s @@ -462,13 +536,11 @@ class PngStream(ChunkStream): k = s v = b"" if v: - comp_method = i8(v[0]) + comp_method = v[0] else: comp_method = 0 if comp_method != 0: - raise SyntaxError( - "Unknown compression method %s in zTXt chunk" % comp_method - ) + raise SyntaxError(f"Unknown compression method {comp_method} in zTXt chunk") try: v = _safe_zlib_decompress(v[1:]) except ValueError: @@ -498,7 +570,7 @@ class PngStream(ChunkStream): return s if len(r) < 2: return s - cf, cm, r = i8(r[0]), i8(r[1]), r[2:] + cf, cm, r = r[0], r[1], r[2:] try: lang, tk, v = r.split(b"\0", 2) except ValueError: @@ -537,9 +609,49 @@ class PngStream(ChunkStream): # APNG chunks def chunk_acTL(self, pos, length): s = ImageFile._safe_read(self.fp, length) + if self.im_n_frames is not None: + self.im_n_frames = None + warnings.warn("Invalid APNG, will use default PNG image if possible") + return s + n_frames = i32(s) + if n_frames == 0 or n_frames > 0x80000000: + warnings.warn("Invalid APNG, will use default PNG image if possible") + return s + self.im_n_frames = n_frames + self.im_info["loop"] = i32(s, 4) self.im_custom_mimetype = "image/apng" return s + def chunk_fcTL(self, pos, length): + s = ImageFile._safe_read(self.fp, length) + seq = i32(s) + if (self._seq_num is None and seq != 0) or ( + self._seq_num is not None and self._seq_num != seq - 1 + ): + raise SyntaxError("APNG contains frame sequence errors") + self._seq_num = seq + width, height = i32(s, 4), i32(s, 8) + px, py = i32(s, 12), i32(s, 16) + im_w, im_h = self.im_size + if px + width > im_w or py + height > im_h: + raise SyntaxError("APNG contains invalid frames") + self.im_info["bbox"] = (px, py, px + width, py + height) + delay_num, delay_den = i16(s, 20), i16(s, 22) + if delay_den == 0: + delay_den = 100 + self.im_info["duration"] = float(delay_num) / float(delay_den) * 1000 + self.im_info["disposal"] = s[24] + self.im_info["blend"] = s[25] + return s + + def chunk_fdAT(self, pos, length): + s = ImageFile._safe_read(self.fp, 4) + seq = i32(s) + if self._seq_num != seq - 1: + raise SyntaxError("APNG contains frame sequence errors") + self._seq_num = seq + return self.chunk_IDAT(pos + 4, length - 4) + # -------------------------------------------------------------------- # PNG reader @@ -560,12 +672,15 @@ class PngImageFile(ImageFile.ImageFile): def _open(self): - if self.fp.read(8) != _MAGIC: + if not _accept(self.fp.read(8)): raise SyntaxError("not a PNG file") + self.__fp = self.fp + self.__frame = 0 # - # Parse headers up to the first IDAT chunk + # Parse headers up to the first IDAT or fDAT chunk + self.private_chunks = [] self.png = PngStream(self.fp) while True: @@ -582,6 +697,8 @@ class PngImageFile(ImageFile.ImageFile): except AttributeError: logger.debug("%r %s %s (unknown)", cid, pos, length) s = ImageFile._safe_read(self.fp, length) + if cid[1:2].islower(): + self.private_chunks.append((cid, s)) self.png.crc(cid, s) @@ -598,12 +715,28 @@ class PngImageFile(ImageFile.ImageFile): self._text = None self.tile = self.png.im_tile self.custom_mimetype = self.png.im_custom_mimetype + self.n_frames = self.png.im_n_frames or 1 + self.default_image = self.info.get("default_image", False) if self.png.im_palette: rawmode, data = self.png.im_palette self.palette = ImagePalette.raw(rawmode, data) - self.__prepare_idat = length # used by load_prepare() + if cid == b"fdAT": + self.__prepare_idat = length - 4 + else: + self.__prepare_idat = length # used by load_prepare() + + if self.png.im_n_frames is not None: + self._close_exclusive_fp_after_loading = False + self.png.save_rewind() + self.__rewind_idat = self.__prepare_idat + self.__rewind = self.__fp.tell() + if self.default_image: + # IDAT chunk contains default image and not first animation frame + self.n_frames += 1 + self._seek(0) + self.is_animated = self.n_frames > 1 @property def text(self): @@ -611,7 +744,13 @@ class PngImageFile(ImageFile.ImageFile): if self._text is None: # iTxt, tEXt and zTXt chunks may appear at the end of the file # So load the file to ensure that they are read + if self.is_animated: + frame = self.__frame + # for APNG, seek to the final frame before loading + self.seek(self.n_frames - 1) self.load() + if self.is_animated: + self.seek(frame) return self._text def verify(self): @@ -630,6 +769,113 @@ class PngImageFile(ImageFile.ImageFile): self.fp.close() self.fp = None + def seek(self, frame): + if not self._seek_check(frame): + return + if frame < self.__frame: + self._seek(0, True) + + last_frame = self.__frame + for f in range(self.__frame + 1, frame + 1): + try: + self._seek(f) + except EOFError as e: + self.seek(last_frame) + raise EOFError("no more images in APNG file") from e + + def _seek(self, frame, rewind=False): + if frame == 0: + if rewind: + self.__fp.seek(self.__rewind) + self.png.rewind() + self.__prepare_idat = self.__rewind_idat + self.im = None + if self.pyaccess: + self.pyaccess = None + self.info = self.png.im_info + self.tile = self.png.im_tile + self.fp = self.__fp + self._prev_im = None + self.dispose = None + self.default_image = self.info.get("default_image", False) + self.dispose_op = self.info.get("disposal") + self.blend_op = self.info.get("blend") + self.dispose_extent = self.info.get("bbox") + self.__frame = 0 + else: + if frame != self.__frame + 1: + raise ValueError(f"cannot seek to frame {frame}") + + # ensure previous frame was loaded + self.load() + + if self.dispose: + self.im.paste(self.dispose, self.dispose_extent) + self._prev_im = self.im.copy() + + self.fp = self.__fp + + # advance to the next frame + if self.__prepare_idat: + ImageFile._safe_read(self.fp, self.__prepare_idat) + self.__prepare_idat = 0 + frame_start = False + while True: + self.fp.read(4) # CRC + + try: + cid, pos, length = self.png.read() + except (struct.error, SyntaxError): + break + + if cid == b"IEND": + raise EOFError("No more images in APNG file") + if cid == b"fcTL": + if frame_start: + # there must be at least one fdAT chunk between fcTL chunks + raise SyntaxError("APNG missing frame data") + frame_start = True + + try: + self.png.call(cid, pos, length) + except UnicodeDecodeError: + break + except EOFError: + if cid == b"fdAT": + length -= 4 + if frame_start: + self.__prepare_idat = length + break + ImageFile._safe_read(self.fp, length) + except AttributeError: + logger.debug("%r %s %s (unknown)", cid, pos, length) + ImageFile._safe_read(self.fp, length) + + self.__frame = frame + self.tile = self.png.im_tile + self.dispose_op = self.info.get("disposal") + self.blend_op = self.info.get("blend") + self.dispose_extent = self.info.get("bbox") + + if not self.tile: + raise EOFError + + # setup frame disposal (actual disposal done when needed in the next _seek()) + if self._prev_im is None and self.dispose_op == APNG_DISPOSE_OP_PREVIOUS: + self.dispose_op = APNG_DISPOSE_OP_BACKGROUND + + if self.dispose_op == APNG_DISPOSE_OP_PREVIOUS: + self.dispose = self._prev_im.copy() + self.dispose = self._crop(self.dispose, self.dispose_extent) + elif self.dispose_op == APNG_DISPOSE_OP_BACKGROUND: + self.dispose = Image.core.fill(self.mode, self.size) + self.dispose = self._crop(self.dispose, self.dispose_extent) + else: + self.dispose = None + + def tell(self): + return self.__frame + def load_prepare(self): """internal: prepare to read PNG file""" @@ -649,11 +895,18 @@ class PngImageFile(ImageFile.ImageFile): cid, pos, length = self.png.read() - if cid not in [b"IDAT", b"DDAT"]: + if cid not in [b"IDAT", b"DDAT", b"fdAT"]: self.png.push(cid, pos, length) return b"" - self.__idat = length # empty chunks are allowed + if cid == b"fdAT": + try: + self.png.call(cid, pos, length) + except EOFError: + pass + self.__idat = length - 4 # sequence_num has already been read + else: + self.__idat = length # empty chunks are allowed # read more data from this chunk if read_bytes <= 0: @@ -677,31 +930,60 @@ class PngImageFile(ImageFile.ImageFile): if cid == b"IEND": break + elif cid == b"fcTL" and self.is_animated: + # start of the next frame, stop reading + self.__prepare_idat = 0 + self.png.push(cid, pos, length) + break try: self.png.call(cid, pos, length) except UnicodeDecodeError: break except EOFError: + if cid == b"fdAT": + length -= 4 ImageFile._safe_read(self.fp, length) except AttributeError: logger.debug("%r %s %s (unknown)", cid, pos, length) - ImageFile._safe_read(self.fp, length) + s = ImageFile._safe_read(self.fp, length) + if cid[1:2].islower(): + self.private_chunks.append((cid, s, True)) self._text = self.png.im_text - self.png.close() - self.png = None + if not self.is_animated: + self.png.close() + self.png = None + else: + if self._prev_im and self.blend_op == APNG_BLEND_OP_OVER: + updated = self._crop(self.im, self.dispose_extent) + self._prev_im.paste( + updated, self.dispose_extent, updated.convert("RGBA") + ) + self.im = self._prev_im + if self.pyaccess: + self.pyaccess = None def _getexif(self): if "exif" not in self.info: self.load() - if "exif" not in self.info: + if "exif" not in self.info and "Raw profile type exif" not in self.info: return None return dict(self.getexif()) def getexif(self): if "exif" not in self.info: self.load() - return ImageFile.ImageFile.getexif(self) + + return super().getexif() + + def _close__fp(self): + try: + if self.__fp != self.fp: + self.__fp.close() + except AttributeError: + pass + finally: + self.__fp = None # -------------------------------------------------------------------- @@ -748,7 +1030,152 @@ class _idat: self.chunk(self.fp, b"IDAT", data) -def _save(im, fp, filename, chunk=putchunk): +class _fdat: + # wrap encoder output in fdAT chunks + + def __init__(self, fp, chunk, seq_num): + self.fp = fp + self.chunk = chunk + self.seq_num = seq_num + + def write(self, data): + self.chunk(self.fp, b"fdAT", o32(self.seq_num), data) + self.seq_num += 1 + + +def _write_multiple_frames(im, fp, chunk, rawmode): + default_image = im.encoderinfo.get("default_image", im.info.get("default_image")) + duration = im.encoderinfo.get("duration", im.info.get("duration", 0)) + loop = im.encoderinfo.get("loop", im.info.get("loop", 0)) + disposal = im.encoderinfo.get("disposal", im.info.get("disposal")) + blend = im.encoderinfo.get("blend", im.info.get("blend")) + + if default_image: + chain = itertools.chain(im.encoderinfo.get("append_images", [])) + else: + chain = itertools.chain([im], im.encoderinfo.get("append_images", [])) + + im_frames = [] + frame_count = 0 + for im_seq in chain: + for im_frame in ImageSequence.Iterator(im_seq): + im_frame = im_frame.copy() + if im_frame.mode != im.mode: + if im.mode == "P": + im_frame = im_frame.convert(im.mode, palette=im.palette) + else: + im_frame = im_frame.convert(im.mode) + encoderinfo = im.encoderinfo.copy() + if isinstance(duration, (list, tuple)): + encoderinfo["duration"] = duration[frame_count] + if isinstance(disposal, (list, tuple)): + encoderinfo["disposal"] = disposal[frame_count] + if isinstance(blend, (list, tuple)): + encoderinfo["blend"] = blend[frame_count] + frame_count += 1 + + if im_frames: + previous = im_frames[-1] + prev_disposal = previous["encoderinfo"].get("disposal") + prev_blend = previous["encoderinfo"].get("blend") + if prev_disposal == APNG_DISPOSE_OP_PREVIOUS and len(im_frames) < 2: + prev_disposal = APNG_DISPOSE_OP_BACKGROUND + + if prev_disposal == APNG_DISPOSE_OP_BACKGROUND: + base_im = previous["im"] + dispose = Image.core.fill("RGBA", im.size, (0, 0, 0, 0)) + bbox = previous["bbox"] + if bbox: + dispose = dispose.crop(bbox) + else: + bbox = (0, 0) + im.size + base_im.paste(dispose, bbox) + elif prev_disposal == APNG_DISPOSE_OP_PREVIOUS: + base_im = im_frames[-2]["im"] + else: + base_im = previous["im"] + delta = ImageChops.subtract_modulo( + im_frame.convert("RGB"), base_im.convert("RGB") + ) + bbox = delta.getbbox() + if ( + not bbox + and prev_disposal == encoderinfo.get("disposal") + and prev_blend == encoderinfo.get("blend") + ): + duration = encoderinfo.get("duration", 0) + if duration: + if "duration" in previous["encoderinfo"]: + previous["encoderinfo"]["duration"] += duration + else: + previous["encoderinfo"]["duration"] = duration + continue + else: + bbox = None + im_frames.append({"im": im_frame, "bbox": bbox, "encoderinfo": encoderinfo}) + + # animation control + chunk( + fp, + b"acTL", + o32(len(im_frames)), # 0: num_frames + o32(loop), # 4: num_plays + ) + + # default image IDAT (if it exists) + if default_image: + ImageFile._save(im, _idat(fp, chunk), [("zip", (0, 0) + im.size, 0, rawmode)]) + + seq_num = 0 + for frame, frame_data in enumerate(im_frames): + im_frame = frame_data["im"] + if not frame_data["bbox"]: + bbox = (0, 0) + im_frame.size + else: + bbox = frame_data["bbox"] + im_frame = im_frame.crop(bbox) + size = im_frame.size + duration = int(round(frame_data["encoderinfo"].get("duration", 0))) + disposal = frame_data["encoderinfo"].get("disposal", APNG_DISPOSE_OP_NONE) + blend = frame_data["encoderinfo"].get("blend", APNG_BLEND_OP_SOURCE) + # frame control + chunk( + fp, + b"fcTL", + o32(seq_num), # sequence_number + o32(size[0]), # width + o32(size[1]), # height + o32(bbox[0]), # x_offset + o32(bbox[1]), # y_offset + o16(duration), # delay_numerator + o16(1000), # delay_denominator + o8(disposal), # dispose_op + o8(blend), # blend_op + ) + seq_num += 1 + # frame data + if frame == 0 and not default_image: + # first frame must be in IDAT chunks for backwards compatibility + ImageFile._save( + im_frame, + _idat(fp, chunk), + [("zip", (0, 0) + im_frame.size, 0, rawmode)], + ) + else: + fdat_chunks = _fdat(fp, chunk, seq_num) + ImageFile._save( + im_frame, + fdat_chunks, + [("zip", (0, 0) + im_frame.size, 0, rawmode)], + ) + seq_num = fdat_chunks.seq_num + + +def _save_all(im, fp, filename): + _save(im, fp, filename, save_all=True) + + +def _save(im, fp, filename, chunk=putchunk, save_all=False): # save an image to disk (called by the save method) mode = im.mode @@ -776,7 +1203,7 @@ def _save(im, fp, filename, chunk=putchunk): else: bits = 8 if bits != 8: - mode = "%s;%d" % (mode, bits) + mode = f"{mode};{bits}" # encoder options im.encoderconfig = ( @@ -789,8 +1216,8 @@ def _save(im, fp, filename, chunk=putchunk): # get the corresponding PNG mode try: rawmode, mode = _OUTMODES[mode] - except KeyError: - raise OSError("cannot write mode %s as PNG" % mode) + except KeyError as e: + raise OSError(f"cannot write mode {mode} as PNG") from e # # write minimal PNG file @@ -829,12 +1256,18 @@ def _save(im, fp, filename, chunk=putchunk): info = im.encoderinfo.get("pnginfo") if info: chunks_multiple_allowed = [b"sPLT", b"iTXt", b"tEXt", b"zTXt"] - for cid, data in info.chunks: + for info_chunk in info.chunks: + cid, data = info_chunk[:2] if cid in chunks: chunks.remove(cid) chunk(fp, cid, data) elif cid in chunks_multiple_allowed: chunk(fp, cid, data) + elif cid[1:2].islower(): + # Private chunk + after_idat = info_chunk[2:3] + if not after_idat: + chunk(fp, cid, data) if im.mode == "P": palette_byte_number = (2 ** bits) * 3 @@ -884,7 +1317,8 @@ def _save(im, fp, filename, chunk=putchunk): if info: chunks = [b"bKGD", b"hIST"] - for cid, data in info.chunks: + for info_chunk in info.chunks: + cid, data = info_chunk[:2] if cid in chunks: chunks.remove(cid) chunk(fp, cid, data) @@ -897,7 +1331,19 @@ def _save(im, fp, filename, chunk=putchunk): exif = exif[6:] chunk(fp, b"eXIf", exif) - ImageFile._save(im, _idat(fp, chunk), [("zip", (0, 0) + im.size, 0, rawmode)]) + if save_all: + _write_multiple_frames(im, fp, chunk, rawmode) + else: + ImageFile._save(im, _idat(fp, chunk), [("zip", (0, 0) + im.size, 0, rawmode)]) + + if info: + for info_chunk in info.chunks: + cid, data = info_chunk[:2] + if cid[1:2].islower(): + # Private chunk + after_idat = info_chunk[2:3] + if after_idat: + chunk(fp, cid, data) chunk(fp, b"IEND", b"") @@ -942,6 +1388,7 @@ def getchunks(im, **params): Image.register_open(PngImageFile.format, PngImageFile, _accept) Image.register_save(PngImageFile.format, _save) +Image.register_save_all(PngImageFile.format, _save_all) Image.register_extensions(PngImageFile.format, [".png", ".apng"]) diff --git a/src/PIL/PpmImagePlugin.py b/src/PIL/PpmImagePlugin.py index 35a77bafb..abf4d651d 100644 --- a/src/PIL/PpmImagePlugin.py +++ b/src/PIL/PpmImagePlugin.py @@ -104,7 +104,7 @@ class PpmImageFile(ImageFile.ImageFile): # maxgrey if s > 255: if not mode == "L": - raise ValueError("Too many colors for band: %s" % s) + raise ValueError(f"Too many colors for band: {s}") if s < 2 ** 16: self.mode = "I" rawmode = "I;16B" @@ -135,7 +135,7 @@ def _save(im, fp, filename): elif im.mode == "RGBA": rawmode, head = "RGB", b"P6" else: - raise OSError("cannot write mode %s as PPM" % im.mode) + raise OSError(f"cannot write mode {im.mode} as PPM") fp.write(head + ("\n%d %d\n" % im.size).encode("ascii")) if head == b"P6": fp.write(b"255\n") diff --git a/src/PIL/PsdImagePlugin.py b/src/PIL/PsdImagePlugin.py index cceb85c5b..d3799edc3 100644 --- a/src/PIL/PsdImagePlugin.py +++ b/src/PIL/PsdImagePlugin.py @@ -19,7 +19,9 @@ import io from . import Image, ImageFile, ImagePalette -from ._binary import i8, i16be as i16, i32be as i32 +from ._binary import i8 +from ._binary import i16be as i16 +from ._binary import i32be as i32 MODES = { # (photoshop mode, bits) -> (pil mode, required channels) @@ -61,12 +63,12 @@ class PsdImageFile(ImageFile.ImageFile): # header s = read(26) - if s[:4] != b"8BPS" or i16(s[4:]) != 1: + if not _accept(s) or i16(s, 4) != 1: raise SyntaxError("not a PSD file") - psd_bits = i16(s[22:]) - psd_channels = i16(s[12:]) - psd_mode = i16(s[24:]) + psd_bits = i16(s, 22) + psd_channels = i16(s, 12) + psd_mode = i16(s, 24) mode, channels = MODES[(psd_mode, psd_bits)] @@ -74,7 +76,7 @@ class PsdImageFile(ImageFile.ImageFile): raise OSError("not enough channels") self.mode = mode - self._size = i32(s[18:]), i32(s[14:]) + self._size = i32(s, 18), i32(s, 14) # # color mode data @@ -119,6 +121,8 @@ class PsdImageFile(ImageFile.ImageFile): if size: self.layers = _layerinfo(self.fp) self.fp.seek(end) + self.n_frames = len(self.layers) + self.is_animated = self.n_frames > 1 # # image descriptor @@ -130,14 +134,6 @@ class PsdImageFile(ImageFile.ImageFile): self.frame = 1 self._min_frame = 1 - @property - def n_frames(self): - return len(self.layers) - - @property - def is_animated(self): - return len(self.layers) > 1 - def seek(self, layer): if not self._seek_check(layer): return @@ -150,8 +146,8 @@ class PsdImageFile(ImageFile.ImageFile): self.frame = layer self.fp = self.__fp return name, bbox - except IndexError: - raise EOFError("no such layer") + except IndexError as e: + raise EOFError("no such layer") from e def tell(self): # return layer number (0=image, 1..max=layers) @@ -295,7 +291,7 @@ def _maketile(file, mode, bbox, channels): layer += ";I" tile.append(("packbits", bbox, offset, layer)) for y in range(ysize): - offset = offset + i16(bytecount[i : i + 2]) + offset = offset + i16(bytecount, i) i += 2 file.seek(offset) @@ -313,3 +309,5 @@ def _maketile(file, mode, bbox, channels): Image.register_open(PsdImageFile.format, PsdImageFile, _accept) Image.register_extension(PsdImageFile.format, ".psd") + +Image.register_mime(PsdImageFile.format, "image/vnd.adobe.photoshop") diff --git a/src/PIL/PyAccess.py b/src/PIL/PyAccess.py index 359a94919..494f5f9f4 100644 --- a/src/PIL/PyAccess.py +++ b/src/PIL/PyAccess.py @@ -23,23 +23,29 @@ import logging import sys -from cffi import FFI +try: + from cffi import FFI + + defs = """ + struct Pixel_RGBA { + unsigned char r,g,b,a; + }; + struct Pixel_I16 { + unsigned char l,r; + }; + """ + ffi = FFI() + ffi.cdef(defs) +except ImportError as ex: + # Allow error import for doc purposes, but error out when accessing + # anything in core. + from ._util import deferred_error + + FFI = ffi = deferred_error(ex) logger = logging.getLogger(__name__) -defs = """ -struct Pixel_RGBA { - unsigned char r,g,b,a; -}; -struct Pixel_I16 { - unsigned char l,r; -}; -""" -ffi = FFI() -ffi.cdef(defs) - - class PyAccess: def __init__(self, img, readonly=False): vals = dict(img.im.unsafe_ptrs) diff --git a/src/PIL/SgiImagePlugin.py b/src/PIL/SgiImagePlugin.py index ddd3de379..d0f7c9993 100644 --- a/src/PIL/SgiImagePlugin.py +++ b/src/PIL/SgiImagePlugin.py @@ -26,7 +26,8 @@ import os import struct from . import Image, ImageFile -from ._binary import i8, i16be as i16, o8 +from ._binary import i16be as i16 +from ._binary import o8 def _accept(prefix): @@ -58,27 +59,26 @@ class SgiImageFile(ImageFile.ImageFile): headlen = 512 s = self.fp.read(headlen) - # magic number : 474 - if i16(s) != 474: + if not _accept(s): raise ValueError("Not an SGI image file") # compression : verbatim or RLE - compression = i8(s[2]) + compression = s[2] # bpc : 1 or 2 bytes (8bits or 16bits) - bpc = i8(s[3]) + bpc = s[3] # dimension : 1, 2 or 3 (depending on xsize, ysize and zsize) - dimension = i16(s[4:]) + dimension = i16(s, 4) # xsize : width - xsize = i16(s[6:]) + xsize = i16(s, 6) # ysize : height - ysize = i16(s[8:]) + ysize = i16(s, 8) # zsize : channels count - zsize = i16(s[10:]) + zsize = i16(s, 10) # layout layout = bpc, dimension, zsize @@ -159,9 +159,7 @@ def _save(im, fp, filename): # assert we've got the right number of bands. if len(im.getbands()) != z: raise ValueError( - "incorrect number of bands in SGI write: {} vs {}".format( - z, len(im.getbands()) - ) + f"incorrect number of bands in SGI write: {z} vs {len(im.getbands())}" ) # Minimum Byte value diff --git a/src/PIL/SpiderImagePlugin.py b/src/PIL/SpiderImagePlugin.py index dd0620c14..819f2ed0a 100644 --- a/src/PIL/SpiderImagePlugin.py +++ b/src/PIL/SpiderImagePlugin.py @@ -111,8 +111,8 @@ class SpiderImageFile(ImageFile.ImageFile): hdrlen = isSpiderHeader(t) if hdrlen == 0: raise SyntaxError("not a valid Spider file") - except struct.error: - raise SyntaxError("not a valid Spider file") + except struct.error as e: + raise SyntaxError("not a valid Spider file") from e h = (99,) + t # add 1 value : spider header index starts at 1 iform = int(h[5]) @@ -213,7 +213,7 @@ def loadImageSeries(filelist=None): imglist = [] for img in filelist: if not os.path.exists(img): - print("unable to find %s" % img) + print(f"unable to find {img}") continue try: with Image.open(img) as im: @@ -304,21 +304,21 @@ if __name__ == "__main__": print("input image must be in Spider format") sys.exit() - im = Image.open(filename) - print("image: " + str(im)) - print("format: " + str(im.format)) - print("size: " + str(im.size)) - print("mode: " + str(im.mode)) - print("max, min: ", end=" ") - print(im.getextrema()) + with Image.open(filename) as im: + print("image: " + str(im)) + print("format: " + str(im.format)) + print("size: " + str(im.size)) + print("mode: " + str(im.mode)) + print("max, min: ", end=" ") + print(im.getextrema()) - if len(sys.argv) > 2: - outfile = sys.argv[2] + if len(sys.argv) > 2: + outfile = sys.argv[2] - # perform some image operation - im = im.transpose(Image.FLIP_LEFT_RIGHT) - print( - "saving a flipped version of %s as %s " - % (os.path.basename(filename), outfile) - ) - im.save(outfile, SpiderImageFile.format) + # perform some image operation + im = im.transpose(Image.FLIP_LEFT_RIGHT) + print( + f"saving a flipped version of {os.path.basename(filename)} " + f"as {outfile} " + ) + im.save(outfile, SpiderImageFile.format) diff --git a/src/PIL/SunImagePlugin.py b/src/PIL/SunImagePlugin.py index fd7ca8a40..c03759a01 100644 --- a/src/PIL/SunImagePlugin.py +++ b/src/PIL/SunImagePlugin.py @@ -53,18 +53,18 @@ class SunImageFile(ImageFile.ImageFile): # HEAD s = self.fp.read(32) - if i32(s) != 0x59A66A95: + if not _accept(s): raise SyntaxError("not an SUN raster file") offset = 32 - self._size = i32(s[4:8]), i32(s[8:12]) + self._size = i32(s, 4), i32(s, 8) - depth = i32(s[12:16]) - # data_length = i32(s[16:20]) # unreliable, ignore. - file_type = i32(s[20:24]) - palette_type = i32(s[24:28]) # 0: None, 1: RGB, 2: Raw/arbitrary - palette_length = i32(s[28:32]) + depth = i32(s, 12) + # data_length = i32(s, 16) # unreliable, ignore. + file_type = i32(s, 20) + palette_type = i32(s, 24) # 0: None, 1: RGB, 2: Raw/arbitrary + palette_length = i32(s, 28) if depth == 1: self.mode, rawmode = "1", "1;I" diff --git a/src/PIL/TarIO.py b/src/PIL/TarIO.py index ede646453..d108362fc 100644 --- a/src/PIL/TarIO.py +++ b/src/PIL/TarIO.py @@ -18,12 +18,10 @@ import io from . import ContainerIO -## -# A file object that provides read access to a given member of a TAR -# file. - class TarIO(ContainerIO.ContainerIO): + """A file object that provides read access to a given member of a TAR file.""" + def __init__(self, tarfile, file): """ Create file object. diff --git a/src/PIL/TgaImagePlugin.py b/src/PIL/TgaImagePlugin.py index fd71e545d..2b936d687 100644 --- a/src/PIL/TgaImagePlugin.py +++ b/src/PIL/TgaImagePlugin.py @@ -20,7 +20,9 @@ import warnings from . import Image, ImageFile, ImagePalette -from ._binary import i8, i16le as i16, o8, o16le as o16 +from ._binary import i16le as i16 +from ._binary import o8 +from ._binary import o16le as o16 # # -------------------------------------------------------------------- @@ -53,16 +55,16 @@ class TgaImageFile(ImageFile.ImageFile): # process header s = self.fp.read(18) - id_len = i8(s[0]) + id_len = s[0] - colormaptype = i8(s[1]) - imagetype = i8(s[2]) + colormaptype = s[1] + imagetype = s[2] - depth = i8(s[16]) + depth = s[16] - flags = i8(s[17]) + flags = s[17] - self._size = i16(s[12:]), i16(s[14:]) + self._size = i16(s, 12), i16(s, 14) # validate header fields if ( @@ -108,7 +110,7 @@ class TgaImageFile(ImageFile.ImageFile): if colormaptype: # read palette - start, size, mapdepth = i16(s[3:]), i16(s[5:]), i16(s[7:]) + start, size, mapdepth = i16(s, 3), i16(s, 5), i16(s, 7) if mapdepth == 16: self.palette = ImagePalette.raw( "BGR;16", b"\0" * 2 * start + self.fp.read(2 * size) @@ -167,8 +169,8 @@ def _save(im, fp, filename): try: rawmode, bits, colormaptype, imagetype = SAVE[im.mode] - except KeyError: - raise OSError("cannot write mode %s as TGA" % im.mode) + except KeyError as e: + raise OSError(f"cannot write mode {im.mode} as TGA") from e if "rle" in im.encoderinfo: rle = im.encoderinfo["rle"] diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index 74fb69516..0b70ce382 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -40,6 +40,7 @@ # import io import itertools +import logging import os import struct import warnings @@ -48,10 +49,10 @@ from fractions import Fraction from numbers import Number, Rational from . import Image, ImageFile, ImagePalette, TiffTags -from ._binary import i8, o8 +from ._binary import o8 from .TiffTags import TYPES -DEBUG = False # Needs to be merged with the new logging approach. +logger = logging.getLogger(__name__) # Set these to true to force use of libtiff for reading or writing. READ_LIBTIFF = False @@ -88,6 +89,7 @@ ARTIST = 315 PREDICTOR = 317 COLORMAP = 320 TILEOFFSETS = 324 +SUBIFD = 330 EXTRASAMPLES = 338 SAMPLEFORMAT = 339 JPEGTABLES = 347 @@ -285,7 +287,7 @@ _write_dispatch = {} class IFDRational(Rational): - """ Implements a rational class where 0/0 is a legal value to match + """Implements a rational class where 0/0 is a legal value to match the in the wild use of exif rationals. e.g., DigitalZoomRatio - 0.00/0.00 indicates that no digital zoom was used @@ -352,6 +354,8 @@ class IFDRational(Rational): return self._val.__hash__() def __eq__(self, other): + if isinstance(other, IFDRational): + other = other._val return self._val == other def _delegate(op): @@ -413,7 +417,7 @@ class ImageFileDirectory_v2(MutableMapping): The tiff metadata type of each item is stored in a dictionary of tag types in - `~PIL.TiffImagePlugin.ImageFileDirectory_v2.tagtype`. The types + :attr:`~PIL.TiffImagePlugin.ImageFileDirectory_v2.tagtype`. The types are read from a tiff file, guessed from the type added, or added manually. @@ -463,7 +467,7 @@ class ImageFileDirectory_v2(MutableMapping): :param prefix: Override the endianness of the file. """ if ifh[:4] not in PREFIXES: - raise SyntaxError("not a TIFF file (header %r not valid)" % ifh) + raise SyntaxError(f"not a TIFF file (header {repr(ifh)} not valid)") self._prefix = prefix if prefix is not None else ifh[:2] if self._prefix == MM: self._endian = ">" @@ -471,6 +475,8 @@ class ImageFileDirectory_v2(MutableMapping): self._endian = "<" else: raise SyntaxError("not a TIFF IFD") + self.tagtype = {} + """ Dictionary of tag types """ self.reset() (self.next,) = self._unpack("L", ifh[4:]) self._legacy_api = False @@ -552,9 +558,10 @@ class ImageFileDirectory_v2(MutableMapping): ) elif all(isinstance(v, float) for v in values): self.tagtype[tag] = TiffTags.DOUBLE - else: - if all(isinstance(v, str) for v in values): - self.tagtype[tag] = TiffTags.ASCII + elif all(isinstance(v, str) for v in values): + self.tagtype[tag] = TiffTags.ASCII + elif all(isinstance(v, bytes) for v in values): + self.tagtype[tag] = TiffTags.BYTE if self.tagtype[tag] == TiffTags.UNDEFINED: values = [ @@ -563,7 +570,9 @@ class ImageFileDirectory_v2(MutableMapping): elif self.tagtype[tag] == TiffTags.RATIONAL: values = [float(v) if isinstance(v, int) else v for v in values] - values = tuple(info.cvt_enum(value) for value in values) + is_ifd = self.tagtype[tag] == TiffTags.LONG and isinstance(values, dict) + if not is_ifd: + values = tuple(info.cvt_enum(value) for value in values) dest = self._tags_v1 if legacy_api else self._tags_v2 @@ -572,8 +581,10 @@ class ImageFileDirectory_v2(MutableMapping): # Spec'd length == 1, Actual > 1, Warn and truncate. Formerly barfed. # No Spec, Actual length 1, Formerly (<4.2) returned a 1 element tuple. # Don't mess with the legacy api, since it's frozen. - if (info.length == 1) or ( - info.length is None and len(values) == 1 and not legacy_api + if not is_ifd and ( + (info.length == 1) + or self.tagtype[tag] == TiffTags.BYTE + or (info.length is None and len(values) == 1 and not legacy_api) ): # Don't mess with the legacy api, since it's frozen. if legacy_api and self.tagtype[tag] in [ @@ -586,8 +597,8 @@ class ImageFileDirectory_v2(MutableMapping): except ValueError: # We've got a builtin tag with 1 expected entry warnings.warn( - "Metadata Warning, tag %s had too many entries: %s, expected 1" - % (tag, len(values)) + f"Metadata Warning, tag {tag} had too many entries: " + f"{len(values)}, expected 1" ) dest[tag] = values[0] @@ -655,6 +666,7 @@ class ImageFileDirectory_v2(MutableMapping): (TiffTags.SIGNED_LONG, "l", "signed long"), (TiffTags.FLOAT, "f", "float"), (TiffTags.DOUBLE, "d", "double"), + (TiffTags.IFD, "L", "long"), ], ) ) @@ -722,7 +734,7 @@ class ImageFileDirectory_v2(MutableMapping): if len(ret) != size: raise OSError( "Corrupt EXIF data. " - + "Expecting to read %d bytes but only got %d. " % (size, len(ret)) + f"Expecting to read {size} bytes but only got {len(ret)}. " ) return ret @@ -734,29 +746,21 @@ class ImageFileDirectory_v2(MutableMapping): try: for i in range(self._unpack("H", self._ensure_read(fp, 2))[0]): tag, typ, count, data = self._unpack("HHL4s", self._ensure_read(fp, 12)) - if DEBUG: - tagname = TiffTags.lookup(tag).name - typname = TYPES.get(typ, "unknown") - print( - "tag: %s (%d) - type: %s (%d)" % (tagname, tag, typname, typ), - end=" ", - ) + + tagname = TiffTags.lookup(tag).name + typname = TYPES.get(typ, "unknown") + msg = f"tag: {tagname} ({tag}) - type: {typname} ({typ})" try: unit_size, handler = self._load_dispatch[typ] except KeyError: - if DEBUG: - print("- unsupported type", typ) + logger.debug(msg + f" - unsupported type {typ}") continue # ignore unsupported type size = count * unit_size if size > 4: here = fp.tell() (offset,) = self._unpack("L", data) - if DEBUG: - print( - "Tag Location: {} - Data Location: {}".format(here, offset), - end=" ", - ) + msg += f" Tag Location: {here} - Data Location: {offset}" fp.seek(offset) data = ImageFile._safe_read(fp, size) fp.seek(here) @@ -766,22 +770,23 @@ class ImageFileDirectory_v2(MutableMapping): if len(data) != size: warnings.warn( "Possibly corrupt EXIF data. " - "Expecting to read %d bytes but only got %d." - " Skipping tag %s" % (size, len(data), tag) + f"Expecting to read {size} bytes but only got {len(data)}." + f" Skipping tag {tag}" ) + logger.debug(msg) continue if not data: + logger.debug(msg) continue self._tagdata[tag] = data self.tagtype[tag] = typ - if DEBUG: - if size > 32: - print("- value: " % size) - else: - print("- value:", self[tag]) + msg += " - value: " + ( + "" % size if size > 32 else repr(data) + ) + logger.debug(msg) (self.next,) = self._unpack("L", self._ensure_read(fp, 4)) except OSError as msg: @@ -802,24 +807,33 @@ class ImageFileDirectory_v2(MutableMapping): if tag == STRIPOFFSETS: stripoffsets = len(entries) typ = self.tagtype.get(tag) - if DEBUG: - print("Tag {}, Type: {}, Value: {}".format(tag, typ, value)) - values = value if isinstance(value, tuple) else (value,) - data = self._write_dispatch[typ](self, *values) - if DEBUG: - tagname = TiffTags.lookup(tag).name - typname = TYPES.get(typ, "unknown") - print( - "save: %s (%d) - type: %s (%d)" % (tagname, tag, typname, typ), - end=" ", - ) - if len(data) >= 16: - print("- value: " % len(data)) + logger.debug(f"Tag {tag}, Type: {typ}, Value: {repr(value)}") + is_ifd = typ == TiffTags.LONG and isinstance(value, dict) + if is_ifd: + if self._endian == "<": + ifh = b"II\x2A\x00\x08\x00\x00\x00" else: - print("- value:", values) + ifh = b"MM\x00\x2A\x00\x00\x00\x08" + ifd = ImageFileDirectory_v2(ifh) + for ifd_tag, ifd_value in self._tags_v2[tag].items(): + ifd[ifd_tag] = ifd_value + data = ifd.tobytes(offset) + else: + values = value if isinstance(value, tuple) else (value,) + data = self._write_dispatch[typ](self, *values) + + tagname = TiffTags.lookup(tag).name + typname = "ifd" if is_ifd else TYPES.get(typ, "unknown") + msg = f"save: {tagname} ({tag}) - type: {typname} ({typ})" + msg += " - value: " + ( + "" % len(data) if len(data) >= 16 else str(values) + ) + logger.debug(msg) # count is sum of lengths for string and arbitrary data - if typ in [TiffTags.BYTE, TiffTags.ASCII, TiffTags.UNDEFINED]: + if is_ifd: + count = 1 + elif typ in [TiffTags.BYTE, TiffTags.ASCII, TiffTags.UNDEFINED]: count = len(data) else: count = len(values) @@ -840,8 +854,7 @@ class ImageFileDirectory_v2(MutableMapping): # pass 2: write entries to file for tag, typ, count, value, data in entries: - if DEBUG: - print(tag, typ, count, repr(value), repr(data)) + logger.debug(f"{tag} {typ} {count} {repr(value)} {repr(data)}") result += self._pack("HHL4s", tag, typ, count, value) # -- overwrite here for multi-page -- @@ -889,7 +902,7 @@ class ImageFileDirectory_v1(ImageFileDirectory_v2): ('Some Data',) Also contains a dictionary of tag types as read from the tiff image file, - `~PIL.TiffImagePlugin.ImageFileDirectory_v1.tagtype`. + :attr:`~PIL.TiffImagePlugin.ImageFileDirectory_v1.tagtype`. Values are returned as a tuple. @@ -903,9 +916,13 @@ class ImageFileDirectory_v1(ImageFileDirectory_v2): tags = property(lambda self: self._tags_v1) tagdata = property(lambda self: self._tagdata) + # defined in ImageFileDirectory_v2 + tagtype: dict + """Dictionary of tag types""" + @classmethod def from_v2(cls, original): - """ Returns an + """Returns an :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v1` instance with the same data as is contained in the original :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v2` @@ -922,7 +939,7 @@ class ImageFileDirectory_v1(ImageFileDirectory_v2): return ifd def to_v2(self): - """ Returns an + """Returns an :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v2` instance with the same data as is contained in the original :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v1` @@ -978,17 +995,25 @@ class TiffImageFile(ImageFile.ImageFile): format_description = "Adobe TIFF" _close_exclusive_fp_after_loading = False + def __init__(self, fp=None, filename=None): + self.tag_v2 = None + """ Image file directory (tag dictionary) """ + + self.tag = None + """ Legacy tag entries """ + + super().__init__(fp, filename) + def _open(self): """Open the first image in a TIFF file""" # Header ifh = self.fp.read(8) - # image file directory (tag dictionary) self.tag_v2 = ImageFileDirectory_v2(ifh) - # legacy tag/ifd entries will be filled in later - self.tag = self.ifd = None + # legacy IFD entries will be filled in later + self.ifd = None # setup frame pointers self.__first = self.__next = self.tag_v2.next @@ -997,10 +1022,9 @@ class TiffImageFile(ImageFile.ImageFile): self._frame_pos = [] self._n_frames = None - if DEBUG: - print("*** TiffImageFile._open ***") - print("- __first:", self.__first) - print("- ifh: ", ifh) + logger.debug("*** TiffImageFile._open ***") + logger.debug(f"- __first: {self.__first}") + logger.debug(f"- ifh: {repr(ifh)}") # Use repr to avoid str(bytes) # and load the first frame self._seek(0) @@ -1015,10 +1039,6 @@ class TiffImageFile(ImageFile.ImageFile): self.seek(current) return self._n_frames - @property - def is_animated(self): - return self._is_animated - def seek(self, frame): """Select a given frame as current image""" if not self._seek_check(frame): @@ -1035,24 +1055,22 @@ class TiffImageFile(ImageFile.ImageFile): while len(self._frame_pos) <= frame: if not self.__next: raise EOFError("no more images in TIFF file") - if DEBUG: - print( - "Seeking to frame %s, on frame %s, __next %s, location: %s" - % (frame, self.__frame, self.__next, self.fp.tell()) - ) + logger.debug( + f"Seeking to frame {frame}, on frame {self.__frame}, " + f"__next {self.__next}, location: {self.fp.tell()}" + ) # reset buffered io handle in case fp # was passed to libtiff, invalidating the buffer self.fp.tell() self.fp.seek(self.__next) self._frame_pos.append(self.__next) - if DEBUG: - print("Loading tags, location: %s" % self.fp.tell()) + logger.debug("Loading tags, location: %s" % self.fp.tell()) self.tag_v2.load(self.fp) self.__next = self.tag_v2.next if self.__next == 0: self._n_frames = frame + 1 if len(self._frame_pos) == 1: - self._is_animated = self.__next != 0 + self.is_animated = self.__next != 0 self.__frame += 1 self.fp.seek(self._frame_pos[frame]) self.tag_v2.load(self.fp) @@ -1066,7 +1084,7 @@ class TiffImageFile(ImageFile.ImageFile): return self.__frame def load(self): - if self.use_load_libtiff: + if self.tile and self.use_load_libtiff: return self._load_libtiff() return super().load() @@ -1087,19 +1105,14 @@ class TiffImageFile(ImageFile.ImageFile): # allow closing if we're on the first frame, there's no next # This is the ImageFile.load path only, libtiff specific below. - if not self._is_animated: + if not self.is_animated: self._close_exclusive_fp_after_loading = True def _load_libtiff(self): - """ Overload method triggered when we detect a compressed tiff - Calls out to libtiff """ + """Overload method triggered when we detect a compressed tiff + Calls out to libtiff""" - pixel = Image.Image.load(self) - - if self.tile is None: - raise OSError("cannot load this image") - if not self.tile: - return pixel + Image.Image.load(self) self.load_prepare() @@ -1123,7 +1136,7 @@ class TiffImageFile(ImageFile.ImageFile): if hasattr(self.fp, "flush"): self.fp.flush() except OSError: - # io.BytesIO have a fileno, but returns an IOError if + # io.BytesIO have a fileno, but returns an OSError if # it doesn't use a file descriptor. fp = False @@ -1135,10 +1148,10 @@ class TiffImageFile(ImageFile.ImageFile): ) try: decoder.setimage(self.im, extents) - except ValueError: - raise OSError("Couldn't set the image") + except ValueError as e: + raise OSError("Couldn't set the image") from e - close_self_fp = self._exclusive_fp and not self._is_animated + close_self_fp = self._exclusive_fp and not self.is_animated if hasattr(self.fp, "getvalue"): # We've got a stringio like thing passed in. Yay for all in memory. # The decoder needs the entire file in one shot, so there's not @@ -1147,23 +1160,20 @@ class TiffImageFile(ImageFile.ImageFile): # underlying string for stringio. # # Rearranging for supporting byteio items, since they have a fileno - # that returns an IOError if there's no underlying fp. Easier to + # that returns an OSError if there's no underlying fp. Easier to # deal with here by reordering. - if DEBUG: - print("have getvalue. just sending in a string from getvalue") + logger.debug("have getvalue. just sending in a string from getvalue") n, err = decoder.decode(self.fp.getvalue()) elif fp: # we've got a actual file on disk, pass in the fp. - if DEBUG: - print("have fileno, calling fileno version of the decoder.") + logger.debug("have fileno, calling fileno version of the decoder.") if not close_self_fp: self.fp.seek(0) # 4 bytes, otherwise the trace might error out n, err = decoder.decode(b"fpfp") else: # we have something else. - if DEBUG: - print("don't have fileno or getvalue. just reading") + logger.debug("don't have fileno or getvalue. just reading") self.fp.seek(0) # UNDONE -- so much for that buffer size thing. n, err = decoder.decode(self.fp.read()) @@ -1203,21 +1213,19 @@ class TiffImageFile(ImageFile.ImageFile): fillorder = self.tag_v2.get(FILLORDER, 1) - if DEBUG: - print("*** Summary ***") - print("- compression:", self._compression) - print("- photometric_interpretation:", photo) - print("- planar_configuration:", self._planar_configuration) - print("- fill_order:", fillorder) - print("- YCbCr subsampling:", self.tag.get(530)) + logger.debug("*** Summary ***") + logger.debug(f"- compression: {self._compression}") + logger.debug(f"- photometric_interpretation: {photo}") + logger.debug(f"- planar_configuration: {self._planar_configuration}") + logger.debug(f"- fill_order: {fillorder}") + logger.debug(f"- YCbCr subsampling: {self.tag.get(530)}") # size xsize = int(self.tag_v2.get(IMAGEWIDTH)) ysize = int(self.tag_v2.get(IMAGELENGTH)) self._size = xsize, ysize - if DEBUG: - print("- size:", self.size) + logger.debug(f"- size: {self.size}") sampleFormat = self.tag_v2.get(SAMPLEFORMAT, (1,)) if len(sampleFormat) > 1 and max(sampleFormat) == min(sampleFormat) == 1: @@ -1251,18 +1259,15 @@ class TiffImageFile(ImageFile.ImageFile): bps_tuple, extra_tuple, ) - if DEBUG: - print("format key:", key) + logger.debug(f"format key: {key}") try: self.mode, rawmode = OPEN_INFO[key] - except KeyError: - if DEBUG: - print("- unsupported format") - raise SyntaxError("unknown pixel mode") + except KeyError as e: + logger.debug("- unsupported format") + raise SyntaxError("unknown pixel mode") from e - if DEBUG: - print("- raw mode:", rawmode) - print("- pil mode:", self.mode) + logger.debug(f"- raw mode: {rawmode}") + logger.debug(f"- pil mode: {self.mode}") self.info["compression"] = self._compression @@ -1303,8 +1308,7 @@ class TiffImageFile(ImageFile.ImageFile): if fillorder == 2: # Replace fillorder with fillorder=1 key = key[:3] + (1,) + key[4:] - if DEBUG: - print("format key:", key) + logger.debug(f"format key: {key}") # this should always work, since all the # fillorder==2 modes have a corresponding # fillorder=1 mode @@ -1366,8 +1370,7 @@ class TiffImageFile(ImageFile.ImageFile): x = y = 0 layer += 1 else: - if DEBUG: - print("- unsupported data organization") + logger.debug("- unsupported data organization") raise SyntaxError("unknown data organization") # Fix up info. @@ -1428,14 +1431,17 @@ def _save(im, fp, filename): try: rawmode, prefix, photo, format, bits, extra = SAVE_INFO[im.mode] - except KeyError: - raise OSError("cannot write mode %s as TIFF" % im.mode) + except KeyError as e: + raise OSError(f"cannot write mode {im.mode} as TIFF") from e ifd = ImageFileDirectory_v2(prefix=prefix) compression = im.encoderinfo.get("compression", im.info.get("compression")) if compression is None: compression = "raw" + elif compression == "tiff_jpeg": + # OJPEG is obsolete, so use new-style JPEG compression instead + compression = "jpeg" libtiff = WRITE_LIBTIFF or compression != "raw" @@ -1447,8 +1453,7 @@ def _save(im, fp, filename): # write any arbitrary tags passed in as an ImageFileDirectory info = im.encoderinfo.get("tiffinfo", {}) - if DEBUG: - print("Tiffinfo Keys: %s" % list(info)) + logger.debug("Tiffinfo Keys: %s" % list(info)) if isinstance(info, ImageFileDirectory_v1): info = info.to_v2() for key in info: @@ -1513,11 +1518,14 @@ def _save(im, fp, filename): if im.mode in ["P", "PA"]: lut = im.im.getpalette("RGB", "RGB;L") - ifd[COLORMAP] = tuple(i8(v) * 256 for v in lut) + ifd[COLORMAP] = tuple(v * 256 for v in lut) # data orientation stride = len(bits) * ((im.size[0] * bits[0] + 7) // 8) ifd[ROWSPERSTRIP] = im.size[1] - ifd[STRIPBYTECOUNTS] = stride * im.size[1] + strip_byte_counts = stride * im.size[1] + if strip_byte_counts >= 2 ** 16: + ifd.tagtype[STRIPBYTECOUNTS] = TiffTags.LONG + ifd[STRIPBYTECOUNTS] = strip_byte_counts ifd[STRIPOFFSETS] = 0 # this is adjusted by IFD writer # no compression by default: ifd[COMPRESSION] = COMPRESSION_INFO_REV.get(compression, 1) @@ -1533,9 +1541,8 @@ def _save(im, fp, filename): ) ifd[JPEGQUALITY] = quality - if DEBUG: - print("Saving using libtiff encoder") - print("Items: %s" % sorted(ifd.items())) + logger.debug("Saving using libtiff encoder") + logger.debug("Items: %s" % sorted(ifd.items())) _fp = 0 if hasattr(fp, "fileno"): try: @@ -1553,13 +1560,14 @@ def _save(im, fp, filename): # The other tags expect arrays with a certain length (fixed or depending on # BITSPERSAMPLE, etc), passing arrays with a different length will result in # segfaults. Block these tags until we add extra validation. + # SUBIFD may also cause a segfault. blocklist = [ - COLORMAP, REFERENCEBLACKWHITE, SAMPLEFORMAT, STRIPBYTECOUNTS, STRIPOFFSETS, TRANSFERFUNCTION, + SUBIFD, ] atts = {} @@ -1579,16 +1587,17 @@ def _save(im, fp, filename): # Custom items are supported for int, float, unicode, string and byte # values. Other types and tuples require a tagtype. if tag not in TiffTags.LIBTIFF_CORE: - if ( - TiffTags.lookup(tag).type == TiffTags.UNDEFINED - or not Image.core.libtiff_support_custom_tags - ): + if not Image.core.libtiff_support_custom_tags: continue if tag in ifd.tagtype: types[tag] = ifd.tagtype[tag] elif not (isinstance(value, (int, float, str, bytes))): continue + else: + type = TiffTags.lookup(tag).type + if type: + types[tag] = type if tag not in atts and tag not in blocklist: if isinstance(value, str): atts[tag] = value.encode("ascii", "replace") + b"\0" @@ -1597,8 +1606,7 @@ def _save(im, fp, filename): else: atts[tag] = value - if DEBUG: - print("Converted items: %s" % sorted(atts.items())) + logger.debug("Converted items: %s" % sorted(atts.items())) # libtiff always expects the bytes in native order. # we're storing image byte order. So, if the rawmode @@ -1623,7 +1631,7 @@ def _save(im, fp, filename): if s: break if s < 0: - raise OSError("encoder error %d when writing image file" % s) + raise OSError(f"encoder error {s} when writing image file") else: offset = ifd.save(fp) @@ -1789,29 +1797,29 @@ class AppendingTiffWriter: self.f.seek(-2, os.SEEK_CUR) bytesWritten = self.f.write(struct.pack(self.longFmt, value)) if bytesWritten is not None and bytesWritten != 4: - raise RuntimeError("wrote only %u bytes but wanted 4" % bytesWritten) + raise RuntimeError(f"wrote only {bytesWritten} bytes but wanted 4") def rewriteLastShort(self, value): self.f.seek(-2, os.SEEK_CUR) bytesWritten = self.f.write(struct.pack(self.shortFmt, value)) if bytesWritten is not None and bytesWritten != 2: - raise RuntimeError("wrote only %u bytes but wanted 2" % bytesWritten) + raise RuntimeError(f"wrote only {bytesWritten} bytes but wanted 2") def rewriteLastLong(self, value): self.f.seek(-4, os.SEEK_CUR) bytesWritten = self.f.write(struct.pack(self.longFmt, value)) if bytesWritten is not None and bytesWritten != 4: - raise RuntimeError("wrote only %u bytes but wanted 4" % bytesWritten) + raise RuntimeError(f"wrote only {bytesWritten} bytes but wanted 4") def writeShort(self, value): bytesWritten = self.f.write(struct.pack(self.shortFmt, value)) if bytesWritten is not None and bytesWritten != 2: - raise RuntimeError("wrote only %u bytes but wanted 2" % bytesWritten) + raise RuntimeError(f"wrote only {bytesWritten} bytes but wanted 2") def writeLong(self, value): bytesWritten = self.f.write(struct.pack(self.longFmt, value)) if bytesWritten is not None and bytesWritten != 4: - raise RuntimeError("wrote only %u bytes but wanted 4" % bytesWritten) + raise RuntimeError(f"wrote only {bytesWritten} bytes but wanted 4") def close(self): self.finalize() diff --git a/src/PIL/TiffTags.py b/src/PIL/TiffTags.py index 6cc9ff7f3..796ff3479 100644 --- a/src/PIL/TiffTags.py +++ b/src/PIL/TiffTags.py @@ -69,6 +69,7 @@ SIGNED_LONG = 9 SIGNED_RATIONAL = 10 FLOAT = 11 DOUBLE = 12 +IFD = 13 TAGS_V2 = { 254: ("NewSubfileType", LONG, 1), @@ -483,7 +484,6 @@ LIBTIFF_CORE = { 65537, } -LIBTIFF_CORE.remove(320) # Array of short, crashes LIBTIFF_CORE.remove(301) # Array of short, crashes LIBTIFF_CORE.remove(532) # Array of long, crashes diff --git a/src/PIL/WalImageFile.py b/src/PIL/WalImageFile.py index d5a5c8e67..b578d6981 100644 --- a/src/PIL/WalImageFile.py +++ b/src/PIL/WalImageFile.py @@ -12,13 +12,16 @@ # See the README file for information on usage and redistribution. # -# NOTE: This format cannot be automatically recognized, so the reader -# is not registered for use with Image.open(). To open a WAL file, use -# the WalImageFile.open() function instead. +""" +This reader is based on the specification available from: +https://www.flipcode.com/archives/Quake_2_BSP_File_Format.shtml +and has been tested with a few sample files found using google. -# This reader is based on the specification available from: -# https://www.flipcode.com/archives/Quake_2_BSP_File_Format.shtml -# and has been tested with a few sample files found using google. +.. note:: + This format cannot be automatically recognized, so the reader + is not registered for use with :py:func:`PIL.Image.open()`. + To open a WAL file, use the :py:func:`PIL.WalImageFile.open()` function instead. +""" import builtins @@ -31,7 +34,7 @@ def open(filename): Load texture from a Quake2 WAL texture file. By default, a Quake2 standard palette is attached to the texture. - To override the palette, use the putpalette method. + To override the palette, use the :py:func:`PIL.Image.Image.putpalette()` method. :param filename: WAL file name, or an opened file handle. :returns: An image instance. diff --git a/src/PIL/WebPImagePlugin.py b/src/PIL/WebPImagePlugin.py index eda685508..2e9746fa3 100644 --- a/src/PIL/WebPImagePlugin.py +++ b/src/PIL/WebPImagePlugin.py @@ -38,6 +38,8 @@ class WebPImageFile(ImageFile.ImageFile): format = "WEBP" format_description = "WebP image" + __loaded = 0 + __logical_frame = 0 def _open(self): if not _webp.HAVE_WEBPANIM: @@ -52,7 +54,8 @@ class WebPImageFile(ImageFile.ImageFile): self._size = width, height self.fp = BytesIO(data) self.tile = [("raw", (0, 0) + self.size, 0, self.mode)] - self._n_frames = 1 + self.n_frames = 1 + self.is_animated = False return # Use the newer AnimDecoder API to parse the (possibly) animated file, @@ -70,7 +73,8 @@ class WebPImageFile(ImageFile.ImageFile): bgcolor & 0xFF, ) self.info["background"] = (bg_r, bg_g, bg_b, bg_a) - self._n_frames = frame_count + self.n_frames = frame_count + self.is_animated = self.n_frames > 1 self.mode = "RGB" if mode == "RGBX" else mode self.rawmode = mode self.tile = [] @@ -88,30 +92,15 @@ class WebPImageFile(ImageFile.ImageFile): # Initialize seek state self._reset(reset=False) - self.seek(0) def _getexif(self): if "exif" not in self.info: return None return dict(self.getexif()) - @property - def n_frames(self): - return self._n_frames - - @property - def is_animated(self): - return self._n_frames > 1 - def seek(self, frame): - if not _webp.HAVE_WEBPANIM: - return super().seek(frame) - - # Perform some simple checks first - if frame >= self._n_frames: - raise EOFError("attempted to seek beyond end of sequence") - if frame < 0: - raise EOFError("negative frame index is not valid") + if not self._seek_check(frame): + return # Set logical frame to requested position self.__logical_frame = frame @@ -325,6 +314,7 @@ def _save(im, fp, filename): if isinstance(exif, Image.Exif): exif = exif.tobytes() xmp = im.encoderinfo.get("xmp", "") + method = im.encoderinfo.get("method", 0) if im.mode not in _VALID_WEBP_LEGACY_MODES: alpha = ( @@ -342,6 +332,7 @@ def _save(im, fp, filename): float(quality), im.mode, icc_profile, + method, exif, xmp, ) diff --git a/src/PIL/WmfImagePlugin.py b/src/PIL/WmfImagePlugin.py index 024222c9b..87847a107 100644 --- a/src/PIL/WmfImagePlugin.py +++ b/src/PIL/WmfImagePlugin.py @@ -20,7 +20,10 @@ # http://wvware.sourceforge.net/caolan/ora-wmf.html from . import Image, ImageFile -from ._binary import i16le as word, i32le as dword, si16le as short, si32le as _long +from ._binary import i16le as word +from ._binary import i32le as dword +from ._binary import si16le as short +from ._binary import si32le as _long _handler = None diff --git a/src/PIL/XVThumbImagePlugin.py b/src/PIL/XVThumbImagePlugin.py index c0d8db09a..4efedb77e 100644 --- a/src/PIL/XVThumbImagePlugin.py +++ b/src/PIL/XVThumbImagePlugin.py @@ -18,7 +18,7 @@ # from . import Image, ImageFile, ImagePalette -from ._binary import i8, o8 +from ._binary import o8 _MAGIC = b"P7 332" @@ -59,7 +59,7 @@ class XVThumbImageFile(ImageFile.ImageFile): s = self.fp.readline() if not s: raise SyntaxError("Unexpected EOF reading XV thumbnail file") - if i8(s[0]) != 35: # ie. when not a comment: '#' + if s[0] != 35: # ie. when not a comment: '#' break # parse header line (already read) diff --git a/src/PIL/XbmImagePlugin.py b/src/PIL/XbmImagePlugin.py index ead9722c8..644cfb39b 100644 --- a/src/PIL/XbmImagePlugin.py +++ b/src/PIL/XbmImagePlugin.py @@ -69,15 +69,15 @@ class XbmImageFile(ImageFile.ImageFile): def _save(im, fp, filename): if im.mode != "1": - raise OSError("cannot write mode %s as XBM" % im.mode) + raise OSError(f"cannot write mode {im.mode} as XBM") - fp.write(("#define im_width %d\n" % im.size[0]).encode("ascii")) - fp.write(("#define im_height %d\n" % im.size[1]).encode("ascii")) + fp.write(f"#define im_width {im.size[0]}\n".encode("ascii")) + fp.write(f"#define im_height {im.size[1]}\n".encode("ascii")) hotspot = im.encoderinfo.get("hotspot") if hotspot: - fp.write(("#define im_x_hot %d\n" % hotspot[0]).encode("ascii")) - fp.write(("#define im_y_hot %d\n" % hotspot[1]).encode("ascii")) + fp.write(f"#define im_x_hot {hotspot[0]}\n".encode("ascii")) + fp.write(f"#define im_y_hot {hotspot[1]}\n".encode("ascii")) fp.write(b"static char im_bits[] = {\n") diff --git a/src/PIL/XpmImagePlugin.py b/src/PIL/XpmImagePlugin.py index d8bd00a1b..ebd65ba58 100644 --- a/src/PIL/XpmImagePlugin.py +++ b/src/PIL/XpmImagePlugin.py @@ -18,7 +18,7 @@ import re from . import Image, ImageFile, ImagePalette -from ._binary import i8, o8 +from ._binary import o8 # XPM header xpm_head = re.compile(b'"([0-9]*) ([0-9]*) ([0-9]*) ([0-9]*)') @@ -72,7 +72,7 @@ class XpmImageFile(ImageFile.ImageFile): elif s[-1:] in b"\r\n": s = s[:-1] - c = i8(s[1]) + c = s[1] s = s[2:-2].split() for i in range(0, len(s), 2): diff --git a/src/PIL/__init__.py b/src/PIL/__init__.py index e7f26488d..890ae44f5 100644 --- a/src/PIL/__init__.py +++ b/src/PIL/__init__.py @@ -13,13 +13,72 @@ Use PIL.__version__ for this Pillow version. ;-) """ +import sys +import warnings + from . import _version # VERSION was removed in Pillow 6.0.0. -# PILLOW_VERSION was removed in Pillow 7.0.0. -# Use __version__ instead. __version__ = _version.__version__ + +# PILLOW_VERSION is deprecated and will be removed in a future release. +# Use __version__ instead. +def _raise_version_warning(): + warnings.warn( + "PILLOW_VERSION is deprecated and will be removed in Pillow 9 (2022-01-02). " + "Use __version__ instead.", + DeprecationWarning, + stacklevel=3, + ) + + +if sys.version_info >= (3, 7): + + def __getattr__(name): + if name == "PILLOW_VERSION": + _raise_version_warning() + return __version__ + raise AttributeError(f"module '{__name__}' has no attribute '{name}'") + + +else: + + class _Deprecated_Version(str): + def __str__(self): + _raise_version_warning() + return super().__str__() + + def __getitem__(self, key): + _raise_version_warning() + return super().__getitem__(key) + + def __eq__(self, other): + _raise_version_warning() + return super().__eq__(other) + + def __ne__(self, other): + _raise_version_warning() + return super().__ne__(other) + + def __gt__(self, other): + _raise_version_warning() + return super().__gt__(other) + + def __lt__(self, other): + _raise_version_warning() + return super().__lt__(other) + + def __ge__(self, other): + _raise_version_warning() + return super().__gt__(other) + + def __le__(self, other): + _raise_version_warning() + return super().__lt__(other) + + PILLOW_VERSION = _Deprecated_Version(__version__) + del _version @@ -72,5 +131,9 @@ _plugins = [ ] -class UnidentifiedImageError(IOError): +class UnidentifiedImageError(OSError): + """ + Raised in :py:meth:`PIL.Image.open` if an image cannot be opened and identified. + """ + pass diff --git a/src/PIL/_binary.py b/src/PIL/_binary.py index 529b8c94b..5564f450d 100644 --- a/src/PIL/_binary.py +++ b/src/PIL/_binary.py @@ -11,6 +11,10 @@ # See the README file for information on usage and redistribution. # + +"""Binary input/output support routines.""" + + from struct import pack, unpack_from diff --git a/src/PIL/_tkinter_finder.py b/src/PIL/_tkinter_finder.py index 30493066a..7018a1b79 100644 --- a/src/PIL/_tkinter_finder.py +++ b/src/PIL/_tkinter_finder.py @@ -4,13 +4,6 @@ import sys from tkinter import _tkinter as tk if hasattr(sys, "pypy_find_executable"): - # Tested with packages at https://bitbucket.org/pypy/pypy/downloads. - # PyPies 1.6, 2.0 do not have tkinter built in. PyPy3-2.3.1 gives an - # OSError trying to import tkinter. Otherwise: - try: # PyPy 5.1, 4.0.0, 2.6.1, 2.6.0 - TKINTER_LIB = tk.tklib_cffi.__file__ - except AttributeError: - # PyPy3 2.4, 2.1-beta1; PyPy 2.5.1, 2.5.0, 2.4.0, 2.3, 2.2, 2.1 - TKINTER_LIB = tk.tkffi.verifier.modulefilename + TKINTER_LIB = tk.tklib_cffi.__file__ else: TKINTER_LIB = tk.__file__ diff --git a/src/PIL/_util.py b/src/PIL/_util.py index 755b4b272..0c5d3892e 100644 --- a/src/PIL/_util.py +++ b/src/PIL/_util.py @@ -1,20 +1,9 @@ import os -import sys - -py36 = sys.version_info[0:2] >= (3, 6) +from pathlib import Path -if py36: - from pathlib import Path - - def isPath(f): - return isinstance(f, (bytes, str, Path)) - - -else: - - def isPath(f): - return isinstance(f, (bytes, str)) +def isPath(f): + return isinstance(f, (bytes, str, Path)) # Checks if an object is a string, and that it points to a directory. diff --git a/src/PIL/_version.py b/src/PIL/_version.py index 26aad70c3..20e8754a4 100644 --- a/src/PIL/_version.py +++ b/src/PIL/_version.py @@ -1,2 +1,2 @@ # Master version for Pillow -__version__ = "7.1.0.dev0" +__version__ = "8.2.0.dev0" diff --git a/src/PIL/features.py b/src/PIL/features.py index 5822febab..da0ca557c 100644 --- a/src/PIL/features.py +++ b/src/PIL/features.py @@ -1,25 +1,33 @@ import collections import os import sys +import warnings import PIL from . import Image modules = { - "pil": "PIL._imaging", - "tkinter": "PIL._tkinter_finder", - "freetype2": "PIL._imagingft", - "littlecms2": "PIL._imagingcms", - "webp": "PIL._webp", + "pil": ("PIL._imaging", "PILLOW_VERSION"), + "tkinter": ("PIL._tkinter_finder", None), + "freetype2": ("PIL._imagingft", "freetype2_version"), + "littlecms2": ("PIL._imagingcms", "littlecms_version"), + "webp": ("PIL._webp", "webpdecoder_version"), } def check_module(feature): - if not (feature in modules): - raise ValueError("Unknown module %s" % feature) + """ + Checks if a module is available. - module = modules[feature] + :param feature: The module to check for. + :returns: ``True`` if available, ``False`` otherwise. + :raises ValueError: If the module is not defined in this version of Pillow. + """ + if not (feature in modules): + raise ValueError(f"Unknown module {feature}") + + module, ver = modules[feature] try: __import__(module) @@ -28,41 +36,106 @@ def check_module(feature): return False +def version_module(feature): + """ + :param feature: The module to check for. + :returns: + The loaded version number as a string, or ``None`` if unknown or not available. + :raises ValueError: If the module is not defined in this version of Pillow. + """ + if not check_module(feature): + return None + + module, ver = modules[feature] + + if ver is None: + return None + + return getattr(__import__(module, fromlist=[ver]), ver) + + def get_supported_modules(): + """ + :returns: A list of all supported modules. + """ return [f for f in modules if check_module(f)] -codecs = {"jpg": "jpeg", "jpg_2000": "jpeg2k", "zlib": "zip", "libtiff": "libtiff"} +codecs = { + "jpg": ("jpeg", "jpeglib"), + "jpg_2000": ("jpeg2k", "jp2klib"), + "zlib": ("zip", "zlib"), + "libtiff": ("libtiff", "libtiff"), +} def check_codec(feature): - if feature not in codecs: - raise ValueError("Unknown codec %s" % feature) + """ + Checks if a codec is available. - codec = codecs[feature] + :param feature: The codec to check for. + :returns: ``True`` if available, ``False`` otherwise. + :raises ValueError: If the codec is not defined in this version of Pillow. + """ + if feature not in codecs: + raise ValueError(f"Unknown codec {feature}") + + codec, lib = codecs[feature] return codec + "_encoder" in dir(Image.core) +def version_codec(feature): + """ + :param feature: The codec to check for. + :returns: + The version number as a string, or ``None`` if not available. + Checked at compile time for ``jpg``, run-time otherwise. + :raises ValueError: If the codec is not defined in this version of Pillow. + """ + if not check_codec(feature): + return None + + codec, lib = codecs[feature] + + version = getattr(Image.core, lib + "_version") + + if feature == "libtiff": + return version.split("\n")[0].split("Version ")[1] + + return version + + def get_supported_codecs(): + """ + :returns: A list of all supported codecs. + """ return [f for f in codecs if check_codec(f)] features = { - "webp_anim": ("PIL._webp", "HAVE_WEBPANIM"), - "webp_mux": ("PIL._webp", "HAVE_WEBPMUX"), - "transp_webp": ("PIL._webp", "HAVE_TRANSPARENCY"), - "raqm": ("PIL._imagingft", "HAVE_RAQM"), - "libjpeg_turbo": ("PIL._imaging", "HAVE_LIBJPEGTURBO"), - "libimagequant": ("PIL._imaging", "HAVE_LIBIMAGEQUANT"), + "webp_anim": ("PIL._webp", "HAVE_WEBPANIM", None), + "webp_mux": ("PIL._webp", "HAVE_WEBPMUX", None), + "transp_webp": ("PIL._webp", "HAVE_TRANSPARENCY", None), + "raqm": ("PIL._imagingft", "HAVE_RAQM", "raqm_version"), + "libjpeg_turbo": ("PIL._imaging", "HAVE_LIBJPEGTURBO", "libjpeg_turbo_version"), + "libimagequant": ("PIL._imaging", "HAVE_LIBIMAGEQUANT", "imagequant_version"), + "xcb": ("PIL._imaging", "HAVE_XCB", None), } def check_feature(feature): - if feature not in features: - raise ValueError("Unknown feature %s" % feature) + """ + Checks if a feature is available. - module, flag = features[feature] + :param feature: The feature to check for. + :returns: ``True`` if available, ``False`` if unavailable, ``None`` if unknown. + :raises ValueError: If the feature is not defined in this version of Pillow. + """ + if feature not in features: + raise ValueError(f"Unknown feature {feature}") + + module, flag, ver = features[feature] try: imported_module = __import__(module, fromlist=["PIL"]) @@ -71,22 +144,69 @@ def check_feature(feature): return None +def version_feature(feature): + """ + :param feature: The feature to check for. + :returns: The version number as a string, or ``None`` if not available. + :raises ValueError: If the feature is not defined in this version of Pillow. + """ + if not check_feature(feature): + return None + + module, flag, ver = features[feature] + + if ver is None: + return None + + return getattr(__import__(module, fromlist=[ver]), ver) + + def get_supported_features(): + """ + :returns: A list of all supported features. + """ return [f for f in features if check_feature(f)] def check(feature): - return ( - feature in modules - and check_module(feature) - or feature in codecs - and check_codec(feature) - or feature in features - and check_feature(feature) - ) + """ + :param feature: A module, codec, or feature name. + :returns: + ``True`` if the module, codec, or feature is available, + ``False`` or ``None`` otherwise. + """ + + if feature in modules: + return check_module(feature) + if feature in codecs: + return check_codec(feature) + if feature in features: + return check_feature(feature) + warnings.warn(f"Unknown feature '{feature}'.", stacklevel=2) + return False + + +def version(feature): + """ + :param feature: + The module, codec, or feature to check for. + :returns: + The version number as a string, or ``None`` if unknown or not available. + """ + if feature in modules: + return version_module(feature) + if feature in codecs: + return version_codec(feature) + if feature in features: + return version_feature(feature) + return None def get_supported(): + """ + :returns: A list of all supported modules, features, and codecs. + """ + ret = get_supported_modules() ret.extend(get_supported_features()) ret.extend(get_supported_codecs()) @@ -94,24 +214,34 @@ def get_supported(): def pilinfo(out=None, supported_formats=True): + """ + Prints information about this installation of Pillow. + This function can be called with ``python -m PIL``. + + :param out: + The output stream to print to. Defaults to ``sys.stdout`` if ``None``. + :param supported_formats: + If ``True``, a list of all supported image file formats will be printed. + """ + if out is None: out = sys.stdout Image.init() print("-" * 68, file=out) - print("Pillow {}".format(PIL.__version__), file=out) + print(f"Pillow {PIL.__version__}", file=out) py_version = sys.version.splitlines() - print("Python {}".format(py_version[0].strip()), file=out) + print(f"Python {py_version[0].strip()}", file=out) for py_version in py_version[1:]: - print(" {}".format(py_version.strip()), file=out) + print(f" {py_version.strip()}", file=out) print("-" * 68, file=out) print( - "Python modules loaded from {}".format(os.path.dirname(Image.__file__)), + f"Python modules loaded from {os.path.dirname(Image.__file__)}", file=out, ) print( - "Binary modules loaded from {}".format(os.path.dirname(Image.core.__file__)), + f"Binary modules loaded from {os.path.dirname(Image.core.__file__)}", file=out, ) print("-" * 68, file=out) @@ -131,9 +261,22 @@ def pilinfo(out=None, supported_formats=True): ("libtiff", "LIBTIFF"), ("raqm", "RAQM (Bidirectional Text)"), ("libimagequant", "LIBIMAGEQUANT (Quantization method)"), + ("xcb", "XCB (X protocol)"), ]: if check(name): - print("---", feature, "support ok", file=out) + if name == "jpg" and check_feature("libjpeg_turbo"): + v = "libjpeg-turbo " + version_feature("libjpeg_turbo") + else: + v = version(name) + if v is not None: + version_static = name in ("pil", "jpg") + if name == "littlecms2": + # this check is also in src/_imagingcms.c:setup_module() + version_static = tuple(int(x) for x in v.split(".")) < (2, 7) + t = "compiled for" if version_static else "loaded" + print("---", feature, "support ok,", t, v, file=out) + else: + print("---", feature, "support ok", file=out) else: print("***", feature, "support not installed", file=out) print("-" * 68, file=out) @@ -144,9 +287,9 @@ def pilinfo(out=None, supported_formats=True): extensions[i].append(ext) for i in sorted(Image.ID): - line = "{}".format(i) + line = f"{i}" if i in Image.MIME: - line = "{} {}".format(line, Image.MIME[i]) + line = f"{line} {Image.MIME[i]}" print(line, file=out) if i in extensions: diff --git a/src/Tk/_tkmini.h b/src/Tk/_tkmini.h index adc470532..b6945eb1a 100644 --- a/src/Tk/_tkmini.h +++ b/src/Tk/_tkmini.h @@ -79,18 +79,20 @@ typedef struct Tcl_Interp Tcl_Interp; typedef struct Tcl_Command_ *Tcl_Command; typedef void *ClientData; -typedef int (Tcl_CmdProc) (ClientData clientData, Tcl_Interp - *interp, int argc, const char *argv[]); -typedef void (Tcl_CmdDeleteProc) (ClientData clientData); +typedef int(Tcl_CmdProc)( + ClientData clientData, Tcl_Interp *interp, int argc, const char *argv[]); +typedef void(Tcl_CmdDeleteProc)(ClientData clientData); /* Typedefs derived from function signatures in Tcl header */ /* Tcl_CreateCommand */ -typedef Tcl_Command (*Tcl_CreateCommand_t)(Tcl_Interp *interp, - const char *cmdName, Tcl_CmdProc *proc, - ClientData clientData, - Tcl_CmdDeleteProc *deleteProc); +typedef Tcl_Command (*Tcl_CreateCommand_t)( + Tcl_Interp *interp, + const char *cmdName, + Tcl_CmdProc *proc, + ClientData clientData, + Tcl_CmdDeleteProc *deleteProc); /* Tcl_AppendResult */ -typedef void (*Tcl_AppendResult_t) (Tcl_Interp *interp, ...); +typedef void (*Tcl_AppendResult_t)(Tcl_Interp *interp, ...); /* Tk header excerpts */ @@ -107,8 +109,7 @@ typedef struct Tk_Window_ *Tk_Window; typedef void *Tk_PhotoHandle; -typedef struct Tk_PhotoImageBlock -{ +typedef struct Tk_PhotoImageBlock { unsigned char *pixelPtr; int width; int height; @@ -119,23 +120,30 @@ typedef struct Tk_PhotoImageBlock /* Typedefs derived from function signatures in Tk header */ /* Tk_PhotoPutBlock for Tk <= 8.4 */ -typedef void (*Tk_PhotoPutBlock_84_t) (Tk_PhotoHandle handle, - Tk_PhotoImageBlock *blockPtr, int x, int y, - int width, int height, int compRule); +typedef void (*Tk_PhotoPutBlock_84_t)( + Tk_PhotoHandle handle, + Tk_PhotoImageBlock *blockPtr, + int x, + int y, + int width, + int height, + int compRule); /* Tk_PhotoPutBlock for Tk >= 8.5 */ -typedef int (*Tk_PhotoPutBlock_85_t) (Tcl_Interp * interp, - Tk_PhotoHandle handle, - Tk_PhotoImageBlock * blockPtr, int x, int y, - int width, int height, int compRule); +typedef int (*Tk_PhotoPutBlock_85_t)( + Tcl_Interp *interp, + Tk_PhotoHandle handle, + Tk_PhotoImageBlock *blockPtr, + int x, + int y, + int width, + int height, + int compRule); /* Tk_PhotoSetSize for Tk <= 8.4 */ -typedef void (*Tk_PhotoSetSize_84_t) (Tk_PhotoHandle handle, - int width, int height); +typedef void (*Tk_PhotoSetSize_84_t)(Tk_PhotoHandle handle, int width, int height); /* Tk_FindPhoto */ -typedef Tk_PhotoHandle (*Tk_FindPhoto_t) (Tcl_Interp *interp, - const char *imageName); +typedef Tk_PhotoHandle (*Tk_FindPhoto_t)(Tcl_Interp *interp, const char *imageName); /* Tk_PhotoGetImage */ -typedef int (*Tk_PhotoGetImage_t) (Tk_PhotoHandle handle, - Tk_PhotoImageBlock * blockPtr); +typedef int (*Tk_PhotoGetImage_t)(Tk_PhotoHandle handle, Tk_PhotoImageBlock *blockPtr); /* * end block for C++ diff --git a/src/Tk/tkImaging.c b/src/Tk/tkImaging.c index 59801f58e..1c6c5f34a 100644 --- a/src/Tk/tkImaging.c +++ b/src/Tk/tkImaging.c @@ -39,7 +39,7 @@ * See the README file for information on usage and redistribution. */ -#include "Imaging.h" +#include "../libImaging/Imaging.h" #include "_tkmini.h" #include @@ -58,54 +58,50 @@ static Tk_PhotoSetSize_84_t TK_PHOTO_SET_SIZE_84; static Tk_PhotoPutBlock_85_t TK_PHOTO_PUT_BLOCK_85; static Imaging -ImagingFind(const char* name) -{ +ImagingFind(const char *name) { Py_ssize_t id; /* FIXME: use CObject instead? */ -#if defined(_MSC_VER) && defined(_WIN64) +#if defined(_WIN64) id = _atoi64(name); #else id = atol(name); #endif - if (!id) + if (!id) { return NULL; + } - return (Imaging) id; + return (Imaging)id; } - static int -PyImagingPhotoPut(ClientData clientdata, Tcl_Interp* interp, - int argc, const char **argv) -{ +PyImagingPhotoPut( + ClientData clientdata, Tcl_Interp *interp, int argc, const char **argv) { Imaging im; Tk_PhotoHandle photo; Tk_PhotoImageBlock block; if (argc != 3) { - TCL_APPEND_RESULT(interp, "usage: ", argv[0], - " destPhoto srcImage", (char *) NULL); + TCL_APPEND_RESULT( + interp, "usage: ", argv[0], " destPhoto srcImage", (char *)NULL); return TCL_ERROR; } /* get Tcl PhotoImage handle */ photo = TK_FIND_PHOTO(interp, argv[1]); if (photo == NULL) { - TCL_APPEND_RESULT( - interp, "destination photo must exist", (char *) NULL - ); + TCL_APPEND_RESULT(interp, "destination photo must exist", (char *)NULL); return TCL_ERROR; } /* get PIL Image handle */ im = ImagingFind(argv[2]); if (!im) { - TCL_APPEND_RESULT(interp, "bad name", (char*) NULL); + TCL_APPEND_RESULT(interp, "bad name", (char *)NULL); return TCL_ERROR; } if (!im->block) { - TCL_APPEND_RESULT(interp, "bad display memory", (char*) NULL); + TCL_APPEND_RESULT(interp, "bad display memory", (char *)NULL); return TCL_ERROR; } @@ -113,79 +109,85 @@ PyImagingPhotoPut(ClientData clientdata, Tcl_Interp* interp, if (strcmp(im->mode, "1") == 0 || strcmp(im->mode, "L") == 0) { block.pixelSize = 1; - block.offset[0] = block.offset[1] = block.offset[2] = 0; + block.offset[0] = block.offset[1] = block.offset[2] = block.offset[3] = 0; } else if (strncmp(im->mode, "RGB", 3) == 0) { block.pixelSize = 4; block.offset[0] = 0; block.offset[1] = 1; block.offset[2] = 2; - if (strcmp(im->mode, "RGBA") == 0) + if (strcmp(im->mode, "RGBA") == 0) { block.offset[3] = 3; /* alpha (or reserved, under 8.2) */ - else + } else { block.offset[3] = 0; /* no alpha */ + } } else { - TCL_APPEND_RESULT(interp, "Bad mode", (char*) NULL); + TCL_APPEND_RESULT(interp, "Bad mode", (char *)NULL); return TCL_ERROR; } block.width = im->xsize; block.height = im->ysize; block.pitch = im->linesize; - block.pixelPtr = (unsigned char*) im->block; + block.pixelPtr = (unsigned char *)im->block; if (TK_LT_85) { /* Tk 8.4 */ - TK_PHOTO_PUT_BLOCK_84(photo, &block, 0, 0, block.width, block.height, - TK_PHOTO_COMPOSITE_SET); - if (strcmp(im->mode, "RGBA") == 0) + TK_PHOTO_PUT_BLOCK_84( + photo, &block, 0, 0, block.width, block.height, TK_PHOTO_COMPOSITE_SET); + if (strcmp(im->mode, "RGBA") == 0) { /* Tk workaround: we need apply ToggleComplexAlphaIfNeeded */ /* (fixed in Tk 8.5a3) */ TK_PHOTO_SET_SIZE_84(photo, block.width, block.height); + } } else { /* Tk >=8.5 */ - TK_PHOTO_PUT_BLOCK_85(interp, photo, &block, 0, 0, block.width, - block.height, TK_PHOTO_COMPOSITE_SET); + TK_PHOTO_PUT_BLOCK_85( + interp, + photo, + &block, + 0, + 0, + block.width, + block.height, + TK_PHOTO_COMPOSITE_SET); } return TCL_OK; } static int -PyImagingPhotoGet(ClientData clientdata, Tcl_Interp* interp, - int argc, const char **argv) -{ +PyImagingPhotoGet( + ClientData clientdata, Tcl_Interp *interp, int argc, const char **argv) { Imaging im; Tk_PhotoHandle photo; Tk_PhotoImageBlock block; int x, y, z; if (argc != 3) { - TCL_APPEND_RESULT(interp, "usage: ", argv[0], - " srcPhoto destImage", (char *) NULL); + TCL_APPEND_RESULT( + interp, "usage: ", argv[0], " srcPhoto destImage", (char *)NULL); return TCL_ERROR; } /* get Tcl PhotoImage handle */ photo = TK_FIND_PHOTO(interp, argv[1]); if (photo == NULL) { - TCL_APPEND_RESULT( - interp, "source photo must exist", (char *) NULL - ); + TCL_APPEND_RESULT(interp, "source photo must exist", (char *)NULL); return TCL_ERROR; } /* get PIL Image handle */ im = ImagingFind(argv[2]); if (!im) { - TCL_APPEND_RESULT(interp, "bad name", (char*) NULL); + TCL_APPEND_RESULT(interp, "bad name", (char *)NULL); return TCL_ERROR; } TK_PHOTO_GET_IMAGE(photo, &block); for (y = 0; y < block.height; y++) { - UINT8* out = (UINT8*)im->image32[y]; + UINT8 *out = (UINT8 *)im->image32[y]; for (x = 0; x < block.pitch; x += block.pixelSize) { - for (z=0; z < block.pixelSize; z++) { + for (z = 0; z < block.pixelSize; z++) { int offset = block.offset[z]; out[x + offset] = block.pixelPtr[y * block.pitch + x + offset]; } @@ -195,14 +197,20 @@ PyImagingPhotoGet(ClientData clientdata, Tcl_Interp* interp, return TCL_OK; } - void -TkImaging_Init(Tcl_Interp* interp) -{ - TCL_CREATE_COMMAND(interp, "PyImagingPhoto", PyImagingPhotoPut, - (ClientData) 0, (Tcl_CmdDeleteProc*) NULL); - TCL_CREATE_COMMAND(interp, "PyImagingPhotoGet", PyImagingPhotoGet, - (ClientData) 0, (Tcl_CmdDeleteProc*) NULL); +TkImaging_Init(Tcl_Interp *interp) { + TCL_CREATE_COMMAND( + interp, + "PyImagingPhoto", + PyImagingPhotoPut, + (ClientData)0, + (Tcl_CmdDeleteProc *)NULL); + TCL_CREATE_COMMAND( + interp, + "PyImagingPhotoGet", + PyImagingPhotoGet, + (ClientData)0, + (Tcl_CmdDeleteProc *)NULL); } /* @@ -227,13 +235,13 @@ TkImaging_Init(Tcl_Interp* interp) #define TKINTER_PKG "tkinter" -FARPROC _dfunc(HMODULE lib_handle, const char *func_name) -{ +FARPROC +_dfunc(HMODULE lib_handle, const char *func_name) { /* * Load function `func_name` from `lib_handle`. * Set Python exception if we can't find `func_name` in `lib_handle`. * Returns function pointer or NULL if not present. - */ + */ char message[100]; @@ -245,24 +253,26 @@ FARPROC _dfunc(HMODULE lib_handle, const char *func_name) return func; } -int get_tcl(HMODULE hMod) -{ +int +get_tcl(HMODULE hMod) { /* * Try to fill Tcl global vars with function pointers. Return 0 for no * functions found, 1 for all functions found, -1 for some but not all * functions found. */ - if ((TCL_CREATE_COMMAND = (Tcl_CreateCommand_t) - GetProcAddress(hMod, "Tcl_CreateCommand")) == NULL) { + if ((TCL_CREATE_COMMAND = + (Tcl_CreateCommand_t)GetProcAddress(hMod, "Tcl_CreateCommand")) == NULL) { return 0; /* Maybe not Tcl module */ } - return ((TCL_APPEND_RESULT = (Tcl_AppendResult_t) _dfunc(hMod, - "Tcl_AppendResult")) == NULL) ? -1 : 1; + return ((TCL_APPEND_RESULT = + (Tcl_AppendResult_t)_dfunc(hMod, "Tcl_AppendResult")) == NULL) + ? -1 + : 1; } -int get_tk(HMODULE hMod) -{ +int +get_tk(HMODULE hMod) { /* * Try to fill Tk global vars with function pointers. Return 0 for no * functions found, 1 for all functions found, -1 for some but not all @@ -270,26 +280,31 @@ int get_tk(HMODULE hMod) */ FARPROC func = GetProcAddress(hMod, "Tk_PhotoPutBlock"); - if (func == NULL) { /* Maybe not Tk module */ + if (func == NULL) { /* Maybe not Tk module */ return 0; } - if ((TK_PHOTO_GET_IMAGE = (Tk_PhotoGetImage_t) - _dfunc(hMod, "Tk_PhotoGetImage")) == NULL) { return -1; }; - if ((TK_FIND_PHOTO = (Tk_FindPhoto_t) - _dfunc(hMod, "Tk_FindPhoto")) == NULL) { return -1; }; + if ((TK_PHOTO_GET_IMAGE = (Tk_PhotoGetImage_t)_dfunc(hMod, "Tk_PhotoGetImage")) == + NULL) { + return -1; + }; + if ((TK_FIND_PHOTO = (Tk_FindPhoto_t)_dfunc(hMod, "Tk_FindPhoto")) == NULL) { + return -1; + }; TK_LT_85 = GetProcAddress(hMod, "Tk_PhotoPutBlock_Panic") == NULL; /* Tk_PhotoPutBlock_Panic defined as of 8.5.0 */ if (TK_LT_85) { - TK_PHOTO_PUT_BLOCK_84 = (Tk_PhotoPutBlock_84_t) func; - return ((TK_PHOTO_SET_SIZE_84 = (Tk_PhotoSetSize_84_t) - _dfunc(hMod, "Tk_PhotoSetSize")) == NULL) ? -1 : 1; + TK_PHOTO_PUT_BLOCK_84 = (Tk_PhotoPutBlock_84_t)func; + return ((TK_PHOTO_SET_SIZE_84 = + (Tk_PhotoSetSize_84_t)_dfunc(hMod, "Tk_PhotoSetSize")) == NULL) + ? -1 + : 1; } - TK_PHOTO_PUT_BLOCK_85 = (Tk_PhotoPutBlock_85_t) func; + TK_PHOTO_PUT_BLOCK_85 = (Tk_PhotoPutBlock_85_t)func; return 1; } -int load_tkinter_funcs(void) -{ +int +load_tkinter_funcs(void) { /* * Load Tcl and Tk functions by searching all modules in current process. * Return 0 for success, non-zero for failure. @@ -341,7 +356,7 @@ int load_tkinter_funcs(void) return 1; } -#else /* not Windows */ +#else /* not Windows */ /* * On Unix, we can get the Tcl and Tk symbols from the tkinter module, because @@ -350,9 +365,9 @@ int load_tkinter_funcs(void) */ /* From module __file__ attribute to char *string for dlopen. */ -char *fname2char(PyObject *fname) -{ - PyObject* bytes; +char * +fname2char(PyObject *fname) { + PyObject *bytes; bytes = PyUnicode_EncodeFSDefault(fname); if (bytes == NULL) { return NULL; @@ -362,15 +377,15 @@ char *fname2char(PyObject *fname) #include -void *_dfunc(void *lib_handle, const char *func_name) -{ +void * +_dfunc(void *lib_handle, const char *func_name) { /* * Load function `func_name` from `lib_handle`. * Set Python exception if we can't find `func_name` in `lib_handle`. * Returns function pointer or NULL if not present. */ - void* func; + void *func; /* Reset errors. */ dlerror(); func = dlsym(lib_handle, func_name); @@ -381,35 +396,44 @@ void *_dfunc(void *lib_handle, const char *func_name) return func; } -int _func_loader(void *lib) -{ +int +_func_loader(void *lib) { /* * Fill global function pointers from dynamic lib. * Return 1 if any pointer is NULL, 0 otherwise. */ - if ((TCL_CREATE_COMMAND = (Tcl_CreateCommand_t) - _dfunc(lib, "Tcl_CreateCommand")) == NULL) { return 1; } - if ((TCL_APPEND_RESULT = (Tcl_AppendResult_t) _dfunc(lib, - "Tcl_AppendResult")) == NULL) { return 1; } - if ((TK_PHOTO_GET_IMAGE = (Tk_PhotoGetImage_t) - _dfunc(lib, "Tk_PhotoGetImage")) == NULL) { return 1; } - if ((TK_FIND_PHOTO = (Tk_FindPhoto_t) - _dfunc(lib, "Tk_FindPhoto")) == NULL) { return 1; } + if ((TCL_CREATE_COMMAND = (Tcl_CreateCommand_t)_dfunc(lib, "Tcl_CreateCommand")) == + NULL) { + return 1; + } + if ((TCL_APPEND_RESULT = (Tcl_AppendResult_t)_dfunc(lib, "Tcl_AppendResult")) == + NULL) { + return 1; + } + if ((TK_PHOTO_GET_IMAGE = (Tk_PhotoGetImage_t)_dfunc(lib, "Tk_PhotoGetImage")) == + NULL) { + return 1; + } + if ((TK_FIND_PHOTO = (Tk_FindPhoto_t)_dfunc(lib, "Tk_FindPhoto")) == NULL) { + return 1; + } /* Tk_PhotoPutBlock_Panic defined as of 8.5.0 */ TK_LT_85 = (dlsym(lib, "Tk_PhotoPutBlock_Panic") == NULL); if (TK_LT_85) { - return (((TK_PHOTO_PUT_BLOCK_84 = (Tk_PhotoPutBlock_84_t) - _dfunc(lib, "Tk_PhotoPutBlock")) == NULL) || - ((TK_PHOTO_SET_SIZE_84 = (Tk_PhotoSetSize_84_t) - _dfunc(lib, "Tk_PhotoSetSize")) == NULL)); + return ( + ((TK_PHOTO_PUT_BLOCK_84 = + (Tk_PhotoPutBlock_84_t)_dfunc(lib, "Tk_PhotoPutBlock")) == NULL) || + ((TK_PHOTO_SET_SIZE_84 = + (Tk_PhotoSetSize_84_t)_dfunc(lib, "Tk_PhotoSetSize")) == NULL)); } - return ((TK_PHOTO_PUT_BLOCK_85 = (Tk_PhotoPutBlock_85_t) - _dfunc(lib, "Tk_PhotoPutBlock")) == NULL); + return ( + (TK_PHOTO_PUT_BLOCK_85 = + (Tk_PhotoPutBlock_85_t)_dfunc(lib, "Tk_PhotoPutBlock")) == NULL); } -int load_tkinter_funcs(void) -{ +int +load_tkinter_funcs(void) { /* * Load tkinter global funcs from tkinter compiled module. * Return 0 for success, non-zero for failure. @@ -444,8 +468,7 @@ int load_tkinter_funcs(void) } tkinter_lib = dlopen(tkinter_libname, RTLD_LAZY); if (tkinter_lib == NULL) { - PyErr_SetString(PyExc_RuntimeError, - "Cannot dlopen tkinter module file"); + PyErr_SetString(PyExc_RuntimeError, "Cannot dlopen tkinter module file"); goto exit; } ret = _func_loader(tkinter_lib); diff --git a/src/_imaging.c b/src/_imaging.c index 4affef92e..684e677da 100644 --- a/src/_imaging.c +++ b/src/_imaging.c @@ -88,37 +88,36 @@ #endif #endif -#include "Imaging.h" +#include "libImaging/Imaging.h" #define _USE_MATH_DEFINES #include /* Configuration stuff. Feel free to undef things you don't need. */ -#define WITH_IMAGECHOPS /* ImageChops support */ -#define WITH_IMAGEDRAW /* ImageDraw support */ -#define WITH_MAPPING /* use memory mapping to read some file formats */ -#define WITH_IMAGEPATH /* ImagePath stuff */ -#define WITH_ARROW /* arrow graphics stuff (experimental) */ -#define WITH_EFFECTS /* special effects */ -#define WITH_QUANTIZE /* quantization support */ -#define WITH_RANKFILTER /* rank filter */ -#define WITH_MODEFILTER /* mode filter */ -#define WITH_THREADING /* "friendly" threading support */ +#define WITH_IMAGECHOPS /* ImageChops support */ +#define WITH_IMAGEDRAW /* ImageDraw support */ +#define WITH_MAPPING /* use memory mapping to read some file formats */ +#define WITH_IMAGEPATH /* ImagePath stuff */ +#define WITH_ARROW /* arrow graphics stuff (experimental) */ +#define WITH_EFFECTS /* special effects */ +#define WITH_QUANTIZE /* quantization support */ +#define WITH_RANKFILTER /* rank filter */ +#define WITH_MODEFILTER /* mode filter */ +#define WITH_THREADING /* "friendly" threading support */ #define WITH_UNSHARPMASK /* Kevin Cazabon's unsharpmask module */ -#undef VERBOSE +#undef VERBOSE -#define B16(p, i) ((((int)p[(i)]) << 8) + p[(i)+1]) -#define L16(p, i) ((((int)p[(i)+1]) << 8) + p[(i)]) -#define S16(v) ((v) < 32768 ? (v) : ((v) - 65536)) +#define B16(p, i) ((((int)p[(i)]) << 8) + p[(i) + 1]) +#define L16(p, i) ((((int)p[(i) + 1]) << 8) + p[(i)]) +#define S16(v) ((v) < 32768 ? (v) : ((v)-65536)) /* -------------------------------------------------------------------- */ /* OBJECT ADMINISTRATION */ /* -------------------------------------------------------------------- */ typedef struct { - PyObject_HEAD - Imaging image; + PyObject_HEAD Imaging image; ImagingAccess access; } ImagingObject; @@ -126,8 +125,7 @@ static PyTypeObject Imaging_Type; #ifdef WITH_IMAGEDRAW -typedef struct -{ +typedef struct { /* to write a character, cut out sxy from glyph data, place at current position plus dxy, and advance by (dx, dy) */ int dx, dy; @@ -136,8 +134,7 @@ typedef struct } Glyph; typedef struct { - PyObject_HEAD - ImagingObject* ref; + PyObject_HEAD ImagingObject *ref; Imaging bitmap; int ysize; int baseline; @@ -147,8 +144,7 @@ typedef struct { static PyTypeObject ImagingFont_Type; typedef struct { - PyObject_HEAD - ImagingObject* image; + PyObject_HEAD ImagingObject *image; UINT8 ink[4]; int blend; } ImagingDrawObject; @@ -158,20 +154,19 @@ static PyTypeObject ImagingDraw_Type; #endif typedef struct { - PyObject_HEAD - ImagingObject* image; + PyObject_HEAD ImagingObject *image; int readonly; } PixelAccessObject; static PyTypeObject PixelAccess_Type; -PyObject* -PyImagingNew(Imaging imOut) -{ - ImagingObject* imagep; +PyObject * +PyImagingNew(Imaging imOut) { + ImagingObject *imagep; - if (!imOut) + if (!imOut) { return NULL; + } imagep = PyObject_New(ImagingObject, &Imaging_Type); if (imagep == NULL) { @@ -186,27 +181,26 @@ PyImagingNew(Imaging imOut) imagep->image = imOut; imagep->access = ImagingAccessNew(imOut); - return (PyObject*) imagep; + return (PyObject *)imagep; } static void -_dealloc(ImagingObject* imagep) -{ - +_dealloc(ImagingObject *imagep) { #ifdef VERBOSE printf("imaging %p deleted\n", imagep); #endif - if (imagep->access) + if (imagep->access) { ImagingAccessDelete(imagep->image, imagep->access); + } ImagingDelete(imagep->image); PyObject_Del(imagep); } #define PyImaging_Check(op) (Py_TYPE(op) == &Imaging_Type) -Imaging PyImaging_AsImaging(PyObject *op) -{ +Imaging +PyImaging_AsImaging(PyObject *op) { if (!PyImaging_Check(op)) { PyErr_BadInternalCall(); return NULL; @@ -215,22 +209,21 @@ Imaging PyImaging_AsImaging(PyObject *op) return ((ImagingObject *)op)->image; } - /* -------------------------------------------------------------------- */ /* THREAD HANDLING */ /* -------------------------------------------------------------------- */ -void ImagingSectionEnter(ImagingSectionCookie* cookie) -{ +void +ImagingSectionEnter(ImagingSectionCookie *cookie) { #ifdef WITH_THREADING - *cookie = (PyThreadState *) PyEval_SaveThread(); + *cookie = (PyThreadState *)PyEval_SaveThread(); #endif } -void ImagingSectionLeave(ImagingSectionCookie* cookie) -{ +void +ImagingSectionLeave(ImagingSectionCookie *cookie) { #ifdef WITH_THREADING - PyEval_RestoreThread((PyThreadState*) *cookie); + PyEval_RestoreThread((PyThreadState *)*cookie); #endif } @@ -239,13 +232,13 @@ void ImagingSectionLeave(ImagingSectionCookie* cookie) /* -------------------------------------------------------------------- */ /* Python compatibility API */ -int PyImaging_CheckBuffer(PyObject* buffer) -{ +int +PyImaging_CheckBuffer(PyObject *buffer) { return PyObject_CheckBuffer(buffer); } -int PyImaging_GetBuffer(PyObject* buffer, Py_buffer *view) -{ +int +PyImaging_GetBuffer(PyObject *buffer, Py_buffer *view) { /* must call check_buffer first! */ return PyObject_GetBuffer(buffer, view, PyBUF_SIMPLE); } @@ -255,58 +248,50 @@ int PyImaging_GetBuffer(PyObject* buffer, Py_buffer *view) /* -------------------------------------------------------------------- */ /* error messages */ -static const char* must_be_sequence = "argument must be a sequence"; -static const char* must_be_two_coordinates = - "coordinate list must contain exactly 2 coordinates"; -static const char* wrong_mode = "unrecognized image mode"; -static const char* wrong_raw_mode = "unrecognized raw mode"; -static const char* outside_image = "image index out of range"; -static const char* outside_palette = "palette index out of range"; -static const char* wrong_palette_size = "invalid palette size"; -static const char* no_palette = "image has no palette"; -static const char* readonly = "image is readonly"; +static const char *must_be_sequence = "argument must be a sequence"; +static const char *must_be_two_coordinates = + "coordinate list must contain exactly 2 coordinates"; +static const char *wrong_mode = "unrecognized image mode"; +static const char *wrong_raw_mode = "unrecognized raw mode"; +static const char *outside_image = "image index out of range"; +static const char *outside_palette = "palette index out of range"; +static const char *wrong_palette_size = "invalid palette size"; +static const char *no_palette = "image has no palette"; +static const char *readonly = "image is readonly"; /* static const char* no_content = "image has no content"; */ void * -ImagingError_IOError(void) -{ - PyErr_SetString(PyExc_IOError, "error when accessing file"); +ImagingError_OSError(void) { + PyErr_SetString(PyExc_OSError, "error when accessing file"); return NULL; } void * -ImagingError_MemoryError(void) -{ +ImagingError_MemoryError(void) { return PyErr_NoMemory(); } void * -ImagingError_Mismatch(void) -{ +ImagingError_Mismatch(void) { PyErr_SetString(PyExc_ValueError, "images do not match"); return NULL; } void * -ImagingError_ModeError(void) -{ +ImagingError_ModeError(void) { PyErr_SetString(PyExc_ValueError, "image has wrong mode"); return NULL; } void * -ImagingError_ValueError(const char *message) -{ +ImagingError_ValueError(const char *message) { PyErr_SetString( - PyExc_ValueError, - (message) ? (char*) message : "unrecognized argument value" - ); + PyExc_ValueError, (message) ? (char *)message : "unrecognized argument value"); return NULL; } void -ImagingError_Clear(void) -{ +ImagingError_Clear(void) { PyErr_Clear(); } @@ -315,15 +300,15 @@ ImagingError_Clear(void) /* -------------------------------------------------------------------- */ static int -getbands(const char* mode) -{ +getbands(const char *mode) { Imaging im; int bands; /* FIXME: add primitive to libImaging to avoid extra allocation */ im = ImagingNew(mode, 0, 0); - if (!im) + if (!im) { return -1; + } bands = im->bands; @@ -332,15 +317,14 @@ getbands(const char* mode) return bands; } -#define TYPE_UINT8 (0x100|sizeof(UINT8)) -#define TYPE_INT32 (0x200|sizeof(INT32)) -#define TYPE_FLOAT16 (0x500|sizeof(FLOAT16)) -#define TYPE_FLOAT32 (0x300|sizeof(FLOAT32)) -#define TYPE_DOUBLE (0x400|sizeof(double)) +#define TYPE_UINT8 (0x100 | sizeof(UINT8)) +#define TYPE_INT32 (0x200 | sizeof(INT32)) +#define TYPE_FLOAT16 (0x500 | sizeof(FLOAT16)) +#define TYPE_FLOAT32 (0x300 | sizeof(FLOAT32)) +#define TYPE_DOUBLE (0x400 | sizeof(double)) -static void* -getlist(PyObject* arg, Py_ssize_t* length, const char* wrong_length, int type) -{ +static void * +getlist(PyObject *arg, Py_ssize_t *length, const char *wrong_length, int type) { /* - allocates and returns a c array of the items in the python sequence arg. - the size of the returned array is in length @@ -355,11 +339,11 @@ getlist(PyObject* arg, Py_ssize_t* length, const char* wrong_length, int type) int itemp; double dtemp; FLOAT32 ftemp; - UINT8* list; - PyObject* seq; - PyObject* op; + UINT8 *list; + PyObject *seq; + PyObject *op; - if ( ! PySequence_Check(arg)) { + if (!PySequence_Check(arg)) { PyErr_SetString(PyExc_TypeError, must_be_sequence); return NULL; } @@ -373,11 +357,12 @@ getlist(PyObject* arg, Py_ssize_t* length, const char* wrong_length, int type) /* malloc check ok, type & ff is just a sizeof(something) calloc checks for overflow */ list = calloc(n, type & 0xff); - if ( ! list) - return PyErr_NoMemory(); + if (!list) { + return ImagingError_MemoryError(); + } seq = PySequence_Fast(arg, must_be_sequence); - if ( ! seq) { + if (!seq) { free(list); return NULL; } @@ -387,22 +372,22 @@ getlist(PyObject* arg, Py_ssize_t* length, const char* wrong_length, int type) // DRY, branch prediction is going to work _really_ well // on this switch. And 3 fewer loops to copy/paste. switch (type) { - case TYPE_UINT8: - itemp = PyLong_AsLong(op); - list[i] = CLIP8(itemp); - break; - case TYPE_INT32: - itemp = PyLong_AsLong(op); - memcpy(list + i * sizeof(INT32), &itemp, sizeof(itemp)); - break; - case TYPE_FLOAT32: - ftemp = (FLOAT32)PyFloat_AsDouble(op); - memcpy(list + i * sizeof(ftemp), &ftemp, sizeof(ftemp)); - break; - case TYPE_DOUBLE: - dtemp = PyFloat_AsDouble(op); - memcpy(list + i * sizeof(dtemp), &dtemp, sizeof(dtemp)); - break; + case TYPE_UINT8: + itemp = PyLong_AsLong(op); + list[i] = CLIP8(itemp); + break; + case TYPE_INT32: + itemp = PyLong_AsLong(op); + memcpy(list + i * sizeof(INT32), &itemp, sizeof(itemp)); + break; + case TYPE_FLOAT32: + ftemp = (FLOAT32)PyFloat_AsDouble(op); + memcpy(list + i * sizeof(ftemp), &ftemp, sizeof(ftemp)); + break; + case TYPE_DOUBLE: + dtemp = PyFloat_AsDouble(op); + memcpy(list + i * sizeof(dtemp), &dtemp, sizeof(dtemp)); + break; } } @@ -413,8 +398,9 @@ getlist(PyObject* arg, Py_ssize_t* length, const char* wrong_length, int type) return NULL; } - if (length) + if (length) { *length = n; + } return list; } @@ -426,31 +412,30 @@ float16tofloat32(const FLOAT16 in) { UINT32 t3; FLOAT32 out[1] = {0}; - t1 = in & 0x7fff; // Non-sign bits - t2 = in & 0x8000; // Sign bit - t3 = in & 0x7c00; // Exponent + t1 = in & 0x7fff; // Non-sign bits + t2 = in & 0x8000; // Sign bit + t3 = in & 0x7c00; // Exponent - t1 <<= 13; // Align mantissa on MSB - t2 <<= 16; // Shift sign bit into position + t1 <<= 13; // Align mantissa on MSB + t2 <<= 16; // Shift sign bit into position - t1 += 0x38000000; // Adjust bias + t1 += 0x38000000; // Adjust bias - t1 = (t3 == 0 ? 0 : t1); // Denormals-as-zero + t1 = (t3 == 0 ? 0 : t1); // Denormals-as-zero - t1 |= t2; // Re-insert sign bit + t1 |= t2; // Re-insert sign bit memcpy(out, &t1, 4); return out[0]; } -static inline PyObject* -getpixel(Imaging im, ImagingAccess access, int x, int y) -{ +static inline PyObject * +getpixel(Imaging im, ImagingAccess access, int x, int y) { union { - UINT8 b[4]; - UINT16 h; - INT32 i; - FLOAT32 f; + UINT8 b[4]; + UINT16 h; + INT32 i; + FLOAT32 f; } pixel; if (x < 0) { @@ -468,26 +453,28 @@ getpixel(Imaging im, ImagingAccess access, int x, int y) access->get_pixel(im, x, y, &pixel); switch (im->type) { - case IMAGING_TYPE_UINT8: - switch (im->bands) { - case 1: - return PyLong_FromLong(pixel.b[0]); - case 2: - return Py_BuildValue("BB", pixel.b[0], pixel.b[1]); - case 3: - return Py_BuildValue("BBB", pixel.b[0], pixel.b[1], pixel.b[2]); - case 4: - return Py_BuildValue("BBBB", pixel.b[0], pixel.b[1], pixel.b[2], pixel.b[3]); - } - break; - case IMAGING_TYPE_INT32: - return PyLong_FromLong(pixel.i); - case IMAGING_TYPE_FLOAT32: - return PyFloat_FromDouble(pixel.f); - case IMAGING_TYPE_SPECIAL: - if (strncmp(im->mode, "I;16", 4) == 0) - return PyLong_FromLong(pixel.h); - break; + case IMAGING_TYPE_UINT8: + switch (im->bands) { + case 1: + return PyLong_FromLong(pixel.b[0]); + case 2: + return Py_BuildValue("BB", pixel.b[0], pixel.b[1]); + case 3: + return Py_BuildValue("BBB", pixel.b[0], pixel.b[1], pixel.b[2]); + case 4: + return Py_BuildValue( + "BBBB", pixel.b[0], pixel.b[1], pixel.b[2], pixel.b[3]); + } + break; + case IMAGING_TYPE_INT32: + return PyLong_FromLong(pixel.i); + case IMAGING_TYPE_FLOAT32: + return PyFloat_FromDouble(pixel.f); + case IMAGING_TYPE_SPECIAL: + if (strncmp(im->mode, "I;16", 4) == 0) { + return PyLong_FromLong(pixel.h); + } + break; } /* unknown type */ @@ -495,11 +482,10 @@ getpixel(Imaging im, ImagingAccess access, int x, int y) return Py_None; } -static char* -getink(PyObject* color, Imaging im, char* ink) -{ - int g=0, b=0, a=0; - double f=0; +static char * +getink(PyObject *color, Imaging im, char *ink) { + int g = 0, b = 0, a = 0; + double f = 0; /* Windows 64 bit longs are 32 bits, and 0xFFFFFFFF (white) is a python long (not int) that raises an overflow error when trying to return it into a 32 bit C long @@ -512,78 +498,88 @@ getink(PyObject* color, Imaging im, char* ink) be cast to either UINT8 or INT32 */ int rIsInt = 0; - if (im->type == IMAGING_TYPE_UINT8 || - im->type == IMAGING_TYPE_INT32 || + if (PyTuple_Check(color) && PyTuple_Size(color) == 1) { + color = PyTuple_GetItem(color, 0); + } + if (im->type == IMAGING_TYPE_UINT8 || im->type == IMAGING_TYPE_INT32 || im->type == IMAGING_TYPE_SPECIAL) { - if (PyLong_Check(color)) { - r = PyLong_AsLongLong(color); + if (PyLong_Check(color)) { + r = PyLong_AsLongLong(color); + if (r == -1 && PyErr_Occurred()) { + return NULL; + } rIsInt = 1; - } - if (r == -1 && PyErr_Occurred()) { - rIsInt = 0; + } else if (im->type == IMAGING_TYPE_UINT8) { + if (!PyTuple_Check(color)) { + PyErr_SetString(PyExc_TypeError, "color must be int or tuple"); + return NULL; + } + } else { + PyErr_SetString( + PyExc_TypeError, "color must be int or single-element tuple"); + return NULL; } } switch (im->type) { - case IMAGING_TYPE_UINT8: - /* unsigned integer */ - if (im->bands == 1) { - /* unsigned integer, single layer */ - if (rIsInt != 1) { - if (!PyArg_ParseTuple(color, "L", &r)) { - return NULL; + case IMAGING_TYPE_UINT8: + /* unsigned integer */ + if (im->bands == 1) { + /* unsigned integer, single layer */ + if (rIsInt != 1) { + if (!PyArg_ParseTuple(color, "L", &r)) { + return NULL; + } } - } - ink[0] = (char) CLIP8(r); - ink[1] = ink[2] = ink[3] = 0; - } else { - a = 255; - if (rIsInt) { - /* compatibility: ABGR */ - a = (UINT8) (r >> 24); - b = (UINT8) (r >> 16); - g = (UINT8) (r >> 8); - r = (UINT8) r; + ink[0] = (char)CLIP8(r); + ink[1] = ink[2] = ink[3] = 0; } else { - if (im->bands == 2) { - if (!PyArg_ParseTuple(color, "L|i", &r, &a)) - return NULL; - g = b = r; + a = 255; + if (rIsInt) { + /* compatibility: ABGR */ + a = (UINT8)(r >> 24); + b = (UINT8)(r >> 16); + g = (UINT8)(r >> 8); + r = (UINT8)r; } else { - if (!PyArg_ParseTuple(color, "Lii|i", &r, &g, &b, &a)) - return NULL; + if (im->bands == 2) { + if (!PyArg_ParseTuple(color, "L|i", &r, &a)) { + return NULL; + } + g = b = r; + } else { + if (!PyArg_ParseTuple(color, "Lii|i", &r, &g, &b, &a)) { + return NULL; + } + } } + ink[0] = (char)CLIP8(r); + ink[1] = (char)CLIP8(g); + ink[2] = (char)CLIP8(b); + ink[3] = (char)CLIP8(a); } - ink[0] = (char) CLIP8(r); - ink[1] = (char) CLIP8(g); - ink[2] = (char) CLIP8(b); - ink[3] = (char) CLIP8(a); - } - return ink; - case IMAGING_TYPE_INT32: - /* signed integer */ - if (rIsInt != 1) - return NULL; - itmp = r; - memcpy(ink, &itmp, sizeof(itmp)); - return ink; - case IMAGING_TYPE_FLOAT32: - /* floating point */ - f = PyFloat_AsDouble(color); - if (f == -1.0 && PyErr_Occurred()) - return NULL; - ftmp = f; - memcpy(ink, &ftmp, sizeof(ftmp)); - return ink; - case IMAGING_TYPE_SPECIAL: - if (strncmp(im->mode, "I;16", 4) == 0) { - if (rIsInt != 1) - return NULL; - ink[0] = (UINT8) r; - ink[1] = (UINT8) (r >> 8); - ink[2] = ink[3] = 0; return ink; - } + case IMAGING_TYPE_INT32: + /* signed integer */ + itmp = r; + memcpy(ink, &itmp, sizeof(itmp)); + return ink; + case IMAGING_TYPE_FLOAT32: + /* floating point */ + f = PyFloat_AsDouble(color); + if (f == -1.0 && PyErr_Occurred()) { + return NULL; + } + ftmp = f; + memcpy(ink, &ftmp, sizeof(ftmp)); + return ink; + case IMAGING_TYPE_SPECIAL: + if (strncmp(im->mode, "I;16", 4) == 0) { + ink[0] = (UINT8)r; + ink[1] = (UINT8)(r >> 8); + ink[2] = ink[3] = 0; + return ink; + } } PyErr_SetString(PyExc_ValueError, wrong_mode); @@ -594,24 +590,25 @@ getink(PyObject* color, Imaging im, char* ink) /* FACTORIES */ /* -------------------------------------------------------------------- */ -static PyObject* -_fill(PyObject* self, PyObject* args) -{ - char* mode; +static PyObject * +_fill(PyObject *self, PyObject *args) { + char *mode; int xsize, ysize; - PyObject* color; + PyObject *color; char buffer[4]; Imaging im; xsize = ysize = 256; color = NULL; - if (!PyArg_ParseTuple(args, "s|(ii)O", &mode, &xsize, &ysize, &color)) + if (!PyArg_ParseTuple(args, "s|(ii)O", &mode, &xsize, &ysize, &color)) { return NULL; + } im = ImagingNewDirty(mode, xsize, ysize); - if (!im) + if (!im) { return NULL; + } buffer[0] = buffer[1] = buffer[2] = buffer[3] = 0; if (color) { @@ -621,114 +618,108 @@ _fill(PyObject* self, PyObject* args) } } - - (void) ImagingFill(im, buffer); + (void)ImagingFill(im, buffer); return PyImagingNew(im); } -static PyObject* -_new(PyObject* self, PyObject* args) -{ - char* mode; +static PyObject * +_new(PyObject *self, PyObject *args) { + char *mode; int xsize, ysize; - if (!PyArg_ParseTuple(args, "s(ii)", &mode, &xsize, &ysize)) + if (!PyArg_ParseTuple(args, "s(ii)", &mode, &xsize, &ysize)) { return NULL; + } return PyImagingNew(ImagingNew(mode, xsize, ysize)); } -static PyObject* -_new_block(PyObject* self, PyObject* args) -{ - char* mode; +static PyObject * +_new_block(PyObject *self, PyObject *args) { + char *mode; int xsize, ysize; - if (!PyArg_ParseTuple(args, "s(ii)", &mode, &xsize, &ysize)) + if (!PyArg_ParseTuple(args, "s(ii)", &mode, &xsize, &ysize)) { return NULL; + } return PyImagingNew(ImagingNewBlock(mode, xsize, ysize)); } -static PyObject* -_linear_gradient(PyObject* self, PyObject* args) -{ - char* mode; +static PyObject * +_linear_gradient(PyObject *self, PyObject *args) { + char *mode; - if (!PyArg_ParseTuple(args, "s", &mode)) + if (!PyArg_ParseTuple(args, "s", &mode)) { return NULL; + } return PyImagingNew(ImagingFillLinearGradient(mode)); } -static PyObject* -_radial_gradient(PyObject* self, PyObject* args) -{ - char* mode; +static PyObject * +_radial_gradient(PyObject *self, PyObject *args) { + char *mode; - if (!PyArg_ParseTuple(args, "s", &mode)) + if (!PyArg_ParseTuple(args, "s", &mode)) { return NULL; + } return PyImagingNew(ImagingFillRadialGradient(mode)); } -static PyObject* -_alpha_composite(ImagingObject* self, PyObject* args) -{ - ImagingObject* imagep1; - ImagingObject* imagep2; +static PyObject * +_alpha_composite(ImagingObject *self, PyObject *args) { + ImagingObject *imagep1; + ImagingObject *imagep2; - if (!PyArg_ParseTuple(args, "O!O!", - &Imaging_Type, &imagep1, - &Imaging_Type, &imagep2)) + if (!PyArg_ParseTuple( + args, "O!O!", &Imaging_Type, &imagep1, &Imaging_Type, &imagep2)) { return NULL; + } return PyImagingNew(ImagingAlphaComposite(imagep1->image, imagep2->image)); } -static PyObject* -_blend(ImagingObject* self, PyObject* args) -{ - ImagingObject* imagep1; - ImagingObject* imagep2; +static PyObject * +_blend(ImagingObject *self, PyObject *args) { + ImagingObject *imagep1; + ImagingObject *imagep2; double alpha; alpha = 0.5; - if (!PyArg_ParseTuple(args, "O!O!|d", - &Imaging_Type, &imagep1, - &Imaging_Type, &imagep2, - &alpha)) + if (!PyArg_ParseTuple( + args, "O!O!|d", &Imaging_Type, &imagep1, &Imaging_Type, &imagep2, &alpha)) { return NULL; + } - return PyImagingNew(ImagingBlend(imagep1->image, imagep2->image, - (float) alpha)); + return PyImagingNew(ImagingBlend(imagep1->image, imagep2->image, (float)alpha)); } /* -------------------------------------------------------------------- */ /* METHODS */ /* -------------------------------------------------------------------- */ -static INT16* -_prepare_lut_table(PyObject* table, Py_ssize_t table_size) -{ +static INT16 * +_prepare_lut_table(PyObject *table, Py_ssize_t table_size) { int i; Py_buffer buffer_info; INT32 data_type = TYPE_FLOAT32; float item = 0; - void* table_data = NULL; - int free_table_data = 0; - INT16* prepared; + void *table_data = NULL; + int free_table_data = 0; + INT16 *prepared; - /* NOTE: This value should be the same as in ColorLUT.c */ - #define PRECISION_BITS (16 - 8 - 2) +/* NOTE: This value should be the same as in ColorLUT.c */ +#define PRECISION_BITS (16 - 8 - 2) - const char* wrong_size = ("The table should have table_channels * " - "size1D * size2D * size3D float items."); + const char *wrong_size = + ("The table should have table_channels * " + "size1D * size2D * size3D float items."); if (PyObject_CheckBuffer(table)) { - if ( ! PyObject_GetBuffer(table, &buffer_info, - PyBUF_CONTIG_RO | PyBUF_FORMAT)) { + if (!PyObject_GetBuffer(table, &buffer_info, PyBUF_CONTIG_RO | PyBUF_FORMAT)) { if (buffer_info.ndim == 1 && buffer_info.shape[0] == table_size) { if (strlen(buffer_info.format) == 1) { switch (buffer_info.format[0]) { @@ -751,20 +742,21 @@ _prepare_lut_table(PyObject* table, Py_ssize_t table_size) } } - if ( ! table_data) { + if (!table_data) { free_table_data = 1; table_data = getlist(table, &table_size, wrong_size, TYPE_FLOAT32); - if ( ! table_data) { + if (!table_data) { return NULL; } } /* malloc check ok, max is 2 * 4 * 65**3 = 2197000 */ - prepared = (INT16*) malloc(sizeof(INT16) * table_size); - if ( ! prepared) { - if (free_table_data) + prepared = (INT16 *)malloc(sizeof(INT16) * table_size); + if (!prepared) { + if (free_table_data) { free(table_data); - return (INT16*) PyErr_NoMemory(); + } + return (INT16 *)ImagingError_MemoryError(); } for (i = 0; i < table_size; i++) { @@ -772,15 +764,16 @@ _prepare_lut_table(PyObject* table, Py_ssize_t table_size) double dtmp; switch (data_type) { case TYPE_FLOAT16: - memcpy(&htmp, ((char*) table_data) + i * sizeof(htmp), sizeof(htmp)); + memcpy(&htmp, ((char *)table_data) + i * sizeof(htmp), sizeof(htmp)); item = float16tofloat32(htmp); break; case TYPE_FLOAT32: - memcpy(&item, ((char*) table_data) + i * sizeof(FLOAT32), sizeof(FLOAT32)); + memcpy( + &item, ((char *)table_data) + i * sizeof(FLOAT32), sizeof(FLOAT32)); break; case TYPE_DOUBLE: - memcpy(&dtmp, ((char*) table_data) + i * sizeof(dtmp), sizeof(dtmp)); - item = (FLOAT32) dtmp; + memcpy(&dtmp, ((char *)table_data) + i * sizeof(dtmp), sizeof(dtmp)); + item = (FLOAT32)dtmp; break; } /* Max value for INT16 */ @@ -800,69 +793,75 @@ _prepare_lut_table(PyObject* table, Py_ssize_t table_size) } } - #undef PRECISION_BITS +#undef PRECISION_BITS if (free_table_data) { free(table_data); } return prepared; } - -static PyObject* -_color_lut_3d(ImagingObject* self, PyObject* args) -{ - char* mode; +static PyObject * +_color_lut_3d(ImagingObject *self, PyObject *args) { + char *mode; int filter; int table_channels; int size1D, size2D, size3D; - PyObject* table; + PyObject *table; - INT16* prepared_table; + INT16 *prepared_table; Imaging imOut; - if ( ! PyArg_ParseTuple(args, "siiiiiO:color_lut_3d", &mode, &filter, - &table_channels, &size1D, &size2D, &size3D, - &table)) { + if (!PyArg_ParseTuple( + args, + "siiiiiO:color_lut_3d", + &mode, + &filter, + &table_channels, + &size1D, + &size2D, + &size3D, + &table)) { return NULL; } /* actually, it is trilinear */ if (filter != IMAGING_TRANSFORM_BILINEAR) { - PyErr_SetString(PyExc_ValueError, - "Only LINEAR filter is supported."); + PyErr_SetString(PyExc_ValueError, "Only LINEAR filter is supported."); return NULL; } if (1 > table_channels || table_channels > 4) { - PyErr_SetString(PyExc_ValueError, - "table_channels should be from 1 to 4"); + PyErr_SetString(PyExc_ValueError, "table_channels should be from 1 to 4"); return NULL; } - if (2 > size1D || size1D > 65 || - 2 > size2D || size2D > 65 || - 2 > size3D || size3D > 65 - ) { - PyErr_SetString(PyExc_ValueError, - "Table size in any dimension should be from 2 to 65"); + if (2 > size1D || size1D > 65 || 2 > size2D || size2D > 65 || 2 > size3D || + size3D > 65) { + PyErr_SetString( + PyExc_ValueError, "Table size in any dimension should be from 2 to 65"); return NULL; } - prepared_table = _prepare_lut_table( - table, table_channels * size1D * size2D * size3D); - if ( ! prepared_table) { + prepared_table = + _prepare_lut_table(table, table_channels * size1D * size2D * size3D); + if (!prepared_table) { return NULL; } imOut = ImagingNewDirty(mode, self->image->xsize, self->image->ysize); - if ( ! imOut) { + if (!imOut) { free(prepared_table); return NULL; } - if ( ! ImagingColorLUT3D_linear(imOut, self->image, - table_channels, size1D, size2D, size3D, - prepared_table)) { + if (!ImagingColorLUT3D_linear( + imOut, + self->image, + table_channels, + size1D, + size2D, + size3D, + prepared_table)) { free(prepared_table); ImagingDelete(imOut); return NULL; @@ -873,19 +872,20 @@ _color_lut_3d(ImagingObject* self, PyObject* args) return PyImagingNew(imOut); } -static PyObject* -_convert(ImagingObject* self, PyObject* args) -{ - char* mode; +static PyObject * +_convert(ImagingObject *self, PyObject *args) { + char *mode; int dither = 0; ImagingObject *paletteimage = NULL; - if (!PyArg_ParseTuple(args, "s|iO", &mode, &dither, &paletteimage)) + if (!PyArg_ParseTuple(args, "s|iO", &mode, &dither, &paletteimage)) { return NULL; + } if (paletteimage != NULL) { if (!PyImaging_Check(paletteimage)) { PyObject_Print((PyObject *)paletteimage, stderr, 0); - PyErr_SetString(PyExc_ValueError, "palette argument must be image with mode 'P'"); + PyErr_SetString( + PyExc_ValueError, "palette argument must be image with mode 'P'"); return NULL; } if (paletteimage->image->palette == NULL) { @@ -894,37 +894,49 @@ _convert(ImagingObject* self, PyObject* args) } } - return PyImagingNew(ImagingConvert(self->image, mode, paletteimage ? paletteimage->image->palette : NULL, dither)); + return PyImagingNew(ImagingConvert( + self->image, mode, paletteimage ? paletteimage->image->palette : NULL, dither)); } -static PyObject* -_convert2(ImagingObject* self, PyObject* args) -{ - ImagingObject* imagep1; - ImagingObject* imagep2; - if (!PyArg_ParseTuple(args, "O!O!", - &Imaging_Type, &imagep1, - &Imaging_Type, &imagep2)) +static PyObject * +_convert2(ImagingObject *self, PyObject *args) { + ImagingObject *imagep1; + ImagingObject *imagep2; + if (!PyArg_ParseTuple( + args, "O!O!", &Imaging_Type, &imagep1, &Imaging_Type, &imagep2)) { return NULL; + } - if (!ImagingConvert2(imagep1->image, imagep2->image)) + if (!ImagingConvert2(imagep1->image, imagep2->image)) { return NULL; + } Py_INCREF(Py_None); return Py_None; } -static PyObject* -_convert_matrix(ImagingObject* self, PyObject* args) -{ - char* mode; +static PyObject * +_convert_matrix(ImagingObject *self, PyObject *args) { + char *mode; float m[12]; - if (!PyArg_ParseTuple(args, "s(ffff)", &mode, m+0, m+1, m+2, m+3)) { + if (!PyArg_ParseTuple(args, "s(ffff)", &mode, m + 0, m + 1, m + 2, m + 3)) { PyErr_Clear(); - if (!PyArg_ParseTuple(args, "s(ffffffffffff)", &mode, - m+0, m+1, m+2, m+3, - m+4, m+5, m+6, m+7, - m+8, m+9, m+10, m+11)){ + if (!PyArg_ParseTuple( + args, + "s(ffffffffffff)", + &mode, + m + 0, + m + 1, + m + 2, + m + 3, + m + 4, + m + 5, + m + 6, + m + 7, + m + 8, + m + 9, + m + 10, + m + 11)) { return NULL; } } @@ -932,11 +944,10 @@ _convert_matrix(ImagingObject* self, PyObject* args) return PyImagingNew(ImagingConvertMatrix(self->image, mode, m)); } -static PyObject* -_convert_transparent(ImagingObject* self, PyObject* args) -{ - char* mode; - int r,g,b; +static PyObject * +_convert_transparent(ImagingObject *self, PyObject *args) { + char *mode; + int r, g, b; if (PyArg_ParseTuple(args, "s(iii)", &mode, &r, &g, &b)) { return PyImagingNew(ImagingConvertTransparent(self->image, mode, r, g, b)); } @@ -947,55 +958,56 @@ _convert_transparent(ImagingObject* self, PyObject* args) return NULL; } -static PyObject* -_copy(ImagingObject* self, PyObject* args) -{ - if (!PyArg_ParseTuple(args, "")) +static PyObject * +_copy(ImagingObject *self, PyObject *args) { + if (!PyArg_ParseTuple(args, "")) { return NULL; + } return PyImagingNew(ImagingCopy(self->image)); } -static PyObject* -_crop(ImagingObject* self, PyObject* args) -{ +static PyObject * +_crop(ImagingObject *self, PyObject *args) { int x0, y0, x1, y1; - if (!PyArg_ParseTuple(args, "(iiii)", &x0, &y0, &x1, &y1)) + if (!PyArg_ParseTuple(args, "(iiii)", &x0, &y0, &x1, &y1)) { return NULL; + } return PyImagingNew(ImagingCrop(self->image, x0, y0, x1, y1)); } -static PyObject* -_expand_image(ImagingObject* self, PyObject* args) -{ +static PyObject * +_expand_image(ImagingObject *self, PyObject *args) { int x, y; int mode = 0; - if (!PyArg_ParseTuple(args, "ii|i", &x, &y, &mode)) + if (!PyArg_ParseTuple(args, "ii|i", &x, &y, &mode)) { return NULL; + } return PyImagingNew(ImagingExpand(self->image, x, y, mode)); } -static PyObject* -_filter(ImagingObject* self, PyObject* args) -{ - PyObject* imOut; +static PyObject * +_filter(ImagingObject *self, PyObject *args) { + PyObject *imOut; Py_ssize_t kernelsize; - FLOAT32* kerneldata; + FLOAT32 *kerneldata; int xsize, ysize, i; float divisor, offset; - PyObject* kernel = NULL; - if (!PyArg_ParseTuple(args, "(ii)ffO", &xsize, &ysize, - &divisor, &offset, &kernel)) + PyObject *kernel = NULL; + if (!PyArg_ParseTuple( + args, "(ii)ffO", &xsize, &ysize, &divisor, &offset, &kernel)) { return NULL; + } /* get user-defined kernel */ kerneldata = getlist(kernel, &kernelsize, NULL, TYPE_FLOAT32); - if (!kerneldata) + if (!kerneldata) { return NULL; - if (kernelsize != (Py_ssize_t) xsize * (Py_ssize_t) ysize) { + } + if (kernelsize != (Py_ssize_t)xsize * (Py_ssize_t)ysize) { free(kerneldata); return ImagingError_ValueError("bad kernel size"); } @@ -1004,9 +1016,7 @@ _filter(ImagingObject* self, PyObject* args) kerneldata[i] /= divisor; } - imOut = PyImagingNew( - ImagingFilter(self->image, xsize, ysize, kerneldata, offset) - ); + imOut = PyImagingNew(ImagingFilter(self->image, xsize, ysize, kerneldata, offset)); free(kerneldata); @@ -1014,21 +1024,22 @@ _filter(ImagingObject* self, PyObject* args) } #ifdef WITH_UNSHARPMASK -static PyObject* -_gaussian_blur(ImagingObject* self, PyObject* args) -{ +static PyObject * +_gaussian_blur(ImagingObject *self, PyObject *args) { Imaging imIn; Imaging imOut; float radius = 0; int passes = 3; - if (!PyArg_ParseTuple(args, "f|i", &radius, &passes)) + if (!PyArg_ParseTuple(args, "f|i", &radius, &passes)) { return NULL; + } imIn = self->image; imOut = ImagingNewDirty(imIn->mode, imIn->xsize, imIn->ysize); - if (!imOut) + if (!imOut) { return NULL; + } if (!ImagingGaussianBlur(imOut, imIn, radius, passes)) { ImagingDelete(imOut); @@ -1039,18 +1050,18 @@ _gaussian_blur(ImagingObject* self, PyObject* args) } #endif -static PyObject* -_getpalette(ImagingObject* self, PyObject* args) -{ - PyObject* palette; +static PyObject * +_getpalette(ImagingObject *self, PyObject *args) { + PyObject *palette; int palettesize = 256; int bits; ImagingShuffler pack; - char* mode = "RGB"; - char* rawmode = "RGB"; - if (!PyArg_ParseTuple(args, "|ss", &mode, &rawmode)) + char *mode = "RGB"; + char *rawmode = "RGB"; + if (!PyArg_ParseTuple(args, "|ss", &mode, &rawmode)) { return NULL; + } if (!self->image->palette) { PyErr_SetString(PyExc_ValueError, no_palette); @@ -1064,85 +1075,78 @@ _getpalette(ImagingObject* self, PyObject* args) } palette = PyBytes_FromStringAndSize(NULL, palettesize * bits / 8); - if (!palette) + if (!palette) { return NULL; + } - pack((UINT8*) PyBytes_AsString(palette), - self->image->palette->palette, palettesize); + pack( + (UINT8 *)PyBytes_AsString(palette), self->image->palette->palette, palettesize); return palette; } -static PyObject* -_getpalettemode(ImagingObject* self, PyObject* args) -{ +static PyObject * +_getpalettemode(ImagingObject *self, PyObject *args) { if (!self->image->palette) { - PyErr_SetString(PyExc_ValueError, no_palette); - return NULL; + PyErr_SetString(PyExc_ValueError, no_palette); + return NULL; } return PyUnicode_FromString(self->image->palette->mode); } static inline int -_getxy(PyObject* xy, int* x, int *y) -{ - PyObject* value; +_getxy(PyObject *xy, int *x, int *y) { + PyObject *value; - if (!PyTuple_Check(xy) || PyTuple_GET_SIZE(xy) != 2) + if (!PyTuple_Check(xy) || PyTuple_GET_SIZE(xy) != 2) { goto badarg; + } value = PyTuple_GET_ITEM(xy, 0); - if (PyLong_Check(value)) + if (PyLong_Check(value)) { *x = PyLong_AS_LONG(value); - else if (PyFloat_Check(value)) - *x = (int) PyFloat_AS_DOUBLE(value); - else + } else if (PyFloat_Check(value)) { + *x = (int)PyFloat_AS_DOUBLE(value); + } else { goto badval; + } value = PyTuple_GET_ITEM(xy, 1); - if (PyLong_Check(value)) + if (PyLong_Check(value)) { *y = PyLong_AS_LONG(value); - else if (PyFloat_Check(value)) - *y = (int) PyFloat_AS_DOUBLE(value); - else + } else if (PyFloat_Check(value)) { + *y = (int)PyFloat_AS_DOUBLE(value); + } else { goto badval; + } return 0; - badarg: - PyErr_SetString( - PyExc_TypeError, - "argument must be sequence of length 2" - ); +badarg: + PyErr_SetString(PyExc_TypeError, "argument must be sequence of length 2"); return -1; - badval: - PyErr_SetString( - PyExc_TypeError, - "an integer is required" - ); +badval: + PyErr_SetString(PyExc_TypeError, "an integer is required"); return -1; } -static PyObject* -_getpixel(ImagingObject* self, PyObject* args) -{ - PyObject* xy; +static PyObject * +_getpixel(ImagingObject *self, PyObject *args) { + PyObject *xy; int x, y; if (PyTuple_GET_SIZE(args) != 1) { - PyErr_SetString( - PyExc_TypeError, - "argument 1 must be sequence of length 2" - ); + PyErr_SetString(PyExc_TypeError, "argument 1 must be sequence of length 2"); return NULL; } xy = PyTuple_GET_ITEM(args, 0); - if (_getxy(xy, &x, &y)) + if (_getxy(xy, &x, &y)) { return NULL; + } if (self->access == NULL) { Py_INCREF(Py_None); @@ -1158,35 +1162,37 @@ union hist_extrema { FLOAT32 f[2]; }; -static union hist_extrema* -parse_histogram_extremap(ImagingObject* self, PyObject* extremap, - union hist_extrema* ep) -{ +static union hist_extrema * +parse_histogram_extremap( + ImagingObject *self, PyObject *extremap, union hist_extrema *ep) { int i0, i1; double f0, f1; if (extremap) { switch (self->image->type) { - case IMAGING_TYPE_UINT8: - if (!PyArg_ParseTuple(extremap, "ii", &i0, &i1)) + case IMAGING_TYPE_UINT8: + if (!PyArg_ParseTuple(extremap, "ii", &i0, &i1)) { + return NULL; + } + ep->u[0] = CLIP8(i0); + ep->u[1] = CLIP8(i1); + break; + case IMAGING_TYPE_INT32: + if (!PyArg_ParseTuple(extremap, "ii", &i0, &i1)) { + return NULL; + } + ep->i[0] = i0; + ep->i[1] = i1; + break; + case IMAGING_TYPE_FLOAT32: + if (!PyArg_ParseTuple(extremap, "dd", &f0, &f1)) { + return NULL; + } + ep->f[0] = (FLOAT32)f0; + ep->f[1] = (FLOAT32)f1; + break; + default: return NULL; - ep->u[0] = CLIP8(i0); - ep->u[1] = CLIP8(i1); - break; - case IMAGING_TYPE_INT32: - if (!PyArg_ParseTuple(extremap, "ii", &i0, &i1)) - return NULL; - ep->i[0] = i0; - ep->i[1] = i1; - break; - case IMAGING_TYPE_FLOAT32: - if (!PyArg_ParseTuple(extremap, "dd", &f0, &f1)) - return NULL; - ep->f[0] = (FLOAT32) f0; - ep->f[1] = (FLOAT32) f1; - break; - default: - return NULL; } } else { return NULL; @@ -1194,31 +1200,32 @@ parse_histogram_extremap(ImagingObject* self, PyObject* extremap, return ep; } -static PyObject* -_histogram(ImagingObject* self, PyObject* args) -{ +static PyObject * +_histogram(ImagingObject *self, PyObject *args) { ImagingHistogram h; - PyObject* list; + PyObject *list; int i; union hist_extrema extrema; - union hist_extrema* ep; + union hist_extrema *ep; - PyObject* extremap = NULL; - ImagingObject* maskp = NULL; - if (!PyArg_ParseTuple(args, "|OO!", &extremap, &Imaging_Type, &maskp)) + PyObject *extremap = NULL; + ImagingObject *maskp = NULL; + if (!PyArg_ParseTuple(args, "|OO!", &extremap, &Imaging_Type, &maskp)) { return NULL; + } /* Using a var to avoid allocations. */ ep = parse_histogram_extremap(self, extremap, &extrema); h = ImagingGetHistogram(self->image, (maskp) ? maskp->image : NULL, ep); - if (!h) + if (!h) { return NULL; + } /* Build an integer list containing the histogram */ list = PyList_New(h->bands * 256); for (i = 0; i < h->bands * 256; i++) { - PyObject* item; + PyObject *item; item = PyLong_FromLong(h->histogram[i]); if (item == NULL) { Py_DECREF(list); @@ -1234,27 +1241,28 @@ _histogram(ImagingObject* self, PyObject* args) return list; } -static PyObject* -_entropy(ImagingObject* self, PyObject* args) -{ +static PyObject * +_entropy(ImagingObject *self, PyObject *args) { ImagingHistogram h; int idx, length; long sum; double entropy, fsum, p; union hist_extrema extrema; - union hist_extrema* ep; + union hist_extrema *ep; - PyObject* extremap = NULL; - ImagingObject* maskp = NULL; - if (!PyArg_ParseTuple(args, "|OO!", &extremap, &Imaging_Type, &maskp)) + PyObject *extremap = NULL; + ImagingObject *maskp = NULL; + if (!PyArg_ParseTuple(args, "|OO!", &extremap, &Imaging_Type, &maskp)) { return NULL; + } /* Using a local var to avoid allocations. */ ep = parse_histogram_extremap(self, extremap, &extrema); h = ImagingGetHistogram(self->image, (maskp) ? maskp->image : NULL, ep); - if (!h) + if (!h) { return NULL; + } /* Calculate the histogram entropy */ /* First, sum the histogram data */ @@ -1282,136 +1290,143 @@ _entropy(ImagingObject* self, PyObject* args) } #ifdef WITH_MODEFILTER -static PyObject* -_modefilter(ImagingObject* self, PyObject* args) -{ +static PyObject * +_modefilter(ImagingObject *self, PyObject *args) { int size; - if (!PyArg_ParseTuple(args, "i", &size)) + if (!PyArg_ParseTuple(args, "i", &size)) { return NULL; + } return PyImagingNew(ImagingModeFilter(self->image, size)); } #endif -static PyObject* -_offset(ImagingObject* self, PyObject* args) -{ +static PyObject * +_offset(ImagingObject *self, PyObject *args) { int xoffset, yoffset; - if (!PyArg_ParseTuple(args, "ii", &xoffset, &yoffset)) + if (!PyArg_ParseTuple(args, "ii", &xoffset, &yoffset)) { return NULL; + } return PyImagingNew(ImagingOffset(self->image, xoffset, yoffset)); } -static PyObject* -_paste(ImagingObject* self, PyObject* args) -{ +static PyObject * +_paste(ImagingObject *self, PyObject *args) { int status; char ink[4]; - PyObject* source; + PyObject *source; int x0, y0, x1, y1; - ImagingObject* maskp = NULL; - if (!PyArg_ParseTuple(args, "O(iiii)|O!", - &source, - &x0, &y0, &x1, &y1, - &Imaging_Type, &maskp)) - return NULL; - - if (PyImaging_Check(source)) - status = ImagingPaste( - self->image, PyImaging_AsImaging(source), - (maskp) ? maskp->image : NULL, - x0, y0, x1, y1 - ); - - else { - if (!getink(source, self->image, ink)) - return NULL; - status = ImagingFill2( - self->image, ink, - (maskp) ? maskp->image : NULL, - x0, y0, x1, y1 - ); + ImagingObject *maskp = NULL; + if (!PyArg_ParseTuple( + args, "O(iiii)|O!", &source, &x0, &y0, &x1, &y1, &Imaging_Type, &maskp)) { + return NULL; } - if (status < 0) + if (PyImaging_Check(source)) { + status = ImagingPaste( + self->image, + PyImaging_AsImaging(source), + (maskp) ? maskp->image : NULL, + x0, + y0, + x1, + y1); + + } else { + if (!getink(source, self->image, ink)) { + return NULL; + } + status = ImagingFill2( + self->image, ink, (maskp) ? maskp->image : NULL, x0, y0, x1, y1); + } + + if (status < 0) { return NULL; + } Py_INCREF(Py_None); return Py_None; } -static PyObject* -_point(ImagingObject* self, PyObject* args) -{ - static const char* wrong_number = "wrong number of lut entries"; +static PyObject * +_point(ImagingObject *self, PyObject *args) { + static const char *wrong_number = "wrong number of lut entries"; Py_ssize_t n; int i, bands; Imaging im; - PyObject* list; - char* mode; - if (!PyArg_ParseTuple(args, "Oz", &list, &mode)) - return NULL; + PyObject *list; + char *mode; + if (!PyArg_ParseTuple(args, "Oz", &list, &mode)) { + return NULL; + } if (mode && !strcmp(mode, "F")) { - FLOAT32* data; + FLOAT32 *data; /* map from 8-bit data to floating point */ n = 256; data = getlist(list, &n, wrong_number, TYPE_FLOAT32); - if (!data) + if (!data) { return NULL; - im = ImagingPoint(self->image, mode, (void*) data); + } + im = ImagingPoint(self->image, mode, (void *)data); free(data); } else if (!strcmp(self->image->mode, "I") && mode && !strcmp(mode, "L")) { - UINT8* data; + UINT8 *data; /* map from 16-bit subset of 32-bit data to 8-bit */ /* FIXME: support arbitrary number of entries (requires API change) */ n = 65536; data = getlist(list, &n, wrong_number, TYPE_UINT8); - if (!data) + if (!data) { return NULL; - im = ImagingPoint(self->image, mode, (void*) data); + } + im = ImagingPoint(self->image, mode, (void *)data); free(data); } else { - INT32* data; + INT32 *data; UINT8 lut[1024]; if (mode) { bands = getbands(mode); - if (bands < 0) + if (bands < 0) { return NULL; - } else + } + } else { bands = self->image->bands; + } /* map to integer data */ n = 256 * bands; data = getlist(list, &n, wrong_number, TYPE_INT32); - if (!data) + if (!data) { return NULL; + } - if (mode && !strcmp(mode, "I")) - im = ImagingPoint(self->image, mode, (void*) data); - else if (mode && bands > 1) { + if (mode && !strcmp(mode, "I")) { + im = ImagingPoint(self->image, mode, (void *)data); + } else if (mode && bands > 1) { for (i = 0; i < 256; i++) { - lut[i*4] = CLIP8(data[i]); - lut[i*4+1] = CLIP8(data[i+256]); - lut[i*4+2] = CLIP8(data[i+512]); - if (n > 768) - lut[i*4+3] = CLIP8(data[i+768]); + lut[i * 4] = CLIP8(data[i]); + lut[i * 4 + 1] = CLIP8(data[i + 256]); + lut[i * 4 + 2] = CLIP8(data[i + 512]); + if (n > 768) { + lut[i * 4 + 3] = CLIP8(data[i + 768]); + } } - im = ImagingPoint(self->image, mode, (void*) lut); + im = ImagingPoint(self->image, mode, (void *)lut); } else { /* map individual bands */ - for (i = 0; i < n; i++) + for (i = 0; i < n; i++) { lut[i] = CLIP8(data[i]); - im = ImagingPoint(self->image, mode, (void*) lut); + } + im = ImagingPoint(self->image, mode, (void *)lut); } free(data); } @@ -1419,32 +1434,32 @@ _point(ImagingObject* self, PyObject* args) return PyImagingNew(im); } -static PyObject* -_point_transform(ImagingObject* self, PyObject* args) -{ +static PyObject * +_point_transform(ImagingObject *self, PyObject *args) { double scale = 1.0; double offset = 0.0; - if (!PyArg_ParseTuple(args, "|dd", &scale, &offset)) - return NULL; + if (!PyArg_ParseTuple(args, "|dd", &scale, &offset)) { + return NULL; + } return PyImagingNew(ImagingPointTransform(self->image, scale, offset)); } -static PyObject* -_putdata(ImagingObject* self, PyObject* args) -{ +static PyObject * +_putdata(ImagingObject *self, PyObject *args) { Imaging image; // i & n are # pixels, require py_ssize_t. x can be as large as n. y, just because. Py_ssize_t n, i, x, y; - PyObject* data; - PyObject* seq = NULL; - PyObject* op; + PyObject *data; + PyObject *seq = NULL; + PyObject *op; double scale = 1.0; double offset = 0.0; - if (!PyArg_ParseTuple(args, "O|dd", &data, &scale, &offset)) + if (!PyArg_ParseTuple(args, "O|dd", &data, &scale, &offset)) { return NULL; + } if (!PySequence_Check(data)) { PyErr_SetString(PyExc_TypeError, must_be_sequence); @@ -1461,51 +1476,54 @@ _putdata(ImagingObject* self, PyObject* args) if (image->image8) { if (PyBytes_Check(data)) { - unsigned char* p; - p = (unsigned char*) PyBytes_AS_STRING(data); - if (scale == 1.0 && offset == 0.0) + unsigned char *p; + p = (unsigned char *)PyBytes_AS_STRING(data); + if (scale == 1.0 && offset == 0.0) { /* Plain string data */ for (i = y = 0; i < n; i += image->xsize, y++) { x = n - i; - if (x > (int) image->xsize) + if (x > (int)image->xsize) { x = image->xsize; - memcpy(image->image8[y], p+i, x); + } + memcpy(image->image8[y], p + i, x); } - else + } else { /* Scaled and clipped string data */ for (i = x = y = 0; i < n; i++) { - image->image8[y][x] = CLIP8((int) (p[i] * scale + offset)); - if (++x >= (int) image->xsize) + image->image8[y][x] = CLIP8((int)(p[i] * scale + offset)); + if (++x >= (int)image->xsize) { x = 0, y++; + } } + } } else { - seq = PySequence_Fast(data, must_be_sequence); - if (!seq) { - PyErr_SetString(PyExc_TypeError, must_be_sequence); - return NULL; - } - if (scale == 1.0 && offset == 0.0) { - /* Clipped data */ - for (i = x = y = 0; i < n; i++) { - op = PySequence_Fast_GET_ITEM(seq, i); - image->image8[y][x] = (UINT8) CLIP8(PyLong_AsLong(op)); - if (++x >= (int) image->xsize){ - x = 0, y++; - } - } + seq = PySequence_Fast(data, must_be_sequence); + if (!seq) { + PyErr_SetString(PyExc_TypeError, must_be_sequence); + return NULL; + } + if (scale == 1.0 && offset == 0.0) { + /* Clipped data */ + for (i = x = y = 0; i < n; i++) { + op = PySequence_Fast_GET_ITEM(seq, i); + image->image8[y][x] = (UINT8)CLIP8(PyLong_AsLong(op)); + if (++x >= (int)image->xsize) { + x = 0, y++; + } + } } else { - /* Scaled and clipped data */ - for (i = x = y = 0; i < n; i++) { - PyObject *op = PySequence_Fast_GET_ITEM(seq, i); - image->image8[y][x] = CLIP8( - (int) (PyFloat_AsDouble(op) * scale + offset)); - if (++x >= (int) image->xsize){ - x = 0, y++; - } - } - } - PyErr_Clear(); /* Avoid weird exceptions */ + /* Scaled and clipped data */ + for (i = x = y = 0; i < n; i++) { + PyObject *op = PySequence_Fast_GET_ITEM(seq, i); + image->image8[y][x] = + CLIP8((int)(PyFloat_AsDouble(op) * scale + offset)); + if (++x >= (int)image->xsize) { + x = 0, y++; + } + } + } + PyErr_Clear(); /* Avoid weird exceptions */ } } else { /* 32-bit images */ @@ -1515,50 +1533,50 @@ _putdata(ImagingObject* self, PyObject* args) return NULL; } switch (image->type) { - case IMAGING_TYPE_INT32: - for (i = x = y = 0; i < n; i++) { - op = PySequence_Fast_GET_ITEM(seq, i); - IMAGING_PIXEL_INT32(image, x, y) = - (INT32) (PyFloat_AsDouble(op) * scale + offset); - if (++x >= (int) image->xsize){ - x = 0, y++; + case IMAGING_TYPE_INT32: + for (i = x = y = 0; i < n; i++) { + op = PySequence_Fast_GET_ITEM(seq, i); + IMAGING_PIXEL_INT32(image, x, y) = + (INT32)(PyFloat_AsDouble(op) * scale + offset); + if (++x >= (int)image->xsize) { + x = 0, y++; + } } - } - PyErr_Clear(); /* Avoid weird exceptions */ - break; - case IMAGING_TYPE_FLOAT32: - for (i = x = y = 0; i < n; i++) { - op = PySequence_Fast_GET_ITEM(seq, i); - IMAGING_PIXEL_FLOAT32(image, x, y) = - (FLOAT32) (PyFloat_AsDouble(op) * scale + offset); - if (++x >= (int) image->xsize){ - x = 0, y++; + PyErr_Clear(); /* Avoid weird exceptions */ + break; + case IMAGING_TYPE_FLOAT32: + for (i = x = y = 0; i < n; i++) { + op = PySequence_Fast_GET_ITEM(seq, i); + IMAGING_PIXEL_FLOAT32(image, x, y) = + (FLOAT32)(PyFloat_AsDouble(op) * scale + offset); + if (++x >= (int)image->xsize) { + x = 0, y++; + } } - } - PyErr_Clear(); /* Avoid weird exceptions */ - break; - default: - for (i = x = y = 0; i < n; i++) { - union { - char ink[4]; - INT32 inkint; - } u; + PyErr_Clear(); /* Avoid weird exceptions */ + break; + default: + for (i = x = y = 0; i < n; i++) { + union { + char ink[4]; + INT32 inkint; + } u; - u.inkint = 0; + u.inkint = 0; - op = PySequence_Fast_GET_ITEM(seq, i); - if (!op || !getink(op, image, u.ink)) { - Py_DECREF(seq); - return NULL; + op = PySequence_Fast_GET_ITEM(seq, i); + if (!op || !getink(op, image, u.ink)) { + Py_DECREF(seq); + return NULL; + } + /* FIXME: what about scale and offset? */ + image->image32[y][x] = u.inkint; + if (++x >= (int)image->xsize) { + x = 0, y++; + } } - /* FIXME: what about scale and offset? */ - image->image32[y][x] = u.inkint; - if (++x >= (int) image->xsize){ - x = 0, y++; - } - } - PyErr_Clear(); /* Avoid weird exceptions */ - break; + PyErr_Clear(); /* Avoid weird exceptions */ + break; } } @@ -1570,37 +1588,35 @@ _putdata(ImagingObject* self, PyObject* args) #ifdef WITH_QUANTIZE -static PyObject* -_quantize(ImagingObject* self, PyObject* args) -{ +static PyObject * +_quantize(ImagingObject *self, PyObject *args) { int colours = 256; int method = 0; int kmeans = 0; - if (!PyArg_ParseTuple(args, "|iii", &colours, &method, &kmeans)) + if (!PyArg_ParseTuple(args, "|iii", &colours, &method, &kmeans)) { return NULL; + } if (!self->image->xsize || !self->image->ysize) { /* no content; return an empty image */ - return PyImagingNew( - ImagingNew("P", self->image->xsize, self->image->ysize) - ); + return PyImagingNew(ImagingNew("P", self->image->xsize, self->image->ysize)); } return PyImagingNew(ImagingQuantize(self->image, colours, method, kmeans)); } #endif -static PyObject* -_putpalette(ImagingObject* self, PyObject* args) -{ +static PyObject * +_putpalette(ImagingObject *self, PyObject *args) { ImagingShuffler unpack; int bits; - char* rawmode; - UINT8* palette; + char *rawmode; + UINT8 *palette; Py_ssize_t palettesize; - if (!PyArg_ParseTuple(args, "sy#", &rawmode, &palette, &palettesize)) + if (!PyArg_ParseTuple(args, "sy#", &rawmode, &palette, &palettesize)) { return NULL; + } if (strcmp(self->image->mode, "L") && strcmp(self->image->mode, "LA") && strcmp(self->image->mode, "P") && strcmp(self->image->mode, "PA")) { @@ -1614,7 +1630,7 @@ _putpalette(ImagingObject* self, PyObject* args) return NULL; } - if ( palettesize * 8 / bits > 256) { + if (palettesize * 8 / bits > 256) { PyErr_SetString(PyExc_ValueError, wrong_palette_size); return NULL; } @@ -1631,13 +1647,13 @@ _putpalette(ImagingObject* self, PyObject* args) return Py_None; } -static PyObject* -_putpalettealpha(ImagingObject* self, PyObject* args) -{ +static PyObject * +_putpalettealpha(ImagingObject *self, PyObject *args) { int index; int alpha = 0; - if (!PyArg_ParseTuple(args, "i|i", &index, &alpha)) + if (!PyArg_ParseTuple(args, "i|i", &index, &alpha)) { return NULL; + } if (!self->image->palette) { PyErr_SetString(PyExc_ValueError, no_palette); @@ -1650,50 +1666,50 @@ _putpalettealpha(ImagingObject* self, PyObject* args) } strcpy(self->image->palette->mode, "RGBA"); - self->image->palette->palette[index*4+3] = (UINT8) alpha; + self->image->palette->palette[index * 4 + 3] = (UINT8)alpha; Py_INCREF(Py_None); return Py_None; } -static PyObject* -_putpalettealphas(ImagingObject* self, PyObject* args) -{ +static PyObject * +_putpalettealphas(ImagingObject *self, PyObject *args) { int i; UINT8 *values; Py_ssize_t length; - if (!PyArg_ParseTuple(args, "y#", &values, &length)) + if (!PyArg_ParseTuple(args, "y#", &values, &length)) { return NULL; + } if (!self->image->palette) { PyErr_SetString(PyExc_ValueError, no_palette); return NULL; } - if (length > 256) { + if (length > 256) { PyErr_SetString(PyExc_ValueError, outside_palette); return NULL; } strcpy(self->image->palette->mode, "RGBA"); - for (i=0; iimage->palette->palette[i*4+3] = (UINT8) values[i]; + for (i = 0; i < length; i++) { + self->image->palette->palette[i * 4 + 3] = (UINT8)values[i]; } Py_INCREF(Py_None); return Py_None; } -static PyObject* -_putpixel(ImagingObject* self, PyObject* args) -{ +static PyObject * +_putpixel(ImagingObject *self, PyObject *args) { Imaging im; char ink[4]; int x, y; - PyObject* color; - if (!PyArg_ParseTuple(args, "(ii)O", &x, &y, &color)) + PyObject *color; + if (!PyArg_ParseTuple(args, "(ii)O", &x, &y, &color)) { return NULL; + } im = self->image; @@ -1709,31 +1725,32 @@ _putpixel(ImagingObject* self, PyObject* args) return NULL; } - if (!getink(color, im, ink)) + if (!getink(color, im, ink)) { return NULL; + } - if (self->access) + if (self->access) { self->access->put_pixel(im, x, y, ink); + } Py_INCREF(Py_None); return Py_None; } #ifdef WITH_RANKFILTER -static PyObject* -_rankfilter(ImagingObject* self, PyObject* args) -{ +static PyObject * +_rankfilter(ImagingObject *self, PyObject *args) { int size, rank; - if (!PyArg_ParseTuple(args, "ii", &size, &rank)) + if (!PyArg_ParseTuple(args, "ii", &size, &rank)) { return NULL; + } return PyImagingNew(ImagingRankFilter(self->image, size, rank)); } #endif -static PyObject* -_resize(ImagingObject* self, PyObject* args) -{ +static PyObject * +_resize(ImagingObject *self, PyObject *args) { Imaging imIn; Imaging imOut; @@ -1745,9 +1762,18 @@ _resize(ImagingObject* self, PyObject* args) box[2] = imIn->xsize; box[3] = imIn->ysize; - if (!PyArg_ParseTuple(args, "(ii)|i(ffff)", &xsize, &ysize, &filter, - &box[0], &box[1], &box[2], &box[3])) + if (!PyArg_ParseTuple( + args, + "(ii)|i(ffff)", + &xsize, + &ysize, + &filter, + &box[0], + &box[1], + &box[2], + &box[3])) { return NULL; + } if (xsize < 1 || ysize < 1) { return ImagingError_ValueError("height and width must be > 0"); @@ -1766,36 +1792,31 @@ _resize(ImagingObject* self, PyObject* args) } // If box's coordinates are int and box size matches requested size - if (box[0] - (int) box[0] == 0 && box[2] - box[0] == xsize - && box[1] - (int) box[1] == 0 && box[3] - box[1] == ysize) { + if (box[0] - (int)box[0] == 0 && box[2] - box[0] == xsize && + box[1] - (int)box[1] == 0 && box[3] - box[1] == ysize) { imOut = ImagingCrop(imIn, box[0], box[1], box[2], box[3]); - } - else if (filter == IMAGING_TRANSFORM_NEAREST) { + } else if (filter == IMAGING_TRANSFORM_NEAREST) { double a[6]; memset(a, 0, sizeof a); - a[0] = (double) (box[2] - box[0]) / xsize; - a[4] = (double) (box[3] - box[1]) / ysize; + a[0] = (double)(box[2] - box[0]) / xsize; + a[4] = (double)(box[3] - box[1]) / ysize; a[2] = box[0]; a[5] = box[1]; imOut = ImagingNewDirty(imIn->mode, xsize, ysize); imOut = ImagingTransform( - imOut, imIn, IMAGING_TRANSFORM_AFFINE, - 0, 0, xsize, ysize, - a, filter, 1); - } - else { + imOut, imIn, IMAGING_TRANSFORM_AFFINE, 0, 0, xsize, ysize, a, filter, 1); + } else { imOut = ImagingResample(imIn, xsize, ysize, filter, box); } return PyImagingNew(imOut); } -static PyObject* -_reduce(ImagingObject* self, PyObject* args) -{ +static PyObject * +_reduce(ImagingObject *self, PyObject *args) { Imaging imIn; Imaging imOut; @@ -1806,9 +1827,17 @@ _reduce(ImagingObject* self, PyObject* args) box[2] = imIn->xsize; box[3] = imIn->ysize; - if (!PyArg_ParseTuple(args, "(ii)|(iiii)", &xscale, &yscale, - &box[0], &box[1], &box[2], &box[3])) + if (!PyArg_ParseTuple( + args, + "(ii)|(iiii)", + &xscale, + &yscale, + &box[0], + &box[1], + &box[2], + &box[3])) { return NULL; + } if (xscale < 1 || yscale < 1) { return ImagingError_ValueError("scale must be > 0"); @@ -1838,21 +1867,20 @@ _reduce(ImagingObject* self, PyObject* args) return PyImagingNew(imOut); } - -#define IS_RGB(mode)\ +#define IS_RGB(mode) \ (!strcmp(mode, "RGB") || !strcmp(mode, "RGBA") || !strcmp(mode, "RGBX")) -static PyObject* -im_setmode(ImagingObject* self, PyObject* args) -{ +static PyObject * +im_setmode(ImagingObject *self, PyObject *args) { /* attempt to modify the mode of an image in place */ Imaging im; - char* mode; + char *mode; Py_ssize_t modelen; - if (!PyArg_ParseTuple(args, "s#:setmode", &mode, &modelen)) + if (!PyArg_ParseTuple(args, "s#:setmode", &mode, &modelen)) { return NULL; + } im = self->image; @@ -1864,172 +1892,187 @@ im_setmode(ImagingObject* self, PyObject* args) /* color to color */ strcpy(im->mode, mode); im->bands = modelen; - if (!strcmp(mode, "RGBA")) - (void) ImagingFillBand(im, 3, 255); + if (!strcmp(mode, "RGBA")) { + (void)ImagingFillBand(im, 3, 255); + } } else { /* trying doing an in-place conversion */ - if (!ImagingConvertInPlace(im, mode)) + if (!ImagingConvertInPlace(im, mode)) { return NULL; + } } - if (self->access) + if (self->access) { ImagingAccessDelete(im, self->access); + } self->access = ImagingAccessNew(im); Py_INCREF(Py_None); return Py_None; } - -static PyObject* -_transform2(ImagingObject* self, PyObject* args) -{ - static const char* wrong_number = "wrong number of matrix entries"; +static PyObject * +_transform2(ImagingObject *self, PyObject *args) { + static const char *wrong_number = "wrong number of matrix entries"; Imaging imOut; Py_ssize_t n; double *a; - ImagingObject* imagep; + ImagingObject *imagep; int x0, y0, x1, y1; int method; - PyObject* data; + PyObject *data; int filter = IMAGING_TRANSFORM_NEAREST; int fill = 1; - if (!PyArg_ParseTuple(args, "(iiii)O!iO|ii", - &x0, &y0, &x1, &y1, - &Imaging_Type, &imagep, - &method, &data, - &filter, &fill)) - return NULL; + if (!PyArg_ParseTuple( + args, + "(iiii)O!iO|ii", + &x0, + &y0, + &x1, + &y1, + &Imaging_Type, + &imagep, + &method, + &data, + &filter, + &fill)) { + return NULL; + } switch (method) { - case IMAGING_TRANSFORM_AFFINE: - n = 6; - break; - case IMAGING_TRANSFORM_PERSPECTIVE: - n = 8; - break; - case IMAGING_TRANSFORM_QUAD: - n = 8; - break; - default: - n = -1; /* force error */ + case IMAGING_TRANSFORM_AFFINE: + n = 6; + break; + case IMAGING_TRANSFORM_PERSPECTIVE: + n = 8; + break; + case IMAGING_TRANSFORM_QUAD: + n = 8; + break; + default: + n = -1; /* force error */ } a = getlist(data, &n, wrong_number, TYPE_DOUBLE); - if (!a) + if (!a) { return NULL; + } imOut = ImagingTransform( - self->image, imagep->image, method, - x0, y0, x1, y1, a, filter, fill); + self->image, imagep->image, method, x0, y0, x1, y1, a, filter, fill); free(a); - if (!imOut) + if (!imOut) { return NULL; + } Py_INCREF(Py_None); return Py_None; } -static PyObject* -_transpose(ImagingObject* self, PyObject* args) -{ +static PyObject * +_transpose(ImagingObject *self, PyObject *args) { Imaging imIn; Imaging imOut; int op; - if (!PyArg_ParseTuple(args, "i", &op)) - return NULL; + if (!PyArg_ParseTuple(args, "i", &op)) { + return NULL; + } imIn = self->image; switch (op) { - case 0: /* flip left right */ - case 1: /* flip top bottom */ - case 3: /* rotate 180 */ - imOut = ImagingNewDirty(imIn->mode, imIn->xsize, imIn->ysize); - break; - case 2: /* rotate 90 */ - case 4: /* rotate 270 */ - case 5: /* transpose */ - case 6: /* transverse */ - imOut = ImagingNewDirty(imIn->mode, imIn->ysize, imIn->xsize); - break; - default: - PyErr_SetString(PyExc_ValueError, "No such transpose operation"); - return NULL; + case 0: /* flip left right */ + case 1: /* flip top bottom */ + case 3: /* rotate 180 */ + imOut = ImagingNewDirty(imIn->mode, imIn->xsize, imIn->ysize); + break; + case 2: /* rotate 90 */ + case 4: /* rotate 270 */ + case 5: /* transpose */ + case 6: /* transverse */ + imOut = ImagingNewDirty(imIn->mode, imIn->ysize, imIn->xsize); + break; + default: + PyErr_SetString(PyExc_ValueError, "No such transpose operation"); + return NULL; } - if (imOut) + if (imOut) { switch (op) { - case 0: - (void) ImagingFlipLeftRight(imOut, imIn); - break; - case 1: - (void) ImagingFlipTopBottom(imOut, imIn); - break; - case 2: - (void) ImagingRotate90(imOut, imIn); - break; - case 3: - (void) ImagingRotate180(imOut, imIn); - break; - case 4: - (void) ImagingRotate270(imOut, imIn); - break; - case 5: - (void) ImagingTranspose(imOut, imIn); - break; - case 6: - (void) ImagingTransverse(imOut, imIn); - break; + case 0: + (void)ImagingFlipLeftRight(imOut, imIn); + break; + case 1: + (void)ImagingFlipTopBottom(imOut, imIn); + break; + case 2: + (void)ImagingRotate90(imOut, imIn); + break; + case 3: + (void)ImagingRotate180(imOut, imIn); + break; + case 4: + (void)ImagingRotate270(imOut, imIn); + break; + case 5: + (void)ImagingTranspose(imOut, imIn); + break; + case 6: + (void)ImagingTransverse(imOut, imIn); + break; } + } return PyImagingNew(imOut); } #ifdef WITH_UNSHARPMASK -static PyObject* -_unsharp_mask(ImagingObject* self, PyObject* args) -{ +static PyObject * +_unsharp_mask(ImagingObject *self, PyObject *args) { Imaging imIn; Imaging imOut; float radius; int percent, threshold; - if (!PyArg_ParseTuple(args, "fii", &radius, &percent, &threshold)) + if (!PyArg_ParseTuple(args, "fii", &radius, &percent, &threshold)) { return NULL; + } imIn = self->image; imOut = ImagingNewDirty(imIn->mode, imIn->xsize, imIn->ysize); - if (!imOut) + if (!imOut) { return NULL; + } - if (!ImagingUnsharpMask(imOut, imIn, radius, percent, threshold)) + if (!ImagingUnsharpMask(imOut, imIn, radius, percent, threshold)) { return NULL; + } return PyImagingNew(imOut); } #endif -static PyObject* -_box_blur(ImagingObject* self, PyObject* args) -{ +static PyObject * +_box_blur(ImagingObject *self, PyObject *args) { Imaging imIn; Imaging imOut; float radius; int n = 1; - if (!PyArg_ParseTuple(args, "f|i", &radius, &n)) + if (!PyArg_ParseTuple(args, "f|i", &radius, &n)) { return NULL; + } imIn = self->image; imOut = ImagingNewDirty(imIn->mode, imIn->xsize, imIn->ysize); - if (!imOut) + if (!imOut) { return NULL; + } if (!ImagingBoxBlur(imOut, imIn, radius, n)) { ImagingDelete(imOut); @@ -2041,15 +2084,13 @@ _box_blur(ImagingObject* self, PyObject* args) /* -------------------------------------------------------------------- */ -static PyObject* -_isblock(ImagingObject* self, PyObject* args) -{ +static PyObject * +_isblock(ImagingObject *self, PyObject *args) { return PyBool_FromLong(self->image->block != NULL); } -static PyObject* -_getbbox(ImagingObject* self, PyObject* args) -{ +static PyObject * +_getbbox(ImagingObject *self, PyObject *args) { int bbox[4]; if (!ImagingGetBBox(self->image, bbox)) { Py_INCREF(Py_None); @@ -2059,20 +2100,21 @@ _getbbox(ImagingObject* self, PyObject* args) return Py_BuildValue("iiii", bbox[0], bbox[1], bbox[2], bbox[3]); } -static PyObject* -_getcolors(ImagingObject* self, PyObject* args) -{ - ImagingColorItem* items; +static PyObject * +_getcolors(ImagingObject *self, PyObject *args) { + ImagingColorItem *items; int i, colors; - PyObject* out; + PyObject *out; int maxcolors = 256; - if (!PyArg_ParseTuple(args, "i:getcolors", &maxcolors)) + if (!PyArg_ParseTuple(args, "i:getcolors", &maxcolors)) { return NULL; + } items = ImagingGetColors(self->image, maxcolors, &colors); - if (!items) + if (!items) { return NULL; + } if (colors > maxcolors) { out = Py_None; @@ -2080,10 +2122,9 @@ _getcolors(ImagingObject* self, PyObject* args) } else { out = PyList_New(colors); for (i = 0; i < colors; i++) { - ImagingColorItem* v = &items[i]; - PyObject* item = Py_BuildValue( - "iN", v->count, getpixel(self->image, self->access, v->x, v->y) - ); + ImagingColorItem *v = &items[i]; + PyObject *item = Py_BuildValue( + "iN", v->count, getpixel(self->image, self->access, v->x, v->y)); PyList_SetItem(out, i, item); } } @@ -2093,9 +2134,8 @@ _getcolors(ImagingObject* self, PyObject* args) return out; } -static PyObject* -_getextrema(ImagingObject* self, PyObject* args) -{ +static PyObject * +_getextrema(ImagingObject *self, PyObject *args) { union { UINT8 u[2]; INT32 i[2]; @@ -2105,33 +2145,34 @@ _getextrema(ImagingObject* self, PyObject* args) int status; status = ImagingGetExtrema(self->image, &extrema); - if (status < 0) + if (status < 0) { return NULL; + } - if (status) + if (status) { switch (self->image->type) { - case IMAGING_TYPE_UINT8: - return Py_BuildValue("BB", extrema.u[0], extrema.u[1]); - case IMAGING_TYPE_INT32: - return Py_BuildValue("ii", extrema.i[0], extrema.i[1]); - case IMAGING_TYPE_FLOAT32: - return Py_BuildValue("dd", extrema.f[0], extrema.f[1]); - case IMAGING_TYPE_SPECIAL: - if (strcmp(self->image->mode, "I;16") == 0) { - return Py_BuildValue("HH", extrema.s[0], extrema.s[1]); - } + case IMAGING_TYPE_UINT8: + return Py_BuildValue("BB", extrema.u[0], extrema.u[1]); + case IMAGING_TYPE_INT32: + return Py_BuildValue("ii", extrema.i[0], extrema.i[1]); + case IMAGING_TYPE_FLOAT32: + return Py_BuildValue("dd", extrema.f[0], extrema.f[1]); + case IMAGING_TYPE_SPECIAL: + if (strcmp(self->image->mode, "I;16") == 0) { + return Py_BuildValue("HH", extrema.s[0], extrema.s[1]); + } } + } Py_INCREF(Py_None); return Py_None; } -static PyObject* -_getprojection(ImagingObject* self, PyObject* args) -{ - unsigned char* xprofile; - unsigned char* yprofile; - PyObject* result; +static PyObject * +_getprojection(ImagingObject *self, PyObject *args) { + unsigned char *xprofile; + unsigned char *yprofile; + PyObject *result; /* malloc check ok */ xprofile = malloc(self->image->xsize); @@ -2140,14 +2181,18 @@ _getprojection(ImagingObject* self, PyObject* args) if (xprofile == NULL || yprofile == NULL) { free(xprofile); free(yprofile); - return PyErr_NoMemory(); + return ImagingError_MemoryError(); } - ImagingGetProjection(self->image, (unsigned char *)xprofile, (unsigned char *)yprofile); + ImagingGetProjection( + self->image, (unsigned char *)xprofile, (unsigned char *)yprofile); - result = Py_BuildValue("y#y#", - xprofile, (Py_ssize_t)self->image->xsize, - yprofile, (Py_ssize_t)self->image->ysize); + result = Py_BuildValue( + "y#y#", + xprofile, + (Py_ssize_t)self->image->xsize, + yprofile, + (Py_ssize_t)self->image->ysize); free(xprofile); free(yprofile); @@ -2157,90 +2202,108 @@ _getprojection(ImagingObject* self, PyObject* args) /* -------------------------------------------------------------------- */ -static PyObject* -_getband(ImagingObject* self, PyObject* args) -{ +static PyObject * +_getband(ImagingObject *self, PyObject *args) { int band; - if (!PyArg_ParseTuple(args, "i", &band)) + if (!PyArg_ParseTuple(args, "i", &band)) { return NULL; + } return PyImagingNew(ImagingGetBand(self->image, band)); } -static PyObject* -_fillband(ImagingObject* self, PyObject* args) -{ +static PyObject * +_fillband(ImagingObject *self, PyObject *args) { int band; int color; - if (!PyArg_ParseTuple(args, "ii", &band, &color)) + if (!PyArg_ParseTuple(args, "ii", &band, &color)) { return NULL; + } - if (!ImagingFillBand(self->image, band, color)) + if (!ImagingFillBand(self->image, band, color)) { return NULL; + } Py_INCREF(Py_None); return Py_None; } -static PyObject* -_putband(ImagingObject* self, PyObject* args) -{ - ImagingObject* imagep; +static PyObject * +_putband(ImagingObject *self, PyObject *args) { + ImagingObject *imagep; int band; - if (!PyArg_ParseTuple(args, "O!i", - &Imaging_Type, &imagep, - &band)) + if (!PyArg_ParseTuple(args, "O!i", &Imaging_Type, &imagep, &band)) { return NULL; + } - if (!ImagingPutBand(self->image, imagep->image, band)) + if (!ImagingPutBand(self->image, imagep->image, band)) { return NULL; + } Py_INCREF(Py_None); return Py_None; } -static PyObject* -_merge(PyObject* self, PyObject* args) -{ - char* mode; +static PyObject * +_merge(PyObject *self, PyObject *args) { + char *mode; ImagingObject *band0 = NULL; ImagingObject *band1 = NULL; ImagingObject *band2 = NULL; ImagingObject *band3 = NULL; Imaging bands[4] = {NULL, NULL, NULL, NULL}; - if (!PyArg_ParseTuple(args, "sO!|O!O!O!", &mode, - &Imaging_Type, &band0, &Imaging_Type, &band1, - &Imaging_Type, &band2, &Imaging_Type, &band3)) + if (!PyArg_ParseTuple( + args, + "sO!|O!O!O!", + &mode, + &Imaging_Type, + &band0, + &Imaging_Type, + &band1, + &Imaging_Type, + &band2, + &Imaging_Type, + &band3)) { return NULL; + } - if (band0) bands[0] = band0->image; - if (band1) bands[1] = band1->image; - if (band2) bands[2] = band2->image; - if (band3) bands[3] = band3->image; + if (band0) { + bands[0] = band0->image; + } + if (band1) { + bands[1] = band1->image; + } + if (band2) { + bands[2] = band2->image; + } + if (band3) { + bands[3] = band3->image; + } return PyImagingNew(ImagingMerge(mode, bands)); } -static PyObject* -_split(ImagingObject* self, PyObject* args) -{ +static PyObject * +_split(ImagingObject *self, PyObject *args) { int fails = 0; Py_ssize_t i; - PyObject* list; - PyObject* imaging_object; + PyObject *list; + PyObject *imaging_object; Imaging bands[4] = {NULL, NULL, NULL, NULL}; - if ( ! ImagingSplit(self->image, bands)) + if (!ImagingSplit(self->image, bands)) { return NULL; + } list = PyTuple_New(self->image->bands); for (i = 0; i < self->image->bands; i++) { imaging_object = PyImagingNew(bands[i]); - if ( ! imaging_object) + if (!imaging_object) { fails += 1; + } PyTuple_SET_ITEM(list, i, imaging_object); } if (fails) { @@ -2254,179 +2317,204 @@ _split(ImagingObject* self, PyObject* args) #ifdef WITH_IMAGECHOPS -static PyObject* -_chop_invert(ImagingObject* self, PyObject* args) -{ +static PyObject * +_chop_invert(ImagingObject *self, PyObject *args) { return PyImagingNew(ImagingNegative(self->image)); } -static PyObject* -_chop_lighter(ImagingObject* self, PyObject* args) -{ - ImagingObject* imagep; +static PyObject * +_chop_lighter(ImagingObject *self, PyObject *args) { + ImagingObject *imagep; - if (!PyArg_ParseTuple(args, "O!", &Imaging_Type, &imagep)) + if (!PyArg_ParseTuple(args, "O!", &Imaging_Type, &imagep)) { return NULL; + } return PyImagingNew(ImagingChopLighter(self->image, imagep->image)); } -static PyObject* -_chop_darker(ImagingObject* self, PyObject* args) -{ - ImagingObject* imagep; +static PyObject * +_chop_darker(ImagingObject *self, PyObject *args) { + ImagingObject *imagep; - if (!PyArg_ParseTuple(args, "O!", &Imaging_Type, &imagep)) + if (!PyArg_ParseTuple(args, "O!", &Imaging_Type, &imagep)) { return NULL; + } return PyImagingNew(ImagingChopDarker(self->image, imagep->image)); } -static PyObject* -_chop_difference(ImagingObject* self, PyObject* args) -{ - ImagingObject* imagep; +static PyObject * +_chop_difference(ImagingObject *self, PyObject *args) { + ImagingObject *imagep; - if (!PyArg_ParseTuple(args, "O!", &Imaging_Type, &imagep)) + if (!PyArg_ParseTuple(args, "O!", &Imaging_Type, &imagep)) { return NULL; + } return PyImagingNew(ImagingChopDifference(self->image, imagep->image)); } -static PyObject* -_chop_multiply(ImagingObject* self, PyObject* args) -{ - ImagingObject* imagep; +static PyObject * +_chop_multiply(ImagingObject *self, PyObject *args) { + ImagingObject *imagep; - if (!PyArg_ParseTuple(args, "O!", &Imaging_Type, &imagep)) + if (!PyArg_ParseTuple(args, "O!", &Imaging_Type, &imagep)) { return NULL; + } return PyImagingNew(ImagingChopMultiply(self->image, imagep->image)); } -static PyObject* -_chop_screen(ImagingObject* self, PyObject* args) -{ - ImagingObject* imagep; +static PyObject * +_chop_screen(ImagingObject *self, PyObject *args) { + ImagingObject *imagep; - if (!PyArg_ParseTuple(args, "O!", &Imaging_Type, &imagep)) + if (!PyArg_ParseTuple(args, "O!", &Imaging_Type, &imagep)) { return NULL; + } return PyImagingNew(ImagingChopScreen(self->image, imagep->image)); } -static PyObject* -_chop_add(ImagingObject* self, PyObject* args) -{ - ImagingObject* imagep; +static PyObject * +_chop_add(ImagingObject *self, PyObject *args) { + ImagingObject *imagep; float scale; int offset; scale = 1.0; offset = 0; - if (!PyArg_ParseTuple(args, "O!|fi", &Imaging_Type, &imagep, - &scale, &offset)) + if (!PyArg_ParseTuple(args, "O!|fi", &Imaging_Type, &imagep, &scale, &offset)) { return NULL; + } - return PyImagingNew(ImagingChopAdd(self->image, imagep->image, - scale, offset)); + return PyImagingNew(ImagingChopAdd(self->image, imagep->image, scale, offset)); } -static PyObject* -_chop_subtract(ImagingObject* self, PyObject* args) -{ - ImagingObject* imagep; +static PyObject * +_chop_subtract(ImagingObject *self, PyObject *args) { + ImagingObject *imagep; float scale; int offset; scale = 1.0; offset = 0; - if (!PyArg_ParseTuple(args, "O!|fi", &Imaging_Type, &imagep, - &scale, &offset)) + if (!PyArg_ParseTuple(args, "O!|fi", &Imaging_Type, &imagep, &scale, &offset)) { return NULL; + } - return PyImagingNew(ImagingChopSubtract(self->image, imagep->image, - scale, offset)); + return PyImagingNew(ImagingChopSubtract(self->image, imagep->image, scale, offset)); } -static PyObject* -_chop_and(ImagingObject* self, PyObject* args) -{ - ImagingObject* imagep; +static PyObject * +_chop_and(ImagingObject *self, PyObject *args) { + ImagingObject *imagep; - if (!PyArg_ParseTuple(args, "O!", &Imaging_Type, &imagep)) + if (!PyArg_ParseTuple(args, "O!", &Imaging_Type, &imagep)) { return NULL; + } return PyImagingNew(ImagingChopAnd(self->image, imagep->image)); } -static PyObject* -_chop_or(ImagingObject* self, PyObject* args) -{ - ImagingObject* imagep; +static PyObject * +_chop_or(ImagingObject *self, PyObject *args) { + ImagingObject *imagep; - if (!PyArg_ParseTuple(args, "O!", &Imaging_Type, &imagep)) + if (!PyArg_ParseTuple(args, "O!", &Imaging_Type, &imagep)) { return NULL; + } return PyImagingNew(ImagingChopOr(self->image, imagep->image)); } -static PyObject* -_chop_xor(ImagingObject* self, PyObject* args) -{ - ImagingObject* imagep; +static PyObject * +_chop_xor(ImagingObject *self, PyObject *args) { + ImagingObject *imagep; - if (!PyArg_ParseTuple(args, "O!", &Imaging_Type, &imagep)) + if (!PyArg_ParseTuple(args, "O!", &Imaging_Type, &imagep)) { return NULL; + } return PyImagingNew(ImagingChopXor(self->image, imagep->image)); } -static PyObject* -_chop_add_modulo(ImagingObject* self, PyObject* args) -{ - ImagingObject* imagep; +static PyObject * +_chop_add_modulo(ImagingObject *self, PyObject *args) { + ImagingObject *imagep; - if (!PyArg_ParseTuple(args, "O!", &Imaging_Type, &imagep)) + if (!PyArg_ParseTuple(args, "O!", &Imaging_Type, &imagep)) { return NULL; + } return PyImagingNew(ImagingChopAddModulo(self->image, imagep->image)); } -static PyObject* -_chop_subtract_modulo(ImagingObject* self, PyObject* args) -{ - ImagingObject* imagep; +static PyObject * +_chop_subtract_modulo(ImagingObject *self, PyObject *args) { + ImagingObject *imagep; - if (!PyArg_ParseTuple(args, "O!", &Imaging_Type, &imagep)) + if (!PyArg_ParseTuple(args, "O!", &Imaging_Type, &imagep)) { return NULL; + } return PyImagingNew(ImagingChopSubtractModulo(self->image, imagep->image)); } -#endif +static PyObject * +_chop_soft_light(ImagingObject *self, PyObject *args) { + ImagingObject *imagep; + if (!PyArg_ParseTuple(args, "O!", &Imaging_Type, &imagep)) { + return NULL; + } + + return PyImagingNew(ImagingChopSoftLight(self->image, imagep->image)); +} + +static PyObject * +_chop_hard_light(ImagingObject *self, PyObject *args) { + ImagingObject *imagep; + + if (!PyArg_ParseTuple(args, "O!", &Imaging_Type, &imagep)) { + return NULL; + } + + return PyImagingNew(ImagingChopHardLight(self->image, imagep->image)); +} + +static PyObject * +_chop_overlay(ImagingObject *self, PyObject *args) { + ImagingObject *imagep; + + if (!PyArg_ParseTuple(args, "O!", &Imaging_Type, &imagep)) { + return NULL; + } + + return PyImagingNew(ImagingOverlay(self->image, imagep->image)); +} +#endif /* -------------------------------------------------------------------- */ #ifdef WITH_IMAGEDRAW -static PyObject* -_font_new(PyObject* self_, PyObject* args) -{ +static PyObject * +_font_new(PyObject *self_, PyObject *args) { ImagingFontObject *self; int i, y0, y1; - static const char* wrong_length = "descriptor table has wrong size"; + static const char *wrong_length = "descriptor table has wrong size"; - ImagingObject* imagep; - unsigned char* glyphdata; + ImagingObject *imagep; + unsigned char *glyphdata; Py_ssize_t glyphdata_length; - if (!PyArg_ParseTuple(args, "O!y#", - &Imaging_Type, &imagep, - &glyphdata, &glyphdata_length)) + if (!PyArg_ParseTuple( + args, "O!y#", &Imaging_Type, &imagep, &glyphdata, &glyphdata_length)) { return NULL; + } if (glyphdata_length != 256 * 20) { PyErr_SetString(PyExc_ValueError, wrong_length); @@ -2434,8 +2522,9 @@ _font_new(PyObject* self_, PyObject* args) } self = PyObject_New(ImagingFontObject, &ImagingFont_Type); - if (self == NULL) + if (self == NULL) { return NULL; + } /* glyph bitmap */ self->bitmap = imagep->image; @@ -2454,10 +2543,12 @@ _font_new(PyObject* self_, PyObject* args) self->glyphs[i].sy0 = S16(B16(glyphdata, 14)); self->glyphs[i].sx1 = S16(B16(glyphdata, 16)); self->glyphs[i].sy1 = S16(B16(glyphdata, 18)); - if (self->glyphs[i].dy0 < y0) + if (self->glyphs[i].dy0 < y0) { y0 = self->glyphs[i].dy0; - if (self->glyphs[i].dy1 > y1) + } + if (self->glyphs[i].dy1 > y1) { y1 = self->glyphs[i].dy1; + } glyphdata += 20; } @@ -2468,37 +2559,37 @@ _font_new(PyObject* self_, PyObject* args) Py_INCREF(imagep); self->ref = imagep; - return (PyObject*) self; + return (PyObject *)self; } static void -_font_dealloc(ImagingFontObject* self) -{ +_font_dealloc(ImagingFontObject *self) { Py_XDECREF(self->ref); PyObject_Del(self); } static inline int -textwidth(ImagingFontObject* self, const unsigned char* text) -{ +textwidth(ImagingFontObject *self, const unsigned char *text) { int xsize; - for (xsize = 0; *text; text++) + for (xsize = 0; *text; text++) { xsize += self->glyphs[*text].dx; + } return xsize; } -void _font_text_asBytes(PyObject* encoded_string, unsigned char** text){ +void +_font_text_asBytes(PyObject *encoded_string, unsigned char **text) { /* Allocates *text, returns a 'new reference'. Caller is required to free */ - PyObject* bytes = NULL; + PyObject *bytes = NULL; Py_ssize_t len = 0; char *buffer; *text = NULL; - if (PyUnicode_CheckExact(encoded_string)){ + if (PyUnicode_CheckExact(encoded_string)) { bytes = PyUnicode_AsLatin1String(encoded_string); if (!bytes) { return; @@ -2508,7 +2599,7 @@ void _font_text_asBytes(PyObject* encoded_string, unsigned char** text){ PyBytes_AsStringAndSize(encoded_string, &buffer, &len); } - *text = calloc(len+1,1); + *text = calloc(len + 1, 1); if (*text) { memcpy(*text, buffer, len); } else { @@ -2521,23 +2612,21 @@ void _font_text_asBytes(PyObject* encoded_string, unsigned char** text){ return; } - -static PyObject* -_font_getmask(ImagingFontObject* self, PyObject* args) -{ +static PyObject * +_font_getmask(ImagingFontObject *self, PyObject *args) { Imaging im; Imaging bitmap; int x, b; - int i=0; + int i = 0; int status; - Glyph* glyph; + Glyph *glyph; - PyObject* encoded_string; + PyObject *encoded_string; - unsigned char* text; - char* mode = ""; + unsigned char *text; + char *mode = ""; - if (!PyArg_ParseTuple(args, "O|s:getmask", &encoded_string, &mode)){ + if (!PyArg_ParseTuple(args, "O|s:getmask", &encoded_string, &mode)) { return NULL; } @@ -2549,50 +2638,53 @@ _font_getmask(ImagingFontObject* self, PyObject* args) im = ImagingNew(self->bitmap->mode, textwidth(self, text), self->ysize); if (!im) { free(text); - ImagingError_MemoryError(); - return NULL; + return ImagingError_MemoryError(); } b = 0; - (void) ImagingFill(im, &b); + (void)ImagingFill(im, &b); b = self->baseline; for (x = 0; text[i]; i++) { glyph = &self->glyphs[text[i]]; - bitmap = ImagingCrop( - self->bitmap, - glyph->sx0, glyph->sy0, glyph->sx1, glyph->sy1 - ); - if (!bitmap) + bitmap = + ImagingCrop(self->bitmap, glyph->sx0, glyph->sy0, glyph->sx1, glyph->sy1); + if (!bitmap) { goto failed; + } status = ImagingPaste( - im, bitmap, NULL, - glyph->dx0+x, glyph->dy0+b, glyph->dx1+x, glyph->dy1+b - ); + im, + bitmap, + NULL, + glyph->dx0 + x, + glyph->dy0 + b, + glyph->dx1 + x, + glyph->dy1 + b); ImagingDelete(bitmap); - if (status < 0) + if (status < 0) { goto failed; + } x = x + glyph->dx; b = b + glyph->dy; } free(text); return PyImagingNew(im); - failed: +failed: free(text); ImagingDelete(im); Py_RETURN_NONE; } -static PyObject* -_font_getsize(ImagingFontObject* self, PyObject* args) -{ - unsigned char* text; - PyObject* encoded_string; - PyObject* val; +static PyObject * +_font_getsize(ImagingFontObject *self, PyObject *args) { + unsigned char *text; + PyObject *encoded_string; + PyObject *val; - if (!PyArg_ParseTuple(args, "O:getsize", &encoded_string)) + if (!PyArg_ParseTuple(args, "O:getsize", &encoded_string)) { return NULL; + } _font_text_asBytes(encoded_string, &text); if (!text) { @@ -2612,19 +2704,20 @@ static struct PyMethodDef _font_methods[] = { /* -------------------------------------------------------------------- */ -static PyObject* -_draw_new(PyObject* self_, PyObject* args) -{ +static PyObject * +_draw_new(PyObject *self_, PyObject *args) { ImagingDrawObject *self; - ImagingObject* imagep; + ImagingObject *imagep; int blend = 0; - if (!PyArg_ParseTuple(args, "O!|i", &Imaging_Type, &imagep, &blend)) + if (!PyArg_ParseTuple(args, "O!|i", &Imaging_Type, &imagep, &blend)) { return NULL; + } self = PyObject_New(ImagingDrawObject, &ImagingDraw_Type); - if (self == NULL) + if (self == NULL) { return NULL; + } /* keep a reference to the image object */ Py_INCREF(imagep); @@ -2634,223 +2727,253 @@ _draw_new(PyObject* self_, PyObject* args) self->blend = blend; - return (PyObject*) self; + return (PyObject *)self; } static void -_draw_dealloc(ImagingDrawObject* self) -{ +_draw_dealloc(ImagingDrawObject *self) { Py_XDECREF(self->image); PyObject_Del(self); } -extern Py_ssize_t PyPath_Flatten(PyObject* data, double **xy); +extern Py_ssize_t +PyPath_Flatten(PyObject *data, double **xy); -static PyObject* -_draw_ink(ImagingDrawObject* self, PyObject* args) -{ +static PyObject * +_draw_ink(ImagingDrawObject *self, PyObject *args) { INT32 ink = 0; - PyObject* color; - if (!PyArg_ParseTuple(args, "O", &color)) + PyObject *color; + if (!PyArg_ParseTuple(args, "O", &color)) { return NULL; + } - if (!getink(color, self->image->image, (char*) &ink)) + if (!getink(color, self->image->image, (char *)&ink)) { return NULL; + } - return PyLong_FromLong((int) ink); + return PyLong_FromLong((int)ink); } -static PyObject* -_draw_arc(ImagingDrawObject* self, PyObject* args) -{ - double* xy; +static PyObject * +_draw_arc(ImagingDrawObject *self, PyObject *args) { + double *xy; Py_ssize_t n; - PyObject* data; + PyObject *data; int ink; int width = 0; float start, end; - int op = 0; - if (!PyArg_ParseTuple(args, "Offi|ii", &data, &start, &end, &ink, &width)) + if (!PyArg_ParseTuple(args, "Offi|i", &data, &start, &end, &ink, &width)) { return NULL; + } n = PyPath_Flatten(data, &xy); - if (n < 0) + if (n < 0) { return NULL; + } if (n != 2) { PyErr_SetString(PyExc_TypeError, must_be_two_coordinates); free(xy); return NULL; } - n = ImagingDrawArc(self->image->image, - (int) xy[0], (int) xy[1], - (int) xy[2], (int) xy[3], - start, end, &ink, width, op - ); + n = ImagingDrawArc( + self->image->image, + (int)xy[0], + (int)xy[1], + (int)xy[2], + (int)xy[3], + start, + end, + &ink, + width, + self->blend); free(xy); - if (n < 0) + if (n < 0) { return NULL; + } Py_INCREF(Py_None); return Py_None; } -static PyObject* -_draw_bitmap(ImagingDrawObject* self, PyObject* args) -{ +static PyObject * +_draw_bitmap(ImagingDrawObject *self, PyObject *args) { double *xy; Py_ssize_t n; PyObject *data; - ImagingObject* bitmap; + ImagingObject *bitmap; int ink; - if (!PyArg_ParseTuple(args, "OO!i", &data, &Imaging_Type, &bitmap, &ink)) + if (!PyArg_ParseTuple(args, "OO!i", &data, &Imaging_Type, &bitmap, &ink)) { return NULL; + } n = PyPath_Flatten(data, &xy); - if (n < 0) + if (n < 0) { return NULL; + } if (n != 1) { - PyErr_SetString(PyExc_TypeError, - "coordinate list must contain exactly 1 coordinate" - ); + PyErr_SetString( + PyExc_TypeError, "coordinate list must contain exactly 1 coordinate"); free(xy); return NULL; } n = ImagingDrawBitmap( - self->image->image, (int) xy[0], (int) xy[1], bitmap->image, - &ink, self->blend - ); + self->image->image, (int)xy[0], (int)xy[1], bitmap->image, &ink, self->blend); free(xy); - if (n < 0) + if (n < 0) { return NULL; + } Py_INCREF(Py_None); return Py_None; } -static PyObject* -_draw_chord(ImagingDrawObject* self, PyObject* args) -{ - double* xy; +static PyObject * +_draw_chord(ImagingDrawObject *self, PyObject *args) { + double *xy; Py_ssize_t n; - PyObject* data; + PyObject *data; int ink, fill; int width = 0; float start, end; - if (!PyArg_ParseTuple(args, "Offii|i", - &data, &start, &end, &ink, &fill, &width)) + if (!PyArg_ParseTuple(args, "Offii|i", &data, &start, &end, &ink, &fill, &width)) { return NULL; + } n = PyPath_Flatten(data, &xy); - if (n < 0) + if (n < 0) { return NULL; + } if (n != 2) { PyErr_SetString(PyExc_TypeError, must_be_two_coordinates); free(xy); return NULL; } - n = ImagingDrawChord(self->image->image, - (int) xy[0], (int) xy[1], - (int) xy[2], (int) xy[3], - start, end, &ink, fill, width, self->blend - ); + n = ImagingDrawChord( + self->image->image, + (int)xy[0], + (int)xy[1], + (int)xy[2], + (int)xy[3], + start, + end, + &ink, + fill, + width, + self->blend); free(xy); - if (n < 0) + if (n < 0) { return NULL; + } Py_INCREF(Py_None); return Py_None; } -static PyObject* -_draw_ellipse(ImagingDrawObject* self, PyObject* args) -{ - double* xy; +static PyObject * +_draw_ellipse(ImagingDrawObject *self, PyObject *args) { + double *xy; Py_ssize_t n; - PyObject* data; + PyObject *data; int ink; int fill = 0; int width = 0; - if (!PyArg_ParseTuple(args, "Oi|ii", &data, &ink, &fill, &width)) + if (!PyArg_ParseTuple(args, "Oi|ii", &data, &ink, &fill, &width)) { return NULL; + } n = PyPath_Flatten(data, &xy); - if (n < 0) + if (n < 0) { return NULL; + } if (n != 2) { PyErr_SetString(PyExc_TypeError, must_be_two_coordinates); free(xy); return NULL; } - n = ImagingDrawEllipse(self->image->image, - (int) xy[0], (int) xy[1], - (int) xy[2], (int) xy[3], - &ink, fill, width, self->blend - ); + n = ImagingDrawEllipse( + self->image->image, + (int)xy[0], + (int)xy[1], + (int)xy[2], + (int)xy[3], + &ink, + fill, + width, + self->blend); free(xy); - if (n < 0) + if (n < 0) { return NULL; + } Py_INCREF(Py_None); return Py_None; } -static PyObject* -_draw_lines(ImagingDrawObject* self, PyObject* args) -{ +static PyObject * +_draw_lines(ImagingDrawObject *self, PyObject *args) { double *xy; Py_ssize_t i, n; PyObject *data; int ink; int width = 0; - if (!PyArg_ParseTuple(args, "Oi|i", &data, &ink, &width)) + if (!PyArg_ParseTuple(args, "Oi|i", &data, &ink, &width)) { return NULL; + } n = PyPath_Flatten(data, &xy); - if (n < 0) + if (n < 0) { return NULL; + } if (width <= 1) { double *p = NULL; - for (i = 0; i < n-1; i++) { - p = &xy[i+i]; + for (i = 0; i < n - 1; i++) { + p = &xy[i + i]; if (ImagingDrawLine( self->image->image, - (int) p[0], (int) p[1], (int) p[2], (int) p[3], - &ink, self->blend) < 0) { + (int)p[0], + (int)p[1], + (int)p[2], + (int)p[3], + &ink, + self->blend) < 0) { free(xy); return NULL; } } - if (p) /* draw last point */ + if (p) { /* draw last point */ ImagingDrawPoint( - self->image->image, - (int) p[2], (int) p[3], - &ink, self->blend - ); + self->image->image, (int)p[2], (int)p[3], &ink, self->blend); + } } else { - for (i = 0; i < n-1; i++) { - double *p = &xy[i+i]; + for (i = 0; i < n - 1; i++) { + double *p = &xy[i + i]; if (ImagingDrawWideLine( self->image->image, - (int) p[0], (int) p[1], (int) p[2], (int) p[3], - &ink, width, self->blend) < 0) { + (int)p[0], + (int)p[1], + (int)p[2], + (int)p[3], + &ink, + width, + self->blend) < 0) { free(xy); return NULL; } @@ -2863,28 +2986,29 @@ _draw_lines(ImagingDrawObject* self, PyObject* args) return Py_None; } -static PyObject* -_draw_points(ImagingDrawObject* self, PyObject* args) -{ +static PyObject * +_draw_points(ImagingDrawObject *self, PyObject *args) { double *xy; Py_ssize_t i, n; PyObject *data; int ink; - if (!PyArg_ParseTuple(args, "Oi", &data, &ink)) - return NULL; - - n = PyPath_Flatten(data, &xy); - if (n < 0) - return NULL; - - for (i = 0; i < n; i++) { - double *p = &xy[i+i]; - if (ImagingDrawPoint(self->image->image, (int) p[0], (int) p[1], - &ink, self->blend) < 0) { - free(xy); + if (!PyArg_ParseTuple(args, "Oi", &data, &ink)) { return NULL; } + + n = PyPath_Flatten(data, &xy); + if (n < 0) { + return NULL; + } + + for (i = 0; i < n; i++) { + double *p = &xy[i + i]; + if (ImagingDrawPoint( + self->image->image, (int)p[0], (int)p[1], &ink, self->blend) < 0) { + free(xy); + return NULL; + } } free(xy); @@ -2893,21 +3017,22 @@ _draw_points(ImagingDrawObject* self, PyObject* args) return Py_None; } -#ifdef WITH_ARROW +#ifdef WITH_ARROW /* from outline.c */ -extern ImagingOutline PyOutline_AsOutline(PyObject* outline); +extern ImagingOutline +PyOutline_AsOutline(PyObject *outline); -static PyObject* -_draw_outline(ImagingDrawObject* self, PyObject* args) -{ +static PyObject * +_draw_outline(ImagingDrawObject *self, PyObject *args) { ImagingOutline outline; - PyObject* outline_; + PyObject *outline_; int ink; int fill = 0; - if (!PyArg_ParseTuple(args, "Oi|i", &outline_, &ink, &fill)) + if (!PyArg_ParseTuple(args, "Oi|i", &outline_, &ink, &fill)) { return NULL; + } outline = PyOutline_AsOutline(outline_); if (!outline) { @@ -2915,9 +3040,9 @@ _draw_outline(ImagingDrawObject* self, PyObject* args) return NULL; } - if (ImagingDrawOutline(self->image->image, outline, - &ink, fill, self->blend) < 0) - return NULL; + if (ImagingDrawOutline(self->image->image, outline, &ink, fill, self->blend) < 0) { + return NULL; + } Py_INCREF(Py_None); return Py_None; @@ -2925,79 +3050,91 @@ _draw_outline(ImagingDrawObject* self, PyObject* args) #endif -static PyObject* -_draw_pieslice(ImagingDrawObject* self, PyObject* args) -{ - double* xy; +static PyObject * +_draw_pieslice(ImagingDrawObject *self, PyObject *args) { + double *xy; Py_ssize_t n; - PyObject* data; + PyObject *data; int ink, fill; int width = 0; float start, end; - if (!PyArg_ParseTuple(args, "Offii|i", &data, &start, &end, &ink, &fill, &width)) + if (!PyArg_ParseTuple(args, "Offii|i", &data, &start, &end, &ink, &fill, &width)) { return NULL; + } n = PyPath_Flatten(data, &xy); - if (n < 0) + if (n < 0) { return NULL; + } if (n != 2) { PyErr_SetString(PyExc_TypeError, must_be_two_coordinates); free(xy); return NULL; } - n = ImagingDrawPieslice(self->image->image, - (int) xy[0], (int) xy[1], - (int) xy[2], (int) xy[3], - start, end, &ink, fill, width, self->blend - ); + n = ImagingDrawPieslice( + self->image->image, + (int)xy[0], + (int)xy[1], + (int)xy[2], + (int)xy[3], + start, + end, + &ink, + fill, + width, + self->blend); free(xy); - if (n < 0) + if (n < 0) { return NULL; + } Py_INCREF(Py_None); return Py_None; } -static PyObject* -_draw_polygon(ImagingDrawObject* self, PyObject* args) -{ +static PyObject * +_draw_polygon(ImagingDrawObject *self, PyObject *args) { double *xy; int *ixy; Py_ssize_t n, i; - PyObject* data; + PyObject *data; int ink; int fill = 0; - if (!PyArg_ParseTuple(args, "Oi|i", &data, &ink, &fill)) + if (!PyArg_ParseTuple(args, "Oi|i", &data, &ink, &fill)) { return NULL; + } n = PyPath_Flatten(data, &xy); - if (n < 0) + if (n < 0) { return NULL; + } if (n < 2) { - PyErr_SetString(PyExc_TypeError, - "coordinate list must contain at least 2 coordinates" - ); + PyErr_SetString( + PyExc_TypeError, "coordinate list must contain at least 2 coordinates"); free(xy); return NULL; } /* Copy list of vertices to array */ - ixy = (int*) calloc(n, 2 * sizeof(int)); + ixy = (int *)calloc(n, 2 * sizeof(int)); + if (ixy == NULL) { + free(xy); + return ImagingError_MemoryError(); + } for (i = 0; i < n; i++) { - ixy[i+i] = (int) xy[i+i]; - ixy[i+i+1] = (int) xy[i+i+1]; + ixy[i + i] = (int)xy[i + i]; + ixy[i + i + 1] = (int)xy[i + i + 1]; } free(xy); - if (ImagingDrawPolygon(self->image->image, n, ixy, - &ink, fill, self->blend) < 0) { + if (ImagingDrawPolygon(self->image->image, n, ixy, &ink, fill, self->blend) < 0) { free(ixy); return NULL; } @@ -3008,38 +3145,45 @@ _draw_polygon(ImagingDrawObject* self, PyObject* args) return Py_None; } -static PyObject* -_draw_rectangle(ImagingDrawObject* self, PyObject* args) -{ - double* xy; +static PyObject * +_draw_rectangle(ImagingDrawObject *self, PyObject *args) { + double *xy; Py_ssize_t n; - PyObject* data; + PyObject *data; int ink; int fill = 0; int width = 0; - if (!PyArg_ParseTuple(args, "Oi|ii", &data, &ink, &fill, &width)) + if (!PyArg_ParseTuple(args, "Oi|ii", &data, &ink, &fill, &width)) { return NULL; + } n = PyPath_Flatten(data, &xy); - if (n < 0) + if (n < 0) { return NULL; + } if (n != 2) { PyErr_SetString(PyExc_TypeError, must_be_two_coordinates); free(xy); return NULL; } - n = ImagingDrawRectangle(self->image->image, - (int) xy[0], (int) xy[1], - (int) xy[2], (int) xy[3], - &ink, fill, width, self->blend - ); + n = ImagingDrawRectangle( + self->image->image, + (int)xy[0], + (int)xy[1], + (int)xy[2], + (int)xy[3], + &ink, + fill, + width, + self->blend); free(xy); - if (n < 0) + if (n < 0) { return NULL; + } Py_INCREF(Py_None); return Py_None; @@ -3067,19 +3211,19 @@ static struct PyMethodDef _draw_methods[] = { #endif - -static PyObject* -pixel_access_new(ImagingObject* imagep, PyObject* args) -{ +static PyObject * +pixel_access_new(ImagingObject *imagep, PyObject *args) { PixelAccessObject *self; int readonly = 0; - if (!PyArg_ParseTuple(args, "|i", &readonly)) + if (!PyArg_ParseTuple(args, "|i", &readonly)) { return NULL; + } self = PyObject_New(PixelAccessObject, &PixelAccess_Type); - if (self == NULL) + if (self == NULL) { return NULL; + } /* keep a reference to the image object */ Py_INCREF(imagep); @@ -3087,40 +3231,39 @@ pixel_access_new(ImagingObject* imagep, PyObject* args) self->readonly = readonly; - return (PyObject*) self; + return (PyObject *)self; } static void -pixel_access_dealloc(PixelAccessObject* self) -{ +pixel_access_dealloc(PixelAccessObject *self) { Py_XDECREF(self->image); PyObject_Del(self); } static PyObject * -pixel_access_getitem(PixelAccessObject *self, PyObject *xy) -{ +pixel_access_getitem(PixelAccessObject *self, PyObject *xy) { int x, y; - if (_getxy(xy, &x, &y)) + if (_getxy(xy, &x, &y)) { return NULL; + } return getpixel(self->image->image, self->image->access, x, y); } static int -pixel_access_setitem(PixelAccessObject *self, PyObject *xy, PyObject *color) -{ +pixel_access_setitem(PixelAccessObject *self, PyObject *xy, PyObject *color) { Imaging im = self->image->image; char ink[4]; int x, y; if (self->readonly) { - (void) ImagingError_ValueError(readonly); + (void)ImagingError_ValueError(readonly); return -1; } - if (_getxy(xy, &x, &y)) + if (_getxy(xy, &x, &y)) { return -1; + } if (x < 0) { x = im->xsize + x; @@ -3134,11 +3277,13 @@ pixel_access_setitem(PixelAccessObject *self, PyObject *xy, PyObject *color) return -1; } - if (!color) /* FIXME: raise exception? */ + if (!color) { /* FIXME: raise exception? */ return 0; + } - if (!getink(color, im, ink)) + if (!getink(color, im, ink)) { return -1; + } self->image->access->put_pixel(im, x, y, ink); @@ -3151,43 +3296,52 @@ pixel_access_setitem(PixelAccessObject *self, PyObject *xy, PyObject *color) #ifdef WITH_EFFECTS -static PyObject* -_effect_mandelbrot(ImagingObject* self, PyObject* args) -{ +static PyObject * +_effect_mandelbrot(ImagingObject *self, PyObject *args) { int xsize = 512; int ysize = 512; double extent[4]; int quality = 100; - extent[0] = -3; extent[1] = -2.5; - extent[2] = 2; extent[3] = 2.5; + extent[0] = -3; + extent[1] = -2.5; + extent[2] = 2; + extent[3] = 2.5; - if (!PyArg_ParseTuple(args, "|(ii)(dddd)i", &xsize, &ysize, - &extent[0], &extent[1], &extent[2], &extent[3], - &quality)) - return NULL; + if (!PyArg_ParseTuple( + args, + "|(ii)(dddd)i", + &xsize, + &ysize, + &extent[0], + &extent[1], + &extent[2], + &extent[3], + &quality)) { + return NULL; + } return PyImagingNew(ImagingEffectMandelbrot(xsize, ysize, extent, quality)); } -static PyObject* -_effect_noise(ImagingObject* self, PyObject* args) -{ +static PyObject * +_effect_noise(ImagingObject *self, PyObject *args) { int xsize, ysize; float sigma = 128; - if (!PyArg_ParseTuple(args, "(ii)|f", &xsize, &ysize, &sigma)) + if (!PyArg_ParseTuple(args, "(ii)|f", &xsize, &ysize, &sigma)) { return NULL; + } return PyImagingNew(ImagingEffectNoise(xsize, ysize, sigma)); } -static PyObject* -_effect_spread(ImagingObject* self, PyObject* args) -{ +static PyObject * +_effect_spread(ImagingObject *self, PyObject *args) { int dist; - if (!PyArg_ParseTuple(args, "i", &dist)) + if (!PyArg_ParseTuple(args, "i", &dist)) { return NULL; + } return PyImagingNew(ImagingEffectSpread(self->image, dist)); } @@ -3198,29 +3352,33 @@ _effect_spread(ImagingObject* self, PyObject* args) /* UTILITIES */ /* -------------------------------------------------------------------- */ - -static PyObject* -_getcodecstatus(PyObject* self, PyObject* args) -{ +static PyObject * +_getcodecstatus(PyObject *self, PyObject *args) { int status; - char* msg; + char *msg; - if (!PyArg_ParseTuple(args, "i", &status)) + if (!PyArg_ParseTuple(args, "i", &status)) { return NULL; + } switch (status) { - case IMAGING_CODEC_OVERRUN: - msg = "buffer overrun"; break; - case IMAGING_CODEC_BROKEN: - msg = "broken data stream"; break; - case IMAGING_CODEC_UNKNOWN: - msg = "unrecognized data stream contents"; break; - case IMAGING_CODEC_CONFIG: - msg = "codec configuration error"; break; - case IMAGING_CODEC_MEMORY: - msg = "out of memory"; break; - default: - Py_RETURN_NONE; + case IMAGING_CODEC_OVERRUN: + msg = "buffer overrun"; + break; + case IMAGING_CODEC_BROKEN: + msg = "broken data stream"; + break; + case IMAGING_CODEC_UNKNOWN: + msg = "unrecognized data stream contents"; + break; + case IMAGING_CODEC_CONFIG: + msg = "codec configuration error"; + break; + case IMAGING_CODEC_MEMORY: + msg = "out of memory"; + break; + default: + Py_RETURN_NONE; } return PyUnicode_FromString(msg); @@ -3230,23 +3388,22 @@ _getcodecstatus(PyObject* self, PyObject* args) /* DEBUGGING HELPERS */ /* -------------------------------------------------------------------- */ +static PyObject * +_save_ppm(ImagingObject *self, PyObject *args) { + char *filename; -static PyObject* -_save_ppm(ImagingObject* self, PyObject* args) -{ - char* filename; - - if (!PyArg_ParseTuple(args, "s", &filename)) + if (!PyArg_ParseTuple(args, "s", &filename)) { return NULL; + } - if (!ImagingSavePPM(self->image, filename)) + if (!ImagingSavePPM(self->image, filename)) { return NULL; + } Py_INCREF(Py_None); return Py_None; } - /* -------------------------------------------------------------------- */ /* methods */ @@ -3325,6 +3482,10 @@ static struct PyMethodDef methods[] = { {"chop_and", (PyCFunction)_chop_and, 1}, {"chop_or", (PyCFunction)_chop_or, 1}, {"chop_xor", (PyCFunction)_chop_xor, 1}, + {"chop_soft_light", (PyCFunction)_chop_soft_light, 1}, + {"chop_hard_light", (PyCFunction)_chop_hard_light, 1}, + {"chop_overlay", (PyCFunction)_chop_overlay, 1}, + #endif #ifdef WITH_UNSHARPMASK @@ -3348,47 +3509,43 @@ static struct PyMethodDef methods[] = { {NULL, NULL} /* sentinel */ }; - /* attributes */ -static PyObject* -_getattr_mode(ImagingObject* self, void* closure) -{ +static PyObject * +_getattr_mode(ImagingObject *self, void *closure) { return PyUnicode_FromString(self->image->mode); } -static PyObject* -_getattr_size(ImagingObject* self, void* closure) -{ +static PyObject * +_getattr_size(ImagingObject *self, void *closure) { return Py_BuildValue("ii", self->image->xsize, self->image->ysize); } -static PyObject* -_getattr_bands(ImagingObject* self, void* closure) -{ +static PyObject * +_getattr_bands(ImagingObject *self, void *closure) { return PyLong_FromLong(self->image->bands); } -static PyObject* -_getattr_id(ImagingObject* self, void* closure) -{ - return PyLong_FromSsize_t((Py_ssize_t) self->image); +static PyObject * +_getattr_id(ImagingObject *self, void *closure) { + return PyLong_FromSsize_t((Py_ssize_t)self->image); } -static PyObject* -_getattr_ptr(ImagingObject* self, void* closure) -{ +static PyObject * +_getattr_ptr(ImagingObject *self, void *closure) { return PyCapsule_New(self->image, IMAGING_MAGIC, NULL); } -static PyObject* -_getattr_unsafe_ptrs(ImagingObject* self, void* closure) -{ - return Py_BuildValue("(sn)(sn)(sn)", - "image8", self->image->image8, - "image32", self->image->image32, - "image", self->image->image - ); +static PyObject * +_getattr_unsafe_ptrs(ImagingObject *self, void *closure) { + return Py_BuildValue( + "(sn)(sn)(sn)", + "image8", + self->image->image8, + "image32", + self->image->image32, + "image", + self->image->image); }; static PyObject* @@ -3398,220 +3555,213 @@ _getattr_readonly(ImagingObject* self, void* closure) } static struct PyGetSetDef getsetters[] = { - { "mode", (getter) _getattr_mode }, - { "size", (getter) _getattr_size }, - { "bands", (getter) _getattr_bands }, - { "id", (getter) _getattr_id }, - { "ptr", (getter) _getattr_ptr }, - { "unsafe_ptrs", (getter) _getattr_unsafe_ptrs }, - { "readonly", (getter) _getattr_readonly }, - { NULL } -}; + {"mode", (getter)_getattr_mode}, + {"size", (getter)_getattr_size}, + {"bands", (getter)_getattr_bands}, + {"id", (getter)_getattr_id}, + {"ptr", (getter)_getattr_ptr}, + {"unsafe_ptrs", (getter)_getattr_unsafe_ptrs}, + {"readonly", (getter) _getattr_readonly}, + {NULL}}; /* basic sequence semantics */ static Py_ssize_t -image_length(ImagingObject *self) -{ +image_length(ImagingObject *self) { Imaging im = self->image; - return (Py_ssize_t) im->xsize * im->ysize; + return (Py_ssize_t)im->xsize * im->ysize; } static PyObject * -image_item(ImagingObject *self, Py_ssize_t i) -{ +image_item(ImagingObject *self, Py_ssize_t i) { int x, y; Imaging im = self->image; if (im->xsize > 0) { x = i % im->xsize; y = i / im->xsize; - } else + } else { x = y = 0; /* leave it to getpixel to raise an exception */ + } return getpixel(im, self->access, x, y); } static PySequenceMethods image_as_sequence = { - (lenfunc) image_length, /*sq_length*/ - (binaryfunc) NULL, /*sq_concat*/ - (ssizeargfunc) NULL, /*sq_repeat*/ - (ssizeargfunc) image_item, /*sq_item*/ - (ssizessizeargfunc) NULL, /*sq_slice*/ - (ssizeobjargproc) NULL, /*sq_ass_item*/ - (ssizessizeobjargproc) NULL, /*sq_ass_slice*/ + (lenfunc)image_length, /*sq_length*/ + (binaryfunc)NULL, /*sq_concat*/ + (ssizeargfunc)NULL, /*sq_repeat*/ + (ssizeargfunc)image_item, /*sq_item*/ + (ssizessizeargfunc)NULL, /*sq_slice*/ + (ssizeobjargproc)NULL, /*sq_ass_item*/ + (ssizessizeobjargproc)NULL, /*sq_ass_slice*/ }; - /* type description */ static PyTypeObject Imaging_Type = { - PyVarObject_HEAD_INIT(NULL, 0) - "ImagingCore", /*tp_name*/ - sizeof(ImagingObject), /*tp_size*/ - 0, /*tp_itemsize*/ + PyVarObject_HEAD_INIT(NULL, 0) "ImagingCore", /*tp_name*/ + sizeof(ImagingObject), /*tp_size*/ + 0, /*tp_itemsize*/ /* methods */ - (destructor)_dealloc, /*tp_dealloc*/ - 0, /*tp_print*/ - 0, /*tp_getattr*/ - 0, /*tp_setattr*/ - 0, /*tp_compare*/ - 0, /*tp_repr*/ - 0, /*tp_as_number */ - &image_as_sequence, /*tp_as_sequence */ - 0, /*tp_as_mapping */ - 0, /*tp_hash*/ - 0, /*tp_call*/ - 0, /*tp_str*/ - 0, /*tp_getattro*/ - 0, /*tp_setattro*/ - 0, /*tp_as_buffer*/ - Py_TPFLAGS_DEFAULT, /*tp_flags*/ - 0, /*tp_doc*/ - 0, /*tp_traverse*/ - 0, /*tp_clear*/ - 0, /*tp_richcompare*/ - 0, /*tp_weaklistoffset*/ - 0, /*tp_iter*/ - 0, /*tp_iternext*/ - methods, /*tp_methods*/ - 0, /*tp_members*/ - getsetters, /*tp_getset*/ + (destructor)_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number */ + &image_as_sequence, /*tp_as_sequence */ + 0, /*tp_as_mapping */ + 0, /*tp_hash*/ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT, /*tp_flags*/ + 0, /*tp_doc*/ + 0, /*tp_traverse*/ + 0, /*tp_clear*/ + 0, /*tp_richcompare*/ + 0, /*tp_weaklistoffset*/ + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + methods, /*tp_methods*/ + 0, /*tp_members*/ + getsetters, /*tp_getset*/ }; #ifdef WITH_IMAGEDRAW static PyTypeObject ImagingFont_Type = { - PyVarObject_HEAD_INIT(NULL, 0) - "ImagingFont", /*tp_name*/ - sizeof(ImagingFontObject), /*tp_size*/ - 0, /*tp_itemsize*/ + PyVarObject_HEAD_INIT(NULL, 0) "ImagingFont", /*tp_name*/ + sizeof(ImagingFontObject), /*tp_size*/ + 0, /*tp_itemsize*/ /* methods */ - (destructor)_font_dealloc, /*tp_dealloc*/ - 0, /*tp_print*/ - 0, /*tp_getattr*/ - 0, /*tp_setattr*/ - 0, /*tp_compare*/ - 0, /*tp_repr*/ - 0, /*tp_as_number */ - 0, /*tp_as_sequence */ - 0, /*tp_as_mapping */ - 0, /*tp_hash*/ - 0, /*tp_call*/ - 0, /*tp_str*/ - 0, /*tp_getattro*/ - 0, /*tp_setattro*/ - 0, /*tp_as_buffer*/ - Py_TPFLAGS_DEFAULT, /*tp_flags*/ - 0, /*tp_doc*/ - 0, /*tp_traverse*/ - 0, /*tp_clear*/ - 0, /*tp_richcompare*/ - 0, /*tp_weaklistoffset*/ - 0, /*tp_iter*/ - 0, /*tp_iternext*/ - _font_methods, /*tp_methods*/ - 0, /*tp_members*/ - 0, /*tp_getset*/ + (destructor)_font_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number */ + 0, /*tp_as_sequence */ + 0, /*tp_as_mapping */ + 0, /*tp_hash*/ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT, /*tp_flags*/ + 0, /*tp_doc*/ + 0, /*tp_traverse*/ + 0, /*tp_clear*/ + 0, /*tp_richcompare*/ + 0, /*tp_weaklistoffset*/ + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + _font_methods, /*tp_methods*/ + 0, /*tp_members*/ + 0, /*tp_getset*/ }; static PyTypeObject ImagingDraw_Type = { - PyVarObject_HEAD_INIT(NULL, 0) - "ImagingDraw", /*tp_name*/ - sizeof(ImagingDrawObject), /*tp_size*/ - 0, /*tp_itemsize*/ + PyVarObject_HEAD_INIT(NULL, 0) "ImagingDraw", /*tp_name*/ + sizeof(ImagingDrawObject), /*tp_size*/ + 0, /*tp_itemsize*/ /* methods */ - (destructor)_draw_dealloc, /*tp_dealloc*/ - 0, /*tp_print*/ - 0, /*tp_getattr*/ - 0, /*tp_setattr*/ - 0, /*tp_compare*/ - 0, /*tp_repr*/ - 0, /*tp_as_number */ - 0, /*tp_as_sequence */ - 0, /*tp_as_mapping */ - 0, /*tp_hash*/ - 0, /*tp_call*/ - 0, /*tp_str*/ - 0, /*tp_getattro*/ - 0, /*tp_setattro*/ - 0, /*tp_as_buffer*/ - Py_TPFLAGS_DEFAULT, /*tp_flags*/ - 0, /*tp_doc*/ - 0, /*tp_traverse*/ - 0, /*tp_clear*/ - 0, /*tp_richcompare*/ - 0, /*tp_weaklistoffset*/ - 0, /*tp_iter*/ - 0, /*tp_iternext*/ - _draw_methods, /*tp_methods*/ - 0, /*tp_members*/ - 0, /*tp_getset*/ + (destructor)_draw_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number */ + 0, /*tp_as_sequence */ + 0, /*tp_as_mapping */ + 0, /*tp_hash*/ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT, /*tp_flags*/ + 0, /*tp_doc*/ + 0, /*tp_traverse*/ + 0, /*tp_clear*/ + 0, /*tp_richcompare*/ + 0, /*tp_weaklistoffset*/ + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + _draw_methods, /*tp_methods*/ + 0, /*tp_members*/ + 0, /*tp_getset*/ }; #endif static PyMappingMethods pixel_access_as_mapping = { - (lenfunc) NULL, /*mp_length*/ - (binaryfunc) pixel_access_getitem, /*mp_subscript*/ - (objobjargproc) pixel_access_setitem, /*mp_ass_subscript*/ + (lenfunc)NULL, /*mp_length*/ + (binaryfunc)pixel_access_getitem, /*mp_subscript*/ + (objobjargproc)pixel_access_setitem, /*mp_ass_subscript*/ }; /* type description */ static PyTypeObject PixelAccess_Type = { - PyVarObject_HEAD_INIT(NULL, 0) - "PixelAccess", sizeof(PixelAccessObject), 0, + PyVarObject_HEAD_INIT(NULL, 0) "PixelAccess", + sizeof(PixelAccessObject), + 0, /* methods */ (destructor)pixel_access_dealloc, /*tp_dealloc*/ - 0, /*tp_print*/ - 0, /*tp_getattr*/ - 0, /*tp_setattr*/ - 0, /*tp_compare*/ - 0, /*tp_repr*/ - 0, /*tp_as_number */ - 0, /*tp_as_sequence */ - &pixel_access_as_mapping, /*tp_as_mapping */ - 0 /*tp_hash*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number */ + 0, /*tp_as_sequence */ + &pixel_access_as_mapping, /*tp_as_mapping */ + 0 /*tp_hash*/ }; /* -------------------------------------------------------------------- */ -static PyObject* -_get_stats(PyObject* self, PyObject* args) -{ - PyObject* d; +static PyObject * +_get_stats(PyObject *self, PyObject *args) { + PyObject *d; ImagingMemoryArena arena = &ImagingDefaultArena; - if (!PyArg_ParseTuple(args, ":get_stats")) + if (!PyArg_ParseTuple(args, ":get_stats")) { return NULL; + } d = PyDict_New(); - if ( ! d) + if (!d) { return NULL; - PyDict_SetItemString(d, "new_count", - PyLong_FromLong(arena->stats_new_count)); - PyDict_SetItemString(d, "allocated_blocks", - PyLong_FromLong(arena->stats_allocated_blocks)); - PyDict_SetItemString(d, "reused_blocks", - PyLong_FromLong(arena->stats_reused_blocks)); - PyDict_SetItemString(d, "reallocated_blocks", - PyLong_FromLong(arena->stats_reallocated_blocks)); - PyDict_SetItemString(d, "freed_blocks", - PyLong_FromLong(arena->stats_freed_blocks)); - PyDict_SetItemString(d, "blocks_cached", - PyLong_FromLong(arena->blocks_cached)); + } + PyDict_SetItemString(d, "new_count", PyLong_FromLong(arena->stats_new_count)); + PyDict_SetItemString( + d, "allocated_blocks", PyLong_FromLong(arena->stats_allocated_blocks)); + PyDict_SetItemString( + d, "reused_blocks", PyLong_FromLong(arena->stats_reused_blocks)); + PyDict_SetItemString( + d, "reallocated_blocks", PyLong_FromLong(arena->stats_reallocated_blocks)); + PyDict_SetItemString(d, "freed_blocks", PyLong_FromLong(arena->stats_freed_blocks)); + PyDict_SetItemString(d, "blocks_cached", PyLong_FromLong(arena->blocks_cached)); return d; } -static PyObject* -_reset_stats(PyObject* self, PyObject* args) -{ +static PyObject * +_reset_stats(PyObject *self, PyObject *args) { ImagingMemoryArena arena = &ImagingDefaultArena; - if (!PyArg_ParseTuple(args, ":reset_stats")) + if (!PyArg_ParseTuple(args, ":reset_stats")) { return NULL; + } arena->stats_new_count = 0; arena->stats_allocated_blocks = 0; @@ -3623,39 +3773,39 @@ _reset_stats(PyObject* self, PyObject* args) return Py_None; } -static PyObject* -_get_alignment(PyObject* self, PyObject* args) -{ - if (!PyArg_ParseTuple(args, ":get_alignment")) +static PyObject * +_get_alignment(PyObject *self, PyObject *args) { + if (!PyArg_ParseTuple(args, ":get_alignment")) { return NULL; + } return PyLong_FromLong(ImagingDefaultArena.alignment); } -static PyObject* -_get_block_size(PyObject* self, PyObject* args) -{ - if (!PyArg_ParseTuple(args, ":get_block_size")) +static PyObject * +_get_block_size(PyObject *self, PyObject *args) { + if (!PyArg_ParseTuple(args, ":get_block_size")) { return NULL; + } return PyLong_FromLong(ImagingDefaultArena.block_size); } -static PyObject* -_get_blocks_max(PyObject* self, PyObject* args) -{ - if (!PyArg_ParseTuple(args, ":get_blocks_max")) +static PyObject * +_get_blocks_max(PyObject *self, PyObject *args) { + if (!PyArg_ParseTuple(args, ":get_blocks_max")) { return NULL; + } return PyLong_FromLong(ImagingDefaultArena.blocks_max); } -static PyObject* -_set_alignment(PyObject* self, PyObject* args) -{ +static PyObject * +_set_alignment(PyObject *self, PyObject *args) { int alignment; - if (!PyArg_ParseTuple(args, "i:set_alignment", &alignment)) + if (!PyArg_ParseTuple(args, "i:set_alignment", &alignment)) { return NULL; + } if (alignment < 1 || alignment > 128) { PyErr_SetString(PyExc_ValueError, "alignment should be from 1 to 128"); @@ -3673,22 +3823,20 @@ _set_alignment(PyObject* self, PyObject* args) return Py_None; } -static PyObject* -_set_block_size(PyObject* self, PyObject* args) -{ +static PyObject * +_set_block_size(PyObject *self, PyObject *args) { int block_size; - if (!PyArg_ParseTuple(args, "i:set_block_size", &block_size)) + if (!PyArg_ParseTuple(args, "i:set_block_size", &block_size)) { return NULL; + } if (block_size <= 0) { - PyErr_SetString(PyExc_ValueError, - "block_size should be greater than 0"); + PyErr_SetString(PyExc_ValueError, "block_size should be greater than 0"); return NULL; } if (block_size & 0xfff) { - PyErr_SetString(PyExc_ValueError, - "block_size should be multiple of 4096"); + PyErr_SetString(PyExc_ValueError, "block_size should be multiple of 4096"); return NULL; } @@ -3698,41 +3846,38 @@ _set_block_size(PyObject* self, PyObject* args) return Py_None; } -static PyObject* -_set_blocks_max(PyObject* self, PyObject* args) -{ +static PyObject * +_set_blocks_max(PyObject *self, PyObject *args) { int blocks_max; - if (!PyArg_ParseTuple(args, "i:set_blocks_max", &blocks_max)) + if (!PyArg_ParseTuple(args, "i:set_blocks_max", &blocks_max)) { return NULL; + } if (blocks_max < 0) { - PyErr_SetString(PyExc_ValueError, - "blocks_max should be greater than 0"); + PyErr_SetString(PyExc_ValueError, "blocks_max should be greater than 0"); return NULL; - } - else if ( blocks_max > SIZE_MAX/sizeof(ImagingDefaultArena.blocks_pool[0])) { - PyErr_SetString(PyExc_ValueError, - "blocks_max is too large"); + } else if ( + (unsigned long)blocks_max > + SIZE_MAX / sizeof(ImagingDefaultArena.blocks_pool[0])) { + PyErr_SetString(PyExc_ValueError, "blocks_max is too large"); return NULL; } - - if ( ! ImagingMemorySetBlocksMax(&ImagingDefaultArena, blocks_max)) { - ImagingError_MemoryError(); - return NULL; + if (!ImagingMemorySetBlocksMax(&ImagingDefaultArena, blocks_max)) { + return ImagingError_MemoryError(); } Py_INCREF(Py_None); return Py_None; } -static PyObject* -_clear_cache(PyObject* self, PyObject* args) -{ +static PyObject * +_clear_cache(PyObject *self, PyObject *args) { int i = 0; - if (!PyArg_ParseTuple(args, "|i:clear_cache", &i)) + if (!PyArg_ParseTuple(args, "|i:clear_cache", &i)) { return NULL; + } ImagingMemoryClearCache(&ImagingDefaultArena, i); @@ -3746,56 +3891,99 @@ _clear_cache(PyObject* self, PyObject* args) pluggable codecs, but not before PIL 1.2 */ /* Decoders (in decode.c) */ -extern PyObject* PyImaging_BcnDecoderNew(PyObject* self, PyObject* args); -extern PyObject* PyImaging_BitDecoderNew(PyObject* self, PyObject* args); -extern PyObject* PyImaging_FliDecoderNew(PyObject* self, PyObject* args); -extern PyObject* PyImaging_GifDecoderNew(PyObject* self, PyObject* args); -extern PyObject* PyImaging_HexDecoderNew(PyObject* self, PyObject* args); -extern PyObject* PyImaging_JpegDecoderNew(PyObject* self, PyObject* args); -extern PyObject* PyImaging_Jpeg2KDecoderNew(PyObject* self, PyObject* args); -extern PyObject* PyImaging_LibTiffDecoderNew(PyObject* self, PyObject* args); -extern PyObject* PyImaging_PackbitsDecoderNew(PyObject* self, PyObject* args); -extern PyObject* PyImaging_PcdDecoderNew(PyObject* self, PyObject* args); -extern PyObject* PyImaging_PcxDecoderNew(PyObject* self, PyObject* args); -extern PyObject* PyImaging_RawDecoderNew(PyObject* self, PyObject* args); -extern PyObject* PyImaging_SgiRleDecoderNew(PyObject* self, PyObject* args); -extern PyObject* PyImaging_SunRleDecoderNew(PyObject* self, PyObject* args); -extern PyObject* PyImaging_TgaRleDecoderNew(PyObject* self, PyObject* args); -extern PyObject* PyImaging_XbmDecoderNew(PyObject* self, PyObject* args); -extern PyObject* PyImaging_ZipDecoderNew(PyObject* self, PyObject* args); +extern PyObject * +PyImaging_BcnDecoderNew(PyObject *self, PyObject *args); +extern PyObject * +PyImaging_BitDecoderNew(PyObject *self, PyObject *args); +extern PyObject * +PyImaging_FliDecoderNew(PyObject *self, PyObject *args); +extern PyObject * +PyImaging_GifDecoderNew(PyObject *self, PyObject *args); +extern PyObject * +PyImaging_HexDecoderNew(PyObject *self, PyObject *args); +extern PyObject * +PyImaging_JpegDecoderNew(PyObject *self, PyObject *args); +extern PyObject * +PyImaging_Jpeg2KDecoderNew(PyObject *self, PyObject *args); +extern PyObject * +PyImaging_LibTiffDecoderNew(PyObject *self, PyObject *args); +extern PyObject * +PyImaging_PackbitsDecoderNew(PyObject *self, PyObject *args); +extern PyObject * +PyImaging_PcdDecoderNew(PyObject *self, PyObject *args); +extern PyObject * +PyImaging_PcxDecoderNew(PyObject *self, PyObject *args); +extern PyObject * +PyImaging_RawDecoderNew(PyObject *self, PyObject *args); +extern PyObject * +PyImaging_SgiRleDecoderNew(PyObject *self, PyObject *args); +extern PyObject * +PyImaging_SunRleDecoderNew(PyObject *self, PyObject *args); +extern PyObject * +PyImaging_TgaRleDecoderNew(PyObject *self, PyObject *args); +extern PyObject * +PyImaging_XbmDecoderNew(PyObject *self, PyObject *args); +extern PyObject * +PyImaging_ZipDecoderNew(PyObject *self, PyObject *args); /* Encoders (in encode.c) */ -extern PyObject* PyImaging_EpsEncoderNew(PyObject* self, PyObject* args); -extern PyObject* PyImaging_GifEncoderNew(PyObject* self, PyObject* args); -extern PyObject* PyImaging_JpegEncoderNew(PyObject* self, PyObject* args); -extern PyObject* PyImaging_Jpeg2KEncoderNew(PyObject* self, PyObject* args); -extern PyObject* PyImaging_PcxEncoderNew(PyObject* self, PyObject* args); -extern PyObject* PyImaging_RawEncoderNew(PyObject* self, PyObject* args); -extern PyObject* PyImaging_TgaRleEncoderNew(PyObject* self, PyObject* args); -extern PyObject* PyImaging_XbmEncoderNew(PyObject* self, PyObject* args); -extern PyObject* PyImaging_ZipEncoderNew(PyObject* self, PyObject* args); -extern PyObject* PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args); +extern PyObject * +PyImaging_EpsEncoderNew(PyObject *self, PyObject *args); +extern PyObject * +PyImaging_GifEncoderNew(PyObject *self, PyObject *args); +extern PyObject * +PyImaging_JpegEncoderNew(PyObject *self, PyObject *args); +extern PyObject * +PyImaging_Jpeg2KEncoderNew(PyObject *self, PyObject *args); +extern PyObject * +PyImaging_PcxEncoderNew(PyObject *self, PyObject *args); +extern PyObject * +PyImaging_RawEncoderNew(PyObject *self, PyObject *args); +extern PyObject * +PyImaging_TgaRleEncoderNew(PyObject *self, PyObject *args); +extern PyObject * +PyImaging_XbmEncoderNew(PyObject *self, PyObject *args); +extern PyObject * +PyImaging_ZipEncoderNew(PyObject *self, PyObject *args); +extern PyObject * +PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args); /* Display support etc (in display.c) */ #ifdef _WIN32 -extern PyObject* PyImaging_CreateWindowWin32(PyObject* self, PyObject* args); -extern PyObject* PyImaging_DisplayWin32(PyObject* self, PyObject* args); -extern PyObject* PyImaging_DisplayModeWin32(PyObject* self, PyObject* args); -extern PyObject* PyImaging_GrabScreenWin32(PyObject* self, PyObject* args); -extern PyObject* PyImaging_GrabClipboardWin32(PyObject* self, PyObject* args); -extern PyObject* PyImaging_ListWindowsWin32(PyObject* self, PyObject* args); -extern PyObject* PyImaging_EventLoopWin32(PyObject* self, PyObject* args); -extern PyObject* PyImaging_DrawWmf(PyObject* self, PyObject* args); +extern PyObject * +PyImaging_CreateWindowWin32(PyObject *self, PyObject *args); +extern PyObject * +PyImaging_DisplayWin32(PyObject *self, PyObject *args); +extern PyObject * +PyImaging_DisplayModeWin32(PyObject *self, PyObject *args); +extern PyObject * +PyImaging_GrabScreenWin32(PyObject *self, PyObject *args); +extern PyObject * +PyImaging_GrabClipboardWin32(PyObject *self, PyObject *args); +extern PyObject * +PyImaging_ListWindowsWin32(PyObject *self, PyObject *args); +extern PyObject * +PyImaging_EventLoopWin32(PyObject *self, PyObject *args); +extern PyObject * +PyImaging_DrawWmf(PyObject *self, PyObject *args); +#endif +#ifdef HAVE_XCB +extern PyObject * +PyImaging_GrabScreenX11(PyObject *self, PyObject *args); #endif /* Experimental path stuff (in path.c) */ -extern PyObject* PyPath_Create(ImagingObject* self, PyObject* args); +extern PyObject * +PyPath_Create(ImagingObject *self, PyObject *args); /* Experimental outline stuff (in outline.c) */ -extern PyObject* PyOutline_Create(ImagingObject* self, PyObject* args); +extern PyObject * +PyOutline_Create(ImagingObject *self, PyObject *args); -extern PyObject* PyImaging_Mapper(PyObject* self, PyObject* args); -extern PyObject* PyImaging_MapBuffer(PyObject* self, PyObject* args); +extern PyObject * +PyImaging_Mapper(PyObject *self, PyObject *args); +extern PyObject * +PyImaging_MapBuffer(PyObject *self, PyObject *args); static PyMethodDef functions[] = { @@ -3847,7 +4035,7 @@ static PyMethodDef functions[] = { {"zip_encoder", (PyCFunction)PyImaging_ZipEncoderNew, 1}, #endif - /* Memory mapping */ +/* Memory mapping */ #ifdef WITH_MAPPING #ifdef _WIN32 {"map", (PyCFunction)PyImaging_Mapper, 1}, @@ -3855,22 +4043,25 @@ static PyMethodDef functions[] = { {"map_buffer", (PyCFunction)PyImaging_MapBuffer, 1}, #endif - /* Display support */ +/* Display support */ #ifdef _WIN32 {"display", (PyCFunction)PyImaging_DisplayWin32, 1}, {"display_mode", (PyCFunction)PyImaging_DisplayModeWin32, 1}, - {"grabscreen", (PyCFunction)PyImaging_GrabScreenWin32, 1}, - {"grabclipboard", (PyCFunction)PyImaging_GrabClipboardWin32, 1}, + {"grabscreen_win32", (PyCFunction)PyImaging_GrabScreenWin32, 1}, + {"grabclipboard_win32", (PyCFunction)PyImaging_GrabClipboardWin32, 1}, {"createwindow", (PyCFunction)PyImaging_CreateWindowWin32, 1}, {"eventloop", (PyCFunction)PyImaging_EventLoopWin32, 1}, {"listwindows", (PyCFunction)PyImaging_ListWindowsWin32, 1}, {"drawwmf", (PyCFunction)PyImaging_DrawWmf, 1}, #endif +#ifdef HAVE_XCB + {"grabscreen_x11", (PyCFunction)PyImaging_GrabScreenX11, 1}, +#endif /* Utilities */ {"getcodecstatus", (PyCFunction)_getcodecstatus, 1}, - /* Special effects (experimental) */ +/* Special effects (experimental) */ #ifdef WITH_EFFECTS {"effect_mandelbrot", (PyCFunction)_effect_mandelbrot, 1}, {"effect_noise", (PyCFunction)_effect_noise, 1}, @@ -3879,18 +4070,18 @@ static PyMethodDef functions[] = { {"wedge", (PyCFunction)_linear_gradient, 1}, /* Compatibility */ #endif - /* Drawing support stuff */ +/* Drawing support stuff */ #ifdef WITH_IMAGEDRAW {"font", (PyCFunction)_font_new, 1}, {"draw", (PyCFunction)_draw_new, 1}, #endif - /* Experimental path stuff */ +/* Experimental path stuff */ #ifdef WITH_IMAGEPATH {"path", (PyCFunction)PyPath_Create, 1}, #endif - /* Experimental arrow graphics stuff */ +/* Experimental arrow graphics stuff */ #ifdef WITH_ARROW {"outline", (PyCFunction)PyOutline_Create, 1}, #endif @@ -3910,79 +4101,105 @@ static PyMethodDef functions[] = { }; static int -setup_module(PyObject* m) { - PyObject* d = PyModule_GetDict(m); - const char* version = (char*)PILLOW_VERSION; +setup_module(PyObject *m) { + PyObject *d = PyModule_GetDict(m); + const char *version = (char *)PILLOW_VERSION; /* Ready object types */ - if (PyType_Ready(&Imaging_Type) < 0) + if (PyType_Ready(&Imaging_Type) < 0) { return -1; + } #ifdef WITH_IMAGEDRAW - if (PyType_Ready(&ImagingFont_Type) < 0) + if (PyType_Ready(&ImagingFont_Type) < 0) { return -1; + } - if (PyType_Ready(&ImagingDraw_Type) < 0) + if (PyType_Ready(&ImagingDraw_Type) < 0) { return -1; + } #endif - if (PyType_Ready(&PixelAccess_Type) < 0) + if (PyType_Ready(&PixelAccess_Type) < 0) { return -1; + } ImagingAccessInit(); #ifdef HAVE_LIBJPEG - { - extern const char* ImagingJpegVersion(void); - PyDict_SetItemString(d, "jpeglib_version", PyUnicode_FromString(ImagingJpegVersion())); - } + { + extern const char *ImagingJpegVersion(void); + PyDict_SetItemString( + d, "jpeglib_version", PyUnicode_FromString(ImagingJpegVersion())); + } #endif #ifdef HAVE_OPENJPEG - { - extern const char *ImagingJpeg2KVersion(void); - PyDict_SetItemString(d, "jp2klib_version", PyUnicode_FromString(ImagingJpeg2KVersion())); - } + { + extern const char *ImagingJpeg2KVersion(void); + PyDict_SetItemString( + d, "jp2klib_version", PyUnicode_FromString(ImagingJpeg2KVersion())); + } #endif #ifdef LIBJPEG_TURBO_VERSION PyModule_AddObject(m, "HAVE_LIBJPEGTURBO", Py_True); +#define tostr1(a) #a +#define tostr(a) tostr1(a) + PyDict_SetItemString( + d, "libjpeg_turbo_version", PyUnicode_FromString(tostr(LIBJPEG_TURBO_VERSION))); +#undef tostr +#undef tostr1 #else PyModule_AddObject(m, "HAVE_LIBJPEGTURBO", Py_False); #endif #ifdef HAVE_LIBIMAGEQUANT PyModule_AddObject(m, "HAVE_LIBIMAGEQUANT", Py_True); + { + extern const char *ImagingImageQuantVersion(void); + PyDict_SetItemString( + d, "imagequant_version", PyUnicode_FromString(ImagingImageQuantVersion())); + } #else PyModule_AddObject(m, "HAVE_LIBIMAGEQUANT", Py_False); #endif #ifdef HAVE_LIBZ - /* zip encoding strategies */ - PyModule_AddIntConstant(m, "DEFAULT_STRATEGY", Z_DEFAULT_STRATEGY); - PyModule_AddIntConstant(m, "FILTERED", Z_FILTERED); - PyModule_AddIntConstant(m, "HUFFMAN_ONLY", Z_HUFFMAN_ONLY); - PyModule_AddIntConstant(m, "RLE", Z_RLE); - PyModule_AddIntConstant(m, "FIXED", Z_FIXED); - { - extern const char* ImagingZipVersion(void); - PyDict_SetItemString(d, "zlib_version", PyUnicode_FromString(ImagingZipVersion())); - } + /* zip encoding strategies */ + PyModule_AddIntConstant(m, "DEFAULT_STRATEGY", Z_DEFAULT_STRATEGY); + PyModule_AddIntConstant(m, "FILTERED", Z_FILTERED); + PyModule_AddIntConstant(m, "HUFFMAN_ONLY", Z_HUFFMAN_ONLY); + PyModule_AddIntConstant(m, "RLE", Z_RLE); + PyModule_AddIntConstant(m, "FIXED", Z_FIXED); + { + extern const char *ImagingZipVersion(void); + PyDict_SetItemString( + d, "zlib_version", PyUnicode_FromString(ImagingZipVersion())); + } #endif #ifdef HAVE_LIBTIFF - { - extern const char * ImagingTiffVersion(void); - PyDict_SetItemString(d, "libtiff_version", PyUnicode_FromString(ImagingTiffVersion())); + { + extern const char *ImagingTiffVersion(void); + PyDict_SetItemString( + d, "libtiff_version", PyUnicode_FromString(ImagingTiffVersion())); - // Test for libtiff 4.0 or later, excluding libtiff 3.9.6 and 3.9.7 - PyObject* support_custom_tags; -#if TIFFLIB_VERSION >= 20111221 && TIFFLIB_VERSION != 20120218 && TIFFLIB_VERSION != 20120922 - support_custom_tags = Py_True; + // Test for libtiff 4.0 or later, excluding libtiff 3.9.6 and 3.9.7 + PyObject *support_custom_tags; +#if TIFFLIB_VERSION >= 20111221 && TIFFLIB_VERSION != 20120218 && \ + TIFFLIB_VERSION != 20120922 + support_custom_tags = Py_True; #else - support_custom_tags = Py_False; + support_custom_tags = Py_False; #endif - PyDict_SetItemString(d, "libtiff_support_custom_tags", support_custom_tags); - } + PyDict_SetItemString(d, "libtiff_support_custom_tags", support_custom_tags); + } +#endif + +#ifdef HAVE_XCB + PyModule_AddObject(m, "HAVE_XCB", Py_True); +#else + PyModule_AddObject(m, "HAVE_XCB", Py_False); #endif PyDict_SetItemString(d, "PILLOW_VERSION", PyUnicode_FromString(version)); @@ -3992,20 +4209,21 @@ setup_module(PyObject* m) { PyMODINIT_FUNC PyInit__imaging(void) { - PyObject* m; + PyObject *m; static PyModuleDef module_def = { PyModuleDef_HEAD_INIT, - "_imaging", /* m_name */ - NULL, /* m_doc */ - -1, /* m_size */ - functions, /* m_methods */ + "_imaging", /* m_name */ + NULL, /* m_doc */ + -1, /* m_size */ + functions, /* m_methods */ }; m = PyModule_Create(&module_def); - if (setup_module(m) < 0) + if (setup_module(m) < 0) { return NULL; + } return m; } diff --git a/src/_imagingcms.c b/src/_imagingcms.c index 0b22ab695..314150420 100644 --- a/src/_imagingcms.c +++ b/src/_imagingcms.c @@ -17,7 +17,8 @@ * March 2009, for distribution under the standard PIL license */ -#define COPYRIGHTINFO "\ +#define COPYRIGHTINFO \ + "\ pyCMS\n\ a Python / PIL interface to the littleCMS ICC Color Management System\n\ Copyright (C) 2002-2003 Kevin Cazabon\n\ @@ -26,12 +27,12 @@ http://www.cazabon.com\n\ " #define PY_SSIZE_T_CLEAN -#include "Python.h" // Include before wchar.h so _GNU_SOURCE is set +#include "Python.h" // Include before wchar.h so _GNU_SOURCE is set #include "wchar.h" #include "datetime.h" #include "lcms2.h" -#include "Imaging.h" +#include "libImaging/Imaging.h" #define PYCMSVERSION "1.0.0 pil" @@ -41,10 +42,11 @@ http://www.cazabon.com\n\ 1.0.0 pil Integrating littleCMS2 0.1.0 pil integration & refactoring 0.0.2 alpha: Minor updates, added interfaces to littleCMS features, Jan 6, 2003 - - fixed some memory holes in how transforms/profiles were created and passed back to Python - due to improper destructor setup for PyCObjects + - fixed some memory holes in how transforms/profiles were created and passed back to + Python due to improper destructor setup for PyCObjects - added buildProofTransformFromOpenProfiles() function - - eliminated some code redundancy, centralizing several common tasks with internal functions + - eliminated some code redundancy, centralizing several common tasks with internal + functions 0.0.1 alpha: First public release Dec 26, 2002 @@ -74,94 +76,92 @@ http://www.cazabon.com\n\ /* a profile represents the ICC characteristics for a specific device */ typedef struct { - PyObject_HEAD - cmsHPROFILE profile; + PyObject_HEAD cmsHPROFILE profile; } CmsProfileObject; static PyTypeObject CmsProfile_Type; #define CmsProfile_Check(op) (Py_TYPE(op) == &CmsProfile_Type) -static PyObject* -cms_profile_new(cmsHPROFILE profile) -{ - CmsProfileObject* self; +static PyObject * +cms_profile_new(cmsHPROFILE profile) { + CmsProfileObject *self; self = PyObject_New(CmsProfileObject, &CmsProfile_Type); - if (!self) + if (!self) { return NULL; + } self->profile = profile; - return (PyObject*) self; + return (PyObject *)self; } -static PyObject* -cms_profile_open(PyObject* self, PyObject* args) -{ +static PyObject * +cms_profile_open(PyObject *self, PyObject *args) { cmsHPROFILE hProfile; - char* sProfile; - if (!PyArg_ParseTuple(args, "s:profile_open", &sProfile)) + char *sProfile; + if (!PyArg_ParseTuple(args, "s:profile_open", &sProfile)) { return NULL; + } hProfile = cmsOpenProfileFromFile(sProfile, "r"); if (!hProfile) { - PyErr_SetString(PyExc_IOError, "cannot open profile file"); + PyErr_SetString(PyExc_OSError, "cannot open profile file"); return NULL; } return cms_profile_new(hProfile); } -static PyObject* -cms_profile_fromstring(PyObject* self, PyObject* args) -{ +static PyObject * +cms_profile_fromstring(PyObject *self, PyObject *args) { cmsHPROFILE hProfile; - char* pProfile; + char *pProfile; Py_ssize_t nProfile; - if (!PyArg_ParseTuple(args, "y#:profile_frombytes", &pProfile, &nProfile)) + if (!PyArg_ParseTuple(args, "y#:profile_frombytes", &pProfile, &nProfile)) { return NULL; + } hProfile = cmsOpenProfileFromMem(pProfile, nProfile); if (!hProfile) { - PyErr_SetString(PyExc_IOError, "cannot open profile from string"); + PyErr_SetString(PyExc_OSError, "cannot open profile from string"); return NULL; } return cms_profile_new(hProfile); } -static PyObject* -cms_profile_tobytes(PyObject* self, PyObject* args) -{ - char *pProfile =NULL; +static PyObject * +cms_profile_tobytes(PyObject *self, PyObject *args) { + char *pProfile = NULL; cmsUInt32Number nProfile; - PyObject* CmsProfile; + PyObject *CmsProfile; cmsHPROFILE *profile; - PyObject* ret; - if (!PyArg_ParseTuple(args, "O", &CmsProfile)){ + PyObject *ret; + if (!PyArg_ParseTuple(args, "O", &CmsProfile)) { return NULL; } - profile = ((CmsProfileObject*)CmsProfile)->profile; + profile = ((CmsProfileObject *)CmsProfile)->profile; if (!cmsSaveProfileToMem(profile, pProfile, &nProfile)) { - PyErr_SetString(PyExc_IOError, "Could not determine profile size"); + PyErr_SetString(PyExc_OSError, "Could not determine profile size"); return NULL; } - pProfile = (char*)malloc(nProfile); + pProfile = (char *)malloc(nProfile); if (!pProfile) { - PyErr_SetString(PyExc_IOError, "Out of Memory"); + PyErr_SetString(PyExc_OSError, "Out of Memory"); return NULL; } if (!cmsSaveProfileToMem(profile, pProfile, &nProfile)) { - PyErr_SetString(PyExc_IOError, "Could not get profile"); + PyErr_SetString(PyExc_OSError, "Could not get profile"); free(pProfile); return NULL; } @@ -173,17 +173,15 @@ cms_profile_tobytes(PyObject* self, PyObject* args) } static void -cms_profile_dealloc(CmsProfileObject* self) -{ - (void) cmsCloseProfile(self->profile); +cms_profile_dealloc(CmsProfileObject *self) { + (void)cmsCloseProfile(self->profile); PyObject_Del(self); } /* a transform represents the mapping between two profiles */ typedef struct { - PyObject_HEAD - char mode_in[8]; + PyObject_HEAD char mode_in[8]; char mode_out[8]; cmsHTRANSFORM transform; } CmsTransformObject; @@ -192,26 +190,25 @@ static PyTypeObject CmsTransform_Type; #define CmsTransform_Check(op) (Py_TYPE(op) == &CmsTransform_Type) -static PyObject* -cms_transform_new(cmsHTRANSFORM transform, char* mode_in, char* mode_out) -{ - CmsTransformObject* self; +static PyObject * +cms_transform_new(cmsHTRANSFORM transform, char *mode_in, char *mode_out) { + CmsTransformObject *self; self = PyObject_New(CmsTransformObject, &CmsTransform_Type); - if (!self) + if (!self) { return NULL; + } self->transform = transform; strcpy(self->mode_in, mode_in); strcpy(self->mode_out, mode_out); - return (PyObject*) self; + return (PyObject *)self; } static void -cms_transform_dealloc(CmsTransformObject* self) -{ +cms_transform_dealloc(CmsTransformObject *self) { cmsDeleteTransform(self->transform); PyObject_Del(self); } @@ -219,61 +216,31 @@ cms_transform_dealloc(CmsTransformObject* self) /* -------------------------------------------------------------------- */ /* internal functions */ -static const char* -findICmode(cmsColorSpaceSignature cs) -{ - switch (cs) { - case cmsSigXYZData: return "XYZ"; - case cmsSigLabData: return "LAB"; - case cmsSigLuvData: return "LUV"; - case cmsSigYCbCrData: return "YCbCr"; - case cmsSigYxyData: return "YXY"; - case cmsSigRgbData: return "RGB"; - case cmsSigGrayData: return "L"; - case cmsSigHsvData: return "HSV"; - case cmsSigHlsData: return "HLS"; - case cmsSigCmykData: return "CMYK"; - case cmsSigCmyData: return "CMY"; - default: return ""; /* other TBA */ - } -} - static cmsUInt32Number -findLCMStype(char* PILmode) -{ +findLCMStype(char *PILmode) { if (strcmp(PILmode, "RGB") == 0) { return TYPE_RGBA_8; - } - else if (strcmp(PILmode, "RGBA") == 0) { + } else if (strcmp(PILmode, "RGBA") == 0) { return TYPE_RGBA_8; - } - else if (strcmp(PILmode, "RGBX") == 0) { + } else if (strcmp(PILmode, "RGBX") == 0) { return TYPE_RGBA_8; - } - else if (strcmp(PILmode, "RGBA;16B") == 0) { + } else if (strcmp(PILmode, "RGBA;16B") == 0) { return TYPE_RGBA_16; - } - else if (strcmp(PILmode, "CMYK") == 0) { + } else if (strcmp(PILmode, "CMYK") == 0) { return TYPE_CMYK_8; - } - else if (strcmp(PILmode, "L") == 0) { + } else if (strcmp(PILmode, "L") == 0) { return TYPE_GRAY_8; - } - else if (strcmp(PILmode, "L;16") == 0) { + } else if (strcmp(PILmode, "L;16") == 0) { return TYPE_GRAY_16; - } - else if (strcmp(PILmode, "L;16B") == 0) { + } else if (strcmp(PILmode, "L;16B") == 0) { return TYPE_GRAY_16_SE; - } - else if (strcmp(PILmode, "YCCA") == 0) { + } else if (strcmp(PILmode, "YCCA") == 0) { return TYPE_YCbCr_8; - } - else if (strcmp(PILmode, "YCC") == 0) { + } else if (strcmp(PILmode, "YCC") == 0) { return TYPE_YCbCr_8; - } - else if (strcmp(PILmode, "LAB") == 0) { + } else if (strcmp(PILmode, "LAB") == 0) { // LabX equivalent like ALab, but not reversed -- no #define in lcms2 - return (COLORSPACE_SH(PT_LabV2)|CHANNELS_SH(3)|BYTES_SH(1)|EXTRA_SH(1)); + return (COLORSPACE_SH(PT_LabV2) | CHANNELS_SH(3) | BYTES_SH(1) | EXTRA_SH(1)); } else { @@ -285,38 +252,35 @@ findLCMStype(char* PILmode) #define Cms_Min(a, b) ((a) < (b) ? (a) : (b)) static int -pyCMSgetAuxChannelChannel (cmsUInt32Number format, int auxChannelNdx) -{ +pyCMSgetAuxChannelChannel(cmsUInt32Number format, int auxChannelNdx) { int numColors = T_CHANNELS(format); int numExtras = T_EXTRA(format); if (T_SWAPFIRST(format) && T_DOSWAP(format)) { // reverse order, before anything but last extra is shifted last - if (auxChannelNdx == numExtras - 1) + if (auxChannelNdx == numExtras - 1) { return numColors + numExtras - 1; - else + } else { return numExtras - 2 - auxChannelNdx; - } - else if (T_SWAPFIRST(format)) { + } + } else if (T_SWAPFIRST(format)) { // in order, after color channels, but last extra is shifted to first - if (auxChannelNdx == numExtras - 1) + if (auxChannelNdx == numExtras - 1) { return 0; - else + } else { return numColors + 1 + auxChannelNdx; - } - else if (T_DOSWAP(format)) { + } + } else if (T_DOSWAP(format)) { // reverse order, before anything return numExtras - 1 - auxChannelNdx; - } - else { + } else { // in order, after color channels return numColors + auxChannelNdx; } } static void -pyCMScopyAux (cmsHTRANSFORM hTransform, Imaging imDst, const Imaging imSrc) -{ +pyCMScopyAux(cmsHTRANSFORM hTransform, Imaging imDst, const Imaging imSrc) { cmsUInt32Number dstLCMSFormat; cmsUInt32Number srcLCMSFormat; int numSrcExtras; @@ -330,23 +294,26 @@ pyCMScopyAux (cmsHTRANSFORM hTransform, Imaging imDst, const Imaging imSrc) int e; // trivially copied - if (imDst == imSrc) + if (imDst == imSrc) { return; + } dstLCMSFormat = cmsGetTransformOutputFormat(hTransform); srcLCMSFormat = cmsGetTransformInputFormat(hTransform); // currently, all Pillow formats are chunky formats, but check it anyway - if (T_PLANAR(dstLCMSFormat) || T_PLANAR(srcLCMSFormat)) + if (T_PLANAR(dstLCMSFormat) || T_PLANAR(srcLCMSFormat)) { return; + } // copy only if channel format is identical, except OPTIMIZED is ignored as it // does not affect the aux channel - if (T_FLOAT(dstLCMSFormat) != T_FLOAT(srcLCMSFormat) - || T_FLAVOR(dstLCMSFormat) != T_FLAVOR(srcLCMSFormat) - || T_ENDIAN16(dstLCMSFormat) != T_ENDIAN16(srcLCMSFormat) - || T_BYTES(dstLCMSFormat) != T_BYTES(srcLCMSFormat)) - return; + if (T_FLOAT(dstLCMSFormat) != T_FLOAT(srcLCMSFormat) || + T_FLAVOR(dstLCMSFormat) != T_FLAVOR(srcLCMSFormat) || + T_ENDIAN16(dstLCMSFormat) != T_ENDIAN16(srcLCMSFormat) || + T_BYTES(dstLCMSFormat) != T_BYTES(srcLCMSFormat)) { + return; + } numSrcExtras = T_EXTRA(srcLCMSFormat); numDstExtras = T_EXTRA(dstLCMSFormat); @@ -364,28 +331,33 @@ pyCMScopyAux (cmsHTRANSFORM hTransform, Imaging imDst, const Imaging imSrc) for (y = 0; y < ySize; y++) { int x; - char* pDstExtras = imDst->image[y] + dstChannel * channelSize; - const char* pSrcExtras = imSrc->image[y] + srcChannel * channelSize; + char *pDstExtras = imDst->image[y] + dstChannel * channelSize; + const char *pSrcExtras = imSrc->image[y] + srcChannel * channelSize; - for (x = 0; x < xSize; x++) - memcpy(pDstExtras + x * dstChunkSize, pSrcExtras + x * srcChunkSize, channelSize); + for (x = 0; x < xSize; x++) { + memcpy( + pDstExtras + x * dstChunkSize, + pSrcExtras + x * srcChunkSize, + channelSize); + } } } } static int -pyCMSdoTransform(Imaging im, Imaging imOut, cmsHTRANSFORM hTransform) -{ +pyCMSdoTransform(Imaging im, Imaging imOut, cmsHTRANSFORM hTransform) { int i; - if (im->xsize > imOut->xsize || im->ysize > imOut->ysize) + if (im->xsize > imOut->xsize || im->ysize > imOut->ysize) { return -1; + } Py_BEGIN_ALLOW_THREADS - // transform color channels only - for (i = 0; i < im->ysize; i++) + // transform color channels only + for (i = 0; i < im->ysize; i++) { cmsDoTransform(hTransform, im->image[i], imOut->image[i], im->xsize); + } // lcms by default does nothing to the auxiliary channels leaving those // unchanged. To do "the right thing" here, i.e. maintain identical results @@ -398,52 +370,69 @@ pyCMSdoTransform(Imaging im, Imaging imOut, cmsHTRANSFORM hTransform) Py_END_ALLOW_THREADS - return 0; + return 0; } static cmsHTRANSFORM -_buildTransform(cmsHPROFILE hInputProfile, cmsHPROFILE hOutputProfile, char *sInMode, char *sOutMode, int iRenderingIntent, cmsUInt32Number cmsFLAGS) -{ +_buildTransform( + cmsHPROFILE hInputProfile, + cmsHPROFILE hOutputProfile, + char *sInMode, + char *sOutMode, + int iRenderingIntent, + cmsUInt32Number cmsFLAGS) { cmsHTRANSFORM hTransform; Py_BEGIN_ALLOW_THREADS - /* create the transform */ - hTransform = cmsCreateTransform(hInputProfile, - findLCMStype(sInMode), - hOutputProfile, - findLCMStype(sOutMode), - iRenderingIntent, cmsFLAGS); + /* create the transform */ + hTransform = cmsCreateTransform( + hInputProfile, + findLCMStype(sInMode), + hOutputProfile, + findLCMStype(sOutMode), + iRenderingIntent, + cmsFLAGS); Py_END_ALLOW_THREADS - if (!hTransform) + if (!hTransform) { PyErr_SetString(PyExc_ValueError, "cannot build transform"); + } return hTransform; /* if NULL, an exception is set */ } static cmsHTRANSFORM -_buildProofTransform(cmsHPROFILE hInputProfile, cmsHPROFILE hOutputProfile, cmsHPROFILE hProofProfile, char *sInMode, char *sOutMode, int iRenderingIntent, int iProofIntent, cmsUInt32Number cmsFLAGS) -{ +_buildProofTransform( + cmsHPROFILE hInputProfile, + cmsHPROFILE hOutputProfile, + cmsHPROFILE hProofProfile, + char *sInMode, + char *sOutMode, + int iRenderingIntent, + int iProofIntent, + cmsUInt32Number cmsFLAGS) { cmsHTRANSFORM hTransform; Py_BEGIN_ALLOW_THREADS - /* create the transform */ - hTransform = cmsCreateProofingTransform(hInputProfile, - findLCMStype(sInMode), - hOutputProfile, - findLCMStype(sOutMode), - hProofProfile, - iRenderingIntent, - iProofIntent, - cmsFLAGS); + /* create the transform */ + hTransform = cmsCreateProofingTransform( + hInputProfile, + findLCMStype(sInMode), + hOutputProfile, + findLCMStype(sOutMode), + hProofProfile, + iRenderingIntent, + iProofIntent, + cmsFLAGS); Py_END_ALLOW_THREADS - if (!hTransform) + if (!hTransform) { PyErr_SetString(PyExc_ValueError, "cannot build proof transform"); + } return hTransform; /* if NULL, an exception is set */ } @@ -462,20 +451,37 @@ buildTransform(PyObject *self, PyObject *args) { cmsHTRANSFORM transform = NULL; - if (!PyArg_ParseTuple(args, "O!O!ss|ii:buildTransform", &CmsProfile_Type, &pInputProfile, &CmsProfile_Type, &pOutputProfile, &sInMode, &sOutMode, &iRenderingIntent, &cmsFLAGS)) + if (!PyArg_ParseTuple( + args, + "O!O!ss|ii:buildTransform", + &CmsProfile_Type, + &pInputProfile, + &CmsProfile_Type, + &pOutputProfile, + &sInMode, + &sOutMode, + &iRenderingIntent, + &cmsFLAGS)) { return NULL; + } - transform = _buildTransform(pInputProfile->profile, pOutputProfile->profile, sInMode, sOutMode, iRenderingIntent, cmsFLAGS); + transform = _buildTransform( + pInputProfile->profile, + pOutputProfile->profile, + sInMode, + sOutMode, + iRenderingIntent, + cmsFLAGS); - if (!transform) + if (!transform) { return NULL; + } return cms_transform_new(transform, sInMode, sOutMode); } static PyObject * -buildProofTransform(PyObject *self, PyObject *args) -{ +buildProofTransform(PyObject *self, PyObject *args) { CmsProfileObject *pInputProfile; CmsProfileObject *pOutputProfile; CmsProfileObject *pProofProfile; @@ -487,21 +493,42 @@ buildProofTransform(PyObject *self, PyObject *args) cmsHTRANSFORM transform = NULL; - if (!PyArg_ParseTuple(args, "O!O!O!ss|iii:buildProofTransform", &CmsProfile_Type, &pInputProfile, &CmsProfile_Type, &pOutputProfile, &CmsProfile_Type, &pProofProfile, &sInMode, &sOutMode, &iRenderingIntent, &iProofIntent, &cmsFLAGS)) + if (!PyArg_ParseTuple( + args, + "O!O!O!ss|iii:buildProofTransform", + &CmsProfile_Type, + &pInputProfile, + &CmsProfile_Type, + &pOutputProfile, + &CmsProfile_Type, + &pProofProfile, + &sInMode, + &sOutMode, + &iRenderingIntent, + &iProofIntent, + &cmsFLAGS)) { return NULL; + } - transform = _buildProofTransform(pInputProfile->profile, pOutputProfile->profile, pProofProfile->profile, sInMode, sOutMode, iRenderingIntent, iProofIntent, cmsFLAGS); + transform = _buildProofTransform( + pInputProfile->profile, + pOutputProfile->profile, + pProofProfile->profile, + sInMode, + sOutMode, + iRenderingIntent, + iProofIntent, + cmsFLAGS); - if (!transform) + if (!transform) { return NULL; + } return cms_transform_new(transform, sInMode, sOutMode); - } static PyObject * -cms_transform_apply(CmsTransformObject *self, PyObject *args) -{ +cms_transform_apply(CmsTransformObject *self, PyObject *args) { Py_ssize_t idIn; Py_ssize_t idOut; Imaging im; @@ -509,11 +536,12 @@ cms_transform_apply(CmsTransformObject *self, PyObject *args) int result; - if (!PyArg_ParseTuple(args, "nn:apply", &idIn, &idOut)) + if (!PyArg_ParseTuple(args, "nn:apply", &idIn, &idOut)) { return NULL; + } - im = (Imaging) idIn; - imOut = (Imaging) idOut; + im = (Imaging)idIn; + imOut = (Imaging)idOut; result = pyCMSdoTransform(im, imOut, self->transform); @@ -524,36 +552,36 @@ cms_transform_apply(CmsTransformObject *self, PyObject *args) /* Python-Callable On-The-Fly profile creation functions */ static PyObject * -createProfile(PyObject *self, PyObject *args) -{ +createProfile(PyObject *self, PyObject *args) { char *sColorSpace; cmsHPROFILE hProfile; cmsFloat64Number dColorTemp = 0.0; cmsCIExyY whitePoint; cmsBool result; - if (!PyArg_ParseTuple(args, "s|d:createProfile", &sColorSpace, &dColorTemp)) + if (!PyArg_ParseTuple(args, "s|d:createProfile", &sColorSpace, &dColorTemp)) { return NULL; + } if (strcmp(sColorSpace, "LAB") == 0) { if (dColorTemp > 0.0) { result = cmsWhitePointFromTemp(&whitePoint, dColorTemp); if (!result) { - PyErr_SetString(PyExc_ValueError, "ERROR: Could not calculate white point from color temperature provided, must be float in degrees Kelvin"); + PyErr_SetString( + PyExc_ValueError, + "ERROR: Could not calculate white point from color temperature " + "provided, must be float in degrees Kelvin"); return NULL; } hProfile = cmsCreateLab2Profile(&whitePoint); } else { hProfile = cmsCreateLab2Profile(NULL); } - } - else if (strcmp(sColorSpace, "XYZ") == 0) { + } else if (strcmp(sColorSpace, "XYZ") == 0) { hProfile = cmsCreateXYZProfile(); - } - else if (strcmp(sColorSpace, "sRGB") == 0) { + } else if (strcmp(sColorSpace, "sRGB") == 0) { hProfile = cmsCreate_sRGBProfile(); - } - else { + } else { hProfile = NULL; } @@ -569,18 +597,19 @@ createProfile(PyObject *self, PyObject *args) /* profile methods */ static PyObject * -cms_profile_is_intent_supported(CmsProfileObject *self, PyObject *args) -{ +cms_profile_is_intent_supported(CmsProfileObject *self, PyObject *args) { cmsBool result; int intent; int direction; - if (!PyArg_ParseTuple(args, "ii:is_intent_supported", &intent, &direction)) + if (!PyArg_ParseTuple(args, "ii:is_intent_supported", &intent, &direction)) { return NULL; + } result = cmsIsIntentSupported(self->profile, intent, direction); - /* printf("cmsIsIntentSupported(%p, %d, %d) => %d\n", self->profile, intent, direction, result); */ + /* printf("cmsIsIntentSupported(%p, %d, %d) => %d\n", self->profile, intent, + * direction, result); */ return PyLong_FromLong(result != 0); } @@ -594,29 +623,31 @@ cms_profile_is_intent_supported(CmsProfileObject *self, PyObject *args) #endif static PyObject * -cms_get_display_profile_win32(PyObject* self, PyObject* args) -{ +cms_get_display_profile_win32(PyObject *self, PyObject *args) { char filename[MAX_PATH]; cmsUInt32Number filename_size; BOOL ok; HANDLE handle = 0; int is_dc = 0; - if (!PyArg_ParseTuple(args, "|" F_HANDLE "i:get_display_profile", &handle, &is_dc)) + if (!PyArg_ParseTuple( + args, "|" F_HANDLE "i:get_display_profile", &handle, &is_dc)) { return NULL; + } filename_size = sizeof(filename); if (is_dc) { - ok = GetICMProfile((HDC) handle, &filename_size, filename); + ok = GetICMProfile((HDC)handle, &filename_size, filename); } else { - HDC dc = GetDC((HWND) handle); + HDC dc = GetDC((HWND)handle); ok = GetICMProfile(dc, &filename_size, filename); - ReleaseDC((HWND) handle, dc); + ReleaseDC((HWND)handle, dc); } - if (ok) - return PyUnicode_FromStringAndSize(filename, filename_size-1); + if (ok) { + return PyUnicode_FromStringAndSize(filename, filename_size - 1); + } Py_INCREF(Py_None); return Py_None; @@ -626,9 +657,8 @@ cms_get_display_profile_win32(PyObject* self, PyObject* args) /* -------------------------------------------------------------------- */ /* Helper functions. */ -static PyObject* -_profile_read_mlu(CmsProfileObject* self, cmsTagSignature info) -{ +static PyObject * +_profile_read_mlu(CmsProfileObject *self, cmsTagSignature info) { PyObject *uni; char *lc = "en"; char *cc = cmsNoCountry; @@ -655,7 +685,7 @@ _profile_read_mlu(CmsProfileObject* self, cmsTagSignature info) buf = malloc(len); if (!buf) { - PyErr_SetString(PyExc_IOError, "Out of Memory"); + PyErr_SetString(PyExc_OSError, "Out of Memory"); return NULL; } /* Just in case the next call fails. */ @@ -669,26 +699,22 @@ _profile_read_mlu(CmsProfileObject* self, cmsTagSignature info) return uni; } - -static PyObject* -_profile_read_int_as_string(cmsUInt32Number nr) -{ - PyObject* ret; +static PyObject * +_profile_read_int_as_string(cmsUInt32Number nr) { + PyObject *ret; char buf[5]; - buf[0] = (char) ((nr >> 24) & 0xff); - buf[1] = (char) ((nr >> 16) & 0xff); - buf[2] = (char) ((nr >> 8) & 0xff); - buf[3] = (char) (nr & 0xff); + buf[0] = (char)((nr >> 24) & 0xff); + buf[1] = (char)((nr >> 16) & 0xff); + buf[2] = (char)((nr >> 8) & 0xff); + buf[3] = (char)(nr & 0xff); buf[4] = 0; ret = PyUnicode_DecodeASCII(buf, 4, NULL); return ret; } - -static PyObject* -_profile_read_signature(CmsProfileObject* self, cmsTagSignature info) -{ +static PyObject * +_profile_read_signature(CmsProfileObject *self, cmsTagSignature info) { unsigned int *sig; if (!cmsIsTag(self->profile, info)) { @@ -696,7 +722,7 @@ _profile_read_signature(CmsProfileObject* self, cmsTagSignature info) return Py_None; } - sig = (unsigned int *) cmsReadTag(self->profile, info); + sig = (unsigned int *)cmsReadTag(self->profile, info); if (!sig) { Py_INCREF(Py_None); return Py_None; @@ -705,63 +731,74 @@ _profile_read_signature(CmsProfileObject* self, cmsTagSignature info) return _profile_read_int_as_string(*sig); } -static PyObject* -_xyz_py(cmsCIEXYZ* XYZ) -{ +static PyObject * +_xyz_py(cmsCIEXYZ *XYZ) { cmsCIExyY xyY; cmsXYZ2xyY(&xyY, XYZ); - return Py_BuildValue("((d,d,d),(d,d,d))", XYZ->X, XYZ->Y, XYZ->Z, xyY.x, xyY.y, xyY.Y); + return Py_BuildValue( + "((d,d,d),(d,d,d))", XYZ->X, XYZ->Y, XYZ->Z, xyY.x, xyY.y, xyY.Y); } -static PyObject* -_xyz3_py(cmsCIEXYZ* XYZ) -{ +static PyObject * +_xyz3_py(cmsCIEXYZ *XYZ) { cmsCIExyY xyY[3]; cmsXYZ2xyY(&xyY[0], &XYZ[0]); cmsXYZ2xyY(&xyY[1], &XYZ[1]); cmsXYZ2xyY(&xyY[2], &XYZ[2]); - return Py_BuildValue("(((d,d,d),(d,d,d),(d,d,d)),((d,d,d),(d,d,d),(d,d,d)))", - XYZ[0].X, XYZ[0].Y, XYZ[0].Z, - XYZ[1].X, XYZ[1].Y, XYZ[1].Z, - XYZ[2].X, XYZ[2].Y, XYZ[2].Z, - xyY[0].x, xyY[0].y, xyY[0].Y, - xyY[1].x, xyY[1].y, xyY[1].Y, - xyY[2].x, xyY[2].y, xyY[2].Y); + return Py_BuildValue( + "(((d,d,d),(d,d,d),(d,d,d)),((d,d,d),(d,d,d),(d,d,d)))", + XYZ[0].X, + XYZ[0].Y, + XYZ[0].Z, + XYZ[1].X, + XYZ[1].Y, + XYZ[1].Z, + XYZ[2].X, + XYZ[2].Y, + XYZ[2].Z, + xyY[0].x, + xyY[0].y, + xyY[0].Y, + xyY[1].x, + xyY[1].y, + xyY[1].Y, + xyY[2].x, + xyY[2].y, + xyY[2].Y); } -static PyObject* -_profile_read_ciexyz(CmsProfileObject* self, cmsTagSignature info, int multi) -{ - cmsCIEXYZ* XYZ; +static PyObject * +_profile_read_ciexyz(CmsProfileObject *self, cmsTagSignature info, int multi) { + cmsCIEXYZ *XYZ; if (!cmsIsTag(self->profile, info)) { Py_INCREF(Py_None); return Py_None; } - XYZ = (cmsCIEXYZ*) cmsReadTag(self->profile, info); + XYZ = (cmsCIEXYZ *)cmsReadTag(self->profile, info); if (!XYZ) { Py_INCREF(Py_None); return Py_None; } - if (multi) + if (multi) { return _xyz3_py(XYZ); - else + } else { return _xyz_py(XYZ); + } } -static PyObject* -_profile_read_ciexyy_triple(CmsProfileObject* self, cmsTagSignature info) -{ - cmsCIExyYTRIPLE* triple; +static PyObject * +_profile_read_ciexyy_triple(CmsProfileObject *self, cmsTagSignature info) { + cmsCIExyYTRIPLE *triple; if (!cmsIsTag(self->profile, info)) { Py_INCREF(Py_None); return Py_None; } - triple = (cmsCIExyYTRIPLE*) cmsReadTag(self->profile, info); + triple = (cmsCIExyYTRIPLE *)cmsReadTag(self->profile, info); if (!triple) { Py_INCREF(Py_None); return Py_None; @@ -769,26 +806,32 @@ _profile_read_ciexyy_triple(CmsProfileObject* self, cmsTagSignature info) /* Note: lcms does all the heavy lifting and error checking (nr of channels == 3). */ - return Py_BuildValue("((d,d,d),(d,d,d),(d,d,d)),", - triple->Red.x, triple->Red.y, triple->Red.Y, - triple->Green.x, triple->Green.y, triple->Green.Y, - triple->Blue.x, triple->Blue.y, triple->Blue.Y); + return Py_BuildValue( + "((d,d,d),(d,d,d),(d,d,d)),", + triple->Red.x, + triple->Red.y, + triple->Red.Y, + triple->Green.x, + triple->Green.y, + triple->Green.Y, + triple->Blue.x, + triple->Blue.y, + triple->Blue.Y); } -static PyObject* -_profile_read_named_color_list(CmsProfileObject* self, cmsTagSignature info) -{ - cmsNAMEDCOLORLIST* ncl; +static PyObject * +_profile_read_named_color_list(CmsProfileObject *self, cmsTagSignature info) { + cmsNAMEDCOLORLIST *ncl; int i, n; char name[cmsMAX_PATH]; - PyObject* result; + PyObject *result; if (!cmsIsTag(self->profile, info)) { Py_INCREF(Py_None); return Py_None; } - ncl = (cmsNAMEDCOLORLIST*) cmsReadTag(self->profile, info); + ncl = (cmsNAMEDCOLORLIST *)cmsReadTag(self->profile, info); if (ncl == NULL) { Py_INCREF(Py_None); return Py_None; @@ -802,7 +845,7 @@ _profile_read_named_color_list(CmsProfileObject* self, cmsTagSignature info) } for (i = 0; i < n; i++) { - PyObject* str; + PyObject *str; cmsNamedColorInfo(ncl, i, name, NULL, NULL, NULL, NULL); str = PyUnicode_FromString(name); if (str == NULL) { @@ -816,9 +859,9 @@ _profile_read_named_color_list(CmsProfileObject* self, cmsTagSignature info) return result; } -static cmsBool _calculate_rgb_primaries(CmsProfileObject* self, cmsCIEXYZTRIPLE* result) -{ - double input[3][3] = { { 1, 0, 0 }, { 0, 1, 0 }, { 0, 0, 1 } }; +static cmsBool +_calculate_rgb_primaries(CmsProfileObject *self, cmsCIEXYZTRIPLE *result) { + double input[3][3] = {{1, 0, 0}, {0, 1, 0}, {0, 0, 1}}; cmsHPROFILE hXYZ; cmsHTRANSFORM hTransform; @@ -826,39 +869,46 @@ static cmsBool _calculate_rgb_primaries(CmsProfileObject* self, cmsCIEXYZTRIPLE* // double array of RGB values with max on each identity hXYZ = cmsCreateXYZProfile(); - if (hXYZ == NULL) + if (hXYZ == NULL) { return 0; + } // transform from our profile to XYZ using doubles for highest precision - hTransform = cmsCreateTransform(self->profile, TYPE_RGB_DBL, - hXYZ, TYPE_XYZ_DBL, - INTENT_RELATIVE_COLORIMETRIC, - cmsFLAGS_NOCACHE | cmsFLAGS_NOOPTIMIZE); + hTransform = cmsCreateTransform( + self->profile, + TYPE_RGB_DBL, + hXYZ, + TYPE_XYZ_DBL, + INTENT_RELATIVE_COLORIMETRIC, + cmsFLAGS_NOCACHE | cmsFLAGS_NOOPTIMIZE); cmsCloseProfile(hXYZ); - if (hTransform == NULL) + if (hTransform == NULL) { return 0; + } - cmsDoTransform(hTransform, (void*) input, result, 3); + cmsDoTransform(hTransform, (void *)input, result, 3); cmsDeleteTransform(hTransform); return 1; } -static cmsBool _check_intent(int clut, cmsHPROFILE hProfile, cmsUInt32Number Intent, cmsUInt32Number UsedDirection) -{ +static cmsBool +_check_intent( + int clut, + cmsHPROFILE hProfile, + cmsUInt32Number Intent, + cmsUInt32Number UsedDirection) { if (clut) { return cmsIsCLUT(hProfile, Intent, UsedDirection); - } - else { + } else { return cmsIsIntentSupported(hProfile, Intent, UsedDirection); } } #define INTENTS 200 -static PyObject* -_is_intent_supported(CmsProfileObject* self, int clut) -{ - PyObject* result; +static PyObject * +_is_intent_supported(CmsProfileObject *self, int clut) { + PyObject *result; int n; int i; cmsUInt32Number intent_ids[INTENTS]; @@ -870,25 +920,28 @@ _is_intent_supported(CmsProfileObject* self, int clut) return Py_None; } - - n = cmsGetSupportedIntents(INTENTS, - intent_ids, - intent_descs); + n = cmsGetSupportedIntents(INTENTS, intent_ids, intent_descs); for (i = 0; i < n; i++) { - int intent = (int) intent_ids[i]; - PyObject* id; - PyObject* entry; + int intent = (int)intent_ids[i]; + PyObject *id; + PyObject *entry; - /* Only valid for ICC Intents (otherwise we read invalid memory in lcms cmsio1.c). */ - if (!(intent == INTENT_PERCEPTUAL || intent == INTENT_RELATIVE_COLORIMETRIC - || intent == INTENT_SATURATION || intent == INTENT_ABSOLUTE_COLORIMETRIC)) + /* Only valid for ICC Intents (otherwise we read invalid memory in lcms + * cmsio1.c). */ + if (!(intent == INTENT_PERCEPTUAL || intent == INTENT_RELATIVE_COLORIMETRIC || + intent == INTENT_SATURATION || intent == INTENT_ABSOLUTE_COLORIMETRIC)) { continue; + } - id = PyLong_FromLong((long) intent); - entry = Py_BuildValue("(OOO)", - _check_intent(clut, self->profile, intent, LCMS_USED_AS_INPUT) ? Py_True : Py_False, - _check_intent(clut, self->profile, intent, LCMS_USED_AS_OUTPUT) ? Py_True : Py_False, - _check_intent(clut, self->profile, intent, LCMS_USED_AS_PROOF) ? Py_True : Py_False); + id = PyLong_FromLong((long)intent); + entry = Py_BuildValue( + "(OOO)", + _check_intent(clut, self->profile, intent, LCMS_USED_AS_INPUT) ? Py_True + : Py_False, + _check_intent(clut, self->profile, intent, LCMS_USED_AS_OUTPUT) ? Py_True + : Py_False, + _check_intent(clut, self->profile, intent, LCMS_USED_AS_PROOF) ? Py_True + : Py_False); if (id == NULL || entry == NULL) { Py_XDECREF(id); Py_XDECREF(entry); @@ -916,267 +969,161 @@ static PyMethodDef pyCMSdll_methods[] = { {"buildProofTransform", buildProofTransform, 1}, {"createProfile", createProfile, 1}, - /* platform specific tools */ +/* platform specific tools */ #ifdef _WIN32 {"get_display_profile_win32", cms_get_display_profile_win32, 1}, #endif - {NULL, NULL} -}; + {NULL, NULL}}; static struct PyMethodDef cms_profile_methods[] = { - {"is_intent_supported", (PyCFunction) cms_profile_is_intent_supported, 1}, + {"is_intent_supported", (PyCFunction)cms_profile_is_intent_supported, 1}, {NULL, NULL} /* sentinel */ }; -static PyObject* -_profile_getattr(CmsProfileObject* self, cmsInfoType field) -{ - // UNDONE -- check that I'm getting the right fields on these. - // return PyUnicode_DecodeFSDefault(cmsTakeProductName(self->profile)); - //wchar_t buf[256]; -- UNDONE need wchar_t for unicode version. - char buf[256]; - cmsUInt32Number written; - written = cmsGetProfileInfoASCII(self->profile, - field, - "en", - "us", - buf, - 256); - if (written) { - return PyUnicode_FromString(buf); - } - // UNDONE suppressing error here by sending back blank string. - return PyUnicode_FromString(""); -} - -static PyObject* -cms_profile_getattr_product_desc(CmsProfileObject* self, void* closure) -{ - PyErr_WarnEx(PyExc_DeprecationWarning, - "product_desc is deprecated. Use Unicode profile_description instead.", 1); - // description was Description != 'Copyright' || or "%s - %s" (manufacturer, model) in 1.x - return _profile_getattr(self, cmsInfoDescription); -} - -/* use these four for the individual fields. - */ -static PyObject* -cms_profile_getattr_product_description(CmsProfileObject* self, void* closure) -{ - PyErr_WarnEx(PyExc_DeprecationWarning, - "product_description is deprecated. Use Unicode profile_description instead.", 1); - return _profile_getattr(self, cmsInfoDescription); -} - -static PyObject* -cms_profile_getattr_product_model(CmsProfileObject* self, void* closure) -{ - PyErr_WarnEx(PyExc_DeprecationWarning, - "product_model is deprecated. Use Unicode model instead.", 1); - return _profile_getattr(self, cmsInfoModel); -} - -static PyObject* -cms_profile_getattr_product_manufacturer(CmsProfileObject* self, void* closure) -{ - PyErr_WarnEx(PyExc_DeprecationWarning, - "product_manufacturer is deprecated. Use Unicode manufacturer instead.", 1); - return _profile_getattr(self, cmsInfoManufacturer); -} - -static PyObject* -cms_profile_getattr_product_copyright(CmsProfileObject* self, void* closure) -{ - PyErr_WarnEx(PyExc_DeprecationWarning, - "product_copyright is deprecated. Use Unicode copyright instead.", 1); - return _profile_getattr(self, cmsInfoCopyright); -} - -static PyObject* -cms_profile_getattr_rendering_intent(CmsProfileObject* self, void* closure) -{ +static PyObject * +cms_profile_getattr_rendering_intent(CmsProfileObject *self, void *closure) { return PyLong_FromLong(cmsGetHeaderRenderingIntent(self->profile)); } -static PyObject* -cms_profile_getattr_pcs(CmsProfileObject* self, void* closure) -{ - PyErr_WarnEx(PyExc_DeprecationWarning, - "pcs is deprecated. Use padded connection_space instead.", 1); - return PyUnicode_DecodeFSDefault(findICmode(cmsGetPCS(self->profile))); -} - -static PyObject* -cms_profile_getattr_color_space(CmsProfileObject* self, void* closure) -{ - PyErr_WarnEx(PyExc_DeprecationWarning, - "color_space is deprecated. Use padded xcolor_space instead.", 1); - return PyUnicode_DecodeFSDefault(findICmode(cmsGetColorSpace(self->profile))); -} - /* New-style unicode interfaces. */ -static PyObject* -cms_profile_getattr_copyright(CmsProfileObject* self, void* closure) -{ +static PyObject * +cms_profile_getattr_copyright(CmsProfileObject *self, void *closure) { return _profile_read_mlu(self, cmsSigCopyrightTag); } -static PyObject* -cms_profile_getattr_target(CmsProfileObject* self, void* closure) -{ +static PyObject * +cms_profile_getattr_target(CmsProfileObject *self, void *closure) { return _profile_read_mlu(self, cmsSigCharTargetTag); } -static PyObject* -cms_profile_getattr_manufacturer(CmsProfileObject* self, void* closure) -{ +static PyObject * +cms_profile_getattr_manufacturer(CmsProfileObject *self, void *closure) { return _profile_read_mlu(self, cmsSigDeviceMfgDescTag); } -static PyObject* -cms_profile_getattr_model(CmsProfileObject* self, void* closure) -{ +static PyObject * +cms_profile_getattr_model(CmsProfileObject *self, void *closure) { return _profile_read_mlu(self, cmsSigDeviceModelDescTag); } -static PyObject* -cms_profile_getattr_profile_description(CmsProfileObject* self, void* closure) -{ +static PyObject * +cms_profile_getattr_profile_description(CmsProfileObject *self, void *closure) { return _profile_read_mlu(self, cmsSigProfileDescriptionTag); } -static PyObject* -cms_profile_getattr_screening_description(CmsProfileObject* self, void* closure) -{ +static PyObject * +cms_profile_getattr_screening_description(CmsProfileObject *self, void *closure) { return _profile_read_mlu(self, cmsSigScreeningDescTag); } -static PyObject* -cms_profile_getattr_viewing_condition(CmsProfileObject* self, void* closure) -{ +static PyObject * +cms_profile_getattr_viewing_condition(CmsProfileObject *self, void *closure) { return _profile_read_mlu(self, cmsSigViewingCondDescTag); } -static PyObject* -cms_profile_getattr_creation_date(CmsProfileObject* self, void* closure) -{ +static PyObject * +cms_profile_getattr_creation_date(CmsProfileObject *self, void *closure) { cmsBool result; struct tm ct; result = cmsGetHeaderCreationDateTime(self->profile, &ct); - if (! result) { + if (!result) { Py_INCREF(Py_None); return Py_None; } - return PyDateTime_FromDateAndTime(1900 + ct.tm_year, ct.tm_mon, ct.tm_mday, - ct.tm_hour, ct.tm_min, ct.tm_sec, 0); + return PyDateTime_FromDateAndTime( + 1900 + ct.tm_year, ct.tm_mon, ct.tm_mday, ct.tm_hour, ct.tm_min, ct.tm_sec, 0); } -static PyObject* -cms_profile_getattr_version(CmsProfileObject* self, void* closure) -{ +static PyObject * +cms_profile_getattr_version(CmsProfileObject *self, void *closure) { cmsFloat64Number version = cmsGetProfileVersion(self->profile); return PyFloat_FromDouble(version); } -static PyObject* -cms_profile_getattr_icc_version(CmsProfileObject* self, void* closure) -{ - return PyLong_FromLong((long) cmsGetEncodedICCversion(self->profile)); +static PyObject * +cms_profile_getattr_icc_version(CmsProfileObject *self, void *closure) { + return PyLong_FromLong((long)cmsGetEncodedICCversion(self->profile)); } -static PyObject* -cms_profile_getattr_attributes(CmsProfileObject* self, void* closure) -{ +static PyObject * +cms_profile_getattr_attributes(CmsProfileObject *self, void *closure) { cmsUInt64Number attr; cmsGetHeaderAttributes(self->profile, &attr); /* This works just as well on Windows (LLP64), 32-bit Linux (ILP32) and 64-bit Linux (LP64) systems. */ - return PyLong_FromUnsignedLongLong((unsigned long long) attr); + return PyLong_FromUnsignedLongLong((unsigned long long)attr); } -static PyObject* -cms_profile_getattr_header_flags(CmsProfileObject* self, void* closure) -{ +static PyObject * +cms_profile_getattr_header_flags(CmsProfileObject *self, void *closure) { cmsUInt32Number flags = cmsGetHeaderFlags(self->profile); return PyLong_FromLong(flags); } -static PyObject* -cms_profile_getattr_header_manufacturer(CmsProfileObject* self, void* closure) -{ +static PyObject * +cms_profile_getattr_header_manufacturer(CmsProfileObject *self, void *closure) { return _profile_read_int_as_string(cmsGetHeaderManufacturer(self->profile)); } -static PyObject* -cms_profile_getattr_header_model(CmsProfileObject* self, void* closure) -{ +static PyObject * +cms_profile_getattr_header_model(CmsProfileObject *self, void *closure) { return _profile_read_int_as_string(cmsGetHeaderModel(self->profile)); } -static PyObject* -cms_profile_getattr_device_class(CmsProfileObject* self, void* closure) -{ +static PyObject * +cms_profile_getattr_device_class(CmsProfileObject *self, void *closure) { return _profile_read_int_as_string(cmsGetDeviceClass(self->profile)); } -/* Duplicate of pcs, but uninterpreted. */ -static PyObject* -cms_profile_getattr_connection_space(CmsProfileObject* self, void* closure) -{ +static PyObject * +cms_profile_getattr_connection_space(CmsProfileObject *self, void *closure) { return _profile_read_int_as_string(cmsGetPCS(self->profile)); } -/* Duplicate of color_space, but uninterpreted. */ -static PyObject* -cms_profile_getattr_xcolor_space(CmsProfileObject* self, void* closure) -{ +static PyObject * +cms_profile_getattr_xcolor_space(CmsProfileObject *self, void *closure) { return _profile_read_int_as_string(cmsGetColorSpace(self->profile)); } -static PyObject* -cms_profile_getattr_profile_id(CmsProfileObject* self, void* closure) -{ +static PyObject * +cms_profile_getattr_profile_id(CmsProfileObject *self, void *closure) { cmsUInt8Number id[16]; cmsGetHeaderProfileID(self->profile, id); - return PyBytes_FromStringAndSize((char *) id, 16); + return PyBytes_FromStringAndSize((char *)id, 16); } -static PyObject* -cms_profile_getattr_is_matrix_shaper(CmsProfileObject* self, void* closure) -{ - return PyBool_FromLong((long) cmsIsMatrixShaper(self->profile)); +static PyObject * +cms_profile_getattr_is_matrix_shaper(CmsProfileObject *self, void *closure) { + return PyBool_FromLong((long)cmsIsMatrixShaper(self->profile)); } -static PyObject* -cms_profile_getattr_technology(CmsProfileObject* self, void* closure) -{ +static PyObject * +cms_profile_getattr_technology(CmsProfileObject *self, void *closure) { return _profile_read_signature(self, cmsSigTechnologyTag); } -static PyObject* -cms_profile_getattr_colorimetric_intent(CmsProfileObject* self, void* closure) -{ +static PyObject * +cms_profile_getattr_colorimetric_intent(CmsProfileObject *self, void *closure) { return _profile_read_signature(self, cmsSigColorimetricIntentImageStateTag); } -static PyObject* -cms_profile_getattr_perceptual_rendering_intent_gamut(CmsProfileObject* self, void* closure) -{ +static PyObject * +cms_profile_getattr_perceptual_rendering_intent_gamut( + CmsProfileObject *self, void *closure) { return _profile_read_signature(self, cmsSigPerceptualRenderingIntentGamutTag); } -static PyObject* -cms_profile_getattr_saturation_rendering_intent_gamut(CmsProfileObject* self, void* closure) -{ +static PyObject * +cms_profile_getattr_saturation_rendering_intent_gamut( + CmsProfileObject *self, void *closure) { return _profile_read_signature(self, cmsSigSaturationRenderingIntentGamutTag); } -static PyObject* -cms_profile_getattr_red_colorant(CmsProfileObject* self, void* closure) -{ +static PyObject * +cms_profile_getattr_red_colorant(CmsProfileObject *self, void *closure) { if (!cmsIsMatrixShaper(self->profile)) { Py_INCREF(Py_None); return Py_None; @@ -1184,10 +1131,8 @@ cms_profile_getattr_red_colorant(CmsProfileObject* self, void* closure) return _profile_read_ciexyz(self, cmsSigRedColorantTag, 0); } - -static PyObject* -cms_profile_getattr_green_colorant(CmsProfileObject* self, void* closure) -{ +static PyObject * +cms_profile_getattr_green_colorant(CmsProfileObject *self, void *closure) { if (!cmsIsMatrixShaper(self->profile)) { Py_INCREF(Py_None); return Py_None; @@ -1195,10 +1140,8 @@ cms_profile_getattr_green_colorant(CmsProfileObject* self, void* closure) return _profile_read_ciexyz(self, cmsSigGreenColorantTag, 0); } - -static PyObject* -cms_profile_getattr_blue_colorant(CmsProfileObject* self, void* closure) -{ +static PyObject * +cms_profile_getattr_blue_colorant(CmsProfileObject *self, void *closure) { if (!cmsIsMatrixShaper(self->profile)) { Py_INCREF(Py_None); return Py_None; @@ -1206,10 +1149,10 @@ cms_profile_getattr_blue_colorant(CmsProfileObject* self, void* closure) return _profile_read_ciexyz(self, cmsSigBlueColorantTag, 0); } -static PyObject* -cms_profile_getattr_media_white_point_temperature(CmsProfileObject *self, void* closure) -{ - cmsCIEXYZ* XYZ; +static PyObject * +cms_profile_getattr_media_white_point_temperature( + CmsProfileObject *self, void *closure) { + cmsCIEXYZ *XYZ; cmsCIExyY xyY; cmsFloat64Number tempK; cmsTagSignature info = cmsSigMediaWhitePointTag; @@ -1220,11 +1163,7 @@ cms_profile_getattr_media_white_point_temperature(CmsProfileObject *self, void* return Py_None; } - XYZ = (cmsCIEXYZ*) cmsReadTag(self->profile, info); - if (!XYZ) { - Py_INCREF(Py_None); - return Py_None; - } + XYZ = (cmsCIEXYZ *)cmsReadTag(self->profile, info); if (XYZ == NULL || XYZ->X == 0) { Py_INCREF(Py_None); return Py_None; @@ -1239,46 +1178,40 @@ cms_profile_getattr_media_white_point_temperature(CmsProfileObject *self, void* return PyFloat_FromDouble(tempK); } -static PyObject* -cms_profile_getattr_media_white_point(CmsProfileObject* self, void* closure) -{ +static PyObject * +cms_profile_getattr_media_white_point(CmsProfileObject *self, void *closure) { return _profile_read_ciexyz(self, cmsSigMediaWhitePointTag, 0); } - -static PyObject* -cms_profile_getattr_media_black_point(CmsProfileObject* self, void* closure) -{ +static PyObject * +cms_profile_getattr_media_black_point(CmsProfileObject *self, void *closure) { return _profile_read_ciexyz(self, cmsSigMediaBlackPointTag, 0); } -static PyObject* -cms_profile_getattr_luminance(CmsProfileObject* self, void* closure) -{ +static PyObject * +cms_profile_getattr_luminance(CmsProfileObject *self, void *closure) { return _profile_read_ciexyz(self, cmsSigLuminanceTag, 0); } -static PyObject* -cms_profile_getattr_chromatic_adaptation(CmsProfileObject* self, void* closure) -{ +static PyObject * +cms_profile_getattr_chromatic_adaptation(CmsProfileObject *self, void *closure) { return _profile_read_ciexyz(self, cmsSigChromaticAdaptationTag, 1); } -static PyObject* -cms_profile_getattr_chromaticity(CmsProfileObject* self, void* closure) -{ +static PyObject * +cms_profile_getattr_chromaticity(CmsProfileObject *self, void *closure) { return _profile_read_ciexyy_triple(self, cmsSigChromaticityTag); } -static PyObject* -cms_profile_getattr_red_primary(CmsProfileObject* self, void* closure) -{ +static PyObject * +cms_profile_getattr_red_primary(CmsProfileObject *self, void *closure) { cmsBool result = 0; cmsCIEXYZTRIPLE primaries; - if (cmsIsMatrixShaper(self->profile)) + if (cmsIsMatrixShaper(self->profile)) { result = _calculate_rgb_primaries(self, &primaries); - if (! result) { + } + if (!result) { Py_INCREF(Py_None); return Py_None; } @@ -1286,15 +1219,15 @@ cms_profile_getattr_red_primary(CmsProfileObject* self, void* closure) return _xyz_py(&primaries.Red); } -static PyObject* -cms_profile_getattr_green_primary(CmsProfileObject* self, void* closure) -{ +static PyObject * +cms_profile_getattr_green_primary(CmsProfileObject *self, void *closure) { cmsBool result = 0; cmsCIEXYZTRIPLE primaries; - if (cmsIsMatrixShaper(self->profile)) + if (cmsIsMatrixShaper(self->profile)) { result = _calculate_rgb_primaries(self, &primaries); - if (! result) { + } + if (!result) { Py_INCREF(Py_None); return Py_None; } @@ -1302,15 +1235,15 @@ cms_profile_getattr_green_primary(CmsProfileObject* self, void* closure) return _xyz_py(&primaries.Green); } -static PyObject* -cms_profile_getattr_blue_primary(CmsProfileObject* self, void* closure) -{ +static PyObject * +cms_profile_getattr_blue_primary(CmsProfileObject *self, void *closure) { cmsBool result = 0; cmsCIEXYZTRIPLE primaries; - if (cmsIsMatrixShaper(self->profile)) + if (cmsIsMatrixShaper(self->profile)) { result = _calculate_rgb_primaries(self, &primaries); - if (! result) { + } + if (!result) { Py_INCREF(Py_None); return Py_None; } @@ -1318,61 +1251,55 @@ cms_profile_getattr_blue_primary(CmsProfileObject* self, void* closure) return _xyz_py(&primaries.Blue); } -static PyObject* -cms_profile_getattr_colorant_table(CmsProfileObject* self, void* closure) -{ +static PyObject * +cms_profile_getattr_colorant_table(CmsProfileObject *self, void *closure) { return _profile_read_named_color_list(self, cmsSigColorantTableTag); } -static PyObject* -cms_profile_getattr_colorant_table_out(CmsProfileObject* self, void* closure) -{ +static PyObject * +cms_profile_getattr_colorant_table_out(CmsProfileObject *self, void *closure) { return _profile_read_named_color_list(self, cmsSigColorantTableOutTag); } -static PyObject* -cms_profile_getattr_is_intent_supported (CmsProfileObject* self, void* closure) -{ +static PyObject * +cms_profile_getattr_is_intent_supported(CmsProfileObject *self, void *closure) { return _is_intent_supported(self, 0); } -static PyObject* -cms_profile_getattr_is_clut (CmsProfileObject* self, void* closure) -{ +static PyObject * +cms_profile_getattr_is_clut(CmsProfileObject *self, void *closure) { return _is_intent_supported(self, 1); } -static const char* -_illu_map(int i) -{ - switch(i) { - case 0: - return "unknown"; - case 1: - return "D50"; - case 2: - return "D65"; - case 3: - return "D93"; - case 4: - return "F2"; - case 5: - return "D55"; - case 6: - return "A"; - case 7: - return "E"; - case 8: - return "F8"; - default: - return NULL; +static const char * +_illu_map(int i) { + switch (i) { + case 0: + return "unknown"; + case 1: + return "D50"; + case 2: + return "D65"; + case 3: + return "D93"; + case 4: + return "F2"; + case 5: + return "D55"; + case 6: + return "A"; + case 7: + return "E"; + case 8: + return "F8"; + default: + return NULL; } } -static PyObject* -cms_profile_getattr_icc_measurement_condition (CmsProfileObject* self, void* closure) -{ - cmsICCMeasurementConditions* mc; +static PyObject * +cms_profile_getattr_icc_measurement_condition(CmsProfileObject *self, void *closure) { + cmsICCMeasurementConditions *mc; cmsTagSignature info = cmsSigMeasurementTag; const char *geo; @@ -1381,31 +1308,39 @@ cms_profile_getattr_icc_measurement_condition (CmsProfileObject* self, void* clo return Py_None; } - mc = (cmsICCMeasurementConditions*) cmsReadTag(self->profile, info); + mc = (cmsICCMeasurementConditions *)cmsReadTag(self->profile, info); if (!mc) { Py_INCREF(Py_None); return Py_None; } - if (mc->Geometry == 1) + if (mc->Geometry == 1) { geo = "45/0, 0/45"; - else if (mc->Geometry == 2) + } else if (mc->Geometry == 2) { geo = "0d, d/0"; - else + } else { geo = "unknown"; + } - return Py_BuildValue("{s:i,s:(ddd),s:s,s:d,s:s}", - "observer", mc->Observer, - "backing", mc->Backing.X, mc->Backing.Y, mc->Backing.Z, - "geo", geo, - "flare", mc->Flare, - "illuminant_type", _illu_map(mc->IlluminantType)); + return Py_BuildValue( + "{s:i,s:(ddd),s:s,s:d,s:s}", + "observer", + mc->Observer, + "backing", + mc->Backing.X, + mc->Backing.Y, + mc->Backing.Z, + "geo", + geo, + "flare", + mc->Flare, + "illuminant_type", + _illu_map(mc->IlluminantType)); } -static PyObject* -cms_profile_getattr_icc_viewing_condition (CmsProfileObject* self, void* closure) -{ - cmsICCViewingConditions* vc; +static PyObject * +cms_profile_getattr_icc_viewing_condition(CmsProfileObject *self, void *closure) { + cmsICCViewingConditions *vc; cmsTagSignature info = cmsSigViewingConditionsTag; if (!cmsIsTag(self->profile, info)) { @@ -1413,172 +1348,167 @@ cms_profile_getattr_icc_viewing_condition (CmsProfileObject* self, void* closure return Py_None; } - vc = (cmsICCViewingConditions*) cmsReadTag(self->profile, info); + vc = (cmsICCViewingConditions *)cmsReadTag(self->profile, info); if (!vc) { Py_INCREF(Py_None); return Py_None; } - return Py_BuildValue("{s:(ddd),s:(ddd),s:s}", - "illuminant", vc->IlluminantXYZ.X, vc->IlluminantXYZ.Y, vc->IlluminantXYZ.Z, - "surround", vc->SurroundXYZ.X, vc->SurroundXYZ.Y, vc->SurroundXYZ.Z, - "illuminant_type", _illu_map(vc->IlluminantType)); + return Py_BuildValue( + "{s:(ddd),s:(ddd),s:s}", + "illuminant", + vc->IlluminantXYZ.X, + vc->IlluminantXYZ.Y, + vc->IlluminantXYZ.Z, + "surround", + vc->SurroundXYZ.X, + vc->SurroundXYZ.Y, + vc->SurroundXYZ.Z, + "illuminant_type", + _illu_map(vc->IlluminantType)); } - static struct PyGetSetDef cms_profile_getsetters[] = { - /* Compatibility interfaces. */ - { "product_desc", (getter) cms_profile_getattr_product_desc }, - { "product_description", (getter) cms_profile_getattr_product_description }, - { "product_manufacturer", (getter) cms_profile_getattr_product_manufacturer }, - { "product_model", (getter) cms_profile_getattr_product_model }, - { "product_copyright", (getter) cms_profile_getattr_product_copyright }, - { "pcs", (getter) cms_profile_getattr_pcs }, - { "color_space", (getter) cms_profile_getattr_color_space }, - /* New style interfaces. */ - { "rendering_intent", (getter) cms_profile_getattr_rendering_intent }, - { "creation_date", (getter) cms_profile_getattr_creation_date }, - { "copyright", (getter) cms_profile_getattr_copyright }, - { "target", (getter) cms_profile_getattr_target }, - { "manufacturer", (getter) cms_profile_getattr_manufacturer }, - { "model", (getter) cms_profile_getattr_model }, - { "profile_description", (getter) cms_profile_getattr_profile_description }, - { "screening_description", (getter) cms_profile_getattr_screening_description }, - { "viewing_condition", (getter) cms_profile_getattr_viewing_condition }, - { "version", (getter) cms_profile_getattr_version }, - { "icc_version", (getter) cms_profile_getattr_icc_version }, - { "attributes", (getter) cms_profile_getattr_attributes }, - { "header_flags", (getter) cms_profile_getattr_header_flags }, - { "header_manufacturer", (getter) cms_profile_getattr_header_manufacturer }, - { "header_model", (getter) cms_profile_getattr_header_model }, - { "device_class", (getter) cms_profile_getattr_device_class }, - { "connection_space", (getter) cms_profile_getattr_connection_space }, - /* Similar to color_space, but with full 4-letter signature (including trailing whitespace). */ - { "xcolor_space", (getter) cms_profile_getattr_xcolor_space }, - { "profile_id", (getter) cms_profile_getattr_profile_id }, - { "is_matrix_shaper", (getter) cms_profile_getattr_is_matrix_shaper }, - { "technology", (getter) cms_profile_getattr_technology }, - { "colorimetric_intent", (getter) cms_profile_getattr_colorimetric_intent }, - { "perceptual_rendering_intent_gamut", (getter) cms_profile_getattr_perceptual_rendering_intent_gamut }, - { "saturation_rendering_intent_gamut", (getter) cms_profile_getattr_saturation_rendering_intent_gamut }, - { "red_colorant", (getter) cms_profile_getattr_red_colorant }, - { "green_colorant", (getter) cms_profile_getattr_green_colorant }, - { "blue_colorant", (getter) cms_profile_getattr_blue_colorant }, - { "red_primary", (getter) cms_profile_getattr_red_primary }, - { "green_primary", (getter) cms_profile_getattr_green_primary }, - { "blue_primary", (getter) cms_profile_getattr_blue_primary }, - { "media_white_point_temperature", (getter) cms_profile_getattr_media_white_point_temperature }, - { "media_white_point", (getter) cms_profile_getattr_media_white_point }, - { "media_black_point", (getter) cms_profile_getattr_media_black_point }, - { "luminance", (getter) cms_profile_getattr_luminance }, - { "chromatic_adaptation", (getter) cms_profile_getattr_chromatic_adaptation }, - { "chromaticity", (getter) cms_profile_getattr_chromaticity }, - { "colorant_table", (getter) cms_profile_getattr_colorant_table }, - { "colorant_table_out", (getter) cms_profile_getattr_colorant_table_out }, - { "intent_supported", (getter) cms_profile_getattr_is_intent_supported }, - { "clut", (getter) cms_profile_getattr_is_clut }, - { "icc_measurement_condition", (getter) cms_profile_getattr_icc_measurement_condition }, - { "icc_viewing_condition", (getter) cms_profile_getattr_icc_viewing_condition }, - - { NULL } -}; + {"rendering_intent", (getter)cms_profile_getattr_rendering_intent}, + {"creation_date", (getter)cms_profile_getattr_creation_date}, + {"copyright", (getter)cms_profile_getattr_copyright}, + {"target", (getter)cms_profile_getattr_target}, + {"manufacturer", (getter)cms_profile_getattr_manufacturer}, + {"model", (getter)cms_profile_getattr_model}, + {"profile_description", (getter)cms_profile_getattr_profile_description}, + {"screening_description", (getter)cms_profile_getattr_screening_description}, + {"viewing_condition", (getter)cms_profile_getattr_viewing_condition}, + {"version", (getter)cms_profile_getattr_version}, + {"icc_version", (getter)cms_profile_getattr_icc_version}, + {"attributes", (getter)cms_profile_getattr_attributes}, + {"header_flags", (getter)cms_profile_getattr_header_flags}, + {"header_manufacturer", (getter)cms_profile_getattr_header_manufacturer}, + {"header_model", (getter)cms_profile_getattr_header_model}, + {"device_class", (getter)cms_profile_getattr_device_class}, + {"connection_space", (getter)cms_profile_getattr_connection_space}, + {"xcolor_space", (getter)cms_profile_getattr_xcolor_space}, + {"profile_id", (getter)cms_profile_getattr_profile_id}, + {"is_matrix_shaper", (getter)cms_profile_getattr_is_matrix_shaper}, + {"technology", (getter)cms_profile_getattr_technology}, + {"colorimetric_intent", (getter)cms_profile_getattr_colorimetric_intent}, + {"perceptual_rendering_intent_gamut", + (getter)cms_profile_getattr_perceptual_rendering_intent_gamut}, + {"saturation_rendering_intent_gamut", + (getter)cms_profile_getattr_saturation_rendering_intent_gamut}, + {"red_colorant", (getter)cms_profile_getattr_red_colorant}, + {"green_colorant", (getter)cms_profile_getattr_green_colorant}, + {"blue_colorant", (getter)cms_profile_getattr_blue_colorant}, + {"red_primary", (getter)cms_profile_getattr_red_primary}, + {"green_primary", (getter)cms_profile_getattr_green_primary}, + {"blue_primary", (getter)cms_profile_getattr_blue_primary}, + {"media_white_point_temperature", + (getter)cms_profile_getattr_media_white_point_temperature}, + {"media_white_point", (getter)cms_profile_getattr_media_white_point}, + {"media_black_point", (getter)cms_profile_getattr_media_black_point}, + {"luminance", (getter)cms_profile_getattr_luminance}, + {"chromatic_adaptation", (getter)cms_profile_getattr_chromatic_adaptation}, + {"chromaticity", (getter)cms_profile_getattr_chromaticity}, + {"colorant_table", (getter)cms_profile_getattr_colorant_table}, + {"colorant_table_out", (getter)cms_profile_getattr_colorant_table_out}, + {"intent_supported", (getter)cms_profile_getattr_is_intent_supported}, + {"clut", (getter)cms_profile_getattr_is_clut}, + {"icc_measurement_condition", + (getter)cms_profile_getattr_icc_measurement_condition}, + {"icc_viewing_condition", (getter)cms_profile_getattr_icc_viewing_condition}, + {NULL}}; static PyTypeObject CmsProfile_Type = { - PyVarObject_HEAD_INIT(NULL, 0) - "PIL._imagingcms.CmsProfile", /*tp_name */ - sizeof(CmsProfileObject), 0,/*tp_basicsize, tp_itemsize */ + PyVarObject_HEAD_INIT(NULL, 0) "PIL._imagingcms.CmsProfile", /*tp_name */ + sizeof(CmsProfileObject), + 0, /*tp_basicsize, tp_itemsize */ /* methods */ - (destructor) cms_profile_dealloc, /*tp_dealloc*/ - 0, /*tp_print*/ - 0, /*tp_getattr*/ - 0, /*tp_setattr*/ - 0, /*tp_compare*/ - 0, /*tp_repr*/ - 0, /*tp_as_number */ - 0, /*tp_as_sequence */ - 0, /*tp_as_mapping */ - 0, /*tp_hash*/ - 0, /*tp_call*/ - 0, /*tp_str*/ - 0, /*tp_getattro*/ - 0, /*tp_setattro*/ - 0, /*tp_as_buffer*/ - Py_TPFLAGS_DEFAULT, /*tp_flags*/ - 0, /*tp_doc*/ - 0, /*tp_traverse*/ - 0, /*tp_clear*/ - 0, /*tp_richcompare*/ - 0, /*tp_weaklistoffset*/ - 0, /*tp_iter*/ - 0, /*tp_iternext*/ - cms_profile_methods, /*tp_methods*/ - 0, /*tp_members*/ - cms_profile_getsetters, /*tp_getset*/ + (destructor)cms_profile_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number */ + 0, /*tp_as_sequence */ + 0, /*tp_as_mapping */ + 0, /*tp_hash*/ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT, /*tp_flags*/ + 0, /*tp_doc*/ + 0, /*tp_traverse*/ + 0, /*tp_clear*/ + 0, /*tp_richcompare*/ + 0, /*tp_weaklistoffset*/ + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + cms_profile_methods, /*tp_methods*/ + 0, /*tp_members*/ + cms_profile_getsetters, /*tp_getset*/ }; static struct PyMethodDef cms_transform_methods[] = { - {"apply", (PyCFunction) cms_transform_apply, 1}, - {NULL, NULL} /* sentinel */ + {"apply", (PyCFunction)cms_transform_apply, 1}, {NULL, NULL} /* sentinel */ }; -static PyObject* -cms_transform_getattr_inputMode(CmsTransformObject* self, void* closure) -{ +static PyObject * +cms_transform_getattr_inputMode(CmsTransformObject *self, void *closure) { return PyUnicode_FromString(self->mode_in); } -static PyObject* -cms_transform_getattr_outputMode(CmsTransformObject* self, void* closure) -{ +static PyObject * +cms_transform_getattr_outputMode(CmsTransformObject *self, void *closure) { return PyUnicode_FromString(self->mode_out); } static struct PyGetSetDef cms_transform_getsetters[] = { - { "inputMode", (getter) cms_transform_getattr_inputMode }, - { "outputMode", (getter) cms_transform_getattr_outputMode }, - { NULL } -}; + {"inputMode", (getter)cms_transform_getattr_inputMode}, + {"outputMode", (getter)cms_transform_getattr_outputMode}, + {NULL}}; static PyTypeObject CmsTransform_Type = { - PyVarObject_HEAD_INIT(NULL, 0) - "CmsTransform", sizeof(CmsTransformObject), 0, + PyVarObject_HEAD_INIT(NULL, 0) "CmsTransform", + sizeof(CmsTransformObject), + 0, /* methods */ - (destructor) cms_transform_dealloc, /*tp_dealloc*/ - 0, /*tp_print*/ - 0, /*tp_getattr*/ - 0, /*tp_setattr*/ - 0, /*tp_compare*/ - 0, /*tp_repr*/ - 0, /*tp_as_number */ - 0, /*tp_as_sequence */ - 0, /*tp_as_mapping */ - 0, /*tp_hash*/ - 0, /*tp_call*/ - 0, /*tp_str*/ - 0, /*tp_getattro*/ - 0, /*tp_setattro*/ - 0, /*tp_as_buffer*/ - Py_TPFLAGS_DEFAULT, /*tp_flags*/ - 0, /*tp_doc*/ - 0, /*tp_traverse*/ - 0, /*tp_clear*/ - 0, /*tp_richcompare*/ - 0, /*tp_weaklistoffset*/ - 0, /*tp_iter*/ - 0, /*tp_iternext*/ - cms_transform_methods, /*tp_methods*/ - 0, /*tp_members*/ - cms_transform_getsetters, /*tp_getset*/ + (destructor)cms_transform_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number */ + 0, /*tp_as_sequence */ + 0, /*tp_as_mapping */ + 0, /*tp_hash*/ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT, /*tp_flags*/ + 0, /*tp_doc*/ + 0, /*tp_traverse*/ + 0, /*tp_clear*/ + 0, /*tp_richcompare*/ + 0, /*tp_weaklistoffset*/ + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + cms_transform_methods, /*tp_methods*/ + 0, /*tp_members*/ + cms_transform_getsetters, /*tp_getset*/ }; static int -setup_module(PyObject* m) { +setup_module(PyObject *m) { PyObject *d; PyObject *v; - - d = PyModule_GetDict(m); + int vn; CmsProfile_Type.tp_new = PyType_GenericNew; @@ -1591,7 +1521,17 @@ setup_module(PyObject* m) { d = PyModule_GetDict(m); - v = PyUnicode_FromFormat("%d.%d", LCMS_VERSION / 100, LCMS_VERSION % 100); + /* this check is also in PIL.features.pilinfo() */ +#if LCMS_VERSION < 2070 + vn = LCMS_VERSION; +#else + vn = cmsGetEncodedCMMversion(); +#endif + if (vn % 10) { + v = PyUnicode_FromFormat("%d.%d.%d", vn / 1000, (vn / 10) % 100, vn % 10); + } else { + v = PyUnicode_FromFormat("%d.%d", vn / 1000, (vn / 10) % 100); + } PyDict_SetItemString(d, "littlecms_version", v); return 0; @@ -1599,20 +1539,21 @@ setup_module(PyObject* m) { PyMODINIT_FUNC PyInit__imagingcms(void) { - PyObject* m; + PyObject *m; static PyModuleDef module_def = { PyModuleDef_HEAD_INIT, - "_imagingcms", /* m_name */ - NULL, /* m_doc */ - -1, /* m_size */ - pyCMSdll_methods, /* m_methods */ + "_imagingcms", /* m_name */ + NULL, /* m_doc */ + -1, /* m_size */ + pyCMSdll_methods, /* m_methods */ }; m = PyModule_Create(&module_def); - if (setup_module(m) < 0) + if (setup_module(m) < 0) { return NULL; + } PyDateTime_IMPORT; diff --git a/src/_imagingft.c b/src/_imagingft.c index 62a4c283e..d73c6c2d5 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -20,23 +20,27 @@ #define PY_SSIZE_T_CLEAN #include "Python.h" -#include "Imaging.h" +#include "libImaging/Imaging.h" #include #include FT_FREETYPE_H #include FT_GLYPH_H +#include FT_BITMAP_H #include FT_STROKER_H #include FT_MULTIPLE_MASTERS_H #include FT_SFNT_NAMES_H +#ifdef FT_COLOR_H +#include FT_COLOR_H +#endif #define KEEP_PY_UNICODE -#if !defined(_MSC_VER) +#ifndef _WIN32 #include #endif #if !defined(FT_LOAD_TARGET_MONO) -#define FT_LOAD_TARGET_MONO FT_LOAD_MONOCHROME +#define FT_LOAD_TARGET_MONO FT_LOAD_MONOCHROME #endif /* -------------------------------------------------------------------- */ @@ -45,73 +49,63 @@ #undef FTERRORS_H #undef __FTERRORS_H__ -#define FT_ERRORDEF( e, v, s ) { e, s }, -#define FT_ERROR_START_LIST { -#define FT_ERROR_END_LIST { 0, 0 } }; +#define FT_ERRORDEF(e, v, s) {e, s}, +#define FT_ERROR_START_LIST { +#define FT_ERROR_END_LIST \ + { 0, 0 } \ + } \ + ; -#include +#include "libImaging/raqm.h" #define LAYOUT_FALLBACK 0 #define LAYOUT_RAQM 1 -typedef struct -{ - int index, x_offset, x_advance, y_offset, y_advance; - unsigned int cluster; +typedef struct { + int index, x_offset, x_advance, y_offset, y_advance; + unsigned int cluster; } GlyphInfo; struct { int code; - const char* message; + const char *message; } ft_errors[] = #include FT_ERRORS_H -/* -------------------------------------------------------------------- */ -/* font objects */ + /* -------------------------------------------------------------------- */ + /* font objects */ -static FT_Library library; + static FT_Library library; typedef struct { - PyObject_HEAD - FT_Face face; + PyObject_HEAD FT_Face face; unsigned char *font_bytes; int layout_engine; } FontObject; static PyTypeObject Font_Type; -typedef bool (*t_raqm_version_atleast)(unsigned int major, - unsigned int minor, - unsigned int micro); -typedef raqm_t* (*t_raqm_create)(void); -typedef int (*t_raqm_set_text)(raqm_t *rq, - const uint32_t *text, - size_t len); -typedef bool (*t_raqm_set_text_utf8) (raqm_t *rq, - const char *text, - size_t len); -typedef bool (*t_raqm_set_par_direction) (raqm_t *rq, - raqm_direction_t dir); -typedef bool (*t_raqm_set_language) (raqm_t *rq, - const char *lang, - size_t start, - size_t len); -typedef bool (*t_raqm_add_font_feature) (raqm_t *rq, - const char *feature, - int len); -typedef bool (*t_raqm_set_freetype_face) (raqm_t *rq, - FT_Face face); -typedef bool (*t_raqm_layout) (raqm_t *rq); -typedef raqm_glyph_t* (*t_raqm_get_glyphs) (raqm_t *rq, - size_t *length); -typedef raqm_glyph_t_01* (*t_raqm_get_glyphs_01) (raqm_t *rq, - size_t *length); -typedef void (*t_raqm_destroy) (raqm_t *rq); +typedef const char *(*t_raqm_version_string)(void); +typedef bool (*t_raqm_version_atleast)( + unsigned int major, unsigned int minor, unsigned int micro); +typedef raqm_t *(*t_raqm_create)(void); +typedef int (*t_raqm_set_text)(raqm_t *rq, const uint32_t *text, size_t len); +typedef bool (*t_raqm_set_text_utf8)(raqm_t *rq, const char *text, size_t len); +typedef bool (*t_raqm_set_par_direction)(raqm_t *rq, raqm_direction_t dir); +typedef bool (*t_raqm_set_language)( + raqm_t *rq, const char *lang, size_t start, size_t len); +typedef bool (*t_raqm_add_font_feature)(raqm_t *rq, const char *feature, int len); +typedef bool (*t_raqm_set_freetype_face)(raqm_t *rq, FT_Face face); +typedef bool (*t_raqm_layout)(raqm_t *rq); +typedef raqm_glyph_t *(*t_raqm_get_glyphs)(raqm_t *rq, size_t *length); +typedef raqm_glyph_t_01 *(*t_raqm_get_glyphs_01)(raqm_t *rq, size_t *length); +typedef void (*t_raqm_destroy)(raqm_t *rq); typedef struct { - void* raqm; + void *raqm; int version; + t_raqm_version_string version_string; t_raqm_version_atleast version_atleast; t_raqm_create create; t_raqm_set_text set_text; @@ -128,106 +122,114 @@ typedef struct { static p_raqm_func p_raqm; +/* round a 26.6 pixel coordinate to the nearest integer */ +#define PIXEL(x) ((((x) + 32) & -64) >> 6) -/* round a 26.6 pixel coordinate to the nearest larger integer */ -#define PIXEL(x) ((((x)+63) & -64)>>6) - -static PyObject* -geterror(int code) -{ +static PyObject * +geterror(int code) { int i; - for (i = 0; ft_errors[i].message; i++) + for (i = 0; ft_errors[i].message; i++) { if (ft_errors[i].code == code) { - PyErr_SetString(PyExc_IOError, ft_errors[i].message); + PyErr_SetString(PyExc_OSError, ft_errors[i].message); return NULL; } + } - PyErr_SetString(PyExc_IOError, "unknown freetype error"); + PyErr_SetString(PyExc_OSError, "unknown freetype error"); return NULL; } static int -setraqm(void) -{ +setraqm(void) { /* set the static function pointers for dynamic raqm linking */ p_raqm.raqm = NULL; /* Microsoft needs a totally different system */ -#if !defined(_MSC_VER) +#ifndef _WIN32 p_raqm.raqm = dlopen("libraqm.so.0", RTLD_LAZY); if (!p_raqm.raqm) { p_raqm.raqm = dlopen("libraqm.dylib", RTLD_LAZY); } #else p_raqm.raqm = LoadLibrary("libraqm"); + /* MSYS */ + if (!p_raqm.raqm) { + p_raqm.raqm = LoadLibrary("libraqm-0"); + } #endif if (!p_raqm.raqm) { return 1; } -#if !defined(_MSC_VER) - p_raqm.version_atleast = (t_raqm_version_atleast)dlsym(p_raqm.raqm, "raqm_version_atleast"); +#ifndef _WIN32 + p_raqm.version_string = + (t_raqm_version_string)dlsym(p_raqm.raqm, "raqm_version_string"); + p_raqm.version_atleast = + (t_raqm_version_atleast)dlsym(p_raqm.raqm, "raqm_version_atleast"); p_raqm.create = (t_raqm_create)dlsym(p_raqm.raqm, "raqm_create"); p_raqm.set_text = (t_raqm_set_text)dlsym(p_raqm.raqm, "raqm_set_text"); - p_raqm.set_text_utf8 = (t_raqm_set_text_utf8)dlsym(p_raqm.raqm, "raqm_set_text_utf8"); - p_raqm.set_par_direction = (t_raqm_set_par_direction)dlsym(p_raqm.raqm, "raqm_set_par_direction"); + p_raqm.set_text_utf8 = + (t_raqm_set_text_utf8)dlsym(p_raqm.raqm, "raqm_set_text_utf8"); + p_raqm.set_par_direction = + (t_raqm_set_par_direction)dlsym(p_raqm.raqm, "raqm_set_par_direction"); p_raqm.set_language = (t_raqm_set_language)dlsym(p_raqm.raqm, "raqm_set_language"); - p_raqm.add_font_feature = (t_raqm_add_font_feature)dlsym(p_raqm.raqm, "raqm_add_font_feature"); - p_raqm.set_freetype_face = (t_raqm_set_freetype_face)dlsym(p_raqm.raqm, "raqm_set_freetype_face"); + p_raqm.add_font_feature = + (t_raqm_add_font_feature)dlsym(p_raqm.raqm, "raqm_add_font_feature"); + p_raqm.set_freetype_face = + (t_raqm_set_freetype_face)dlsym(p_raqm.raqm, "raqm_set_freetype_face"); p_raqm.layout = (t_raqm_layout)dlsym(p_raqm.raqm, "raqm_layout"); p_raqm.destroy = (t_raqm_destroy)dlsym(p_raqm.raqm, "raqm_destroy"); - if(dlsym(p_raqm.raqm, "raqm_index_to_position")) { + if (dlsym(p_raqm.raqm, "raqm_index_to_position")) { p_raqm.get_glyphs = (t_raqm_get_glyphs)dlsym(p_raqm.raqm, "raqm_get_glyphs"); p_raqm.version = 2; } else { p_raqm.version = 1; - p_raqm.get_glyphs_01 = (t_raqm_get_glyphs_01)dlsym(p_raqm.raqm, "raqm_get_glyphs"); + p_raqm.get_glyphs_01 = + (t_raqm_get_glyphs_01)dlsym(p_raqm.raqm, "raqm_get_glyphs"); } if (dlerror() || - !(p_raqm.create && - p_raqm.set_text && - p_raqm.set_text_utf8 && - p_raqm.set_par_direction && - p_raqm.set_language && - p_raqm.add_font_feature && - p_raqm.set_freetype_face && - p_raqm.layout && - (p_raqm.get_glyphs || p_raqm.get_glyphs_01) && - p_raqm.destroy)) { + !(p_raqm.create && p_raqm.set_text && p_raqm.set_text_utf8 && + p_raqm.set_par_direction && p_raqm.set_language && p_raqm.add_font_feature && + p_raqm.set_freetype_face && p_raqm.layout && + (p_raqm.get_glyphs || p_raqm.get_glyphs_01) && p_raqm.destroy)) { dlclose(p_raqm.raqm); p_raqm.raqm = NULL; return 2; } #else - p_raqm.version_atleast = (t_raqm_version_atleast)GetProcAddress(p_raqm.raqm, "raqm_version_atleast"); + p_raqm.version_string = + (t_raqm_version_string)GetProcAddress(p_raqm.raqm, "raqm_version_string"); + p_raqm.version_atleast = + (t_raqm_version_atleast)GetProcAddress(p_raqm.raqm, "raqm_version_atleast"); p_raqm.create = (t_raqm_create)GetProcAddress(p_raqm.raqm, "raqm_create"); p_raqm.set_text = (t_raqm_set_text)GetProcAddress(p_raqm.raqm, "raqm_set_text"); - p_raqm.set_text_utf8 = (t_raqm_set_text_utf8)GetProcAddress(p_raqm.raqm, "raqm_set_text_utf8"); - p_raqm.set_par_direction = (t_raqm_set_par_direction)GetProcAddress(p_raqm.raqm, "raqm_set_par_direction"); - p_raqm.set_language = (t_raqm_set_language)GetProcAddress(p_raqm.raqm, "raqm_set_language"); - p_raqm.add_font_feature = (t_raqm_add_font_feature)GetProcAddress(p_raqm.raqm, "raqm_add_font_feature"); - p_raqm.set_freetype_face = (t_raqm_set_freetype_face)GetProcAddress(p_raqm.raqm, "raqm_set_freetype_face"); + p_raqm.set_text_utf8 = + (t_raqm_set_text_utf8)GetProcAddress(p_raqm.raqm, "raqm_set_text_utf8"); + p_raqm.set_par_direction = + (t_raqm_set_par_direction)GetProcAddress(p_raqm.raqm, "raqm_set_par_direction"); + p_raqm.set_language = + (t_raqm_set_language)GetProcAddress(p_raqm.raqm, "raqm_set_language"); + p_raqm.add_font_feature = + (t_raqm_add_font_feature)GetProcAddress(p_raqm.raqm, "raqm_add_font_feature"); + p_raqm.set_freetype_face = + (t_raqm_set_freetype_face)GetProcAddress(p_raqm.raqm, "raqm_set_freetype_face"); p_raqm.layout = (t_raqm_layout)GetProcAddress(p_raqm.raqm, "raqm_layout"); p_raqm.destroy = (t_raqm_destroy)GetProcAddress(p_raqm.raqm, "raqm_destroy"); - if(GetProcAddress(p_raqm.raqm, "raqm_index_to_position")) { - p_raqm.get_glyphs = (t_raqm_get_glyphs)GetProcAddress(p_raqm.raqm, "raqm_get_glyphs"); + if (GetProcAddress(p_raqm.raqm, "raqm_index_to_position")) { + p_raqm.get_glyphs = + (t_raqm_get_glyphs)GetProcAddress(p_raqm.raqm, "raqm_get_glyphs"); p_raqm.version = 2; } else { p_raqm.version = 1; - p_raqm.get_glyphs_01 = (t_raqm_get_glyphs_01)GetProcAddress(p_raqm.raqm, "raqm_get_glyphs"); + p_raqm.get_glyphs_01 = + (t_raqm_get_glyphs_01)GetProcAddress(p_raqm.raqm, "raqm_get_glyphs"); } - if (!(p_raqm.create && - p_raqm.set_text && - p_raqm.set_text_utf8 && - p_raqm.set_par_direction && - p_raqm.set_language && - p_raqm.add_font_feature && - p_raqm.set_freetype_face && - p_raqm.layout && - (p_raqm.get_glyphs || p_raqm.get_glyphs_01) && - p_raqm.destroy)) { + if (!(p_raqm.create && p_raqm.set_text && p_raqm.set_text_utf8 && + p_raqm.set_par_direction && p_raqm.set_language && p_raqm.add_font_feature && + p_raqm.set_freetype_face && p_raqm.layout && + (p_raqm.get_glyphs || p_raqm.get_glyphs_01) && p_raqm.destroy)) { FreeLibrary(p_raqm.raqm); p_raqm.raqm = NULL; return 2; @@ -237,45 +239,49 @@ setraqm(void) return 0; } -static PyObject* -getfont(PyObject* self_, PyObject* args, PyObject* kw) -{ +static PyObject * +getfont(PyObject *self_, PyObject *args, PyObject *kw) { /* create a font object from a file name and a size (in pixels) */ - FontObject* self; + FontObject *self; int error = 0; - char* filename = NULL; + char *filename = NULL; Py_ssize_t size; Py_ssize_t index = 0; Py_ssize_t layout_engine = 0; - unsigned char* encoding; - unsigned char* font_bytes; + unsigned char *encoding; + unsigned char *font_bytes; Py_ssize_t font_bytes_size = 0; - static char* kwlist[] = { - "filename", "size", "index", "encoding", "font_bytes", - "layout_engine", NULL - }; + static char *kwlist[] = { + "filename", "size", "index", "encoding", "font_bytes", "layout_engine", NULL}; if (!library) { - PyErr_SetString( - PyExc_IOError, - "failed to initialize FreeType library" - ); + PyErr_SetString(PyExc_OSError, "failed to initialize FreeType library"); return NULL; } - if (!PyArg_ParseTupleAndKeywords(args, kw, "etn|nsy#n", kwlist, - Py_FileSystemDefaultEncoding, &filename, - &size, &index, &encoding, &font_bytes, - &font_bytes_size, &layout_engine)) { + if (!PyArg_ParseTupleAndKeywords( + args, + kw, + "etn|nsy#n", + kwlist, + Py_FileSystemDefaultEncoding, + &filename, + &size, + &index, + &encoding, + &font_bytes, + &font_bytes_size, + &layout_engine)) { return NULL; } self = PyObject_New(FontObject, &Font_Type); if (!self) { - if (filename) + if (filename) { PyMem_Free(filename); + } return NULL; } @@ -290,26 +296,31 @@ getfont(PyObject* self_, PyObject* args, PyObject* kw) /* Don't free this before FT_Done_Face */ self->font_bytes = PyMem_Malloc(font_bytes_size); if (!self->font_bytes) { - error = 65; // Out of Memory in Freetype. + error = 65; // Out of Memory in Freetype. } if (!error) { memcpy(self->font_bytes, font_bytes, (size_t)font_bytes_size); - error = FT_New_Memory_Face(library, (FT_Byte*)self->font_bytes, - font_bytes_size, index, &self->face); + error = FT_New_Memory_Face( + library, + (FT_Byte *)self->font_bytes, + font_bytes_size, + index, + &self->face); } } - if (!error) + if (!error) { error = FT_Set_Pixel_Sizes(self->face, 0, size); + } - if (!error && encoding && strlen((char*) encoding) == 4) { - FT_Encoding encoding_tag = FT_MAKE_TAG( - encoding[0], encoding[1], encoding[2], encoding[3] - ); + if (!error && encoding && strlen((char *)encoding) == 4) { + FT_Encoding encoding_tag = + FT_MAKE_TAG(encoding[0], encoding[1], encoding[2], encoding[3]); error = FT_Select_Charmap(self->face, encoding_tag); } - if (filename) - PyMem_Free(filename); + if (filename) { + PyMem_Free(filename); + } if (error) { if (self->font_bytes) { @@ -320,15 +331,15 @@ getfont(PyObject* self_, PyObject* args, PyObject* kw) return geterror(error); } - return (PyObject*) self; + return (PyObject *)self; } static int -font_getchar(PyObject* string, int index, FT_ULong* char_out) -{ +font_getchar(PyObject *string, int index, FT_ULong *char_out) { if (PyUnicode_Check(string)) { - if (index >= PyUnicode_GET_LENGTH(string)) + if (index >= PyUnicode_GET_LENGTH(string)) { return 0; + } *char_out = PyUnicode_READ_CHAR(string, index); return 1; } @@ -336,9 +347,15 @@ font_getchar(PyObject* string, int index, FT_ULong* char_out) } static size_t -text_layout_raqm(PyObject* string, FontObject* self, const char* dir, PyObject *features, - const char* lang, GlyphInfo **glyph_info, int mask) -{ +text_layout_raqm( + PyObject *string, + FontObject *self, + const char *dir, + PyObject *features, + const char *lang, + GlyphInfo **glyph_info, + int mask, + int color) { size_t i = 0, count = 0, start = 0; raqm_t *rq; raqm_glyph_t *glyphs = NULL; @@ -351,27 +368,6 @@ text_layout_raqm(PyObject* string, FontObject* self, const char* dir, PyObject * goto failed; } -#if (defined(PYPY_VERSION_NUM) && (PYPY_VERSION_NUM < 0x07020000)) - if (PyUnicode_Check(string)) { - Py_UNICODE *text = PyUnicode_AS_UNICODE(string); - Py_ssize_t size = PyUnicode_GET_SIZE(string); - if (! size) { - /* return 0 and clean up, no glyphs==no size, - and raqm fails with empty strings */ - goto failed; - } - if (!(*p_raqm.set_text)(rq, (const uint32_t *)(text), size)) { - PyErr_SetString(PyExc_ValueError, "raqm_set_text() failed"); - goto failed; - } - if (lang) { - if (!(*p_raqm.set_language)(rq, lang, start, size)) { - PyErr_SetString(PyExc_ValueError, "raqm_set_language() failed"); - goto failed; - } - } - } -#else if (PyUnicode_Check(string)) { Py_UCS4 *text = PyUnicode_AsUCS4Copy(string); Py_ssize_t size = PyUnicode_GET_LENGTH(string); @@ -392,27 +388,28 @@ text_layout_raqm(PyObject* string, FontObject* self, const char* dir, PyObject * goto failed; } } - } -#endif - else { + } else { PyErr_SetString(PyExc_TypeError, "expected string"); goto failed; } direction = RAQM_DIRECTION_DEFAULT; if (dir) { - if (strcmp(dir, "rtl") == 0) + if (strcmp(dir, "rtl") == 0) { direction = RAQM_DIRECTION_RTL; - else if (strcmp(dir, "ltr") == 0) + } else if (strcmp(dir, "ltr") == 0) { direction = RAQM_DIRECTION_LTR; - else if (strcmp(dir, "ttb") == 0) { + } else if (strcmp(dir, "ttb") == 0) { direction = RAQM_DIRECTION_TTB; if (p_raqm.version_atleast == NULL || !(*p_raqm.version_atleast)(0, 7, 0)) { - PyErr_SetString(PyExc_ValueError, "libraqm 0.7 or greater required for 'ttb' direction"); + PyErr_SetString( + PyExc_ValueError, + "libraqm 0.7 or greater required for 'ttb' direction"); goto failed; } } else { - PyErr_SetString(PyExc_ValueError, "direction must be either 'rtl', 'ltr' or 'ttb'"); + PyErr_SetString( + PyExc_ValueError, "direction must be either 'rtl', 'ltr' or 'ttb'"); goto failed; } } @@ -443,8 +440,9 @@ text_layout_raqm(PyObject* string, FontObject* self, const char* dir, PyObject * if (PyUnicode_Check(item)) { bytes = PyUnicode_AsUTF8String(item); - if (bytes == NULL) + if (bytes == NULL) { goto failed; + } feature = PyBytes_AS_STRING(bytes); size = PyBytes_GET_SIZE(bytes); } @@ -514,9 +512,15 @@ failed: } static size_t -text_layout_fallback(PyObject* string, FontObject* self, const char* dir, PyObject *features, - const char* lang, GlyphInfo **glyph_info, int mask) -{ +text_layout_fallback( + PyObject *string, + FontObject *self, + const char *dir, + PyObject *features, + const char *lang, + GlyphInfo **glyph_info, + int mask, + int color) { int error, load_flags; FT_ULong ch; Py_ssize_t count; @@ -526,7 +530,10 @@ text_layout_fallback(PyObject* string, FontObject* self, const char* dir, PyObje int i; if (features != Py_None || dir != NULL || lang != NULL) { - PyErr_SetString(PyExc_KeyError, "setting text direction, language or font features is not supported without libraqm"); + PyErr_SetString( + PyExc_KeyError, + "setting text direction, language or font features is not supported " + "without libraqm"); } if (!PyUnicode_Check(string)) { PyErr_SetString(PyExc_TypeError, "expected string"); @@ -535,7 +542,7 @@ text_layout_fallback(PyObject* string, FontObject* self, const char* dir, PyObje count = 0; while (font_getchar(string, count, &ch)) { - count++; + count++; } if (count == 0) { return 0; @@ -547,10 +554,15 @@ text_layout_fallback(PyObject* string, FontObject* self, const char* dir, PyObje return 0; } - load_flags = FT_LOAD_RENDER|FT_LOAD_NO_BITMAP; + load_flags = FT_LOAD_DEFAULT; if (mask) { load_flags |= FT_LOAD_TARGET_MONO; } +#ifdef FT_LOAD_COLOR + if (color) { + load_flags |= FT_LOAD_COLOR; + } +#endif for (i = 0; font_getchar(string, i, &ch); i++) { (*glyph_info)[i].index = FT_Get_Char_Index(self->face, ch); error = FT_Load_Glyph(self->face, (*glyph_info)[i].index, load_flags); @@ -559,18 +571,24 @@ text_layout_fallback(PyObject* string, FontObject* self, const char* dir, PyObje return 0; } glyph = self->face->glyph; - (*glyph_info)[i].x_offset=0; - (*glyph_info)[i].y_offset=0; + (*glyph_info)[i].x_offset = 0; + (*glyph_info)[i].y_offset = 0; if (kerning && last_index && (*glyph_info)[i].index) { FT_Vector delta; - if (FT_Get_Kerning(self->face, last_index, (*glyph_info)[i].index, - ft_kerning_default,&delta) == 0) - (*glyph_info)[i-1].x_advance += PIXEL(delta.x); - (*glyph_info)[i-1].y_advance += PIXEL(delta.y); + if (FT_Get_Kerning( + self->face, + last_index, + (*glyph_info)[i].index, + ft_kerning_default, + &delta) == 0) { + (*glyph_info)[i - 1].x_advance += PIXEL(delta.x); + (*glyph_info)[i - 1].y_advance += PIXEL(delta.y); + } } (*glyph_info)[i].x_advance = glyph->metrics.horiAdvance; - (*glyph_info)[i].y_advance = glyph->metrics.vertAdvance; + // y_advance is only used in ttb, which is not supported by basic layout + (*glyph_info)[i].y_advance = 0; last_index = (*glyph_info)[i].index; (*glyph_info)[i].cluster = ch; } @@ -578,116 +596,190 @@ text_layout_fallback(PyObject* string, FontObject* self, const char* dir, PyObje } static size_t -text_layout(PyObject* string, FontObject* self, const char* dir, PyObject *features, - const char* lang, GlyphInfo **glyph_info, int mask) -{ +text_layout( + PyObject *string, + FontObject *self, + const char *dir, + PyObject *features, + const char *lang, + GlyphInfo **glyph_info, + int mask, + int color) { size_t count; if (p_raqm.raqm && self->layout_engine == LAYOUT_RAQM) { - count = text_layout_raqm(string, self, dir, features, lang, glyph_info, mask); + count = text_layout_raqm( + string, self, dir, features, lang, glyph_info, mask, color); } else { - count = text_layout_fallback(string, self, dir, features, lang, glyph_info, mask); + count = text_layout_fallback( + string, self, dir, features, lang, glyph_info, mask, color); } return count; } -static PyObject* -font_getsize(FontObject* self, PyObject* args) -{ - int x_position, x_max, x_min, y_max, y_min; - FT_Face face; - int xoffset, yoffset; - int horizontal_dir; +static PyObject * +font_getlength(FontObject *self, PyObject *args) { + int length; /* length along primary axis, in 26.6 precision */ + GlyphInfo *glyph_info = NULL; /* computed text layout */ + size_t i, count; /* glyph_info index and length */ + int horizontal_dir; /* is primary axis horizontal? */ + int mask = 0; /* is FT_LOAD_TARGET_MONO enabled? */ + int color = 0; /* is FT_LOAD_COLOR enabled? */ + const char *mode = NULL; const char *dir = NULL; const char *lang = NULL; - size_t i, count; - GlyphInfo *glyph_info = NULL; PyObject *features = Py_None; + PyObject *string; /* calculate size and bearing for a given string */ - PyObject* string; - if (!PyArg_ParseTuple(args, "O|zOz:getsize", &string, &dir, &features, &lang)) + if (!PyArg_ParseTuple( + args, "O|zzOz:getlength", &string, &mode, &dir, &features, &lang)) { return NULL; + } - count = text_layout(string, self, dir, features, lang, &glyph_info, 0); + horizontal_dir = dir && strcmp(dir, "ttb") == 0 ? 0 : 1; + + mask = mode && strcmp(mode, "1") == 0; + color = mode && strcmp(mode, "RGBA") == 0; + + count = text_layout(string, self, dir, features, lang, &glyph_info, mask, color); if (PyErr_Occurred()) { return NULL; } - face = NULL; - xoffset = yoffset = 0; - x_position = x_max = x_min = y_max = y_min = 0; + length = 0; + for (i = 0; i < count; i++) { + if (horizontal_dir) { + length += glyph_info[i].x_advance; + } else { + length -= glyph_info[i].y_advance; + } + } + + if (glyph_info) { + PyMem_Free(glyph_info); + glyph_info = NULL; + } + + return PyLong_FromLong(length); +} + +static PyObject * +font_getsize(FontObject *self, PyObject *args) { + int position; /* pen position along primary axis, in 26.6 precision */ + int advanced; /* pen position along primary axis, in pixels */ + int px, py; /* position of current glyph, in pixels */ + int x_min, x_max, y_min, y_max; /* text bounding box, in pixels */ + int x_anchor, y_anchor; /* offset of point drawn at (0, 0), in pixels */ + int load_flags; /* FreeType load_flags parameter */ + int error; + FT_Face face; + FT_Glyph glyph; + FT_BBox bbox; /* glyph bounding box */ + GlyphInfo *glyph_info = NULL; /* computed text layout */ + size_t i, count; /* glyph_info index and length */ + int horizontal_dir; /* is primary axis horizontal? */ + int mask = 0; /* is FT_LOAD_TARGET_MONO enabled? */ + int color = 0; /* is FT_LOAD_COLOR enabled? */ + const char *mode = NULL; + const char *dir = NULL; + const char *lang = NULL; + const char *anchor = NULL; + PyObject *features = Py_None; + PyObject *string; + + /* calculate size and bearing for a given string */ + + if (!PyArg_ParseTuple( + args, "O|zzOzz:getsize", &string, &mode, &dir, &features, &lang, &anchor)) { + return NULL; + } horizontal_dir = dir && strcmp(dir, "ttb") == 0 ? 0 : 1; - for (i = 0; i < count; i++) { - int index, error, offset, x_advanced; - FT_BBox bbox; - FT_Glyph glyph; - face = self->face; - index = glyph_info[i].index; - /* Note: bitmap fonts within ttf fonts do not work, see #891/pr#960 - * Yifu Yu, 2014-10-15 - */ - error = FT_Load_Glyph(face, index, FT_LOAD_DEFAULT|FT_LOAD_NO_BITMAP); - if (error) - return geterror(error); - if (i == 0) { - if (horizontal_dir) { - if (face->glyph->metrics.horiBearingX < 0) { - xoffset = face->glyph->metrics.horiBearingX; - x_position -= xoffset; - } - } else { - if (face->glyph->metrics.vertBearingY < 0) { - yoffset = face->glyph->metrics.vertBearingY; - y_max -= yoffset; - } + mask = mode && strcmp(mode, "1") == 0; + color = mode && strcmp(mode, "RGBA") == 0; + + if (anchor == NULL) { + anchor = horizontal_dir ? "la" : "lt"; + } + if (strlen(anchor) != 2) { + goto bad_anchor; + } + + count = text_layout(string, self, dir, features, lang, &glyph_info, mask, color); + if (PyErr_Occurred()) { + return NULL; + } + + load_flags = FT_LOAD_DEFAULT; + if (mask) { + load_flags |= FT_LOAD_TARGET_MONO; + } +#ifdef FT_LOAD_COLOR + if (color) { + load_flags |= FT_LOAD_COLOR; + } +#endif + + /* + * text bounds are given by: + * - bounding boxes of individual glyphs + * - pen line, i.e. 0 to `advanced` along primary axis + * this means point (0, 0) is part of the text bounding box + */ + face = NULL; + position = x_min = x_max = y_min = y_max = 0; + for (i = 0; i < count; i++) { + face = self->face; + + if (horizontal_dir) { + px = PIXEL(position + glyph_info[i].x_offset); + py = PIXEL(glyph_info[i].y_offset); + + position += glyph_info[i].x_advance; + advanced = PIXEL(position); + if (advanced > x_max) { + x_max = advanced; + } + } else { + px = PIXEL(glyph_info[i].x_offset); + py = PIXEL(position + glyph_info[i].y_offset); + + position += glyph_info[i].y_advance; + advanced = PIXEL(position); + if (advanced < y_min) { + y_min = advanced; } } - FT_Get_Glyph(face->glyph, &glyph); - FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_SUBPIXELS, &bbox); - if (horizontal_dir) { - x_position += glyph_info[i].x_advance; + error = FT_Load_Glyph(face, glyph_info[i].index, load_flags); + if (error) { + return geterror(error); + } - x_advanced = x_position; - offset = glyph_info[i].x_advance - - face->glyph->metrics.width - - face->glyph->metrics.horiBearingX; - if (offset < 0) - x_advanced -= offset; - if (x_advanced > x_max) - x_max = x_advanced; + error = FT_Get_Glyph(face->glyph, &glyph); + if (error) { + return geterror(error); + } - bbox.yMax += glyph_info[i].y_offset; - bbox.yMin += glyph_info[i].y_offset; - if (bbox.yMax > y_max) - y_max = bbox.yMax; - if (bbox.yMin < y_min) - y_min = bbox.yMin; - - // find max distance of baseline from top - if (face->glyph->metrics.horiBearingY > yoffset) - yoffset = face->glyph->metrics.horiBearingY; - } else { - y_max -= glyph_info[i].y_advance; - - if (i == count - 1) { - // trim end gap from final glyph - int offset; - offset = -glyph_info[i].y_advance - - face->glyph->metrics.height - - face->glyph->metrics.vertBearingY; - if (offset < 0) - y_max -= offset; - } - - if (bbox.xMax > x_max) - x_max = bbox.xMax; - if (i == 0 || bbox.xMin < x_min) - x_min = bbox.xMin; + FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_PIXELS, &bbox); + bbox.xMax += px; + if (bbox.xMax > x_max) { + x_max = bbox.xMax; + } + bbox.xMin += px; + if (bbox.xMin < x_min) { + x_min = bbox.xMin; + } + bbox.yMax += py; + if (bbox.yMax > y_max) { + y_max = bbox.yMax; + } + bbox.yMin += py; + if (bbox.yMin < y_min) { + y_min = bbox.yMin; } FT_Done_Glyph(glyph); @@ -698,71 +790,166 @@ font_getsize(FontObject* self, PyObject* args) glyph_info = NULL; } + x_anchor = y_anchor = 0; if (face) { if (horizontal_dir) { - // left bearing - if (xoffset < 0) - x_max -= xoffset; - else - xoffset = 0; - - /* difference between the font ascender and the distance of - * the baseline from the top */ - yoffset = PIXEL(self->face->size->metrics.ascender - yoffset); + switch (anchor[0]) { + case 'l': // left + x_anchor = 0; + break; + case 'm': // middle (left + right) / 2 + x_anchor = PIXEL(position / 2); + break; + case 'r': // right + x_anchor = PIXEL(position); + break; + case 's': // vertical baseline + default: + goto bad_anchor; + } + switch (anchor[1]) { + case 'a': // ascender + y_anchor = PIXEL(self->face->size->metrics.ascender); + break; + case 't': // top + y_anchor = y_max; + break; + case 'm': // middle (ascender + descender) / 2 + y_anchor = PIXEL( + (self->face->size->metrics.ascender + + self->face->size->metrics.descender) / + 2); + break; + case 's': // horizontal baseline + y_anchor = 0; + break; + case 'b': // bottom + y_anchor = y_min; + break; + case 'd': // descender + y_anchor = PIXEL(self->face->size->metrics.descender); + break; + default: + goto bad_anchor; + } } else { - // top bearing - if (yoffset < 0) - y_max -= yoffset; - else - yoffset = 0; + switch (anchor[0]) { + case 'l': // left + x_anchor = x_min; + break; + case 'm': // middle (left + right) / 2 + x_anchor = (x_min + x_max) / 2; + break; + case 'r': // right + x_anchor = x_max; + break; + case 's': // vertical baseline + x_anchor = 0; + break; + default: + goto bad_anchor; + } + switch (anchor[1]) { + case 't': // top + y_anchor = 0; + break; + case 'm': // middle (top + bottom) / 2 + y_anchor = PIXEL(position / 2); + break; + case 'b': // bottom + y_anchor = PIXEL(position); + break; + case 'a': // ascender + case 's': // horizontal baseline + case 'd': // descender + default: + goto bad_anchor; + } } } return Py_BuildValue( "(ii)(ii)", - PIXEL(x_max - x_min), PIXEL(y_max - y_min), - PIXEL(xoffset), yoffset - ); + (x_max - x_min), + (y_max - y_min), + (-x_anchor + x_min), + -(-y_anchor + y_max)); + +bad_anchor: + PyErr_Format(PyExc_ValueError, "bad anchor specified: %s", anchor); + return NULL; } -static PyObject* -font_render(FontObject* self, PyObject* args) -{ - int x; - unsigned int y; - Imaging im; - int index, error, ascender, horizontal_dir; - int load_flags; - unsigned char *source; +static PyObject * +font_render(FontObject *self, PyObject *args) { + int x, y; /* pen position, in 26.6 precision */ + int px, py; /* position of current glyph, in pixels */ + int x_min, y_max; /* text offset in 26.6 precision */ + int load_flags; /* FreeType load_flags parameter */ + int error; FT_Glyph glyph; FT_GlyphSlot glyph_slot; FT_Bitmap bitmap; + FT_Bitmap bitmap_converted; /* initialized lazily, for non-8bpp fonts */ FT_BitmapGlyph bitmap_glyph; - int stroke_width = 0; FT_Stroker stroker = NULL; - FT_Int left; - /* render string into given buffer (the buffer *must* have - the right size, or this will crash) */ - PyObject* string; + int bitmap_converted_ready = 0; /* has bitmap_converted been initialized */ + GlyphInfo *glyph_info = NULL; /* computed text layout */ + size_t i, count; /* glyph_info index and length */ + int xx, yy; /* pixel offset of current glyph bitmap */ + int x0, x1; /* horizontal bounds of glyph bitmap to copy */ + unsigned int bitmap_y; /* glyph bitmap y index */ + unsigned char *source; /* glyph bitmap source buffer */ + unsigned char convert_scale; /* scale factor for non-8bpp bitmaps */ + Imaging im; Py_ssize_t id; - int mask = 0; - int temp; - int xx, x0, x1; - int yy; - unsigned int bitmap_y; + int mask = 0; /* is FT_LOAD_TARGET_MONO enabled? */ + int color = 0; /* is FT_LOAD_COLOR enabled? */ + int stroke_width = 0; + PY_LONG_LONG foreground_ink_long = 0; + unsigned int foreground_ink; + const char *mode = NULL; const char *dir = NULL; const char *lang = NULL; - size_t i, count; - GlyphInfo *glyph_info; - PyObject *features = NULL; + PyObject *features = Py_None; + PyObject *string; - if (!PyArg_ParseTuple(args, "On|izOzi:render", &string, &id, &mask, &dir, &features, &lang, - &stroke_width)) { + /* render string into given buffer (the buffer *must* have + the right size, or this will crash) */ + + if (!PyArg_ParseTuple( + args, + "On|zzOziL:render", + &string, + &id, + &mode, + &dir, + &features, + &lang, + &stroke_width, + &foreground_ink_long)) { return NULL; } - glyph_info = NULL; - count = text_layout(string, self, dir, features, lang, &glyph_info, mask); + mask = mode && strcmp(mode, "1") == 0; + color = mode && strcmp(mode, "RGBA") == 0; + + foreground_ink = foreground_ink_long; + +#ifdef FT_COLOR_H + if (color) { + FT_Color foreground_color; + FT_Byte *ink = (FT_Byte *)&foreground_ink; + foreground_color.red = ink[0]; + foreground_color.green = ink[1]; + foreground_color.blue = ink[2]; + foreground_color.alpha = + (FT_Byte)255; /* ink alpha is handled in ImageDraw.text */ + FT_Palette_Set_Foreground_Color(self->face, foreground_color); + } +#endif + + count = text_layout(string, self, dir, features, lang, &glyph_info, mask, color); if (PyErr_Occurred()) { return NULL; } @@ -776,23 +963,36 @@ font_render(FontObject* self, PyObject* args) return geterror(error); } - FT_Stroker_Set(stroker, (FT_Fixed)stroke_width*64, FT_STROKER_LINECAP_ROUND, FT_STROKER_LINEJOIN_ROUND, 0); + FT_Stroker_Set( + stroker, + (FT_Fixed)stroke_width * 64, + FT_STROKER_LINECAP_ROUND, + FT_STROKER_LINEJOIN_ROUND, + 0); } - im = (Imaging) id; - /* Note: bitmap fonts within ttf fonts do not work, see #891/pr#960 */ - load_flags = FT_LOAD_NO_BITMAP; - if (stroker == NULL) { - load_flags |= FT_LOAD_RENDER; - } + im = (Imaging)id; + load_flags = FT_LOAD_DEFAULT; if (mask) { load_flags |= FT_LOAD_TARGET_MONO; } +#ifdef FT_LOAD_COLOR + if (color) { + load_flags |= FT_LOAD_COLOR; + } +#endif - ascender = 0; + /* + * calculate x_min and y_max + * must match font_getsize or there may be clipping! + */ + x = y = x_min = y_max = 0; for (i = 0; i < count; i++) { - index = glyph_info[i].index; - error = FT_Load_Glyph(self->face, index, load_flags); + px = PIXEL(x + glyph_info[i].x_offset); + py = PIXEL(y + glyph_info[i].y_offset); + + error = + FT_Load_Glyph(self->face, glyph_info[i].index, load_flags | FT_LOAD_RENDER); if (error) { return geterror(error); } @@ -800,17 +1000,30 @@ font_render(FontObject* self, PyObject* args) glyph_slot = self->face->glyph; bitmap = glyph_slot->bitmap; - temp = bitmap.rows - glyph_slot->bitmap_top; - temp -= PIXEL(glyph_info[i].y_offset); - if (temp > ascender) - ascender = temp; + if (glyph_slot->bitmap_top + py > y_max) { + y_max = glyph_slot->bitmap_top + py; + } + if (glyph_slot->bitmap_left + px < x_min) { + x_min = glyph_slot->bitmap_left + px; + } + + x += glyph_info[i].x_advance; + y += glyph_info[i].y_advance; + } + + /* set pen position to text origin */ + x = (-x_min + stroke_width) << 6; + y = (-y_max + (-stroke_width)) << 6; + + if (stroker == NULL) { + load_flags |= FT_LOAD_RENDER; } - x = y = 0; - horizontal_dir = dir && strcmp(dir, "ttb") == 0 ? 0 : 1; for (i = 0; i < count; i++) { - index = glyph_info[i].index; - error = FT_Load_Glyph(self->face, index, load_flags); + px = PIXEL(x + glyph_info[i].x_offset); + py = PIXEL(y + glyph_info[i].y_offset); + + error = FT_Load_Glyph(self->face, glyph_info[i].index, load_flags); if (error) { return geterror(error); } @@ -832,236 +1045,316 @@ font_render(FontObject* self, PyObject* args) bitmap_glyph = (FT_BitmapGlyph)glyph; bitmap = bitmap_glyph->bitmap; - left = bitmap_glyph->left; + xx = px + bitmap_glyph->left; + yy = -(py + bitmap_glyph->top); } else { bitmap = glyph_slot->bitmap; - left = glyph_slot->bitmap_left; + xx = px + glyph_slot->bitmap_left; + yy = -(py + glyph_slot->bitmap_top); } - if (horizontal_dir) { - if (i == 0 && glyph_slot->metrics.horiBearingX < 0) { - x = -glyph_slot->metrics.horiBearingX; - } - xx = PIXEL(x) + left; - xx += PIXEL(glyph_info[i].x_offset) + stroke_width; - } else { - if (glyph_slot->metrics.vertBearingX < 0) { - x = -glyph_slot->metrics.vertBearingX; - } - xx = im->xsize / 2 - bitmap.width / 2; + /* convert non-8bpp bitmaps */ + switch (bitmap.pixel_mode) { + case FT_PIXEL_MODE_MONO: + convert_scale = 255; + break; + case FT_PIXEL_MODE_GRAY2: + convert_scale = 255 / 3; + break; + case FT_PIXEL_MODE_GRAY4: + convert_scale = 255 / 15; + break; + default: + convert_scale = 1; + } + switch (bitmap.pixel_mode) { + case FT_PIXEL_MODE_MONO: + case FT_PIXEL_MODE_GRAY2: + case FT_PIXEL_MODE_GRAY4: + if (!bitmap_converted_ready) { +#if FREETYPE_MAJOR > 2 || (FREETYPE_MAJOR == 2 && FREETYPE_MINOR > 6) + FT_Bitmap_Init(&bitmap_converted); +#else + FT_Bitmap_New(&bitmap_converted); +#endif + bitmap_converted_ready = 1; + } + error = FT_Bitmap_Convert(library, &bitmap, &bitmap_converted, 1); + if (error) { + geterror(error); + goto glyph_error; + } + bitmap = bitmap_converted; + /* bitmap is now FT_PIXEL_MODE_GRAY, fall through */ + case FT_PIXEL_MODE_GRAY: + break; +#ifdef FT_LOAD_COLOR + case FT_PIXEL_MODE_BGRA: + if (color) { + break; + } + /* we didn't ask for color, fall through to default */ +#endif + default: + PyErr_SetString(PyExc_IOError, "unsupported bitmap pixel mode"); + goto glyph_error; } + /* clip glyph bitmap width to target image bounds */ x0 = 0; x1 = bitmap.width; - if (xx < 0) + if (xx < 0) { x0 = -xx; - if (xx + x1 > im->xsize) + } + if (xx + x1 > im->xsize) { x1 = im->xsize - xx; + } - source = (unsigned char*) bitmap.buffer; - for (bitmap_y = 0; bitmap_y < bitmap.rows; bitmap_y++) { - if (horizontal_dir) { - yy = bitmap_y + im->ysize - (PIXEL(glyph_slot->metrics.horiBearingY) + ascender); - yy -= PIXEL(glyph_info[i].y_offset) + stroke_width * 2; - } else { - yy = bitmap_y + PIXEL(y + glyph_slot->metrics.vertBearingY) + ascender; - yy += PIXEL(glyph_info[i].y_offset); - } + source = (unsigned char *)bitmap.buffer; + for (bitmap_y = 0; bitmap_y < bitmap.rows; bitmap_y++, yy++) { + /* clip glyph bitmap height to target image bounds */ if (yy >= 0 && yy < im->ysize) { - // blend this glyph into the buffer - unsigned char *target = im->image8[yy] + xx; - if (mask) { - // use monochrome mask (on palette images, etc) - int j, k, m = 128; - for (j = k = 0; j < x1; j++) { - if (j >= x0 && (source[k] & m)) - target[j] = 255; - if (!(m >>= 1)) { - m = 128; - k++; + /* blend this glyph into the buffer */ + int k; + unsigned char v; + unsigned char *target; + if (color) { + /* target[RGB] returns the color, target[A] returns the mask */ + /* target bands get split again in ImageDraw.text */ + target = (unsigned char *)im->image[yy] + xx * 4; + } else { + target = im->image8[yy] + xx; + } +#ifdef FT_LOAD_COLOR + if (color && bitmap.pixel_mode == FT_PIXEL_MODE_BGRA) { + /* paste color glyph */ + for (k = x0; k < x1; k++) { + if (target[k * 4 + 3] < source[k * 4 + 3]) { + /* unpremultiply BGRa to RGBA */ + target[k * 4 + 0] = CLIP8( + (255 * (int)source[k * 4 + 2]) / source[k * 4 + 3]); + target[k * 4 + 1] = CLIP8( + (255 * (int)source[k * 4 + 1]) / source[k * 4 + 3]); + target[k * 4 + 2] = CLIP8( + (255 * (int)source[k * 4 + 0]) / source[k * 4 + 3]); + target[k * 4 + 3] = source[k * 4 + 3]; + } + } + } else +#endif + if (bitmap.pixel_mode == FT_PIXEL_MODE_GRAY) { + if (color) { + unsigned char *ink = (unsigned char *)&foreground_ink; + for (k = x0; k < x1; k++) { + v = source[k] * convert_scale; + if (target[k * 4 + 3] < v) { + target[k * 4 + 0] = ink[0]; + target[k * 4 + 1] = ink[1]; + target[k * 4 + 2] = ink[2]; + target[k * 4 + 3] = v; + } + } + } else { + for (k = x0; k < x1; k++) { + v = source[k] * convert_scale; + if (target[k] < v) { + target[k] = v; + } } } } else { - // use antialiased rendering - int k; - for (k = x0; k < x1; k++) { - if (target[k] < source[k]) - target[k] = source[k]; - } + PyErr_SetString(PyExc_IOError, "unsupported bitmap pixel mode"); + goto glyph_error; } } source += bitmap.pitch; } x += glyph_info[i].x_advance; - y -= glyph_info[i].y_advance; + y += glyph_info[i].y_advance; if (stroker != NULL) { FT_Done_Glyph(glyph); } } + if (bitmap_converted_ready) { + FT_Bitmap_Done(library, &bitmap_converted); + } FT_Stroker_Done(stroker); PyMem_Del(glyph_info); Py_RETURN_NONE; + +glyph_error: + if (stroker != NULL) { + FT_Done_Glyph(glyph); + } + if (bitmap_converted_ready) { + FT_Bitmap_Done(library, &bitmap_converted); + } + FT_Stroker_Done(stroker); + PyMem_Del(glyph_info); + return NULL; } -#if FREETYPE_MAJOR > 2 ||\ - (FREETYPE_MAJOR == 2 && FREETYPE_MINOR > 9) ||\ +#if FREETYPE_MAJOR > 2 || (FREETYPE_MAJOR == 2 && FREETYPE_MINOR > 9) || \ (FREETYPE_MAJOR == 2 && FREETYPE_MINOR == 9 && FREETYPE_PATCH == 1) - static PyObject* - font_getvarnames(FontObject* self, PyObject* args) - { - int error; - FT_UInt i, j, num_namedstyles, name_count; - FT_MM_Var *master; - FT_SfntName name; - PyObject *list_names, *list_name; +static PyObject * +font_getvarnames(FontObject *self) { + int error; + FT_UInt i, j, num_namedstyles, name_count; + FT_MM_Var *master; + FT_SfntName name; + PyObject *list_names, *list_name; - error = FT_Get_MM_Var(self->face, &master); - if (error) + error = FT_Get_MM_Var(self->face, &master); + if (error) { + return geterror(error); + } + + num_namedstyles = master->num_namedstyles; + list_names = PyList_New(num_namedstyles); + + name_count = FT_Get_Sfnt_Name_Count(self->face); + for (i = 0; i < name_count; i++) { + error = FT_Get_Sfnt_Name(self->face, i, &name); + if (error) { return geterror(error); + } - num_namedstyles = master->num_namedstyles; - list_names = PyList_New(num_namedstyles); + for (j = 0; j < num_namedstyles; j++) { + if (PyList_GetItem(list_names, j) != NULL) { + continue; + } - name_count = FT_Get_Sfnt_Name_Count(self->face); - for (i = 0; i < name_count; i++) { - error = FT_Get_Sfnt_Name(self->face, i, &name); - if (error) + if (master->namedstyle[j].strid == name.name_id) { + list_name = Py_BuildValue("y#", name.string, name.string_len); + PyList_SetItem(list_names, j, list_name); + break; + } + } + } + + FT_Done_MM_Var(library, master); + + return list_names; +} + +static PyObject * +font_getvaraxes(FontObject *self) { + int error; + FT_UInt i, j, num_axis, name_count; + FT_MM_Var *master; + FT_Var_Axis axis; + FT_SfntName name; + PyObject *list_axes, *list_axis, *axis_name; + error = FT_Get_MM_Var(self->face, &master); + if (error) { + return geterror(error); + } + + num_axis = master->num_axis; + name_count = FT_Get_Sfnt_Name_Count(self->face); + + list_axes = PyList_New(num_axis); + for (i = 0; i < num_axis; i++) { + axis = master->axis[i]; + + list_axis = PyDict_New(); + PyDict_SetItemString( + list_axis, "minimum", PyLong_FromLong(axis.minimum / 65536)); + PyDict_SetItemString(list_axis, "default", PyLong_FromLong(axis.def / 65536)); + PyDict_SetItemString( + list_axis, "maximum", PyLong_FromLong(axis.maximum / 65536)); + + for (j = 0; j < name_count; j++) { + error = FT_Get_Sfnt_Name(self->face, j, &name); + if (error) { return geterror(error); + } - for (j = 0; j < num_namedstyles; j++) { - if (PyList_GetItem(list_names, j) != NULL) - continue; - - if (master->namedstyle[j].strid == name.name_id) { - list_name = Py_BuildValue("y#", name.string, name.string_len); - PyList_SetItem(list_names, j, list_name); - break; - } + if (name.name_id == axis.strid) { + axis_name = Py_BuildValue("y#", name.string, name.string_len); + PyDict_SetItemString(list_axis, "name", axis_name); + break; } } - FT_Done_MM_Var(library, master); - - return list_names; + PyList_SetItem(list_axes, i, list_axis); } - static PyObject* - font_getvaraxes(FontObject* self, PyObject* args) - { - int error; - FT_UInt i, j, num_axis, name_count; - FT_MM_Var* master; - FT_Var_Axis axis; - FT_SfntName name; - PyObject *list_axes, *list_axis, *axis_name; - error = FT_Get_MM_Var(self->face, &master); - if (error) - return geterror(error); + FT_Done_MM_Var(library, master); - num_axis = master->num_axis; - name_count = FT_Get_Sfnt_Name_Count(self->face); + return list_axes; +} - list_axes = PyList_New(num_axis); - for (i = 0; i < num_axis; i++) { - axis = master->axis[i]; +static PyObject * +font_setvarname(FontObject *self, PyObject *args) { + int error; - list_axis = PyDict_New(); - PyDict_SetItemString(list_axis, "minimum", - PyLong_FromLong(axis.minimum / 65536)); - PyDict_SetItemString(list_axis, "default", - PyLong_FromLong(axis.def / 65536)); - PyDict_SetItemString(list_axis, "maximum", - PyLong_FromLong(axis.maximum / 65536)); - - for (j = 0; j < name_count; j++) { - error = FT_Get_Sfnt_Name(self->face, j, &name); - if (error) - return geterror(error); - - if (name.name_id == axis.strid) { - axis_name = Py_BuildValue("y#", name.string, name.string_len); - PyDict_SetItemString(list_axis, "name", axis_name); - break; - } - } - - PyList_SetItem(list_axes, i, list_axis); - } - - FT_Done_MM_Var(library, master); - - return list_axes; + int instance_index; + if (!PyArg_ParseTuple(args, "i", &instance_index)) { + return NULL; } - static PyObject* - font_setvarname(FontObject* self, PyObject* args) - { - int error; - - int instance_index; - if (!PyArg_ParseTuple(args, "i", &instance_index)) - return NULL; - - error = FT_Set_Named_Instance(self->face, instance_index); - if (error) - return geterror(error); - - Py_INCREF(Py_None); - return Py_None; + error = FT_Set_Named_Instance(self->face, instance_index); + if (error) { + return geterror(error); } - static PyObject* - font_setvaraxes(FontObject* self, PyObject* args) - { - int error; + Py_INCREF(Py_None); + return Py_None; +} - PyObject *axes, *item; - Py_ssize_t i, num_coords; - FT_Fixed *coords; - FT_Fixed coord; - if (!PyArg_ParseTuple(args, "O", &axes)) - return NULL; +static PyObject * +font_setvaraxes(FontObject *self, PyObject *args) { + int error; - if (!PyList_Check(axes)) { - PyErr_SetString(PyExc_TypeError, "argument must be a list"); + PyObject *axes, *item; + Py_ssize_t i, num_coords; + FT_Fixed *coords; + FT_Fixed coord; + if (!PyArg_ParseTuple(args, "O", &axes)) { + return NULL; + } + + if (!PyList_Check(axes)) { + PyErr_SetString(PyExc_TypeError, "argument must be a list"); + return NULL; + } + + num_coords = PyObject_Length(axes); + coords = malloc(2 * sizeof(coords)); + if (coords == NULL) { + return PyErr_NoMemory(); + } + for (i = 0; i < num_coords; i++) { + item = PyList_GET_ITEM(axes, i); + if (PyFloat_Check(item)) { + coord = PyFloat_AS_DOUBLE(item); + } else if (PyLong_Check(item)) { + coord = (float)PyLong_AS_LONG(item); + } else if (PyNumber_Check(item)) { + coord = PyFloat_AsDouble(item); + } else { + free(coords); + PyErr_SetString(PyExc_TypeError, "list must contain numbers"); return NULL; } - - num_coords = PyObject_Length(axes); - coords = malloc(2 * sizeof(coords)); - if (coords == NULL) { - return PyErr_NoMemory(); - } - for (i = 0; i < num_coords; i++) { - item = PyList_GET_ITEM(axes, i); - if (PyFloat_Check(item)) - coord = PyFloat_AS_DOUBLE(item); - else if (PyLong_Check(item)) - coord = (float) PyLong_AS_LONG(item); - else if (PyNumber_Check(item)) - coord = PyFloat_AsDouble(item); - else { - free(coords); - PyErr_SetString(PyExc_TypeError, "list must contain numbers"); - return NULL; - } - coords[i] = coord * 65536; - } - - error = FT_Set_Var_Design_Coordinates(self->face, num_coords, coords); - free(coords); - if (error) - return geterror(error); - - Py_INCREF(Py_None); - return Py_None; + coords[i] = coord * 65536; } + + error = FT_Set_Var_Design_Coordinates(self->face, num_coords, coords); + free(coords); + if (error) { + return geterror(error); + } + + Py_INCREF(Py_None); + return Py_None; +} #endif static void -font_dealloc(FontObject* self) -{ +font_dealloc(FontObject *self) { if (self->face) { FT_Done_Face(self->face); } @@ -1072,125 +1365,115 @@ font_dealloc(FontObject* self) } static PyMethodDef font_methods[] = { - {"render", (PyCFunction) font_render, METH_VARARGS}, - {"getsize", (PyCFunction) font_getsize, METH_VARARGS}, -#if FREETYPE_MAJOR > 2 ||\ - (FREETYPE_MAJOR == 2 && FREETYPE_MINOR > 9) ||\ + {"render", (PyCFunction)font_render, METH_VARARGS}, + {"getsize", (PyCFunction)font_getsize, METH_VARARGS}, + {"getlength", (PyCFunction)font_getlength, METH_VARARGS}, +#if FREETYPE_MAJOR > 2 || (FREETYPE_MAJOR == 2 && FREETYPE_MINOR > 9) || \ (FREETYPE_MAJOR == 2 && FREETYPE_MINOR == 9 && FREETYPE_PATCH == 1) - {"getvarnames", (PyCFunction) font_getvarnames, METH_VARARGS }, - {"getvaraxes", (PyCFunction) font_getvaraxes, METH_VARARGS }, - {"setvarname", (PyCFunction) font_setvarname, METH_VARARGS}, - {"setvaraxes", (PyCFunction) font_setvaraxes, METH_VARARGS}, + {"getvarnames", (PyCFunction)font_getvarnames, METH_NOARGS}, + {"getvaraxes", (PyCFunction)font_getvaraxes, METH_NOARGS}, + {"setvarname", (PyCFunction)font_setvarname, METH_VARARGS}, + {"setvaraxes", (PyCFunction)font_setvaraxes, METH_VARARGS}, #endif - {NULL, NULL} -}; + {NULL, NULL}}; -static PyObject* -font_getattr_family(FontObject* self, void* closure) -{ - if (self->face->family_name) +static PyObject * +font_getattr_family(FontObject *self, void *closure) { + if (self->face->family_name) { return PyUnicode_FromString(self->face->family_name); + } Py_RETURN_NONE; } -static PyObject* -font_getattr_style(FontObject* self, void* closure) -{ - if (self->face->style_name) +static PyObject * +font_getattr_style(FontObject *self, void *closure) { + if (self->face->style_name) { return PyUnicode_FromString(self->face->style_name); + } Py_RETURN_NONE; } -static PyObject* -font_getattr_ascent(FontObject* self, void* closure) -{ +static PyObject * +font_getattr_ascent(FontObject *self, void *closure) { return PyLong_FromLong(PIXEL(self->face->size->metrics.ascender)); } -static PyObject* -font_getattr_descent(FontObject* self, void* closure) -{ +static PyObject * +font_getattr_descent(FontObject *self, void *closure) { return PyLong_FromLong(-PIXEL(self->face->size->metrics.descender)); } -static PyObject* -font_getattr_height(FontObject* self, void* closure) -{ +static PyObject * +font_getattr_height(FontObject *self, void *closure) { return PyLong_FromLong(PIXEL(self->face->size->metrics.height)); } -static PyObject* -font_getattr_x_ppem(FontObject* self, void* closure) -{ +static PyObject * +font_getattr_x_ppem(FontObject *self, void *closure) { return PyLong_FromLong(self->face->size->metrics.x_ppem); } -static PyObject* -font_getattr_y_ppem(FontObject* self, void* closure) -{ +static PyObject * +font_getattr_y_ppem(FontObject *self, void *closure) { return PyLong_FromLong(self->face->size->metrics.y_ppem); } - -static PyObject* -font_getattr_glyphs(FontObject* self, void* closure) -{ +static PyObject * +font_getattr_glyphs(FontObject *self, void *closure) { return PyLong_FromLong(self->face->num_glyphs); } static struct PyGetSetDef font_getsetters[] = { - { "family", (getter) font_getattr_family }, - { "style", (getter) font_getattr_style }, - { "ascent", (getter) font_getattr_ascent }, - { "descent", (getter) font_getattr_descent }, - { "height", (getter) font_getattr_height }, - { "x_ppem", (getter) font_getattr_x_ppem }, - { "y_ppem", (getter) font_getattr_y_ppem }, - { "glyphs", (getter) font_getattr_glyphs }, - { NULL } -}; + {"family", (getter)font_getattr_family}, + {"style", (getter)font_getattr_style}, + {"ascent", (getter)font_getattr_ascent}, + {"descent", (getter)font_getattr_descent}, + {"height", (getter)font_getattr_height}, + {"x_ppem", (getter)font_getattr_x_ppem}, + {"y_ppem", (getter)font_getattr_y_ppem}, + {"glyphs", (getter)font_getattr_glyphs}, + {NULL}}; static PyTypeObject Font_Type = { - PyVarObject_HEAD_INIT(NULL, 0) - "Font", sizeof(FontObject), 0, + PyVarObject_HEAD_INIT(NULL, 0) "Font", + sizeof(FontObject), + 0, /* methods */ (destructor)font_dealloc, /* tp_dealloc */ - 0, /* tp_print */ - 0, /*tp_getattr*/ - 0, /*tp_setattr*/ - 0, /*tp_compare*/ - 0, /*tp_repr*/ - 0, /*tp_as_number */ - 0, /*tp_as_sequence */ - 0, /*tp_as_mapping */ - 0, /*tp_hash*/ - 0, /*tp_call*/ - 0, /*tp_str*/ - 0, /*tp_getattro*/ - 0, /*tp_setattro*/ - 0, /*tp_as_buffer*/ - Py_TPFLAGS_DEFAULT, /*tp_flags*/ - 0, /*tp_doc*/ - 0, /*tp_traverse*/ - 0, /*tp_clear*/ - 0, /*tp_richcompare*/ - 0, /*tp_weaklistoffset*/ - 0, /*tp_iter*/ - 0, /*tp_iternext*/ - font_methods, /*tp_methods*/ - 0, /*tp_members*/ - font_getsetters, /*tp_getset*/ + 0, /* tp_print */ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number */ + 0, /*tp_as_sequence */ + 0, /*tp_as_mapping */ + 0, /*tp_hash*/ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT, /*tp_flags*/ + 0, /*tp_doc*/ + 0, /*tp_traverse*/ + 0, /*tp_clear*/ + 0, /*tp_richcompare*/ + 0, /*tp_weaklistoffset*/ + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + font_methods, /*tp_methods*/ + 0, /*tp_members*/ + font_getsetters, /*tp_getset*/ }; static PyMethodDef _functions[] = { - {"getfont", (PyCFunction) getfont, METH_VARARGS|METH_KEYWORDS}, - {NULL, NULL} -}; + {"getfont", (PyCFunction)getfont, METH_VARARGS | METH_KEYWORDS}, {NULL, NULL}}; static int -setup_module(PyObject* m) { - PyObject* d; - PyObject* v; +setup_module(PyObject *m) { + PyObject *d; + PyObject *v; int major, minor, patch; d = PyModule_GetDict(m); @@ -1198,38 +1481,43 @@ setup_module(PyObject* m) { /* Ready object type */ PyType_Ready(&Font_Type); - if (FT_Init_FreeType(&library)) + if (FT_Init_FreeType(&library)) { return 0; /* leave it uninitialized */ + } FT_Library_Version(library, &major, &minor, &patch); v = PyUnicode_FromFormat("%d.%d.%d", major, minor, patch); PyDict_SetItemString(d, "freetype2_version", v); - setraqm(); v = PyBool_FromLong(!!p_raqm.raqm); PyDict_SetItemString(d, "HAVE_RAQM", v); + if (p_raqm.version_string) { + PyDict_SetItemString( + d, "raqm_version", PyUnicode_FromString(p_raqm.version_string())); + } return 0; } PyMODINIT_FUNC PyInit__imagingft(void) { - PyObject* m; + PyObject *m; static PyModuleDef module_def = { PyModuleDef_HEAD_INIT, - "_imagingft", /* m_name */ - NULL, /* m_doc */ - -1, /* m_size */ - _functions, /* m_methods */ + "_imagingft", /* m_name */ + NULL, /* m_doc */ + -1, /* m_size */ + _functions, /* m_methods */ }; m = PyModule_Create(&module_def); - if (setup_module(m) < 0) + if (setup_module(m) < 0) { return NULL; + } return m; } diff --git a/src/_imagingmath.c b/src/_imagingmath.c index bc66a581a..067c165b2 100644 --- a/src/_imagingmath.c +++ b/src/_imagingmath.c @@ -15,7 +15,7 @@ #include "Python.h" -#include "Imaging.h" +#include "libImaging/Imaging.h" #include "math.h" #include "float.h" @@ -23,50 +23,51 @@ #define MAX_INT32 2147483647.0 #define MIN_INT32 -2147483648.0 -#define UNOP(name, op, type)\ -void name(Imaging out, Imaging im1)\ -{\ - int x, y;\ - for (y = 0; y < out->ysize; y++) {\ - type* p0 = (type*) out->image[y];\ - type* p1 = (type*) im1->image[y];\ - for (x = 0; x < out->xsize; x++) {\ - *p0 = op(type, *p1);\ - p0++; p1++;\ - }\ - }\ -} +#define UNOP(name, op, type) \ + void name(Imaging out, Imaging im1) { \ + int x, y; \ + for (y = 0; y < out->ysize; y++) { \ + type *p0 = (type *)out->image[y]; \ + type *p1 = (type *)im1->image[y]; \ + for (x = 0; x < out->xsize; x++) { \ + *p0 = op(type, *p1); \ + p0++; \ + p1++; \ + } \ + } \ + } -#define BINOP(name, op, type)\ -void name(Imaging out, Imaging im1, Imaging im2)\ -{\ - int x, y;\ - for (y = 0; y < out->ysize; y++) {\ - type* p0 = (type*) out->image[y];\ - type* p1 = (type*) im1->image[y];\ - type* p2 = (type*) im2->image[y];\ - for (x = 0; x < out->xsize; x++) {\ - *p0 = op(type, *p1, *p2);\ - p0++; p1++; p2++;\ - }\ - }\ -} +#define BINOP(name, op, type) \ + void name(Imaging out, Imaging im1, Imaging im2) { \ + int x, y; \ + for (y = 0; y < out->ysize; y++) { \ + type *p0 = (type *)out->image[y]; \ + type *p1 = (type *)im1->image[y]; \ + type *p2 = (type *)im2->image[y]; \ + for (x = 0; x < out->xsize; x++) { \ + *p0 = op(type, *p1, *p2); \ + p0++; \ + p1++; \ + p2++; \ + } \ + } \ + } #define NEG(type, v1) -(v1) #define INVERT(type, v1) ~(v1) -#define ADD(type, v1, v2) (v1)+(v2) -#define SUB(type, v1, v2) (v1)-(v2) -#define MUL(type, v1, v2) (v1)*(v2) +#define ADD(type, v1, v2) (v1) + (v2) +#define SUB(type, v1, v2) (v1) - (v2) +#define MUL(type, v1, v2) (v1) * (v2) -#define MIN(type, v1, v2) ((v1)<(v2))?(v1):(v2) -#define MAX(type, v1, v2) ((v1)>(v2))?(v1):(v2) +#define MIN(type, v1, v2) ((v1) < (v2)) ? (v1) : (v2) +#define MAX(type, v1, v2) ((v1) > (v2)) ? (v1) : (v2) -#define AND(type, v1, v2) (v1)&(v2) -#define OR(type, v1, v2) (v1)|(v2) -#define XOR(type, v1, v2) (v1)^(v2) -#define LSHIFT(type, v1, v2) (v1)<<(v2) -#define RSHIFT(type, v1, v2) (v1)>>(v2) +#define AND(type, v1, v2) (v1) & (v2) +#define OR(type, v1, v2) (v1) | (v2) +#define XOR(type, v1, v2) (v1) ^ (v2) +#define LSHIFT(type, v1, v2) (v1) << (v2) +#define RSHIFT(type, v1, v2) (v1) >> (v2) #define ABS_I(type, v1) abs((v1)) #define ABS_F(type, v1) fabs((v1)) @@ -79,36 +80,38 @@ void name(Imaging out, Imaging im1, Imaging im2)\ * PyFPE_END_PROTECT(result) */ -#define DIV_I(type, v1, v2) ((v2)!=0)?(v1)/(v2):0 -#define DIV_F(type, v1, v2) ((v2)!=0.0F)?(v1)/(v2):0.0F +#define DIV_I(type, v1, v2) ((v2) != 0) ? (v1) / (v2) : 0 +#define DIV_F(type, v1, v2) ((v2) != 0.0F) ? (v1) / (v2) : 0.0F -#define MOD_I(type, v1, v2) ((v2)!=0)?(v1)%(v2):0 -#define MOD_F(type, v1, v2) ((v2)!=0.0F)?fmod((v1),(v2)):0.0F +#define MOD_I(type, v1, v2) ((v2) != 0) ? (v1) % (v2) : 0 +#define MOD_F(type, v1, v2) ((v2) != 0.0F) ? fmod((v1), (v2)) : 0.0F -static int powi(int x, int y) -{ +static int +powi(int x, int y) { double v = pow(x, y) + 0.5; - if (errno == EDOM) + if (errno == EDOM) { return 0; - if (v < MIN_INT32) + } + if (v < MIN_INT32) { v = MIN_INT32; - else if (v > MAX_INT32) + } else if (v > MAX_INT32) { v = MAX_INT32; - return (int) v; + } + return (int)v; } #define POW_I(type, v1, v2) powi(v1, v2) #define POW_F(type, v1, v2) powf(v1, v2) /* FIXME: EDOM handling */ -#define DIFF_I(type, v1, v2) abs((v1)-(v2)) -#define DIFF_F(type, v1, v2) fabs((v1)-(v2)) +#define DIFF_I(type, v1, v2) abs((v1) - (v2)) +#define DIFF_F(type, v1, v2) fabs((v1) - (v2)) -#define EQ(type, v1, v2) (v1)==(v2) -#define NE(type, v1, v2) (v1)!=(v2) -#define LT(type, v1, v2) (v1)<(v2) -#define LE(type, v1, v2) (v1)<=(v2) -#define GT(type, v1, v2) (v1)>(v2) -#define GE(type, v1, v2) (v1)>=(v2) +#define EQ(type, v1, v2) (v1) == (v2) +#define NE(type, v1, v2) (v1) != (v2) +#define LT(type, v1, v2) (v1) < (v2) +#define LE(type, v1, v2) (v1) <= (v2) +#define GT(type, v1, v2) (v1) > (v2) +#define GE(type, v1, v2) (v1) >= (v2) UNOP(abs_I, ABS_I, INT32) UNOP(neg_I, NEG, INT32) @@ -160,20 +163,20 @@ BINOP(gt_F, GT, FLOAT32) BINOP(ge_F, GE, FLOAT32) static PyObject * -_unop(PyObject* self, PyObject* args) -{ +_unop(PyObject *self, PyObject *args) { Imaging out; Imaging im1; void (*unop)(Imaging, Imaging); Py_ssize_t op, i0, i1; - if (!PyArg_ParseTuple(args, "nnn", &op, &i0, &i1)) + if (!PyArg_ParseTuple(args, "nnn", &op, &i0, &i1)) { return NULL; + } - out = (Imaging) i0; - im1 = (Imaging) i1; + out = (Imaging)i0; + im1 = (Imaging)i1; - unop = (void*) op; + unop = (void *)op; unop(out, im1); @@ -182,22 +185,22 @@ _unop(PyObject* self, PyObject* args) } static PyObject * -_binop(PyObject* self, PyObject* args) -{ +_binop(PyObject *self, PyObject *args) { Imaging out; Imaging im1; Imaging im2; void (*binop)(Imaging, Imaging, Imaging); Py_ssize_t op, i0, i1, i2; - if (!PyArg_ParseTuple(args, "nnnn", &op, &i0, &i1, &i2)) + if (!PyArg_ParseTuple(args, "nnnn", &op, &i0, &i1, &i2)) { return NULL; + } - out = (Imaging) i0; - im1 = (Imaging) i1; - im2 = (Imaging) i2; + out = (Imaging)i0; + im1 = (Imaging)i1; + im2 = (Imaging)i2; - binop = (void*) op; + binop = (void *)op; binop(out, im1, im2); @@ -206,23 +209,20 @@ _binop(PyObject* self, PyObject* args) } static PyMethodDef _functions[] = { - {"unop", _unop, 1}, - {"binop", _binop, 1}, - {NULL, NULL} -}; + {"unop", _unop, 1}, {"binop", _binop, 1}, {NULL, NULL}}; static void -install(PyObject *d, char* name, void* value) -{ - PyObject *v = PyLong_FromSsize_t((Py_ssize_t) value); - if (!v || PyDict_SetItemString(d, name, v)) +install(PyObject *d, char *name, void *value) { + PyObject *v = PyLong_FromSsize_t((Py_ssize_t)value); + if (!v || PyDict_SetItemString(d, name, v)) { PyErr_Clear(); + } Py_XDECREF(v); } static int -setup_module(PyObject* m) { - PyObject* d = PyModule_GetDict(m); +setup_module(PyObject *m) { + PyObject *d = PyModule_GetDict(m); install(d, "abs_I", abs_I); install(d, "neg_I", neg_I); @@ -274,20 +274,21 @@ setup_module(PyObject* m) { PyMODINIT_FUNC PyInit__imagingmath(void) { - PyObject* m; + PyObject *m; static PyModuleDef module_def = { PyModuleDef_HEAD_INIT, - "_imagingmath", /* m_name */ - NULL, /* m_doc */ - -1, /* m_size */ - _functions, /* m_methods */ + "_imagingmath", /* m_name */ + NULL, /* m_doc */ + -1, /* m_size */ + _functions, /* m_methods */ }; m = PyModule_Create(&module_def); - if (setup_module(m) < 0) + if (setup_module(m) < 0) { return NULL; + } return m; } diff --git a/src/_imagingmorph.c b/src/_imagingmorph.c index 050ae9f02..c0644b616 100644 --- a/src/_imagingmorph.c +++ b/src/_imagingmorph.c @@ -12,9 +12,9 @@ */ #include "Python.h" -#include "Imaging.h" +#include "libImaging/Imaging.h" -#define LUT_SIZE (1<<9) +#define LUT_SIZE (1 << 9) /* Apply a morphologic LUT to a binary image. Outputs a a new binary image. @@ -27,9 +27,8 @@ Returns number of changed pixels. */ -static PyObject* -apply(PyObject *self, PyObject* args) -{ +static PyObject * +apply(PyObject *self, PyObject *args) { const char *lut; PyObject *py_lut; Py_ssize_t lut_len, i0, i1; @@ -58,18 +57,16 @@ apply(PyObject *self, PyObject* args) lut = PyBytes_AsString(py_lut); - imgin = (Imaging) i0; - imgout = (Imaging) i1; + imgin = (Imaging)i0; + imgout = (Imaging)i1; width = imgin->xsize; height = imgin->ysize; - if (imgin->type != IMAGING_TYPE_UINT8 || - imgin->bands != 1) { + if (imgin->type != IMAGING_TYPE_UINT8 || imgin->bands != 1) { PyErr_SetString(PyExc_RuntimeError, "Unsupported image type"); return NULL; } - if (imgout->type != IMAGING_TYPE_UINT8 || - imgout->bands != 1) { + if (imgout->type != IMAGING_TYPE_UINT8 || imgout->bands != 1) { PyErr_SetString(PyExc_RuntimeError, "Unsupported image type"); return NULL; } @@ -77,51 +74,46 @@ apply(PyObject *self, PyObject* args) inrows = imgin->image8; outrows = imgout->image8; - for (row_idx=0; row_idx < height; row_idx++) { + for (row_idx = 0; row_idx < height; row_idx++) { UINT8 *outrow = outrows[row_idx]; UINT8 *inrow = inrows[row_idx]; UINT8 *prow, *nrow; /* Previous and next row */ /* zero boundary conditions. TBD support other modes */ - outrow[0] = outrow[width-1] = 0; - if (row_idx==0 || row_idx == height-1) { - for(col_idx=0; col_idxtype != IMAGING_TYPE_UINT8 || - imgin->bands != 1) { + if (imgin->type != IMAGING_TYPE_UINT8 || imgin->bands != 1) { PyErr_SetString(PyExc_RuntimeError, "Unsupported image type"); return NULL; } @@ -176,39 +166,33 @@ match(PyObject *self, PyObject* args) width = imgin->xsize; height = imgin->ysize; - for (row_idx=1; row_idx < height-1; row_idx++) { + for (row_idx = 1; row_idx < height - 1; row_idx++) { UINT8 *inrow = inrows[row_idx]; UINT8 *prow, *nrow; - prow = inrows[row_idx-1]; - nrow = inrows[row_idx+1]; + prow = inrows[row_idx - 1]; + nrow = inrows[row_idx + 1]; - for (col_idx=1; col_idximage8; width = img->xsize; height = img->ysize; - for (row_idx=0; row_idx < height; row_idx++) { + for (row_idx = 0; row_idx < height; row_idx++) { UINT8 *row = rows[row_idx]; - for (col_idx=0; col_idxinterp; } @@ -68,10 +68,10 @@ PyMODINIT_FUNC PyInit__imagingtk(void) { static PyModuleDef module_def = { PyModuleDef_HEAD_INIT, - "_imagingtk", /* m_name */ - NULL, /* m_doc */ - -1, /* m_size */ - functions, /* m_methods */ + "_imagingtk", /* m_name */ + NULL, /* m_doc */ + -1, /* m_size */ + functions, /* m_methods */ }; PyObject *m; m = PyModule_Create(&module_def); diff --git a/src/_webp.c b/src/_webp.c index 4581ef89d..c7875fa36 100644 --- a/src/_webp.c +++ b/src/_webp.c @@ -1,6 +1,6 @@ #define PY_SSIZE_T_CLEAN #include -#include "Imaging.h" +#include "libImaging/Imaging.h" #include #include #include @@ -21,18 +21,31 @@ #endif +void +ImagingSectionEnter(ImagingSectionCookie *cookie) { + *cookie = (PyThreadState *)PyEval_SaveThread(); +} + +void +ImagingSectionLeave(ImagingSectionCookie *cookie) { + PyEval_RestoreThread((PyThreadState *)*cookie); +} + /* -------------------------------------------------------------------- */ /* WebP Muxer Error Handling */ /* -------------------------------------------------------------------- */ #ifdef HAVE_WEBPMUX -static const char* const kErrorMessages[-WEBP_MUX_NOT_ENOUGH_DATA + 1] = { - "WEBP_MUX_NOT_FOUND", "WEBP_MUX_INVALID_ARGUMENT", "WEBP_MUX_BAD_DATA", - "WEBP_MUX_MEMORY_ERROR", "WEBP_MUX_NOT_ENOUGH_DATA" -}; +static const char *const kErrorMessages[-WEBP_MUX_NOT_ENOUGH_DATA + 1] = { + "WEBP_MUX_NOT_FOUND", + "WEBP_MUX_INVALID_ARGUMENT", + "WEBP_MUX_BAD_DATA", + "WEBP_MUX_MEMORY_ERROR", + "WEBP_MUX_NOT_ENOUGH_DATA"}; -PyObject* HandleMuxError(WebPMuxError err, char* chunk) { +PyObject * +HandleMuxError(WebPMuxError err, char *chunk) { char message[100]; int message_len; assert(err <= WEBP_MUX_NOT_FOUND && err >= WEBP_MUX_NOT_ENOUGH_DATA); @@ -44,9 +57,11 @@ PyObject* HandleMuxError(WebPMuxError err, char* chunk) { // Create the error message if (chunk == NULL) { - message_len = sprintf(message, "could not assemble chunks: %s", kErrorMessages[-err]); + message_len = + sprintf(message, "could not assemble chunks: %s", kErrorMessages[-err]); } else { - message_len = sprintf(message, "could not set %.4s chunk: %s", chunk, kErrorMessages[-err]); + message_len = sprintf( + message, "could not set %.4s chunk: %s", chunk, kErrorMessages[-err]); } if (message_len < 0) { PyErr_SetString(PyExc_RuntimeError, "failed to construct error message"); @@ -62,7 +77,7 @@ PyObject* HandleMuxError(WebPMuxError err, char* chunk) { case WEBP_MUX_BAD_DATA: case WEBP_MUX_NOT_ENOUGH_DATA: - PyErr_SetString(PyExc_IOError, message); + PyErr_SetString(PyExc_OSError, message); break; default: @@ -82,8 +97,7 @@ PyObject* HandleMuxError(WebPMuxError err, char* chunk) { // Encoder type typedef struct { - PyObject_HEAD - WebPAnimEncoder* enc; + PyObject_HEAD WebPAnimEncoder *enc; WebPPicture frame; } WebPAnimEncoderObject; @@ -91,18 +105,17 @@ static PyTypeObject WebPAnimEncoder_Type; // Decoder type typedef struct { - PyObject_HEAD - WebPAnimDecoder* dec; + PyObject_HEAD WebPAnimDecoder *dec; WebPAnimInfo info; WebPData data; - char* mode; + char *mode; } WebPAnimDecoderObject; static PyTypeObject WebPAnimDecoder_Type; // Encoder functions -PyObject* _anim_encoder_new(PyObject* self, PyObject* args) -{ +PyObject * +_anim_encoder_new(PyObject *self, PyObject *args) { int width, height; uint32_t bgcolor; int loop_count; @@ -111,12 +124,21 @@ PyObject* _anim_encoder_new(PyObject* self, PyObject* args) int allow_mixed; int verbose; WebPAnimEncoderOptions enc_options; - WebPAnimEncoderObject* encp = NULL; - WebPAnimEncoder* enc = NULL; + WebPAnimEncoderObject *encp = NULL; + WebPAnimEncoder *enc = NULL; - if (!PyArg_ParseTuple(args, "iiIiiiiii", - &width, &height, &bgcolor, &loop_count, &minimize_size, - &kmin, &kmax, &allow_mixed, &verbose)) { + if (!PyArg_ParseTuple( + args, + "iiIiiiiii", + &width, + &height, + &bgcolor, + &loop_count, + &minimize_size, + &kmin, + &kmax, + &allow_mixed, + &verbose)) { return NULL; } @@ -146,7 +168,7 @@ PyObject* _anim_encoder_new(PyObject* self, PyObject* args) enc = WebPAnimEncoderNew(width, height, &enc_options); if (enc) { encp->enc = enc; - return (PyObject*) encp; + return (PyObject *)encp; } WebPPictureFree(&(encp->frame)); } @@ -156,33 +178,42 @@ PyObject* _anim_encoder_new(PyObject* self, PyObject* args) return NULL; } -PyObject* _anim_encoder_dealloc(PyObject* self) -{ - WebPAnimEncoderObject* encp = (WebPAnimEncoderObject*)self; +PyObject * +_anim_encoder_dealloc(PyObject *self) { + WebPAnimEncoderObject *encp = (WebPAnimEncoderObject *)self; WebPPictureFree(&(encp->frame)); WebPAnimEncoderDelete(encp->enc); Py_RETURN_NONE; } -PyObject* _anim_encoder_add(PyObject* self, PyObject* args) -{ - uint8_t* rgb; +PyObject * +_anim_encoder_add(PyObject *self, PyObject *args) { + uint8_t *rgb; Py_ssize_t size; int timestamp; int width; int height; - char* mode; + char *mode; int lossless; float quality_factor; int method; WebPConfig config; - WebPAnimEncoderObject* encp = (WebPAnimEncoderObject*)self; - WebPAnimEncoder* enc = encp->enc; - WebPPicture* frame = &(encp->frame); + WebPAnimEncoderObject *encp = (WebPAnimEncoderObject *)self; + WebPAnimEncoder *enc = encp->enc; + WebPPicture *frame = &(encp->frame); - if (!PyArg_ParseTuple(args, "z#iiisifi", - (char**)&rgb, &size, ×tamp, &width, &height, &mode, - &lossless, &quality_factor, &method)) { + if (!PyArg_ParseTuple( + args, + "z#iiisifi", + (char **)&rgb, + &size, + ×tamp, + &width, + &height, + &mode, + &lossless, + &quality_factor, + &method)) { return NULL; } @@ -210,10 +241,10 @@ PyObject* _anim_encoder_add(PyObject* self, PyObject* args) // Populate the frame with raw bytes passed to us frame->width = width; frame->height = height; - frame->use_argb = 1; // Don't convert RGB pixels to YUV - if (strcmp(mode, "RGBA")==0) { + frame->use_argb = 1; // Don't convert RGB pixels to YUV + if (strcmp(mode, "RGBA") == 0) { WebPPictureImportRGBA(frame, rgb, 4 * width); - } else if (strcmp(mode, "RGBX")==0) { + } else if (strcmp(mode, "RGBX") == 0) { WebPPictureImportRGBX(frame, rgb, 4 * width); } else { WebPPictureImportRGB(frame, rgb, 3 * width); @@ -228,22 +259,29 @@ PyObject* _anim_encoder_add(PyObject* self, PyObject* args) Py_RETURN_NONE; } -PyObject* _anim_encoder_assemble(PyObject* self, PyObject* args) -{ - uint8_t* icc_bytes; - uint8_t* exif_bytes; - uint8_t* xmp_bytes; +PyObject * +_anim_encoder_assemble(PyObject *self, PyObject *args) { + uint8_t *icc_bytes; + uint8_t *exif_bytes; + uint8_t *xmp_bytes; Py_ssize_t icc_size; Py_ssize_t exif_size; Py_ssize_t xmp_size; WebPData webp_data; - WebPAnimEncoderObject* encp = (WebPAnimEncoderObject*)self; - WebPAnimEncoder* enc = encp->enc; - WebPMux* mux = NULL; - PyObject* ret = NULL; + WebPAnimEncoderObject *encp = (WebPAnimEncoderObject *)self; + WebPAnimEncoder *enc = encp->enc; + WebPMux *mux = NULL; + PyObject *ret = NULL; - if (!PyArg_ParseTuple(args, "s#s#s#", - &icc_bytes, &icc_size, &exif_bytes, &exif_size, &xmp_bytes, &xmp_size)) { + if (!PyArg_ParseTuple( + args, + "s#s#s#", + &icc_bytes, + &icc_size, + &exif_bytes, + &exif_size, + &xmp_bytes, + &xmp_size)) { return NULL; } @@ -262,9 +300,9 @@ PyObject* _anim_encoder_assemble(PyObject* self, PyObject* args) int i_icc_size = (int)icc_size; int i_exif_size = (int)exif_size; int i_xmp_size = (int)xmp_size; - WebPData icc_profile = { icc_bytes, i_icc_size }; - WebPData exif = { exif_bytes, i_exif_size }; - WebPData xmp = { xmp_bytes, i_xmp_size }; + WebPData icc_profile = {icc_bytes, i_icc_size}; + WebPData exif = {exif_bytes, i_exif_size}; + WebPData xmp = {xmp_bytes, i_xmp_size}; mux = WebPMuxCreate(&webp_data, 1); if (mux == NULL) { @@ -304,7 +342,7 @@ PyObject* _anim_encoder_assemble(PyObject* self, PyObject* args) } // Convert to Python bytes - ret = PyBytes_FromStringAndSize((char*)webp_data.bytes, webp_data.size); + ret = PyBytes_FromStringAndSize((char *)webp_data.bytes, webp_data.size); WebPDataClear(&webp_data); // If we had to re-mux, we should free it now that we're done with it @@ -316,21 +354,21 @@ PyObject* _anim_encoder_assemble(PyObject* self, PyObject* args) } // Decoder functions -PyObject* _anim_decoder_new(PyObject* self, PyObject* args) -{ +PyObject * +_anim_decoder_new(PyObject *self, PyObject *args) { PyBytesObject *webp_string; const uint8_t *webp; Py_ssize_t size; WebPData webp_src; - char* mode; + char *mode; WebPDecoderConfig config; - WebPAnimDecoderObject* decp = NULL; - WebPAnimDecoder* dec = NULL; + WebPAnimDecoderObject *decp = NULL; + WebPAnimDecoder *dec = NULL; if (!PyArg_ParseTuple(args, "S", &webp_string)) { return NULL; } - PyBytes_AsStringAndSize((PyObject *)webp_string, (char**)&webp, &size); + PyBytes_AsStringAndSize((PyObject *)webp_string, (char **)&webp, &size); webp_src.bytes = webp; webp_src.size = size; @@ -351,7 +389,7 @@ PyObject* _anim_decoder_new(PyObject* self, PyObject* args) if (dec) { if (WebPAnimDecoderGetInfo(dec, &(decp->info))) { decp->dec = dec; - return (PyObject*)decp; + return (PyObject *)decp; } } } @@ -361,33 +399,34 @@ PyObject* _anim_decoder_new(PyObject* self, PyObject* args) return NULL; } -PyObject* _anim_decoder_dealloc(PyObject* self) -{ - WebPAnimDecoderObject* decp = (WebPAnimDecoderObject *)self; +PyObject * +_anim_decoder_dealloc(PyObject *self) { + WebPAnimDecoderObject *decp = (WebPAnimDecoderObject *)self; WebPDataClear(&(decp->data)); WebPAnimDecoderDelete(decp->dec); Py_RETURN_NONE; } -PyObject* _anim_decoder_get_info(PyObject* self, PyObject* args) -{ - WebPAnimDecoderObject* decp = (WebPAnimDecoderObject *)self; - WebPAnimInfo* info = &(decp->info); +PyObject * +_anim_decoder_get_info(PyObject *self) { + WebPAnimDecoderObject *decp = (WebPAnimDecoderObject *)self; + WebPAnimInfo *info = &(decp->info); - return Py_BuildValue("IIIIIs", - info->canvas_width, info->canvas_height, + return Py_BuildValue( + "IIIIIs", + info->canvas_width, + info->canvas_height, info->loop_count, info->bgcolor, info->frame_count, - decp->mode - ); + decp->mode); } -PyObject* _anim_decoder_get_chunk(PyObject* self, PyObject* args) -{ - char* mode; - WebPAnimDecoderObject* decp = (WebPAnimDecoderObject *)self; - const WebPDemuxer* demux; +PyObject * +_anim_decoder_get_chunk(PyObject *self, PyObject *args) { + char *mode; + WebPAnimDecoderObject *decp = (WebPAnimDecoderObject *)self; + const WebPDemuxer *demux; WebPChunkIterator iter; PyObject *ret; @@ -400,27 +439,27 @@ PyObject* _anim_decoder_get_chunk(PyObject* self, PyObject* args) Py_RETURN_NONE; } - ret = PyBytes_FromStringAndSize((const char*)iter.chunk.bytes, iter.chunk.size); + ret = PyBytes_FromStringAndSize((const char *)iter.chunk.bytes, iter.chunk.size); WebPDemuxReleaseChunkIterator(&iter); return ret; } -PyObject* _anim_decoder_get_next(PyObject* self, PyObject* args) -{ - uint8_t* buf; +PyObject * +_anim_decoder_get_next(PyObject *self) { + uint8_t *buf; int timestamp; - PyObject* bytes; - PyObject* ret; - WebPAnimDecoderObject* decp = (WebPAnimDecoderObject*)self; + PyObject *bytes; + PyObject *ret; + WebPAnimDecoderObject *decp = (WebPAnimDecoderObject *)self; if (!WebPAnimDecoderGetNext(decp->dec, &buf, ×tamp)) { - PyErr_SetString(PyExc_IOError, "failed to read next frame"); + PyErr_SetString(PyExc_OSError, "failed to read next frame"); return NULL; } - bytes = PyBytes_FromStringAndSize((char *)buf, - decp->info.canvas_width * 4 * decp->info.canvas_height); + bytes = PyBytes_FromStringAndSize( + (char *)buf, decp->info.canvas_width * 4 * decp->info.canvas_height); ret = Py_BuildValue("Si", bytes, timestamp); @@ -428,15 +467,9 @@ PyObject* _anim_decoder_get_next(PyObject* self, PyObject* args) return ret; } -PyObject* _anim_decoder_has_more_frames(PyObject* self, PyObject* args) -{ - WebPAnimDecoderObject* decp = (WebPAnimDecoderObject*)self; - return Py_BuildValue("i", WebPAnimDecoderHasMoreFrames(decp->dec)); -} - -PyObject* _anim_decoder_reset(PyObject* self, PyObject* args) -{ - WebPAnimDecoderObject* decp = (WebPAnimDecoderObject *)self; +PyObject * +_anim_decoder_reset(PyObject *self) { + WebPAnimDecoderObject *decp = (WebPAnimDecoderObject *)self; WebPAnimDecoderReset(decp->dec); Py_RETURN_NONE; } @@ -454,82 +487,79 @@ static struct PyMethodDef _anim_encoder_methods[] = { // WebPAnimDecoder type definition static PyTypeObject WebPAnimEncoder_Type = { - PyVarObject_HEAD_INIT(NULL, 0) - "WebPAnimEncoder", /*tp_name */ - sizeof(WebPAnimEncoderObject), /*tp_size */ - 0, /*tp_itemsize */ + PyVarObject_HEAD_INIT(NULL, 0) "WebPAnimEncoder", /*tp_name */ + sizeof(WebPAnimEncoderObject), /*tp_size */ + 0, /*tp_itemsize */ /* methods */ (destructor)_anim_encoder_dealloc, /*tp_dealloc*/ - 0, /*tp_print*/ - 0, /*tp_getattr*/ - 0, /*tp_setattr*/ - 0, /*tp_compare*/ - 0, /*tp_repr*/ - 0, /*tp_as_number */ - 0, /*tp_as_sequence */ - 0, /*tp_as_mapping */ - 0, /*tp_hash*/ - 0, /*tp_call*/ - 0, /*tp_str*/ - 0, /*tp_getattro*/ - 0, /*tp_setattro*/ - 0, /*tp_as_buffer*/ - Py_TPFLAGS_DEFAULT, /*tp_flags*/ - 0, /*tp_doc*/ - 0, /*tp_traverse*/ - 0, /*tp_clear*/ - 0, /*tp_richcompare*/ - 0, /*tp_weaklistoffset*/ - 0, /*tp_iter*/ - 0, /*tp_iternext*/ - _anim_encoder_methods, /*tp_methods*/ - 0, /*tp_members*/ - 0, /*tp_getset*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number */ + 0, /*tp_as_sequence */ + 0, /*tp_as_mapping */ + 0, /*tp_hash*/ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT, /*tp_flags*/ + 0, /*tp_doc*/ + 0, /*tp_traverse*/ + 0, /*tp_clear*/ + 0, /*tp_richcompare*/ + 0, /*tp_weaklistoffset*/ + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + _anim_encoder_methods, /*tp_methods*/ + 0, /*tp_members*/ + 0, /*tp_getset*/ }; // WebPAnimDecoder methods static struct PyMethodDef _anim_decoder_methods[] = { - {"get_info", (PyCFunction)_anim_decoder_get_info, METH_VARARGS, "get_info"}, + {"get_info", (PyCFunction)_anim_decoder_get_info, METH_NOARGS, "get_info"}, {"get_chunk", (PyCFunction)_anim_decoder_get_chunk, METH_VARARGS, "get_chunk"}, - {"get_next", (PyCFunction)_anim_decoder_get_next, METH_VARARGS, "get_next"}, - {"has_more_frames", (PyCFunction)_anim_decoder_has_more_frames, METH_VARARGS, "has_more_frames"}, - {"reset", (PyCFunction)_anim_decoder_reset, METH_VARARGS, "reset"}, + {"get_next", (PyCFunction)_anim_decoder_get_next, METH_NOARGS, "get_next"}, + {"reset", (PyCFunction)_anim_decoder_reset, METH_NOARGS, "reset"}, {NULL, NULL} /* sentinel */ }; // WebPAnimDecoder type definition static PyTypeObject WebPAnimDecoder_Type = { - PyVarObject_HEAD_INIT(NULL, 0) - "WebPAnimDecoder", /*tp_name */ - sizeof(WebPAnimDecoderObject), /*tp_size */ - 0, /*tp_itemsize */ + PyVarObject_HEAD_INIT(NULL, 0) "WebPAnimDecoder", /*tp_name */ + sizeof(WebPAnimDecoderObject), /*tp_size */ + 0, /*tp_itemsize */ /* methods */ (destructor)_anim_decoder_dealloc, /*tp_dealloc*/ - 0, /*tp_print*/ - 0, /*tp_getattr*/ - 0, /*tp_setattr*/ - 0, /*tp_compare*/ - 0, /*tp_repr*/ - 0, /*tp_as_number */ - 0, /*tp_as_sequence */ - 0, /*tp_as_mapping */ - 0, /*tp_hash*/ - 0, /*tp_call*/ - 0, /*tp_str*/ - 0, /*tp_getattro*/ - 0, /*tp_setattro*/ - 0, /*tp_as_buffer*/ - Py_TPFLAGS_DEFAULT, /*tp_flags*/ - 0, /*tp_doc*/ - 0, /*tp_traverse*/ - 0, /*tp_clear*/ - 0, /*tp_richcompare*/ - 0, /*tp_weaklistoffset*/ - 0, /*tp_iter*/ - 0, /*tp_iternext*/ - _anim_decoder_methods, /*tp_methods*/ - 0, /*tp_members*/ - 0, /*tp_getset*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number */ + 0, /*tp_as_sequence */ + 0, /*tp_as_mapping */ + 0, /*tp_hash*/ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT, /*tp_flags*/ + 0, /*tp_doc*/ + 0, /*tp_traverse*/ + 0, /*tp_clear*/ + 0, /*tp_richcompare*/ + 0, /*tp_weaklistoffset*/ + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + _anim_decoder_methods, /*tp_methods*/ + 0, /*tp_members*/ + 0, /*tp_getset*/ }; #endif @@ -538,151 +568,203 @@ static PyTypeObject WebPAnimDecoder_Type = { /* Legacy WebP Support */ /* -------------------------------------------------------------------- */ -PyObject* WebPEncode_wrapper(PyObject* self, PyObject* args) -{ +PyObject * +WebPEncode_wrapper(PyObject *self, PyObject *args) { int width; int height; int lossless; float quality_factor; - uint8_t* rgb; - uint8_t* icc_bytes; - uint8_t* exif_bytes; - uint8_t* xmp_bytes; - uint8_t* output; - char* mode; + int method; + uint8_t *rgb; + uint8_t *icc_bytes; + uint8_t *exif_bytes; + uint8_t *xmp_bytes; + uint8_t *output; + char *mode; Py_ssize_t size; Py_ssize_t icc_size; Py_ssize_t exif_size; Py_ssize_t xmp_size; size_t ret_size; + int rgba_mode; + int channels; + int ok; + ImagingSectionCookie cookie; + WebPConfig config; + WebPMemoryWriter writer; + WebPPicture pic; - if (!PyArg_ParseTuple(args, "y#iiifss#s#s#", - (char**)&rgb, &size, &width, &height, &lossless, &quality_factor, &mode, - &icc_bytes, &icc_size, &exif_bytes, &exif_size, &xmp_bytes, &xmp_size)) { + if (!PyArg_ParseTuple( + args, + "y#iiifss#is#s#", + (char **)&rgb, + &size, + &width, + &height, + &lossless, + &quality_factor, + &mode, + &icc_bytes, + &icc_size, + &method, + &exif_bytes, + &exif_size, + &xmp_bytes, + &xmp_size)) { return NULL; } - if (strcmp(mode, "RGBA")==0){ - if (size < width * height * 4){ - Py_RETURN_NONE; - } - #if WEBP_ENCODER_ABI_VERSION >= 0x0100 - if (lossless) { - ret_size = WebPEncodeLosslessRGBA(rgb, width, height, 4 * width, &output); - } else - #endif - { - ret_size = WebPEncodeRGBA(rgb, width, height, 4 * width, quality_factor, &output); - } - } else if (strcmp(mode, "RGB")==0){ - if (size < width * height * 3){ - Py_RETURN_NONE; - } - #if WEBP_ENCODER_ABI_VERSION >= 0x0100 - if (lossless) { - ret_size = WebPEncodeLosslessRGB(rgb, width, height, 3 * width, &output); - } else - #endif - { - ret_size = WebPEncodeRGB(rgb, width, height, 3 * width, quality_factor, &output); - } - } else { + + rgba_mode = strcmp(mode, "RGBA") == 0; + if (!rgba_mode && strcmp(mode, "RGB") != 0) { Py_RETURN_NONE; } + channels = rgba_mode ? 4 : 3; + if (size < width * height * channels) { + Py_RETURN_NONE; + } + + // Setup config for this frame + if (!WebPConfigInit(&config)) { + PyErr_SetString(PyExc_RuntimeError, "failed to initialize config!"); + return NULL; + } + config.lossless = lossless; + config.quality = quality_factor; + config.method = method; + + // Validate the config + if (!WebPValidateConfig(&config)) { + PyErr_SetString(PyExc_ValueError, "invalid configuration"); + return NULL; + } + + if (!WebPPictureInit(&pic)) { + PyErr_SetString(PyExc_ValueError, "could not initialise picture"); + return NULL; + } + pic.width = width; + pic.height = height; + pic.use_argb = 1; // Don't convert RGB pixels to YUV + + if (rgba_mode) { + WebPPictureImportRGBA(&pic, rgb, channels * width); + } else { + WebPPictureImportRGB(&pic, rgb, channels * width); + } + + WebPMemoryWriterInit(&writer); + pic.writer = WebPMemoryWrite; + pic.custom_ptr = &writer; + + ImagingSectionEnter(&cookie); + ok = WebPEncode(&config, &pic); + ImagingSectionLeave(&cookie); + + WebPPictureFree(&pic); + if (!ok) { + PyErr_SetString(PyExc_ValueError, "encoding error"); + return NULL; + } + output = writer.mem; + ret_size = writer.size; + #ifndef HAVE_WEBPMUX if (ret_size > 0) { - PyObject *ret = PyBytes_FromStringAndSize((char*)output, ret_size); + PyObject *ret = PyBytes_FromStringAndSize((char *)output, ret_size); free(output); return ret; } #else { - /* I want to truncate the *_size items that get passed into WebP - data. Pypy2.1.0 had some issues where the Py_ssize_t items had - data in the upper byte. (Not sure why, it shouldn't have been there) - */ - int i_icc_size = (int)icc_size; - int i_exif_size = (int)exif_size; - int i_xmp_size = (int)xmp_size; - WebPData output_data = {0}; - WebPData image = { output, ret_size }; - WebPData icc_profile = { icc_bytes, i_icc_size }; - WebPData exif = { exif_bytes, i_exif_size }; - WebPData xmp = { xmp_bytes, i_xmp_size }; - WebPMuxError err; - int dbg = 0; + /* I want to truncate the *_size items that get passed into WebP + data. Pypy2.1.0 had some issues where the Py_ssize_t items had + data in the upper byte. (Not sure why, it shouldn't have been there) + */ + int i_icc_size = (int)icc_size; + int i_exif_size = (int)exif_size; + int i_xmp_size = (int)xmp_size; + WebPData output_data = {0}; + WebPData image = {output, ret_size}; + WebPData icc_profile = {icc_bytes, i_icc_size}; + WebPData exif = {exif_bytes, i_exif_size}; + WebPData xmp = {xmp_bytes, i_xmp_size}; + WebPMuxError err; + int dbg = 0; - int copy_data = 0; // value 1 indicates given data WILL be copied to the mux - // and value 0 indicates data will NOT be copied. + int copy_data = 0; // value 1 indicates given data WILL be copied to the mux + // and value 0 indicates data will NOT be copied. - WebPMux* mux = WebPMuxNew(); - WebPMuxSetImage(mux, &image, copy_data); + WebPMux *mux = WebPMuxNew(); + WebPMuxSetImage(mux, &image, copy_data); - if (dbg) { - /* was getting %ld icc_size == 0, icc_size>0 was true */ - fprintf(stderr, "icc size %d, %d \n", i_icc_size, i_icc_size > 0); - } - - if (i_icc_size > 0) { if (dbg) { - fprintf(stderr, "Adding ICC Profile\n"); + /* was getting %ld icc_size == 0, icc_size>0 was true */ + fprintf(stderr, "icc size %d, %d \n", i_icc_size, i_icc_size > 0); + } + + if (i_icc_size > 0) { + if (dbg) { + fprintf(stderr, "Adding ICC Profile\n"); + } + err = WebPMuxSetChunk(mux, "ICCP", &icc_profile, copy_data); + if (err != WEBP_MUX_OK) { + return HandleMuxError(err, "ICCP"); + } } - err = WebPMuxSetChunk(mux, "ICCP", &icc_profile, copy_data); - if (err != WEBP_MUX_OK) { - return HandleMuxError(err, "ICCP"); - } - } - if (dbg) { - fprintf(stderr, "exif size %d \n", i_exif_size); - } - if (i_exif_size > 0) { if (dbg) { - fprintf(stderr, "Adding Exif Data\n"); + fprintf(stderr, "exif size %d \n", i_exif_size); } - err = WebPMuxSetChunk(mux, "EXIF", &exif, copy_data); - if (err != WEBP_MUX_OK) { - return HandleMuxError(err, "EXIF"); + if (i_exif_size > 0) { + if (dbg) { + fprintf(stderr, "Adding Exif Data\n"); + } + err = WebPMuxSetChunk(mux, "EXIF", &exif, copy_data); + if (err != WEBP_MUX_OK) { + return HandleMuxError(err, "EXIF"); + } } - } - if (dbg) { - fprintf(stderr, "xmp size %d \n", i_xmp_size); - } - if (i_xmp_size > 0) { - if (dbg){ - fprintf(stderr, "Adding XMP Data\n"); + if (dbg) { + fprintf(stderr, "xmp size %d \n", i_xmp_size); } - err = WebPMuxSetChunk(mux, "XMP ", &xmp, copy_data); - if (err != WEBP_MUX_OK) { - return HandleMuxError(err, "XMP "); + if (i_xmp_size > 0) { + if (dbg) { + fprintf(stderr, "Adding XMP Data\n"); + } + err = WebPMuxSetChunk(mux, "XMP ", &xmp, copy_data); + if (err != WEBP_MUX_OK) { + return HandleMuxError(err, "XMP "); + } } - } - WebPMuxAssemble(mux, &output_data); - WebPMuxDelete(mux); - free(output); + WebPMuxAssemble(mux, &output_data); + WebPMuxDelete(mux); + free(output); - ret_size = output_data.size; - if (ret_size > 0) { - PyObject *ret = PyBytes_FromStringAndSize((char*)output_data.bytes, ret_size); - WebPDataClear(&output_data); - return ret; - } + ret_size = output_data.size; + if (ret_size > 0) { + PyObject *ret = + PyBytes_FromStringAndSize((char *)output_data.bytes, ret_size); + WebPDataClear(&output_data); + return ret; + } } #endif Py_RETURN_NONE; } -PyObject* WebPDecode_wrapper(PyObject* self, PyObject* args) -{ - PyBytesObject* webp_string; - const uint8_t* webp; +PyObject * +WebPDecode_wrapper(PyObject *self, PyObject *args) { + PyBytesObject *webp_string; + const uint8_t *webp; Py_ssize_t size; - PyObject *ret = Py_None, *bytes = NULL, *pymode = NULL, *icc_profile = NULL, *exif = NULL; + PyObject *ret = Py_None, *bytes = NULL, *pymode = NULL, *icc_profile = NULL, + *exif = NULL; WebPDecoderConfig config; VP8StatusCode vp8_status_code = VP8_STATUS_OK; - char* mode = "RGB"; + char *mode = "RGB"; if (!PyArg_ParseTuple(args, "S", &webp_string)) { return NULL; @@ -692,7 +774,7 @@ PyObject* WebPDecode_wrapper(PyObject* self, PyObject* args) Py_RETURN_NONE; } - PyBytes_AsStringAndSize((PyObject*) webp_string, (char**)&webp, &size); + PyBytes_AsStringAndSize((PyObject *)webp_string, (char **)&webp, &size); vp8_status_code = WebPGetFeatures(webp, size, &config.input); if (vp8_status_code == VP8_STATUS_OK) { @@ -706,58 +788,67 @@ PyObject* WebPDecode_wrapper(PyObject* self, PyObject* args) #ifndef HAVE_WEBPMUX vp8_status_code = WebPDecode(webp, size, &config); #else - { - int copy_data = 0; - WebPData data = { webp, size }; - WebPMuxFrameInfo image; - WebPData icc_profile_data = {0}; - WebPData exif_data = {0}; - - WebPMux* mux = WebPMuxCreate(&data, copy_data); - if (NULL == mux) - goto end; - - if (WEBP_MUX_OK != WebPMuxGetFrame(mux, 1, &image)) { + int copy_data = 0; + WebPData data = {webp, size}; + WebPMuxFrameInfo image; + WebPData icc_profile_data = {0}; + WebPData exif_data = {0}; + + WebPMux *mux = WebPMuxCreate(&data, copy_data); + if (NULL == mux) { + goto end; + } + + if (WEBP_MUX_OK != WebPMuxGetFrame(mux, 1, &image)) { + WebPMuxDelete(mux); + goto end; + } + + webp = image.bitstream.bytes; + size = image.bitstream.size; + + vp8_status_code = WebPDecode(webp, size, &config); + + if (WEBP_MUX_OK == WebPMuxGetChunk(mux, "ICCP", &icc_profile_data)) { + icc_profile = PyBytes_FromStringAndSize( + (const char *)icc_profile_data.bytes, icc_profile_data.size); + } + + if (WEBP_MUX_OK == WebPMuxGetChunk(mux, "EXIF", &exif_data)) { + exif = PyBytes_FromStringAndSize( + (const char *)exif_data.bytes, exif_data.size); + } + + WebPDataClear(&image.bitstream); WebPMuxDelete(mux); - goto end; - } - - webp = image.bitstream.bytes; - size = image.bitstream.size; - - vp8_status_code = WebPDecode(webp, size, &config); - - if (WEBP_MUX_OK == WebPMuxGetChunk(mux, "ICCP", &icc_profile_data)) - icc_profile = PyBytes_FromStringAndSize((const char*)icc_profile_data.bytes, icc_profile_data.size); - - if (WEBP_MUX_OK == WebPMuxGetChunk(mux, "EXIF", &exif_data)) - exif = PyBytes_FromStringAndSize((const char*)exif_data.bytes, exif_data.size); - - WebPDataClear(&image.bitstream); - WebPMuxDelete(mux); } #endif } - if (vp8_status_code != VP8_STATUS_OK) + if (vp8_status_code != VP8_STATUS_OK) { goto end; + } if (config.output.colorspace < MODE_YUV) { - bytes = PyBytes_FromStringAndSize((char*)config.output.u.RGBA.rgba, - config.output.u.RGBA.size); + bytes = PyBytes_FromStringAndSize( + (char *)config.output.u.RGBA.rgba, config.output.u.RGBA.size); } else { // Skipping YUV for now. Need Test Images. // UNDONE -- unclear if we'll ever get here if we set mode_rgb* - bytes = PyBytes_FromStringAndSize((char*)config.output.u.YUVA.y, - config.output.u.YUVA.y_size); + bytes = PyBytes_FromStringAndSize( + (char *)config.output.u.YUVA.y, config.output.u.YUVA.y_size); } pymode = PyUnicode_FromString(mode); - ret = Py_BuildValue("SiiSSS", bytes, config.output.width, - config.output.height, pymode, - NULL == icc_profile ? Py_None : icc_profile, - NULL == exif ? Py_None : exif); + ret = Py_BuildValue( + "SiiSSS", + bytes, + config.output.width, + config.output.height, + pymode, + NULL == icc_profile ? Py_None : icc_profile, + NULL == exif ? Py_None : exif); end: WebPFreeDecBuffer(&config.output); @@ -767,27 +858,45 @@ end: Py_XDECREF(icc_profile); Py_XDECREF(exif); - if (Py_None == ret) + if (Py_None == ret) { Py_RETURN_NONE; + } return ret; } // Return the decoder's version number, packed in hexadecimal using 8bits for // each of major/minor/revision. E.g: v2.5.7 is 0x020507. -PyObject* WebPDecoderVersion_wrapper(PyObject* self, PyObject* args){ +PyObject * +WebPDecoderVersion_wrapper() { return Py_BuildValue("i", WebPGetDecoderVersion()); } +// Version as string +const char * +WebPDecoderVersion_str(void) { + static char version[20]; + int version_number = WebPGetDecoderVersion(); + sprintf( + version, + "%d.%d.%d", + version_number >> 16, + (version_number >> 8) % 0x100, + version_number % 0x100); + return version; +} + /* * The version of webp that ships with (0.1.3) Ubuntu 12.04 doesn't handle alpha well. * Files that are valid with 0.3 are reported as being invalid. */ -int WebPDecoderBuggyAlpha(void) { - return WebPGetDecoderVersion()==0x0103; +int +WebPDecoderBuggyAlpha(void) { + return WebPGetDecoderVersion() == 0x0103; } -PyObject* WebPDecoderBuggyAlpha_wrapper(PyObject* self, PyObject* args){ +PyObject * +WebPDecoderBuggyAlpha_wrapper() { return Py_BuildValue("i", WebPDecoderBuggyAlpha()); } @@ -795,20 +904,22 @@ PyObject* WebPDecoderBuggyAlpha_wrapper(PyObject* self, PyObject* args){ /* Module Setup */ /* -------------------------------------------------------------------- */ -static PyMethodDef webpMethods[] = -{ +static PyMethodDef webpMethods[] = { #ifdef HAVE_WEBPANIM {"WebPAnimDecoder", _anim_decoder_new, METH_VARARGS, "WebPAnimDecoder"}, {"WebPAnimEncoder", _anim_encoder_new, METH_VARARGS, "WebPAnimEncoder"}, #endif {"WebPEncode", WebPEncode_wrapper, METH_VARARGS, "WebPEncode"}, {"WebPDecode", WebPDecode_wrapper, METH_VARARGS, "WebPDecode"}, - {"WebPDecoderVersion", WebPDecoderVersion_wrapper, METH_VARARGS, "WebPVersion"}, - {"WebPDecoderBuggyAlpha", WebPDecoderBuggyAlpha_wrapper, METH_VARARGS, "WebPDecoderBuggyAlpha"}, - {NULL, NULL} -}; + {"WebPDecoderVersion", WebPDecoderVersion_wrapper, METH_NOARGS, "WebPVersion"}, + {"WebPDecoderBuggyAlpha", + WebPDecoderBuggyAlpha_wrapper, + METH_NOARGS, + "WebPDecoderBuggyAlpha"}, + {NULL, NULL}}; -void addMuxFlagToModule(PyObject* m) { +void +addMuxFlagToModule(PyObject *m) { #ifdef HAVE_WEBPMUX PyModule_AddObject(m, "HAVE_WEBPMUX", Py_True); #else @@ -816,7 +927,8 @@ void addMuxFlagToModule(PyObject* m) { #endif } -void addAnimFlagToModule(PyObject* m) { +void +addAnimFlagToModule(PyObject *m) { #ifdef HAVE_WEBPANIM PyModule_AddObject(m, "HAVE_WEBPANIM", Py_True); #else @@ -824,40 +936,48 @@ void addAnimFlagToModule(PyObject* m) { #endif } -void addTransparencyFlagToModule(PyObject* m) { - PyModule_AddObject(m, "HAVE_TRANSPARENCY", - PyBool_FromLong(!WebPDecoderBuggyAlpha())); +void +addTransparencyFlagToModule(PyObject *m) { + PyModule_AddObject( + m, "HAVE_TRANSPARENCY", PyBool_FromLong(!WebPDecoderBuggyAlpha())); } -static int setup_module(PyObject* m) { +static int +setup_module(PyObject *m) { + PyObject *d = PyModule_GetDict(m); addMuxFlagToModule(m); addAnimFlagToModule(m); addTransparencyFlagToModule(m); + PyDict_SetItemString( + d, "webpdecoder_version", PyUnicode_FromString(WebPDecoderVersion_str())); + #ifdef HAVE_WEBPANIM /* Ready object types */ if (PyType_Ready(&WebPAnimDecoder_Type) < 0 || - PyType_Ready(&WebPAnimEncoder_Type) < 0) + PyType_Ready(&WebPAnimEncoder_Type) < 0) { return -1; + } #endif return 0; } PyMODINIT_FUNC PyInit__webp(void) { - PyObject* m; + PyObject *m; static PyModuleDef module_def = { PyModuleDef_HEAD_INIT, - "_webp", /* m_name */ - NULL, /* m_doc */ - -1, /* m_size */ - webpMethods, /* m_methods */ + "_webp", /* m_name */ + NULL, /* m_doc */ + -1, /* m_size */ + webpMethods, /* m_methods */ }; m = PyModule_Create(&module_def); - if (setup_module(m) < 0) + if (setup_module(m) < 0) { return NULL; + } return m; } diff --git a/src/decode.c b/src/decode.c index 5ab6ca9d1..5d64bd0b9 100644 --- a/src/decode.c +++ b/src/decode.c @@ -32,57 +32,57 @@ #define PY_SSIZE_T_CLEAN #include "Python.h" -#include "Imaging.h" - -#include "Gif.h" -#include "Raw.h" -#include "Bit.h" -#include "Sgi.h" +#include "libImaging/Imaging.h" +#include "libImaging/Gif.h" +#include "libImaging/Raw.h" +#include "libImaging/Bit.h" +#include "libImaging/Sgi.h" /* -------------------------------------------------------------------- */ /* Common */ /* -------------------------------------------------------------------- */ typedef struct { - PyObject_HEAD - int (*decode)(Imaging im, ImagingCodecState state, - UINT8* buffer, Py_ssize_t bytes); + PyObject_HEAD int (*decode)( + Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes); int (*cleanup)(ImagingCodecState state); struct ImagingCodecStateInstance state; Imaging im; - PyObject* lock; + PyObject *lock; int pulls_fd; } ImagingDecoderObject; static PyTypeObject ImagingDecoderType; -static ImagingDecoderObject* -PyImaging_DecoderNew(int contextsize) -{ +static ImagingDecoderObject * +PyImaging_DecoderNew(int contextsize) { ImagingDecoderObject *decoder; void *context; - if(PyType_Ready(&ImagingDecoderType) < 0) + if (PyType_Ready(&ImagingDecoderType) < 0) { return NULL; + } decoder = PyObject_New(ImagingDecoderObject, &ImagingDecoderType); - if (decoder == NULL) + if (decoder == NULL) { return NULL; + } /* Clear the decoder state */ memset(&decoder->state, 0, sizeof(decoder->state)); /* Allocate decoder context */ if (contextsize > 0) { - context = (void*) calloc(1, contextsize); + context = (void *)calloc(1, contextsize); if (!context) { Py_DECREF(decoder); - (void) PyErr_NoMemory(); + (void)ImagingError_MemoryError(); return NULL; } - } else + } else { context = 0; + } /* Initialize decoder context */ decoder->state.context = context; @@ -102,10 +102,10 @@ PyImaging_DecoderNew(int contextsize) } static void -_dealloc(ImagingDecoderObject* decoder) -{ - if (decoder->cleanup) +_dealloc(ImagingDecoderObject *decoder) { + if (decoder->cleanup) { decoder->cleanup(&decoder->state); + } free(decoder->state.buffer); free(decoder->state.context); Py_XDECREF(decoder->lock); @@ -113,16 +113,16 @@ _dealloc(ImagingDecoderObject* decoder) PyObject_Del(decoder); } -static PyObject* -_decode(ImagingDecoderObject* decoder, PyObject* args) -{ - UINT8* buffer; +static PyObject * +_decode(ImagingDecoderObject *decoder, PyObject *args) { + UINT8 *buffer; Py_ssize_t bufsize; int status; ImagingSectionCookie cookie; - if (!PyArg_ParseTuple(args, "y#", &buffer, &bufsize)) + if (!PyArg_ParseTuple(args, "y#", &buffer, &bufsize)) { return NULL; + } if (!decoder->pulls_fd) { ImagingSectionEnter(&cookie); @@ -137,26 +137,23 @@ _decode(ImagingDecoderObject* decoder, PyObject* args) return Py_BuildValue("ii", status, decoder->state.errcode); } -static PyObject* -_decode_cleanup(ImagingDecoderObject* decoder, PyObject* args) -{ +static PyObject * +_decode_cleanup(ImagingDecoderObject *decoder, PyObject *args) { int status = 0; - if (decoder->cleanup){ + if (decoder->cleanup) { status = decoder->cleanup(&decoder->state); } return Py_BuildValue("i", status); } +extern Imaging +PyImaging_AsImaging(PyObject *op); - -extern Imaging PyImaging_AsImaging(PyObject *op); - -static PyObject* -_setimage(ImagingDecoderObject* decoder, PyObject* args) -{ - PyObject* op; +static PyObject * +_setimage(ImagingDecoderObject *decoder, PyObject *args) { + PyObject *op; Imaging im; ImagingCodecState state; int x0, y0, x1, y1; @@ -164,11 +161,13 @@ _setimage(ImagingDecoderObject* decoder, PyObject* args) x0 = y0 = x1 = y1 = 0; /* FIXME: should publish the ImagingType descriptor */ - if (!PyArg_ParseTuple(args, "O|(iiii)", &op, &x0, &y0, &x1, &y1)) + if (!PyArg_ParseTuple(args, "O|(iiii)", &op, &x0, &y0, &x1, &y1)) { return NULL; + } im = PyImaging_AsImaging(op); - if (!im) + if (!im) { return NULL; + } decoder->im = im; @@ -185,10 +184,8 @@ _setimage(ImagingDecoderObject* decoder, PyObject* args) state->ysize = y1 - y0; } - if (state->xsize <= 0 || - state->xsize + state->xoff > (int) im->xsize || - state->ysize <= 0 || - state->ysize + state->yoff > (int) im->ysize) { + if (state->xsize <= 0 || state->xsize + state->xoff > (int)im->xsize || + state->ysize <= 0 || state->ysize + state->yoff > (int)im->ysize) { PyErr_SetString(PyExc_ValueError, "tile cannot extend outside image"); return NULL; } @@ -196,15 +193,16 @@ _setimage(ImagingDecoderObject* decoder, PyObject* args) /* Allocate memory buffer (if bits field is set) */ if (state->bits > 0) { if (!state->bytes) { - if (state->xsize > ((INT_MAX / state->bits)-7)){ - return PyErr_NoMemory(); + if (state->xsize > ((INT_MAX / state->bits) - 7)) { + return ImagingError_MemoryError(); } - state->bytes = (state->bits * state->xsize+7)/8; + state->bytes = (state->bits * state->xsize + 7) / 8; } /* malloc check ok, overflow checked above */ - state->buffer = (UINT8*) malloc(state->bytes); - if (!state->buffer) - return PyErr_NoMemory(); + state->buffer = (UINT8 *)malloc(state->bytes); + if (!state->buffer) { + return ImagingError_MemoryError(); + } } /* Keep a reference to the image object, to make sure it doesn't @@ -217,14 +215,14 @@ _setimage(ImagingDecoderObject* decoder, PyObject* args) return Py_None; } -static PyObject* -_setfd(ImagingDecoderObject* decoder, PyObject* args) -{ - PyObject* fd; +static PyObject * +_setfd(ImagingDecoderObject *decoder, PyObject *args) { + PyObject *fd; ImagingCodecState state; - if (!PyArg_ParseTuple(args, "O", &fd)) + if (!PyArg_ParseTuple(args, "O", &fd)) { return NULL; + } state = &decoder->state; @@ -235,10 +233,8 @@ _setfd(ImagingDecoderObject* decoder, PyObject* args) return Py_None; } - static PyObject * -_get_pulls_fd(ImagingDecoderObject *decoder) -{ +_get_pulls_fd(ImagingDecoderObject *decoder) { return PyBool_FromLong(decoder->pulls_fd); } @@ -251,52 +247,51 @@ static struct PyMethodDef methods[] = { }; static struct PyGetSetDef getseters[] = { - {"pulls_fd", (getter)_get_pulls_fd, NULL, + {"pulls_fd", + (getter)_get_pulls_fd, + NULL, "True if this decoder expects to pull from self.fd itself.", NULL}, {NULL, NULL, NULL, NULL, NULL} /* sentinel */ }; static PyTypeObject ImagingDecoderType = { - PyVarObject_HEAD_INIT(NULL, 0) - "ImagingDecoder", /*tp_name*/ - sizeof(ImagingDecoderObject), /*tp_size*/ - 0, /*tp_itemsize*/ + PyVarObject_HEAD_INIT(NULL, 0) "ImagingDecoder", /*tp_name*/ + sizeof(ImagingDecoderObject), /*tp_size*/ + 0, /*tp_itemsize*/ /* methods */ - (destructor)_dealloc, /*tp_dealloc*/ - 0, /*tp_print*/ - 0, /*tp_getattr*/ - 0, /*tp_setattr*/ - 0, /*tp_compare*/ - 0, /*tp_repr*/ - 0, /*tp_as_number */ - 0, /*tp_as_sequence */ - 0, /*tp_as_mapping */ - 0, /*tp_hash*/ - 0, /*tp_call*/ - 0, /*tp_str*/ - 0, /*tp_getattro*/ - 0, /*tp_setattro*/ - 0, /*tp_as_buffer*/ - Py_TPFLAGS_DEFAULT, /*tp_flags*/ - 0, /*tp_doc*/ - 0, /*tp_traverse*/ - 0, /*tp_clear*/ - 0, /*tp_richcompare*/ - 0, /*tp_weaklistoffset*/ - 0, /*tp_iter*/ - 0, /*tp_iternext*/ - methods, /*tp_methods*/ - 0, /*tp_members*/ - getseters, /*tp_getset*/ + (destructor)_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number */ + 0, /*tp_as_sequence */ + 0, /*tp_as_mapping */ + 0, /*tp_hash*/ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT, /*tp_flags*/ + 0, /*tp_doc*/ + 0, /*tp_traverse*/ + 0, /*tp_clear*/ + 0, /*tp_richcompare*/ + 0, /*tp_weaklistoffset*/ + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + methods, /*tp_methods*/ + 0, /*tp_members*/ + getseters, /*tp_getset*/ }; /* -------------------------------------------------------------------- */ int -get_unpacker(ImagingDecoderObject* decoder, const char* mode, - const char* rawmode) -{ +get_unpacker(ImagingDecoderObject *decoder, const char *mode, const char *rawmode) { int bits; ImagingShuffler unpack; @@ -313,25 +308,23 @@ get_unpacker(ImagingDecoderObject* decoder, const char* mode, return 0; } - /* -------------------------------------------------------------------- */ /* BIT (packed fields) */ /* -------------------------------------------------------------------- */ -PyObject* -PyImaging_BitDecoderNew(PyObject* self, PyObject* args) -{ - ImagingDecoderObject* decoder; +PyObject * +PyImaging_BitDecoderNew(PyObject *self, PyObject *args) { + ImagingDecoderObject *decoder; - char* mode; - int bits = 8; - int pad = 8; - int fill = 0; - int sign = 0; + char *mode; + int bits = 8; + int pad = 8; + int fill = 0; + int sign = 0; int ystep = 1; - if (!PyArg_ParseTuple(args, "s|iiiii", &mode, &bits, &pad, &fill, - &sign, &ystep)) + if (!PyArg_ParseTuple(args, "s|iiiii", &mode, &bits, &pad, &fill, &sign, &ystep)) { return NULL; + } if (strcmp(mode, "F") != 0) { PyErr_SetString(PyExc_ValueError, "bad image mode"); @@ -339,53 +332,56 @@ PyImaging_BitDecoderNew(PyObject* self, PyObject* args) } decoder = PyImaging_DecoderNew(sizeof(BITSTATE)); - if (decoder == NULL) + if (decoder == NULL) { return NULL; + } decoder->decode = ImagingBitDecode; decoder->state.ystep = ystep; - ((BITSTATE*)decoder->state.context)->bits = bits; - ((BITSTATE*)decoder->state.context)->pad = pad; - ((BITSTATE*)decoder->state.context)->fill = fill; - ((BITSTATE*)decoder->state.context)->sign = sign; + ((BITSTATE *)decoder->state.context)->bits = bits; + ((BITSTATE *)decoder->state.context)->pad = pad; + ((BITSTATE *)decoder->state.context)->fill = fill; + ((BITSTATE *)decoder->state.context)->sign = sign; - return (PyObject*) decoder; + return (PyObject *)decoder; } - /* -------------------------------------------------------------------- */ /* BCn: GPU block-compressed texture formats */ /* -------------------------------------------------------------------- */ -PyObject* -PyImaging_BcnDecoderNew(PyObject* self, PyObject* args) -{ - ImagingDecoderObject* decoder; +PyObject * +PyImaging_BcnDecoderNew(PyObject *self, PyObject *args) { + ImagingDecoderObject *decoder; - char* mode; - char* actual; + char *mode; + char *actual; int n = 0; int ystep = 1; - if (!PyArg_ParseTuple(args, "s|ii", &mode, &n, &ystep)) + if (!PyArg_ParseTuple(args, "s|ii", &mode, &n, &ystep)) { return NULL; + } switch (n) { - case 1: /* BC1: 565 color, 1-bit alpha */ - case 2: /* BC2: 565 color, 4-bit alpha */ - case 3: /* BC3: 565 color, 2-endpoint 8-bit interpolated alpha */ - case 5: /* BC5: 2-channel 8-bit via 2 BC3 alpha blocks */ - case 7: /* BC7: 4-channel 8-bit via everything */ - actual = "RGBA"; break; - case 4: /* BC4: 1-channel 8-bit via 1 BC3 alpha block */ - actual = "L"; break; - case 6: /* BC6: 3-channel 16-bit float */ - /* TODO: support 4-channel floating point images */ - actual = "RGBAF"; break; - default: - PyErr_SetString(PyExc_ValueError, "block compression type unknown"); - return NULL; + case 1: /* BC1: 565 color, 1-bit alpha */ + case 2: /* BC2: 565 color, 4-bit alpha */ + case 3: /* BC3: 565 color, 2-endpoint 8-bit interpolated alpha */ + case 5: /* BC5: 2-channel 8-bit via 2 BC3 alpha blocks */ + case 7: /* BC7: 4-channel 8-bit via everything */ + actual = "RGBA"; + break; + case 4: /* BC4: 1-channel 8-bit via 1 BC3 alpha block */ + actual = "L"; + break; + case 6: /* BC6: 3-channel 16-bit float */ + /* TODO: support 4-channel floating point images */ + actual = "RGBAF"; + break; + default: + PyErr_SetString(PyExc_ValueError, "block compression type unknown"); + return NULL; } if (strcmp(mode, actual) != 0) { @@ -394,50 +390,49 @@ PyImaging_BcnDecoderNew(PyObject* self, PyObject* args) } decoder = PyImaging_DecoderNew(0); - if (decoder == NULL) + if (decoder == NULL) { return NULL; + } decoder->decode = ImagingBcnDecode; decoder->state.state = n; decoder->state.ystep = ystep; - return (PyObject*) decoder; + return (PyObject *)decoder; } - /* -------------------------------------------------------------------- */ /* FLI */ /* -------------------------------------------------------------------- */ -PyObject* -PyImaging_FliDecoderNew(PyObject* self, PyObject* args) -{ - ImagingDecoderObject* decoder; +PyObject * +PyImaging_FliDecoderNew(PyObject *self, PyObject *args) { + ImagingDecoderObject *decoder; decoder = PyImaging_DecoderNew(0); - if (decoder == NULL) + if (decoder == NULL) { return NULL; + } decoder->decode = ImagingFliDecode; - return (PyObject*) decoder; + return (PyObject *)decoder; } - /* -------------------------------------------------------------------- */ /* GIF */ /* -------------------------------------------------------------------- */ -PyObject* -PyImaging_GifDecoderNew(PyObject* self, PyObject* args) -{ - ImagingDecoderObject* decoder; +PyObject * +PyImaging_GifDecoderNew(PyObject *self, PyObject *args) { + ImagingDecoderObject *decoder; - char* mode; + char *mode; int bits = 8; int interlace = 0; - if (!PyArg_ParseTuple(args, "s|ii", &mode, &bits, &interlace)) + if (!PyArg_ParseTuple(args, "s|ii", &mode, &bits, &interlace)) { return NULL; + } if (strcmp(mode, "L") != 0 && strcmp(mode, "P") != 0) { PyErr_SetString(PyExc_ValueError, "bad image mode"); @@ -445,354 +440,363 @@ PyImaging_GifDecoderNew(PyObject* self, PyObject* args) } decoder = PyImaging_DecoderNew(sizeof(GIFDECODERSTATE)); - if (decoder == NULL) + if (decoder == NULL) { return NULL; + } decoder->decode = ImagingGifDecode; - ((GIFDECODERSTATE*)decoder->state.context)->bits = bits; - ((GIFDECODERSTATE*)decoder->state.context)->interlace = interlace; + ((GIFDECODERSTATE *)decoder->state.context)->bits = bits; + ((GIFDECODERSTATE *)decoder->state.context)->interlace = interlace; - return (PyObject*) decoder; + return (PyObject *)decoder; } - /* -------------------------------------------------------------------- */ /* HEX */ /* -------------------------------------------------------------------- */ -PyObject* -PyImaging_HexDecoderNew(PyObject* self, PyObject* args) -{ - ImagingDecoderObject* decoder; +PyObject * +PyImaging_HexDecoderNew(PyObject *self, PyObject *args) { + ImagingDecoderObject *decoder; - char* mode; - char* rawmode; - if (!PyArg_ParseTuple(args, "ss", &mode, &rawmode)) + char *mode; + char *rawmode; + if (!PyArg_ParseTuple(args, "ss", &mode, &rawmode)) { return NULL; + } decoder = PyImaging_DecoderNew(0); - if (decoder == NULL) + if (decoder == NULL) { return NULL; + } - if (get_unpacker(decoder, mode, rawmode) < 0) + if (get_unpacker(decoder, mode, rawmode) < 0) { return NULL; + } decoder->decode = ImagingHexDecode; - return (PyObject*) decoder; + return (PyObject *)decoder; } - /* -------------------------------------------------------------------- */ /* LibTiff */ /* -------------------------------------------------------------------- */ #ifdef HAVE_LIBTIFF -#include "TiffDecode.h" +#include "libImaging/TiffDecode.h" #include -PyObject* -PyImaging_LibTiffDecoderNew(PyObject* self, PyObject* args) -{ - ImagingDecoderObject* decoder; - char* mode; - char* rawmode; - char* compname; +PyObject * +PyImaging_LibTiffDecoderNew(PyObject *self, PyObject *args) { + ImagingDecoderObject *decoder; + char *mode; + char *rawmode; + char *compname; int fp; uint32 ifdoffset; - if (! PyArg_ParseTuple(args, "sssiI", &mode, &rawmode, &compname, &fp, &ifdoffset)) + if (!PyArg_ParseTuple(args, "sssiI", &mode, &rawmode, &compname, &fp, &ifdoffset)) { return NULL; + } TRACE(("new tiff decoder %s\n", compname)); decoder = PyImaging_DecoderNew(sizeof(TIFFSTATE)); - if (decoder == NULL) + if (decoder == NULL) { return NULL; + } - if (get_unpacker(decoder, mode, rawmode) < 0) + if (get_unpacker(decoder, mode, rawmode) < 0) { return NULL; + } - if (! ImagingLibTiffInit(&decoder->state, fp, ifdoffset)) { + if (!ImagingLibTiffInit(&decoder->state, fp, ifdoffset)) { Py_DECREF(decoder); PyErr_SetString(PyExc_RuntimeError, "tiff codec initialization failed"); return NULL; } - decoder->decode = ImagingLibTiffDecode; + decoder->decode = ImagingLibTiffDecode; - return (PyObject*) decoder; + return (PyObject *)decoder; } #endif - /* -------------------------------------------------------------------- */ /* PackBits */ /* -------------------------------------------------------------------- */ -PyObject* -PyImaging_PackbitsDecoderNew(PyObject* self, PyObject* args) -{ - ImagingDecoderObject* decoder; +PyObject * +PyImaging_PackbitsDecoderNew(PyObject *self, PyObject *args) { + ImagingDecoderObject *decoder; - char* mode; - char* rawmode; - if (!PyArg_ParseTuple(args, "ss", &mode, &rawmode)) + char *mode; + char *rawmode; + if (!PyArg_ParseTuple(args, "ss", &mode, &rawmode)) { return NULL; + } decoder = PyImaging_DecoderNew(0); - if (decoder == NULL) + if (decoder == NULL) { return NULL; + } - if (get_unpacker(decoder, mode, rawmode) < 0) + if (get_unpacker(decoder, mode, rawmode) < 0) { return NULL; + } decoder->decode = ImagingPackbitsDecode; - return (PyObject*) decoder; + return (PyObject *)decoder; } - /* -------------------------------------------------------------------- */ /* PCD */ /* -------------------------------------------------------------------- */ -PyObject* -PyImaging_PcdDecoderNew(PyObject* self, PyObject* args) -{ - ImagingDecoderObject* decoder; +PyObject * +PyImaging_PcdDecoderNew(PyObject *self, PyObject *args) { + ImagingDecoderObject *decoder; decoder = PyImaging_DecoderNew(0); - if (decoder == NULL) + if (decoder == NULL) { return NULL; + } /* Unpack from PhotoYCC to RGB */ - if (get_unpacker(decoder, "RGB", "YCC;P") < 0) + if (get_unpacker(decoder, "RGB", "YCC;P") < 0) { return NULL; + } decoder->decode = ImagingPcdDecode; - return (PyObject*) decoder; + return (PyObject *)decoder; } - /* -------------------------------------------------------------------- */ /* PCX */ /* -------------------------------------------------------------------- */ -PyObject* -PyImaging_PcxDecoderNew(PyObject* self, PyObject* args) -{ - ImagingDecoderObject* decoder; +PyObject * +PyImaging_PcxDecoderNew(PyObject *self, PyObject *args) { + ImagingDecoderObject *decoder; - char* mode; - char* rawmode; + char *mode; + char *rawmode; int stride; - if (!PyArg_ParseTuple(args, "ssi", &mode, &rawmode, &stride)) + if (!PyArg_ParseTuple(args, "ssi", &mode, &rawmode, &stride)) { return NULL; + } decoder = PyImaging_DecoderNew(0); - if (decoder == NULL) + if (decoder == NULL) { return NULL; + } - if (get_unpacker(decoder, mode, rawmode) < 0) + if (get_unpacker(decoder, mode, rawmode) < 0) { return NULL; + } decoder->state.bytes = stride; decoder->decode = ImagingPcxDecode; - return (PyObject*) decoder; + return (PyObject *)decoder; } - /* -------------------------------------------------------------------- */ /* RAW */ /* -------------------------------------------------------------------- */ -PyObject* -PyImaging_RawDecoderNew(PyObject* self, PyObject* args) -{ - ImagingDecoderObject* decoder; +PyObject * +PyImaging_RawDecoderNew(PyObject *self, PyObject *args) { + ImagingDecoderObject *decoder; - char* mode; - char* rawmode; + char *mode; + char *rawmode; int stride = 0; - int ystep = 1; - if (!PyArg_ParseTuple(args, "ss|ii", &mode, &rawmode, &stride, &ystep)) + int ystep = 1; + if (!PyArg_ParseTuple(args, "ss|ii", &mode, &rawmode, &stride, &ystep)) { return NULL; + } decoder = PyImaging_DecoderNew(sizeof(RAWSTATE)); - if (decoder == NULL) + if (decoder == NULL) { return NULL; + } - if (get_unpacker(decoder, mode, rawmode) < 0) + if (get_unpacker(decoder, mode, rawmode) < 0) { return NULL; + } decoder->decode = ImagingRawDecode; decoder->state.ystep = ystep; - ((RAWSTATE*)decoder->state.context)->stride = stride; + ((RAWSTATE *)decoder->state.context)->stride = stride; - return (PyObject*) decoder; + return (PyObject *)decoder; } - /* -------------------------------------------------------------------- */ /* SGI RLE */ /* -------------------------------------------------------------------- */ -PyObject* -PyImaging_SgiRleDecoderNew(PyObject* self, PyObject* args) -{ - ImagingDecoderObject* decoder; +PyObject * +PyImaging_SgiRleDecoderNew(PyObject *self, PyObject *args) { + ImagingDecoderObject *decoder; - char* mode; - char* rawmode; + char *mode; + char *rawmode; int ystep = 1; int bpc = 1; - if (!PyArg_ParseTuple(args, "ss|ii", &mode, &rawmode, &ystep, &bpc)) + if (!PyArg_ParseTuple(args, "ss|ii", &mode, &rawmode, &ystep, &bpc)) { return NULL; + } decoder = PyImaging_DecoderNew(sizeof(SGISTATE)); - if (decoder == NULL) + if (decoder == NULL) { return NULL; + } - if (get_unpacker(decoder, mode, rawmode) < 0) + if (get_unpacker(decoder, mode, rawmode) < 0) { return NULL; + } decoder->pulls_fd = 1; decoder->decode = ImagingSgiRleDecode; decoder->state.ystep = ystep; - ((SGISTATE*)decoder->state.context)->bpc = bpc; + ((SGISTATE *)decoder->state.context)->bpc = bpc; - return (PyObject*) decoder; + return (PyObject *)decoder; } - /* -------------------------------------------------------------------- */ /* SUN RLE */ /* -------------------------------------------------------------------- */ -PyObject* -PyImaging_SunRleDecoderNew(PyObject* self, PyObject* args) -{ - ImagingDecoderObject* decoder; +PyObject * +PyImaging_SunRleDecoderNew(PyObject *self, PyObject *args) { + ImagingDecoderObject *decoder; - char* mode; - char* rawmode; - if (!PyArg_ParseTuple(args, "ss", &mode, &rawmode)) + char *mode; + char *rawmode; + if (!PyArg_ParseTuple(args, "ss", &mode, &rawmode)) { return NULL; + } decoder = PyImaging_DecoderNew(0); - if (decoder == NULL) + if (decoder == NULL) { return NULL; + } - if (get_unpacker(decoder, mode, rawmode) < 0) + if (get_unpacker(decoder, mode, rawmode) < 0) { return NULL; + } decoder->decode = ImagingSunRleDecode; - return (PyObject*) decoder; + return (PyObject *)decoder; } - /* -------------------------------------------------------------------- */ /* TGA RLE */ /* -------------------------------------------------------------------- */ -PyObject* -PyImaging_TgaRleDecoderNew(PyObject* self, PyObject* args) -{ - ImagingDecoderObject* decoder; +PyObject * +PyImaging_TgaRleDecoderNew(PyObject *self, PyObject *args) { + ImagingDecoderObject *decoder; - char* mode; - char* rawmode; + char *mode; + char *rawmode; int ystep = 1; int depth = 8; - if (!PyArg_ParseTuple(args, "ss|ii", &mode, &rawmode, &ystep, &depth)) + if (!PyArg_ParseTuple(args, "ss|ii", &mode, &rawmode, &ystep, &depth)) { return NULL; + } decoder = PyImaging_DecoderNew(0); - if (decoder == NULL) + if (decoder == NULL) { return NULL; + } - if (get_unpacker(decoder, mode, rawmode) < 0) + if (get_unpacker(decoder, mode, rawmode) < 0) { return NULL; + } decoder->decode = ImagingTgaRleDecode; decoder->state.ystep = ystep; decoder->state.count = depth / 8; - return (PyObject*) decoder; + return (PyObject *)decoder; } - /* -------------------------------------------------------------------- */ /* XBM */ /* -------------------------------------------------------------------- */ -PyObject* -PyImaging_XbmDecoderNew(PyObject* self, PyObject* args) -{ - ImagingDecoderObject* decoder; +PyObject * +PyImaging_XbmDecoderNew(PyObject *self, PyObject *args) { + ImagingDecoderObject *decoder; decoder = PyImaging_DecoderNew(0); - if (decoder == NULL) + if (decoder == NULL) { return NULL; + } - if (get_unpacker(decoder, "1", "1;R") < 0) + if (get_unpacker(decoder, "1", "1;R") < 0) { return NULL; + } decoder->decode = ImagingXbmDecode; - return (PyObject*) decoder; + return (PyObject *)decoder; } - /* -------------------------------------------------------------------- */ /* ZIP */ /* -------------------------------------------------------------------- */ #ifdef HAVE_LIBZ -#include "Zip.h" +#include "libImaging/ZipCodecs.h" -PyObject* -PyImaging_ZipDecoderNew(PyObject* self, PyObject* args) -{ - ImagingDecoderObject* decoder; +PyObject * +PyImaging_ZipDecoderNew(PyObject *self, PyObject *args) { + ImagingDecoderObject *decoder; - char* mode; - char* rawmode; + char *mode; + char *rawmode; int interlaced = 0; - if (!PyArg_ParseTuple(args, "ss|i", &mode, &rawmode, &interlaced)) + if (!PyArg_ParseTuple(args, "ss|i", &mode, &rawmode, &interlaced)) { return NULL; + } decoder = PyImaging_DecoderNew(sizeof(ZIPSTATE)); - if (decoder == NULL) + if (decoder == NULL) { return NULL; + } - if (get_unpacker(decoder, mode, rawmode) < 0) + if (get_unpacker(decoder, mode, rawmode) < 0) { return NULL; + } decoder->decode = ImagingZipDecode; decoder->cleanup = ImagingZipDecodeCleanup; - ((ZIPSTATE*)decoder->state.context)->interlaced = interlaced; + ((ZIPSTATE *)decoder->state.context)->interlaced = interlaced; - return (PyObject*) decoder; + return (PyObject *)decoder; } #endif - /* -------------------------------------------------------------------- */ /* JPEG */ /* -------------------------------------------------------------------- */ @@ -802,39 +806,40 @@ PyImaging_ZipDecoderNew(PyObject* self, PyObject* args) /* We better define this decoder last in this file, so the following undef's won't mess things up for the Imaging library proper. */ -#undef HAVE_PROTOTYPES -#undef HAVE_STDDEF_H -#undef HAVE_STDLIB_H -#undef UINT8 -#undef UINT16 -#undef UINT32 -#undef INT8 -#undef INT16 -#undef INT32 +#undef HAVE_PROTOTYPES +#undef HAVE_STDDEF_H +#undef HAVE_STDLIB_H +#undef UINT8 +#undef UINT16 +#undef UINT32 +#undef INT8 +#undef INT16 +#undef INT32 -#include "Jpeg.h" +#include "libImaging/Jpeg.h" -PyObject* -PyImaging_JpegDecoderNew(PyObject* self, PyObject* args) -{ - ImagingDecoderObject* decoder; +PyObject * +PyImaging_JpegDecoderNew(PyObject *self, PyObject *args) { + ImagingDecoderObject *decoder; - char* mode; - char* rawmode; /* what we want from the decoder */ - char* jpegmode; /* what's in the file */ + char *mode; + char *rawmode; /* what we want from the decoder */ + char *jpegmode; /* what's in the file */ int scale = 1; int draft = 0; - if (!PyArg_ParseTuple(args, "ssz|ii", &mode, &rawmode, &jpegmode, - &scale, &draft)) + if (!PyArg_ParseTuple(args, "ssz|ii", &mode, &rawmode, &jpegmode, &scale, &draft)) { return NULL; + } - if (!jpegmode) + if (!jpegmode) { jpegmode = ""; + } decoder = PyImaging_DecoderNew(sizeof(JPEGSTATE)); - if (decoder == NULL) + if (decoder == NULL) { return NULL; + } // libjpeg-turbo supports different output formats. // We are choosing Pillow's native format (3 color bytes + 1 padding) @@ -843,19 +848,20 @@ PyImaging_JpegDecoderNew(PyObject* self, PyObject* args) rawmode = "RGBX"; } - if (get_unpacker(decoder, mode, rawmode) < 0) + if (get_unpacker(decoder, mode, rawmode) < 0) { return NULL; + } decoder->decode = ImagingJpegDecode; decoder->cleanup = ImagingJpegDecodeCleanup; - strncpy(((JPEGSTATE*)decoder->state.context)->rawmode, rawmode, 8); - strncpy(((JPEGSTATE*)decoder->state.context)->jpegmode, jpegmode, 8); + strncpy(((JPEGSTATE *)decoder->state.context)->rawmode, rawmode, 8); + strncpy(((JPEGSTATE *)decoder->state.context)->jpegmode, jpegmode, 8); - ((JPEGSTATE*)decoder->state.context)->scale = scale; - ((JPEGSTATE*)decoder->state.context)->draft = draft; + ((JPEGSTATE *)decoder->state.context)->scale = scale; + ((JPEGSTATE *)decoder->state.context)->draft = draft; - return (PyObject*) decoder; + return (PyObject *)decoder; } #endif @@ -865,38 +871,40 @@ PyImaging_JpegDecoderNew(PyObject* self, PyObject* args) #ifdef HAVE_OPENJPEG -#include "Jpeg2K.h" +#include "libImaging/Jpeg2K.h" -PyObject* -PyImaging_Jpeg2KDecoderNew(PyObject* self, PyObject* args) -{ - ImagingDecoderObject* decoder; +PyObject * +PyImaging_Jpeg2KDecoderNew(PyObject *self, PyObject *args) { + ImagingDecoderObject *decoder; JPEG2KDECODESTATE *context; - char* mode; - char* format; + char *mode; + char *format; OPJ_CODEC_FORMAT codec_format; int reduce = 0; int layers = 0; int fd = -1; PY_LONG_LONG length = -1; - if (!PyArg_ParseTuple(args, "ss|iiiL", &mode, &format, - &reduce, &layers, &fd, &length)) + if (!PyArg_ParseTuple( + args, "ss|iiiL", &mode, &format, &reduce, &layers, &fd, &length)) { return NULL; + } - if (strcmp(format, "j2k") == 0) + if (strcmp(format, "j2k") == 0) { codec_format = OPJ_CODEC_J2K; - else if (strcmp(format, "jpt") == 0) + } else if (strcmp(format, "jpt") == 0) { codec_format = OPJ_CODEC_JPT; - else if (strcmp(format, "jp2") == 0) + } else if (strcmp(format, "jp2") == 0) { codec_format = OPJ_CODEC_JP2; - else + } else { return NULL; + } decoder = PyImaging_DecoderNew(sizeof(JPEG2KDECODESTATE)); - if (decoder == NULL) + if (decoder == NULL) { return NULL; + } decoder->pulls_fd = 1; decoder->decode = ImagingJpeg2KDecode; @@ -910,7 +918,6 @@ PyImaging_Jpeg2KDecoderNew(PyObject* self, PyObject* args) context->reduce = reduce; context->layers = layers; - return (PyObject*) decoder; + return (PyObject *)decoder; } #endif /* HAVE_OPENJPEG */ - diff --git a/src/display.c b/src/display.c index 4c2faf9e0..3541655cf 100644 --- a/src/display.c +++ b/src/display.c @@ -22,17 +22,17 @@ * See the README file for information on usage and redistribution. */ - +#define PY_SSIZE_T_CLEAN #include "Python.h" -#include "Imaging.h" +#include "libImaging/Imaging.h" /* -------------------------------------------------------------------- */ -/* Windows DIB support */ +/* Windows DIB support */ #ifdef _WIN32 -#include "ImDib.h" +#include "libImaging/ImDib.h" #if SIZEOF_VOID_P == 8 #define F_HANDLE "K" @@ -41,47 +41,47 @@ #endif typedef struct { - PyObject_HEAD - ImagingDIB dib; + PyObject_HEAD ImagingDIB dib; } ImagingDisplayObject; static PyTypeObject ImagingDisplayType; -static ImagingDisplayObject* -_new(const char* mode, int xsize, int ysize) -{ +static ImagingDisplayObject * +_new(const char *mode, int xsize, int ysize) { ImagingDisplayObject *display; - if (PyType_Ready(&ImagingDisplayType) < 0) + if (PyType_Ready(&ImagingDisplayType) < 0) { return NULL; + } display = PyObject_New(ImagingDisplayObject, &ImagingDisplayType); - if (display == NULL) - return NULL; + if (display == NULL) { + return NULL; + } display->dib = ImagingNewDIB(mode, xsize, ysize); if (!display->dib) { - Py_DECREF(display); - return NULL; + Py_DECREF(display); + return NULL; } return display; } static void -_delete(ImagingDisplayObject* display) -{ - if (display->dib) - ImagingDeleteDIB(display->dib); +_delete(ImagingDisplayObject *display) { + if (display->dib) { + ImagingDeleteDIB(display->dib); + } PyObject_Del(display); } -static PyObject* -_expose(ImagingDisplayObject* display, PyObject* args) -{ +static PyObject * +_expose(ImagingDisplayObject *display, PyObject *args) { HDC hdc; - if (!PyArg_ParseTuple(args, F_HANDLE, &hdc)) - return NULL; + if (!PyArg_ParseTuple(args, F_HANDLE, &hdc)) { + return NULL; + } ImagingExposeDIB(display->dib, hdc); @@ -89,16 +89,25 @@ _expose(ImagingDisplayObject* display, PyObject* args) return Py_None; } -static PyObject* -_draw(ImagingDisplayObject* display, PyObject* args) -{ +static PyObject * +_draw(ImagingDisplayObject *display, PyObject *args) { HDC hdc; int dst[4]; int src[4]; - if (!PyArg_ParseTuple(args, F_HANDLE "(iiii)(iiii)", &hdc, - dst+0, dst+1, dst+2, dst+3, - src+0, src+1, src+2, src+3)) - return NULL; + if (!PyArg_ParseTuple( + args, + F_HANDLE "(iiii)(iiii)", + &hdc, + dst + 0, + dst + 1, + dst + 2, + dst + 3, + src + 0, + src + 1, + src + 2, + src + 3)) { + return NULL; + } ImagingDrawDIB(display->dib, hdc, dst, src); @@ -106,26 +115,30 @@ _draw(ImagingDisplayObject* display, PyObject* args) return Py_None; } -extern Imaging PyImaging_AsImaging(PyObject *op); +extern Imaging +PyImaging_AsImaging(PyObject *op); -static PyObject* -_paste(ImagingDisplayObject* display, PyObject* args) -{ +static PyObject * +_paste(ImagingDisplayObject *display, PyObject *args) { Imaging im; - PyObject* op; + PyObject *op; int xy[4]; xy[0] = xy[1] = xy[2] = xy[3] = 0; - if (!PyArg_ParseTuple(args, "O|(iiii)", &op, xy+0, xy+1, xy+2, xy+3)) - return NULL; + if (!PyArg_ParseTuple(args, "O|(iiii)", &op, xy + 0, xy + 1, xy + 2, xy + 3)) { + return NULL; + } im = PyImaging_AsImaging(op); - if (!im) - return NULL; + if (!im) { + return NULL; + } - if (xy[2] <= xy[0]) - xy[2] = xy[0] + im->xsize; - if (xy[3] <= xy[1]) - xy[3] = xy[1] + im->ysize; + if (xy[2] <= xy[0]) { + xy[2] = xy[0] + im->xsize; + } + if (xy[3] <= xy[1]) { + xy[3] = xy[1] + im->ysize; + } ImagingPasteDIB(display->dib, im, xy); @@ -133,46 +146,46 @@ _paste(ImagingDisplayObject* display, PyObject* args) return Py_None; } -static PyObject* -_query_palette(ImagingDisplayObject* display, PyObject* args) -{ +static PyObject * +_query_palette(ImagingDisplayObject *display, PyObject *args) { HDC hdc; int status; - if (!PyArg_ParseTuple(args, F_HANDLE, &hdc)) - return NULL; + if (!PyArg_ParseTuple(args, F_HANDLE, &hdc)) { + return NULL; + } status = ImagingQueryPaletteDIB(display->dib, hdc); return Py_BuildValue("i", status); } -static PyObject* -_getdc(ImagingDisplayObject* display, PyObject* args) -{ +static PyObject * +_getdc(ImagingDisplayObject *display, PyObject *args) { HWND window; HDC dc; - if (!PyArg_ParseTuple(args, F_HANDLE, &window)) - return NULL; + if (!PyArg_ParseTuple(args, F_HANDLE, &window)) { + return NULL; + } dc = GetDC(window); if (!dc) { - PyErr_SetString(PyExc_IOError, "cannot create dc"); + PyErr_SetString(PyExc_OSError, "cannot create dc"); return NULL; } return Py_BuildValue(F_HANDLE, dc); } -static PyObject* -_releasedc(ImagingDisplayObject* display, PyObject* args) -{ +static PyObject * +_releasedc(ImagingDisplayObject *display, PyObject *args) { HWND window; HDC dc; - if (!PyArg_ParseTuple(args, F_HANDLE F_HANDLE, &window, &dc)) - return NULL; + if (!PyArg_ParseTuple(args, F_HANDLE F_HANDLE, &window, &dc)) { + return NULL; + } ReleaseDC(window, dc); @@ -180,14 +193,14 @@ _releasedc(ImagingDisplayObject* display, PyObject* args) return Py_None; } -static PyObject* -_frombytes(ImagingDisplayObject* display, PyObject* args) -{ - char* ptr; - int bytes; +static PyObject * +_frombytes(ImagingDisplayObject *display, PyObject *args) { + char *ptr; + Py_ssize_t bytes; - if (!PyArg_ParseTuple(args, "y#:frombytes", &ptr, &bytes)) + if (!PyArg_ParseTuple(args, "y#:frombytes", &ptr, &bytes)) { return NULL; + } if (display->dib->ysize * display->dib->linesize != bytes) { PyErr_SetString(PyExc_ValueError, "wrong size"); @@ -200,15 +213,14 @@ _frombytes(ImagingDisplayObject* display, PyObject* args) return Py_None; } -static PyObject* -_tobytes(ImagingDisplayObject* display, PyObject* args) -{ - if (!PyArg_ParseTuple(args, ":tobytes")) +static PyObject * +_tobytes(ImagingDisplayObject *display, PyObject *args) { + if (!PyArg_ParseTuple(args, ":tobytes")) { return NULL; + } return PyBytes_FromStringAndSize( - display->dib->bits, display->dib->ysize * display->dib->linesize - ); + display->dib->bits, display->dib->ysize * display->dib->linesize); } static struct PyMethodDef methods[] = { @@ -220,83 +232,75 @@ static struct PyMethodDef methods[] = { {"releasedc", (PyCFunction)_releasedc, 1}, {"frombytes", (PyCFunction)_frombytes, 1}, {"tobytes", (PyCFunction)_tobytes, 1}, - {"fromstring", (PyCFunction)_frombytes, 1}, - {"tostring", (PyCFunction)_tobytes, 1}, {NULL, NULL} /* sentinel */ }; -static PyObject* -_getattr_mode(ImagingDisplayObject* self, void* closure) -{ - return Py_BuildValue("s", self->dib->mode); +static PyObject * +_getattr_mode(ImagingDisplayObject *self, void *closure) { + return Py_BuildValue("s", self->dib->mode); } -static PyObject* -_getattr_size(ImagingDisplayObject* self, void* closure) -{ - return Py_BuildValue("ii", self->dib->xsize, self->dib->ysize); +static PyObject * +_getattr_size(ImagingDisplayObject *self, void *closure) { + return Py_BuildValue("ii", self->dib->xsize, self->dib->ysize); } static struct PyGetSetDef getsetters[] = { - { "mode", (getter) _getattr_mode }, - { "size", (getter) _getattr_size }, - { NULL } -}; + {"mode", (getter)_getattr_mode}, {"size", (getter)_getattr_size}, {NULL}}; static PyTypeObject ImagingDisplayType = { - PyVarObject_HEAD_INIT(NULL, 0) - "ImagingDisplay", /*tp_name*/ - sizeof(ImagingDisplayObject), /*tp_size*/ - 0, /*tp_itemsize*/ - /* methods */ - (destructor)_delete, /*tp_dealloc*/ - 0, /*tp_print*/ - 0, /*tp_getattr*/ - 0, /*tp_setattr*/ - 0, /*tp_compare*/ - 0, /*tp_repr*/ - 0, /*tp_as_number */ - 0, /*tp_as_sequence */ - 0, /*tp_as_mapping */ - 0, /*tp_hash*/ - 0, /*tp_call*/ - 0, /*tp_str*/ - 0, /*tp_getattro*/ - 0, /*tp_setattro*/ - 0, /*tp_as_buffer*/ - Py_TPFLAGS_DEFAULT, /*tp_flags*/ - 0, /*tp_doc*/ - 0, /*tp_traverse*/ - 0, /*tp_clear*/ - 0, /*tp_richcompare*/ - 0, /*tp_weaklistoffset*/ - 0, /*tp_iter*/ - 0, /*tp_iternext*/ - methods, /*tp_methods*/ - 0, /*tp_members*/ - getsetters, /*tp_getset*/ + PyVarObject_HEAD_INIT(NULL, 0) "ImagingDisplay", /*tp_name*/ + sizeof(ImagingDisplayObject), /*tp_size*/ + 0, /*tp_itemsize*/ + /* methods */ + (destructor)_delete, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number */ + 0, /*tp_as_sequence */ + 0, /*tp_as_mapping */ + 0, /*tp_hash*/ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT, /*tp_flags*/ + 0, /*tp_doc*/ + 0, /*tp_traverse*/ + 0, /*tp_clear*/ + 0, /*tp_richcompare*/ + 0, /*tp_weaklistoffset*/ + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + methods, /*tp_methods*/ + 0, /*tp_members*/ + getsetters, /*tp_getset*/ }; -PyObject* -PyImaging_DisplayWin32(PyObject* self, PyObject* args) -{ - ImagingDisplayObject* display; +PyObject * +PyImaging_DisplayWin32(PyObject *self, PyObject *args) { + ImagingDisplayObject *display; char *mode; int xsize, ysize; - if (!PyArg_ParseTuple(args, "s(ii)", &mode, &xsize, &ysize)) - return NULL; + if (!PyArg_ParseTuple(args, "s(ii)", &mode, &xsize, &ysize)) { + return NULL; + } display = _new(mode, xsize, ysize); - if (display == NULL) - return NULL; + if (display == NULL) { + return NULL; + } - return (PyObject*) display; + return (PyObject *)display; } -PyObject* -PyImaging_DisplayModeWin32(PyObject* self, PyObject* args) -{ +PyObject * +PyImaging_DisplayModeWin32(PyObject *self, PyObject *args) { char *mode; int size[2]; @@ -308,24 +312,24 @@ PyImaging_DisplayModeWin32(PyObject* self, PyObject* args) /* -------------------------------------------------------------------- */ /* Windows screen grabber */ -typedef HANDLE(__stdcall* Func_SetThreadDpiAwarenessContext)(HANDLE); +typedef HANDLE(__stdcall *Func_SetThreadDpiAwarenessContext)(HANDLE); -PyObject* -PyImaging_GrabScreenWin32(PyObject* self, PyObject* args) -{ +PyObject * +PyImaging_GrabScreenWin32(PyObject *self, PyObject *args) { int x = 0, y = 0, width, height; int includeLayeredWindows = 0, all_screens = 0; HBITMAP bitmap; BITMAPCOREHEADER core; HDC screen, screen_copy; DWORD rop; - PyObject* buffer; + PyObject *buffer; HANDLE dpiAwareness; HMODULE user32; Func_SetThreadDpiAwarenessContext SetThreadDpiAwarenessContext_function; - if (!PyArg_ParseTuple(args, "|ii", &includeLayeredWindows, &all_screens)) + if (!PyArg_ParseTuple(args, "|ii", &includeLayeredWindows, &all_screens)) { return NULL; + } /* step 1: create a memory DC large enough to hold the entire screen */ @@ -337,11 +341,11 @@ PyImaging_GrabScreenWin32(PyObject* self, PyObject* args) // loaded dynamically to avoid link errors user32 = LoadLibraryA("User32.dll"); SetThreadDpiAwarenessContext_function = - (Func_SetThreadDpiAwarenessContext) - GetProcAddress(user32, "SetThreadDpiAwarenessContext"); + (Func_SetThreadDpiAwarenessContext)GetProcAddress( + user32, "SetThreadDpiAwarenessContext"); if (SetThreadDpiAwarenessContext_function != NULL) { // DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE = ((DPI_CONTEXT_HANDLE)-3) - dpiAwareness = SetThreadDpiAwarenessContext_function((HANDLE) -3); + dpiAwareness = SetThreadDpiAwarenessContext_function((HANDLE)-3); } if (all_screens) { @@ -361,34 +365,46 @@ PyImaging_GrabScreenWin32(PyObject* self, PyObject* args) FreeLibrary(user32); bitmap = CreateCompatibleBitmap(screen, width, height); - if (!bitmap) + if (!bitmap) { goto error; + } - if (!SelectObject(screen_copy, bitmap)) + if (!SelectObject(screen_copy, bitmap)) { goto error; + } /* step 2: copy bits into memory DC bitmap */ rop = SRCCOPY; - if (includeLayeredWindows) + if (includeLayeredWindows) { rop |= CAPTUREBLT; - if (!BitBlt(screen_copy, 0, 0, width, height, screen, x, y, rop)) + } + if (!BitBlt(screen_copy, 0, 0, width, height, screen, x, y, rop)) { goto error; + } /* step 3: extract bits from bitmap */ - buffer = PyBytes_FromStringAndSize(NULL, height * ((width*3 + 3) & -4)); - if (!buffer) + buffer = PyBytes_FromStringAndSize(NULL, height * ((width * 3 + 3) & -4)); + if (!buffer) { return NULL; + } core.bcSize = sizeof(core); core.bcWidth = width; core.bcHeight = height; core.bcPlanes = 1; core.bcBitCount = 24; - if (!GetDIBits(screen_copy, bitmap, 0, height, PyBytes_AS_STRING(buffer), - (BITMAPINFO*) &core, DIB_RGB_COLORS)) + if (!GetDIBits( + screen_copy, + bitmap, + 0, + height, + PyBytes_AS_STRING(buffer), + (BITMAPINFO *)&core, + DIB_RGB_COLORS)) { goto error; + } DeleteObject(bitmap); DeleteDC(screen_copy); @@ -397,7 +413,7 @@ PyImaging_GrabScreenWin32(PyObject* self, PyObject* args) return Py_BuildValue("(ii)(ii)N", x, y, width, height, buffer); error: - PyErr_SetString(PyExc_IOError, "screen grab failed"); + PyErr_SetString(PyExc_OSError, "screen grab failed"); DeleteDC(screen_copy); DeleteDC(screen); @@ -405,11 +421,11 @@ error: return NULL; } -static BOOL CALLBACK list_windows_callback(HWND hwnd, LPARAM lParam) -{ - PyObject* window_list = (PyObject*) lParam; - PyObject* item; - PyObject* title; +static BOOL CALLBACK +list_windows_callback(HWND hwnd, LPARAM lParam) { + PyObject *window_list = (PyObject *)lParam; + PyObject *item; + PyObject *title; RECT inner, outer; int title_size; int status; @@ -418,45 +434,57 @@ static BOOL CALLBACK list_windows_callback(HWND hwnd, LPARAM lParam) title_size = GetWindowTextLength(hwnd); if (title_size > 0) { title = PyUnicode_FromStringAndSize(NULL, title_size); - if (title) - GetWindowTextW(hwnd, PyUnicode_AS_UNICODE(title), title_size+1); - } else + if (title) { + GetWindowTextW(hwnd, PyUnicode_AS_UNICODE(title), title_size + 1); + } + } else { title = PyUnicode_FromString(""); - if (!title) + } + if (!title) { return 0; + } /* get bounding boxes */ GetClientRect(hwnd, &inner); GetWindowRect(hwnd, &outer); item = Py_BuildValue( - F_HANDLE "N(iiii)(iiii)", hwnd, title, - inner.left, inner.top, inner.right, inner.bottom, - outer.left, outer.top, outer.right, outer.bottom - ); - if (!item) + F_HANDLE "N(iiii)(iiii)", + hwnd, + title, + inner.left, + inner.top, + inner.right, + inner.bottom, + outer.left, + outer.top, + outer.right, + outer.bottom); + if (!item) { return 0; + } status = PyList_Append(window_list, item); Py_DECREF(item); - if (status < 0) + if (status < 0) { return 0; + } return 1; } -PyObject* -PyImaging_ListWindowsWin32(PyObject* self, PyObject* args) -{ - PyObject* window_list; +PyObject * +PyImaging_ListWindowsWin32(PyObject *self, PyObject *args) { + PyObject *window_list; window_list = PyList_New(0); - if (!window_list) + if (!window_list) { return NULL; + } - EnumWindows(list_windows_callback, (LPARAM) window_list); + EnumWindows(list_windows_callback, (LPARAM)window_list); if (PyErr_Occurred()) { Py_DECREF(window_list); @@ -469,37 +497,48 @@ PyImaging_ListWindowsWin32(PyObject* self, PyObject* args) /* -------------------------------------------------------------------- */ /* Windows clipboard grabber */ -PyObject* -PyImaging_GrabClipboardWin32(PyObject* self, PyObject* args) -{ +PyObject * +PyImaging_GrabClipboardWin32(PyObject *self, PyObject *args) { int clip; - HANDLE handle; + HANDLE handle = NULL; int size; - void* data; - PyObject* result; + void *data; + PyObject *result; + UINT format; + UINT formats[] = {CF_DIB, CF_DIBV5, CF_HDROP, RegisterClipboardFormatA("PNG"), 0}; + LPCSTR format_names[] = {"DIB", "DIB", "file", "png", NULL}; - clip = OpenClipboard(NULL); - /* FIXME: check error status */ - - handle = GetClipboardData(CF_DIB); - if (!handle) { - /* FIXME: add CF_HDROP support to allow cut-and-paste from - the explorer */ - CloseClipboard(); - Py_INCREF(Py_None); - return Py_None; + if (!OpenClipboard(NULL)) { + PyErr_SetString(PyExc_OSError, "failed to open clipboard"); + return NULL; + } + + // find best format as set by clipboard owner + format = 0; + while (!handle && (format = EnumClipboardFormats(format))) { + for (UINT i = 0; formats[i] != 0; i++) { + if (format == formats[i]) { + handle = GetClipboardData(format); + format = i; + break; + } + } + } + + if (!handle) { + CloseClipboard(); + return Py_BuildValue("zO", NULL, Py_None); } - size = GlobalSize(handle); data = GlobalLock(handle); + size = GlobalSize(handle); result = PyBytes_FromStringAndSize(data, size); GlobalUnlock(handle); - CloseClipboard(); - return result; + return Py_BuildValue("zN", format_names[format], result); } /* -------------------------------------------------------------------- */ @@ -512,15 +551,14 @@ PyImaging_GrabClipboardWin32(PyObject* self, PyObject* args) static int mainloop = 0; static void -callback_error(const char* handler) -{ - PyObject* sys_stderr; +callback_error(const char *handler) { + PyObject *sys_stderr; sys_stderr = PySys_GetObject("stderr"); if (sys_stderr) { PyFile_WriteString("*** ImageWin: error in ", sys_stderr); - PyFile_WriteString((char*) handler, sys_stderr); + PyFile_WriteString((char *)handler, sys_stderr); PyFile_WriteString(":\n", sys_stderr); } @@ -529,103 +567,119 @@ callback_error(const char* handler) } static LRESULT CALLBACK -windowCallback(HWND wnd, UINT message, WPARAM wParam, LPARAM lParam) -{ +windowCallback(HWND wnd, UINT message, WPARAM wParam, LPARAM lParam) { PAINTSTRUCT ps; - PyObject* callback = NULL; - PyObject* result; - PyThreadState* threadstate; - PyThreadState* current_threadstate; + PyObject *callback = NULL; + PyObject *result; + PyThreadState *threadstate; + PyThreadState *current_threadstate; HDC dc; RECT rect; LRESULT status = 0; /* set up threadstate for messages that calls back into python */ switch (message) { - case WM_CREATE: - mainloop++; - break; - case WM_DESTROY: - mainloop--; - /* fall through... */ - case WM_PAINT: - case WM_SIZE: - callback = (PyObject*) GetWindowLongPtr(wnd, 0); - if (callback) { - threadstate = (PyThreadState*) - GetWindowLongPtr(wnd, sizeof(PyObject*)); - current_threadstate = PyThreadState_Swap(NULL); - PyEval_RestoreThread(threadstate); - } else - return DefWindowProc(wnd, message, wParam, lParam); + case WM_CREATE: + mainloop++; + break; + case WM_DESTROY: + mainloop--; + /* fall through... */ + case WM_PAINT: + case WM_SIZE: + callback = (PyObject *)GetWindowLongPtr(wnd, 0); + if (callback) { + threadstate = + (PyThreadState *)GetWindowLongPtr(wnd, sizeof(PyObject *)); + current_threadstate = PyThreadState_Swap(NULL); + PyEval_RestoreThread(threadstate); + } else { + return DefWindowProc(wnd, message, wParam, lParam); + } } /* process message */ switch (message) { + case WM_PAINT: + /* redraw (part of) window. this generates a WCK-style + damage/clear/repair cascade */ + BeginPaint(wnd, &ps); + dc = GetDC(wnd); + GetWindowRect(wnd, &rect); /* in screen coordinates */ - case WM_PAINT: - /* redraw (part of) window. this generates a WCK-style - damage/clear/repair cascade */ - BeginPaint(wnd, &ps); - dc = GetDC(wnd); - GetWindowRect(wnd, &rect); /* in screen coordinates */ + result = PyObject_CallFunction( + callback, + "siiii", + "damage", + ps.rcPaint.left, + ps.rcPaint.top, + ps.rcPaint.right, + ps.rcPaint.bottom); + if (result) { + Py_DECREF(result); + } else { + callback_error("window damage callback"); + } - result = PyObject_CallFunction( - callback, "siiii", "damage", - ps.rcPaint.left, ps.rcPaint.top, - ps.rcPaint.right, ps.rcPaint.bottom - ); - if (result) - Py_DECREF(result); - else - callback_error("window damage callback"); + result = PyObject_CallFunction( + callback, + "s" F_HANDLE "iiii", + "clear", + dc, + 0, + 0, + rect.right - rect.left, + rect.bottom - rect.top); + if (result) { + Py_DECREF(result); + } else { + callback_error("window clear callback"); + } - result = PyObject_CallFunction( - callback, "s" F_HANDLE "iiii", "clear", dc, - 0, 0, rect.right-rect.left, rect.bottom-rect.top - ); - if (result) - Py_DECREF(result); - else - callback_error("window clear callback"); + result = PyObject_CallFunction( + callback, + "s" F_HANDLE "iiii", + "repair", + dc, + 0, + 0, + rect.right - rect.left, + rect.bottom - rect.top); + if (result) { + Py_DECREF(result); + } else { + callback_error("window repair callback"); + } - result = PyObject_CallFunction( - callback, "s" F_HANDLE "iiii", "repair", dc, - 0, 0, rect.right-rect.left, rect.bottom-rect.top - ); - if (result) - Py_DECREF(result); - else - callback_error("window repair callback"); + ReleaseDC(wnd, dc); + EndPaint(wnd, &ps); + break; - ReleaseDC(wnd, dc); - EndPaint(wnd, &ps); - break; + case WM_SIZE: + /* resize window */ + result = PyObject_CallFunction( + callback, "sii", "resize", LOWORD(lParam), HIWORD(lParam)); + if (result) { + InvalidateRect(wnd, NULL, 1); + Py_DECREF(result); + } else { + callback_error("window resize callback"); + } + break; - case WM_SIZE: - /* resize window */ - result = PyObject_CallFunction( - callback, "sii", "resize", LOWORD(lParam), HIWORD(lParam) - ); - if (result) { - InvalidateRect(wnd, NULL, 1); - Py_DECREF(result); - } else - callback_error("window resize callback"); - break; + case WM_DESTROY: + /* destroy window */ + result = PyObject_CallFunction(callback, "s", "destroy"); + if (result) { + Py_DECREF(result); + } else { + callback_error("window destroy callback"); + } + Py_DECREF(callback); + break; - case WM_DESTROY: - /* destroy window */ - result = PyObject_CallFunction(callback, "s", "destroy"); - if (result) - Py_DECREF(result); - else - callback_error("window destroy callback"); - Py_DECREF(callback); - break; - - default: - status = DefWindowProc(wnd, message, wParam, lParam); + default: + status = DefWindowProc(wnd, message, wParam, lParam); } if (callback) { @@ -637,27 +691,29 @@ windowCallback(HWND wnd, UINT message, WPARAM wParam, LPARAM lParam) return status; } -PyObject* -PyImaging_CreateWindowWin32(PyObject* self, PyObject* args) -{ +PyObject * +PyImaging_CreateWindowWin32(PyObject *self, PyObject *args) { HWND wnd; WNDCLASS windowClass; - char* title; - PyObject* callback; + char *title; + PyObject *callback; int width = 0, height = 0; - if (!PyArg_ParseTuple(args, "sO|ii", &title, &callback, &width, &height)) - return NULL; + if (!PyArg_ParseTuple(args, "sO|ii", &title, &callback, &width, &height)) { + return NULL; + } - if (width <= 0) + if (width <= 0) { width = CW_USEDEFAULT; - if (height <= 0) + } + if (height <= 0) { height = CW_USEDEFAULT; + } /* register toplevel window class */ windowClass.style = CS_CLASSDC; windowClass.cbClsExtra = 0; - windowClass.cbWndExtra = sizeof(PyObject*) + sizeof(PyThreadState*); + windowClass.cbWndExtra = sizeof(PyObject *) + sizeof(PyThreadState *); windowClass.hInstance = GetModuleHandle(NULL); /* windowClass.hbrBackground = (HBRUSH) (COLOR_BTNFACE + 1); */ windowClass.hbrBackground = NULL; @@ -670,92 +726,100 @@ PyImaging_CreateWindowWin32(PyObject* self, PyObject* args) RegisterClass(&windowClass); /* FIXME: check return status */ wnd = CreateWindowEx( - 0, windowClass.lpszClassName, title, + 0, + windowClass.lpszClassName, + title, WS_OVERLAPPEDWINDOW, - CW_USEDEFAULT, CW_USEDEFAULT, width, height, - HWND_DESKTOP, NULL, NULL, NULL - ); + CW_USEDEFAULT, + CW_USEDEFAULT, + width, + height, + HWND_DESKTOP, + NULL, + NULL, + NULL); if (!wnd) { - PyErr_SetString(PyExc_IOError, "failed to create window"); + PyErr_SetString(PyExc_OSError, "failed to create window"); return NULL; } /* register window callback */ Py_INCREF(callback); - SetWindowLongPtr(wnd, 0, (LONG_PTR) callback); - SetWindowLongPtr(wnd, sizeof(callback), (LONG_PTR) PyThreadState_Get()); + SetWindowLongPtr(wnd, 0, (LONG_PTR)callback); + SetWindowLongPtr(wnd, sizeof(callback), (LONG_PTR)PyThreadState_Get()); - Py_BEGIN_ALLOW_THREADS - ShowWindow(wnd, SW_SHOWNORMAL); + Py_BEGIN_ALLOW_THREADS ShowWindow(wnd, SW_SHOWNORMAL); SetForegroundWindow(wnd); /* to make sure it's visible */ Py_END_ALLOW_THREADS - return Py_BuildValue(F_HANDLE, wnd); + return Py_BuildValue(F_HANDLE, wnd); } -PyObject* -PyImaging_EventLoopWin32(PyObject* self, PyObject* args) -{ +PyObject * +PyImaging_EventLoopWin32(PyObject *self, PyObject *args) { MSG msg; - Py_BEGIN_ALLOW_THREADS - while (mainloop && GetMessage(&msg, NULL, 0, 0)) { + Py_BEGIN_ALLOW_THREADS while (mainloop && GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } Py_END_ALLOW_THREADS - Py_INCREF(Py_None); + Py_INCREF(Py_None); return Py_None; } /* -------------------------------------------------------------------- */ /* windows WMF renderer */ -#define GET32(p,o) ((DWORD*)(p+o))[0] +#define GET32(p, o) ((DWORD *)(p + o))[0] PyObject * -PyImaging_DrawWmf(PyObject* self, PyObject* args) -{ +PyImaging_DrawWmf(PyObject *self, PyObject *args) { HBITMAP bitmap; HENHMETAFILE meta; BITMAPCOREHEADER core; HDC dc; RECT rect; - PyObject* buffer = NULL; - char* ptr; + PyObject *buffer = NULL; + char *ptr; - char* data; - int datasize; + char *data; + Py_ssize_t datasize; int width, height; int x0, y0, x1, y1; - if (!PyArg_ParseTuple(args, "y#(ii)(iiii):_load", &data, &datasize, - &width, &height, &x0, &x1, &y0, &y1)) + if (!PyArg_ParseTuple( + args, + "y#(ii)(iiii):_load", + &data, + &datasize, + &width, + &height, + &x0, + &x1, + &y0, + &y1)) { return NULL; + } /* step 1: copy metafile contents into METAFILE object */ if (datasize > 22 && GET32(data, 0) == 0x9ac6cdd7) { - /* placeable windows metafile (22-byte aldus header) */ - meta = SetWinMetaFileBits(datasize-22, data+22, NULL, NULL); - - } else if (datasize > 80 && GET32(data, 0) == 1 && - GET32(data, 40) == 0x464d4520) { + meta = SetWinMetaFileBits(datasize - 22, data + 22, NULL, NULL); + } else if (datasize > 80 && GET32(data, 0) == 1 && GET32(data, 40) == 0x464d4520) { /* enhanced metafile */ meta = SetEnhMetaFileBits(datasize, data); } else { - /* unknown meta format */ meta = NULL; - } if (!meta) { - PyErr_SetString(PyExc_IOError, "cannot load metafile"); + PyErr_SetString(PyExc_OSError, "cannot load metafile"); return NULL; } @@ -769,17 +833,15 @@ PyImaging_DrawWmf(PyObject* self, PyObject* args) dc = CreateCompatibleDC(NULL); - bitmap = CreateDIBSection( - dc, (BITMAPINFO*) &core, DIB_RGB_COLORS, &ptr, NULL, 0 - ); + bitmap = CreateDIBSection(dc, (BITMAPINFO *)&core, DIB_RGB_COLORS, &ptr, NULL, 0); if (!bitmap) { - PyErr_SetString(PyExc_IOError, "cannot create bitmap"); + PyErr_SetString(PyExc_OSError, "cannot create bitmap"); goto error; } if (!SelectObject(dc, bitmap)) { - PyErr_SetString(PyExc_IOError, "cannot select bitmap"); + PyErr_SetString(PyExc_OSError, "cannot select bitmap"); goto error; } @@ -793,7 +855,7 @@ PyImaging_DrawWmf(PyObject* self, PyObject* args) FillRect(dc, &rect, GetStockObject(WHITE_BRUSH)); if (!PlayEnhMetaFile(dc, meta, &rect)) { - PyErr_SetString(PyExc_IOError, "cannot render metafile"); + PyErr_SetString(PyExc_OSError, "cannot render metafile"); goto error; } @@ -801,13 +863,14 @@ PyImaging_DrawWmf(PyObject* self, PyObject* args) GdiFlush(); - buffer = PyBytes_FromStringAndSize(ptr, height * ((width*3 + 3) & -4)); + buffer = PyBytes_FromStringAndSize(ptr, height * ((width * 3 + 3) & -4)); error: DeleteEnhMetaFile(meta); - if (bitmap) + if (bitmap) { DeleteObject(bitmap); + } DeleteDC(dc); @@ -815,3 +878,105 @@ error: } #endif /* _WIN32 */ + +/* -------------------------------------------------------------------- */ +/* X11 support */ + +#ifdef HAVE_XCB +#include + +/* -------------------------------------------------------------------- */ +/* X11 screen grabber */ + +PyObject * +PyImaging_GrabScreenX11(PyObject *self, PyObject *args) { + int width, height; + char *display_name; + xcb_connection_t *connection; + int screen_number; + xcb_screen_iterator_t iter; + xcb_screen_t *screen = NULL; + xcb_get_image_reply_t *reply; + xcb_generic_error_t *error; + PyObject *buffer = NULL; + + if (!PyArg_ParseTuple(args, "|z", &display_name)) { + return NULL; + } + + /* connect to X and get screen data */ + + connection = xcb_connect(display_name, &screen_number); + if (xcb_connection_has_error(connection)) { + PyErr_Format( + PyExc_OSError, + "X connection failed: error %i", + xcb_connection_has_error(connection)); + xcb_disconnect(connection); + return NULL; + } + + iter = xcb_setup_roots_iterator(xcb_get_setup(connection)); + for (; iter.rem; --screen_number, xcb_screen_next(&iter)) { + if (screen_number == 0) { + screen = iter.data; + break; + } + } + if (screen == NULL || screen->root == 0) { + // this case is usually caught with "X connection failed: error 6" above + xcb_disconnect(connection); + PyErr_SetString(PyExc_OSError, "X screen not found"); + return NULL; + } + + width = screen->width_in_pixels; + height = screen->height_in_pixels; + + /* get image data */ + + reply = xcb_get_image_reply( + connection, + xcb_get_image( + connection, + XCB_IMAGE_FORMAT_Z_PIXMAP, + screen->root, + 0, + 0, + width, + height, + 0x00ffffff), + &error); + if (reply == NULL) { + PyErr_Format( + PyExc_OSError, + "X get_image failed: error %i (%i, %i, %i)", + error->error_code, + error->major_code, + error->minor_code, + error->resource_id); + free(error); + xcb_disconnect(connection); + return NULL; + } + + /* store data in Python buffer */ + + if (reply->depth == 24) { + buffer = PyBytes_FromStringAndSize( + (char *)xcb_get_image_data(reply), xcb_get_image_data_length(reply)); + } else { + PyErr_Format(PyExc_OSError, "unsupported bit depth: %i", reply->depth); + } + + free(reply); + xcb_disconnect(connection); + + if (!buffer) { + return NULL; + } + + return Py_BuildValue("(ii)N", width, height, buffer); +} + +#endif /* HAVE_XCB */ diff --git a/src/encode.c b/src/encode.c index 41ba124c4..f92ba62c2 100644 --- a/src/encode.c +++ b/src/encode.c @@ -25,8 +25,8 @@ #define PY_SSIZE_T_CLEAN #include "Python.h" -#include "Imaging.h" -#include "Gif.h" +#include "libImaging/Imaging.h" +#include "libImaging/Gif.h" #ifdef HAVE_UNISTD_H #include /* write */ @@ -37,44 +37,45 @@ /* -------------------------------------------------------------------- */ typedef struct { - PyObject_HEAD - int (*encode)(Imaging im, ImagingCodecState state, - UINT8* buffer, int bytes); + PyObject_HEAD int (*encode)( + Imaging im, ImagingCodecState state, UINT8 *buffer, int bytes); int (*cleanup)(ImagingCodecState state); struct ImagingCodecStateInstance state; Imaging im; - PyObject* lock; + PyObject *lock; int pushes_fd; } ImagingEncoderObject; static PyTypeObject ImagingEncoderType; -static ImagingEncoderObject* -PyImaging_EncoderNew(int contextsize) -{ +static ImagingEncoderObject * +PyImaging_EncoderNew(int contextsize) { ImagingEncoderObject *encoder; void *context; - if(PyType_Ready(&ImagingEncoderType) < 0) + if (PyType_Ready(&ImagingEncoderType) < 0) { return NULL; + } encoder = PyObject_New(ImagingEncoderObject, &ImagingEncoderType); - if (encoder == NULL) + if (encoder == NULL) { return NULL; + } /* Clear the encoder state */ memset(&encoder->state, 0, sizeof(encoder->state)); /* Allocate encoder context */ if (contextsize > 0) { - context = (void*) calloc(1, contextsize); + context = (void *)calloc(1, contextsize); if (!context) { Py_DECREF(encoder); - (void) PyErr_NoMemory(); + (void)ImagingError_MemoryError(); return NULL; } - } else + } else { context = 0; + } /* Initialize encoder context */ encoder->state.context = context; @@ -91,10 +92,10 @@ PyImaging_EncoderNew(int contextsize) } static void -_dealloc(ImagingEncoderObject* encoder) -{ - if (encoder->cleanup) +_dealloc(ImagingEncoderObject *encoder) { + if (encoder->cleanup) { encoder->cleanup(&encoder->state); + } free(encoder->state.buffer); free(encoder->state.context); Py_XDECREF(encoder->lock); @@ -102,42 +103,43 @@ _dealloc(ImagingEncoderObject* encoder) PyObject_Del(encoder); } -static PyObject* -_encode_cleanup(ImagingEncoderObject* encoder, PyObject* args) -{ +static PyObject * +_encode_cleanup(ImagingEncoderObject *encoder, PyObject *args) { int status = 0; - if (encoder->cleanup){ + if (encoder->cleanup) { status = encoder->cleanup(&encoder->state); } return Py_BuildValue("i", status); } -static PyObject* -_encode(ImagingEncoderObject* encoder, PyObject* args) -{ - PyObject* buf; - PyObject* result; +static PyObject * +_encode(ImagingEncoderObject *encoder, PyObject *args) { + PyObject *buf; + PyObject *result; int status; /* Encode to a Python string (allocated by this method) */ Py_ssize_t bufsize = 16384; - if (!PyArg_ParseTuple(args, "|n", &bufsize)) + if (!PyArg_ParseTuple(args, "|n", &bufsize)) { return NULL; + } buf = PyBytes_FromStringAndSize(NULL, bufsize); - if (!buf) + if (!buf) { return NULL; + } - status = encoder->encode(encoder->im, &encoder->state, - (UINT8*) PyBytes_AsString(buf), bufsize); + status = encoder->encode( + encoder->im, &encoder->state, (UINT8 *)PyBytes_AsString(buf), bufsize); /* adjust string length to avoid slicing in encoder */ - if (_PyBytes_Resize(&buf, (status > 0) ? status : 0) < 0) + if (_PyBytes_Resize(&buf, (status > 0) ? status : 0) < 0) { return NULL; + } result = Py_BuildValue("iiO", status, encoder->state.errcode, buf); @@ -146,31 +148,28 @@ _encode(ImagingEncoderObject* encoder, PyObject* args) return result; } -static PyObject* -_encode_to_pyfd(ImagingEncoderObject* encoder, PyObject* args) -{ - +static PyObject * +_encode_to_pyfd(ImagingEncoderObject *encoder, PyObject *args) { PyObject *result; int status; if (!encoder->pushes_fd) { // UNDONE, appropriate errcode??? - result = Py_BuildValue("ii", 0, IMAGING_CODEC_CONFIG);; + result = Py_BuildValue("ii", 0, IMAGING_CODEC_CONFIG); + ; return result; } - status = encoder->encode(encoder->im, &encoder->state, - (UINT8*) NULL, 0); + status = encoder->encode(encoder->im, &encoder->state, (UINT8 *)NULL, 0); result = Py_BuildValue("ii", status, encoder->state.errcode); return result; } -static PyObject* -_encode_to_file(ImagingEncoderObject* encoder, PyObject* args) -{ - UINT8* buf; +static PyObject * +_encode_to_file(ImagingEncoderObject *encoder, PyObject *args) { + UINT8 *buf; int status; ImagingSectionCookie cookie; @@ -179,30 +178,32 @@ _encode_to_file(ImagingEncoderObject* encoder, PyObject* args) Py_ssize_t fh; Py_ssize_t bufsize = 16384; - if (!PyArg_ParseTuple(args, "n|n", &fh, &bufsize)) + if (!PyArg_ParseTuple(args, "n|n", &fh, &bufsize)) { return NULL; + } /* Allocate an encoder buffer */ /* malloc check ok, either constant int, or checked by PyArg_ParseTuple */ - buf = (UINT8*) malloc(bufsize); - if (!buf) - return PyErr_NoMemory(); + buf = (UINT8 *)malloc(bufsize); + if (!buf) { + return ImagingError_MemoryError(); + } ImagingSectionEnter(&cookie); do { - /* This replaces the inner loop in the ImageFile _save function. */ status = encoder->encode(encoder->im, &encoder->state, buf, bufsize); - if (status > 0) + if (status > 0) { if (write(fh, buf, status) < 0) { ImagingSectionLeave(&cookie); free(buf); - return PyErr_SetFromErrno(PyExc_IOError); + return PyErr_SetFromErrno(PyExc_OSError); } + } } while (encoder->state.errcode == 0); @@ -213,12 +214,12 @@ _encode_to_file(ImagingEncoderObject* encoder, PyObject* args) return Py_BuildValue("i", encoder->state.errcode); } -extern Imaging PyImaging_AsImaging(PyObject *op); +extern Imaging +PyImaging_AsImaging(PyObject *op); -static PyObject* -_setimage(ImagingEncoderObject* encoder, PyObject* args) -{ - PyObject* op; +static PyObject * +_setimage(ImagingEncoderObject *encoder, PyObject *args) { + PyObject *op; Imaging im; ImagingCodecState state; Py_ssize_t x0, y0, x1, y1; @@ -228,11 +229,13 @@ _setimage(ImagingEncoderObject* encoder, PyObject* args) x0 = y0 = x1 = y1 = 0; /* FIXME: should publish the ImagingType descriptor */ - if (!PyArg_ParseTuple(args, "O|(nnnn)", &op, &x0, &y0, &x1, &y1)) + if (!PyArg_ParseTuple(args, "O|(nnnn)", &op, &x0, &y0, &x1, &y1)) { return NULL; + } im = PyImaging_AsImaging(op); - if (!im) + if (!im) { return NULL; + } encoder->im = im; @@ -248,24 +251,23 @@ _setimage(ImagingEncoderObject* encoder, PyObject* args) state->ysize = y1 - y0; } - if (state->xsize <= 0 || - state->xsize + state->xoff > im->xsize || - state->ysize <= 0 || - state->ysize + state->yoff > im->ysize) { + if (state->xsize <= 0 || state->xsize + state->xoff > im->xsize || + state->ysize <= 0 || state->ysize + state->yoff > im->ysize) { PyErr_SetString(PyExc_SystemError, "tile cannot extend outside image"); return NULL; } /* Allocate memory buffer (if bits field is set) */ if (state->bits > 0) { - if (state->xsize > ((INT_MAX / state->bits)-7)) { - return PyErr_NoMemory(); + if (state->xsize > ((INT_MAX / state->bits) - 7)) { + return ImagingError_MemoryError(); } - state->bytes = (state->bits * state->xsize+7)/8; + state->bytes = (state->bits * state->xsize + 7) / 8; /* malloc check ok, overflow checked above */ - state->buffer = (UINT8*) malloc(state->bytes); - if (!state->buffer) - return PyErr_NoMemory(); + state->buffer = (UINT8 *)malloc(state->bytes); + if (!state->buffer) { + return ImagingError_MemoryError(); + } } /* Keep a reference to the image object, to make sure it doesn't @@ -278,14 +280,14 @@ _setimage(ImagingEncoderObject* encoder, PyObject* args) return Py_None; } -static PyObject* -_setfd(ImagingEncoderObject* encoder, PyObject* args) -{ - PyObject* fd; +static PyObject * +_setfd(ImagingEncoderObject *encoder, PyObject *args) { + PyObject *fd; ImagingCodecState state; - if (!PyArg_ParseTuple(args, "O", &fd)) + if (!PyArg_ParseTuple(args, "O", &fd)) { return NULL; + } state = &encoder->state; @@ -297,8 +299,7 @@ _setfd(ImagingEncoderObject* encoder, PyObject* args) } static PyObject * -_get_pushes_fd(ImagingEncoderObject *encoder) -{ +_get_pushes_fd(ImagingEncoderObject *encoder) { return PyBool_FromLong(encoder->pushes_fd); } @@ -313,52 +314,51 @@ static struct PyMethodDef methods[] = { }; static struct PyGetSetDef getseters[] = { - {"pushes_fd", (getter)_get_pushes_fd, NULL, + {"pushes_fd", + (getter)_get_pushes_fd, + NULL, "True if this decoder expects to push directly to self.fd", NULL}, {NULL, NULL, NULL, NULL, NULL} /* sentinel */ }; static PyTypeObject ImagingEncoderType = { - PyVarObject_HEAD_INIT(NULL, 0) - "ImagingEncoder", /*tp_name*/ - sizeof(ImagingEncoderObject), /*tp_size*/ - 0, /*tp_itemsize*/ + PyVarObject_HEAD_INIT(NULL, 0) "ImagingEncoder", /*tp_name*/ + sizeof(ImagingEncoderObject), /*tp_size*/ + 0, /*tp_itemsize*/ /* methods */ - (destructor)_dealloc, /*tp_dealloc*/ - 0, /*tp_print*/ - 0, /*tp_getattr*/ - 0, /*tp_setattr*/ - 0, /*tp_compare*/ - 0, /*tp_repr*/ - 0, /*tp_as_number */ - 0, /*tp_as_sequence */ - 0, /*tp_as_mapping */ - 0, /*tp_hash*/ - 0, /*tp_call*/ - 0, /*tp_str*/ - 0, /*tp_getattro*/ - 0, /*tp_setattro*/ - 0, /*tp_as_buffer*/ - Py_TPFLAGS_DEFAULT, /*tp_flags*/ - 0, /*tp_doc*/ - 0, /*tp_traverse*/ - 0, /*tp_clear*/ - 0, /*tp_richcompare*/ - 0, /*tp_weaklistoffset*/ - 0, /*tp_iter*/ - 0, /*tp_iternext*/ - methods, /*tp_methods*/ - 0, /*tp_members*/ - getseters, /*tp_getset*/ + (destructor)_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number */ + 0, /*tp_as_sequence */ + 0, /*tp_as_mapping */ + 0, /*tp_hash*/ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT, /*tp_flags*/ + 0, /*tp_doc*/ + 0, /*tp_traverse*/ + 0, /*tp_clear*/ + 0, /*tp_richcompare*/ + 0, /*tp_weaklistoffset*/ + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + methods, /*tp_methods*/ + 0, /*tp_members*/ + getseters, /*tp_getset*/ }; /* -------------------------------------------------------------------- */ int -get_packer(ImagingEncoderObject* encoder, const char* mode, - const char* rawmode) -{ +get_packer(ImagingEncoderObject *encoder, const char *mode, const char *rawmode) { int bits; ImagingShuffler pack; @@ -375,66 +375,64 @@ get_packer(ImagingEncoderObject* encoder, const char* mode, return 0; } - /* -------------------------------------------------------------------- */ /* EPS */ /* -------------------------------------------------------------------- */ -PyObject* -PyImaging_EpsEncoderNew(PyObject* self, PyObject* args) -{ - ImagingEncoderObject* encoder; +PyObject * +PyImaging_EpsEncoderNew(PyObject *self, PyObject *args) { + ImagingEncoderObject *encoder; encoder = PyImaging_EncoderNew(0); - if (encoder == NULL) + if (encoder == NULL) { return NULL; + } encoder->encode = ImagingEpsEncode; - return (PyObject*) encoder; + return (PyObject *)encoder; } - /* -------------------------------------------------------------------- */ /* GIF */ /* -------------------------------------------------------------------- */ -PyObject* -PyImaging_GifEncoderNew(PyObject* self, PyObject* args) -{ - ImagingEncoderObject* encoder; +PyObject * +PyImaging_GifEncoderNew(PyObject *self, PyObject *args) { + ImagingEncoderObject *encoder; char *mode; char *rawmode; Py_ssize_t bits = 8; Py_ssize_t interlace = 0; - if (!PyArg_ParseTuple(args, "ss|nn", &mode, &rawmode, &bits, &interlace)) + if (!PyArg_ParseTuple(args, "ss|nn", &mode, &rawmode, &bits, &interlace)) { return NULL; + } encoder = PyImaging_EncoderNew(sizeof(GIFENCODERSTATE)); - if (encoder == NULL) + if (encoder == NULL) { return NULL; + } - if (get_packer(encoder, mode, rawmode) < 0) + if (get_packer(encoder, mode, rawmode) < 0) { return NULL; + } encoder->encode = ImagingGifEncode; - ((GIFENCODERSTATE*)encoder->state.context)->bits = bits; - ((GIFENCODERSTATE*)encoder->state.context)->interlace = interlace; + ((GIFENCODERSTATE *)encoder->state.context)->bits = bits; + ((GIFENCODERSTATE *)encoder->state.context)->interlace = interlace; - return (PyObject*) encoder; + return (PyObject *)encoder; } - /* -------------------------------------------------------------------- */ /* PCX */ /* -------------------------------------------------------------------- */ -PyObject* -PyImaging_PcxEncoderNew(PyObject* self, PyObject* args) -{ - ImagingEncoderObject* encoder; +PyObject * +PyImaging_PcxEncoderNew(PyObject *self, PyObject *args) { + ImagingEncoderObject *encoder; char *mode; char *rawmode; @@ -455,133 +453,141 @@ PyImaging_PcxEncoderNew(PyObject* self, PyObject* args) encoder->encode = ImagingPcxEncode; - return (PyObject*) encoder; + return (PyObject *)encoder; } - /* -------------------------------------------------------------------- */ /* RAW */ /* -------------------------------------------------------------------- */ -PyObject* -PyImaging_RawEncoderNew(PyObject* self, PyObject* args) -{ - ImagingEncoderObject* encoder; +PyObject * +PyImaging_RawEncoderNew(PyObject *self, PyObject *args) { + ImagingEncoderObject *encoder; char *mode; char *rawmode; Py_ssize_t stride = 0; Py_ssize_t ystep = 1; - if (!PyArg_ParseTuple(args, "ss|nn", &mode, &rawmode, &stride, &ystep)) + if (!PyArg_ParseTuple(args, "ss|nn", &mode, &rawmode, &stride, &ystep)) { return NULL; + } encoder = PyImaging_EncoderNew(0); - if (encoder == NULL) + if (encoder == NULL) { return NULL; + } - if (get_packer(encoder, mode, rawmode) < 0) + if (get_packer(encoder, mode, rawmode) < 0) { return NULL; + } encoder->encode = ImagingRawEncode; encoder->state.ystep = ystep; encoder->state.count = stride; - return (PyObject*) encoder; + return (PyObject *)encoder; } - /* -------------------------------------------------------------------- */ /* TGA */ /* -------------------------------------------------------------------- */ -PyObject* -PyImaging_TgaRleEncoderNew(PyObject* self, PyObject* args) -{ - ImagingEncoderObject* encoder; +PyObject * +PyImaging_TgaRleEncoderNew(PyObject *self, PyObject *args) { + ImagingEncoderObject *encoder; char *mode; char *rawmode; Py_ssize_t ystep = 1; - if (!PyArg_ParseTuple(args, "ss|n", &mode, &rawmode, &ystep)) + if (!PyArg_ParseTuple(args, "ss|n", &mode, &rawmode, &ystep)) { return NULL; + } encoder = PyImaging_EncoderNew(0); - if (encoder == NULL) + if (encoder == NULL) { return NULL; + } - if (get_packer(encoder, mode, rawmode) < 0) + if (get_packer(encoder, mode, rawmode) < 0) { return NULL; + } encoder->encode = ImagingTgaRleEncode; encoder->state.ystep = ystep; - return (PyObject*) encoder; + return (PyObject *)encoder; } - - /* -------------------------------------------------------------------- */ /* XBM */ /* -------------------------------------------------------------------- */ -PyObject* -PyImaging_XbmEncoderNew(PyObject* self, PyObject* args) -{ - ImagingEncoderObject* encoder; +PyObject * +PyImaging_XbmEncoderNew(PyObject *self, PyObject *args) { + ImagingEncoderObject *encoder; encoder = PyImaging_EncoderNew(0); - if (encoder == NULL) + if (encoder == NULL) { return NULL; + } - if (get_packer(encoder, "1", "1;R") < 0) + if (get_packer(encoder, "1", "1;R") < 0) { return NULL; + } encoder->encode = ImagingXbmEncode; - return (PyObject*) encoder; + return (PyObject *)encoder; } - /* -------------------------------------------------------------------- */ /* ZIP */ /* -------------------------------------------------------------------- */ #ifdef HAVE_LIBZ -#include "Zip.h" +#include "libImaging/ZipCodecs.h" -PyObject* -PyImaging_ZipEncoderNew(PyObject* self, PyObject* args) -{ - ImagingEncoderObject* encoder; +PyObject * +PyImaging_ZipEncoderNew(PyObject *self, PyObject *args) { + ImagingEncoderObject *encoder; - char* mode; - char* rawmode; + char *mode; + char *rawmode; Py_ssize_t optimize = 0; Py_ssize_t compress_level = -1; Py_ssize_t compress_type = -1; - char* dictionary = NULL; + char *dictionary = NULL; Py_ssize_t dictionary_size = 0; - if (!PyArg_ParseTuple(args, "ss|nnny#", &mode, &rawmode, - &optimize, - &compress_level, &compress_type, - &dictionary, &dictionary_size)) + if (!PyArg_ParseTuple( + args, + "ss|nnny#", + &mode, + &rawmode, + &optimize, + &compress_level, + &compress_type, + &dictionary, + &dictionary_size)) { return NULL; + } /* Copy to avoid referencing Python's memory */ if (dictionary && dictionary_size > 0) { /* malloc check ok, size comes from PyArg_ParseTuple */ - char* p = malloc(dictionary_size); - if (!p) - return PyErr_NoMemory(); + char *p = malloc(dictionary_size); + if (!p) { + return ImagingError_MemoryError(); + } memcpy(p, dictionary, dictionary_size); dictionary = p; - } else + } else { dictionary = NULL; + } encoder = PyImaging_EncoderNew(sizeof(ZIPSTATE)); if (encoder == NULL) { @@ -597,40 +603,39 @@ PyImaging_ZipEncoderNew(PyObject* self, PyObject* args) encoder->encode = ImagingZipEncode; encoder->cleanup = ImagingZipEncodeCleanup; - if (rawmode[0] == 'P') + if (rawmode[0] == 'P') { /* disable filtering */ - ((ZIPSTATE*)encoder->state.context)->mode = ZIP_PNG_PALETTE; + ((ZIPSTATE *)encoder->state.context)->mode = ZIP_PNG_PALETTE; + } - ((ZIPSTATE*)encoder->state.context)->optimize = optimize; - ((ZIPSTATE*)encoder->state.context)->compress_level = compress_level; - ((ZIPSTATE*)encoder->state.context)->compress_type = compress_type; - ((ZIPSTATE*)encoder->state.context)->dictionary = dictionary; - ((ZIPSTATE*)encoder->state.context)->dictionary_size = dictionary_size; + ((ZIPSTATE *)encoder->state.context)->optimize = optimize; + ((ZIPSTATE *)encoder->state.context)->compress_level = compress_level; + ((ZIPSTATE *)encoder->state.context)->compress_type = compress_type; + ((ZIPSTATE *)encoder->state.context)->dictionary = dictionary; + ((ZIPSTATE *)encoder->state.context)->dictionary_size = dictionary_size; - return (PyObject*) encoder; + return (PyObject *)encoder; } #endif - /* -------------------------------------------------------------------- */ /* LibTiff */ /* -------------------------------------------------------------------- */ #ifdef HAVE_LIBTIFF -#include "TiffDecode.h" +#include "libImaging/TiffDecode.h" #include -PyObject* -PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args) -{ - ImagingEncoderObject* encoder; +PyObject * +PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) { + ImagingEncoderObject *encoder; - char* mode; - char* rawmode; - char* compname; - char* filename; + char *mode; + char *rawmode; + char *compname; + char *filename; Py_ssize_t fp; PyObject *tags, *types; @@ -639,16 +644,24 @@ PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args) int key_int, status, is_core_tag, is_var_length, num_core_tags, i; TIFFDataType type = TIFF_NOTYPE; // This list also exists in TiffTags.py - const int core_tags[] = { - 256, 257, 258, 259, 262, 263, 266, 269, 274, 277, 278, 280, 281, 340, - 341, 282, 283, 284, 286, 287, 296, 297, 321, 338, 32995, 32998, 32996, - 339, 32997, 330, 531, 530, 65537 - }; + const int core_tags[] = {256, 257, 258, 259, 262, 263, 266, 269, 274, + 277, 278, 280, 281, 340, 341, 282, 283, 284, + 286, 287, 296, 297, 320, 321, 338, 32995, 32998, + 32996, 339, 32997, 330, 531, 530, 65537}; Py_ssize_t tags_size; PyObject *item; - if (! PyArg_ParseTuple(args, "sssnsOO", &mode, &rawmode, &compname, &fp, &filename, &tags, &types)) { + if (!PyArg_ParseTuple( + args, + "sssnsOO", + &mode, + &rawmode, + &compname, + &fp, + &filename, + &tags, + &types)) { return NULL; } @@ -658,11 +671,11 @@ PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args) } else { tags_size = PyList_Size(tags); TRACE(("tags size: %d\n", (int)tags_size)); - for (pos=0;posstate, filename, fp)) { + if (!ImagingLibTiffEncodeInit(&encoder->state, filename, fp)) { Py_DECREF(encoder); PyErr_SetString(PyExc_RuntimeError, "tiff codec initialization failed"); return NULL; @@ -699,7 +714,7 @@ PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args) is_var_length = 0; type = TIFF_NOTYPE; - for (i=0; istate, type, key_int, is_var_length)) { + if (type == TIFF_BYTE) { + is_var_length = 1; + } + if (ImagingLibTiffMergeFieldInfo( + &encoder->state, type, key_int, is_var_length)) { continue; } } - if (is_var_length) { - Py_ssize_t len,i; + if (type == TIFF_BYTE || type == TIFF_UNDEFINED) { + status = ImagingLibTiffSetField( + &encoder->state, + (ttag_t)key_int, + PyBytes_Size(value), + PyBytes_AsString(value)); + } else if (is_var_length) { + Py_ssize_t len, i; TRACE(("Setting from Tuple: %d \n", key_int)); len = PyTuple_Size(value); - if (type == TIFF_BYTE) { - UINT8 *av; + if (key_int == TIFFTAG_COLORMAP) { + int stride = 256; + if (len != 768) { + PyErr_SetString( + PyExc_ValueError, "Requiring 768 items for for Colormap"); + return NULL; + } + UINT16 *av; /* malloc check ok, calloc checks for overflow */ - av = calloc(len, sizeof(UINT8)); + av = calloc(len, sizeof(UINT16)); if (av) { - for (i=0;istate, (ttag_t) key_int, len, av); + status = ImagingLibTiffSetField( + &encoder->state, + (ttag_t)key_int, + av, + av + stride, + av + stride * 2); free(av); } } else if (type == TIFF_SHORT) { @@ -784,10 +813,11 @@ PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args) /* malloc check ok, calloc checks for overflow */ av = calloc(len, sizeof(UINT16)); if (av) { - for (i=0;istate, (ttag_t) key_int, len, av); + status = ImagingLibTiffSetField( + &encoder->state, (ttag_t)key_int, len, av); free(av); } } else if (type == TIFF_LONG) { @@ -795,10 +825,11 @@ PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args) /* malloc check ok, calloc checks for overflow */ av = calloc(len, sizeof(UINT32)); if (av) { - for (i=0;istate, (ttag_t) key_int, len, av); + status = ImagingLibTiffSetField( + &encoder->state, (ttag_t)key_int, len, av); free(av); } } else if (type == TIFF_SBYTE) { @@ -806,10 +837,11 @@ PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args) /* malloc check ok, calloc checks for overflow */ av = calloc(len, sizeof(INT8)); if (av) { - for (i=0;istate, (ttag_t) key_int, len, av); + status = ImagingLibTiffSetField( + &encoder->state, (ttag_t)key_int, len, av); free(av); } } else if (type == TIFF_SSHORT) { @@ -817,10 +849,11 @@ PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args) /* malloc check ok, calloc checks for overflow */ av = calloc(len, sizeof(INT16)); if (av) { - for (i=0;istate, (ttag_t) key_int, len, av); + status = ImagingLibTiffSetField( + &encoder->state, (ttag_t)key_int, len, av); free(av); } } else if (type == TIFF_SLONG) { @@ -828,10 +861,11 @@ PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args) /* malloc check ok, calloc checks for overflow */ av = calloc(len, sizeof(INT32)); if (av) { - for (i=0;istate, (ttag_t) key_int, len, av); + status = ImagingLibTiffSetField( + &encoder->state, (ttag_t)key_int, len, av); free(av); } } else if (type == TIFF_FLOAT) { @@ -839,10 +873,11 @@ PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args) /* malloc check ok, calloc checks for overflow */ av = calloc(len, sizeof(FLOAT32)); if (av) { - for (i=0;istate, (ttag_t) key_int, len, av); + status = ImagingLibTiffSetField( + &encoder->state, (ttag_t)key_int, len, av); free(av); } } else if (type == TIFF_DOUBLE) { @@ -850,58 +885,47 @@ PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args) /* malloc check ok, calloc checks for overflow */ av = calloc(len, sizeof(FLOAT64)); if (av) { - for (i=0;istate, (ttag_t) key_int, len, av); + status = ImagingLibTiffSetField( + &encoder->state, (ttag_t)key_int, len, av); free(av); } } } else { if (type == TIFF_SHORT) { - status = ImagingLibTiffSetField(&encoder->state, - (ttag_t) key_int, - (UINT16)PyLong_AsLong(value)); + status = ImagingLibTiffSetField( + &encoder->state, (ttag_t)key_int, (UINT16)PyLong_AsLong(value)); } else if (type == TIFF_LONG) { - status = ImagingLibTiffSetField(&encoder->state, - (ttag_t) key_int, - (UINT32)PyLong_AsLong(value)); + status = ImagingLibTiffSetField( + &encoder->state, (ttag_t)key_int, (UINT32)PyLong_AsLong(value)); } else if (type == TIFF_SSHORT) { - status = ImagingLibTiffSetField(&encoder->state, - (ttag_t) key_int, - (INT16)PyLong_AsLong(value)); + status = ImagingLibTiffSetField( + &encoder->state, (ttag_t)key_int, (INT16)PyLong_AsLong(value)); } else if (type == TIFF_SLONG) { - status = ImagingLibTiffSetField(&encoder->state, - (ttag_t) key_int, - (INT32)PyLong_AsLong(value)); + status = ImagingLibTiffSetField( + &encoder->state, (ttag_t)key_int, (INT32)PyLong_AsLong(value)); } else if (type == TIFF_FLOAT) { - status = ImagingLibTiffSetField(&encoder->state, - (ttag_t) key_int, - (FLOAT32)PyFloat_AsDouble(value)); + status = ImagingLibTiffSetField( + &encoder->state, (ttag_t)key_int, (FLOAT32)PyFloat_AsDouble(value)); } else if (type == TIFF_DOUBLE) { - status = ImagingLibTiffSetField(&encoder->state, - (ttag_t) key_int, - (FLOAT64)PyFloat_AsDouble(value)); - } else if (type == TIFF_BYTE) { - status = ImagingLibTiffSetField(&encoder->state, - (ttag_t) key_int, - (UINT8)PyLong_AsLong(value)); + status = ImagingLibTiffSetField( + &encoder->state, (ttag_t)key_int, (FLOAT64)PyFloat_AsDouble(value)); } else if (type == TIFF_SBYTE) { - status = ImagingLibTiffSetField(&encoder->state, - (ttag_t) key_int, - (INT8)PyLong_AsLong(value)); + status = ImagingLibTiffSetField( + &encoder->state, (ttag_t)key_int, (INT8)PyLong_AsLong(value)); } else if (type == TIFF_ASCII) { - status = ImagingLibTiffSetField(&encoder->state, - (ttag_t) key_int, - PyBytes_AsString(value)); + status = ImagingLibTiffSetField( + &encoder->state, (ttag_t)key_int, PyBytes_AsString(value)); } else if (type == TIFF_RATIONAL) { - status = ImagingLibTiffSetField(&encoder->state, - (ttag_t) key_int, - (FLOAT64)PyFloat_AsDouble(value)); + status = ImagingLibTiffSetField( + &encoder->state, (ttag_t)key_int, (FLOAT64)PyFloat_AsDouble(value)); } else { - TRACE(("Unhandled type for key %d : %s \n", - key_int, - PyBytes_AsString(PyObject_Str(value)))); + TRACE( + ("Unhandled type for key %d : %s \n", + key_int, + PyBytes_AsString(PyObject_Str(value)))); } } if (!status) { @@ -912,9 +936,9 @@ PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args) } } - encoder->encode = ImagingLibTiffEncode; + encoder->encode = ImagingLibTiffEncode; - return (PyObject*) encoder; + return (PyObject *)encoder; } #endif @@ -928,26 +952,27 @@ PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args) /* We better define this encoder last in this file, so the following undef's won't mess things up for the Imaging library proper. */ -#undef HAVE_PROTOTYPES -#undef HAVE_STDDEF_H -#undef HAVE_STDLIB_H -#undef UINT8 -#undef UINT16 -#undef UINT32 -#undef INT8 -#undef INT16 -#undef INT32 +#undef HAVE_PROTOTYPES +#undef HAVE_STDDEF_H +#undef HAVE_STDLIB_H +#undef UINT8 +#undef UINT16 +#undef UINT32 +#undef INT8 +#undef INT16 +#undef INT32 -#include "Jpeg.h" +#include "libImaging/Jpeg.h" -static unsigned int* get_qtables_arrays(PyObject* qtables, int* qtablesLen) { - PyObject* tables; - PyObject* table; - PyObject* table_data; +static unsigned int * +get_qtables_arrays(PyObject *qtables, int *qtablesLen) { + PyObject *tables; + PyObject *table; + PyObject *table_data; int i, j, num_tables; unsigned int *qarrays; - if ((qtables == NULL) || (qtables == Py_None)) { + if ((qtables == NULL) || (qtables == Py_None)) { return NULL; } @@ -959,17 +984,17 @@ static unsigned int* get_qtables_arrays(PyObject* qtables, int* qtablesLen) { tables = PySequence_Fast(qtables, "expected a sequence"); num_tables = PySequence_Size(qtables); if (num_tables < 1 || num_tables > NUM_QUANT_TBLS) { - PyErr_SetString(PyExc_ValueError, + PyErr_SetString( + PyExc_ValueError, "Not a valid number of quantization tables. Should be between 1 and 4."); Py_DECREF(tables); return NULL; } /* malloc check ok, num_tables <4, DCTSIZE2 == 64 from jpeglib.h */ - qarrays = (unsigned int*) malloc(num_tables * DCTSIZE2 * sizeof(unsigned int)); + qarrays = (unsigned int *)malloc(num_tables * DCTSIZE2 * sizeof(unsigned int)); if (!qarrays) { Py_DECREF(tables); - PyErr_NoMemory(); - return NULL; + return ImagingError_MemoryError(); } for (i = 0; i < num_tables; i++) { table = PySequence_Fast_GET_ITEM(tables, i); @@ -983,7 +1008,8 @@ static unsigned int* get_qtables_arrays(PyObject* qtables, int* qtablesLen) { } table_data = PySequence_Fast(table, "expected a sequence"); for (j = 0; j < DCTSIZE2; j++) { - qarrays[i * DCTSIZE2 + j] = PyLong_AS_LONG(PySequence_Fast_GET_ITEM(table_data, j)); + qarrays[i * DCTSIZE2 + j] = + PyLong_AS_LONG(PySequence_Fast_GET_ITEM(table_data, j)); } Py_DECREF(table_data); } @@ -1001,10 +1027,9 @@ JPEG_QTABLES_ERR: return qarrays; } -PyObject* -PyImaging_JpegEncoderNew(PyObject* self, PyObject* args) -{ - ImagingEncoderObject* encoder; +PyObject * +PyImaging_JpegEncoderNew(PyObject *self, PyObject *args) { + ImagingEncoderObject *encoder; char *mode; char *rawmode; @@ -1015,24 +1040,39 @@ PyImaging_JpegEncoderNew(PyObject* self, PyObject* args) Py_ssize_t streamtype = 0; /* 0=interchange, 1=tables only, 2=image only */ Py_ssize_t xdpi = 0, ydpi = 0; Py_ssize_t subsampling = -1; /* -1=default, 0=none, 1=medium, 2=high */ - PyObject* qtables=NULL; + PyObject *qtables = NULL; unsigned int *qarrays = NULL; int qtablesLen = 0; - char* extra = NULL; + char *extra = NULL; Py_ssize_t extra_size; - char* rawExif = NULL; + char *rawExif = NULL; Py_ssize_t rawExifLen = 0; - if (!PyArg_ParseTuple(args, "ss|nnnnnnnnOy#y#", - &mode, &rawmode, &quality, - &progressive, &smooth, &optimize, &streamtype, - &xdpi, &ydpi, &subsampling, &qtables, &extra, &extra_size, - &rawExif, &rawExifLen)) + if (!PyArg_ParseTuple( + args, + "ss|nnnnnnnnOy#y#", + &mode, + &rawmode, + &quality, + &progressive, + &smooth, + &optimize, + &streamtype, + &xdpi, + &ydpi, + &subsampling, + &qtables, + &extra, + &extra_size, + &rawExif, + &rawExifLen)) { return NULL; + } encoder = PyImaging_EncoderNew(sizeof(JPEGENCODERSTATE)); - if (encoder == NULL) + if (encoder == NULL) { return NULL; + } // libjpeg-turbo supports different output formats. // We are choosing Pillow's native format (3 color bytes + 1 padding) @@ -1041,86 +1081,91 @@ PyImaging_JpegEncoderNew(PyObject* self, PyObject* args) rawmode = "RGBX"; } - if (get_packer(encoder, mode, rawmode) < 0) + if (get_packer(encoder, mode, rawmode) < 0) { return NULL; + } // Freed in JpegEncode, Case 5 qarrays = get_qtables_arrays(qtables, &qtablesLen); if (extra && extra_size > 0) { /* malloc check ok, length is from python parsearg */ - char* p = malloc(extra_size); // Freed in JpegEncode, Case 5 - if (!p) - return PyErr_NoMemory(); + char *p = malloc(extra_size); // Freed in JpegEncode, Case 5 + if (!p) { + return ImagingError_MemoryError(); + } memcpy(p, extra, extra_size); extra = p; - } else + } else { extra = NULL; + } if (rawExif && rawExifLen > 0) { /* malloc check ok, length is from python parsearg */ - char* pp = malloc(rawExifLen); // Freed in JpegEncode, Case 5 + char *pp = malloc(rawExifLen); // Freed in JpegEncode, Case 5 if (!pp) { - if (extra) free(extra); - return PyErr_NoMemory(); + if (extra) { + free(extra); + } + return ImagingError_MemoryError(); } memcpy(pp, rawExif, rawExifLen); rawExif = pp; - } else + } else { rawExif = NULL; + } encoder->encode = ImagingJpegEncode; - strncpy(((JPEGENCODERSTATE*)encoder->state.context)->rawmode, rawmode, 8); + strncpy(((JPEGENCODERSTATE *)encoder->state.context)->rawmode, rawmode, 8); - ((JPEGENCODERSTATE*)encoder->state.context)->quality = quality; - ((JPEGENCODERSTATE*)encoder->state.context)->qtables = qarrays; - ((JPEGENCODERSTATE*)encoder->state.context)->qtablesLen = qtablesLen; - ((JPEGENCODERSTATE*)encoder->state.context)->subsampling = subsampling; - ((JPEGENCODERSTATE*)encoder->state.context)->progressive = progressive; - ((JPEGENCODERSTATE*)encoder->state.context)->smooth = smooth; - ((JPEGENCODERSTATE*)encoder->state.context)->optimize = optimize; - ((JPEGENCODERSTATE*)encoder->state.context)->streamtype = streamtype; - ((JPEGENCODERSTATE*)encoder->state.context)->xdpi = xdpi; - ((JPEGENCODERSTATE*)encoder->state.context)->ydpi = ydpi; - ((JPEGENCODERSTATE*)encoder->state.context)->extra = extra; - ((JPEGENCODERSTATE*)encoder->state.context)->extra_size = extra_size; - ((JPEGENCODERSTATE*)encoder->state.context)->rawExif = rawExif; - ((JPEGENCODERSTATE*)encoder->state.context)->rawExifLen = rawExifLen; + ((JPEGENCODERSTATE *)encoder->state.context)->quality = quality; + ((JPEGENCODERSTATE *)encoder->state.context)->qtables = qarrays; + ((JPEGENCODERSTATE *)encoder->state.context)->qtablesLen = qtablesLen; + ((JPEGENCODERSTATE *)encoder->state.context)->subsampling = subsampling; + ((JPEGENCODERSTATE *)encoder->state.context)->progressive = progressive; + ((JPEGENCODERSTATE *)encoder->state.context)->smooth = smooth; + ((JPEGENCODERSTATE *)encoder->state.context)->optimize = optimize; + ((JPEGENCODERSTATE *)encoder->state.context)->streamtype = streamtype; + ((JPEGENCODERSTATE *)encoder->state.context)->xdpi = xdpi; + ((JPEGENCODERSTATE *)encoder->state.context)->ydpi = ydpi; + ((JPEGENCODERSTATE *)encoder->state.context)->extra = extra; + ((JPEGENCODERSTATE *)encoder->state.context)->extra_size = extra_size; + ((JPEGENCODERSTATE *)encoder->state.context)->rawExif = rawExif; + ((JPEGENCODERSTATE *)encoder->state.context)->rawExifLen = rawExifLen; - return (PyObject*) encoder; + return (PyObject *)encoder; } #endif - /* -------------------------------------------------------------------- */ -/* JPEG 2000 */ +/* JPEG 2000 */ /* -------------------------------------------------------------------- */ #ifdef HAVE_OPENJPEG -#include "Jpeg2K.h" +#include "libImaging/Jpeg2K.h" static void -j2k_decode_coord_tuple(PyObject *tuple, int *x, int *y) -{ +j2k_decode_coord_tuple(PyObject *tuple, int *x, int *y) { *x = *y = 0; if (tuple && PyTuple_Check(tuple) && PyTuple_GET_SIZE(tuple) == 2) { *x = (int)PyLong_AsLong(PyTuple_GET_ITEM(tuple, 0)); *y = (int)PyLong_AsLong(PyTuple_GET_ITEM(tuple, 1)); - if (*x < 0) + if (*x < 0) { *x = 0; - if (*y < 0) + } + if (*y < 0) { *y = 0; + } } } -PyObject* -PyImaging_Jpeg2KEncoderNew(PyObject *self, PyObject *args) -{ +PyObject * +PyImaging_Jpeg2KEncoderNew(PyObject *self, PyObject *args) { ImagingEncoderObject *encoder; JPEG2KENCODESTATE *context; @@ -1139,50 +1184,66 @@ PyImaging_Jpeg2KEncoderNew(PyObject *self, PyObject *args) OPJ_CINEMA_MODE cine_mode; Py_ssize_t fd = -1; - if (!PyArg_ParseTuple(args, "ss|OOOsOnOOOssn", &mode, &format, - &offset, &tile_offset, &tile_size, - &quality_mode, &quality_layers, &num_resolutions, - &cblk_size, &precinct_size, - &irreversible, &progression, &cinema_mode, - &fd)) + if (!PyArg_ParseTuple( + args, + "ss|OOOsOnOOOssn", + &mode, + &format, + &offset, + &tile_offset, + &tile_size, + &quality_mode, + &quality_layers, + &num_resolutions, + &cblk_size, + &precinct_size, + &irreversible, + &progression, + &cinema_mode, + &fd)) { return NULL; + } - if (strcmp (format, "j2k") == 0) + if (strcmp(format, "j2k") == 0) { codec_format = OPJ_CODEC_J2K; - else if (strcmp (format, "jpt") == 0) + } else if (strcmp(format, "jpt") == 0) { codec_format = OPJ_CODEC_JPT; - else if (strcmp (format, "jp2") == 0) + } else if (strcmp(format, "jp2") == 0) { codec_format = OPJ_CODEC_JP2; - else + } else { return NULL; + } - if (strcmp(progression, "LRCP") == 0) + if (strcmp(progression, "LRCP") == 0) { prog_order = OPJ_LRCP; - else if (strcmp(progression, "RLCP") == 0) + } else if (strcmp(progression, "RLCP") == 0) { prog_order = OPJ_RLCP; - else if (strcmp(progression, "RPCL") == 0) + } else if (strcmp(progression, "RPCL") == 0) { prog_order = OPJ_RPCL; - else if (strcmp(progression, "PCRL") == 0) + } else if (strcmp(progression, "PCRL") == 0) { prog_order = OPJ_PCRL; - else if (strcmp(progression, "CPRL") == 0) + } else if (strcmp(progression, "CPRL") == 0) { prog_order = OPJ_CPRL; - else + } else { return NULL; + } - if (strcmp(cinema_mode, "no") == 0) + if (strcmp(cinema_mode, "no") == 0) { cine_mode = OPJ_OFF; - else if (strcmp(cinema_mode, "cinema2k-24") == 0) + } else if (strcmp(cinema_mode, "cinema2k-24") == 0) { cine_mode = OPJ_CINEMA2K_24; - else if (strcmp(cinema_mode, "cinema2k-48") == 0) + } else if (strcmp(cinema_mode, "cinema2k-48") == 0) { cine_mode = OPJ_CINEMA2K_48; - else if (strcmp(cinema_mode, "cinema4k-24") == 0) + } else if (strcmp(cinema_mode, "cinema4k-24") == 0) { cine_mode = OPJ_CINEMA4K_24; - else + } else { return NULL; + } encoder = PyImaging_EncoderNew(sizeof(JPEG2KENCODESTATE)); - if (!encoder) + if (!encoder) { return NULL; + } encoder->encode = ImagingJpeg2KEncode; encoder->cleanup = ImagingJpeg2KEncodeCleanup; @@ -1194,49 +1255,44 @@ PyImaging_Jpeg2KEncoderNew(PyObject *self, PyObject *args) context->format = codec_format; context->offset_x = context->offset_y = 0; - j2k_decode_coord_tuple(offset, &context->offset_x, &context->offset_y); - j2k_decode_coord_tuple(tile_offset, - &context->tile_offset_x, - &context->tile_offset_y); - j2k_decode_coord_tuple(tile_size, - &context->tile_size_x, - &context->tile_size_y); + j2k_decode_coord_tuple( + tile_offset, &context->tile_offset_x, &context->tile_offset_y); + j2k_decode_coord_tuple(tile_size, &context->tile_size_x, &context->tile_size_y); /* Error on illegal tile offsets */ if (context->tile_size_x && context->tile_size_y) { - if (context->tile_offset_x <= context->offset_x - context->tile_size_x - || context->tile_offset_y <= context->offset_y - context->tile_size_y) { - PyErr_SetString(PyExc_ValueError, - "JPEG 2000 tile offset too small; top left tile must " - "intersect image area"); + if (context->tile_offset_x <= context->offset_x - context->tile_size_x || + context->tile_offset_y <= context->offset_y - context->tile_size_y) { + PyErr_SetString( + PyExc_ValueError, + "JPEG 2000 tile offset too small; top left tile must " + "intersect image area"); Py_DECREF(encoder); return NULL; } - if (context->tile_offset_x > context->offset_x - || context->tile_offset_y > context->offset_y) { - PyErr_SetString(PyExc_ValueError, - "JPEG 2000 tile offset too large to cover image area"); + if (context->tile_offset_x > context->offset_x || + context->tile_offset_y > context->offset_y) { + PyErr_SetString( + PyExc_ValueError, + "JPEG 2000 tile offset too large to cover image area"); Py_DECREF(encoder); return NULL; } } if (quality_layers && PySequence_Check(quality_layers)) { - context->quality_is_in_db = strcmp (quality_mode, "dB") == 0; + context->quality_is_in_db = strcmp(quality_mode, "dB") == 0; context->quality_layers = quality_layers; Py_INCREF(quality_layers); } context->num_resolutions = num_resolutions; - j2k_decode_coord_tuple(cblk_size, - &context->cblk_width, - &context->cblk_height); - j2k_decode_coord_tuple(precinct_size, - &context->precinct_width, - &context->precinct_height); + j2k_decode_coord_tuple(cblk_size, &context->cblk_width, &context->cblk_height); + j2k_decode_coord_tuple( + precinct_size, &context->precinct_width, &context->precinct_height); context->irreversible = PyObject_IsTrue(irreversible); context->progression = prog_order; diff --git a/src/libImaging/Access.c b/src/libImaging/Access.c index 15ffa11fc..6bb16fe3a 100644 --- a/src/libImaging/Access.c +++ b/src/libImaging/Access.c @@ -9,7 +9,6 @@ * See the README file for information on usage and redistribution. */ - #include "Imaging.h" /* use Tests/make_hash.py to calculate these values */ @@ -19,22 +18,25 @@ static struct ImagingAccessInstance access_table[ACCESS_TABLE_SIZE]; static inline UINT32 -hash(const char* mode) -{ +hash(const char *mode) { UINT32 i = ACCESS_TABLE_HASH; - while (*mode) - i = ((i<<5) + i) ^ (UINT8) *mode++; + while (*mode) { + i = ((i << 5) + i) ^ (UINT8)*mode++; + } return i % ACCESS_TABLE_SIZE; } static ImagingAccess -add_item(const char* mode) -{ +add_item(const char *mode) { UINT32 i = hash(mode); /* printf("hash %s => %d\n", mode, i); */ if (access_table[i].mode && strcmp(access_table[i].mode, mode) != 0) { - fprintf(stderr, "AccessInit: hash collision: %d for both %s and %s\n", - i, mode, access_table[i].mode); + fprintf( + stderr, + "AccessInit: hash collision: %d for both %s and %s\n", + i, + mode, + access_table[i].mode); exit(1); } access_table[i].mode = mode; @@ -43,37 +45,33 @@ add_item(const char* mode) /* fetch pointer to pixel line */ -static void* -line_8(Imaging im, int x, int y) -{ +static void * +line_8(Imaging im, int x, int y) { return &im->image8[y][x]; } -static void* -line_16(Imaging im, int x, int y) -{ - return &im->image8[y][x+x]; +static void * +line_16(Imaging im, int x, int y) { + return &im->image8[y][x + x]; } -static void* -line_32(Imaging im, int x, int y) -{ +static void * +line_32(Imaging im, int x, int y) { return &im->image32[y][x]; } /* fetch individual pixel */ static void -get_pixel(Imaging im, int x, int y, void* color) -{ - char* out = color; +get_pixel(Imaging im, int x, int y, void *color) { + char *out = color; /* generic pixel access*/ if (im->image8) { out[0] = im->image8[y][x]; } else { - UINT8* p = (UINT8*) &im->image32[y][x]; + UINT8 *p = (UINT8 *)&im->image32[y][x]; if (im->type == IMAGING_TYPE_UINT8 && im->bands == 2) { out[0] = p[0]; out[1] = p[3]; @@ -84,18 +82,16 @@ get_pixel(Imaging im, int x, int y, void* color) } static void -get_pixel_8(Imaging im, int x, int y, void* color) -{ - char* out = color; +get_pixel_8(Imaging im, int x, int y, void *color) { + char *out = color; out[0] = im->image8[y][x]; } static void -get_pixel_16L(Imaging im, int x, int y, void* color) -{ - UINT8* in = (UINT8*) &im->image[y][x+x]; +get_pixel_16L(Imaging im, int x, int y, void *color) { + UINT8 *in = (UINT8 *)&im->image[y][x + x]; #ifdef WORDS_BIGENDIAN - UINT16 out = in[0] + (in[1]<<8); + UINT16 out = in[0] + (in[1] << 8); memcpy(color, &out, sizeof(out)); #else memcpy(color, in, sizeof(UINT16)); @@ -103,29 +99,26 @@ get_pixel_16L(Imaging im, int x, int y, void* color) } static void -get_pixel_16B(Imaging im, int x, int y, void* color) -{ - UINT8* in = (UINT8*) &im->image[y][x+x]; +get_pixel_16B(Imaging im, int x, int y, void *color) { + UINT8 *in = (UINT8 *)&im->image[y][x + x]; #ifdef WORDS_BIGENDIAN memcpy(color, in, sizeof(UINT16)); #else - UINT16 out = in[1] + (in[0]<<8); + UINT16 out = in[1] + (in[0] << 8); memcpy(color, &out, sizeof(out)); #endif } static void -get_pixel_32(Imaging im, int x, int y, void* color) -{ +get_pixel_32(Imaging im, int x, int y, void *color) { memcpy(color, &im->image32[y][x], sizeof(INT32)); } static void -get_pixel_32L(Imaging im, int x, int y, void* color) -{ - UINT8* in = (UINT8*) &im->image[y][x*4]; +get_pixel_32L(Imaging im, int x, int y, void *color) { + UINT8 *in = (UINT8 *)&im->image[y][x * 4]; #ifdef WORDS_BIGENDIAN - INT32 out = in[0] + (in[1]<<8) + (in[2]<<16) + (in[3]<<24); + INT32 out = in[0] + (in[1] << 8) + (in[2] << 16) + (in[3] << 24); memcpy(color, &out, sizeof(out)); #else memcpy(color, in, sizeof(INT32)); @@ -133,13 +126,12 @@ get_pixel_32L(Imaging im, int x, int y, void* color) } static void -get_pixel_32B(Imaging im, int x, int y, void* color) -{ - UINT8* in = (UINT8*) &im->image[y][x*4]; +get_pixel_32B(Imaging im, int x, int y, void *color) { + UINT8 *in = (UINT8 *)&im->image[y][x * 4]; #ifdef WORDS_BIGENDIAN memcpy(color, in, sizeof(INT32)); #else - INT32 out = in[3] + (in[2]<<8) + (in[1]<<16) + (in[0]<<24); + INT32 out = in[3] + (in[2] << 8) + (in[1] << 16) + (in[0] << 24); memcpy(color, &out, sizeof(out)); #endif } @@ -147,46 +139,41 @@ get_pixel_32B(Imaging im, int x, int y, void* color) /* store individual pixel */ static void -put_pixel(Imaging im, int x, int y, const void* color) -{ - if (im->image8) - im->image8[y][x] = *((UINT8*) color); - else +put_pixel(Imaging im, int x, int y, const void *color) { + if (im->image8) { + im->image8[y][x] = *((UINT8 *)color); + } else { memcpy(&im->image32[y][x], color, sizeof(INT32)); + } } static void -put_pixel_8(Imaging im, int x, int y, const void* color) -{ - im->image8[y][x] = *((UINT8*) color); +put_pixel_8(Imaging im, int x, int y, const void *color) { + im->image8[y][x] = *((UINT8 *)color); } static void -put_pixel_16L(Imaging im, int x, int y, const void* color) -{ - memcpy(&im->image8[y][x+x], color, 2); +put_pixel_16L(Imaging im, int x, int y, const void *color) { + memcpy(&im->image8[y][x + x], color, 2); } static void -put_pixel_16B(Imaging im, int x, int y, const void* color) -{ - const char* in = color; - UINT8* out = (UINT8*) &im->image8[y][x+x]; +put_pixel_16B(Imaging im, int x, int y, const void *color) { + const char *in = color; + UINT8 *out = (UINT8 *)&im->image8[y][x + x]; out[0] = in[1]; out[1] = in[0]; } static void -put_pixel_32L(Imaging im, int x, int y, const void* color) -{ - memcpy(&im->image8[y][x*4], color, 4); +put_pixel_32L(Imaging im, int x, int y, const void *color) { + memcpy(&im->image8[y][x * 4], color, 4); } static void -put_pixel_32B(Imaging im, int x, int y, const void* color) -{ - const char* in = color; - UINT8* out = (UINT8*) &im->image8[y][x*4]; +put_pixel_32B(Imaging im, int x, int y, const void *color) { + const char *in = color; + UINT8 *out = (UINT8 *)&im->image8[y][x * 4]; out[0] = in[3]; out[1] = in[2]; out[2] = in[1]; @@ -194,19 +181,18 @@ put_pixel_32B(Imaging im, int x, int y, const void* color) } static void -put_pixel_32(Imaging im, int x, int y, const void* color) -{ +put_pixel_32(Imaging im, int x, int y, const void *color) { memcpy(&im->image32[y][x], color, sizeof(INT32)); } void -ImagingAccessInit() -{ -#define ADD(mode_, line_, get_pixel_, put_pixel_) \ - { ImagingAccess access = add_item(mode_); \ - access->line = line_; \ - access->get_pixel = get_pixel_; \ - access->put_pixel = put_pixel_; \ +ImagingAccessInit() { +#define ADD(mode_, line_, get_pixel_, put_pixel_) \ + { \ + ImagingAccess access = add_item(mode_); \ + access->line = line_; \ + access->get_pixel = get_pixel_; \ + access->put_pixel = put_pixel_; \ } /* populate access table */ @@ -234,16 +220,13 @@ ImagingAccessInit() } ImagingAccess -ImagingAccessNew(Imaging im) -{ +ImagingAccessNew(Imaging im) { ImagingAccess access = &access_table[hash(im->mode)]; - if (im->mode[0] != access->mode[0] || strcmp(im->mode, access->mode) != 0) + if (im->mode[0] != access->mode[0] || strcmp(im->mode, access->mode) != 0) { return NULL; + } return access; } void -_ImagingAccessDelete(Imaging im, ImagingAccess access) -{ - -} +_ImagingAccessDelete(Imaging im, ImagingAccess access) {} diff --git a/src/libImaging/AlphaComposite.c b/src/libImaging/AlphaComposite.c index a074334aa..6d728f908 100644 --- a/src/libImaging/AlphaComposite.c +++ b/src/libImaging/AlphaComposite.c @@ -8,51 +8,45 @@ * See the README file for details on usage and redistribution. */ - #include "Imaging.h" #define PRECISION_BITS 7 -typedef struct -{ +typedef struct { UINT8 r; UINT8 g; UINT8 b; UINT8 a; } rgba8; - - Imaging -ImagingAlphaComposite(Imaging imDst, Imaging imSrc) -{ +ImagingAlphaComposite(Imaging imDst, Imaging imSrc) { Imaging imOut; int x, y; /* Check arguments */ - if (!imDst || !imSrc || - strcmp(imDst->mode, "RGBA") || - imDst->type != IMAGING_TYPE_UINT8 || - imDst->bands != 4) + if (!imDst || !imSrc || strcmp(imDst->mode, "RGBA") || + imDst->type != IMAGING_TYPE_UINT8 || imDst->bands != 4) { return ImagingError_ModeError(); + } - if (strcmp(imDst->mode, imSrc->mode) || - imDst->type != imSrc->type || - imDst->bands != imSrc->bands || - imDst->xsize != imSrc->xsize || - imDst->ysize != imSrc->ysize) + if (strcmp(imDst->mode, imSrc->mode) || imDst->type != imSrc->type || + imDst->bands != imSrc->bands || imDst->xsize != imSrc->xsize || + imDst->ysize != imSrc->ysize) { return ImagingError_Mismatch(); + } imOut = ImagingNewDirty(imDst->mode, imDst->xsize, imDst->ysize); - if (!imOut) + if (!imOut) { return NULL; + } for (y = 0; y < imDst->ysize; y++) { - rgba8* dst = (rgba8*) imDst->image[y]; - rgba8* src = (rgba8*) imSrc->image[y]; - rgba8* out = (rgba8*) imOut->image[y]; + rgba8 *dst = (rgba8 *)imDst->image[y]; + rgba8 *src = (rgba8 *)imSrc->image[y]; + rgba8 *out = (rgba8 *)imOut->image[y]; - for (x = 0; x < imDst->xsize; x ++) { + for (x = 0; x < imDst->xsize; x++) { if (src->a == 0) { // Copy 4 bytes at once. *out = *dst; @@ -66,21 +60,25 @@ ImagingAlphaComposite(Imaging imDst, Imaging imSrc) UINT32 outa255 = src->a * 255 + blend; // There we use 7 bits for precision. // We could use more, but we go beyond 32 bits. - UINT32 coef1 = src->a * 255 * 255 * (1<a * 255 * 255 * (1 << PRECISION_BITS) / outa255; + UINT32 coef2 = 255 * (1 << PRECISION_BITS) - coef1; tmpr = src->r * coef1 + dst->r * coef2; tmpg = src->g * coef1 + dst->g * coef2; tmpb = src->b * coef1 + dst->b * coef2; - out->r = SHIFTFORDIV255(tmpr + (0x80<> PRECISION_BITS; - out->g = SHIFTFORDIV255(tmpg + (0x80<> PRECISION_BITS; - out->b = SHIFTFORDIV255(tmpb + (0x80<> PRECISION_BITS; + out->r = + SHIFTFORDIV255(tmpr + (0x80 << PRECISION_BITS)) >> PRECISION_BITS; + out->g = + SHIFTFORDIV255(tmpg + (0x80 << PRECISION_BITS)) >> PRECISION_BITS; + out->b = + SHIFTFORDIV255(tmpb + (0x80 << PRECISION_BITS)) >> PRECISION_BITS; out->a = SHIFTFORDIV255(outa255 + 0x80); } - dst++; src++; out++; + dst++; + src++; + out++; } - } return imOut; diff --git a/src/libImaging/Bands.c b/src/libImaging/Bands.c index 7fff04486..e1b16b34a 100644 --- a/src/libImaging/Bands.c +++ b/src/libImaging/Bands.c @@ -15,39 +15,41 @@ * See the README file for details on usage and redistribution. */ - #include "Imaging.h" - Imaging -ImagingGetBand(Imaging imIn, int band) -{ +ImagingGetBand(Imaging imIn, int band) { Imaging imOut; int x, y; /* Check arguments */ - if (!imIn || imIn->type != IMAGING_TYPE_UINT8) - return (Imaging) ImagingError_ModeError(); + if (!imIn || imIn->type != IMAGING_TYPE_UINT8) { + return (Imaging)ImagingError_ModeError(); + } - if (band < 0 || band >= imIn->bands) - return (Imaging) ImagingError_ValueError("band index out of range"); + if (band < 0 || band >= imIn->bands) { + return (Imaging)ImagingError_ValueError("band index out of range"); + } /* Shortcuts */ - if (imIn->bands == 1) + if (imIn->bands == 1) { return ImagingCopy(imIn); + } /* Special case for LXXA etc */ - if (imIn->bands == 2 && band == 1) + if (imIn->bands == 2 && band == 1) { band = 3; + } imOut = ImagingNewDirty("L", imIn->xsize, imIn->ysize); - if (!imOut) + if (!imOut) { return NULL; + } /* Extract band from image */ for (y = 0; y < imIn->ysize; y++) { - UINT8* in = (UINT8*) imIn->image[y] + band; - UINT8* out = imOut->image8[y]; + UINT8 *in = (UINT8 *)imIn->image[y] + band; + UINT8 *out = imOut->image8[y]; x = 0; for (; x < imIn->xsize - 3; x += 4) { UINT32 v = MAKE_UINT32(in[0], in[4], in[8], in[12]); @@ -63,15 +65,13 @@ ImagingGetBand(Imaging imIn, int band) return imOut; } - int -ImagingSplit(Imaging imIn, Imaging bands[4]) -{ +ImagingSplit(Imaging imIn, Imaging bands[4]) { int i, j, x, y; /* Check arguments */ if (!imIn || imIn->type != IMAGING_TYPE_UINT8) { - (void) ImagingError_ModeError(); + (void)ImagingError_ModeError(); return 0; } @@ -83,7 +83,7 @@ ImagingSplit(Imaging imIn, Imaging bands[4]) for (i = 0; i < imIn->bands; i++) { bands[i] = ImagingNewDirty("L", imIn->xsize, imIn->ysize); - if ( ! bands[i]) { + if (!bands[i]) { for (j = 0; j < i; ++j) { ImagingDelete(bands[j]); } @@ -94,14 +94,14 @@ ImagingSplit(Imaging imIn, Imaging bands[4]) /* Extract bands from image */ if (imIn->bands == 2) { for (y = 0; y < imIn->ysize; y++) { - UINT8* in = (UINT8*) imIn->image[y]; - UINT8* out0 = bands[0]->image8[y]; - UINT8* out1 = bands[1]->image8[y]; + UINT8 *in = (UINT8 *)imIn->image[y]; + UINT8 *out0 = bands[0]->image8[y]; + UINT8 *out1 = bands[1]->image8[y]; x = 0; for (; x < imIn->xsize - 3; x += 4) { UINT32 v = MAKE_UINT32(in[0], in[4], in[8], in[12]); memcpy(out0 + x, &v, sizeof(v)); - v = MAKE_UINT32(in[0+3], in[4+3], in[8+3], in[12+3]); + v = MAKE_UINT32(in[0 + 3], in[4 + 3], in[8 + 3], in[12 + 3]); memcpy(out1 + x, &v, sizeof(v)); in += 16; } @@ -113,17 +113,17 @@ ImagingSplit(Imaging imIn, Imaging bands[4]) } } else if (imIn->bands == 3) { for (y = 0; y < imIn->ysize; y++) { - UINT8* in = (UINT8*) imIn->image[y]; - UINT8* out0 = bands[0]->image8[y]; - UINT8* out1 = bands[1]->image8[y]; - UINT8* out2 = bands[2]->image8[y]; + UINT8 *in = (UINT8 *)imIn->image[y]; + UINT8 *out0 = bands[0]->image8[y]; + UINT8 *out1 = bands[1]->image8[y]; + UINT8 *out2 = bands[2]->image8[y]; x = 0; for (; x < imIn->xsize - 3; x += 4) { UINT32 v = MAKE_UINT32(in[0], in[4], in[8], in[12]); memcpy(out0 + x, &v, sizeof(v)); - v = MAKE_UINT32(in[0+1], in[4+1], in[8+1], in[12+1]); + v = MAKE_UINT32(in[0 + 1], in[4 + 1], in[8 + 1], in[12 + 1]); memcpy(out1 + x, &v, sizeof(v)); - v = MAKE_UINT32(in[0+2], in[4+2], in[8+2], in[12+2]); + v = MAKE_UINT32(in[0 + 2], in[4 + 2], in[8 + 2], in[12 + 2]); memcpy(out2 + x, &v, sizeof(v)); in += 16; } @@ -136,20 +136,20 @@ ImagingSplit(Imaging imIn, Imaging bands[4]) } } else { for (y = 0; y < imIn->ysize; y++) { - UINT8* in = (UINT8*) imIn->image[y]; - UINT8* out0 = bands[0]->image8[y]; - UINT8* out1 = bands[1]->image8[y]; - UINT8* out2 = bands[2]->image8[y]; - UINT8* out3 = bands[3]->image8[y]; + UINT8 *in = (UINT8 *)imIn->image[y]; + UINT8 *out0 = bands[0]->image8[y]; + UINT8 *out1 = bands[1]->image8[y]; + UINT8 *out2 = bands[2]->image8[y]; + UINT8 *out3 = bands[3]->image8[y]; x = 0; for (; x < imIn->xsize - 3; x += 4) { UINT32 v = MAKE_UINT32(in[0], in[4], in[8], in[12]); memcpy(out0 + x, &v, sizeof(v)); - v = MAKE_UINT32(in[0+1], in[4+1], in[8+1], in[12+1]); + v = MAKE_UINT32(in[0 + 1], in[4 + 1], in[8 + 1], in[12 + 1]); memcpy(out1 + x, &v, sizeof(v)); - v = MAKE_UINT32(in[0+2], in[4+2], in[8+2], in[12+2]); + v = MAKE_UINT32(in[0 + 2], in[4 + 2], in[8 + 2], in[12 + 2]); memcpy(out2 + x, &v, sizeof(v)); - v = MAKE_UINT32(in[0+3], in[4+3], in[8+3], in[12+3]); + v = MAKE_UINT32(in[0 + 3], in[4 + 3], in[8 + 3], in[12 + 3]); memcpy(out3 + x, &v, sizeof(v)); in += 16; } @@ -166,36 +166,38 @@ ImagingSplit(Imaging imIn, Imaging bands[4]) return imIn->bands; } - Imaging -ImagingPutBand(Imaging imOut, Imaging imIn, int band) -{ +ImagingPutBand(Imaging imOut, Imaging imIn, int band) { int x, y; /* Check arguments */ - if (!imIn || imIn->bands != 1 || !imOut) - return (Imaging) ImagingError_ModeError(); + if (!imIn || imIn->bands != 1 || !imOut) { + return (Imaging)ImagingError_ModeError(); + } - if (band < 0 || band >= imOut->bands) - return (Imaging) ImagingError_ValueError("band index out of range"); + if (band < 0 || band >= imOut->bands) { + return (Imaging)ImagingError_ValueError("band index out of range"); + } - if (imIn->type != imOut->type || - imIn->xsize != imOut->xsize || - imIn->ysize != imOut->ysize) - return (Imaging) ImagingError_Mismatch(); + if (imIn->type != imOut->type || imIn->xsize != imOut->xsize || + imIn->ysize != imOut->ysize) { + return (Imaging)ImagingError_Mismatch(); + } /* Shortcuts */ - if (imOut->bands == 1) + if (imOut->bands == 1) { return ImagingCopy2(imOut, imIn); + } /* Special case for LXXA etc */ - if (imOut->bands == 2 && band == 1) + if (imOut->bands == 2 && band == 1) { band = 3; + } /* Insert band into image */ for (y = 0; y < imIn->ysize; y++) { - UINT8* in = imIn->image8[y]; - UINT8* out = (UINT8*) imOut->image[y] + band; + UINT8 *in = imIn->image8[y]; + UINT8 *out = (UINT8 *)imOut->image[y] + band; for (x = 0; x < imIn->xsize; x++) { *out = in[x]; out += 4; @@ -206,28 +208,30 @@ ImagingPutBand(Imaging imOut, Imaging imIn, int band) } Imaging -ImagingFillBand(Imaging imOut, int band, int color) -{ +ImagingFillBand(Imaging imOut, int band, int color) { int x, y; /* Check arguments */ - if (!imOut || imOut->type != IMAGING_TYPE_UINT8) - return (Imaging) ImagingError_ModeError(); + if (!imOut || imOut->type != IMAGING_TYPE_UINT8) { + return (Imaging)ImagingError_ModeError(); + } - if (band < 0 || band >= imOut->bands) - return (Imaging) ImagingError_ValueError("band index out of range"); + if (band < 0 || band >= imOut->bands) { + return (Imaging)ImagingError_ValueError("band index out of range"); + } /* Special case for LXXA etc */ - if (imOut->bands == 2 && band == 1) + if (imOut->bands == 2 && band == 1) { band = 3; + } color = CLIP8(color); /* Insert color into image */ for (y = 0; y < imOut->ysize; y++) { - UINT8* out = (UINT8*) imOut->image[y] + band; + UINT8 *out = (UINT8 *)imOut->image[y] + band; for (x = 0; x < imOut->xsize; x++) { - *out = (UINT8) color; + *out = (UINT8)color; out += 4; } } @@ -236,70 +240,71 @@ ImagingFillBand(Imaging imOut, int band, int color) } Imaging -ImagingMerge(const char* mode, Imaging bands[4]) -{ +ImagingMerge(const char *mode, Imaging bands[4]) { int i, x, y; int bandsCount = 0; Imaging imOut; Imaging firstBand; firstBand = bands[0]; - if ( ! firstBand) { - return (Imaging) ImagingError_ValueError("wrong number of bands"); + if (!firstBand) { + return (Imaging)ImagingError_ValueError("wrong number of bands"); } for (i = 0; i < 4; ++i) { - if ( ! bands[i]) { + if (!bands[i]) { break; } if (bands[i]->bands != 1) { - return (Imaging) ImagingError_ModeError(); + return (Imaging)ImagingError_ModeError(); } - if (bands[i]->xsize != firstBand->xsize - || bands[i]->ysize != firstBand->ysize) { - return (Imaging) ImagingError_Mismatch(); + if (bands[i]->xsize != firstBand->xsize || + bands[i]->ysize != firstBand->ysize) { + return (Imaging)ImagingError_Mismatch(); } } bandsCount = i; imOut = ImagingNewDirty(mode, firstBand->xsize, firstBand->ysize); - if ( ! imOut) + if (!imOut) { return NULL; + } if (imOut->bands != bandsCount) { ImagingDelete(imOut); - return (Imaging) ImagingError_ValueError("wrong number of bands"); + return (Imaging)ImagingError_ValueError("wrong number of bands"); } - if (imOut->bands == 1) + if (imOut->bands == 1) { return ImagingCopy2(imOut, firstBand); + } if (imOut->bands == 2) { for (y = 0; y < imOut->ysize; y++) { - UINT8* in0 = bands[0]->image8[y]; - UINT8* in1 = bands[1]->image8[y]; - UINT32* out = (UINT32*) imOut->image32[y]; + UINT8 *in0 = bands[0]->image8[y]; + UINT8 *in1 = bands[1]->image8[y]; + UINT32 *out = (UINT32 *)imOut->image32[y]; for (x = 0; x < imOut->xsize; x++) { out[x] = MAKE_UINT32(in0[x], 0, 0, in1[x]); } } } else if (imOut->bands == 3) { for (y = 0; y < imOut->ysize; y++) { - UINT8* in0 = bands[0]->image8[y]; - UINT8* in1 = bands[1]->image8[y]; - UINT8* in2 = bands[2]->image8[y]; - UINT32* out = (UINT32*) imOut->image32[y]; + UINT8 *in0 = bands[0]->image8[y]; + UINT8 *in1 = bands[1]->image8[y]; + UINT8 *in2 = bands[2]->image8[y]; + UINT32 *out = (UINT32 *)imOut->image32[y]; for (x = 0; x < imOut->xsize; x++) { out[x] = MAKE_UINT32(in0[x], in1[x], in2[x], 0); } } } else if (imOut->bands == 4) { for (y = 0; y < imOut->ysize; y++) { - UINT8* in0 = bands[0]->image8[y]; - UINT8* in1 = bands[1]->image8[y]; - UINT8* in2 = bands[2]->image8[y]; - UINT8* in3 = bands[3]->image8[y]; - UINT32* out = (UINT32*) imOut->image32[y]; + UINT8 *in0 = bands[0]->image8[y]; + UINT8 *in1 = bands[1]->image8[y]; + UINT8 *in2 = bands[2]->image8[y]; + UINT8 *in3 = bands[3]->image8[y]; + UINT32 *out = (UINT32 *)imOut->image32[y]; for (x = 0; x < imOut->xsize; x++) { out[x] = MAKE_UINT32(in0[x], in1[x], in2[x], in3[x]); } diff --git a/src/libImaging/BcnDecode.c b/src/libImaging/BcnDecode.c index c2c4f21e7..b6a4cbadc 100644 --- a/src/libImaging/BcnDecode.c +++ b/src/libImaging/BcnDecode.c @@ -11,846 +11,855 @@ * https://creativecommons.org/publicdomain/zero/1.0/ */ - #include "Imaging.h" - typedef struct { - UINT8 r, g, b, a; + UINT8 r, g, b, a; } rgba; typedef struct { - UINT8 l; + UINT8 l; } lum; typedef struct { - FLOAT32 r, g, b; + FLOAT32 r, g, b; } rgb32f; typedef struct { - UINT16 c0, c1; - UINT32 lut; + UINT16 c0, c1; + UINT32 lut; } bc1_color; typedef struct { - UINT8 a0, a1; - UINT8 lut[6]; + UINT8 a0, a1; + UINT8 lut[6]; } bc3_alpha; -#define LOAD16(p) \ - (p)[0] | ((p)[1] << 8) +#define LOAD16(p) (p)[0] | ((p)[1] << 8) -#define LOAD32(p) \ - (p)[0] | ((p)[1] << 8) | ((p)[2] << 16) | ((p)[3] << 24) +#define LOAD32(p) (p)[0] | ((p)[1] << 8) | ((p)[2] << 16) | ((p)[3] << 24) -static void bc1_color_load(bc1_color *dst, const UINT8 *src) { - dst->c0 = LOAD16(src); - dst->c1 = LOAD16(src + 2); - dst->lut = LOAD32(src + 4); +static void +bc1_color_load(bc1_color *dst, const UINT8 *src) { + dst->c0 = LOAD16(src); + dst->c1 = LOAD16(src + 2); + dst->lut = LOAD32(src + 4); } -static void bc3_alpha_load(bc3_alpha *dst, const UINT8 *src) { - memcpy(dst, src, sizeof(bc3_alpha)); +static void +bc3_alpha_load(bc3_alpha *dst, const UINT8 *src) { + memcpy(dst, src, sizeof(bc3_alpha)); } -static rgba decode_565(UINT16 x) { - rgba c; - int r, g, b; - r = (x & 0xf800) >> 8; - r |= r >> 5; - c.r = r; - g = (x & 0x7e0) >> 3; - g |= g >> 6; - c.g = g; - b = (x & 0x1f) << 3; - b |= b >> 5; - c.b = b; - c.a = 0xff; - return c; +static rgba +decode_565(UINT16 x) { + rgba c; + int r, g, b; + r = (x & 0xf800) >> 8; + r |= r >> 5; + c.r = r; + g = (x & 0x7e0) >> 3; + g |= g >> 6; + c.g = g; + b = (x & 0x1f) << 3; + b |= b >> 5; + c.b = b; + c.a = 0xff; + return c; } -static void decode_bc1_color(rgba *dst, const UINT8 *src) { - bc1_color col; - rgba p[4]; - int n, cw; - UINT16 r0, g0, b0, r1, g1, b1; - bc1_color_load(&col, src); +static void +decode_bc1_color(rgba *dst, const UINT8 *src) { + bc1_color col; + rgba p[4]; + int n, cw; + UINT16 r0, g0, b0, r1, g1, b1; + bc1_color_load(&col, src); - p[0] = decode_565(col.c0); - r0 = p[0].r; - g0 = p[0].g; - b0 = p[0].b; - p[1] = decode_565(col.c1); - r1 = p[1].r; - g1 = p[1].g; - b1 = p[1].b; - if (col.c0 > col.c1) { - p[2].r = (2*r0 + 1*r1) / 3; - p[2].g = (2*g0 + 1*g1) / 3; - p[2].b = (2*b0 + 1*b1) / 3; - p[2].a = 0xff; - p[3].r = (1*r0 + 2*r1) / 3; - p[3].g = (1*g0 + 2*g1) / 3; - p[3].b = (1*b0 + 2*b1) / 3; - p[3].a = 0xff; - } else { - p[2].r = (r0 + r1) / 2; - p[2].g = (g0 + g1) / 2; - p[2].b = (b0 + b1) / 2; - p[2].a = 0xff; - p[3].r = 0; - p[3].g = 0; - p[3].b = 0; - p[3].a = 0; - } - for (n = 0; n < 16; n++) { - cw = 3 & (col.lut >> (2 * n)); - dst[n] = p[cw]; - } + p[0] = decode_565(col.c0); + r0 = p[0].r; + g0 = p[0].g; + b0 = p[0].b; + p[1] = decode_565(col.c1); + r1 = p[1].r; + g1 = p[1].g; + b1 = p[1].b; + if (col.c0 > col.c1) { + p[2].r = (2 * r0 + 1 * r1) / 3; + p[2].g = (2 * g0 + 1 * g1) / 3; + p[2].b = (2 * b0 + 1 * b1) / 3; + p[2].a = 0xff; + p[3].r = (1 * r0 + 2 * r1) / 3; + p[3].g = (1 * g0 + 2 * g1) / 3; + p[3].b = (1 * b0 + 2 * b1) / 3; + p[3].a = 0xff; + } else { + p[2].r = (r0 + r1) / 2; + p[2].g = (g0 + g1) / 2; + p[2].b = (b0 + b1) / 2; + p[2].a = 0xff; + p[3].r = 0; + p[3].g = 0; + p[3].b = 0; + p[3].a = 0; + } + for (n = 0; n < 16; n++) { + cw = 3 & (col.lut >> (2 * n)); + dst[n] = p[cw]; + } } -static void decode_bc3_alpha(char *dst, const UINT8 *src, int stride, int o) { - bc3_alpha b; - UINT16 a0, a1; - UINT8 a[8]; - int n, lut, aw; - bc3_alpha_load(&b, src); +static void +decode_bc3_alpha(char *dst, const UINT8 *src, int stride, int o) { + bc3_alpha b; + UINT16 a0, a1; + UINT8 a[8]; + int n, lut, aw; + bc3_alpha_load(&b, src); - a0 = b.a0; - a1 = b.a1; - a[0] = (UINT8)a0; - a[1] = (UINT8)a1; - if (a0 > a1) { - a[2] = (6*a0 + 1*a1) / 7; - a[3] = (5*a0 + 2*a1) / 7; - a[4] = (4*a0 + 3*a1) / 7; - a[5] = (3*a0 + 4*a1) / 7; - a[6] = (2*a0 + 5*a1) / 7; - a[7] = (1*a0 + 6*a1) / 7; - } else { - a[2] = (4*a0 + 1*a1) / 5; - a[3] = (3*a0 + 2*a1) / 5; - a[4] = (2*a0 + 3*a1) / 5; - a[5] = (1*a0 + 4*a1) / 5; - a[6] = 0; - a[7] = 0xff; - } - lut = b.lut[0] | (b.lut[1] << 8) | (b.lut[2] << 16); - for (n = 0; n < 8; n++) { - aw = 7 & (lut >> (3 * n)); - dst[stride * n + o] = a[aw]; - } - lut = b.lut[3] | (b.lut[4] << 8) | (b.lut[5] << 16); - for (n = 0; n < 8; n++) { - aw = 7 & (lut >> (3 * n)); - dst[stride * (8+n) + o] = a[aw]; - } + a0 = b.a0; + a1 = b.a1; + a[0] = (UINT8)a0; + a[1] = (UINT8)a1; + if (a0 > a1) { + a[2] = (6 * a0 + 1 * a1) / 7; + a[3] = (5 * a0 + 2 * a1) / 7; + a[4] = (4 * a0 + 3 * a1) / 7; + a[5] = (3 * a0 + 4 * a1) / 7; + a[6] = (2 * a0 + 5 * a1) / 7; + a[7] = (1 * a0 + 6 * a1) / 7; + } else { + a[2] = (4 * a0 + 1 * a1) / 5; + a[3] = (3 * a0 + 2 * a1) / 5; + a[4] = (2 * a0 + 3 * a1) / 5; + a[5] = (1 * a0 + 4 * a1) / 5; + a[6] = 0; + a[7] = 0xff; + } + lut = b.lut[0] | (b.lut[1] << 8) | (b.lut[2] << 16); + for (n = 0; n < 8; n++) { + aw = 7 & (lut >> (3 * n)); + dst[stride * n + o] = a[aw]; + } + lut = b.lut[3] | (b.lut[4] << 8) | (b.lut[5] << 16); + for (n = 0; n < 8; n++) { + aw = 7 & (lut >> (3 * n)); + dst[stride * (8 + n) + o] = a[aw]; + } } -static void decode_bc1_block(rgba *col, const UINT8* src) { - decode_bc1_color(col, src); +static void +decode_bc1_block(rgba *col, const UINT8 *src) { + decode_bc1_color(col, src); } -static void decode_bc2_block(rgba *col, const UINT8* src) { - int n, bitI, byI, av; - decode_bc1_color(col, src + 8); - for (n = 0; n < 16; n++) { - bitI = n * 4; - byI = bitI >> 3; - av = 0xf & (src[byI] >> (bitI & 7)); - av = (av << 4) | av; - col[n].a = av; - } +static void +decode_bc2_block(rgba *col, const UINT8 *src) { + int n, bitI, byI, av; + decode_bc1_color(col, src + 8); + for (n = 0; n < 16; n++) { + bitI = n * 4; + byI = bitI >> 3; + av = 0xf & (src[byI] >> (bitI & 7)); + av = (av << 4) | av; + col[n].a = av; + } } -static void decode_bc3_block(rgba *col, const UINT8* src) { - decode_bc1_color(col, src + 8); - decode_bc3_alpha((char *)col, src, sizeof(col[0]), 3); +static void +decode_bc3_block(rgba *col, const UINT8 *src) { + decode_bc1_color(col, src + 8); + decode_bc3_alpha((char *)col, src, sizeof(col[0]), 3); } -static void decode_bc4_block(lum *col, const UINT8* src) { - decode_bc3_alpha((char *)col, src, sizeof(col[0]), 0); +static void +decode_bc4_block(lum *col, const UINT8 *src) { + decode_bc3_alpha((char *)col, src, sizeof(col[0]), 0); } -static void decode_bc5_block(rgba *col, const UINT8* src) { - decode_bc3_alpha((char *)col, src, sizeof(col[0]), 0); - decode_bc3_alpha((char *)col, src + 8, sizeof(col[0]), 1); +static void +decode_bc5_block(rgba *col, const UINT8 *src) { + decode_bc3_alpha((char *)col, src, sizeof(col[0]), 0); + decode_bc3_alpha((char *)col, src + 8, sizeof(col[0]), 1); } /* BC6 and BC7 are described here: - https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_texture_compression_bptc.txt */ + https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_texture_compression_bptc.txt + */ -static UINT8 get_bit(const UINT8* src, int bit) { - int by = bit >> 3; - bit &= 7; - return (src[by] >> bit) & 1; +static UINT8 +get_bit(const UINT8 *src, int bit) { + int by = bit >> 3; + bit &= 7; + return (src[by] >> bit) & 1; } -static UINT8 get_bits(const UINT8* src, int bit, int count) { - UINT8 v; - int x; - int by = bit >> 3; - bit &= 7; - if (!count) { - return 0; - } - if (bit + count <= 8) { - v = (src[by] >> bit) & ((1 << count) - 1); - } else { - x = src[by] | (src[by+1] << 8); - v = (x >> bit) & ((1 << count) - 1); - } - return v; +static UINT8 +get_bits(const UINT8 *src, int bit, int count) { + UINT8 v; + int x; + int by = bit >> 3; + bit &= 7; + if (!count) { + return 0; + } + if (bit + count <= 8) { + v = (src[by] >> bit) & ((1 << count) - 1); + } else { + x = src[by] | (src[by + 1] << 8); + v = (x >> bit) & ((1 << count) - 1); + } + return v; } /* BC7 */ typedef struct { - char ns; - char pb; - char rb; - char isb; - char cb; - char ab; - char epb; - char spb; - char ib; - char ib2; + char ns; + char pb; + char rb; + char isb; + char cb; + char ab; + char epb; + char spb; + char ib; + char ib2; } bc7_mode_info; static const bc7_mode_info bc7_modes[] = { - {3, 4, 0, 0, 4, 0, 1, 0, 3, 0}, - {2, 6, 0, 0, 6, 0, 0, 1, 3, 0}, - {3, 6, 0, 0, 5, 0, 0, 0, 2, 0}, - {2, 6, 0, 0, 7, 0, 1, 0, 2, 0}, - {1, 0, 2, 1, 5, 6, 0, 0, 2, 3}, - {1, 0, 2, 0, 7, 8, 0, 0, 2, 2}, - {1, 0, 0, 0, 7, 7, 1, 0, 4, 0}, - {2, 6, 0, 0, 5, 5, 1, 0, 2, 0} -}; + {3, 4, 0, 0, 4, 0, 1, 0, 3, 0}, + {2, 6, 0, 0, 6, 0, 0, 1, 3, 0}, + {3, 6, 0, 0, 5, 0, 0, 0, 2, 0}, + {2, 6, 0, 0, 7, 0, 1, 0, 2, 0}, + {1, 0, 2, 1, 5, 6, 0, 0, 2, 3}, + {1, 0, 2, 0, 7, 8, 0, 0, 2, 2}, + {1, 0, 0, 0, 7, 7, 1, 0, 4, 0}, + {2, 6, 0, 0, 5, 5, 1, 0, 2, 0}}; /* Subset indices: Table.P2, 1 bit per index */ static const UINT16 bc7_si2[] = { - 0xcccc, 0x8888, 0xeeee, 0xecc8, 0xc880, 0xfeec, 0xfec8, 0xec80, - 0xc800, 0xffec, 0xfe80, 0xe800, 0xffe8, 0xff00, 0xfff0, 0xf000, - 0xf710, 0x008e, 0x7100, 0x08ce, 0x008c, 0x7310, 0x3100, 0x8cce, - 0x088c, 0x3110, 0x6666, 0x366c, 0x17e8, 0x0ff0, 0x718e, 0x399c, - 0xaaaa, 0xf0f0, 0x5a5a, 0x33cc, 0x3c3c, 0x55aa, 0x9696, 0xa55a, - 0x73ce, 0x13c8, 0x324c, 0x3bdc, 0x6996, 0xc33c, 0x9966, 0x0660, - 0x0272, 0x04e4, 0x4e40, 0x2720, 0xc936, 0x936c, 0x39c6, 0x639c, - 0x9336, 0x9cc6, 0x817e, 0xe718, 0xccf0, 0x0fcc, 0x7744, 0xee22}; + 0xcccc, 0x8888, 0xeeee, 0xecc8, 0xc880, 0xfeec, 0xfec8, 0xec80, 0xc800, 0xffec, + 0xfe80, 0xe800, 0xffe8, 0xff00, 0xfff0, 0xf000, 0xf710, 0x008e, 0x7100, 0x08ce, + 0x008c, 0x7310, 0x3100, 0x8cce, 0x088c, 0x3110, 0x6666, 0x366c, 0x17e8, 0x0ff0, + 0x718e, 0x399c, 0xaaaa, 0xf0f0, 0x5a5a, 0x33cc, 0x3c3c, 0x55aa, 0x9696, 0xa55a, + 0x73ce, 0x13c8, 0x324c, 0x3bdc, 0x6996, 0xc33c, 0x9966, 0x0660, 0x0272, 0x04e4, + 0x4e40, 0x2720, 0xc936, 0x936c, 0x39c6, 0x639c, 0x9336, 0x9cc6, 0x817e, 0xe718, + 0xccf0, 0x0fcc, 0x7744, 0xee22}; /* Table.P3, 2 bits per index */ static const UINT32 bc7_si3[] = { - 0xaa685050, 0x6a5a5040, 0x5a5a4200, 0x5450a0a8, - 0xa5a50000, 0xa0a05050, 0x5555a0a0, 0x5a5a5050, - 0xaa550000, 0xaa555500, 0xaaaa5500, 0x90909090, - 0x94949494, 0xa4a4a4a4, 0xa9a59450, 0x2a0a4250, - 0xa5945040, 0x0a425054, 0xa5a5a500, 0x55a0a0a0, - 0xa8a85454, 0x6a6a4040, 0xa4a45000, 0x1a1a0500, - 0x0050a4a4, 0xaaa59090, 0x14696914, 0x69691400, - 0xa08585a0, 0xaa821414, 0x50a4a450, 0x6a5a0200, - 0xa9a58000, 0x5090a0a8, 0xa8a09050, 0x24242424, - 0x00aa5500, 0x24924924, 0x24499224, 0x50a50a50, - 0x500aa550, 0xaaaa4444, 0x66660000, 0xa5a0a5a0, - 0x50a050a0, 0x69286928, 0x44aaaa44, 0x66666600, - 0xaa444444, 0x54a854a8, 0x95809580, 0x96969600, - 0xa85454a8, 0x80959580, 0xaa141414, 0x96960000, - 0xaaaa1414, 0xa05050a0, 0xa0a5a5a0, 0x96000000, - 0x40804080, 0xa9a8a9a8, 0xaaaaaa44, 0x2a4a5254}; + 0xaa685050, 0x6a5a5040, 0x5a5a4200, 0x5450a0a8, 0xa5a50000, 0xa0a05050, 0x5555a0a0, + 0x5a5a5050, 0xaa550000, 0xaa555500, 0xaaaa5500, 0x90909090, 0x94949494, 0xa4a4a4a4, + 0xa9a59450, 0x2a0a4250, 0xa5945040, 0x0a425054, 0xa5a5a500, 0x55a0a0a0, 0xa8a85454, + 0x6a6a4040, 0xa4a45000, 0x1a1a0500, 0x0050a4a4, 0xaaa59090, 0x14696914, 0x69691400, + 0xa08585a0, 0xaa821414, 0x50a4a450, 0x6a5a0200, 0xa9a58000, 0x5090a0a8, 0xa8a09050, + 0x24242424, 0x00aa5500, 0x24924924, 0x24499224, 0x50a50a50, 0x500aa550, 0xaaaa4444, + 0x66660000, 0xa5a0a5a0, 0x50a050a0, 0x69286928, 0x44aaaa44, 0x66666600, 0xaa444444, + 0x54a854a8, 0x95809580, 0x96969600, 0xa85454a8, 0x80959580, 0xaa141414, 0x96960000, + 0xaaaa1414, 0xa05050a0, 0xa0a5a5a0, 0x96000000, 0x40804080, 0xa9a8a9a8, 0xaaaaaa44, + 0x2a4a5254}; /* Anchor indices: Table.A2 */ static const char bc7_ai0[] = { - 15,15,15,15,15,15,15,15, - 15,15,15,15,15,15,15,15, - 15, 2, 8, 2, 2, 8, 8,15, - 2, 8, 2, 2, 8, 8, 2, 2, - 15,15, 6, 8, 2, 8,15,15, - 2, 8, 2, 2, 2,15,15, 6, - 6, 2, 6, 8,15,15, 2, 2, - 15,15,15,15,15, 2, 2,15}; + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 2, 8, 2, 2, 8, + 8, 15, 2, 8, 2, 2, 8, 8, 2, 2, 15, 15, 6, 8, 2, 8, 15, 15, 2, 8, 2, 2, + 2, 15, 15, 6, 6, 2, 6, 8, 15, 15, 2, 2, 15, 15, 15, 15, 15, 2, 2, 15}; /* Table.A3a */ static const char bc7_ai1[] = { - 3, 3,15,15, 8, 3,15,15, - 8, 8, 6, 6, 6, 5, 3, 3, - 3, 3, 8,15, 3, 3, 6,10, - 5, 8, 8, 6, 8, 5,15,15, - 8,15, 3, 5, 6,10, 8,15, - 15, 3,15, 5,15,15,15,15, - 3,15, 5, 5, 5, 8, 5,10, - 5,10, 8,13,15,12, 3, 3}; + 3, 3, 15, 15, 8, 3, 15, 15, 8, 8, 6, 6, 6, 5, 3, 3, 3, 3, 8, 15, 3, 3, + 6, 10, 5, 8, 8, 6, 8, 5, 15, 15, 8, 15, 3, 5, 6, 10, 8, 15, 15, 3, 15, 5, + 15, 15, 15, 15, 3, 15, 5, 5, 5, 8, 5, 10, 5, 10, 8, 13, 15, 12, 3, 3}; /* Table.A3b */ -static const char bc7_ai2[] = { - 15, 8, 8, 3,15,15, 3, 8, - 15,15,15,15,15,15,15, 8, - 15, 8,15, 3,15, 8,15, 8, - 3,15, 6,10,15,15,10, 8, - 15, 3,15,10,10, 8, 9,10, - 6,15, 8,15, 3, 6, 6, 8, - 15, 3,15,15,15,15,15,15, - 15,15,15,15, 3,15,15, 8}; +static const char bc7_ai2[] = {15, 8, 8, 3, 15, 15, 3, 8, 15, 15, 15, 15, 15, + 15, 15, 8, 15, 8, 15, 3, 15, 8, 15, 8, 3, 15, + 6, 10, 15, 15, 10, 8, 15, 3, 15, 10, 10, 8, 9, + 10, 6, 15, 8, 15, 3, 6, 6, 8, 15, 3, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 3, 15, 15, 8}; /* Interpolation weights */ static const char bc7_weights2[] = {0, 21, 43, 64}; static const char bc7_weights3[] = {0, 9, 18, 27, 37, 46, 55, 64}; static const char bc7_weights4[] = { - 0, 4, 9, 13, 17, 21, 26, 30, 34, 38, 43, 47, 51, 55, 60, 64}; + 0, 4, 9, 13, 17, 21, 26, 30, 34, 38, 43, 47, 51, 55, 60, 64}; -static const char *bc7_get_weights(int n) { - if (n == 2) { - return bc7_weights2; - } - if (n == 3) { - return bc7_weights3; - } - return bc7_weights4; +static const char * +bc7_get_weights(int n) { + if (n == 2) { + return bc7_weights2; + } + if (n == 3) { + return bc7_weights3; + } + return bc7_weights4; } -static int bc7_get_subset(int ns, int partition, int n) { - if (ns == 2) { - return 1 & (bc7_si2[partition] >> n); - } - if (ns == 3) { - return 3 & (bc7_si3[partition] >> (2 * n)); - } - return 0; +static int +bc7_get_subset(int ns, int partition, int n) { + if (ns == 2) { + return 1 & (bc7_si2[partition] >> n); + } + if (ns == 3) { + return 3 & (bc7_si3[partition] >> (2 * n)); + } + return 0; } -static UINT8 expand_quantized(UINT8 v, int bits) { - v = v << (8 - bits); - return v | (v >> bits); +static UINT8 +expand_quantized(UINT8 v, int bits) { + v = v << (8 - bits); + return v | (v >> bits); } -static void bc7_lerp(rgba *dst, const rgba *e, int s0, int s1) { - int t0 = 64 - s0; - int t1 = 64 - s1; - dst->r = (UINT8)((t0 * e[0].r + s0 * e[1].r + 32) >> 6); - dst->g = (UINT8)((t0 * e[0].g + s0 * e[1].g + 32) >> 6); - dst->b = (UINT8)((t0 * e[0].b + s0 * e[1].b + 32) >> 6); - dst->a = (UINT8)((t1 * e[0].a + s1 * e[1].a + 32) >> 6); +static void +bc7_lerp(rgba *dst, const rgba *e, int s0, int s1) { + int t0 = 64 - s0; + int t1 = 64 - s1; + dst->r = (UINT8)((t0 * e[0].r + s0 * e[1].r + 32) >> 6); + dst->g = (UINT8)((t0 * e[0].g + s0 * e[1].g + 32) >> 6); + dst->b = (UINT8)((t0 * e[0].b + s0 * e[1].b + 32) >> 6); + dst->a = (UINT8)((t1 * e[0].a + s1 * e[1].a + 32) >> 6); } -static void decode_bc7_block(rgba *col, const UINT8* src) { - rgba endpoints[6]; - int bit = 0, cibit, aibit; - int mode = src[0]; - int i, j; - int numep, cb, ab, ib, ib2, i0, i1, s; - UINT8 index_sel, partition, rotation, val; - const char *cw, *aw; - const bc7_mode_info *info; +static void +decode_bc7_block(rgba *col, const UINT8 *src) { + rgba endpoints[6]; + int bit = 0, cibit, aibit; + int mode = src[0]; + int i, j; + int numep, cb, ab, ib, ib2, i0, i1, s; + UINT8 index_sel, partition, rotation, val; + const char *cw, *aw; + const bc7_mode_info *info; - /* mode is the number of unset bits before the first set bit: */ - if (!mode) { - /* degenerate case when no bits set */ - for (i = 0; i < 16; i++) { - col[i].r = col[i].g = col[i].b = 0; - col[i].a = 255; - } - return; - } - while (!(mode & (1 << bit++))) ; - mode = bit - 1; - info = &bc7_modes[mode]; - /* color selection bits: {subset}{endpoint} */ - cb = info->cb; - ab = info->ab; - cw = bc7_get_weights(info->ib); - aw = bc7_get_weights((ab && info->ib2) ? info->ib2 : info->ib); + /* mode is the number of unset bits before the first set bit: */ + if (!mode) { + /* degenerate case when no bits set */ + for (i = 0; i < 16; i++) { + col[i].r = col[i].g = col[i].b = 0; + col[i].a = 255; + } + return; + } + while (!(mode & (1 << bit++))) + ; + mode = bit - 1; + info = &bc7_modes[mode]; + /* color selection bits: {subset}{endpoint} */ + cb = info->cb; + ab = info->ab; + cw = bc7_get_weights(info->ib); + aw = bc7_get_weights((ab && info->ib2) ? info->ib2 : info->ib); -#define LOAD(DST, N) \ - DST = get_bits(src, bit, N); \ - bit += N; - LOAD(partition, info->pb); - LOAD(rotation, info->rb); - LOAD(index_sel, info->isb); - numep = info->ns << 1; +#define LOAD(DST, N) \ + DST = get_bits(src, bit, N); \ + bit += N; + LOAD(partition, info->pb); + LOAD(rotation, info->rb); + LOAD(index_sel, info->isb); + numep = info->ns << 1; - /* red */ - for (i = 0; i < numep; i++) { - LOAD(val, cb); - endpoints[i].r = val; - } + /* red */ + for (i = 0; i < numep; i++) { + LOAD(val, cb); + endpoints[i].r = val; + } - /* green */ - for (i = 0; i < numep; i++) { - LOAD(val, cb); - endpoints[i].g = val; - } + /* green */ + for (i = 0; i < numep; i++) { + LOAD(val, cb); + endpoints[i].g = val; + } - /* blue */ - for (i = 0; i < numep; i++) { - LOAD(val, cb); - endpoints[i].b = val; - } + /* blue */ + for (i = 0; i < numep; i++) { + LOAD(val, cb); + endpoints[i].b = val; + } - /* alpha */ - for (i = 0; i < numep; i++) { - if (ab) { - LOAD(val, ab); - } else { - val = 255; - } - endpoints[i].a = val; - } + /* alpha */ + for (i = 0; i < numep; i++) { + if (ab) { + LOAD(val, ab); + } else { + val = 255; + } + endpoints[i].a = val; + } - /* p-bits */ + /* p-bits */ #define ASSIGN_P(x) x = (x << 1) | val - if (info->epb) { - /* per endpoint */ - cb++; - if (ab) { - ab++; - } - for (i = 0; i < numep; i++) { - LOAD(val, 1); - ASSIGN_P(endpoints[i].r); - ASSIGN_P(endpoints[i].g); - ASSIGN_P(endpoints[i].b); - if (ab) { - ASSIGN_P(endpoints[i].a); - } - } - } - if (info->spb) { - /* per subset */ - cb++; - if (ab) { - ab++; - } - for (i = 0; i < numep; i+=2) { - LOAD(val, 1); - for (j = 0; j < 2; j++) { - ASSIGN_P(endpoints[i+j].r); - ASSIGN_P(endpoints[i+j].g); - ASSIGN_P(endpoints[i+j].b); - if (ab) { - ASSIGN_P(endpoints[i+j].a); - } - } - } - } + if (info->epb) { + /* per endpoint */ + cb++; + if (ab) { + ab++; + } + for (i = 0; i < numep; i++) { + LOAD(val, 1); + ASSIGN_P(endpoints[i].r); + ASSIGN_P(endpoints[i].g); + ASSIGN_P(endpoints[i].b); + if (ab) { + ASSIGN_P(endpoints[i].a); + } + } + } + if (info->spb) { + /* per subset */ + cb++; + if (ab) { + ab++; + } + for (i = 0; i < numep; i += 2) { + LOAD(val, 1); + for (j = 0; j < 2; j++) { + ASSIGN_P(endpoints[i + j].r); + ASSIGN_P(endpoints[i + j].g); + ASSIGN_P(endpoints[i + j].b); + if (ab) { + ASSIGN_P(endpoints[i + j].a); + } + } + } + } #undef ASSIGN_P #define EXPAND(x, b) x = expand_quantized(x, b) - for (i = 0; i < numep; i++) { - EXPAND(endpoints[i].r, cb); - EXPAND(endpoints[i].g, cb); - EXPAND(endpoints[i].b, cb); - if (ab) { - EXPAND(endpoints[i].a, ab); - } - } + for (i = 0; i < numep; i++) { + EXPAND(endpoints[i].r, cb); + EXPAND(endpoints[i].g, cb); + EXPAND(endpoints[i].b, cb); + if (ab) { + EXPAND(endpoints[i].a, ab); + } + } #undef EXPAND #undef LOAD - cibit = bit; - aibit = cibit + 16 * info->ib - info->ns; - for (i = 0; i < 16; i++) { - s = bc7_get_subset(info->ns, partition, i) << 1; - ib = info->ib; - if (i == 0) { - ib--; - } else if (info->ns == 2) { - if (i == bc7_ai0[partition]) { - ib--; - } - } else if (info->ns == 3) { - if (i == bc7_ai1[partition]) { - ib--; - } else if (i == bc7_ai2[partition]) { - ib--; - } - } - i0 = get_bits(src, cibit, ib); - cibit += ib; + cibit = bit; + aibit = cibit + 16 * info->ib - info->ns; + for (i = 0; i < 16; i++) { + s = bc7_get_subset(info->ns, partition, i) << 1; + ib = info->ib; + if (i == 0) { + ib--; + } else if (info->ns == 2) { + if (i == bc7_ai0[partition]) { + ib--; + } + } else if (info->ns == 3) { + if (i == bc7_ai1[partition]) { + ib--; + } else if (i == bc7_ai2[partition]) { + ib--; + } + } + i0 = get_bits(src, cibit, ib); + cibit += ib; - if (ab && info->ib2) { - ib2 = info->ib2; - if (ib2 && i == 0) { - ib2--; - } - i1 = get_bits(src, aibit, ib2); - aibit += ib2; - if (index_sel) { - bc7_lerp(&col[i], &endpoints[s], aw[i1], cw[i0]); - } else { - bc7_lerp(&col[i], &endpoints[s], cw[i0], aw[i1]); - } - } else { - bc7_lerp(&col[i], &endpoints[s], cw[i0], cw[i0]); - } + if (ab && info->ib2) { + ib2 = info->ib2; + if (ib2 && i == 0) { + ib2--; + } + i1 = get_bits(src, aibit, ib2); + aibit += ib2; + if (index_sel) { + bc7_lerp(&col[i], &endpoints[s], aw[i1], cw[i0]); + } else { + bc7_lerp(&col[i], &endpoints[s], cw[i0], aw[i1]); + } + } else { + bc7_lerp(&col[i], &endpoints[s], cw[i0], cw[i0]); + } #define ROTATE(x, y) \ - val = x; \ - x = y; \ - y = val - if (rotation == 1) { - ROTATE(col[i].r, col[i].a); - } else if (rotation == 2) { - ROTATE(col[i].g, col[i].a); - } else if (rotation == 3) { - ROTATE(col[i].b, col[i].a); - } + val = x; \ + x = y; \ + y = val + if (rotation == 1) { + ROTATE(col[i].r, col[i].a); + } else if (rotation == 2) { + ROTATE(col[i].g, col[i].a); + } else if (rotation == 3) { + ROTATE(col[i].b, col[i].a); + } #undef ROTATE - } + } } /* BC6 */ typedef struct { - char ns; /* number of subsets (also called regions) */ - char tr; /* whether endpoints are delta-compressed */ - char pb; /* partition bits */ - char epb; /* endpoint bits */ - char rb; /* red bits (delta) */ - char gb; /* green bits (delta) */ - char bb; /* blue bits (delta) */ + char ns; /* number of subsets (also called regions) */ + char tr; /* whether endpoints are delta-compressed */ + char pb; /* partition bits */ + char epb; /* endpoint bits */ + char rb; /* red bits (delta) */ + char gb; /* green bits (delta) */ + char bb; /* blue bits (delta) */ } bc6_mode_info; static const bc6_mode_info bc6_modes[] = { - // 00 - {2, 1, 5, 10, 5, 5, 5}, - // 01 - {2, 1, 5, 7, 6, 6, 6}, - // 10 - {2, 1, 5, 11, 5, 4, 4}, - {2, 1, 5, 11, 4, 5, 4}, - {2, 1, 5, 11, 4, 4, 5}, - {2, 1, 5, 9, 5, 5, 5}, - {2, 1, 5, 8, 6, 5, 5}, - {2, 1, 5, 8, 5, 6, 5}, - {2, 1, 5, 8, 5, 5, 6}, - {2, 0, 5, 6, 6, 6, 6}, - // 11 - {1, 0, 0, 10, 10, 10, 10}, - {1, 1, 0, 11, 9, 9, 9}, - {1, 1, 0, 12, 8, 8, 8}, - {1, 1, 0, 16, 4, 4, 4} -}; + // 00 + {2, 1, 5, 10, 5, 5, 5}, + // 01 + {2, 1, 5, 7, 6, 6, 6}, + // 10 + {2, 1, 5, 11, 5, 4, 4}, + {2, 1, 5, 11, 4, 5, 4}, + {2, 1, 5, 11, 4, 4, 5}, + {2, 1, 5, 9, 5, 5, 5}, + {2, 1, 5, 8, 6, 5, 5}, + {2, 1, 5, 8, 5, 6, 5}, + {2, 1, 5, 8, 5, 5, 6}, + {2, 0, 5, 6, 6, 6, 6}, + // 11 + {1, 0, 0, 10, 10, 10, 10}, + {1, 1, 0, 11, 9, 9, 9}, + {1, 1, 0, 12, 8, 8, 8}, + {1, 1, 0, 16, 4, 4, 4}}; /* Table.F, encoded as a sequence of bit indices */ static const UINT8 bc6_bit_packings[][75] = { - {116, 132, 176, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 16, 17, 18, 19, 20, 21, 22, - 23, 24, 25, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 48, 49, 50, 51, 52, - 164, 112, 113, 114, 115, 64, 65, 66, 67, 68, 172, 160, 161, 162, 163, 80, - 81, 82, 83, 84, 173, 128, 129, 130, 131, 96, 97, 98, 99, 100, 174, 144, - 145, 146, 147, 148, 175}, - {117, 164, 165, 0, 1, 2, 3, 4, 5, 6, 172, 173, 132, 16, 17, 18, 19, 20, 21, - 22, 133, 174, 116, 32, 33, 34, 35, 36, 37, 38, 175, 177, 176, 48, 49, 50, - 51, 52, 53, 112, 113, 114, 115, 64, 65, 66, 67, 68, 69, 160, 161, 162, 163, - 80, 81, 82, 83, 84, 85, 128, 129, 130, 131, 96, 97, 98, 99, 100, 101, 144, - 145, 146, 147, 148, 149}, - {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 32, - 33, 34, 35, 36, 37, 38, 39, 40, 41, 48, 49, 50, 51, 52, 10, 112, 113, 114, - 115, 64, 65, 66, 67, 26, 172, 160, 161, 162, 163, 80, 81, 82, 83, 42, 173, - 128, 129, 130, 131, 96, 97, 98, 99, 100, 174, 144, 145, 146, 147, 148, - 175}, - {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 32, - 33, 34, 35, 36, 37, 38, 39, 40, 41, 48, 49, 50, 51, 10, 164, 112, 113, 114, - 115, 64, 65, 66, 67, 68, 26, 160, 161, 162, 163, 80, 81, 82, 83, 42, 173, - 128, 129, 130, 131, 96, 97, 98, 99, 172, 174, 144, 145, 146, 147, 116, - 175}, - {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 32, - 33, 34, 35, 36, 37, 38, 39, 40, 41, 48, 49, 50, 51, 10, 132, 112, 113, 114, - 115, 64, 65, 66, 67, 26, 172, 160, 161, 162, 163, 80, 81, 82, 83, 84, 42, - 128, 129, 130, 131, 96, 97, 98, 99, 173, 174, 144, 145, 146, 147, 176, - 175}, - {0, 1, 2, 3, 4, 5, 6, 7, 8, 132, 16, 17, 18, 19, 20, 21, 22, 23, 24, 116, - 32, 33, 34, 35, 36, 37, 38, 39, 40, 176, 48, 49, 50, 51, 52, 164, 112, 113, - 114, 115, 64, 65, 66, 67, 68, 172, 160, 161, 162, 163, 80, 81, 82, 83, 84, - 173, 128, 129, 130, 131, 96, 97, 98, 99, 100, 174, 144, 145, 146, 147, 148, - 175}, - {0, 1, 2, 3, 4, 5, 6, 7, 164, 132, 16, 17, 18, 19, 20, 21, 22, 23, 174, 116, - 32, 33, 34, 35, 36, 37, 38, 39, 175, 176, 48, 49, 50, 51, 52, 53, 112, 113, - 114, 115, 64, 65, 66, 67, 68, 172, 160, 161, 162, 163, 80, 81, 82, 83, 84, - 173, 128, 129, 130, 131, 96, 97, 98, 99, 100, 101, 144, 145, 146, 147, 148, - 149}, - {0, 1, 2, 3, 4, 5, 6, 7, 172, 132, 16, 17, 18, 19, 20, 21, 22, 23, 117, 116, - 32, 33, 34, 35, 36, 37, 38, 39, 165, 176, 48, 49, 50, 51, 52, 164, 112, - 113, 114, 115, 64, 65, 66, 67, 68, 69, 160, 161, 162, 163, 80, 81, 82, 83, - 84, 173, 128, 129, 130, 131, 96, 97, 98, 99, 100, 174, 144, 145, 146, 147, - 148, 175}, - {0, 1, 2, 3, 4, 5, 6, 7, 173, 132, 16, 17, 18, 19, 20, 21, 22, 23, 133, 116, - 32, 33, 34, 35, 36, 37, 38, 39, 177, 176, 48, 49, 50, 51, 52, 164, 112, - 113, 114, 115, 64, 65, 66, 67, 68, 172, 160, 161, 162, 163, 80, 81, 82, 83, - 84, 85, 128, 129, 130, 131, 96, 97, 98, 99, 100, 174, 144, 145, 146, 147, - 148, 175}, - {0, 1, 2, 3, 4, 5, 164, 172, 173, 132, 16, 17, 18, 19, 20, 21, 117, 133, - 174, 116, 32, 33, 34, 35, 36, 37, 165, 175, 177, 176, 48, 49, 50, 51, 52, - 53, 112, 113, 114, 115, 64, 65, 66, 67, 68, 69, 160, 161, 162, 163, 80, 81, - 82, 83, 84, 85, 128, 129, 130, 131, 96, 97, 98, 99, 100, 101, 144, 145, - 146, 147, 148, 149}, - {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 32, - 33, 34, 35, 36, 37, 38, 39, 40, 41, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, - 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 80, 81, 82, 83, 84, 85, 86, 87, 88, - 89}, - {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 32, - 33, 34, 35, 36, 37, 38, 39, 40, 41, 48, 49, 50, 51, 52, 53, 54, 55, 56, 10, - 64, 65, 66, 67, 68, 69, 70, 71, 72, 26, 80, 81, 82, 83, 84, 85, 86, 87, 88, - 42}, - {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 32, - 33, 34, 35, 36, 37, 38, 39, 40, 41, 48, 49, 50, 51, 52, 53, 54, 55, 11, 10, - 64, 65, 66, 67, 68, 69, 70, 71, 27, 26, 80, 81, 82, 83, 84, 85, 86, 87, 43, - 42}, - {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 32, - 33, 34, 35, 36, 37, 38, 39, 40, 41, 48, 49, 50, 51, 15, 14, 13, 12, 11, 10, - 64, 65, 66, 67, 31, 30, 29, 28, 27, 26, 80, 81, 82, 83, 47, 46, 45, 44, 43, - 42}}; + {116, 132, 176, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 16, 17, + 18, 19, 20, 21, 22, 23, 24, 25, 32, 33, 34, 35, 36, 37, 38, + 39, 40, 41, 48, 49, 50, 51, 52, 164, 112, 113, 114, 115, 64, 65, + 66, 67, 68, 172, 160, 161, 162, 163, 80, 81, 82, 83, 84, 173, 128, + 129, 130, 131, 96, 97, 98, 99, 100, 174, 144, 145, 146, 147, 148, 175}, + {117, 164, 165, 0, 1, 2, 3, 4, 5, 6, 172, 173, 132, 16, 17, + 18, 19, 20, 21, 22, 133, 174, 116, 32, 33, 34, 35, 36, 37, 38, + 175, 177, 176, 48, 49, 50, 51, 52, 53, 112, 113, 114, 115, 64, 65, + 66, 67, 68, 69, 160, 161, 162, 163, 80, 81, 82, 83, 84, 85, 128, + 129, 130, 131, 96, 97, 98, 99, 100, 101, 144, 145, 146, 147, 148, 149}, + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 16, 17, 18, 19, 20, + 21, 22, 23, 24, 25, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, + 48, 49, 50, 51, 52, 10, 112, 113, 114, 115, 64, 65, 66, 67, 26, + 172, 160, 161, 162, 163, 80, 81, 82, 83, 42, 173, 128, 129, 130, 131, + 96, 97, 98, 99, 100, 174, 144, 145, 146, 147, 148, 175}, + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 16, 17, 18, 19, 20, + 21, 22, 23, 24, 25, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, + 48, 49, 50, 51, 10, 164, 112, 113, 114, 115, 64, 65, 66, 67, 68, + 26, 160, 161, 162, 163, 80, 81, 82, 83, 42, 173, 128, 129, 130, 131, + 96, 97, 98, 99, 172, 174, 144, 145, 146, 147, 116, 175}, + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 16, 17, 18, 19, 20, + 21, 22, 23, 24, 25, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, + 48, 49, 50, 51, 10, 132, 112, 113, 114, 115, 64, 65, 66, 67, 26, + 172, 160, 161, 162, 163, 80, 81, 82, 83, 84, 42, 128, 129, 130, 131, + 96, 97, 98, 99, 173, 174, 144, 145, 146, 147, 176, 175}, + {0, 1, 2, 3, 4, 5, 6, 7, 8, 132, 16, 17, 18, 19, 20, + 21, 22, 23, 24, 116, 32, 33, 34, 35, 36, 37, 38, 39, 40, 176, + 48, 49, 50, 51, 52, 164, 112, 113, 114, 115, 64, 65, 66, 67, 68, + 172, 160, 161, 162, 163, 80, 81, 82, 83, 84, 173, 128, 129, 130, 131, + 96, 97, 98, 99, 100, 174, 144, 145, 146, 147, 148, 175}, + {0, 1, 2, 3, 4, 5, 6, 7, 164, 132, 16, 17, 18, 19, 20, + 21, 22, 23, 174, 116, 32, 33, 34, 35, 36, 37, 38, 39, 175, 176, + 48, 49, 50, 51, 52, 53, 112, 113, 114, 115, 64, 65, 66, 67, 68, + 172, 160, 161, 162, 163, 80, 81, 82, 83, 84, 173, 128, 129, 130, 131, + 96, 97, 98, 99, 100, 101, 144, 145, 146, 147, 148, 149}, + {0, 1, 2, 3, 4, 5, 6, 7, 172, 132, 16, 17, 18, 19, 20, + 21, 22, 23, 117, 116, 32, 33, 34, 35, 36, 37, 38, 39, 165, 176, + 48, 49, 50, 51, 52, 164, 112, 113, 114, 115, 64, 65, 66, 67, 68, + 69, 160, 161, 162, 163, 80, 81, 82, 83, 84, 173, 128, 129, 130, 131, + 96, 97, 98, 99, 100, 174, 144, 145, 146, 147, 148, 175}, + {0, 1, 2, 3, 4, 5, 6, 7, 173, 132, 16, 17, 18, 19, 20, + 21, 22, 23, 133, 116, 32, 33, 34, 35, 36, 37, 38, 39, 177, 176, + 48, 49, 50, 51, 52, 164, 112, 113, 114, 115, 64, 65, 66, 67, 68, + 172, 160, 161, 162, 163, 80, 81, 82, 83, 84, 85, 128, 129, 130, 131, + 96, 97, 98, 99, 100, 174, 144, 145, 146, 147, 148, 175}, + {0, 1, 2, 3, 4, 5, 164, 172, 173, 132, 16, 17, 18, 19, 20, + 21, 117, 133, 174, 116, 32, 33, 34, 35, 36, 37, 165, 175, 177, 176, + 48, 49, 50, 51, 52, 53, 112, 113, 114, 115, 64, 65, 66, 67, 68, + 69, 160, 161, 162, 163, 80, 81, 82, 83, 84, 85, 128, 129, 130, 131, + 96, 97, 98, 99, 100, 101, 144, 145, 146, 147, 148, 149}, + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, + 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89}, + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 48, 49, 50, 51, 52, 53, 54, 55, 56, 10, + 64, 65, 66, 67, 68, 69, 70, 71, 72, 26, 80, 81, 82, 83, 84, 85, 86, 87, 88, 42}, + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 48, 49, 50, 51, 52, 53, 54, 55, 11, 10, + 64, 65, 66, 67, 68, 69, 70, 71, 27, 26, 80, 81, 82, 83, 84, 85, 86, 87, 43, 42}, + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 48, 49, 50, 51, 15, 14, 13, 12, 11, 10, + 64, 65, 66, 67, 31, 30, 29, 28, 27, 26, 80, 81, 82, 83, 47, 46, 45, 44, 43, 42}}; -static void bc6_sign_extend(UINT16 *v, int prec) { - int x = *v; - if (x & (1 << (prec - 1))) { - x |= -1 << prec; - } - *v = (UINT16)x; +static void +bc6_sign_extend(UINT16 *v, int prec) { + int x = *v; + if (x & (1 << (prec - 1))) { + x |= -1 << prec; + } + *v = (UINT16)x; } -static int bc6_unquantize(UINT16 v, int prec, int sign) { - int s = 0; - int x; - if (!sign) { - x = v; - if (prec >= 15) return x; - if (x == 0) return 0; - if (x == ((1 << prec) - 1)) { - return 0xffff; - } - return ((x << 15) + 0x4000) >> (prec - 1); - } else { - x = (INT16)v; - if (prec >= 16) return x; - if (x < 0) { - s = 1; - x = -x; - } +static int +bc6_unquantize(UINT16 v, int prec, int sign) { + int s = 0; + int x; + if (!sign) { + x = v; + if (prec >= 15) { + return x; + } + if (x == 0) { + return 0; + } + if (x == ((1 << prec) - 1)) { + return 0xffff; + } + return ((x << 15) + 0x4000) >> (prec - 1); + } else { + x = (INT16)v; + if (prec >= 16) { + return x; + } + if (x < 0) { + s = 1; + x = -x; + } - if (x != 0) { - if (x >= ((1 << (prec - 1)) - 1)) { - x = 0x7fff; - } else { - x = ((x << 15) + 0x4000) >> (prec - 1); - } - } + if (x != 0) { + if (x >= ((1 << (prec - 1)) - 1)) { + x = 0x7fff; + } else { + x = ((x << 15) + 0x4000) >> (prec - 1); + } + } - if (s) { - return -x; - } - return x; - } + if (s) { + return -x; + } + return x; + } } -static float half_to_float(UINT16 h) { - /* https://gist.github.com/rygorous/2144712 */ - union { - UINT32 u; - float f; - } o, m; - m.u = 0x77800000; - o.u = (h & 0x7fff) << 13; - o.f *= m.f; - m.u = 0x47800000; - if (o.f >= m.f) { - o.u |= 255 << 23; - } - o.u |= (h & 0x8000) << 16; - return o.f; +static float +half_to_float(UINT16 h) { + /* https://gist.github.com/rygorous/2144712 */ + union { + UINT32 u; + float f; + } o, m; + m.u = 0x77800000; + o.u = (h & 0x7fff) << 13; + o.f *= m.f; + m.u = 0x47800000; + if (o.f >= m.f) { + o.u |= 255 << 23; + } + o.u |= (h & 0x8000) << 16; + return o.f; } -static float bc6_finalize(int v, int sign) { - if (sign) { - if (v < 0) { - v = ((-v) * 31) / 32; - return half_to_float((UINT16)(0x8000 | v)); - } else { - return half_to_float((UINT16)((v * 31) / 32)); - } - } else { - return half_to_float((UINT16)((v * 31) / 64)); - } +static float +bc6_finalize(int v, int sign) { + if (sign) { + if (v < 0) { + v = ((-v) * 31) / 32; + return half_to_float((UINT16)(0x8000 | v)); + } else { + return half_to_float((UINT16)((v * 31) / 32)); + } + } else { + return half_to_float((UINT16)((v * 31) / 64)); + } } -static void bc6_lerp(rgb32f *col, int *e0, int *e1, int s, int sign) { - int r, g, b; - int t = 64 - s; - r = (e0[0] * t + e1[0] * s) >> 6; - g = (e0[1] * t + e1[1] * s) >> 6; - b = (e0[2] * t + e1[2] * s) >> 6; - col->r = bc6_finalize(r, sign); - col->g = bc6_finalize(g, sign); - col->b = bc6_finalize(b, sign); +static void +bc6_lerp(rgb32f *col, int *e0, int *e1, int s, int sign) { + int r, g, b; + int t = 64 - s; + r = (e0[0] * t + e1[0] * s) >> 6; + g = (e0[1] * t + e1[1] * s) >> 6; + b = (e0[2] * t + e1[2] * s) >> 6; + col->r = bc6_finalize(r, sign); + col->g = bc6_finalize(g, sign); + col->b = bc6_finalize(b, sign); } -static void decode_bc6_block(rgb32f *col, const UINT8* src, int sign) { - UINT16 endpoints[12]; /* storage for r0, g0, b0, r1, ... */ - int ueps[12]; - int i, i0, ib2, di, dw, mask, numep, s; - UINT8 partition; - const bc6_mode_info *info; - const char *cw; - int bit = 5; - int epbits = 75; - int ib = 3; - int mode = src[0] & 0x1f; - if ((mode & 3) == 0 || (mode & 3) == 1) { - mode &= 3; - bit = 2; - } else if ((mode & 3) == 2) { - mode = 2 + (mode >> 2); - epbits = 72; - } else { - mode = 10 + (mode >> 2); - epbits = 60; - ib = 4; - } - if (mode >= 14) { - /* invalid block */ - memset(col, 0, 16 * sizeof(col[0])); - return; - } - info = &bc6_modes[mode]; - cw = bc7_get_weights(ib); - numep = info->ns == 2 ? 12 : 6; - for (i = 0; i < 12; i++) { - endpoints[i] = 0; - } - for (i = 0; i < epbits; i++) { - di = bc6_bit_packings[mode][i]; - dw = di >> 4; - di &= 15; - endpoints[dw] |= (UINT16)get_bit(src, bit + i) << di; - } - bit += epbits; - partition = get_bits(src, bit, info->pb); - bit += info->pb; - mask = (1 << info->epb) - 1; - if (sign) { /* sign-extend e0 if signed */ - bc6_sign_extend(&endpoints[0], info->epb); - bc6_sign_extend(&endpoints[1], info->epb); - bc6_sign_extend(&endpoints[2], info->epb); - } - if (sign || info->tr) { /* sign-extend e1,2,3 if signed or deltas */ - for (i = 3; i < numep; i += 3) { - bc6_sign_extend(&endpoints[i+0], info->rb); - bc6_sign_extend(&endpoints[i+1], info->gb); - bc6_sign_extend(&endpoints[i+2], info->bb); - } - } - if (info->tr) { /* apply deltas */ - for (i = 3; i < numep; i++) { - endpoints[i] = (endpoints[i] + endpoints[0]) & mask; - } - if (sign) { - for (i = 3; i < numep; i += 3) { - bc6_sign_extend(&endpoints[i+0], info->rb); - bc6_sign_extend(&endpoints[i+1], info->gb); - bc6_sign_extend(&endpoints[i+2], info->bb); - } - } - } - for (i = 0; i < numep; i++) { - ueps[i] = bc6_unquantize(endpoints[i], info->epb, sign); - } - for (i = 0; i < 16; i++) { - s = bc7_get_subset(info->ns, partition, i) * 6; - ib2 = ib; - if (i == 0) { - ib2--; - } else if (info->ns == 2) { - if (i == bc7_ai0[partition]) { - ib2--; - } - } - i0 = get_bits(src, bit, ib2); - bit += ib2; +static void +decode_bc6_block(rgb32f *col, const UINT8 *src, int sign) { + UINT16 endpoints[12]; /* storage for r0, g0, b0, r1, ... */ + int ueps[12]; + int i, i0, ib2, di, dw, mask, numep, s; + UINT8 partition; + const bc6_mode_info *info; + const char *cw; + int bit = 5; + int epbits = 75; + int ib = 3; + int mode = src[0] & 0x1f; + if ((mode & 3) == 0 || (mode & 3) == 1) { + mode &= 3; + bit = 2; + } else if ((mode & 3) == 2) { + mode = 2 + (mode >> 2); + epbits = 72; + } else { + mode = 10 + (mode >> 2); + epbits = 60; + ib = 4; + } + if (mode >= 14) { + /* invalid block */ + memset(col, 0, 16 * sizeof(col[0])); + return; + } + info = &bc6_modes[mode]; + cw = bc7_get_weights(ib); + numep = info->ns == 2 ? 12 : 6; + for (i = 0; i < 12; i++) { + endpoints[i] = 0; + } + for (i = 0; i < epbits; i++) { + di = bc6_bit_packings[mode][i]; + dw = di >> 4; + di &= 15; + endpoints[dw] |= (UINT16)get_bit(src, bit + i) << di; + } + bit += epbits; + partition = get_bits(src, bit, info->pb); + bit += info->pb; + mask = (1 << info->epb) - 1; + if (sign) { /* sign-extend e0 if signed */ + bc6_sign_extend(&endpoints[0], info->epb); + bc6_sign_extend(&endpoints[1], info->epb); + bc6_sign_extend(&endpoints[2], info->epb); + } + if (sign || info->tr) { /* sign-extend e1,2,3 if signed or deltas */ + for (i = 3; i < numep; i += 3) { + bc6_sign_extend(&endpoints[i + 0], info->rb); + bc6_sign_extend(&endpoints[i + 1], info->gb); + bc6_sign_extend(&endpoints[i + 2], info->bb); + } + } + if (info->tr) { /* apply deltas */ + for (i = 3; i < numep; i++) { + endpoints[i] = (endpoints[i] + endpoints[0]) & mask; + } + if (sign) { + for (i = 3; i < numep; i += 3) { + bc6_sign_extend(&endpoints[i + 0], info->rb); + bc6_sign_extend(&endpoints[i + 1], info->gb); + bc6_sign_extend(&endpoints[i + 2], info->bb); + } + } + } + for (i = 0; i < numep; i++) { + ueps[i] = bc6_unquantize(endpoints[i], info->epb, sign); + } + for (i = 0; i < 16; i++) { + s = bc7_get_subset(info->ns, partition, i) * 6; + ib2 = ib; + if (i == 0) { + ib2--; + } else if (info->ns == 2) { + if (i == bc7_ai0[partition]) { + ib2--; + } + } + i0 = get_bits(src, bit, ib2); + bit += ib2; - bc6_lerp(&col[i], &ueps[s], &ueps[s+3], cw[i0], sign); - } + bc6_lerp(&col[i], &ueps[s], &ueps[s + 3], cw[i0], sign); + } } -static void put_block(Imaging im, ImagingCodecState state, const char *col, int sz, int C) { - int width = state->xsize; - int height = state->ysize; - int xmax = width + state->xoff; - int ymax = height + state->yoff; - int j, i, y, x; - char *dst; - for (j = 0; j < 4; j++) { - y = state->y + j; - if (C) { - if (y >= height) { - continue; - } - if (state->ystep < 0) { - y = state->yoff + ymax - y - 1; - } - dst = im->image[y]; - for (i = 0; i < 4; i++) { - x = state->x + i; - if (x >= width) { - continue; - } - memcpy(dst + sz*x, col + sz*(j*4 + i), sz); - } - } else { - if (state->ystep < 0) { - y = state->yoff + ymax - y - 1; - } - x = state->x; - dst = im->image[y] + sz*x; - memcpy(dst, col + sz*(j*4), 4 * sz); - } - } - state->x += 4; - if (state->x >= xmax) { - state->y += 4; - state->x = state->xoff; - } +static void +put_block(Imaging im, ImagingCodecState state, const char *col, int sz, int C) { + int width = state->xsize; + int height = state->ysize; + int xmax = width + state->xoff; + int ymax = height + state->yoff; + int j, i, y, x; + char *dst; + for (j = 0; j < 4; j++) { + y = state->y + j; + if (C) { + if (y >= height) { + continue; + } + if (state->ystep < 0) { + y = state->yoff + ymax - y - 1; + } + dst = im->image[y]; + for (i = 0; i < 4; i++) { + x = state->x + i; + if (x >= width) { + continue; + } + memcpy(dst + sz * x, col + sz * (j * 4 + i), sz); + } + } else { + if (state->ystep < 0) { + y = state->yoff + ymax - y - 1; + } + x = state->x; + dst = im->image[y] + sz * x; + memcpy(dst, col + sz * (j * 4), 4 * sz); + } + } + state->x += 4; + if (state->x >= xmax) { + state->y += 4; + state->x = state->xoff; + } } -static int decode_bcn(Imaging im, ImagingCodecState state, const UINT8* src, int bytes, int N, int C) { - int ymax = state->ysize + state->yoff; - const UINT8 *ptr = src; - switch (N) { -#define DECODE_LOOP(NN, SZ, TY, ...) \ - case NN: \ - while (bytes >= SZ) { \ - TY col[16]; \ - memset(col, 0, 16 * sizeof(col[0])); \ - decode_bc##NN##_block(col, ptr); \ - put_block(im, state, (const char *)col, sizeof(col[0]), C); \ - ptr += SZ; \ - bytes -= SZ; \ - if (state->y >= ymax) return -1; \ - } \ - break - DECODE_LOOP(1, 8, rgba); - DECODE_LOOP(2, 16, rgba); - DECODE_LOOP(3, 16, rgba); - DECODE_LOOP(4, 8, lum); - DECODE_LOOP(5, 16, rgba); - case 6: - while (bytes >= 16) { - rgb32f col[16]; - decode_bc6_block(col, ptr, (state->state >> 4) & 1); - put_block(im, state, (const char *)col, sizeof(col[0]), C); - ptr += 16; - bytes -= 16; - if (state->y >= ymax) return -1; \ - } - break; - DECODE_LOOP(7, 16, rgba); +static int +decode_bcn( + Imaging im, ImagingCodecState state, const UINT8 *src, int bytes, int N, int C) { + int ymax = state->ysize + state->yoff; + const UINT8 *ptr = src; + switch (N) { +#define DECODE_LOOP(NN, SZ, TY, ...) \ + case NN: \ + while (bytes >= SZ) { \ + TY col[16]; \ + memset(col, 0, 16 * sizeof(col[0])); \ + decode_bc##NN##_block(col, ptr); \ + put_block(im, state, (const char *)col, sizeof(col[0]), C); \ + ptr += SZ; \ + bytes -= SZ; \ + if (state->y >= ymax) { \ + return -1; \ + } \ + } \ + break + + DECODE_LOOP(1, 8, rgba); + DECODE_LOOP(2, 16, rgba); + DECODE_LOOP(3, 16, rgba); + DECODE_LOOP(4, 8, lum); + DECODE_LOOP(5, 16, rgba); + case 6: + while (bytes >= 16) { + rgb32f col[16]; + decode_bc6_block(col, ptr, (state->state >> 4) & 1); + put_block(im, state, (const char *)col, sizeof(col[0]), C); + ptr += 16; + bytes -= 16; + if (state->y >= ymax) { + return -1; + } + } + break; + DECODE_LOOP(7, 16, rgba); #undef DECODE_LOOP - } - return (int)(ptr - src); + } + return (int)(ptr - src); } -int ImagingBcnDecode(Imaging im, ImagingCodecState state, UINT8* buf, Py_ssize_t bytes) { - int N = state->state & 0xf; - int width = state->xsize; - int height = state->ysize; - if ((width & 3) | (height & 3)) { - return decode_bcn(im, state, buf, bytes, N, 1); - } else { - return decode_bcn(im, state, buf, bytes, N, 0); - } +int +ImagingBcnDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t bytes) { + int N = state->state & 0xf; + int width = state->xsize; + int height = state->ysize; + if ((width & 3) | (height & 3)) { + return decode_bcn(im, state, buf, bytes, N, 1); + } else { + return decode_bcn(im, state, buf, bytes, N, 0); + } } diff --git a/src/libImaging/Bit.h b/src/libImaging/Bit.h index 56e3a17d2..f64bfb469 100644 --- a/src/libImaging/Bit.h +++ b/src/libImaging/Bit.h @@ -1,7 +1,6 @@ /* Bit.h */ typedef struct { - /* CONFIGURATION */ /* Number of bits per pixel */ @@ -19,7 +18,7 @@ typedef struct { /* Lookup table (not implemented) */ unsigned long lutsize; - FLOAT32* lut; + FLOAT32 *lut; /* INTERNAL */ unsigned long mask; diff --git a/src/libImaging/BitDecode.c b/src/libImaging/BitDecode.c index 7120b3321..28baa8b7e 100644 --- a/src/libImaging/BitDecode.c +++ b/src/libImaging/BitDecode.c @@ -5,7 +5,7 @@ * decoder for packed bitfields (converts to floating point) * * history: - * 97-05-31 fl created (much more than originally intended) + * 97-05-31 fl created (much more than originally intended) * * Copyright (c) Fredrik Lundh 1997. * Copyright (c) Secret Labs AB 1997. @@ -13,113 +13,112 @@ * See the README file for information on usage and redistribution. */ - #include "Imaging.h" #include "Bit.h" - int -ImagingBitDecode(Imaging im, ImagingCodecState state, UINT8* buf, Py_ssize_t bytes) -{ - BITSTATE* bitstate = state->context; - UINT8* ptr; +ImagingBitDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t bytes) { + BITSTATE *bitstate = state->context; + UINT8 *ptr; if (state->state == 0) { - - /* Initialize context variables */ + /* Initialize context variables */ /* this decoder only works for float32 image buffers */ if (im->type != IMAGING_TYPE_FLOAT32) { - state->errcode = IMAGING_CODEC_CONFIG; - return -1; + state->errcode = IMAGING_CODEC_CONFIG; + return -1; } /* sanity check */ if (bitstate->bits < 1 || bitstate->bits >= 32) { - state->errcode = IMAGING_CODEC_CONFIG; - return -1; + state->errcode = IMAGING_CODEC_CONFIG; + return -1; } - bitstate->mask = (1<bits)-1; + bitstate->mask = (1 << bitstate->bits) - 1; - if (bitstate->sign) - bitstate->signmask = (1<<(bitstate->bits-1)); + if (bitstate->sign) { + bitstate->signmask = (1 << (bitstate->bits - 1)); + } - /* check image orientation */ - if (state->ystep < 0) { - state->y = state->ysize-1; - state->ystep = -1; - } else - state->ystep = 1; - - state->state = 1; + /* check image orientation */ + if (state->ystep < 0) { + state->y = state->ysize - 1; + state->ystep = -1; + } else { + state->ystep = 1; + } + state->state = 1; } ptr = buf; while (bytes > 0) { - UINT8 byte = *ptr; ptr++; bytes--; /* get a byte from the input stream and insert in the bit buffer */ - if (bitstate->fill&1) + if (bitstate->fill & 1) { /* fill MSB first */ - bitstate->bitbuffer |= (unsigned long) byte << bitstate->bitcount; - else + bitstate->bitbuffer |= (unsigned long)byte << bitstate->bitcount; + } else { /* fill LSB first */ bitstate->bitbuffer = (bitstate->bitbuffer << 8) | byte; + } bitstate->bitcount += 8; while (bitstate->bitcount >= bitstate->bits) { - /* get a pixel from the bit buffer */ unsigned long data; FLOAT32 pixel; - if (bitstate->fill&2) { + if (bitstate->fill & 2) { /* store LSB first */ data = bitstate->bitbuffer & bitstate->mask; - if (bitstate->bitcount > 32) + if (bitstate->bitcount > 32) { /* bitbuffer overflow; restore it from last input byte */ - bitstate->bitbuffer = byte >> (8 - (bitstate->bitcount - - bitstate->bits)); - else + bitstate->bitbuffer = + byte >> (8 - (bitstate->bitcount - bitstate->bits)); + } else { bitstate->bitbuffer >>= bitstate->bits; - } else + } + } else { /* store MSB first */ - data = (bitstate->bitbuffer >> (bitstate->bitcount - - bitstate->bits)) - & bitstate->mask; + data = (bitstate->bitbuffer >> (bitstate->bitcount - bitstate->bits)) & + bitstate->mask; + } bitstate->bitcount -= bitstate->bits; if (bitstate->lutsize > 0) { /* map through lookup table */ - if (data <= 0) + if (data <= 0) { pixel = bitstate->lut[0]; - else if (data >= bitstate->lutsize) - pixel = bitstate->lut[bitstate->lutsize-1]; - else + } else if (data >= bitstate->lutsize) { + pixel = bitstate->lut[bitstate->lutsize - 1]; + } else { pixel = bitstate->lut[data]; + } } else { /* convert */ - if (data & bitstate->signmask) + if (data & bitstate->signmask) { /* image memory contains signed data */ - pixel = (FLOAT32) (INT32) (data | ~bitstate->mask); - else - pixel = (FLOAT32) data; + pixel = (FLOAT32)(INT32)(data | ~bitstate->mask); + } else { + pixel = (FLOAT32)data; + } } - *(FLOAT32*)(&im->image32[state->y][state->x]) = pixel; + *(FLOAT32 *)(&im->image32[state->y][state->x]) = pixel; /* step forward */ - if (++state->x >= state->xsize) { + if (++state->x >= state->xsize) { /* new line */ state->y += state->ystep; if (state->y < 0 || state->y >= state->ysize) { @@ -128,8 +127,9 @@ ImagingBitDecode(Imaging im, ImagingCodecState state, UINT8* buf, Py_ssize_t byt } state->x = 0; /* reset bit buffer */ - if (bitstate->pad > 0) + if (bitstate->pad > 0) { bitstate->bitcount = 0; + } } } } diff --git a/src/libImaging/Blend.c b/src/libImaging/Blend.c index 19a080d6d..a53ae0fad 100644 --- a/src/libImaging/Blend.c +++ b/src/libImaging/Blend.c @@ -5,9 +5,9 @@ * interpolate between two existing images * * history: - * 96-03-20 fl Created - * 96-05-18 fl Simplified blend expression - * 96-10-05 fl Fixed expression bug, special case for interpolation + * 96-03-20 fl Created + * 96-05-18 fl Simplified blend expression + * 96-10-05 fl Fixed expression bug, special case for interpolation * * Copyright (c) Fredrik Lundh 1996. * Copyright (c) Secret Labs AB 1997. @@ -15,65 +15,64 @@ * See the README file for details on usage and redistribution. */ - #include "Imaging.h" - Imaging -ImagingBlend(Imaging imIn1, Imaging imIn2, float alpha) -{ +ImagingBlend(Imaging imIn1, Imaging imIn2, float alpha) { Imaging imOut; int x, y; /* Check arguments */ - if (!imIn1 || !imIn2 || imIn1->type != IMAGING_TYPE_UINT8 - || imIn1->palette || strcmp(imIn1->mode, "1") == 0 - || imIn2->palette || strcmp(imIn2->mode, "1") == 0) + if (!imIn1 || !imIn2 || imIn1->type != IMAGING_TYPE_UINT8 || imIn1->palette || + strcmp(imIn1->mode, "1") == 0 || imIn2->palette || + strcmp(imIn2->mode, "1") == 0) { return ImagingError_ModeError(); + } - if (imIn1->type != imIn2->type || - imIn1->bands != imIn2->bands || - imIn1->xsize != imIn2->xsize || - imIn1->ysize != imIn2->ysize) - return ImagingError_Mismatch(); + if (imIn1->type != imIn2->type || imIn1->bands != imIn2->bands || + imIn1->xsize != imIn2->xsize || imIn1->ysize != imIn2->ysize) { + return ImagingError_Mismatch(); + } /* Shortcuts */ - if (alpha == 0.0) - return ImagingCopy(imIn1); - else if (alpha == 1.0) - return ImagingCopy(imIn2); + if (alpha == 0.0) { + return ImagingCopy(imIn1); + } else if (alpha == 1.0) { + return ImagingCopy(imIn2); + } imOut = ImagingNewDirty(imIn1->mode, imIn1->xsize, imIn1->ysize); - if (!imOut) - return NULL; + if (!imOut) { + return NULL; + } if (alpha >= 0 && alpha <= 1.0) { - /* Interpolate between bands */ - for (y = 0; y < imIn1->ysize; y++) { - UINT8* in1 = (UINT8*) imIn1->image[y]; - UINT8* in2 = (UINT8*) imIn2->image[y]; - UINT8* out = (UINT8*) imOut->image[y]; - for (x = 0; x < imIn1->linesize; x++) - out[x] = (UINT8) - ((int) in1[x] + alpha * ((int) in2[x] - (int) in1[x])); - } + /* Interpolate between bands */ + for (y = 0; y < imIn1->ysize; y++) { + UINT8 *in1 = (UINT8 *)imIn1->image[y]; + UINT8 *in2 = (UINT8 *)imIn2->image[y]; + UINT8 *out = (UINT8 *)imOut->image[y]; + for (x = 0; x < imIn1->linesize; x++) { + out[x] = (UINT8)((int)in1[x] + alpha * ((int)in2[x] - (int)in1[x])); + } + } } else { - /* Extrapolation; must make sure to clip resulting values */ - for (y = 0; y < imIn1->ysize; y++) { - UINT8* in1 = (UINT8*) imIn1->image[y]; - UINT8* in2 = (UINT8*) imIn2->image[y]; - UINT8* out = (UINT8*) imOut->image[y]; - for (x = 0; x < imIn1->linesize; x++) { - float temp = (float) - ((int) in1[x] + alpha * ((int) in2[x] - (int) in1[x])); - if (temp <= 0.0) - out[x] = 0; - else if (temp >= 255.0) - out[x] = 255; - else - out[x] = (UINT8) temp; - } - } + /* Extrapolation; must make sure to clip resulting values */ + for (y = 0; y < imIn1->ysize; y++) { + UINT8 *in1 = (UINT8 *)imIn1->image[y]; + UINT8 *in2 = (UINT8 *)imIn2->image[y]; + UINT8 *out = (UINT8 *)imOut->image[y]; + for (x = 0; x < imIn1->linesize; x++) { + float temp = (float)((int)in1[x] + alpha * ((int)in2[x] - (int)in1[x])); + if (temp <= 0.0) { + out[x] = 0; + } else if (temp >= 255.0) { + out[x] = 255; + } else { + out[x] = (UINT8)temp; + } + } + } } return imOut; diff --git a/src/libImaging/BoxBlur.c b/src/libImaging/BoxBlur.c index 9537c4f98..88862eb73 100644 --- a/src/libImaging/BoxBlur.c +++ b/src/libImaging/BoxBlur.c @@ -1,37 +1,40 @@ #include "Imaging.h" - #define MAX(x, y) (((x) > (y)) ? (x) : (y)) #define MIN(x, y) (((x) < (y)) ? (x) : (y)) - typedef UINT8 pixel[4]; -void static inline -ImagingLineBoxBlur32(pixel *lineOut, pixel *lineIn, int lastx, int radius, int edgeA, - int edgeB, UINT32 ww, UINT32 fw) -{ +void static inline ImagingLineBoxBlur32( + pixel *lineOut, + pixel *lineIn, + int lastx, + int radius, + int edgeA, + int edgeB, + UINT32 ww, + UINT32 fw) { int x; UINT32 acc[4]; UINT32 bulk[4]; - #define MOVE_ACC(acc, subtract, add) \ - acc[0] += lineIn[add][0] - lineIn[subtract][0]; \ - acc[1] += lineIn[add][1] - lineIn[subtract][1]; \ - acc[2] += lineIn[add][2] - lineIn[subtract][2]; \ - acc[3] += lineIn[add][3] - lineIn[subtract][3]; +#define MOVE_ACC(acc, subtract, add) \ + acc[0] += lineIn[add][0] - lineIn[subtract][0]; \ + acc[1] += lineIn[add][1] - lineIn[subtract][1]; \ + acc[2] += lineIn[add][2] - lineIn[subtract][2]; \ + acc[3] += lineIn[add][3] - lineIn[subtract][3]; - #define ADD_FAR(bulk, acc, left, right) \ - bulk[0] = (acc[0] * ww) + (lineIn[left][0] + lineIn[right][0]) * fw; \ - bulk[1] = (acc[1] * ww) + (lineIn[left][1] + lineIn[right][1]) * fw; \ - bulk[2] = (acc[2] * ww) + (lineIn[left][2] + lineIn[right][2]) * fw; \ - bulk[3] = (acc[3] * ww) + (lineIn[left][3] + lineIn[right][3]) * fw; +#define ADD_FAR(bulk, acc, left, right) \ + bulk[0] = (acc[0] * ww) + (lineIn[left][0] + lineIn[right][0]) * fw; \ + bulk[1] = (acc[1] * ww) + (lineIn[left][1] + lineIn[right][1]) * fw; \ + bulk[2] = (acc[2] * ww) + (lineIn[left][2] + lineIn[right][2]) * fw; \ + bulk[3] = (acc[3] * ww) + (lineIn[left][3] + lineIn[right][3]) * fw; - #define SAVE(x, bulk) \ - lineOut[x][0] = (UINT8)((bulk[0] + (1 << 23)) >> 24); \ - lineOut[x][1] = (UINT8)((bulk[1] + (1 << 23)) >> 24); \ - lineOut[x][2] = (UINT8)((bulk[2] + (1 << 23)) >> 24); \ - lineOut[x][3] = (UINT8)((bulk[3] + (1 << 23)) >> 24); +#define SAVE(x, bulk) \ + lineOut[x][0] = (UINT8)((bulk[0] + (1 << 23)) >> 24); \ + lineOut[x][1] = (UINT8)((bulk[1] + (1 << 23)) >> 24); \ + lineOut[x][2] = (UINT8)((bulk[2] + (1 << 23)) >> 24); \ + lineOut[x][3] = (UINT8)((bulk[3] + (1 << 23)) >> 24); /* Compute acc for -1 pixel (outside of image): From "-radius-1" to "-1" get first pixel, @@ -53,8 +56,7 @@ ImagingLineBoxBlur32(pixel *lineOut, pixel *lineIn, int lastx, int radius, int e acc[2] += lineIn[lastx][2] * (radius - edgeA + 1); acc[3] += lineIn[lastx][3] * (radius - edgeA + 1); - if (edgeA <= edgeB) - { + if (edgeA <= edgeB) { /* Subtract pixel from left ("0"). Add pixels from radius. */ for (x = 0; x < edgeA; x++) { @@ -76,9 +78,7 @@ ImagingLineBoxBlur32(pixel *lineOut, pixel *lineIn, int lastx, int radius, int e ADD_FAR(bulk, acc, x - radius - 1, lastx); SAVE(x, bulk); } - } - else - { + } else { for (x = 0; x < edgeB; x++) { MOVE_ACC(acc, 0, x + radius); ADD_FAR(bulk, acc, 0, x + radius + 1); @@ -96,28 +96,30 @@ ImagingLineBoxBlur32(pixel *lineOut, pixel *lineIn, int lastx, int radius, int e } } - #undef MOVE_ACC - #undef ADD_FAR - #undef SAVE +#undef MOVE_ACC +#undef ADD_FAR +#undef SAVE } - -void static inline -ImagingLineBoxBlur8(UINT8 *lineOut, UINT8 *lineIn, int lastx, int radius, int edgeA, - int edgeB, UINT32 ww, UINT32 fw) -{ +void static inline ImagingLineBoxBlur8( + UINT8 *lineOut, + UINT8 *lineIn, + int lastx, + int radius, + int edgeA, + int edgeB, + UINT32 ww, + UINT32 fw) { int x; UINT32 acc; UINT32 bulk; - #define MOVE_ACC(acc, subtract, add) \ - acc += lineIn[add] - lineIn[subtract]; +#define MOVE_ACC(acc, subtract, add) acc += lineIn[add] - lineIn[subtract]; - #define ADD_FAR(bulk, acc, left, right) \ - bulk = (acc * ww) + (lineIn[left] + lineIn[right]) * fw; +#define ADD_FAR(bulk, acc, left, right) \ + bulk = (acc * ww) + (lineIn[left] + lineIn[right]) * fw; - #define SAVE(x, bulk) \ - lineOut[x] = (UINT8)((bulk + (1 << 23)) >> 24) +#define SAVE(x, bulk) lineOut[x] = (UINT8)((bulk + (1 << 23)) >> 24) acc = lineIn[0] * (radius + 1); for (x = 0; x < edgeA - 1; x++) { @@ -125,8 +127,7 @@ ImagingLineBoxBlur8(UINT8 *lineOut, UINT8 *lineIn, int lastx, int radius, int ed } acc += lineIn[lastx] * (radius - edgeA + 1); - if (edgeA <= edgeB) - { + if (edgeA <= edgeB) { for (x = 0; x < edgeA; x++) { MOVE_ACC(acc, 0, x + radius); ADD_FAR(bulk, acc, 0, x + radius + 1); @@ -142,9 +143,7 @@ ImagingLineBoxBlur8(UINT8 *lineOut, UINT8 *lineIn, int lastx, int radius, int ed ADD_FAR(bulk, acc, x - radius - 1, lastx); SAVE(x, bulk); } - } - else - { + } else { for (x = 0; x < edgeB; x++) { MOVE_ACC(acc, 0, x + radius); ADD_FAR(bulk, acc, 0, x + radius + 1); @@ -162,61 +161,60 @@ ImagingLineBoxBlur8(UINT8 *lineOut, UINT8 *lineIn, int lastx, int radius, int ed } } - #undef MOVE_ACC - #undef ADD_FAR - #undef SAVE +#undef MOVE_ACC +#undef ADD_FAR +#undef SAVE } - - Imaging -ImagingHorizontalBoxBlur(Imaging imOut, Imaging imIn, float floatRadius) -{ +ImagingHorizontalBoxBlur(Imaging imOut, Imaging imIn, float floatRadius) { ImagingSectionCookie cookie; int y; - int radius = (int) floatRadius; - UINT32 ww = (UINT32) (1 << 24) / (floatRadius * 2 + 1); + int radius = (int)floatRadius; + UINT32 ww = (UINT32)(1 << 24) / (floatRadius * 2 + 1); UINT32 fw = ((1 << 24) - (radius * 2 + 1) * ww) / 2; int edgeA = MIN(radius + 1, imIn->xsize); int edgeB = MAX(imIn->xsize - radius - 1, 0); UINT32 *lineOut = calloc(imIn->xsize, sizeof(UINT32)); - if (lineOut == NULL) + if (lineOut == NULL) { return ImagingError_MemoryError(); + } // printf(">>> %d %d %d\n", radius, ww, fw); ImagingSectionEnter(&cookie); - if (imIn->image8) - { + if (imIn->image8) { for (y = 0; y < imIn->ysize; y++) { ImagingLineBoxBlur8( - (imIn == imOut ? (UINT8 *) lineOut : imOut->image8[y]), + (imIn == imOut ? (UINT8 *)lineOut : imOut->image8[y]), imIn->image8[y], imIn->xsize - 1, - radius, edgeA, edgeB, - ww, fw - ); + radius, + edgeA, + edgeB, + ww, + fw); if (imIn == imOut) { // Commit. memcpy(imOut->image8[y], lineOut, imIn->xsize); } } - } - else - { + } else { for (y = 0; y < imIn->ysize; y++) { ImagingLineBoxBlur32( - imIn == imOut ? (pixel *) lineOut : (pixel *) imOut->image32[y], - (pixel *) imIn->image32[y], + imIn == imOut ? (pixel *)lineOut : (pixel *)imOut->image32[y], + (pixel *)imIn->image32[y], imIn->xsize - 1, - radius, edgeA, edgeB, - ww, fw - ); + radius, + edgeA, + edgeB, + ww, + fw); if (imIn == imOut) { // Commit. memcpy(imOut->image32[y], lineOut, imIn->xsize * 4); @@ -231,55 +229,49 @@ ImagingHorizontalBoxBlur(Imaging imOut, Imaging imIn, float floatRadius) return imOut; } - Imaging -ImagingBoxBlur(Imaging imOut, Imaging imIn, float radius, int n) -{ +ImagingBoxBlur(Imaging imOut, Imaging imIn, float radius, int n) { int i; Imaging imTransposed; if (n < 1) { - return ImagingError_ValueError( - "number of passes must be greater than zero" - ); + return ImagingError_ValueError("number of passes must be greater than zero"); } - if (strcmp(imIn->mode, imOut->mode) || - imIn->type != imOut->type || - imIn->bands != imOut->bands || - imIn->xsize != imOut->xsize || - imIn->ysize != imOut->ysize) + if (strcmp(imIn->mode, imOut->mode) || imIn->type != imOut->type || + imIn->bands != imOut->bands || imIn->xsize != imOut->xsize || + imIn->ysize != imOut->ysize) { return ImagingError_Mismatch(); + } - if (imIn->type != IMAGING_TYPE_UINT8) + if (imIn->type != IMAGING_TYPE_UINT8) { return ImagingError_ModeError(); + } - if (!(strcmp(imIn->mode, "RGB") == 0 || - strcmp(imIn->mode, "RGBA") == 0 || - strcmp(imIn->mode, "RGBa") == 0 || - strcmp(imIn->mode, "RGBX") == 0 || - strcmp(imIn->mode, "CMYK") == 0 || - strcmp(imIn->mode, "L") == 0 || - strcmp(imIn->mode, "LA") == 0 || - strcmp(imIn->mode, "La") == 0)) + if (!(strcmp(imIn->mode, "RGB") == 0 || strcmp(imIn->mode, "RGBA") == 0 || + strcmp(imIn->mode, "RGBa") == 0 || strcmp(imIn->mode, "RGBX") == 0 || + strcmp(imIn->mode, "CMYK") == 0 || strcmp(imIn->mode, "L") == 0 || + strcmp(imIn->mode, "LA") == 0 || strcmp(imIn->mode, "La") == 0)) { return ImagingError_ModeError(); + } imTransposed = ImagingNewDirty(imIn->mode, imIn->ysize, imIn->xsize); - if (!imTransposed) + if (!imTransposed) { return NULL; + } /* Apply blur in one dimension. Use imOut as a destination at first pass, then use imOut as a source too. */ ImagingHorizontalBoxBlur(imOut, imIn, radius); - for (i = 1; i < n; i ++) { + for (i = 1; i < n; i++) { ImagingHorizontalBoxBlur(imOut, imOut, radius); } /* Transpose result for blur in another direction. */ ImagingTranspose(imTransposed, imOut); /* Reuse imTransposed as a source and destination there. */ - for (i = 0; i < n; i ++) { + for (i = 0; i < n; i++) { ImagingHorizontalBoxBlur(imTransposed, imTransposed, radius); } /* Restore original orientation. */ @@ -290,10 +282,8 @@ ImagingBoxBlur(Imaging imOut, Imaging imIn, float radius, int n) return imOut; } - -Imaging ImagingGaussianBlur(Imaging imOut, Imaging imIn, float radius, - int passes) -{ +Imaging +ImagingGaussianBlur(Imaging imOut, Imaging imIn, float radius, int passes) { float sigma2, L, l, a; sigma2 = radius * radius / passes; diff --git a/src/libImaging/Chops.c b/src/libImaging/Chops.c index 8059b6ffb..f9c005efe 100644 --- a/src/libImaging/Chops.c +++ b/src/libImaging/Chops.c @@ -16,58 +16,60 @@ * See the README file for details on usage and redistribution. */ - #include "Imaging.h" -#define CHOP(operation, mode)\ - int x, y;\ - Imaging imOut;\ - imOut = create(imIn1, imIn2, mode);\ - if (!imOut)\ - return NULL;\ - for (y = 0; y < imOut->ysize; y++) {\ - UINT8* out = (UINT8*) imOut->image[y];\ - UINT8* in1 = (UINT8*) imIn1->image[y];\ - UINT8* in2 = (UINT8*) imIn2->image[y];\ - for (x = 0; x < imOut->linesize; x++) {\ - int temp = operation;\ - if (temp <= 0)\ - out[x] = 0;\ - else if (temp >= 255)\ - out[x] = 255;\ - else\ - out[x] = temp;\ - }\ - }\ +#define CHOP(operation) \ + int x, y; \ + Imaging imOut; \ + imOut = create(imIn1, imIn2, NULL); \ + if (!imOut) { \ + return NULL; \ + } \ + for (y = 0; y < imOut->ysize; y++) { \ + UINT8 *out = (UINT8 *)imOut->image[y]; \ + UINT8 *in1 = (UINT8 *)imIn1->image[y]; \ + UINT8 *in2 = (UINT8 *)imIn2->image[y]; \ + for (x = 0; x < imOut->linesize; x++) { \ + int temp = operation; \ + if (temp <= 0) { \ + out[x] = 0; \ + } else if (temp >= 255) { \ + out[x] = 255; \ + } else { \ + out[x] = temp; \ + } \ + } \ + } \ return imOut; -#define CHOP2(operation, mode)\ - int x, y;\ - Imaging imOut;\ - imOut = create(imIn1, imIn2, mode);\ - if (!imOut)\ - return NULL;\ - for (y = 0; y < imOut->ysize; y++) {\ - UINT8* out = (UINT8*) imOut->image[y];\ - UINT8* in1 = (UINT8*) imIn1->image[y];\ - UINT8* in2 = (UINT8*) imIn2->image[y];\ - for (x = 0; x < imOut->linesize; x++) {\ - out[x] = operation;\ - }\ - }\ +#define CHOP2(operation, mode) \ + int x, y; \ + Imaging imOut; \ + imOut = create(imIn1, imIn2, mode); \ + if (!imOut) { \ + return NULL; \ + } \ + for (y = 0; y < imOut->ysize; y++) { \ + UINT8 *out = (UINT8 *)imOut->image[y]; \ + UINT8 *in1 = (UINT8 *)imIn1->image[y]; \ + UINT8 *in2 = (UINT8 *)imIn2->image[y]; \ + for (x = 0; x < imOut->linesize; x++) { \ + out[x] = operation; \ + } \ + } \ return imOut; static Imaging -create(Imaging im1, Imaging im2, char* mode) -{ +create(Imaging im1, Imaging im2, char *mode) { int xsize, ysize; if (!im1 || !im2 || im1->type != IMAGING_TYPE_UINT8 || - (mode != NULL && (strcmp(im1->mode, "1") || strcmp(im2->mode, "1")))) - return (Imaging) ImagingError_ModeError(); - if (im1->type != im2->type || - im1->bands != im2->bands) - return (Imaging) ImagingError_Mismatch(); + (mode != NULL && (strcmp(im1->mode, "1") || strcmp(im2->mode, "1")))) { + return (Imaging)ImagingError_ModeError(); + } + if (im1->type != im2->type || im1->bands != im2->bands) { + return (Imaging)ImagingError_Mismatch(); + } xsize = (im1->xsize < im2->xsize) ? im1->xsize : im2->xsize; ysize = (im1->ysize < im2->ysize) ? im1->ysize : im2->ysize; @@ -76,73 +78,85 @@ create(Imaging im1, Imaging im2, char* mode) } Imaging -ImagingChopLighter(Imaging imIn1, Imaging imIn2) -{ - CHOP((in1[x] > in2[x]) ? in1[x] : in2[x], NULL); +ImagingChopLighter(Imaging imIn1, Imaging imIn2) { + CHOP((in1[x] > in2[x]) ? in1[x] : in2[x]); } Imaging -ImagingChopDarker(Imaging imIn1, Imaging imIn2) -{ - CHOP((in1[x] < in2[x]) ? in1[x] : in2[x], NULL); +ImagingChopDarker(Imaging imIn1, Imaging imIn2) { + CHOP((in1[x] < in2[x]) ? in1[x] : in2[x]); } Imaging -ImagingChopDifference(Imaging imIn1, Imaging imIn2) -{ - CHOP(abs((int) in1[x] - (int) in2[x]), NULL); +ImagingChopDifference(Imaging imIn1, Imaging imIn2) { + CHOP(abs((int)in1[x] - (int)in2[x])); } Imaging -ImagingChopMultiply(Imaging imIn1, Imaging imIn2) -{ - CHOP((int) in1[x] * (int) in2[x] / 255, NULL); +ImagingChopMultiply(Imaging imIn1, Imaging imIn2) { + CHOP((int)in1[x] * (int)in2[x] / 255); } Imaging -ImagingChopScreen(Imaging imIn1, Imaging imIn2) -{ - CHOP(255 - ((int) (255 - in1[x]) * (int) (255 - in2[x])) / 255, NULL); +ImagingChopScreen(Imaging imIn1, Imaging imIn2) { + CHOP(255 - ((int)(255 - in1[x]) * (int)(255 - in2[x])) / 255); } Imaging -ImagingChopAdd(Imaging imIn1, Imaging imIn2, float scale, int offset) -{ - CHOP(((int) in1[x] + (int) in2[x]) / scale + offset, NULL); +ImagingChopAdd(Imaging imIn1, Imaging imIn2, float scale, int offset) { + CHOP(((int)in1[x] + (int)in2[x]) / scale + offset); } Imaging -ImagingChopSubtract(Imaging imIn1, Imaging imIn2, float scale, int offset) -{ - CHOP(((int) in1[x] - (int) in2[x]) / scale + offset, NULL); +ImagingChopSubtract(Imaging imIn1, Imaging imIn2, float scale, int offset) { + CHOP(((int)in1[x] - (int)in2[x]) / scale + offset); } Imaging -ImagingChopAnd(Imaging imIn1, Imaging imIn2) -{ +ImagingChopAnd(Imaging imIn1, Imaging imIn2) { CHOP2((in1[x] && in2[x]) ? 255 : 0, "1"); } Imaging -ImagingChopOr(Imaging imIn1, Imaging imIn2) -{ +ImagingChopOr(Imaging imIn1, Imaging imIn2) { CHOP2((in1[x] || in2[x]) ? 255 : 0, "1"); } Imaging -ImagingChopXor(Imaging imIn1, Imaging imIn2) -{ +ImagingChopXor(Imaging imIn1, Imaging imIn2) { CHOP2(((in1[x] != 0) ^ (in2[x] != 0)) ? 255 : 0, "1"); } Imaging -ImagingChopAddModulo(Imaging imIn1, Imaging imIn2) -{ +ImagingChopAddModulo(Imaging imIn1, Imaging imIn2) { CHOP2(in1[x] + in2[x], NULL); } Imaging -ImagingChopSubtractModulo(Imaging imIn1, Imaging imIn2) -{ +ImagingChopSubtractModulo(Imaging imIn1, Imaging imIn2) { CHOP2(in1[x] - in2[x], NULL); } + +Imaging +ImagingChopSoftLight(Imaging imIn1, Imaging imIn2) { + CHOP2( + (((255 - in1[x]) * (in1[x] * in2[x])) / 65536) + + (in1[x] * (255 - ((255 - in1[x]) * (255 - in2[x]) / 255))) / 255, + NULL); +} + +Imaging +ImagingChopHardLight(Imaging imIn1, Imaging imIn2) { + CHOP2( + (in2[x] < 128) ? ((in1[x] * in2[x]) / 127) + : 255 - (((255 - in2[x]) * (255 - in1[x])) / 127), + NULL); +} + +Imaging +ImagingOverlay(Imaging imIn1, Imaging imIn2) { + CHOP2( + (in1[x] < 128) ? ((in1[x] * in2[x]) / 127) + : 255 - (((255 - in1[x]) * (255 - in2[x])) / 127), + NULL); +} diff --git a/src/libImaging/ColorLUT.c b/src/libImaging/ColorLUT.c index f01d38993..fd6e268b5 100644 --- a/src/libImaging/ColorLUT.c +++ b/src/libImaging/ColorLUT.c @@ -1,52 +1,45 @@ #include "Imaging.h" #include - /* 8 bits for result. Table can overflow [0, 1.0] range, so we need extra bits for overflow and negative values. NOTE: This value should be the same as in _imaging/_prepare_lut_table() */ #define PRECISION_BITS (16 - 8 - 2) -#define PRECISION_ROUNDING (1<<(PRECISION_BITS-1)) +#define PRECISION_ROUNDING (1 << (PRECISION_BITS - 1)) /* 8 — scales are multiplied on byte. 6 — max index in the table (max size is 65, but index 64 is not reachable) */ #define SCALE_BITS (32 - 8 - 6) -#define SCALE_MASK ((1<> PRECISION_BITS]; } static inline void -interpolate3(INT16 out[3], const INT16 a[3], const INT16 b[3], INT16 shift) -{ - out[0] = (a[0] * ((1<> SHIFT_BITS; - out[1] = (a[1] * ((1<> SHIFT_BITS; - out[2] = (a[2] * ((1<> SHIFT_BITS; +interpolate3(INT16 out[3], const INT16 a[3], const INT16 b[3], INT16 shift) { + out[0] = (a[0] * ((1 << SHIFT_BITS) - shift) + b[0] * shift) >> SHIFT_BITS; + out[1] = (a[1] * ((1 << SHIFT_BITS) - shift) + b[1] * shift) >> SHIFT_BITS; + out[2] = (a[2] * ((1 << SHIFT_BITS) - shift) + b[2] * shift) >> SHIFT_BITS; } static inline void -interpolate4(INT16 out[4], const INT16 a[4], const INT16 b[4], INT16 shift) -{ - out[0] = (a[0] * ((1<> SHIFT_BITS; - out[1] = (a[1] * ((1<> SHIFT_BITS; - out[2] = (a[2] * ((1<> SHIFT_BITS; - out[3] = (a[3] * ((1<> SHIFT_BITS; +interpolate4(INT16 out[4], const INT16 a[4], const INT16 b[4], INT16 shift) { + out[0] = (a[0] * ((1 << SHIFT_BITS) - shift) + b[0] * shift) >> SHIFT_BITS; + out[1] = (a[1] * ((1 << SHIFT_BITS) - shift) + b[1] * shift) >> SHIFT_BITS; + out[2] = (a[2] * ((1 << SHIFT_BITS) - shift) + b[2] * shift) >> SHIFT_BITS; + out[3] = (a[3] * ((1 << SHIFT_BITS) - shift) + b[3] * shift) >> SHIFT_BITS; } static inline int -table_index3D(int index1D, int index2D, int index3D, - int size1D, int size1D_2D) -{ +table_index3D(int index1D, int index2D, int index3D, int size1D, int size1D_2D) { return index1D + index2D * size1D + index3D * size1D_2D; } - /* Transforms colors of imIn using provided 3D lookup table and puts the result in imOut. Returns imOut on success or 0 on error. @@ -63,10 +56,14 @@ table_index3D(int index1D, int index2D, int index3D, and 255 << PRECISION_BITS (16320) is highest value. */ Imaging -ImagingColorLUT3D_linear(Imaging imOut, Imaging imIn, int table_channels, - int size1D, int size2D, int size3D, - INT16* table) -{ +ImagingColorLUT3D_linear( + Imaging imOut, + Imaging imIn, + int table_channels, + int size1D, + int size2D, + int size3D, + INT16 *table) { /* This float to int conversion doesn't have rounding error compensation (+0.5) for two reasons: 1. As we don't hit the highest value, @@ -77,9 +74,9 @@ ImagingColorLUT3D_linear(Imaging imOut, Imaging imIn, int table_channels, +1 cells will be outside of the table. With this compensation we never hit the upper cells but this also doesn't introduce any noticeable difference. */ - UINT32 scale1D = (size1D - 1) / 255.0 * (1<type != IMAGING_TYPE_UINT8 || - imOut->type != IMAGING_TYPE_UINT8 || - imIn->bands < 3 || - imOut->bands < table_channels - ) { - return (Imaging) ImagingError_ModeError(); + if (imIn->type != IMAGING_TYPE_UINT8 || imOut->type != IMAGING_TYPE_UINT8 || + imIn->bands < 3 || imOut->bands < table_channels) { + return (Imaging)ImagingError_ModeError(); } /* In case we have one extra band in imOut and don't have in imIn.*/ if (imOut->bands > table_channels && imOut->bands > imIn->bands) { - return (Imaging) ImagingError_ModeError(); + return (Imaging)ImagingError_ModeError(); } ImagingSectionEnter(&cookie); for (y = 0; y < imOut->ysize; y++) { - UINT8* rowIn = (UINT8 *)imIn->image[y]; - char* rowOut = (char *)imOut->image[y]; + UINT8 *rowIn = (UINT8 *)imIn->image[y]; + char *rowOut = (char *)imOut->image[y]; for (x = 0; x < imOut->xsize; x++) { - UINT32 index1D = rowIn[x*4 + 0] * scale1D; - UINT32 index2D = rowIn[x*4 + 1] * scale2D; - UINT32 index3D = rowIn[x*4 + 2] * scale3D; + UINT32 index1D = rowIn[x * 4 + 0] * scale1D; + UINT32 index2D = rowIn[x * 4 + 1] * scale2D; + UINT32 index3D = rowIn[x * 4 + 2] * scale3D; INT16 shift1D = (SCALE_MASK & index1D) >> (SCALE_BITS - SHIFT_BITS); INT16 shift2D = (SCALE_MASK & index2D) >> (SCALE_BITS - SHIFT_BITS); INT16 shift3D = (SCALE_MASK & index3D) >> (SCALE_BITS - SHIFT_BITS); int idx = table_channels * table_index3D( - index1D >> SCALE_BITS, index2D >> SCALE_BITS, - index3D >> SCALE_BITS, size1D, size1D_2D); + index1D >> SCALE_BITS, + index2D >> SCALE_BITS, + index3D >> SCALE_BITS, + size1D, + size1D_2D); INT16 result[4], left[4], right[4]; INT16 leftleft[4], leftright[4], rightleft[4], rightright[4]; if (table_channels == 3) { UINT32 v; interpolate3(leftleft, &table[idx + 0], &table[idx + 3], shift1D); - interpolate3(leftright, &table[idx + size1D*3], - &table[idx + size1D*3 + 3], shift1D); + interpolate3( + leftright, + &table[idx + size1D * 3], + &table[idx + size1D * 3 + 3], + shift1D); interpolate3(left, leftleft, leftright, shift2D); - interpolate3(rightleft, &table[idx + size1D_2D*3], - &table[idx + size1D_2D*3 + 3], shift1D); - interpolate3(rightright, &table[idx + size1D_2D*3 + size1D*3], - &table[idx + size1D_2D*3 + size1D*3 + 3], shift1D); + interpolate3( + rightleft, + &table[idx + size1D_2D * 3], + &table[idx + size1D_2D * 3 + 3], + shift1D); + interpolate3( + rightright, + &table[idx + size1D_2D * 3 + size1D * 3], + &table[idx + size1D_2D * 3 + size1D * 3 + 3], + shift1D); interpolate3(right, rightleft, rightright, shift2D); interpolate3(result, left, right, shift3D); v = MAKE_UINT32( - clip8(result[0]), clip8(result[1]), - clip8(result[2]), rowIn[x*4 + 3]); + clip8(result[0]), + clip8(result[1]), + clip8(result[2]), + rowIn[x * 4 + 3]); memcpy(rowOut + x * sizeof(v), &v, sizeof(v)); } if (table_channels == 4) { UINT32 v; interpolate4(leftleft, &table[idx + 0], &table[idx + 4], shift1D); - interpolate4(leftright, &table[idx + size1D*4], - &table[idx + size1D*4 + 4], shift1D); + interpolate4( + leftright, + &table[idx + size1D * 4], + &table[idx + size1D * 4 + 4], + shift1D); interpolate4(left, leftleft, leftright, shift2D); - interpolate4(rightleft, &table[idx + size1D_2D*4], - &table[idx + size1D_2D*4 + 4], shift1D); - interpolate4(rightright, &table[idx + size1D_2D*4 + size1D*4], - &table[idx + size1D_2D*4 + size1D*4 + 4], shift1D); + interpolate4( + rightleft, + &table[idx + size1D_2D * 4], + &table[idx + size1D_2D * 4 + 4], + shift1D); + interpolate4( + rightright, + &table[idx + size1D_2D * 4 + size1D * 4], + &table[idx + size1D_2D * 4 + size1D * 4 + 4], + shift1D); interpolate4(right, rightleft, rightright, shift2D); interpolate4(result, left, right, shift3D); v = MAKE_UINT32( - clip8(result[0]), clip8(result[1]), - clip8(result[2]), clip8(result[3])); + clip8(result[0]), + clip8(result[1]), + clip8(result[2]), + clip8(result[3])); memcpy(rowOut + x * sizeof(v), &v, sizeof(v)); } } diff --git a/src/libImaging/Convert.c b/src/libImaging/Convert.c index 9f5722543..8c7be36a2 100644 --- a/src/libImaging/Convert.c +++ b/src/libImaging/Convert.c @@ -32,24 +32,21 @@ * See the README file for details on usage and redistribution. */ - #include "Imaging.h" -#define MAX(a, b) (a)>(b) ? (a) : (b) -#define MIN(a, b) (a)<(b) ? (a) : (b) +#define MAX(a, b) (a) > (b) ? (a) : (b) +#define MIN(a, b) (a) < (b) ? (a) : (b) #define CLIP16(v) ((v) <= -32768 ? -32768 : (v) >= 32767 ? 32767 : (v)) /* ITU-R Recommendation 601-2 (assuming nonlinear RGB) */ -#define L(rgb)\ - ((INT32) (rgb)[0]*299 + (INT32) (rgb)[1]*587 + (INT32) (rgb)[2]*114) -#define L24(rgb)\ - ((rgb)[0]*19595 + (rgb)[1]*38470 + (rgb)[2]*7471 + 0x8000) - +#define L(rgb) ((INT32)(rgb)[0] * 299 + (INT32)(rgb)[1] * 587 + (INT32)(rgb)[2] * 114) +#define L24(rgb) ((rgb)[0] * 19595 + (rgb)[1] * 38470 + (rgb)[2] * 7471 + 0x8000) #ifndef round -double round(double x) { - return floor(x+0.5); +double +round(double x) { + return floor(x + 0.5); } #endif @@ -58,16 +55,13 @@ double round(double x) { /* ------------------- */ static void -bit2l(UINT8* out, const UINT8* in, int xsize) -{ +bit2l(UINT8 *out, const UINT8 *in, int xsize) { int x; - for (x = 0; x < xsize; x++) - *out++ = (*in++ != 0) ? 255 : 0; + for (x = 0; x < xsize; x++) *out++ = (*in++ != 0) ? 255 : 0; } static void -bit2rgb(UINT8* out, const UINT8* in, int xsize) -{ +bit2rgb(UINT8 *out, const UINT8 *in, int xsize) { int x; for (x = 0; x < xsize; x++) { UINT8 v = (*in++ != 0) ? 255 : 0; @@ -79,8 +73,7 @@ bit2rgb(UINT8* out, const UINT8* in, int xsize) } static void -bit2cmyk(UINT8* out, const UINT8* in, int xsize) -{ +bit2cmyk(UINT8 *out, const UINT8 *in, int xsize) { int x; for (x = 0; x < xsize; x++) { *out++ = 0; @@ -91,8 +84,7 @@ bit2cmyk(UINT8* out, const UINT8* in, int xsize) } static void -bit2ycbcr(UINT8* out, const UINT8* in, int xsize) -{ +bit2ycbcr(UINT8 *out, const UINT8 *in, int xsize) { int x; for (x = 0; x < xsize; x++) { *out++ = (*in++ != 0) ? 255 : 0; @@ -103,8 +95,7 @@ bit2ycbcr(UINT8* out, const UINT8* in, int xsize) } static void -bit2hsv(UINT8* out, const UINT8* in, int xsize) -{ +bit2hsv(UINT8 *out, const UINT8 *in, int xsize) { int x; for (x = 0; x < xsize; x++, out += 4) { UINT8 v = (*in++ != 0) ? 255 : 0; @@ -120,52 +111,49 @@ bit2hsv(UINT8* out, const UINT8* in, int xsize) /* ----------------- */ static void -l2bit(UINT8* out, const UINT8* in, int xsize) -{ +l2bit(UINT8 *out, const UINT8 *in, int xsize) { int x; - for (x = 0; x < xsize; x++) + for (x = 0; x < xsize; x++) { *out++ = (*in++ >= 128) ? 255 : 0; + } } static void -lA2la(UINT8* out, const UINT8* in, int xsize) -{ +lA2la(UINT8 *out, const UINT8 *in, int xsize) { int x; unsigned int alpha, pixel, tmp; for (x = 0; x < xsize; x++, in += 4) { alpha = in[3]; pixel = MULDIV255(in[0], alpha, tmp); - *out++ = (UINT8) pixel; - *out++ = (UINT8) pixel; - *out++ = (UINT8) pixel; - *out++ = (UINT8) alpha; + *out++ = (UINT8)pixel; + *out++ = (UINT8)pixel; + *out++ = (UINT8)pixel; + *out++ = (UINT8)alpha; } } /* RGBa -> RGBA conversion to remove premultiplication Needed for correct transforms/resizing on RGBA images */ static void -la2lA(UINT8* out, const UINT8* in, int xsize) -{ +la2lA(UINT8 *out, const UINT8 *in, int xsize) { int x; unsigned int alpha, pixel; - for (x = 0; x < xsize; x++, in+=4) { + for (x = 0; x < xsize; x++, in += 4) { alpha = in[3]; if (alpha == 255 || alpha == 0) { pixel = in[0]; } else { pixel = CLIP8((255 * in[0]) / alpha); } - *out++ = (UINT8) pixel; - *out++ = (UINT8) pixel; - *out++ = (UINT8) pixel; - *out++ = (UINT8) alpha; + *out++ = (UINT8)pixel; + *out++ = (UINT8)pixel; + *out++ = (UINT8)pixel; + *out++ = (UINT8)alpha; } } static void -l2la(UINT8* out, const UINT8* in, int xsize) -{ +l2la(UINT8 *out, const UINT8 *in, int xsize) { int x; for (x = 0; x < xsize; x++) { UINT8 v = *in++; @@ -177,8 +165,7 @@ l2la(UINT8* out, const UINT8* in, int xsize) } static void -l2rgb(UINT8* out, const UINT8* in, int xsize) -{ +l2rgb(UINT8 *out, const UINT8 *in, int xsize) { int x; for (x = 0; x < xsize; x++) { UINT8 v = *in++; @@ -190,8 +177,7 @@ l2rgb(UINT8* out, const UINT8* in, int xsize) } static void -l2hsv(UINT8* out, const UINT8* in, int xsize) -{ +l2hsv(UINT8 *out, const UINT8 *in, int xsize) { int x; for (x = 0; x < xsize; x++, out += 4) { UINT8 v = *in++; @@ -203,16 +189,15 @@ l2hsv(UINT8* out, const UINT8* in, int xsize) } static void -la2l(UINT8* out, const UINT8* in, int xsize) -{ +la2l(UINT8 *out, const UINT8 *in, int xsize) { int x; - for (x = 0; x < xsize; x++, in += 4) + for (x = 0; x < xsize; x++, in += 4) { *out++ = in[0]; + } } static void -la2rgb(UINT8* out, const UINT8* in, int xsize) -{ +la2rgb(UINT8 *out, const UINT8 *in, int xsize) { int x; for (x = 0; x < xsize; x++, in += 4) { UINT8 v = in[0]; @@ -224,8 +209,7 @@ la2rgb(UINT8* out, const UINT8* in, int xsize) } static void -la2hsv(UINT8* out, const UINT8* in, int xsize) -{ +la2hsv(UINT8 *out, const UINT8 *in, int xsize) { int x; for (x = 0; x < xsize; x++, in += 4, out += 4) { UINT8 v = in[0]; @@ -237,26 +221,25 @@ la2hsv(UINT8* out, const UINT8* in, int xsize) } static void -rgb2bit(UINT8* out, const UINT8* in, int xsize) -{ +rgb2bit(UINT8 *out, const UINT8 *in, int xsize) { int x; - for (x = 0; x < xsize; x++, in += 4) + for (x = 0; x < xsize; x++, in += 4) { /* ITU-R Recommendation 601-2 (assuming nonlinear RGB) */ *out++ = (L(in) >= 128000) ? 255 : 0; + } } static void -rgb2l(UINT8* out, const UINT8* in, int xsize) -{ +rgb2l(UINT8 *out, const UINT8 *in, int xsize) { int x; - for (x = 0; x < xsize; x++, in += 4) + for (x = 0; x < xsize; x++, in += 4) { /* ITU-R Recommendation 601-2 (assuming nonlinear RGB) */ *out++ = L24(in) >> 16; + } } static void -rgb2la(UINT8* out, const UINT8* in, int xsize) -{ +rgb2la(UINT8 *out, const UINT8 *in, int xsize) { int x; for (x = 0; x < xsize; x++, in += 4, out += 4) { /* ITU-R Recommendation 601-2 (assuming nonlinear RGB) */ @@ -266,8 +249,7 @@ rgb2la(UINT8* out, const UINT8* in, int xsize) } static void -rgb2i(UINT8* out_, const UINT8* in, int xsize) -{ +rgb2i(UINT8 *out_, const UINT8 *in, int xsize) { int x; for (x = 0; x < xsize; x++, in += 4, out_ += 4) { INT32 v = L24(in) >> 16; @@ -276,44 +258,38 @@ rgb2i(UINT8* out_, const UINT8* in, int xsize) } static void -rgb2f(UINT8* out_, const UINT8* in, int xsize) -{ +rgb2f(UINT8 *out_, const UINT8 *in, int xsize) { int x; for (x = 0; x < xsize; x++, in += 4, out_ += 4) { - FLOAT32 v = (float) L(in) / 1000.0F; + FLOAT32 v = (float)L(in) / 1000.0F; memcpy(out_, &v, sizeof(v)); } } static void -rgb2bgr15(UINT8* out_, const UINT8* in, int xsize) -{ +rgb2bgr15(UINT8 *out_, const UINT8 *in, int xsize) { int x; for (x = 0; x < xsize; x++, in += 4, out_ += 2) { - UINT16 v = - ((((UINT16)in[0])<<7)&0x7c00) + - ((((UINT16)in[1])<<2)&0x03e0) + - ((((UINT16)in[2])>>3)&0x001f); + UINT16 v = ((((UINT16)in[0]) << 7) & 0x7c00) + + ((((UINT16)in[1]) << 2) & 0x03e0) + + ((((UINT16)in[2]) >> 3) & 0x001f); memcpy(out_, &v, sizeof(v)); } } static void -rgb2bgr16(UINT8* out_, const UINT8* in, int xsize) -{ +rgb2bgr16(UINT8 *out_, const UINT8 *in, int xsize) { int x; for (x = 0; x < xsize; x++, in += 4, out_ += 2) { - UINT16 v = - ((((UINT16)in[0])<<8)&0xf800) + - ((((UINT16)in[1])<<3)&0x07e0) + - ((((UINT16)in[2])>>3)&0x001f); + UINT16 v = ((((UINT16)in[0]) << 8) & 0xf800) + + ((((UINT16)in[1]) << 3) & 0x07e0) + + ((((UINT16)in[2]) >> 3) & 0x001f); memcpy(out_, &v, sizeof(v)); } } static void -rgb2bgr24(UINT8* out, const UINT8* in, int xsize) -{ +rgb2bgr24(UINT8 *out, const UINT8 *in, int xsize) { int x; for (x = 0; x < xsize; x++, in += 4) { *out++ = in[2]; @@ -323,40 +299,39 @@ rgb2bgr24(UINT8* out, const UINT8* in, int xsize) } static void -rgb2hsv_row(UINT8* out, const UINT8* in) -{ // following colorsys.py - float h,s,rc,gc,bc,cr; - UINT8 maxc,minc; +rgb2hsv_row(UINT8 *out, const UINT8 *in) { // following colorsys.py + float h, s, rc, gc, bc, cr; + UINT8 maxc, minc; UINT8 r, g, b; - UINT8 uh,us,uv; + UINT8 uh, us, uv; r = in[0]; g = in[1]; b = in[2]; - maxc = MAX(r,MAX(g,b)); - minc = MIN(r,MIN(g,b)); + maxc = MAX(r, MAX(g, b)); + minc = MIN(r, MIN(g, b)); uv = maxc; - if (minc == maxc){ + if (minc == maxc) { uh = 0; us = 0; } else { - cr = (float)(maxc-minc); - s = cr/(float)maxc; - rc = ((float)(maxc-r))/cr; - gc = ((float)(maxc-g))/cr; - bc = ((float)(maxc-b))/cr; + cr = (float)(maxc - minc); + s = cr / (float)maxc; + rc = ((float)(maxc - r)) / cr; + gc = ((float)(maxc - g)) / cr; + bc = ((float)(maxc - b)) / cr; if (r == maxc) { - h = bc-gc; + h = bc - gc; } else if (g == maxc) { - h = 2.0 + rc-bc; + h = 2.0 + rc - bc; } else { - h = 4.0 + gc-rc; + h = 4.0 + gc - rc; } // incorrect hue happens if h/6 is negative. - h = fmod((h/6.0 + 1.0), 1.0); + h = fmod((h / 6.0 + 1.0), 1.0); - uh = (UINT8)CLIP8((int)(h*255.0)); - us = (UINT8)CLIP8((int)(s*255.0)); + uh = (UINT8)CLIP8((int)(h * 255.0)); + us = (UINT8)CLIP8((int)(s * 255.0)); } out[0] = uh; out[1] = us; @@ -364,8 +339,7 @@ rgb2hsv_row(UINT8* out, const UINT8* in) } static void -rgb2hsv(UINT8* out, const UINT8* in, int xsize) -{ +rgb2hsv(UINT8 *out, const UINT8 *in, int xsize) { int x; for (x = 0; x < xsize; x++, in += 4, out += 4) { rgb2hsv_row(out, in); @@ -373,98 +347,91 @@ rgb2hsv(UINT8* out, const UINT8* in, int xsize) } } - - static void -hsv2rgb(UINT8* out, const UINT8* in, int xsize) -{ // following colorsys.py +hsv2rgb(UINT8 *out, const UINT8 *in, int xsize) { // following colorsys.py - int p,q,t; - UINT8 up,uq,ut; + int p, q, t; + UINT8 up, uq, ut; int i, x; float f, fs; - UINT8 h,s,v; + UINT8 h, s, v; for (x = 0; x < xsize; x++, in += 4) { h = in[0]; s = in[1]; v = in[2]; - if (s==0){ + if (s == 0) { *out++ = v; *out++ = v; *out++ = v; } else { - i = floor((float)h * 6.0 / 255.0); // 0 - 6 - f = (float)h * 6.0 / 255.0 - (float)i; // 0-1 : remainder. - fs = ((float)s)/255.0; + i = floor((float)h * 6.0 / 255.0); // 0 - 6 + f = (float)h * 6.0 / 255.0 - (float)i; // 0-1 : remainder. + fs = ((float)s) / 255.0; - p = round((float)v * (1.0-fs)); - q = round((float)v * (1.0-fs*f)); - t = round((float)v * (1.0-fs*(1.0-f))); + p = round((float)v * (1.0 - fs)); + q = round((float)v * (1.0 - fs * f)); + t = round((float)v * (1.0 - fs * (1.0 - f))); up = (UINT8)CLIP8(p); uq = (UINT8)CLIP8(q); ut = (UINT8)CLIP8(t); - switch (i%6) { - case 0: - *out++ = v; - *out++ = ut; - *out++ = up; - break; - case 1: - *out++ = uq; - *out++ = v; - *out++ = up; - break; - case 2: - *out++ = up; - *out++ = v; - *out++ = ut; - break; - case 3: - *out++ = up; - *out++ = uq; - *out++ = v; - break; - case 4: - *out++ = ut; - *out++ = up; - *out++ = v; - break; - case 5: - *out++ = v; - *out++ = up; - *out++ = uq; - break; - + switch (i % 6) { + case 0: + *out++ = v; + *out++ = ut; + *out++ = up; + break; + case 1: + *out++ = uq; + *out++ = v; + *out++ = up; + break; + case 2: + *out++ = up; + *out++ = v; + *out++ = ut; + break; + case 3: + *out++ = up; + *out++ = uq; + *out++ = v; + break; + case 4: + *out++ = ut; + *out++ = up; + *out++ = v; + break; + case 5: + *out++ = v; + *out++ = up; + *out++ = uq; + break; } } *out++ = in[3]; } } - - /* ---------------- */ /* RGBA conversions */ /* ---------------- */ static void -rgb2rgba(UINT8* out, const UINT8* in, int xsize) -{ +rgb2rgba(UINT8 *out, const UINT8 *in, int xsize) { int x; for (x = 0; x < xsize; x++) { *out++ = *in++; *out++ = *in++; *out++ = *in++; - *out++ = 255; in++; + *out++ = 255; + in++; } } static void -rgba2la(UINT8* out, const UINT8* in, int xsize) -{ +rgba2la(UINT8 *out, const UINT8 *in, int xsize) { int x; for (x = 0; x < xsize; x++, in += 4, out += 4) { /* ITU-R Recommendation 601-2 (assuming nonlinear RGB) */ @@ -474,20 +441,19 @@ rgba2la(UINT8* out, const UINT8* in, int xsize) } static void -rgba2rgb(UINT8* out, const UINT8* in, int xsize) -{ +rgba2rgb(UINT8 *out, const UINT8 *in, int xsize) { int x; for (x = 0; x < xsize; x++) { *out++ = *in++; *out++ = *in++; *out++ = *in++; - *out++ = 255; in++; + *out++ = 255; + in++; } } static void -rgbA2rgba(UINT8* out, const UINT8* in, int xsize) -{ +rgbA2rgba(UINT8 *out, const UINT8 *in, int xsize) { int x; unsigned int alpha, tmp; for (x = 0; x < xsize; x++) { @@ -502,11 +468,10 @@ rgbA2rgba(UINT8* out, const UINT8* in, int xsize) /* RGBa -> RGBA conversion to remove premultiplication Needed for correct transforms/resizing on RGBA images */ static void -rgba2rgbA(UINT8* out, const UINT8* in, int xsize) -{ +rgba2rgbA(UINT8 *out, const UINT8 *in, int xsize) { int x; unsigned int alpha; - for (x = 0; x < xsize; x++, in+=4) { + for (x = 0; x < xsize; x++, in += 4) { alpha = in[3]; if (alpha == 255 || alpha == 0) { *out++ = in[0]; @@ -528,35 +493,32 @@ rgba2rgbA(UINT8* out, const UINT8* in, int xsize) */ static void -rgbT2rgba(UINT8* out, int xsize, int r, int g, int b) -{ +rgbT2rgba(UINT8 *out, int xsize, int r, int g, int b) { #ifdef WORDS_BIGENDIAN - UINT32 trns = ((r & 0xff)<<24) | ((g & 0xff)<<16) | ((b & 0xff)<<8) | 0xff; + UINT32 trns = ((r & 0xff) << 24) | ((g & 0xff) << 16) | ((b & 0xff) << 8) | 0xff; UINT32 repl = trns & 0xffffff00; #else - UINT32 trns = (0xff <<24) | ((b & 0xff)<<16) | ((g & 0xff)<<8) | (r & 0xff); + UINT32 trns = (0xff << 24) | ((b & 0xff) << 16) | ((g & 0xff) << 8) | (r & 0xff); UINT32 repl = trns & 0x00ffffff; #endif int i; - for (i=0; i < xsize; i++ ,out += sizeof(trns)) { + for (i = 0; i < xsize; i++, out += sizeof(trns)) { UINT32 v; memcpy(&v, out, sizeof(v)); - if (v==trns) { + if (v == trns) { memcpy(out, &repl, sizeof(repl)); } } } - /* ---------------- */ /* CMYK conversions */ /* ---------------- */ static void -l2cmyk(UINT8* out, const UINT8* in, int xsize) -{ +l2cmyk(UINT8 *out, const UINT8 *in, int xsize) { int x; for (x = 0; x < xsize; x++) { *out++ = 0; @@ -567,8 +529,7 @@ l2cmyk(UINT8* out, const UINT8* in, int xsize) } static void -la2cmyk(UINT8* out, const UINT8* in, int xsize) -{ +la2cmyk(UINT8 *out, const UINT8 *in, int xsize) { int x; for (x = 0; x < xsize; x++, in += 4) { *out++ = 0; @@ -579,21 +540,20 @@ la2cmyk(UINT8* out, const UINT8* in, int xsize) } static void -rgb2cmyk(UINT8* out, const UINT8* in, int xsize) -{ +rgb2cmyk(UINT8 *out, const UINT8 *in, int xsize) { int x; for (x = 0; x < xsize; x++) { /* Note: no undercolour removal */ *out++ = ~(*in++); *out++ = ~(*in++); *out++ = ~(*in++); - *out++ = 0; in++; + *out++ = 0; + in++; } } static void -cmyk2rgb(UINT8* out, const UINT8* in, int xsize) -{ +cmyk2rgb(UINT8 *out, const UINT8 *in, int xsize) { int x, nk, tmp; for (x = 0; x < xsize; x++) { nk = 255 - in[3]; @@ -607,8 +567,7 @@ cmyk2rgb(UINT8* out, const UINT8* in, int xsize) } static void -cmyk2hsv(UINT8* out, const UINT8* in, int xsize) -{ +cmyk2hsv(UINT8 *out, const UINT8 *in, int xsize) { int x, nk, tmp; for (x = 0; x < xsize; x++) { nk = 255 - in[3]; @@ -627,8 +586,7 @@ cmyk2hsv(UINT8* out, const UINT8* in, int xsize) /* ------------- */ static void -bit2i(UINT8* out_, const UINT8* in, int xsize) -{ +bit2i(UINT8 *out_, const UINT8 *in, int xsize) { int x; for (x = 0; x < xsize; x++, out_ += 4) { INT32 v = (*in++ != 0) ? 255 : 0; @@ -637,8 +595,7 @@ bit2i(UINT8* out_, const UINT8* in, int xsize) } static void -l2i(UINT8* out_, const UINT8* in, int xsize) -{ +l2i(UINT8 *out_, const UINT8 *in, int xsize) { int x; for (x = 0; x < xsize; x++, out_ += 4) { INT32 v = *in++; @@ -647,24 +604,23 @@ l2i(UINT8* out_, const UINT8* in, int xsize) } static void -i2l(UINT8* out, const UINT8* in_, int xsize) -{ +i2l(UINT8 *out, const UINT8 *in_, int xsize) { int x; for (x = 0; x < xsize; x++, out++, in_ += 4) { INT32 v; memcpy(&v, in_, sizeof(v)); - if (v <= 0) + if (v <= 0) { *out = 0; - else if (v >= 255) + } else if (v >= 255) { *out = 255; - else - *out = (UINT8) v; + } else { + *out = (UINT8)v; + } } } static void -i2f(UINT8* out_, const UINT8* in_, int xsize) -{ +i2f(UINT8 *out_, const UINT8 *in_, int xsize) { int x; for (x = 0; x < xsize; x++, in_ += 4, out_ += 4) { INT32 i; @@ -676,27 +632,26 @@ i2f(UINT8* out_, const UINT8* in_, int xsize) } static void -i2rgb(UINT8* out, const UINT8* in_, int xsize) -{ +i2rgb(UINT8 *out, const UINT8 *in_, int xsize) { int x; - INT32* in = (INT32*) in_; - for (x = 0; x < xsize; x++, in++, out+=4) { - if (*in <= 0) + INT32 *in = (INT32 *)in_; + for (x = 0; x < xsize; x++, in++, out += 4) { + if (*in <= 0) { out[0] = out[1] = out[2] = 0; - else if (*in >= 255) + } else if (*in >= 255) { out[0] = out[1] = out[2] = 255; - else - out[0] = out[1] = out[2] = (UINT8) *in; + } else { + out[0] = out[1] = out[2] = (UINT8)*in; + } out[3] = 255; } } static void -i2hsv(UINT8* out, const UINT8* in_, int xsize) -{ +i2hsv(UINT8 *out, const UINT8 *in_, int xsize) { int x; - INT32* in = (INT32*) in_; - for (x = 0; x < xsize; x++, in++, out+=4) { + INT32 *in = (INT32 *)in_; + for (x = 0; x < xsize; x++, in++, out += 4) { out[0] = 0; out[1] = 0; if (*in <= 0) { @@ -704,7 +659,7 @@ i2hsv(UINT8* out, const UINT8* in_, int xsize) } else if (*in >= 255) { out[2] = 255; } else { - out[2] = (UINT8) *in; + out[2] = (UINT8)*in; } out[3] = 255; } @@ -715,8 +670,7 @@ i2hsv(UINT8* out, const UINT8* in_, int xsize) /* ------------- */ static void -bit2f(UINT8* out_, const UINT8* in, int xsize) -{ +bit2f(UINT8 *out_, const UINT8 *in, int xsize) { int x; for (x = 0; x < xsize; x++, out_ += 4) { FLOAT32 f = (*in++ != 0) ? 255.0F : 0.0F; @@ -725,34 +679,32 @@ bit2f(UINT8* out_, const UINT8* in, int xsize) } static void -l2f(UINT8* out_, const UINT8* in, int xsize) -{ +l2f(UINT8 *out_, const UINT8 *in, int xsize) { int x; for (x = 0; x < xsize; x++, out_ += 4) { - FLOAT32 f = (FLOAT32) *in++; + FLOAT32 f = (FLOAT32)*in++; memcpy(out_, &f, sizeof(f)); } } static void -f2l(UINT8* out, const UINT8* in_, int xsize) -{ +f2l(UINT8 *out, const UINT8 *in_, int xsize) { int x; for (x = 0; x < xsize; x++, out++, in_ += 4) { FLOAT32 v; memcpy(&v, in_, sizeof(v)); - if (v <= 0.0) + if (v <= 0.0) { *out = 0; - else if (v >= 255.0) + } else if (v >= 255.0) { *out = 255; - else - *out = (UINT8) v; + } else { + *out = (UINT8)v; + } } } static void -f2i(UINT8* out_, const UINT8* in_, int xsize) -{ +f2i(UINT8 *out_, const UINT8 *in_, int xsize) { int x; for (x = 0; x < xsize; x++, in_ += 4, out_ += 4) { FLOAT32 f; @@ -770,8 +722,7 @@ f2i(UINT8* out_, const UINT8* in_, int xsize) /* See ConvertYCbCr.c for RGB/YCbCr tables */ static void -l2ycbcr(UINT8* out, const UINT8* in, int xsize) -{ +l2ycbcr(UINT8 *out, const UINT8 *in, int xsize) { int x; for (x = 0; x < xsize; x++) { *out++ = *in++; @@ -782,8 +733,7 @@ l2ycbcr(UINT8* out, const UINT8* in, int xsize) } static void -la2ycbcr(UINT8* out, const UINT8* in, int xsize) -{ +la2ycbcr(UINT8 *out, const UINT8 *in, int xsize) { int x; for (x = 0; x < xsize; x++, in += 4) { *out++ = in[0]; @@ -794,16 +744,15 @@ la2ycbcr(UINT8* out, const UINT8* in, int xsize) } static void -ycbcr2l(UINT8* out, const UINT8* in, int xsize) -{ +ycbcr2l(UINT8 *out, const UINT8 *in, int xsize) { int x; - for (x = 0; x < xsize; x++, in += 4) + for (x = 0; x < xsize; x++, in += 4) { *out++ = in[0]; + } } static void -ycbcr2la(UINT8* out, const UINT8* in, int xsize) -{ +ycbcr2la(UINT8 *out, const UINT8 *in, int xsize) { int x; for (x = 0; x < xsize; x++, in += 4, out += 4) { out[0] = out[1] = out[2] = in[0]; @@ -816,77 +765,67 @@ ycbcr2la(UINT8* out, const UINT8* in, int xsize) /* ------------------------- */ static void -I_I16L(UINT8* out, const UINT8* in_, int xsize) -{ +I_I16L(UINT8 *out, const UINT8 *in_, int xsize) { int x, v; for (x = 0; x < xsize; x++, in_ += 4) { INT32 i; memcpy(&i, in_, sizeof(i)); v = CLIP16(i); - *out++ = (UINT8) v; - *out++ = (UINT8) (v >> 8); + *out++ = (UINT8)v; + *out++ = (UINT8)(v >> 8); } } static void -I_I16B(UINT8* out, const UINT8* in_, int xsize) -{ +I_I16B(UINT8 *out, const UINT8 *in_, int xsize) { int x, v; for (x = 0; x < xsize; x++, in_ += 4) { INT32 i; memcpy(&i, in_, sizeof(i)); v = CLIP16(i); - *out++ = (UINT8) (v >> 8); - *out++ = (UINT8) v; + *out++ = (UINT8)(v >> 8); + *out++ = (UINT8)v; } } - static void -I16L_I(UINT8* out_, const UINT8* in, int xsize) -{ +I16L_I(UINT8 *out_, const UINT8 *in, int xsize) { int x; for (x = 0; x < xsize; x++, in += 2, out_ += 4) { - INT32 v = in[0] + ((int) in[1] << 8); - memcpy(out_, &v, sizeof(v)); - } -} - - -static void -I16B_I(UINT8* out_, const UINT8* in, int xsize) -{ - int x; - for (x = 0; x < xsize; x++, in += 2, out_ += 4) { - INT32 v = in[1] + ((int) in[0] << 8); + INT32 v = in[0] + ((int)in[1] << 8); memcpy(out_, &v, sizeof(v)); } } static void -I16L_F(UINT8* out_, const UINT8* in, int xsize) -{ +I16B_I(UINT8 *out_, const UINT8 *in, int xsize) { int x; for (x = 0; x < xsize; x++, in += 2, out_ += 4) { - FLOAT32 v = in[0] + ((int) in[1] << 8); - memcpy(out_, &v, sizeof(v)); - } -} - - -static void -I16B_F(UINT8* out_, const UINT8* in, int xsize) -{ - int x; - for (x = 0; x < xsize; x++, in += 2, out_ += 4) { - FLOAT32 v = in[1] + ((int) in[0] << 8); + INT32 v = in[1] + ((int)in[0] << 8); memcpy(out_, &v, sizeof(v)); } } static void -L_I16L(UINT8* out, const UINT8* in, int xsize) -{ +I16L_F(UINT8 *out_, const UINT8 *in, int xsize) { + int x; + for (x = 0; x < xsize; x++, in += 2, out_ += 4) { + FLOAT32 v = in[0] + ((int)in[1] << 8); + memcpy(out_, &v, sizeof(v)); + } +} + +static void +I16B_F(UINT8 *out_, const UINT8 *in, int xsize) { + int x; + for (x = 0; x < xsize; x++, in += 2, out_ += 4) { + FLOAT32 v = in[1] + ((int)in[0] << 8); + memcpy(out_, &v, sizeof(v)); + } +} + +static void +L_I16L(UINT8 *out, const UINT8 *in, int xsize) { int x; for (x = 0; x < xsize; x++, in++) { *out++ = *in; @@ -895,8 +834,7 @@ L_I16L(UINT8* out, const UINT8* in, int xsize) } static void -L_I16B(UINT8* out, const UINT8* in, int xsize) -{ +L_I16B(UINT8 *out, const UINT8 *in, int xsize) { int x; for (x = 0; x < xsize; x++, in++) { *out++ = 0; @@ -905,145 +843,146 @@ L_I16B(UINT8* out, const UINT8* in, int xsize) } static void -I16L_L(UINT8* out, const UINT8* in, int xsize) -{ +I16L_L(UINT8 *out, const UINT8 *in, int xsize) { int x; - for (x = 0; x < xsize; x++, in += 2) - if (in[1] != 0) + for (x = 0; x < xsize; x++, in += 2) { + if (in[1] != 0) { *out++ = 255; - else + } else { *out++ = in[0]; + } + } } static void -I16B_L(UINT8* out, const UINT8* in, int xsize) -{ +I16B_L(UINT8 *out, const UINT8 *in, int xsize) { int x; - for (x = 0; x < xsize; x++, in += 2) - if (in[0] != 0) + for (x = 0; x < xsize; x++, in += 2) { + if (in[0] != 0) { *out++ = 255; - else + } else { *out++ = in[1]; + } + } } static struct { - const char* from; - const char* to; + const char *from; + const char *to; ImagingShuffler convert; } converters[] = { - { "1", "L", bit2l }, - { "1", "I", bit2i }, - { "1", "F", bit2f }, - { "1", "RGB", bit2rgb }, - { "1", "RGBA", bit2rgb }, - { "1", "RGBX", bit2rgb }, - { "1", "CMYK", bit2cmyk }, - { "1", "YCbCr", bit2ycbcr }, - { "1", "HSV", bit2hsv }, + {"1", "L", bit2l}, + {"1", "I", bit2i}, + {"1", "F", bit2f}, + {"1", "RGB", bit2rgb}, + {"1", "RGBA", bit2rgb}, + {"1", "RGBX", bit2rgb}, + {"1", "CMYK", bit2cmyk}, + {"1", "YCbCr", bit2ycbcr}, + {"1", "HSV", bit2hsv}, - { "L", "1", l2bit }, - { "L", "LA", l2la }, - { "L", "I", l2i }, - { "L", "F", l2f }, - { "L", "RGB", l2rgb }, - { "L", "RGBA", l2rgb }, - { "L", "RGBX", l2rgb }, - { "L", "CMYK", l2cmyk }, - { "L", "YCbCr", l2ycbcr }, - { "L", "HSV", l2hsv }, + {"L", "1", l2bit}, + {"L", "LA", l2la}, + {"L", "I", l2i}, + {"L", "F", l2f}, + {"L", "RGB", l2rgb}, + {"L", "RGBA", l2rgb}, + {"L", "RGBX", l2rgb}, + {"L", "CMYK", l2cmyk}, + {"L", "YCbCr", l2ycbcr}, + {"L", "HSV", l2hsv}, - { "LA", "L", la2l }, - { "LA", "La", lA2la }, - { "LA", "RGB", la2rgb }, - { "LA", "RGBA", la2rgb }, - { "LA", "RGBX", la2rgb }, - { "LA", "CMYK", la2cmyk }, - { "LA", "YCbCr", la2ycbcr }, - { "LA", "HSV", la2hsv }, + {"LA", "L", la2l}, + {"LA", "La", lA2la}, + {"LA", "RGB", la2rgb}, + {"LA", "RGBA", la2rgb}, + {"LA", "RGBX", la2rgb}, + {"LA", "CMYK", la2cmyk}, + {"LA", "YCbCr", la2ycbcr}, + {"LA", "HSV", la2hsv}, - { "La", "LA", la2lA }, + {"La", "LA", la2lA}, - { "I", "L", i2l }, - { "I", "F", i2f }, - { "I", "RGB", i2rgb }, - { "I", "RGBA", i2rgb }, - { "I", "RGBX", i2rgb }, - { "I", "HSV", i2hsv }, + {"I", "L", i2l}, + {"I", "F", i2f}, + {"I", "RGB", i2rgb}, + {"I", "RGBA", i2rgb}, + {"I", "RGBX", i2rgb}, + {"I", "HSV", i2hsv}, - { "F", "L", f2l }, - { "F", "I", f2i }, + {"F", "L", f2l}, + {"F", "I", f2i}, - { "RGB", "1", rgb2bit }, - { "RGB", "L", rgb2l }, - { "RGB", "LA", rgb2la }, - { "RGB", "I", rgb2i }, - { "RGB", "F", rgb2f }, - { "RGB", "BGR;15", rgb2bgr15 }, - { "RGB", "BGR;16", rgb2bgr16 }, - { "RGB", "BGR;24", rgb2bgr24 }, - { "RGB", "RGBA", rgb2rgba }, - { "RGB", "RGBX", rgb2rgba }, - { "RGB", "CMYK", rgb2cmyk }, - { "RGB", "YCbCr", ImagingConvertRGB2YCbCr }, - { "RGB", "HSV", rgb2hsv }, + {"RGB", "1", rgb2bit}, + {"RGB", "L", rgb2l}, + {"RGB", "LA", rgb2la}, + {"RGB", "I", rgb2i}, + {"RGB", "F", rgb2f}, + {"RGB", "BGR;15", rgb2bgr15}, + {"RGB", "BGR;16", rgb2bgr16}, + {"RGB", "BGR;24", rgb2bgr24}, + {"RGB", "RGBA", rgb2rgba}, + {"RGB", "RGBX", rgb2rgba}, + {"RGB", "CMYK", rgb2cmyk}, + {"RGB", "YCbCr", ImagingConvertRGB2YCbCr}, + {"RGB", "HSV", rgb2hsv}, - { "RGBA", "1", rgb2bit }, - { "RGBA", "L", rgb2l }, - { "RGBA", "LA", rgba2la }, - { "RGBA", "I", rgb2i }, - { "RGBA", "F", rgb2f }, - { "RGBA", "RGB", rgba2rgb }, - { "RGBA", "RGBa", rgbA2rgba }, - { "RGBA", "RGBX", rgb2rgba }, - { "RGBA", "CMYK", rgb2cmyk }, - { "RGBA", "YCbCr", ImagingConvertRGB2YCbCr }, - { "RGBA", "HSV", rgb2hsv }, + {"RGBA", "1", rgb2bit}, + {"RGBA", "L", rgb2l}, + {"RGBA", "LA", rgba2la}, + {"RGBA", "I", rgb2i}, + {"RGBA", "F", rgb2f}, + {"RGBA", "RGB", rgba2rgb}, + {"RGBA", "RGBa", rgbA2rgba}, + {"RGBA", "RGBX", rgb2rgba}, + {"RGBA", "CMYK", rgb2cmyk}, + {"RGBA", "YCbCr", ImagingConvertRGB2YCbCr}, + {"RGBA", "HSV", rgb2hsv}, - { "RGBa", "RGBA", rgba2rgbA }, + {"RGBa", "RGBA", rgba2rgbA}, - { "RGBX", "1", rgb2bit }, - { "RGBX", "L", rgb2l }, - { "RGBX", "LA", rgb2la }, - { "RGBX", "I", rgb2i }, - { "RGBX", "F", rgb2f }, - { "RGBX", "RGB", rgba2rgb }, - { "RGBX", "CMYK", rgb2cmyk }, - { "RGBX", "YCbCr", ImagingConvertRGB2YCbCr }, - { "RGBX", "HSV", rgb2hsv }, + {"RGBX", "1", rgb2bit}, + {"RGBX", "L", rgb2l}, + {"RGBX", "LA", rgb2la}, + {"RGBX", "I", rgb2i}, + {"RGBX", "F", rgb2f}, + {"RGBX", "RGB", rgba2rgb}, + {"RGBX", "CMYK", rgb2cmyk}, + {"RGBX", "YCbCr", ImagingConvertRGB2YCbCr}, + {"RGBX", "HSV", rgb2hsv}, - { "CMYK", "RGB", cmyk2rgb }, - { "CMYK", "RGBA", cmyk2rgb }, - { "CMYK", "RGBX", cmyk2rgb }, - { "CMYK", "HSV", cmyk2hsv }, + {"CMYK", "RGB", cmyk2rgb}, + {"CMYK", "RGBA", cmyk2rgb}, + {"CMYK", "RGBX", cmyk2rgb}, + {"CMYK", "HSV", cmyk2hsv}, - { "YCbCr", "L", ycbcr2l }, - { "YCbCr", "LA", ycbcr2la }, - { "YCbCr", "RGB", ImagingConvertYCbCr2RGB }, + {"YCbCr", "L", ycbcr2l}, + {"YCbCr", "LA", ycbcr2la}, + {"YCbCr", "RGB", ImagingConvertYCbCr2RGB}, - { "HSV", "RGB", hsv2rgb }, + {"HSV", "RGB", hsv2rgb}, - { "I", "I;16", I_I16L }, - { "I;16", "I", I16L_I }, - { "L", "I;16", L_I16L }, - { "I;16", "L", I16L_L }, + {"I", "I;16", I_I16L}, + {"I;16", "I", I16L_I}, + {"L", "I;16", L_I16L}, + {"I;16", "L", I16L_L}, - { "I", "I;16L", I_I16L }, - { "I;16L", "I", I16L_I }, - { "I", "I;16B", I_I16B }, - { "I;16B", "I", I16B_I }, + {"I", "I;16L", I_I16L}, + {"I;16L", "I", I16L_I}, + {"I", "I;16B", I_I16B}, + {"I;16B", "I", I16B_I}, - { "L", "I;16L", L_I16L }, - { "I;16L", "L", I16L_L }, - { "L", "I;16B", L_I16B }, - { "I;16B", "L", I16B_L }, + {"L", "I;16L", L_I16L}, + {"I;16L", "L", I16L_L}, + {"L", "I;16B", L_I16B}, + {"I;16B", "L", I16B_L}, - { "I;16", "F", I16L_F }, - { "I;16L", "F", I16L_F }, - { "I;16B", "F", I16B_F }, + {"I;16", "F", I16L_F}, + {"I;16L", "F", I16L_F}, + {"I;16B", "F", I16B_F}, - { NULL } -}; + {NULL}}; /* FIXME: translate indexed versions to pointer versions below this line */ @@ -1052,47 +991,46 @@ static struct { /* ------------------- */ static void -p2bit(UINT8* out, const UINT8* in, int xsize, const UINT8* palette) -{ +p2bit(UINT8 *out, const UINT8 *in, int xsize, const UINT8 *palette) { int x; /* FIXME: precalculate greyscale palette? */ - for (x = 0; x < xsize; x++) - *out++ = (L(&palette[in[x]*4]) >= 128000) ? 255 : 0; + for (x = 0; x < xsize; x++) { + *out++ = (L(&palette[in[x] * 4]) >= 128000) ? 255 : 0; + } } static void -pa2bit(UINT8* out, const UINT8* in, int xsize, const UINT8* palette) -{ +pa2bit(UINT8 *out, const UINT8 *in, int xsize, const UINT8 *palette) { int x; /* FIXME: precalculate greyscale palette? */ - for (x = 0; x < xsize; x++, in += 4) - *out++ = (L(&palette[in[0]*4]) >= 128000) ? 255 : 0; + for (x = 0; x < xsize; x++, in += 4) { + *out++ = (L(&palette[in[0] * 4]) >= 128000) ? 255 : 0; + } } static void -p2l(UINT8* out, const UINT8* in, int xsize, const UINT8* palette) -{ +p2l(UINT8 *out, const UINT8 *in, int xsize, const UINT8 *palette) { int x; /* FIXME: precalculate greyscale palette? */ - for (x = 0; x < xsize; x++) - *out++ = L(&palette[in[x]*4]) / 1000; + for (x = 0; x < xsize; x++) { + *out++ = L(&palette[in[x] * 4]) / 1000; + } } static void -pa2l(UINT8* out, const UINT8* in, int xsize, const UINT8* palette) -{ +pa2l(UINT8 *out, const UINT8 *in, int xsize, const UINT8 *palette) { int x; /* FIXME: precalculate greyscale palette? */ - for (x = 0; x < xsize; x++, in += 4) - *out++ = L(&palette[in[0]*4]) / 1000; + for (x = 0; x < xsize; x++, in += 4) { + *out++ = L(&palette[in[0] * 4]) / 1000; + } } static void -p2pa(UINT8* out, const UINT8* in, int xsize, const UINT8* palette) -{ +p2pa(UINT8 *out, const UINT8 *in, int xsize, const UINT8 *palette) { int x; for (x = 0; x < xsize; x++, in++) { - const UINT8* rgba = &palette[in[0]]; + const UINT8 *rgba = &palette[in[0]]; *out++ = in[0]; *out++ = in[0]; *out++ = in[0]; @@ -1101,72 +1039,67 @@ p2pa(UINT8* out, const UINT8* in, int xsize, const UINT8* palette) } static void -p2la(UINT8* out, const UINT8* in, int xsize, const UINT8* palette) -{ +p2la(UINT8 *out, const UINT8 *in, int xsize, const UINT8 *palette) { int x; /* FIXME: precalculate greyscale palette? */ - for (x = 0; x < xsize; x++, out+=4) { - const UINT8* rgba = &palette[*in++ * 4]; + for (x = 0; x < xsize; x++, out += 4) { + const UINT8 *rgba = &palette[*in++ * 4]; out[0] = out[1] = out[2] = L(rgba) / 1000; out[3] = rgba[3]; } } static void -pa2la(UINT8* out, const UINT8* in, int xsize, const UINT8* palette) -{ +pa2la(UINT8 *out, const UINT8 *in, int xsize, const UINT8 *palette) { int x; /* FIXME: precalculate greyscale palette? */ for (x = 0; x < xsize; x++, in += 4, out += 4) { - out[0] = out[1] = out[2] = L(&palette[in[0]*4]) / 1000; + out[0] = out[1] = out[2] = L(&palette[in[0] * 4]) / 1000; out[3] = in[3]; } } static void -p2i(UINT8* out_, const UINT8* in, int xsize, const UINT8* palette) -{ +p2i(UINT8 *out_, const UINT8 *in, int xsize, const UINT8 *palette) { int x; for (x = 0; x < xsize; x++, out_ += 4) { - INT32 v = L(&palette[in[x]*4]) / 1000; + INT32 v = L(&palette[in[x] * 4]) / 1000; memcpy(out_, &v, sizeof(v)); } } static void -pa2i(UINT8* out_, const UINT8* in, int xsize, const UINT8* palette) -{ +pa2i(UINT8 *out_, const UINT8 *in, int xsize, const UINT8 *palette) { int x; - INT32* out = (INT32*) out_; - for (x = 0; x < xsize; x++, in += 4) - *out++ = L(&palette[in[0]*4]) / 1000; + INT32 *out = (INT32 *)out_; + for (x = 0; x < xsize; x++, in += 4) { + *out++ = L(&palette[in[0] * 4]) / 1000; + } } static void -p2f(UINT8* out_, const UINT8* in, int xsize, const UINT8* palette) -{ +p2f(UINT8 *out_, const UINT8 *in, int xsize, const UINT8 *palette) { int x; for (x = 0; x < xsize; x++, out_ += 4) { - FLOAT32 v = L(&palette[in[x]*4]) / 1000.0F; + FLOAT32 v = L(&palette[in[x] * 4]) / 1000.0F; memcpy(out_, &v, sizeof(v)); } } static void -pa2f(UINT8* out_, const UINT8* in, int xsize, const UINT8* palette) -{ +pa2f(UINT8 *out_, const UINT8 *in, int xsize, const UINT8 *palette) { int x; - FLOAT32* out = (FLOAT32*) out_; - for (x = 0; x < xsize; x++, in += 4) - *out++ = (float) L(&palette[in[0]*4]) / 1000.0F; + FLOAT32 *out = (FLOAT32 *)out_; + for (x = 0; x < xsize; x++, in += 4) { + *out++ = (float)L(&palette[in[0] * 4]) / 1000.0F; + } } static void -p2rgb(UINT8* out, const UINT8* in, int xsize, const UINT8* palette) -{ +p2rgb(UINT8 *out, const UINT8 *in, int xsize, const UINT8 *palette) { int x; for (x = 0; x < xsize; x++) { - const UINT8* rgb = &palette[*in++ * 4]; + const UINT8 *rgb = &palette[*in++ * 4]; *out++ = rgb[0]; *out++ = rgb[1]; *out++ = rgb[2]; @@ -1175,11 +1108,10 @@ p2rgb(UINT8* out, const UINT8* in, int xsize, const UINT8* palette) } static void -pa2rgb(UINT8* out, const UINT8* in, int xsize, const UINT8* palette) -{ +pa2rgb(UINT8 *out, const UINT8 *in, int xsize, const UINT8 *palette) { int x; for (x = 0; x < xsize; x++, in += 4) { - const UINT8* rgb = &palette[in[0] * 4]; + const UINT8 *rgb = &palette[in[0] * 4]; *out++ = rgb[0]; *out++ = rgb[1]; *out++ = rgb[2]; @@ -1188,33 +1120,30 @@ pa2rgb(UINT8* out, const UINT8* in, int xsize, const UINT8* palette) } static void -p2hsv(UINT8* out, const UINT8* in, int xsize, const UINT8* palette) -{ +p2hsv(UINT8 *out, const UINT8 *in, int xsize, const UINT8 *palette) { int x; for (x = 0; x < xsize; x++, out += 4) { - const UINT8* rgb = &palette[*in++ * 4]; + const UINT8 *rgb = &palette[*in++ * 4]; rgb2hsv_row(out, rgb); out[3] = 255; } } static void -pa2hsv(UINT8* out, const UINT8* in, int xsize, const UINT8* palette) -{ +pa2hsv(UINT8 *out, const UINT8 *in, int xsize, const UINT8 *palette) { int x; for (x = 0; x < xsize; x++, in += 4, out += 4) { - const UINT8* rgb = &palette[in[0] * 4]; + const UINT8 *rgb = &palette[in[0] * 4]; rgb2hsv_row(out, rgb); out[3] = 255; } } static void -p2rgba(UINT8* out, const UINT8* in, int xsize, const UINT8* palette) -{ +p2rgba(UINT8 *out, const UINT8 *in, int xsize, const UINT8 *palette) { int x; for (x = 0; x < xsize; x++) { - const UINT8* rgba = &palette[*in++ * 4]; + const UINT8 *rgba = &palette[*in++ * 4]; *out++ = rgba[0]; *out++ = rgba[1]; *out++ = rgba[2]; @@ -1223,11 +1152,10 @@ p2rgba(UINT8* out, const UINT8* in, int xsize, const UINT8* palette) } static void -pa2rgba(UINT8* out, const UINT8* in, int xsize, const UINT8* palette) -{ +pa2rgba(UINT8 *out, const UINT8 *in, int xsize, const UINT8 *palette) { int x; for (x = 0; x < xsize; x++, in += 4) { - const UINT8* rgb = &palette[in[0] * 4]; + const UINT8 *rgb = &palette[in[0] * 4]; *out++ = rgb[0]; *out++ = rgb[1]; *out++ = rgb[2]; @@ -1236,83 +1164,85 @@ pa2rgba(UINT8* out, const UINT8* in, int xsize, const UINT8* palette) } static void -p2cmyk(UINT8* out, const UINT8* in, int xsize, const UINT8* palette) -{ +p2cmyk(UINT8 *out, const UINT8 *in, int xsize, const UINT8 *palette) { p2rgb(out, in, xsize, palette); rgb2cmyk(out, out, xsize); } static void -pa2cmyk(UINT8* out, const UINT8* in, int xsize, const UINT8* palette) -{ +pa2cmyk(UINT8 *out, const UINT8 *in, int xsize, const UINT8 *palette) { pa2rgb(out, in, xsize, palette); rgb2cmyk(out, out, xsize); } static void -p2ycbcr(UINT8* out, const UINT8* in, int xsize, const UINT8* palette) -{ +p2ycbcr(UINT8 *out, const UINT8 *in, int xsize, const UINT8 *palette) { p2rgb(out, in, xsize, palette); ImagingConvertRGB2YCbCr(out, out, xsize); } static void -pa2ycbcr(UINT8* out, const UINT8* in, int xsize, const UINT8* palette) -{ +pa2ycbcr(UINT8 *out, const UINT8 *in, int xsize, const UINT8 *palette) { pa2rgb(out, in, xsize, palette); ImagingConvertRGB2YCbCr(out, out, xsize); } static Imaging -frompalette(Imaging imOut, Imaging imIn, const char *mode) -{ +frompalette(Imaging imOut, Imaging imIn, const char *mode) { ImagingSectionCookie cookie; int alpha; int y; - void (*convert)(UINT8*, const UINT8*, int, const UINT8*); + void (*convert)(UINT8 *, const UINT8 *, int, const UINT8 *); /* Map palette image to L, RGB, RGBA, or CMYK */ - if (!imIn->palette) - return (Imaging) ImagingError_ValueError("no palette"); + if (!imIn->palette) { + return (Imaging)ImagingError_ValueError("no palette"); + } alpha = !strcmp(imIn->mode, "PA"); - if (strcmp(mode, "1") == 0) + if (strcmp(mode, "1") == 0) { convert = alpha ? pa2bit : p2bit; - else if (strcmp(mode, "L") == 0) + } else if (strcmp(mode, "L") == 0) { convert = alpha ? pa2l : p2l; - else if (strcmp(mode, "LA") == 0) + } else if (strcmp(mode, "LA") == 0) { convert = alpha ? pa2la : p2la; - else if (strcmp(mode, "PA") == 0) + } else if (strcmp(mode, "PA") == 0) { convert = p2pa; - else if (strcmp(mode, "I") == 0) + } else if (strcmp(mode, "I") == 0) { convert = alpha ? pa2i : p2i; - else if (strcmp(mode, "F") == 0) + } else if (strcmp(mode, "F") == 0) { convert = alpha ? pa2f : p2f; - else if (strcmp(mode, "RGB") == 0) + } else if (strcmp(mode, "RGB") == 0) { convert = alpha ? pa2rgb : p2rgb; - else if (strcmp(mode, "RGBA") == 0) + } else if (strcmp(mode, "RGBA") == 0) { convert = alpha ? pa2rgba : p2rgba; - else if (strcmp(mode, "RGBX") == 0) + } else if (strcmp(mode, "RGBX") == 0) { convert = alpha ? pa2rgba : p2rgba; - else if (strcmp(mode, "CMYK") == 0) + } else if (strcmp(mode, "CMYK") == 0) { convert = alpha ? pa2cmyk : p2cmyk; - else if (strcmp(mode, "YCbCr") == 0) + } else if (strcmp(mode, "YCbCr") == 0) { convert = alpha ? pa2ycbcr : p2ycbcr; - else if (strcmp(mode, "HSV") == 0) + } else if (strcmp(mode, "HSV") == 0) { convert = alpha ? pa2hsv : p2hsv; - else - return (Imaging) ImagingError_ValueError("conversion not supported"); + } else { + return (Imaging)ImagingError_ValueError("conversion not supported"); + } imOut = ImagingNew2Dirty(mode, imOut, imIn); - if (!imOut) + if (!imOut) { return NULL; + } ImagingSectionEnter(&cookie); - for (y = 0; y < imIn->ysize; y++) - (*convert)((UINT8*) imOut->image[y], (UINT8*) imIn->image[y], - imIn->xsize, imIn->palette->palette); + for (y = 0; y < imIn->ysize; y++) { + (*convert)( + (UINT8 *)imOut->image[y], + (UINT8 *)imIn->image[y], + imIn->xsize, + imIn->palette->palette); + } ImagingSectionLeave(&cookie); return imOut; @@ -1322,35 +1252,44 @@ frompalette(Imaging imOut, Imaging imIn, const char *mode) #pragma optimize("", off) #endif static Imaging -topalette(Imaging imOut, Imaging imIn, const char *mode, ImagingPalette inpalette, int dither) -{ +topalette( + Imaging imOut, + Imaging imIn, + const char *mode, + ImagingPalette inpalette, + int dither) { ImagingSectionCookie cookie; int alpha; int x, y; - ImagingPalette palette = inpalette;; + ImagingPalette palette = inpalette; + ; /* Map L or RGB/RGBX/RGBA to palette image */ - if (strcmp(imIn->mode, "L") != 0 && strncmp(imIn->mode, "RGB", 3) != 0) - return (Imaging) ImagingError_ValueError("conversion not supported"); + if (strcmp(imIn->mode, "L") != 0 && strncmp(imIn->mode, "RGB", 3) != 0) { + return (Imaging)ImagingError_ValueError("conversion not supported"); + } alpha = !strcmp(mode, "PA"); if (palette == NULL) { - /* FIXME: make user configurable */ - if (imIn->bands == 1) - palette = ImagingPaletteNew("RGB"); /* Initialised to grey ramp */ - else - palette = ImagingPaletteNewBrowser(); /* Standard colour cube */ + /* FIXME: make user configurable */ + if (imIn->bands == 1) { + palette = ImagingPaletteNew("RGB"); /* Initialised to grey ramp */ + } else { + palette = ImagingPaletteNewBrowser(); /* Standard colour cube */ + } } - if (!palette) - return (Imaging) ImagingError_ValueError("no palette"); + if (!palette) { + return (Imaging)ImagingError_ValueError("no palette"); + } imOut = ImagingNew2Dirty(mode, imOut, imIn); if (!imOut) { - if (palette != inpalette) - ImagingPaletteDelete(palette); - return NULL; + if (palette != inpalette) { + ImagingPaletteDelete(palette); + } + return NULL; } ImagingPaletteDelete(imOut->palette); @@ -1363,7 +1302,7 @@ topalette(Imaging imOut, Imaging imIn, const char *mode, ImagingPalette inpalett ImagingSectionEnter(&cookie); for (y = 0; y < imIn->ysize; y++) { if (alpha) { - l2la((UINT8*) imOut->image[y], (UINT8*) imIn->image[y], imIn->xsize); + l2la((UINT8 *)imOut->image[y], (UINT8 *)imIn->image[y], imIn->xsize); } else { memcpy(imOut->image[y], imIn->image[y], imIn->linesize); } @@ -1376,15 +1315,16 @@ topalette(Imaging imOut, Imaging imIn, const char *mode, ImagingPalette inpalett /* Create mapping cache */ if (ImagingPaletteCachePrepare(palette) < 0) { ImagingDelete(imOut); - if (palette != inpalette) - ImagingPaletteDelete(palette); + if (palette != inpalette) { + ImagingPaletteDelete(palette); + } return NULL; } if (dither) { /* floyd-steinberg dither */ - int* errors; + int *errors; errors = calloc(imIn->xsize + 1, sizeof(int) * 3); if (!errors) { ImagingDelete(imOut); @@ -1397,9 +1337,9 @@ topalette(Imaging imOut, Imaging imIn, const char *mode, ImagingPalette inpalett int r, r0, r1, r2; int g, g0, g1, g2; int b, b0, b1, b2; - UINT8* in = (UINT8*) imIn->image[y]; - UINT8* out = alpha ? (UINT8*) imOut->image32[y] : imOut->image8[y]; - int* e = errors; + UINT8 *in = (UINT8 *)imIn->image[y]; + UINT8 *out = alpha ? (UINT8 *)imOut->image32[y] : imOut->image8[y]; + int *e = errors; r = r0 = r1 = 0; g = g0 = g1 = 0; @@ -1407,100 +1347,121 @@ topalette(Imaging imOut, Imaging imIn, const char *mode, ImagingPalette inpalett for (x = 0; x < imIn->xsize; x++, in += 4) { int d2; - INT16* cache; + INT16 *cache; - r = CLIP8(in[0] + (r + e[3+0])/16); - g = CLIP8(in[1] + (g + e[3+1])/16); - b = CLIP8(in[2] + (b + e[3+2])/16); + r = CLIP8(in[0] + (r + e[3 + 0]) / 16); + g = CLIP8(in[1] + (g + e[3 + 1]) / 16); + b = CLIP8(in[2] + (b + e[3 + 2]) / 16); /* get closest colour */ cache = &ImagingPaletteCache(palette, r, g, b); - if (cache[0] == 0x100) + if (cache[0] == 0x100) { ImagingPaletteCacheUpdate(palette, r, g, b); + } if (alpha) { - out[x*4] = out[x*4+1] = out[x*4+2] = (UINT8) cache[0]; - out[x*4+3] = 255; + out[x * 4] = out[x * 4 + 1] = out[x * 4 + 2] = (UINT8)cache[0]; + out[x * 4 + 3] = 255; } else { - out[x] = (UINT8) cache[0]; + out[x] = (UINT8)cache[0]; } - r -= (int) palette->palette[cache[0]*4]; - g -= (int) palette->palette[cache[0]*4+1]; - b -= (int) palette->palette[cache[0]*4+2]; + r -= (int)palette->palette[cache[0] * 4]; + g -= (int)palette->palette[cache[0] * 4 + 1]; + b -= (int)palette->palette[cache[0] * 4 + 2]; /* propagate errors (don't ask ;-) */ - r2 = r; d2 = r + r; r += d2; e[0] = r + r0; - r += d2; r0 = r + r1; r1 = r2; r += d2; - g2 = g; d2 = g + g; g += d2; e[1] = g + g0; - g += d2; g0 = g + g1; g1 = g2; g += d2; - b2 = b; d2 = b + b; b += d2; e[2] = b + b0; - b += d2; b0 = b + b1; b1 = b2; b += d2; + r2 = r; + d2 = r + r; + r += d2; + e[0] = r + r0; + r += d2; + r0 = r + r1; + r1 = r2; + r += d2; + g2 = g; + d2 = g + g; + g += d2; + e[1] = g + g0; + g += d2; + g0 = g + g1; + g1 = g2; + g += d2; + b2 = b; + d2 = b + b; + b += d2; + e[2] = b + b0; + b += d2; + b0 = b + b1; + b1 = b2; + b += d2; e += 3; - } e[0] = b0; e[1] = b1; e[2] = b2; - } ImagingSectionLeave(&cookie); free(errors); } else { - /* closest colour */ ImagingSectionEnter(&cookie); for (y = 0; y < imIn->ysize; y++) { int r, g, b; - UINT8* in = (UINT8*) imIn->image[y]; - UINT8* out = alpha ? (UINT8*) imOut->image32[y] : imOut->image8[y]; + UINT8 *in = (UINT8 *)imIn->image[y]; + UINT8 *out = alpha ? (UINT8 *)imOut->image32[y] : imOut->image8[y]; for (x = 0; x < imIn->xsize; x++, in += 4) { - INT16* cache; + INT16 *cache; - r = in[0]; g = in[1]; b = in[2]; + r = in[0]; + g = in[1]; + b = in[2]; /* get closest colour */ cache = &ImagingPaletteCache(palette, r, g, b); - if (cache[0] == 0x100) + if (cache[0] == 0x100) { ImagingPaletteCacheUpdate(palette, r, g, b); + } if (alpha) { - out[x*4] = out[x*4+1] = out[x*4+2] = (UINT8) cache[0]; - out[x*4+3] = 255; + out[x * 4] = out[x * 4 + 1] = out[x * 4 + 2] = (UINT8)cache[0]; + out[x * 4 + 3] = 255; } else { - out[x] = (UINT8) cache[0]; + out[x] = (UINT8)cache[0]; } } } ImagingSectionLeave(&cookie); - } - if (inpalette != palette) - ImagingPaletteCacheDelete(palette); + if (inpalette != palette) { + ImagingPaletteCacheDelete(palette); + } } - if (inpalette != palette) - ImagingPaletteDelete(palette); + if (inpalette != palette) { + ImagingPaletteDelete(palette); + } return imOut; } static Imaging -tobilevel(Imaging imOut, Imaging imIn, int dither) -{ +tobilevel(Imaging imOut, Imaging imIn, int dither) { ImagingSectionCookie cookie; int x, y; - int* errors; + int *errors; /* Map L or RGB to dithered 1 image */ - if (strcmp(imIn->mode, "L") != 0 && strcmp(imIn->mode, "RGB") != 0) - return (Imaging) ImagingError_ValueError("conversion not supported"); + if (strcmp(imIn->mode, "L") != 0 && strcmp(imIn->mode, "RGB") != 0) { + return (Imaging)ImagingError_ValueError("conversion not supported"); + } imOut = ImagingNew2Dirty("1", imOut, imIn); - if (!imOut) + if (!imOut) { return NULL; + } errors = calloc(imIn->xsize + 1, sizeof(int)); if (!errors) { @@ -1509,59 +1470,64 @@ tobilevel(Imaging imOut, Imaging imIn, int dither) } if (imIn->bands == 1) { - /* map each pixel to black or white, using error diffusion */ ImagingSectionEnter(&cookie); for (y = 0; y < imIn->ysize; y++) { int l, l0, l1, l2, d2; - UINT8* in = (UINT8*) imIn->image[y]; - UINT8* out = imOut->image8[y]; + UINT8 *in = (UINT8 *)imIn->image[y]; + UINT8 *out = imOut->image8[y]; l = l0 = l1 = 0; for (x = 0; x < imIn->xsize; x++) { - /* pick closest colour */ - l = CLIP8(in[x] + (l + errors[x+1])/16); + l = CLIP8(in[x] + (l + errors[x + 1]) / 16); out[x] = (l > 128) ? 255 : 0; /* propagate errors */ - l -= (int) out[x]; - l2 = l; d2 = l + l; l += d2; errors[x] = l + l0; - l += d2; l0 = l + l1; l1 = l2; l += d2; + l -= (int)out[x]; + l2 = l; + d2 = l + l; + l += d2; + errors[x] = l + l0; + l += d2; + l0 = l + l1; + l1 = l2; + l += d2; } errors[x] = l0; - } ImagingSectionLeave(&cookie); } else { - /* map each pixel to black or white, using error diffusion */ ImagingSectionEnter(&cookie); for (y = 0; y < imIn->ysize; y++) { int l, l0, l1, l2, d2; - UINT8* in = (UINT8*) imIn->image[y]; - UINT8* out = imOut->image8[y]; + UINT8 *in = (UINT8 *)imIn->image[y]; + UINT8 *out = imOut->image8[y]; l = l0 = l1 = 0; for (x = 0; x < imIn->xsize; x++, in += 4) { - /* pick closest colour */ - l = CLIP8(L(in)/1000 + (l + errors[x+1])/16); + l = CLIP8(L(in) / 1000 + (l + errors[x + 1]) / 16); out[x] = (l > 128) ? 255 : 0; /* propagate errors */ - l -= (int) out[x]; - l2 = l; d2 = l + l; l += d2; errors[x] = l + l0; - l += d2; l0 = l + l1; l1 = l2; l += d2; - + l -= (int)out[x]; + l2 = l; + d2 = l + l; + l += d2; + errors[x] = l + l0; + l += d2; + l0 = l + l1; + l1 = l2; + l += d2; } errors[x] = l0; - } ImagingSectionLeave(&cookie); } @@ -1575,117 +1541,118 @@ tobilevel(Imaging imOut, Imaging imIn, int dither) #endif static Imaging -convert(Imaging imOut, Imaging imIn, const char *mode, - ImagingPalette palette, int dither) -{ +convert( + Imaging imOut, Imaging imIn, const char *mode, ImagingPalette palette, int dither) { ImagingSectionCookie cookie; ImagingShuffler convert; int y; - if (!imIn) - return (Imaging) ImagingError_ModeError(); + if (!imIn) { + return (Imaging)ImagingError_ModeError(); + } if (!mode) { /* Map palette image to full depth */ - if (!imIn->palette) - return (Imaging) ImagingError_ModeError(); + if (!imIn->palette) { + return (Imaging)ImagingError_ModeError(); + } mode = imIn->palette->mode; - } else + } else { /* Same mode? */ - if (!strcmp(imIn->mode, mode)) + if (!strcmp(imIn->mode, mode)) { return ImagingCopy2(imOut, imIn); - + } + } /* test for special conversions */ - if (strcmp(imIn->mode, "P") == 0 || strcmp(imIn->mode, "PA") == 0) + if (strcmp(imIn->mode, "P") == 0 || strcmp(imIn->mode, "PA") == 0) { return frompalette(imOut, imIn, mode); + } - if (strcmp(mode, "P") == 0 || strcmp(mode, "PA") == 0) + if (strcmp(mode, "P") == 0 || strcmp(mode, "PA") == 0) { return topalette(imOut, imIn, mode, palette, dither); + } - if (dither && strcmp(mode, "1") == 0) + if (dither && strcmp(mode, "1") == 0) { return tobilevel(imOut, imIn, dither); - + } /* standard conversion machinery */ convert = NULL; - for (y = 0; converters[y].from; y++) + for (y = 0; converters[y].from; y++) { if (!strcmp(imIn->mode, converters[y].from) && !strcmp(mode, converters[y].to)) { convert = converters[y].convert; break; } - - if (!convert) -#ifdef notdef - return (Imaging) ImagingError_ValueError("conversion not supported"); -#else - { - static char buf[256]; - /* FIXME: may overflow if mode is too large */ - sprintf(buf, "conversion from %s to %s not supported", imIn->mode, mode); - return (Imaging) ImagingError_ValueError(buf); } + + if (!convert) { +#ifdef notdef + return (Imaging)ImagingError_ValueError("conversion not supported"); +#else + static char buf[256]; + /* FIXME: may overflow if mode is too large */ + sprintf(buf, "conversion from %s to %s not supported", imIn->mode, mode); + return (Imaging)ImagingError_ValueError(buf); #endif + } imOut = ImagingNew2Dirty(mode, imOut, imIn); - if (!imOut) + if (!imOut) { return NULL; + } ImagingSectionEnter(&cookie); - for (y = 0; y < imIn->ysize; y++) - (*convert)((UINT8*) imOut->image[y], (UINT8*) imIn->image[y], - imIn->xsize); + for (y = 0; y < imIn->ysize; y++) { + (*convert)((UINT8 *)imOut->image[y], (UINT8 *)imIn->image[y], imIn->xsize); + } ImagingSectionLeave(&cookie); return imOut; } Imaging -ImagingConvert(Imaging imIn, const char *mode, - ImagingPalette palette, int dither) -{ +ImagingConvert(Imaging imIn, const char *mode, ImagingPalette palette, int dither) { return convert(NULL, imIn, mode, palette, dither); } Imaging -ImagingConvert2(Imaging imOut, Imaging imIn) -{ +ImagingConvert2(Imaging imOut, Imaging imIn) { return convert(imOut, imIn, imOut->mode, NULL, 0); } - Imaging -ImagingConvertTransparent(Imaging imIn, const char *mode, - int r, int g, int b) -{ +ImagingConvertTransparent(Imaging imIn, const char *mode, int r, int g, int b) { ImagingSectionCookie cookie; ImagingShuffler convert; Imaging imOut = NULL; int y; - if (!imIn){ - return (Imaging) ImagingError_ModeError(); + if (!imIn) { + return (Imaging)ImagingError_ModeError(); } - if (!((strcmp(imIn->mode, "RGB") == 0 || - strcmp(imIn->mode, "1") == 0 || - strcmp(imIn->mode, "I") == 0 || - strcmp(imIn->mode, "L") == 0) - && strcmp(mode, "RGBA") == 0)) + if (!((strcmp(imIn->mode, "RGB") == 0 || strcmp(imIn->mode, "1") == 0 || + strcmp(imIn->mode, "I") == 0 || strcmp(imIn->mode, "L") == 0) && + strcmp(mode, "RGBA") == 0)) #ifdef notdef { - return (Imaging) ImagingError_ValueError("conversion not supported"); + return (Imaging)ImagingError_ValueError("conversion not supported"); } #else { - static char buf[256]; - /* FIXME: may overflow if mode is too large */ - sprintf(buf, "conversion from %s to %s not supported in convert_transparent", imIn->mode, mode); - return (Imaging) ImagingError_ValueError(buf); + static char buf[256]; + /* FIXME: may overflow if mode is too large */ + sprintf( + buf, + "conversion from %s to %s not supported in convert_transparent", + imIn->mode, + mode); + return (Imaging)ImagingError_ValueError(buf); } #endif @@ -1703,41 +1670,39 @@ ImagingConvertTransparent(Imaging imIn, const char *mode, } imOut = ImagingNew2Dirty(mode, imOut, imIn); - if (!imOut){ + if (!imOut) { return NULL; } ImagingSectionEnter(&cookie); for (y = 0; y < imIn->ysize; y++) { - (*convert)((UINT8*) imOut->image[y], (UINT8*) imIn->image[y], - imIn->xsize); - rgbT2rgba((UINT8*) imOut->image[y], imIn->xsize, r, g, b); + (*convert)((UINT8 *)imOut->image[y], (UINT8 *)imIn->image[y], imIn->xsize); + rgbT2rgba((UINT8 *)imOut->image[y], imIn->xsize, r, g, b); } ImagingSectionLeave(&cookie); return imOut; - } Imaging -ImagingConvertInPlace(Imaging imIn, const char* mode) -{ +ImagingConvertInPlace(Imaging imIn, const char *mode) { ImagingSectionCookie cookie; ImagingShuffler convert; int y; /* limited support for inplace conversion */ - if (strcmp(imIn->mode, "L") == 0 && strcmp(mode, "1") == 0) + if (strcmp(imIn->mode, "L") == 0 && strcmp(mode, "1") == 0) { convert = l2bit; - else if (strcmp(imIn->mode, "1") == 0 && strcmp(mode, "L") == 0) + } else if (strcmp(imIn->mode, "1") == 0 && strcmp(mode, "L") == 0) { convert = bit2l; - else + } else { return ImagingError_ModeError(); + } ImagingSectionEnter(&cookie); - for (y = 0; y < imIn->ysize; y++) - (*convert)((UINT8*) imIn->image[y], (UINT8*) imIn->image[y], - imIn->xsize); + for (y = 0; y < imIn->ysize; y++) { + (*convert)((UINT8 *)imIn->image[y], (UINT8 *)imIn->image[y], imIn->xsize); + } ImagingSectionLeave(&cookie); return imIn; diff --git a/src/libImaging/ConvertYCbCr.c b/src/libImaging/ConvertYCbCr.c index 6ce549111..142f065e5 100644 --- a/src/libImaging/ConvertYCbCr.c +++ b/src/libImaging/ConvertYCbCr.c @@ -5,7 +5,7 @@ * code to convert YCbCr data * * history: - * 98-07-01 hk Created + * 98-07-01 hk Created * * Copyright (c) Secret Labs AB 1998 * @@ -28,356 +28,332 @@ #define SCALE 6 /* bits */ -static INT16 Y_R[] = { 0, 19, 38, 57, 77, 96, 115, 134, 153, 172, 191, -210, 230, 249, 268, 287, 306, 325, 344, 364, 383, 402, 421, 440, 459, -478, 498, 517, 536, 555, 574, 593, 612, 631, 651, 670, 689, 708, 727, -746, 765, 785, 804, 823, 842, 861, 880, 899, 919, 938, 957, 976, 995, -1014, 1033, 1052, 1072, 1091, 1110, 1129, 1148, 1167, 1186, 1206, -1225, 1244, 1263, 1282, 1301, 1320, 1340, 1359, 1378, 1397, 1416, -1435, 1454, 1473, 1493, 1512, 1531, 1550, 1569, 1588, 1607, 1627, -1646, 1665, 1684, 1703, 1722, 1741, 1761, 1780, 1799, 1818, 1837, -1856, 1875, 1894, 1914, 1933, 1952, 1971, 1990, 2009, 2028, 2048, -2067, 2086, 2105, 2124, 2143, 2162, 2182, 2201, 2220, 2239, 2258, -2277, 2296, 2315, 2335, 2354, 2373, 2392, 2411, 2430, 2449, 2469, -2488, 2507, 2526, 2545, 2564, 2583, 2602, 2622, 2641, 2660, 2679, -2698, 2717, 2736, 2756, 2775, 2794, 2813, 2832, 2851, 2870, 2890, -2909, 2928, 2947, 2966, 2985, 3004, 3023, 3043, 3062, 3081, 3100, -3119, 3138, 3157, 3177, 3196, 3215, 3234, 3253, 3272, 3291, 3311, -3330, 3349, 3368, 3387, 3406, 3425, 3444, 3464, 3483, 3502, 3521, -3540, 3559, 3578, 3598, 3617, 3636, 3655, 3674, 3693, 3712, 3732, -3751, 3770, 3789, 3808, 3827, 3846, 3865, 3885, 3904, 3923, 3942, -3961, 3980, 3999, 4019, 4038, 4057, 4076, 4095, 4114, 4133, 4153, -4172, 4191, 4210, 4229, 4248, 4267, 4286, 4306, 4325, 4344, 4363, -4382, 4401, 4420, 4440, 4459, 4478, 4497, 4516, 4535, 4554, 4574, -4593, 4612, 4631, 4650, 4669, 4688, 4707, 4727, 4746, 4765, 4784, -4803, 4822, 4841, 4861, 4880 }; +static INT16 Y_R[] = { + 0, 19, 38, 57, 77, 96, 115, 134, 153, 172, 191, 210, 230, 249, + 268, 287, 306, 325, 344, 364, 383, 402, 421, 440, 459, 478, 498, 517, + 536, 555, 574, 593, 612, 631, 651, 670, 689, 708, 727, 746, 765, 785, + 804, 823, 842, 861, 880, 899, 919, 938, 957, 976, 995, 1014, 1033, 1052, + 1072, 1091, 1110, 1129, 1148, 1167, 1186, 1206, 1225, 1244, 1263, 1282, 1301, 1320, + 1340, 1359, 1378, 1397, 1416, 1435, 1454, 1473, 1493, 1512, 1531, 1550, 1569, 1588, + 1607, 1627, 1646, 1665, 1684, 1703, 1722, 1741, 1761, 1780, 1799, 1818, 1837, 1856, + 1875, 1894, 1914, 1933, 1952, 1971, 1990, 2009, 2028, 2048, 2067, 2086, 2105, 2124, + 2143, 2162, 2182, 2201, 2220, 2239, 2258, 2277, 2296, 2315, 2335, 2354, 2373, 2392, + 2411, 2430, 2449, 2469, 2488, 2507, 2526, 2545, 2564, 2583, 2602, 2622, 2641, 2660, + 2679, 2698, 2717, 2736, 2756, 2775, 2794, 2813, 2832, 2851, 2870, 2890, 2909, 2928, + 2947, 2966, 2985, 3004, 3023, 3043, 3062, 3081, 3100, 3119, 3138, 3157, 3177, 3196, + 3215, 3234, 3253, 3272, 3291, 3311, 3330, 3349, 3368, 3387, 3406, 3425, 3444, 3464, + 3483, 3502, 3521, 3540, 3559, 3578, 3598, 3617, 3636, 3655, 3674, 3693, 3712, 3732, + 3751, 3770, 3789, 3808, 3827, 3846, 3865, 3885, 3904, 3923, 3942, 3961, 3980, 3999, + 4019, 4038, 4057, 4076, 4095, 4114, 4133, 4153, 4172, 4191, 4210, 4229, 4248, 4267, + 4286, 4306, 4325, 4344, 4363, 4382, 4401, 4420, 4440, 4459, 4478, 4497, 4516, 4535, + 4554, 4574, 4593, 4612, 4631, 4650, 4669, 4688, 4707, 4727, 4746, 4765, 4784, 4803, + 4822, 4841, 4861, 4880}; -static INT16 Y_G[] = { 0, 38, 75, 113, 150, 188, 225, 263, 301, 338, -376, 413, 451, 488, 526, 564, 601, 639, 676, 714, 751, 789, 826, 864, -902, 939, 977, 1014, 1052, 1089, 1127, 1165, 1202, 1240, 1277, 1315, -1352, 1390, 1428, 1465, 1503, 1540, 1578, 1615, 1653, 1691, 1728, -1766, 1803, 1841, 1878, 1916, 1954, 1991, 2029, 2066, 2104, 2141, -2179, 2217, 2254, 2292, 2329, 2367, 2404, 2442, 2479, 2517, 2555, -2592, 2630, 2667, 2705, 2742, 2780, 2818, 2855, 2893, 2930, 2968, -3005, 3043, 3081, 3118, 3156, 3193, 3231, 3268, 3306, 3344, 3381, -3419, 3456, 3494, 3531, 3569, 3607, 3644, 3682, 3719, 3757, 3794, -3832, 3870, 3907, 3945, 3982, 4020, 4057, 4095, 4132, 4170, 4208, -4245, 4283, 4320, 4358, 4395, 4433, 4471, 4508, 4546, 4583, 4621, -4658, 4696, 4734, 4771, 4809, 4846, 4884, 4921, 4959, 4997, 5034, -5072, 5109, 5147, 5184, 5222, 5260, 5297, 5335, 5372, 5410, 5447, -5485, 5522, 5560, 5598, 5635, 5673, 5710, 5748, 5785, 5823, 5861, -5898, 5936, 5973, 6011, 6048, 6086, 6124, 6161, 6199, 6236, 6274, -6311, 6349, 6387, 6424, 6462, 6499, 6537, 6574, 6612, 6650, 6687, -6725, 6762, 6800, 6837, 6875, 6913, 6950, 6988, 7025, 7063, 7100, -7138, 7175, 7213, 7251, 7288, 7326, 7363, 7401, 7438, 7476, 7514, -7551, 7589, 7626, 7664, 7701, 7739, 7777, 7814, 7852, 7889, 7927, -7964, 8002, 8040, 8077, 8115, 8152, 8190, 8227, 8265, 8303, 8340, -8378, 8415, 8453, 8490, 8528, 8566, 8603, 8641, 8678, 8716, 8753, -8791, 8828, 8866, 8904, 8941, 8979, 9016, 9054, 9091, 9129, 9167, -9204, 9242, 9279, 9317, 9354, 9392, 9430, 9467, 9505, 9542, 9580 }; +static INT16 Y_G[] = { + 0, 38, 75, 113, 150, 188, 225, 263, 301, 338, 376, 413, 451, 488, + 526, 564, 601, 639, 676, 714, 751, 789, 826, 864, 902, 939, 977, 1014, + 1052, 1089, 1127, 1165, 1202, 1240, 1277, 1315, 1352, 1390, 1428, 1465, 1503, 1540, + 1578, 1615, 1653, 1691, 1728, 1766, 1803, 1841, 1878, 1916, 1954, 1991, 2029, 2066, + 2104, 2141, 2179, 2217, 2254, 2292, 2329, 2367, 2404, 2442, 2479, 2517, 2555, 2592, + 2630, 2667, 2705, 2742, 2780, 2818, 2855, 2893, 2930, 2968, 3005, 3043, 3081, 3118, + 3156, 3193, 3231, 3268, 3306, 3344, 3381, 3419, 3456, 3494, 3531, 3569, 3607, 3644, + 3682, 3719, 3757, 3794, 3832, 3870, 3907, 3945, 3982, 4020, 4057, 4095, 4132, 4170, + 4208, 4245, 4283, 4320, 4358, 4395, 4433, 4471, 4508, 4546, 4583, 4621, 4658, 4696, + 4734, 4771, 4809, 4846, 4884, 4921, 4959, 4997, 5034, 5072, 5109, 5147, 5184, 5222, + 5260, 5297, 5335, 5372, 5410, 5447, 5485, 5522, 5560, 5598, 5635, 5673, 5710, 5748, + 5785, 5823, 5861, 5898, 5936, 5973, 6011, 6048, 6086, 6124, 6161, 6199, 6236, 6274, + 6311, 6349, 6387, 6424, 6462, 6499, 6537, 6574, 6612, 6650, 6687, 6725, 6762, 6800, + 6837, 6875, 6913, 6950, 6988, 7025, 7063, 7100, 7138, 7175, 7213, 7251, 7288, 7326, + 7363, 7401, 7438, 7476, 7514, 7551, 7589, 7626, 7664, 7701, 7739, 7777, 7814, 7852, + 7889, 7927, 7964, 8002, 8040, 8077, 8115, 8152, 8190, 8227, 8265, 8303, 8340, 8378, + 8415, 8453, 8490, 8528, 8566, 8603, 8641, 8678, 8716, 8753, 8791, 8828, 8866, 8904, + 8941, 8979, 9016, 9054, 9091, 9129, 9167, 9204, 9242, 9279, 9317, 9354, 9392, 9430, + 9467, 9505, 9542, 9580}; -static INT16 Y_B[] = { 0, 7, 15, 22, 29, 36, 44, 51, 58, 66, 73, 80, -88, 95, 102, 109, 117, 124, 131, 139, 146, 153, 161, 168, 175, 182, -190, 197, 204, 212, 219, 226, 233, 241, 248, 255, 263, 270, 277, 285, -292, 299, 306, 314, 321, 328, 336, 343, 350, 358, 365, 372, 379, 387, -394, 401, 409, 416, 423, 430, 438, 445, 452, 460, 467, 474, 482, 489, -496, 503, 511, 518, 525, 533, 540, 547, 554, 562, 569, 576, 584, 591, -598, 606, 613, 620, 627, 635, 642, 649, 657, 664, 671, 679, 686, 693, -700, 708, 715, 722, 730, 737, 744, 751, 759, 766, 773, 781, 788, 795, -803, 810, 817, 824, 832, 839, 846, 854, 861, 868, 876, 883, 890, 897, -905, 912, 919, 927, 934, 941, 948, 956, 963, 970, 978, 985, 992, 1000, -1007, 1014, 1021, 1029, 1036, 1043, 1051, 1058, 1065, 1073, 1080, -1087, 1094, 1102, 1109, 1116, 1124, 1131, 1138, 1145, 1153, 1160, -1167, 1175, 1182, 1189, 1197, 1204, 1211, 1218, 1226, 1233, 1240, -1248, 1255, 1262, 1270, 1277, 1284, 1291, 1299, 1306, 1313, 1321, -1328, 1335, 1342, 1350, 1357, 1364, 1372, 1379, 1386, 1394, 1401, -1408, 1415, 1423, 1430, 1437, 1445, 1452, 1459, 1466, 1474, 1481, -1488, 1496, 1503, 1510, 1518, 1525, 1532, 1539, 1547, 1554, 1561, -1569, 1576, 1583, 1591, 1598, 1605, 1612, 1620, 1627, 1634, 1642, -1649, 1656, 1663, 1671, 1678, 1685, 1693, 1700, 1707, 1715, 1722, -1729, 1736, 1744, 1751, 1758, 1766, 1773, 1780, 1788, 1795, 1802, -1809, 1817, 1824, 1831, 1839, 1846, 1853, 1860 }; +static INT16 Y_B[] = { + 0, 7, 15, 22, 29, 36, 44, 51, 58, 66, 73, 80, 88, 95, + 102, 109, 117, 124, 131, 139, 146, 153, 161, 168, 175, 182, 190, 197, + 204, 212, 219, 226, 233, 241, 248, 255, 263, 270, 277, 285, 292, 299, + 306, 314, 321, 328, 336, 343, 350, 358, 365, 372, 379, 387, 394, 401, + 409, 416, 423, 430, 438, 445, 452, 460, 467, 474, 482, 489, 496, 503, + 511, 518, 525, 533, 540, 547, 554, 562, 569, 576, 584, 591, 598, 606, + 613, 620, 627, 635, 642, 649, 657, 664, 671, 679, 686, 693, 700, 708, + 715, 722, 730, 737, 744, 751, 759, 766, 773, 781, 788, 795, 803, 810, + 817, 824, 832, 839, 846, 854, 861, 868, 876, 883, 890, 897, 905, 912, + 919, 927, 934, 941, 948, 956, 963, 970, 978, 985, 992, 1000, 1007, 1014, + 1021, 1029, 1036, 1043, 1051, 1058, 1065, 1073, 1080, 1087, 1094, 1102, 1109, 1116, + 1124, 1131, 1138, 1145, 1153, 1160, 1167, 1175, 1182, 1189, 1197, 1204, 1211, 1218, + 1226, 1233, 1240, 1248, 1255, 1262, 1270, 1277, 1284, 1291, 1299, 1306, 1313, 1321, + 1328, 1335, 1342, 1350, 1357, 1364, 1372, 1379, 1386, 1394, 1401, 1408, 1415, 1423, + 1430, 1437, 1445, 1452, 1459, 1466, 1474, 1481, 1488, 1496, 1503, 1510, 1518, 1525, + 1532, 1539, 1547, 1554, 1561, 1569, 1576, 1583, 1591, 1598, 1605, 1612, 1620, 1627, + 1634, 1642, 1649, 1656, 1663, 1671, 1678, 1685, 1693, 1700, 1707, 1715, 1722, 1729, + 1736, 1744, 1751, 1758, 1766, 1773, 1780, 1788, 1795, 1802, 1809, 1817, 1824, 1831, + 1839, 1846, 1853, 1860}; -static INT16 Cb_R[] = { 0, -10, -21, -31, -42, -53, -64, -75, -85, --96, -107, -118, -129, -139, -150, -161, -172, -183, -193, -204, -215, --226, -237, -247, -258, -269, -280, -291, -301, -312, -323, -334, --345, -355, -366, -377, -388, -399, -409, -420, -431, -442, -453, --463, -474, -485, -496, -507, -517, -528, -539, -550, -561, -571, --582, -593, -604, -615, -625, -636, -647, -658, -669, -679, -690, --701, -712, -723, -733, -744, -755, -766, -777, -787, -798, -809, --820, -831, -841, -852, -863, -874, -885, -895, -906, -917, -928, --939, -949, -960, -971, -982, -993, -1003, -1014, -1025, -1036, -1047, --1057, -1068, -1079, -1090, -1101, -1111, -1122, -1133, -1144, -1155, --1165, -1176, -1187, -1198, -1209, -1219, -1230, -1241, -1252, -1263, --1273, -1284, -1295, -1306, -1317, -1327, -1338, -1349, -1360, -1371, --1381, -1392, -1403, -1414, -1425, -1435, -1446, -1457, -1468, -1479, --1489, -1500, -1511, -1522, -1533, -1543, -1554, -1565, -1576, -1587, --1597, -1608, -1619, -1630, -1641, -1651, -1662, -1673, -1684, -1694, --1705, -1716, -1727, -1738, -1748, -1759, -1770, -1781, -1792, -1802, --1813, -1824, -1835, -1846, -1856, -1867, -1878, -1889, -1900, -1910, --1921, -1932, -1943, -1954, -1964, -1975, -1986, -1997, -2008, -2018, --2029, -2040, -2051, -2062, -2072, -2083, -2094, -2105, -2116, -2126, --2137, -2148, -2159, -2170, -2180, -2191, -2202, -2213, -2224, -2234, --2245, -2256, -2267, -2278, -2288, -2299, -2310, -2321, -2332, -2342, --2353, -2364, -2375, -2386, -2396, -2407, -2418, -2429, -2440, -2450, --2461, -2472, -2483, -2494, -2504, -2515, -2526, -2537, -2548, -2558, --2569, -2580, -2591, -2602, -2612, -2623, -2634, -2645, -2656, -2666, --2677, -2688, -2699, -2710, -2720, -2731, -2742, -2753 }; +static INT16 Cb_R[] = { + 0, -10, -21, -31, -42, -53, -64, -75, -85, -96, -107, -118, + -129, -139, -150, -161, -172, -183, -193, -204, -215, -226, -237, -247, + -258, -269, -280, -291, -301, -312, -323, -334, -345, -355, -366, -377, + -388, -399, -409, -420, -431, -442, -453, -463, -474, -485, -496, -507, + -517, -528, -539, -550, -561, -571, -582, -593, -604, -615, -625, -636, + -647, -658, -669, -679, -690, -701, -712, -723, -733, -744, -755, -766, + -777, -787, -798, -809, -820, -831, -841, -852, -863, -874, -885, -895, + -906, -917, -928, -939, -949, -960, -971, -982, -993, -1003, -1014, -1025, + -1036, -1047, -1057, -1068, -1079, -1090, -1101, -1111, -1122, -1133, -1144, -1155, + -1165, -1176, -1187, -1198, -1209, -1219, -1230, -1241, -1252, -1263, -1273, -1284, + -1295, -1306, -1317, -1327, -1338, -1349, -1360, -1371, -1381, -1392, -1403, -1414, + -1425, -1435, -1446, -1457, -1468, -1479, -1489, -1500, -1511, -1522, -1533, -1543, + -1554, -1565, -1576, -1587, -1597, -1608, -1619, -1630, -1641, -1651, -1662, -1673, + -1684, -1694, -1705, -1716, -1727, -1738, -1748, -1759, -1770, -1781, -1792, -1802, + -1813, -1824, -1835, -1846, -1856, -1867, -1878, -1889, -1900, -1910, -1921, -1932, + -1943, -1954, -1964, -1975, -1986, -1997, -2008, -2018, -2029, -2040, -2051, -2062, + -2072, -2083, -2094, -2105, -2116, -2126, -2137, -2148, -2159, -2170, -2180, -2191, + -2202, -2213, -2224, -2234, -2245, -2256, -2267, -2278, -2288, -2299, -2310, -2321, + -2332, -2342, -2353, -2364, -2375, -2386, -2396, -2407, -2418, -2429, -2440, -2450, + -2461, -2472, -2483, -2494, -2504, -2515, -2526, -2537, -2548, -2558, -2569, -2580, + -2591, -2602, -2612, -2623, -2634, -2645, -2656, -2666, -2677, -2688, -2699, -2710, + -2720, -2731, -2742, -2753}; -static INT16 Cb_G[] = { 0, -20, -41, -63, -84, -105, -126, -147, -169, --190, -211, -232, -253, -275, -296, -317, -338, -359, -381, -402, --423, -444, -465, -487, -508, -529, -550, -571, -593, -614, -635, --656, -677, -699, -720, -741, -762, -783, -805, -826, -847, -868, --889, -911, -932, -953, -974, -995, -1017, -1038, -1059, -1080, -1101, --1123, -1144, -1165, -1186, -1207, -1229, -1250, -1271, -1292, -1313, --1335, -1356, -1377, -1398, -1419, -1441, -1462, -1483, -1504, -1525, --1547, -1568, -1589, -1610, -1631, -1653, -1674, -1695, -1716, -1737, --1759, -1780, -1801, -1822, -1843, -1865, -1886, -1907, -1928, -1949, --1971, -1992, -2013, -2034, -2055, -2077, -2098, -2119, -2140, -2161, --2183, -2204, -2225, -2246, -2267, -2289, -2310, -2331, -2352, -2373, --2395, -2416, -2437, -2458, -2479, -2501, -2522, -2543, -2564, -2585, --2607, -2628, -2649, -2670, -2691, -2713, -2734, -2755, -2776, -2797, --2819, -2840, -2861, -2882, -2903, -2925, -2946, -2967, -2988, -3009, --3031, -3052, -3073, -3094, -3115, -3137, -3158, -3179, -3200, -3221, --3243, -3264, -3285, -3306, -3328, -3349, -3370, -3391, -3412, -3434, --3455, -3476, -3497, -3518, -3540, -3561, -3582, -3603, -3624, -3646, --3667, -3688, -3709, -3730, -3752, -3773, -3794, -3815, -3836, -3858, --3879, -3900, -3921, -3942, -3964, -3985, -4006, -4027, -4048, -4070, --4091, -4112, -4133, -4154, -4176, -4197, -4218, -4239, -4260, -4282, --4303, -4324, -4345, -4366, -4388, -4409, -4430, -4451, -4472, -4494, --4515, -4536, -4557, -4578, -4600, -4621, -4642, -4663, -4684, -4706, --4727, -4748, -4769, -4790, -4812, -4833, -4854, -4875, -4896, -4918, --4939, -4960, -4981, -5002, -5024, -5045, -5066, -5087, -5108, -5130, --5151, -5172, -5193, -5214, -5236, -5257, -5278, -5299, -5320, -5342, --5363, -5384, -5405 }; +static INT16 Cb_G[] = { + 0, -20, -41, -63, -84, -105, -126, -147, -169, -190, -211, -232, + -253, -275, -296, -317, -338, -359, -381, -402, -423, -444, -465, -487, + -508, -529, -550, -571, -593, -614, -635, -656, -677, -699, -720, -741, + -762, -783, -805, -826, -847, -868, -889, -911, -932, -953, -974, -995, + -1017, -1038, -1059, -1080, -1101, -1123, -1144, -1165, -1186, -1207, -1229, -1250, + -1271, -1292, -1313, -1335, -1356, -1377, -1398, -1419, -1441, -1462, -1483, -1504, + -1525, -1547, -1568, -1589, -1610, -1631, -1653, -1674, -1695, -1716, -1737, -1759, + -1780, -1801, -1822, -1843, -1865, -1886, -1907, -1928, -1949, -1971, -1992, -2013, + -2034, -2055, -2077, -2098, -2119, -2140, -2161, -2183, -2204, -2225, -2246, -2267, + -2289, -2310, -2331, -2352, -2373, -2395, -2416, -2437, -2458, -2479, -2501, -2522, + -2543, -2564, -2585, -2607, -2628, -2649, -2670, -2691, -2713, -2734, -2755, -2776, + -2797, -2819, -2840, -2861, -2882, -2903, -2925, -2946, -2967, -2988, -3009, -3031, + -3052, -3073, -3094, -3115, -3137, -3158, -3179, -3200, -3221, -3243, -3264, -3285, + -3306, -3328, -3349, -3370, -3391, -3412, -3434, -3455, -3476, -3497, -3518, -3540, + -3561, -3582, -3603, -3624, -3646, -3667, -3688, -3709, -3730, -3752, -3773, -3794, + -3815, -3836, -3858, -3879, -3900, -3921, -3942, -3964, -3985, -4006, -4027, -4048, + -4070, -4091, -4112, -4133, -4154, -4176, -4197, -4218, -4239, -4260, -4282, -4303, + -4324, -4345, -4366, -4388, -4409, -4430, -4451, -4472, -4494, -4515, -4536, -4557, + -4578, -4600, -4621, -4642, -4663, -4684, -4706, -4727, -4748, -4769, -4790, -4812, + -4833, -4854, -4875, -4896, -4918, -4939, -4960, -4981, -5002, -5024, -5045, -5066, + -5087, -5108, -5130, -5151, -5172, -5193, -5214, -5236, -5257, -5278, -5299, -5320, + -5342, -5363, -5384, -5405}; -static INT16 Cb_B[] = { 0, 32, 64, 96, 128, 160, 192, 224, 256, 288, -320, 352, 384, 416, 448, 480, 512, 544, 576, 608, 640, 672, 704, 736, -768, 800, 832, 864, 896, 928, 960, 992, 1024, 1056, 1088, 1120, 1152, -1184, 1216, 1248, 1280, 1312, 1344, 1376, 1408, 1440, 1472, 1504, -1536, 1568, 1600, 1632, 1664, 1696, 1728, 1760, 1792, 1824, 1856, -1888, 1920, 1952, 1984, 2016, 2048, 2080, 2112, 2144, 2176, 2208, -2240, 2272, 2304, 2336, 2368, 2400, 2432, 2464, 2496, 2528, 2560, -2592, 2624, 2656, 2688, 2720, 2752, 2784, 2816, 2848, 2880, 2912, -2944, 2976, 3008, 3040, 3072, 3104, 3136, 3168, 3200, 3232, 3264, -3296, 3328, 3360, 3392, 3424, 3456, 3488, 3520, 3552, 3584, 3616, -3648, 3680, 3712, 3744, 3776, 3808, 3840, 3872, 3904, 3936, 3968, -4000, 4032, 4064, 4096, 4128, 4160, 4192, 4224, 4256, 4288, 4320, -4352, 4384, 4416, 4448, 4480, 4512, 4544, 4576, 4608, 4640, 4672, -4704, 4736, 4768, 4800, 4832, 4864, 4896, 4928, 4960, 4992, 5024, -5056, 5088, 5120, 5152, 5184, 5216, 5248, 5280, 5312, 5344, 5376, -5408, 5440, 5472, 5504, 5536, 5568, 5600, 5632, 5664, 5696, 5728, -5760, 5792, 5824, 5856, 5888, 5920, 5952, 5984, 6016, 6048, 6080, -6112, 6144, 6176, 6208, 6240, 6272, 6304, 6336, 6368, 6400, 6432, -6464, 6496, 6528, 6560, 6592, 6624, 6656, 6688, 6720, 6752, 6784, -6816, 6848, 6880, 6912, 6944, 6976, 7008, 7040, 7072, 7104, 7136, -7168, 7200, 7232, 7264, 7296, 7328, 7360, 7392, 7424, 7456, 7488, -7520, 7552, 7584, 7616, 7648, 7680, 7712, 7744, 7776, 7808, 7840, -7872, 7904, 7936, 7968, 8000, 8032, 8064, 8096, 8128, 8160 }; +static INT16 Cb_B[] = { + 0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, + 448, 480, 512, 544, 576, 608, 640, 672, 704, 736, 768, 800, 832, 864, + 896, 928, 960, 992, 1024, 1056, 1088, 1120, 1152, 1184, 1216, 1248, 1280, 1312, + 1344, 1376, 1408, 1440, 1472, 1504, 1536, 1568, 1600, 1632, 1664, 1696, 1728, 1760, + 1792, 1824, 1856, 1888, 1920, 1952, 1984, 2016, 2048, 2080, 2112, 2144, 2176, 2208, + 2240, 2272, 2304, 2336, 2368, 2400, 2432, 2464, 2496, 2528, 2560, 2592, 2624, 2656, + 2688, 2720, 2752, 2784, 2816, 2848, 2880, 2912, 2944, 2976, 3008, 3040, 3072, 3104, + 3136, 3168, 3200, 3232, 3264, 3296, 3328, 3360, 3392, 3424, 3456, 3488, 3520, 3552, + 3584, 3616, 3648, 3680, 3712, 3744, 3776, 3808, 3840, 3872, 3904, 3936, 3968, 4000, + 4032, 4064, 4096, 4128, 4160, 4192, 4224, 4256, 4288, 4320, 4352, 4384, 4416, 4448, + 4480, 4512, 4544, 4576, 4608, 4640, 4672, 4704, 4736, 4768, 4800, 4832, 4864, 4896, + 4928, 4960, 4992, 5024, 5056, 5088, 5120, 5152, 5184, 5216, 5248, 5280, 5312, 5344, + 5376, 5408, 5440, 5472, 5504, 5536, 5568, 5600, 5632, 5664, 5696, 5728, 5760, 5792, + 5824, 5856, 5888, 5920, 5952, 5984, 6016, 6048, 6080, 6112, 6144, 6176, 6208, 6240, + 6272, 6304, 6336, 6368, 6400, 6432, 6464, 6496, 6528, 6560, 6592, 6624, 6656, 6688, + 6720, 6752, 6784, 6816, 6848, 6880, 6912, 6944, 6976, 7008, 7040, 7072, 7104, 7136, + 7168, 7200, 7232, 7264, 7296, 7328, 7360, 7392, 7424, 7456, 7488, 7520, 7552, 7584, + 7616, 7648, 7680, 7712, 7744, 7776, 7808, 7840, 7872, 7904, 7936, 7968, 8000, 8032, + 8064, 8096, 8128, 8160}; #define Cr_R Cb_B -static INT16 Cr_G[] = { 0, -26, -53, -79, -106, -133, -160, -187, --213, -240, -267, -294, -321, -347, -374, -401, -428, -455, -481, --508, -535, -562, -589, -615, -642, -669, -696, -722, -749, -776, --803, -830, -856, -883, -910, -937, -964, -990, -1017, -1044, -1071, --1098, -1124, -1151, -1178, -1205, -1232, -1258, -1285, -1312, -1339, --1366, -1392, -1419, -1446, -1473, -1500, -1526, -1553, -1580, -1607, --1634, -1660, -1687, -1714, -1741, -1768, -1794, -1821, -1848, -1875, --1902, -1928, -1955, -1982, -2009, -2036, -2062, -2089, -2116, -2143, --2169, -2196, -2223, -2250, -2277, -2303, -2330, -2357, -2384, -2411, --2437, -2464, -2491, -2518, -2545, -2571, -2598, -2625, -2652, -2679, --2705, -2732, -2759, -2786, -2813, -2839, -2866, -2893, -2920, -2947, --2973, -3000, -3027, -3054, -3081, -3107, -3134, -3161, -3188, -3215, --3241, -3268, -3295, -3322, -3349, -3375, -3402, -3429, -3456, -3483, --3509, -3536, -3563, -3590, -3616, -3643, -3670, -3697, -3724, -3750, --3777, -3804, -3831, -3858, -3884, -3911, -3938, -3965, -3992, -4018, --4045, -4072, -4099, -4126, -4152, -4179, -4206, -4233, -4260, -4286, --4313, -4340, -4367, -4394, -4420, -4447, -4474, -4501, -4528, -4554, --4581, -4608, -4635, -4662, -4688, -4715, -4742, -4769, -4796, -4822, --4849, -4876, -4903, -4929, -4956, -4983, -5010, -5037, -5063, -5090, --5117, -5144, -5171, -5197, -5224, -5251, -5278, -5305, -5331, -5358, --5385, -5412, -5439, -5465, -5492, -5519, -5546, -5573, -5599, -5626, --5653, -5680, -5707, -5733, -5760, -5787, -5814, -5841, -5867, -5894, --5921, -5948, -5975, -6001, -6028, -6055, -6082, -6109, -6135, -6162, --6189, -6216, -6243, -6269, -6296, -6323, -6350, -6376, -6403, -6430, --6457, -6484, -6510, -6537, -6564, -6591, -6618, -6644, -6671, -6698, --6725, -6752, -6778, -6805, -6832 }; +static INT16 Cr_G[] = { + 0, -26, -53, -79, -106, -133, -160, -187, -213, -240, -267, -294, + -321, -347, -374, -401, -428, -455, -481, -508, -535, -562, -589, -615, + -642, -669, -696, -722, -749, -776, -803, -830, -856, -883, -910, -937, + -964, -990, -1017, -1044, -1071, -1098, -1124, -1151, -1178, -1205, -1232, -1258, + -1285, -1312, -1339, -1366, -1392, -1419, -1446, -1473, -1500, -1526, -1553, -1580, + -1607, -1634, -1660, -1687, -1714, -1741, -1768, -1794, -1821, -1848, -1875, -1902, + -1928, -1955, -1982, -2009, -2036, -2062, -2089, -2116, -2143, -2169, -2196, -2223, + -2250, -2277, -2303, -2330, -2357, -2384, -2411, -2437, -2464, -2491, -2518, -2545, + -2571, -2598, -2625, -2652, -2679, -2705, -2732, -2759, -2786, -2813, -2839, -2866, + -2893, -2920, -2947, -2973, -3000, -3027, -3054, -3081, -3107, -3134, -3161, -3188, + -3215, -3241, -3268, -3295, -3322, -3349, -3375, -3402, -3429, -3456, -3483, -3509, + -3536, -3563, -3590, -3616, -3643, -3670, -3697, -3724, -3750, -3777, -3804, -3831, + -3858, -3884, -3911, -3938, -3965, -3992, -4018, -4045, -4072, -4099, -4126, -4152, + -4179, -4206, -4233, -4260, -4286, -4313, -4340, -4367, -4394, -4420, -4447, -4474, + -4501, -4528, -4554, -4581, -4608, -4635, -4662, -4688, -4715, -4742, -4769, -4796, + -4822, -4849, -4876, -4903, -4929, -4956, -4983, -5010, -5037, -5063, -5090, -5117, + -5144, -5171, -5197, -5224, -5251, -5278, -5305, -5331, -5358, -5385, -5412, -5439, + -5465, -5492, -5519, -5546, -5573, -5599, -5626, -5653, -5680, -5707, -5733, -5760, + -5787, -5814, -5841, -5867, -5894, -5921, -5948, -5975, -6001, -6028, -6055, -6082, + -6109, -6135, -6162, -6189, -6216, -6243, -6269, -6296, -6323, -6350, -6376, -6403, + -6430, -6457, -6484, -6510, -6537, -6564, -6591, -6618, -6644, -6671, -6698, -6725, + -6752, -6778, -6805, -6832}; -static INT16 Cr_B[] = { 0, -4, -9, -15, -20, -25, -30, -35, -41, -46, --51, -56, -61, -67, -72, -77, -82, -87, -93, -98, -103, -108, -113, --119, -124, -129, -134, -140, -145, -150, -155, -160, -166, -171, --176, -181, -186, -192, -197, -202, -207, -212, -218, -223, -228, --233, -238, -244, -249, -254, -259, -264, -270, -275, -280, -285, --290, -296, -301, -306, -311, -316, -322, -327, -332, -337, -342, --348, -353, -358, -363, -368, -374, -379, -384, -389, -394, -400, --405, -410, -415, -421, -426, -431, -436, -441, -447, -452, -457, --462, -467, -473, -478, -483, -488, -493, -499, -504, -509, -514, --519, -525, -530, -535, -540, -545, -551, -556, -561, -566, -571, --577, -582, -587, -592, -597, -603, -608, -613, -618, -623, -629, --634, -639, -644, -649, -655, -660, -665, -670, -675, -681, -686, --691, -696, -702, -707, -712, -717, -722, -728, -733, -738, -743, --748, -754, -759, -764, -769, -774, -780, -785, -790, -795, -800, --806, -811, -816, -821, -826, -832, -837, -842, -847, -852, -858, --863, -868, -873, -878, -884, -889, -894, -899, -904, -910, -915, --920, -925, -930, -936, -941, -946, -951, -957, -962, -967, -972, --977, -983, -988, -993, -998, -1003, -1009, -1014, -1019, -1024, --1029, -1035, -1040, -1045, -1050, -1055, -1061, -1066, -1071, -1076, --1081, -1087, -1092, -1097, -1102, -1107, -1113, -1118, -1123, -1128, --1133, -1139, -1144, -1149, -1154, -1159, -1165, -1170, -1175, -1180, --1185, -1191, -1196, -1201, -1206, -1211, -1217, -1222, -1227, -1232, --1238, -1243, -1248, -1253, -1258, -1264, -1269, -1274, -1279, -1284, --1290, -1295, -1300, -1305, -1310, -1316, -1321, -1326 }; +static INT16 Cr_B[] = { + 0, -4, -9, -15, -20, -25, -30, -35, -41, -46, -51, -56, + -61, -67, -72, -77, -82, -87, -93, -98, -103, -108, -113, -119, + -124, -129, -134, -140, -145, -150, -155, -160, -166, -171, -176, -181, + -186, -192, -197, -202, -207, -212, -218, -223, -228, -233, -238, -244, + -249, -254, -259, -264, -270, -275, -280, -285, -290, -296, -301, -306, + -311, -316, -322, -327, -332, -337, -342, -348, -353, -358, -363, -368, + -374, -379, -384, -389, -394, -400, -405, -410, -415, -421, -426, -431, + -436, -441, -447, -452, -457, -462, -467, -473, -478, -483, -488, -493, + -499, -504, -509, -514, -519, -525, -530, -535, -540, -545, -551, -556, + -561, -566, -571, -577, -582, -587, -592, -597, -603, -608, -613, -618, + -623, -629, -634, -639, -644, -649, -655, -660, -665, -670, -675, -681, + -686, -691, -696, -702, -707, -712, -717, -722, -728, -733, -738, -743, + -748, -754, -759, -764, -769, -774, -780, -785, -790, -795, -800, -806, + -811, -816, -821, -826, -832, -837, -842, -847, -852, -858, -863, -868, + -873, -878, -884, -889, -894, -899, -904, -910, -915, -920, -925, -930, + -936, -941, -946, -951, -957, -962, -967, -972, -977, -983, -988, -993, + -998, -1003, -1009, -1014, -1019, -1024, -1029, -1035, -1040, -1045, -1050, -1055, + -1061, -1066, -1071, -1076, -1081, -1087, -1092, -1097, -1102, -1107, -1113, -1118, + -1123, -1128, -1133, -1139, -1144, -1149, -1154, -1159, -1165, -1170, -1175, -1180, + -1185, -1191, -1196, -1201, -1206, -1211, -1217, -1222, -1227, -1232, -1238, -1243, + -1248, -1253, -1258, -1264, -1269, -1274, -1279, -1284, -1290, -1295, -1300, -1305, + -1310, -1316, -1321, -1326}; -static INT16 R_Cr[] = { -11484, -11394, -11305, -11215, -11125, --11036, -10946, -10856, -10766, -10677, -10587, -10497, -10407, --10318, -10228, -10138, -10049, -9959, -9869, -9779, -9690, -9600, --9510, -9420, -9331, -9241, -9151, -9062, -8972, -8882, -8792, -8703, --8613, -8523, -8433, -8344, -8254, -8164, -8075, -7985, -7895, -7805, --7716, -7626, -7536, -7446, -7357, -7267, -7177, -7088, -6998, -6908, --6818, -6729, -6639, -6549, -6459, -6370, -6280, -6190, -6101, -6011, --5921, -5831, -5742, -5652, -5562, -5472, -5383, -5293, -5203, -5113, --5024, -4934, -4844, -4755, -4665, -4575, -4485, -4396, -4306, -4216, --4126, -4037, -3947, -3857, -3768, -3678, -3588, -3498, -3409, -3319, --3229, -3139, -3050, -2960, -2870, -2781, -2691, -2601, -2511, -2422, --2332, -2242, -2152, -2063, -1973, -1883, -1794, -1704, -1614, -1524, --1435, -1345, -1255, -1165, -1076, -986, -896, -807, -717, -627, -537, --448, -358, -268, -178, -89, 0, 90, 179, 269, 359, 449, 538, 628, 718, -808, 897, 987, 1077, 1166, 1256, 1346, 1436, 1525, 1615, 1705, 1795, -1884, 1974, 2064, 2153, 2243, 2333, 2423, 2512, 2602, 2692, 2782, -2871, 2961, 3051, 3140, 3230, 3320, 3410, 3499, 3589, 3679, 3769, -3858, 3948, 4038, 4127, 4217, 4307, 4397, 4486, 4576, 4666, 4756, -4845, 4935, 5025, 5114, 5204, 5294, 5384, 5473, 5563, 5653, 5743, -5832, 5922, 6012, 6102, 6191, 6281, 6371, 6460, 6550, 6640, 6730, -6819, 6909, 6999, 7089, 7178, 7268, 7358, 7447, 7537, 7627, 7717, -7806, 7896, 7986, 8076, 8165, 8255, 8345, 8434, 8524, 8614, 8704, -8793, 8883, 8973, 9063, 9152, 9242, 9332, 9421, 9511, 9601, 9691, -9780, 9870, 9960, 10050, 10139, 10229, 10319, 10408, 10498, 10588, -10678, 10767, 10857, 10947, 11037, 11126, 11216, 11306, 11395 }; +static INT16 R_Cr[] = { + -11484, -11394, -11305, -11215, -11125, -11036, -10946, -10856, -10766, -10677, + -10587, -10497, -10407, -10318, -10228, -10138, -10049, -9959, -9869, -9779, + -9690, -9600, -9510, -9420, -9331, -9241, -9151, -9062, -8972, -8882, + -8792, -8703, -8613, -8523, -8433, -8344, -8254, -8164, -8075, -7985, + -7895, -7805, -7716, -7626, -7536, -7446, -7357, -7267, -7177, -7088, + -6998, -6908, -6818, -6729, -6639, -6549, -6459, -6370, -6280, -6190, + -6101, -6011, -5921, -5831, -5742, -5652, -5562, -5472, -5383, -5293, + -5203, -5113, -5024, -4934, -4844, -4755, -4665, -4575, -4485, -4396, + -4306, -4216, -4126, -4037, -3947, -3857, -3768, -3678, -3588, -3498, + -3409, -3319, -3229, -3139, -3050, -2960, -2870, -2781, -2691, -2601, + -2511, -2422, -2332, -2242, -2152, -2063, -1973, -1883, -1794, -1704, + -1614, -1524, -1435, -1345, -1255, -1165, -1076, -986, -896, -807, + -717, -627, -537, -448, -358, -268, -178, -89, 0, 90, + 179, 269, 359, 449, 538, 628, 718, 808, 897, 987, + 1077, 1166, 1256, 1346, 1436, 1525, 1615, 1705, 1795, 1884, + 1974, 2064, 2153, 2243, 2333, 2423, 2512, 2602, 2692, 2782, + 2871, 2961, 3051, 3140, 3230, 3320, 3410, 3499, 3589, 3679, + 3769, 3858, 3948, 4038, 4127, 4217, 4307, 4397, 4486, 4576, + 4666, 4756, 4845, 4935, 5025, 5114, 5204, 5294, 5384, 5473, + 5563, 5653, 5743, 5832, 5922, 6012, 6102, 6191, 6281, 6371, + 6460, 6550, 6640, 6730, 6819, 6909, 6999, 7089, 7178, 7268, + 7358, 7447, 7537, 7627, 7717, 7806, 7896, 7986, 8076, 8165, + 8255, 8345, 8434, 8524, 8614, 8704, 8793, 8883, 8973, 9063, + 9152, 9242, 9332, 9421, 9511, 9601, 9691, 9780, 9870, 9960, + 10050, 10139, 10229, 10319, 10408, 10498, 10588, 10678, 10767, 10857, + 10947, 11037, 11126, 11216, 11306, 11395}; -static INT16 G_Cb[] = { 2819, 2797, 2775, 2753, 2731, 2709, 2687, -2665, 2643, 2621, 2599, 2577, 2555, 2533, 2511, 2489, 2467, 2445, -2423, 2401, 2379, 2357, 2335, 2313, 2291, 2269, 2247, 2225, 2202, -2180, 2158, 2136, 2114, 2092, 2070, 2048, 2026, 2004, 1982, 1960, -1938, 1916, 1894, 1872, 1850, 1828, 1806, 1784, 1762, 1740, 1718, -1696, 1674, 1652, 1630, 1608, 1586, 1564, 1542, 1520, 1498, 1476, -1454, 1432, 1410, 1388, 1366, 1344, 1321, 1299, 1277, 1255, 1233, -1211, 1189, 1167, 1145, 1123, 1101, 1079, 1057, 1035, 1013, 991, 969, -947, 925, 903, 881, 859, 837, 815, 793, 771, 749, 727, 705, 683, 661, -639, 617, 595, 573, 551, 529, 507, 485, 463, 440, 418, 396, 374, 352, -330, 308, 286, 264, 242, 220, 198, 176, 154, 132, 110, 88, 66, 44, 22, -0, -21, -43, -65, -87, -109, -131, -153, -175, -197, -219, -241, -263, --285, -307, -329, -351, -373, -395, -417, -439, -462, -484, -506, --528, -550, -572, -594, -616, -638, -660, -682, -704, -726, -748, --770, -792, -814, -836, -858, -880, -902, -924, -946, -968, -990, --1012, -1034, -1056, -1078, -1100, -1122, -1144, -1166, -1188, -1210, --1232, -1254, -1276, -1298, -1320, -1343, -1365, -1387, -1409, -1431, --1453, -1475, -1497, -1519, -1541, -1563, -1585, -1607, -1629, -1651, --1673, -1695, -1717, -1739, -1761, -1783, -1805, -1827, -1849, -1871, --1893, -1915, -1937, -1959, -1981, -2003, -2025, -2047, -2069, -2091, --2113, -2135, -2157, -2179, -2201, -2224, -2246, -2268, -2290, -2312, --2334, -2356, -2378, -2400, -2422, -2444, -2466, -2488, -2510, -2532, --2554, -2576, -2598, -2620, -2642, -2664, -2686, -2708, -2730, -2752, --2774, -2796 }; +static INT16 G_Cb[] = { + 2819, 2797, 2775, 2753, 2731, 2709, 2687, 2665, 2643, 2621, 2599, 2577, + 2555, 2533, 2511, 2489, 2467, 2445, 2423, 2401, 2379, 2357, 2335, 2313, + 2291, 2269, 2247, 2225, 2202, 2180, 2158, 2136, 2114, 2092, 2070, 2048, + 2026, 2004, 1982, 1960, 1938, 1916, 1894, 1872, 1850, 1828, 1806, 1784, + 1762, 1740, 1718, 1696, 1674, 1652, 1630, 1608, 1586, 1564, 1542, 1520, + 1498, 1476, 1454, 1432, 1410, 1388, 1366, 1344, 1321, 1299, 1277, 1255, + 1233, 1211, 1189, 1167, 1145, 1123, 1101, 1079, 1057, 1035, 1013, 991, + 969, 947, 925, 903, 881, 859, 837, 815, 793, 771, 749, 727, + 705, 683, 661, 639, 617, 595, 573, 551, 529, 507, 485, 463, + 440, 418, 396, 374, 352, 330, 308, 286, 264, 242, 220, 198, + 176, 154, 132, 110, 88, 66, 44, 22, 0, -21, -43, -65, + -87, -109, -131, -153, -175, -197, -219, -241, -263, -285, -307, -329, + -351, -373, -395, -417, -439, -462, -484, -506, -528, -550, -572, -594, + -616, -638, -660, -682, -704, -726, -748, -770, -792, -814, -836, -858, + -880, -902, -924, -946, -968, -990, -1012, -1034, -1056, -1078, -1100, -1122, + -1144, -1166, -1188, -1210, -1232, -1254, -1276, -1298, -1320, -1343, -1365, -1387, + -1409, -1431, -1453, -1475, -1497, -1519, -1541, -1563, -1585, -1607, -1629, -1651, + -1673, -1695, -1717, -1739, -1761, -1783, -1805, -1827, -1849, -1871, -1893, -1915, + -1937, -1959, -1981, -2003, -2025, -2047, -2069, -2091, -2113, -2135, -2157, -2179, + -2201, -2224, -2246, -2268, -2290, -2312, -2334, -2356, -2378, -2400, -2422, -2444, + -2466, -2488, -2510, -2532, -2554, -2576, -2598, -2620, -2642, -2664, -2686, -2708, + -2730, -2752, -2774, -2796}; -static INT16 G_Cr[] = { 5850, 5805, 5759, 5713, 5667, 5622, 5576, -5530, 5485, 5439, 5393, 5347, 5302, 5256, 5210, 5165, 5119, 5073, -5028, 4982, 4936, 4890, 4845, 4799, 4753, 4708, 4662, 4616, 4570, -4525, 4479, 4433, 4388, 4342, 4296, 4251, 4205, 4159, 4113, 4068, -4022, 3976, 3931, 3885, 3839, 3794, 3748, 3702, 3656, 3611, 3565, -3519, 3474, 3428, 3382, 3336, 3291, 3245, 3199, 3154, 3108, 3062, -3017, 2971, 2925, 2879, 2834, 2788, 2742, 2697, 2651, 2605, 2559, -2514, 2468, 2422, 2377, 2331, 2285, 2240, 2194, 2148, 2102, 2057, -2011, 1965, 1920, 1874, 1828, 1782, 1737, 1691, 1645, 1600, 1554, -1508, 1463, 1417, 1371, 1325, 1280, 1234, 1188, 1143, 1097, 1051, -1006, 960, 914, 868, 823, 777, 731, 686, 640, 594, 548, 503, 457, 411, -366, 320, 274, 229, 183, 137, 91, 46, 0, -45, -90, -136, -182, -228, --273, -319, -365, -410, -456, -502, -547, -593, -639, -685, -730, --776, -822, -867, -913, -959, -1005, -1050, -1096, -1142, -1187, --1233, -1279, -1324, -1370, -1416, -1462, -1507, -1553, -1599, -1644, --1690, -1736, -1781, -1827, -1873, -1919, -1964, -2010, -2056, -2101, --2147, -2193, -2239, -2284, -2330, -2376, -2421, -2467, -2513, -2558, --2604, -2650, -2696, -2741, -2787, -2833, -2878, -2924, -2970, -3016, --3061, -3107, -3153, -3198, -3244, -3290, -3335, -3381, -3427, -3473, --3518, -3564, -3610, -3655, -3701, -3747, -3793, -3838, -3884, -3930, --3975, -4021, -4067, -4112, -4158, -4204, -4250, -4295, -4341, -4387, --4432, -4478, -4524, -4569, -4615, -4661, -4707, -4752, -4798, -4844, --4889, -4935, -4981, -5027, -5072, -5118, -5164, -5209, -5255, -5301, --5346, -5392, -5438, -5484, -5529, -5575, -5621, -5666, -5712, -5758, --5804 }; - -static INT16 B_Cb[] = { -14515, -14402, -14288, -14175, -14062, --13948, -13835, -13721, -13608, -13495, -13381, -13268, -13154, --13041, -12928, -12814, -12701, -12587, -12474, -12360, -12247, --12134, -12020, -11907, -11793, -11680, -11567, -11453, -11340, --11226, -11113, -11000, -10886, -10773, -10659, -10546, -10433, --10319, -10206, -10092, -9979, -9865, -9752, -9639, -9525, -9412, --9298, -9185, -9072, -8958, -8845, -8731, -8618, -8505, -8391, -8278, --8164, -8051, -7938, -7824, -7711, -7597, -7484, -7371, -7257, -7144, --7030, -6917, -6803, -6690, -6577, -6463, -6350, -6236, -6123, -6010, --5896, -5783, -5669, -5556, -5443, -5329, -5216, -5102, -4989, -4876, --4762, -4649, -4535, -4422, -4309, -4195, -4082, -3968, -3855, -3741, --3628, -3515, -3401, -3288, -3174, -3061, -2948, -2834, -2721, -2607, --2494, -2381, -2267, -2154, -2040, -1927, -1814, -1700, -1587, -1473, --1360, -1246, -1133, -1020, -906, -793, -679, -566, -453, -339, -226, --112, 0, 113, 227, 340, 454, 567, 680, 794, 907, 1021, 1134, 1247, -1361, 1474, 1588, 1701, 1815, 1928, 2041, 2155, 2268, 2382, 2495, -2608, 2722, 2835, 2949, 3062, 3175, 3289, 3402, 3516, 3629, 3742, -3856, 3969, 4083, 4196, 4310, 4423, 4536, 4650, 4763, 4877, 4990, -5103, 5217, 5330, 5444, 5557, 5670, 5784, 5897, 6011, 6124, 6237, -6351, 6464, 6578, 6691, 6804, 6918, 7031, 7145, 7258, 7372, 7485, -7598, 7712, 7825, 7939, 8052, 8165, 8279, 8392, 8506, 8619, 8732, -8846, 8959, 9073, 9186, 9299, 9413, 9526, 9640, 9753, 9866, 9980, -10093, 10207, 10320, 10434, 10547, 10660, 10774, 10887, 11001, 11114, -11227, 11341, 11454, 11568, 11681, 11794, 11908, 12021, 12135, 12248, -12361, 12475, 12588, 12702, 12815, 12929, 13042, 13155, 13269, 13382, -13496, 13609, 13722, 13836, 13949, 14063, 14176, 14289, 14403 }; +static INT16 G_Cr[] = { + 5850, 5805, 5759, 5713, 5667, 5622, 5576, 5530, 5485, 5439, 5393, 5347, + 5302, 5256, 5210, 5165, 5119, 5073, 5028, 4982, 4936, 4890, 4845, 4799, + 4753, 4708, 4662, 4616, 4570, 4525, 4479, 4433, 4388, 4342, 4296, 4251, + 4205, 4159, 4113, 4068, 4022, 3976, 3931, 3885, 3839, 3794, 3748, 3702, + 3656, 3611, 3565, 3519, 3474, 3428, 3382, 3336, 3291, 3245, 3199, 3154, + 3108, 3062, 3017, 2971, 2925, 2879, 2834, 2788, 2742, 2697, 2651, 2605, + 2559, 2514, 2468, 2422, 2377, 2331, 2285, 2240, 2194, 2148, 2102, 2057, + 2011, 1965, 1920, 1874, 1828, 1782, 1737, 1691, 1645, 1600, 1554, 1508, + 1463, 1417, 1371, 1325, 1280, 1234, 1188, 1143, 1097, 1051, 1006, 960, + 914, 868, 823, 777, 731, 686, 640, 594, 548, 503, 457, 411, + 366, 320, 274, 229, 183, 137, 91, 46, 0, -45, -90, -136, + -182, -228, -273, -319, -365, -410, -456, -502, -547, -593, -639, -685, + -730, -776, -822, -867, -913, -959, -1005, -1050, -1096, -1142, -1187, -1233, + -1279, -1324, -1370, -1416, -1462, -1507, -1553, -1599, -1644, -1690, -1736, -1781, + -1827, -1873, -1919, -1964, -2010, -2056, -2101, -2147, -2193, -2239, -2284, -2330, + -2376, -2421, -2467, -2513, -2558, -2604, -2650, -2696, -2741, -2787, -2833, -2878, + -2924, -2970, -3016, -3061, -3107, -3153, -3198, -3244, -3290, -3335, -3381, -3427, + -3473, -3518, -3564, -3610, -3655, -3701, -3747, -3793, -3838, -3884, -3930, -3975, + -4021, -4067, -4112, -4158, -4204, -4250, -4295, -4341, -4387, -4432, -4478, -4524, + -4569, -4615, -4661, -4707, -4752, -4798, -4844, -4889, -4935, -4981, -5027, -5072, + -5118, -5164, -5209, -5255, -5301, -5346, -5392, -5438, -5484, -5529, -5575, -5621, + -5666, -5712, -5758, -5804}; +static INT16 B_Cb[] = { + -14515, -14402, -14288, -14175, -14062, -13948, -13835, -13721, -13608, -13495, + -13381, -13268, -13154, -13041, -12928, -12814, -12701, -12587, -12474, -12360, + -12247, -12134, -12020, -11907, -11793, -11680, -11567, -11453, -11340, -11226, + -11113, -11000, -10886, -10773, -10659, -10546, -10433, -10319, -10206, -10092, + -9979, -9865, -9752, -9639, -9525, -9412, -9298, -9185, -9072, -8958, + -8845, -8731, -8618, -8505, -8391, -8278, -8164, -8051, -7938, -7824, + -7711, -7597, -7484, -7371, -7257, -7144, -7030, -6917, -6803, -6690, + -6577, -6463, -6350, -6236, -6123, -6010, -5896, -5783, -5669, -5556, + -5443, -5329, -5216, -5102, -4989, -4876, -4762, -4649, -4535, -4422, + -4309, -4195, -4082, -3968, -3855, -3741, -3628, -3515, -3401, -3288, + -3174, -3061, -2948, -2834, -2721, -2607, -2494, -2381, -2267, -2154, + -2040, -1927, -1814, -1700, -1587, -1473, -1360, -1246, -1133, -1020, + -906, -793, -679, -566, -453, -339, -226, -112, 0, 113, + 227, 340, 454, 567, 680, 794, 907, 1021, 1134, 1247, + 1361, 1474, 1588, 1701, 1815, 1928, 2041, 2155, 2268, 2382, + 2495, 2608, 2722, 2835, 2949, 3062, 3175, 3289, 3402, 3516, + 3629, 3742, 3856, 3969, 4083, 4196, 4310, 4423, 4536, 4650, + 4763, 4877, 4990, 5103, 5217, 5330, 5444, 5557, 5670, 5784, + 5897, 6011, 6124, 6237, 6351, 6464, 6578, 6691, 6804, 6918, + 7031, 7145, 7258, 7372, 7485, 7598, 7712, 7825, 7939, 8052, + 8165, 8279, 8392, 8506, 8619, 8732, 8846, 8959, 9073, 9186, + 9299, 9413, 9526, 9640, 9753, 9866, 9980, 10093, 10207, 10320, + 10434, 10547, 10660, 10774, 10887, 11001, 11114, 11227, 11341, 11454, + 11568, 11681, 11794, 11908, 12021, 12135, 12248, 12361, 12475, 12588, + 12702, 12815, 12929, 13042, 13155, 13269, 13382, 13496, 13609, 13722, + 13836, 13949, 14063, 14176, 14289, 14403}; void -ImagingConvertRGB2YCbCr(UINT8* out, const UINT8* in, int pixels) -{ - int x; - UINT8 a; - int r, g, b; - int y, cr, cb; - - for (x = 0; x < pixels; x++, in +=4, out += 4) { - - r = in[0]; - g = in[1]; - b = in[2]; - a = in[3]; - - y = (Y_R[r] + Y_G[g] + Y_B[b]) >> SCALE; - cb = ((Cb_R[r] + Cb_G[g] + Cb_B[b]) >> SCALE) + 128; - cr = ((Cr_R[r] + Cr_G[g] + Cr_B[b]) >> SCALE) + 128; - - out[0] = (UINT8) y; - out[1] = (UINT8) cb; - out[2] = (UINT8) cr; - out[3] = a; - } -} - -void -ImagingConvertYCbCr2RGB(UINT8* out, const UINT8* in, int pixels) -{ +ImagingConvertRGB2YCbCr(UINT8 *out, const UINT8 *in, int pixels) { int x; UINT8 a; int r, g, b; int y, cr, cb; for (x = 0; x < pixels; x++, in += 4, out += 4) { + r = in[0]; + g = in[1]; + b = in[2]; + a = in[3]; + y = (Y_R[r] + Y_G[g] + Y_B[b]) >> SCALE; + cb = ((Cb_R[r] + Cb_G[g] + Cb_B[b]) >> SCALE) + 128; + cr = ((Cr_R[r] + Cr_G[g] + Cr_B[b]) >> SCALE) + 128; + + out[0] = (UINT8)y; + out[1] = (UINT8)cb; + out[2] = (UINT8)cr; + out[3] = a; + } +} + +void +ImagingConvertYCbCr2RGB(UINT8 *out, const UINT8 *in, int pixels) { + int x; + UINT8 a; + int r, g, b; + int y, cr, cb; + + for (x = 0; x < pixels; x++, in += 4, out += 4) { y = in[0]; cb = in[1]; cr = in[2]; a = in[3]; - r = y + (( R_Cr[cr]) >> SCALE); + r = y + ((R_Cr[cr]) >> SCALE); g = y + ((G_Cb[cb] + G_Cr[cr]) >> SCALE); - b = y + ((B_Cb[cb] ) >> SCALE); + b = y + ((B_Cb[cb]) >> SCALE); out[0] = (r <= 0) ? 0 : (r >= 255) ? 255 : r; out[1] = (g <= 0) ? 0 : (g >= 255) ? 255 : g; diff --git a/src/libImaging/Copy.c b/src/libImaging/Copy.c index 1bc9b1a70..571133e14 100644 --- a/src/libImaging/Copy.c +++ b/src/libImaging/Copy.c @@ -15,44 +15,43 @@ * See the README file for details on usage and redistribution. */ - #include "Imaging.h" - static Imaging -_copy(Imaging imOut, Imaging imIn) -{ +_copy(Imaging imOut, Imaging imIn) { ImagingSectionCookie cookie; int y; - if (!imIn) - return (Imaging) ImagingError_ValueError(NULL); + if (!imIn) { + return (Imaging)ImagingError_ValueError(NULL); + } imOut = ImagingNew2Dirty(imIn->mode, imOut, imIn); - if (!imOut) + if (!imOut) { return NULL; + } ImagingCopyPalette(imOut, imIn); ImagingSectionEnter(&cookie); - if (imIn->block != NULL && imOut->block != NULL) - memcpy(imOut->block, imIn->block, imIn->ysize * imIn->linesize); - else - for (y = 0; y < imIn->ysize; y++) + if (imIn->block != NULL && imOut->block != NULL) { + memcpy(imOut->block, imIn->block, imIn->ysize * imIn->linesize); + } else { + for (y = 0; y < imIn->ysize; y++) { memcpy(imOut->image[y], imIn->image[y], imIn->linesize); + } + } ImagingSectionLeave(&cookie); return imOut; } Imaging -ImagingCopy(Imaging imIn) -{ +ImagingCopy(Imaging imIn) { return _copy(NULL, imIn); } Imaging -ImagingCopy2(Imaging imOut, Imaging imIn) -{ +ImagingCopy2(Imaging imOut, Imaging imIn) { return _copy(imOut, imIn); } diff --git a/src/libImaging/Crop.c b/src/libImaging/Crop.c index 4407c1b1d..2425b4cd5 100644 --- a/src/libImaging/Crop.c +++ b/src/libImaging/Crop.c @@ -5,9 +5,9 @@ * cut region from image * * history: - * 95-11-27 fl Created - * 98-07-10 fl Fixed "null result" error - * 99-02-05 fl Rewritten to use Paste primitive + * 95-11-27 fl Created + * 98-07-10 fl Fixed "null result" error + * 99-02-05 fl Rewritten to use Paste primitive * * Copyright (c) Secret Labs AB 1997-99. * Copyright (c) Fredrik Lundh 1995. @@ -15,36 +15,38 @@ * See the README file for information on usage and redistribution. */ - #include "Imaging.h" - Imaging -ImagingCrop(Imaging imIn, int sx0, int sy0, int sx1, int sy1) -{ +ImagingCrop(Imaging imIn, int sx0, int sy0, int sx1, int sy1) { Imaging imOut; int xsize, ysize; int dx0, dy0, dx1, dy1; INT32 zero = 0; - if (!imIn) - return (Imaging) ImagingError_ModeError(); + if (!imIn) { + return (Imaging)ImagingError_ModeError(); + } xsize = sx1 - sx0; - if (xsize < 0) + if (xsize < 0) { xsize = 0; + } ysize = sy1 - sy0; - if (ysize < 0) + if (ysize < 0) { ysize = 0; + } imOut = ImagingNewDirty(imIn->mode, xsize, ysize); - if (!imOut) - return NULL; + if (!imOut) { + return NULL; + } ImagingCopyPalette(imOut, imIn); - if (sx0 < 0 || sy0 < 0 || sx1 > imIn->xsize || sy1 > imIn->ysize) - (void) ImagingFill(imOut, &zero); + if (sx0 < 0 || sy0 < 0 || sx1 > imIn->xsize || sy1 > imIn->ysize) { + (void)ImagingFill(imOut, &zero); + } dx0 = -sx0; dy0 = -sy0; diff --git a/src/libImaging/Dib.c b/src/libImaging/Dib.c index 504290231..f8a2901b8 100644 --- a/src/libImaging/Dib.c +++ b/src/libImaging/Dib.c @@ -19,29 +19,27 @@ * See the README file for information on usage and redistribution. */ - #include "Imaging.h" #ifdef _WIN32 #include "ImDib.h" - -char* -ImagingGetModeDIB(int size_out[2]) -{ +char * +ImagingGetModeDIB(int size_out[2]) { /* Get device characteristics */ HDC dc; - char* mode; + char *mode; dc = CreateCompatibleDC(NULL); mode = "P"; if (!(GetDeviceCaps(dc, RASTERCAPS) & RC_PALETTE)) { mode = "RGB"; - if (GetDeviceCaps(dc, BITSPIXEL) == 1) + if (GetDeviceCaps(dc, BITSPIXEL) == 1) { mode = "1"; + } } if (size_out) { @@ -54,10 +52,8 @@ ImagingGetModeDIB(int size_out[2]) return mode; } - ImagingDIB -ImagingNewDIB(const char *mode, int xsize, int ysize) -{ +ImagingNewDIB(const char *mode, int xsize, int ysize) { /* Create a Windows bitmap */ ImagingDIB dib; @@ -65,21 +61,21 @@ ImagingNewDIB(const char *mode, int xsize, int ysize) int i; /* Check mode */ - if (strcmp(mode, "1") != 0 && strcmp(mode, "L") != 0 && - strcmp(mode, "RGB") != 0) - return (ImagingDIB) ImagingError_ModeError(); + if (strcmp(mode, "1") != 0 && strcmp(mode, "L") != 0 && strcmp(mode, "RGB") != 0) { + return (ImagingDIB)ImagingError_ModeError(); + } /* Create DIB context and info header */ /* malloc check ok, small constant allocation */ - dib = (ImagingDIB) malloc(sizeof(*dib)); - if (!dib) - return (ImagingDIB) ImagingError_MemoryError(); + dib = (ImagingDIB)malloc(sizeof(*dib)); + if (!dib) { + return (ImagingDIB)ImagingError_MemoryError(); + } /* malloc check ok, small constant allocation */ - dib->info = (BITMAPINFO*) malloc(sizeof(BITMAPINFOHEADER) + - 256 * sizeof(RGBQUAD)); + dib->info = (BITMAPINFO *)malloc(sizeof(BITMAPINFOHEADER) + 256 * sizeof(RGBQUAD)); if (!dib->info) { free(dib); - return (ImagingDIB) ImagingError_MemoryError(); + return (ImagingDIB)ImagingError_MemoryError(); } memset(dib->info, 0, sizeof(BITMAPINFOHEADER)); @@ -87,7 +83,7 @@ ImagingNewDIB(const char *mode, int xsize, int ysize) dib->info->bmiHeader.biWidth = xsize; dib->info->bmiHeader.biHeight = ysize; dib->info->bmiHeader.biPlanes = 1; - dib->info->bmiHeader.biBitCount = strlen(mode)*8; + dib->info->bmiHeader.biBitCount = strlen(mode) * 8; dib->info->bmiHeader.biCompression = BI_RGB; /* Create DIB */ @@ -95,15 +91,15 @@ ImagingNewDIB(const char *mode, int xsize, int ysize) if (!dib->dc) { free(dib->info); free(dib); - return (ImagingDIB) ImagingError_MemoryError(); + return (ImagingDIB)ImagingError_MemoryError(); } - dib->bitmap = CreateDIBSection(dib->dc, dib->info, DIB_RGB_COLORS, - &dib->bits, NULL, 0); + dib->bitmap = + CreateDIBSection(dib->dc, dib->info, DIB_RGB_COLORS, &dib->bits, NULL, 0); if (!dib->bitmap) { free(dib->info); free(dib); - return (ImagingDIB) ImagingError_MemoryError(); + return (ImagingDIB)ImagingError_MemoryError(); } strcpy(dib->mode, mode); @@ -113,9 +109,9 @@ ImagingNewDIB(const char *mode, int xsize, int ysize) dib->pixelsize = strlen(mode); dib->linesize = (xsize * dib->pixelsize + 3) & -4; - if (dib->pixelsize == 1) - dib->pack = dib->unpack = (ImagingShuffler) memcpy; - else { + if (dib->pixelsize == 1) { + dib->pack = dib->unpack = (ImagingShuffler)memcpy; + } else { dib->pack = ImagingPackBGR; dib->unpack = ImagingPackBGR; } @@ -128,9 +124,7 @@ ImagingNewDIB(const char *mode, int xsize, int ysize) /* Bind a palette to it as well (only required for 8-bit DIBs) */ if (dib->pixelsize == 1) { for (i = 0; i < 256; i++) { - palette[i].rgbRed = - palette[i].rgbGreen = - palette[i].rgbBlue = i; + palette[i].rgbRed = palette[i].rgbGreen = palette[i].rgbBlue = i; palette[i].rgbReserved = 0; } SetDIBColorTable(dib->dc, 0, 256, palette); @@ -138,9 +132,8 @@ ImagingNewDIB(const char *mode, int xsize, int ysize) /* Create an associated palette (for 8-bit displays only) */ if (strcmp(ImagingGetModeDIB(NULL), "P") == 0) { - - char palbuf[sizeof(LOGPALETTE)+256*sizeof(PALETTEENTRY)]; - LPLOGPALETTE pal = (LPLOGPALETTE) palbuf; + char palbuf[sizeof(LOGPALETTE) + 256 * sizeof(PALETTEENTRY)]; + LPLOGPALETTE pal = (LPLOGPALETTE)palbuf; int i, r, g, b; /* Load system palette */ @@ -149,7 +142,6 @@ ImagingNewDIB(const char *mode, int xsize, int ysize) GetSystemPaletteEntries(dib->dc, 0, 256, pal->palPalEntry); if (strcmp(mode, "L") == 0) { - /* Greyscale DIB. Fill all 236 slots with a greyscale ramp * (this is usually overkill on Windows since VGA only offers * 6 bits greyscale resolution). Ignore the slots already @@ -157,16 +149,14 @@ ImagingNewDIB(const char *mode, int xsize, int ysize) i = 10; for (r = 0; r < 236; r++) { - pal->palPalEntry[i].peRed = - pal->palPalEntry[i].peGreen = - pal->palPalEntry[i].peBlue = i; + pal->palPalEntry[i].peRed = pal->palPalEntry[i].peGreen = + pal->palPalEntry[i].peBlue = i; i++; } dib->palette = CreatePalette(pal); } else if (strcmp(mode, "RGB") == 0) { - #ifdef CUBE216 /* Colour DIB. Create a 6x6x6 colour cube (216 entries) and @@ -174,19 +164,20 @@ ImagingNewDIB(const char *mode, int xsize, int ysize) * images. */ i = 10; - for (r = 0; r < 256; r += 51) - for (g = 0; g < 256; g += 51) + for (r = 0; r < 256; r += 51) { + for (g = 0; g < 256; g += 51) { for (b = 0; b < 256; b += 51) { pal->palPalEntry[i].peRed = r; pal->palPalEntry[i].peGreen = g; pal->palPalEntry[i].peBlue = b; i++; } - for (r = 1; r < 22-1; r++) { + } + } + for (r = 1; r < 22 - 1; r++) { /* Black and white are already provided by the cube. */ - pal->palPalEntry[i].peRed = - pal->palPalEntry[i].peGreen = - pal->palPalEntry[i].peBlue = r * 255 / (22-1); + pal->palPalEntry[i].peRed = pal->palPalEntry[i].peGreen = + pal->palPalEntry[i].peBlue = r * 255 / (22 - 1); i++; } @@ -195,105 +186,127 @@ ImagingNewDIB(const char *mode, int xsize, int ysize) /* Colour DIB. Alternate palette. */ i = 10; - for (r = 0; r < 256; r += 37) - for (g = 0; g < 256; g += 32) + for (r = 0; r < 256; r += 37) { + for (g = 0; g < 256; g += 32) { for (b = 0; b < 256; b += 64) { pal->palPalEntry[i].peRed = r; pal->palPalEntry[i].peGreen = g; pal->palPalEntry[i].peBlue = b; i++; } + } + } #endif dib->palette = CreatePalette(pal); - } - } return dib; } void -ImagingPasteDIB(ImagingDIB dib, Imaging im, int xy[4]) -{ +ImagingPasteDIB(ImagingDIB dib, Imaging im, int xy[4]) { /* Paste image data into a bitmap */ /* FIXME: check size! */ int y; - for (y = 0; y < im->ysize; y++) - dib->pack(dib->bits + dib->linesize*(dib->ysize-(xy[1]+y)-1) + - xy[0]*dib->pixelsize, im->image[y], im->xsize); - + for (y = 0; y < im->ysize; y++) { + dib->pack( + dib->bits + dib->linesize * (dib->ysize - (xy[1] + y) - 1) + + xy[0] * dib->pixelsize, + im->image[y], + im->xsize); + } } void -ImagingExposeDIB(ImagingDIB dib, void *dc) -{ +ImagingExposeDIB(ImagingDIB dib, void *dc) { /* Copy bitmap to display */ - if (dib->palette != 0) - SelectPalette((HDC) dc, dib->palette, FALSE); - BitBlt((HDC) dc, 0, 0, dib->xsize, dib->ysize, dib->dc, 0, 0, SRCCOPY); + if (dib->palette != 0) { + SelectPalette((HDC)dc, dib->palette, FALSE); + } + BitBlt((HDC)dc, 0, 0, dib->xsize, dib->ysize, dib->dc, 0, 0, SRCCOPY); } void -ImagingDrawDIB(ImagingDIB dib, void *dc, int dst[4], int src[4]) -{ +ImagingDrawDIB(ImagingDIB dib, void *dc, int dst[4], int src[4]) { /* Copy bitmap to printer/display */ - if (GetDeviceCaps((HDC) dc, RASTERCAPS) & RC_STRETCHDIB) { + if (GetDeviceCaps((HDC)dc, RASTERCAPS) & RC_STRETCHDIB) { /* stretchdib (printers) */ - StretchDIBits((HDC) dc, dst[0], dst[1], dst[2]-dst[0], dst[3]-dst[1], - src[0], src[1], src[2]-src[0], src[3]-src[1], dib->bits, - dib->info, DIB_RGB_COLORS, SRCCOPY); + StretchDIBits( + (HDC)dc, + dst[0], + dst[1], + dst[2] - dst[0], + dst[3] - dst[1], + src[0], + src[1], + src[2] - src[0], + src[3] - src[1], + dib->bits, + dib->info, + DIB_RGB_COLORS, + SRCCOPY); } else { /* stretchblt (displays) */ - if (dib->palette != 0) - SelectPalette((HDC) dc, dib->palette, FALSE); - StretchBlt((HDC) dc, dst[0], dst[1], dst[2]-dst[0], dst[3]-dst[1], - dib->dc, src[0], src[1], src[2]-src[0], src[3]-src[1], - SRCCOPY); + if (dib->palette != 0) { + SelectPalette((HDC)dc, dib->palette, FALSE); + } + StretchBlt( + (HDC)dc, + dst[0], + dst[1], + dst[2] - dst[0], + dst[3] - dst[1], + dib->dc, + src[0], + src[1], + src[2] - src[0], + src[3] - src[1], + SRCCOPY); } } int -ImagingQueryPaletteDIB(ImagingDIB dib, void *dc) -{ +ImagingQueryPaletteDIB(ImagingDIB dib, void *dc) { /* Install bitmap palette */ int n; if (dib->palette != 0) { - /* Realize associated palette */ - HPALETTE now = SelectPalette((HDC) dc, dib->palette, FALSE); - n = RealizePalette((HDC) dc); + HPALETTE now = SelectPalette((HDC)dc, dib->palette, FALSE); + n = RealizePalette((HDC)dc); /* Restore palette */ - SelectPalette((HDC) dc, now, FALSE); + SelectPalette((HDC)dc, now, FALSE); - } else + } else { n = 0; + } return n; /* number of colours that was changed */ } void -ImagingDeleteDIB(ImagingDIB dib) -{ +ImagingDeleteDIB(ImagingDIB dib) { /* Clean up */ - if (dib->palette) + if (dib->palette) { DeleteObject(dib->palette); + } if (dib->bitmap) { SelectObject(dib->dc, dib->old_bitmap); DeleteObject(dib->bitmap); } - if (dib->dc) + if (dib->dc) { DeleteDC(dib->dc); + } free(dib->info); } diff --git a/src/libImaging/Draw.c b/src/libImaging/Draw.c index c1f7684ea..8471ffb17 100644 --- a/src/libImaging/Draw.c +++ b/src/libImaging/Draw.c @@ -35,18 +35,19 @@ #include "Imaging.h" #include +#include -#define CEIL(v) (int) ceil(v) -#define FLOOR(v) ((v) >= 0.0 ? (int) (v) : (int) floor(v)) +#define CEIL(v) (int)ceil(v) +#define FLOOR(v) ((v) >= 0.0 ? (int)(v) : (int)floor(v)) -#define INK8(ink) (*(UINT8*)ink) +#define INK8(ink) (*(UINT8 *)ink) /* * Rounds around zero (up=away from zero, down=towards zero) * This guarantees that ROUND_UP|DOWN(f) == -ROUND_UP|DOWN(-f) */ -#define ROUND_UP(f) ((int) ((f) >= 0.0 ? floor((f) + 0.5F) : -floor(fabs(f) + 0.5F))) -#define ROUND_DOWN(f) ((int) ((f) >= 0.0 ? ceil((f) - 0.5F) : -ceil(fabs(f) - 0.5F))) +#define ROUND_UP(f) ((int)((f) >= 0.0 ? floor((f) + 0.5F) : -floor(fabs(f) + 0.5F))) +#define ROUND_DOWN(f) ((int)((f) >= 0.0 ? ceil((f)-0.5F) : -ceil(fabs(f) - 0.5F))) /* -------------------------------------------------------------------- */ /* Primitives */ @@ -64,33 +65,31 @@ typedef struct { typedef void (*hline_handler)(Imaging, int, int, int, int); static inline void -point8(Imaging im, int x, int y, int ink) -{ +point8(Imaging im, int x, int y, int ink) { if (x >= 0 && x < im->xsize && y >= 0 && y < im->ysize) { if (strncmp(im->mode, "I;16", 4) == 0) { - im->image8[y][x*2] = (UINT8) ink; - im->image8[y][x*2+1] = (UINT8) ink; + im->image8[y][x * 2] = (UINT8)ink; + im->image8[y][x * 2 + 1] = (UINT8)ink; } else { - im->image8[y][x] = (UINT8) ink; + im->image8[y][x] = (UINT8)ink; } } } static inline void -point32(Imaging im, int x, int y, int ink) -{ - if (x >= 0 && x < im->xsize && y >= 0 && y < im->ysize) +point32(Imaging im, int x, int y, int ink) { + if (x >= 0 && x < im->xsize && y >= 0 && y < im->ysize) { im->image32[y][x] = ink; + } } static inline void -point32rgba(Imaging im, int x, int y, int ink) -{ +point32rgba(Imaging im, int x, int y, int ink) { unsigned int tmp1; if (x >= 0 && x < im->xsize && y >= 0 && y < im->ysize) { - UINT8* out = (UINT8*) im->image[y]+x*4; - UINT8* in = (UINT8*) &ink; + UINT8 *out = (UINT8 *)im->image[y] + x * 4; + UINT8 *in = (UINT8 *)&ink; out[0] = BLEND(in[3], out[0], in[0], tmp1); out[1] = BLEND(in[3], out[1], in[1], tmp1); out[2] = BLEND(in[3], out[2], in[2], tmp1); @@ -98,121 +97,129 @@ point32rgba(Imaging im, int x, int y, int ink) } static inline void -hline8(Imaging im, int x0, int y0, int x1, int ink) -{ +hline8(Imaging im, int x0, int y0, int x1, int ink) { int tmp, pixelwidth; if (y0 >= 0 && y0 < im->ysize) { - if (x0 > x1) + if (x0 > x1) { tmp = x0, x0 = x1, x1 = tmp; - if (x0 < 0) + } + if (x0 < 0) { x0 = 0; - else if (x0 >= im->xsize) + } else if (x0 >= im->xsize) { return; - if (x1 < 0) + } + if (x1 < 0) { return; - else if (x1 >= im->xsize) - x1 = im->xsize-1; + } else if (x1 >= im->xsize) { + x1 = im->xsize - 1; + } if (x0 <= x1) { pixelwidth = strncmp(im->mode, "I;16", 4) == 0 ? 2 : 1; - memset(im->image8[y0] + x0 * pixelwidth, (UINT8) ink, - (x1 - x0 + 1) * pixelwidth); + memset( + im->image8[y0] + x0 * pixelwidth, + (UINT8)ink, + (x1 - x0 + 1) * pixelwidth); } } } static inline void -hline32(Imaging im, int x0, int y0, int x1, int ink) -{ +hline32(Imaging im, int x0, int y0, int x1, int ink) { int tmp; - INT32* p; + INT32 *p; if (y0 >= 0 && y0 < im->ysize) { - if (x0 > x1) + if (x0 > x1) { tmp = x0, x0 = x1, x1 = tmp; - if (x0 < 0) + } + if (x0 < 0) { x0 = 0; - else if (x0 >= im->xsize) + } else if (x0 >= im->xsize) { return; - if (x1 < 0) + } + if (x1 < 0) { return; - else if (x1 >= im->xsize) - x1 = im->xsize-1; + } else if (x1 >= im->xsize) { + x1 = im->xsize - 1; + } p = im->image32[y0]; - while (x0 <= x1) + while (x0 <= x1) { p[x0++] = ink; + } } } static inline void -hline32rgba(Imaging im, int x0, int y0, int x1, int ink) -{ +hline32rgba(Imaging im, int x0, int y0, int x1, int ink) { int tmp; unsigned int tmp1; if (y0 >= 0 && y0 < im->ysize) { - if (x0 > x1) + if (x0 > x1) { tmp = x0, x0 = x1, x1 = tmp; - if (x0 < 0) + } + if (x0 < 0) { x0 = 0; - else if (x0 >= im->xsize) + } else if (x0 >= im->xsize) { return; - if (x1 < 0) + } + if (x1 < 0) { return; - else if (x1 >= im->xsize) - x1 = im->xsize-1; + } else if (x1 >= im->xsize) { + x1 = im->xsize - 1; + } if (x0 <= x1) { - UINT8* out = (UINT8*) im->image[y0]+x0*4; - UINT8* in = (UINT8*) &ink; + UINT8 *out = (UINT8 *)im->image[y0] + x0 * 4; + UINT8 *in = (UINT8 *)&ink; while (x0 <= x1) { out[0] = BLEND(in[3], out[0], in[0], tmp1); out[1] = BLEND(in[3], out[1], in[1], tmp1); out[2] = BLEND(in[3], out[2], in[2], tmp1); - x0++; out += 4; + x0++; + out += 4; } } } } static inline void -line8(Imaging im, int x0, int y0, int x1, int y1, int ink) -{ +line8(Imaging im, int x0, int y0, int x1, int y1, int ink) { int i, n, e; int dx, dy; int xs, ys; /* normalize coordinates */ - dx = x1-x0; - if (dx < 0) + dx = x1 - x0; + if (dx < 0) { dx = -dx, xs = -1; - else + } else { xs = 1; - dy = y1-y0; - if (dy < 0) + } + dy = y1 - y0; + if (dy < 0) { dy = -dy, ys = -1; - else + } else { ys = 1; + } n = (dx > dy) ? dx : dy; - if (dx == 0) - + if (dx == 0) { /* vertical */ for (i = 0; i < dy; i++) { point8(im, x0, y0, ink); y0 += ys; } - else if (dy == 0) - + } else if (dy == 0) { /* horizontal */ for (i = 0; i < dx; i++) { point8(im, x0, y0, ink); x0 += xs; } - else if (dx > dy) { - + } else if (dx > dy) { /* bresenham, horizontal slope */ n = dx; dy += dy; @@ -230,7 +237,6 @@ line8(Imaging im, int x0, int y0, int x1, int y1, int ink) } } else { - /* bresenham, vertical slope */ n = dy; dx += dx; @@ -246,49 +252,46 @@ line8(Imaging im, int x0, int y0, int x1, int y1, int ink) e += dx; y0 += ys; } - } } static inline void -line32(Imaging im, int x0, int y0, int x1, int y1, int ink) -{ +line32(Imaging im, int x0, int y0, int x1, int y1, int ink) { int i, n, e; int dx, dy; int xs, ys; /* normalize coordinates */ - dx = x1-x0; - if (dx < 0) + dx = x1 - x0; + if (dx < 0) { dx = -dx, xs = -1; - else + } else { xs = 1; - dy = y1-y0; - if (dy < 0) + } + dy = y1 - y0; + if (dy < 0) { dy = -dy, ys = -1; - else + } else { ys = 1; + } n = (dx > dy) ? dx : dy; - if (dx == 0) - + if (dx == 0) { /* vertical */ for (i = 0; i < dy; i++) { point32(im, x0, y0, ink); y0 += ys; } - else if (dy == 0) - + } else if (dy == 0) { /* horizontal */ for (i = 0; i < dx; i++) { point32(im, x0, y0, ink); x0 += xs; } - else if (dx > dy) { - + } else if (dx > dy) { /* bresenham, horizontal slope */ n = dx; dy += dy; @@ -306,7 +309,6 @@ line32(Imaging im, int x0, int y0, int x1, int y1, int ink) } } else { - /* bresenham, vertical slope */ n = dy; dx += dx; @@ -322,49 +324,46 @@ line32(Imaging im, int x0, int y0, int x1, int y1, int ink) e += dx; y0 += ys; } - } } static inline void -line32rgba(Imaging im, int x0, int y0, int x1, int y1, int ink) -{ +line32rgba(Imaging im, int x0, int y0, int x1, int y1, int ink) { int i, n, e; int dx, dy; int xs, ys; /* normalize coordinates */ - dx = x1-x0; - if (dx < 0) + dx = x1 - x0; + if (dx < 0) { dx = -dx, xs = -1; - else + } else { xs = 1; - dy = y1-y0; - if (dy < 0) + } + dy = y1 - y0; + if (dy < 0) { dy = -dy, ys = -1; - else + } else { ys = 1; + } n = (dx > dy) ? dx : dy; - if (dx == 0) - + if (dx == 0) { /* vertical */ for (i = 0; i < dy; i++) { point32rgba(im, x0, y0, ink); y0 += ys; } - else if (dy == 0) - + } else if (dy == 0) { /* horizontal */ for (i = 0; i < dx; i++) { point32rgba(im, x0, y0, ink); x0 += xs; } - else if (dx > dy) { - + } else if (dx > dy) { /* bresenham, horizontal slope */ n = dx; dy += dy; @@ -382,7 +381,6 @@ line32rgba(Imaging im, int x0, int y0, int x1, int y1, int ink) } } else { - /* bresenham, vertical slope */ n = dy; dx += dx; @@ -398,33 +396,57 @@ line32rgba(Imaging im, int x0, int y0, int x1, int y1, int ink) e += dx; y0 += ys; } - } } static int -x_cmp(const void *x0, const void *x1) -{ - float diff = *((float*)x0) - *((float*)x1); - if (diff < 0) +x_cmp(const void *x0, const void *x1) { + float diff = *((float *)x0) - *((float *)x1); + if (diff < 0) { return -1; - else if (diff > 0) + } else if (diff > 0) { return 1; - else + } else { return 0; + } } +static void +draw_horizontal_lines( + Imaging im, int n, Edge *e, int ink, int *x_pos, int y, hline_handler hline) { + int i; + for (i = 0; i < n; i++) { + if (e[i].ymin == y && e[i].ymin == e[i].ymax) { + int xmax; + int xmin = e[i].xmin; + if (*x_pos < xmin) { + // Line would be after the current position + continue; + } + + xmax = e[i].xmax; + if (*x_pos > xmin) { + // Line would be partway through x_pos, so increase the starting point + xmin = *x_pos; + if (xmax < xmin) { + // Line would now end before it started + continue; + } + } + + (*hline)(im, xmin, e[i].ymin, xmax, ink); + *x_pos = xmax + 1; + } + } +} /* * Filled polygon draw function using scan line algorithm. */ static inline int -polygon_generic(Imaging im, int n, Edge *e, int ink, int eofill, - hline_handler hline) -{ - - Edge** edge_table; - float* xx; +polygon_generic(Imaging im, int n, Edge *e, int ink, int eofill, hline_handler hline) { + Edge **edge_table; + float *xx; int edge_count = 0; int ymin = im->ysize - 1; int ymax = 0; @@ -436,24 +458,21 @@ polygon_generic(Imaging im, int n, Edge *e, int ink, int eofill, /* Initialize the edge table and find polygon boundaries */ /* malloc check ok, using calloc */ - edge_table = calloc(n, sizeof(Edge*)); + edge_table = calloc(n, sizeof(Edge *)); if (!edge_table) { return -1; } for (i = 0; i < n; i++) { - /* This causes the pixels of horizontal edges to be drawn twice :( - * but without it there are inconsistencies in ellipses */ - if (e[i].ymin == e[i].ymax) { - (*hline)(im, e[i].xmin, e[i].ymin, e[i].xmax, ink); - continue; - } if (ymin > e[i].ymin) { ymin = e[i].ymin; } if (ymax < e[i].ymax) { ymax = e[i].ymax; } + if (e[i].ymin == e[i].ymax) { + continue; + } edge_table[edge_count++] = (e + i); } if (ymin < 0) { @@ -472,8 +491,9 @@ polygon_generic(Imaging im, int n, Edge *e, int ink, int eofill, } for (; ymin <= ymax; ymin++) { int j = 0; + int x_pos = 0; for (i = 0; i < edge_count; i++) { - Edge* current = edge_table[i]; + Edge *current = edge_table[i]; if (ymin >= current->ymin && ymin <= current->ymax) { xx[j++] = (ymin - current->y0) * current->dx + current->x0; } @@ -485,8 +505,30 @@ polygon_generic(Imaging im, int n, Edge *e, int ink, int eofill, } qsort(xx, j, sizeof(float), x_cmp); for (i = 1; i < j; i += 2) { - (*hline)(im, ROUND_UP(xx[i - 1]), ymin, ROUND_DOWN(xx[i]), ink); + int x_end = ROUND_DOWN(xx[i]); + if (x_end < x_pos) { + // Line would be before the current position + continue; + } + draw_horizontal_lines(im, n, e, ink, &x_pos, ymin, hline); + if (x_end < x_pos) { + // Line would be before the current position + continue; + } + + int x_start = ROUND_UP(xx[i - 1]); + if (x_pos > x_start) { + // Line would be partway through x_pos, so increase the starting point + x_start = x_pos; + if (x_end < x_start) { + // Line would now end before it started + continue; + } + } + (*hline)(im, x_start, ymin, x_end, ink); + x_pos = x_end + 1; } + draw_horizontal_lines(im, n, e, ink, &x_pos, ymin, hline); } free(xx); @@ -495,47 +537,46 @@ polygon_generic(Imaging im, int n, Edge *e, int ink, int eofill, } static inline int -polygon8(Imaging im, int n, Edge *e, int ink, int eofill) -{ +polygon8(Imaging im, int n, Edge *e, int ink, int eofill) { return polygon_generic(im, n, e, ink, eofill, hline8); } static inline int -polygon32(Imaging im, int n, Edge *e, int ink, int eofill) -{ +polygon32(Imaging im, int n, Edge *e, int ink, int eofill) { return polygon_generic(im, n, e, ink, eofill, hline32); } static inline int -polygon32rgba(Imaging im, int n, Edge *e, int ink, int eofill) -{ +polygon32rgba(Imaging im, int n, Edge *e, int ink, int eofill) { return polygon_generic(im, n, e, ink, eofill, hline32rgba); } static inline void -add_edge(Edge *e, int x0, int y0, int x1, int y1) -{ +add_edge(Edge *e, int x0, int y0, int x1, int y1) { /* printf("edge %d %d %d %d\n", x0, y0, x1, y1); */ - if (x0 <= x1) + if (x0 <= x1) { e->xmin = x0, e->xmax = x1; - else + } else { e->xmin = x1, e->xmax = x0; + } - if (y0 <= y1) + if (y0 <= y1) { e->ymin = y0, e->ymax = y1; - else + } else { e->ymin = y1, e->ymax = y0; + } if (y0 == y1) { e->d = 0; e->dx = 0.0; } else { - e->dx = ((float)(x1-x0)) / (y1-y0); - if (y0 == e->ymin) + e->dx = ((float)(x1 - x0)) / (y1 - y0); + if (y0 == e->ymin) { e->d = 1; - else + } else { e->d = -1; + } } e->x0 = x0; @@ -549,27 +590,26 @@ typedef struct { int (*polygon)(Imaging im, int n, Edge *e, int ink, int eofill); } DRAW; -DRAW draw8 = { point8, hline8, line8, polygon8 }; -DRAW draw32 = { point32, hline32, line32, polygon32 }; -DRAW draw32rgba = { point32rgba, hline32rgba, line32rgba, polygon32rgba }; +DRAW draw8 = {point8, hline8, line8, polygon8}; +DRAW draw32 = {point32, hline32, line32, polygon32}; +DRAW draw32rgba = {point32rgba, hline32rgba, line32rgba, polygon32rgba}; /* -------------------------------------------------------------------- */ /* Interface */ /* -------------------------------------------------------------------- */ -#define DRAWINIT()\ - if (im->image8) {\ - draw = &draw8;\ - ink = INK8(ink_);\ - } else {\ - draw = (op) ? &draw32rgba : &draw32; \ - memcpy(&ink, ink_, sizeof(ink)); \ +#define DRAWINIT() \ + if (im->image8) { \ + draw = &draw8; \ + ink = INK8(ink_); \ + } else { \ + draw = (op) ? &draw32rgba : &draw32; \ + memcpy(&ink, ink_, sizeof(ink)); \ } int -ImagingDrawPoint(Imaging im, int x0, int y0, const void* ink_, int op) -{ - DRAW* draw; +ImagingDrawPoint(Imaging im, int x0, int y0, const void *ink_, int op) { + DRAW *draw; INT32 ink; DRAWINIT(); @@ -580,10 +620,8 @@ ImagingDrawPoint(Imaging im, int x0, int y0, const void* ink_, int op) } int -ImagingDrawLine(Imaging im, int x0, int y0, int x1, int y1, const void* ink_, - int op) -{ - DRAW* draw; +ImagingDrawLine(Imaging im, int x0, int y0, int x1, int y1, const void *ink_, int op) { + DRAW *draw; INT32 ink; DRAWINIT(); @@ -594,10 +632,9 @@ ImagingDrawLine(Imaging im, int x0, int y0, int x1, int y1, const void* ink_, } int -ImagingDrawWideLine(Imaging im, int x0, int y0, int x1, int y1, - const void* ink_, int width, int op) -{ - DRAW* draw; +ImagingDrawWideLine( + Imaging im, int x0, int y0, int x1, int y1, const void *ink_, int width, int op) { + DRAW *draw; INT32 ink; int dx, dy; double big_hypotenuse, small_hypotenuse, ratio_max, ratio_min; @@ -606,14 +643,14 @@ ImagingDrawWideLine(Imaging im, int x0, int y0, int x1, int y1, DRAWINIT(); - dx = x1-x0; - dy = y1-y0; + dx = x1 - x0; + dy = y1 - y0; if (dx == 0 && dy == 0) { draw->point(im, x0, y0, ink); return 0; } - big_hypotenuse = sqrt((double) (dx*dx + dy*dy)); + big_hypotenuse = hypot(dx, dy); small_hypotenuse = (width - 1) / 2.0; ratio_max = ROUND_UP(small_hypotenuse) / big_hypotenuse; ratio_min = ROUND_DOWN(small_hypotenuse) / big_hypotenuse; @@ -627,13 +664,12 @@ ImagingDrawWideLine(Imaging im, int x0, int y0, int x1, int y1, {x0 - dxmin, y0 + dymax}, {x1 - dxmin, y1 + dymax}, {x1 + dxmax, y1 - dymin}, - {x0 + dxmax, y0 - dymin} - }; + {x0 + dxmax, y0 - dymin}}; - add_edge(e+0, vertices[0][0], vertices[0][1], vertices[1][0], vertices[1][1]); - add_edge(e+1, vertices[1][0], vertices[1][1], vertices[2][0], vertices[2][1]); - add_edge(e+2, vertices[2][0], vertices[2][1], vertices[3][0], vertices[3][1]); - add_edge(e+3, vertices[3][0], vertices[3][1], vertices[0][0], vertices[0][1]); + add_edge(e + 0, vertices[0][0], vertices[0][1], vertices[1][0], vertices[1][1]); + add_edge(e + 1, vertices[1][0], vertices[1][1], vertices[2][0], vertices[2][1]); + add_edge(e + 2, vertices[2][0], vertices[2][1], vertices[3][0], vertices[3][1]); + add_edge(e + 3, vertices[3][0], vertices[3][1], vertices[0][0], vertices[0][1]); draw->polygon(im, 4, e, ink, 0); } @@ -641,34 +677,44 @@ ImagingDrawWideLine(Imaging im, int x0, int y0, int x1, int y1, } int -ImagingDrawRectangle(Imaging im, int x0, int y0, int x1, int y1, - const void* ink_, int fill, int width, int op) -{ +ImagingDrawRectangle( + Imaging im, + int x0, + int y0, + int x1, + int y1, + const void *ink_, + int fill, + int width, + int op) { int i; int y; int tmp; - DRAW* draw; + DRAW *draw; INT32 ink; DRAWINIT(); - if (y0 > y1) + if (y0 > y1) { tmp = y0, y0 = y1, y1 = tmp; + } if (fill) { - - if (y0 < 0) + if (y0 < 0) { y0 = 0; - else if (y0 >= im->ysize) + } else if (y0 >= im->ysize) { return 0; + } - if (y1 < 0) + if (y1 < 0) { return 0; - else if (y1 > im->ysize) + } else if (y1 > im->ysize) { y1 = im->ysize; + } - for (y = y0; y <= y1; y++) + for (y = y0; y <= y1; y++) { draw->hline(im, x0, y, x1, ink); + } } else { /* outline */ @@ -676,10 +722,10 @@ ImagingDrawRectangle(Imaging im, int x0, int y0, int x1, int y1, width = 1; } for (i = 0; i < width; i++) { - draw->hline(im, x0, y0+i, x1, ink); - draw->hline(im, x0, y1-i, x1, ink); - draw->line(im, x1-i, y0, x1-i, y1, ink); - draw->line(im, x0+i, y1, x0+i, y0, ink); + draw->hline(im, x0, y0 + i, x1, ink); + draw->hline(im, x0, y1 - i, x1, ink); + draw->line(im, x1 - i, y0, x1 - i, y1, ink); + draw->line(im, x0 + i, y1, x0 + i, y0, ink); } } @@ -687,267 +733,898 @@ ImagingDrawRectangle(Imaging im, int x0, int y0, int x1, int y1, } int -ImagingDrawPolygon(Imaging im, int count, int* xy, const void* ink_, - int fill, int op) -{ +ImagingDrawPolygon(Imaging im, int count, int *xy, const void *ink_, int fill, int op) { int i, n; - DRAW* draw; + DRAW *draw; INT32 ink; - if (count <= 0) + if (count <= 0) { return 0; + } DRAWINIT(); if (fill) { - /* Build edge list */ /* malloc check ok, using calloc */ - Edge* e = calloc(count, sizeof(Edge)); + Edge *e = calloc(count, sizeof(Edge)); if (!e) { - (void) ImagingError_MemoryError(); + (void)ImagingError_MemoryError(); return -1; } - for (i = n = 0; i < count-1; i++) - add_edge(&e[n++], xy[i+i], xy[i+i+1], xy[i+i+2], xy[i+i+3]); - if (xy[i+i] != xy[0] || xy[i+i+1] != xy[1]) - add_edge(&e[n++], xy[i+i], xy[i+i+1], xy[0], xy[1]); + for (i = n = 0; i < count - 1; i++) { + add_edge(&e[n++], xy[i + i], xy[i + i + 1], xy[i + i + 2], xy[i + i + 3]); + } + if (xy[i + i] != xy[0] || xy[i + i + 1] != xy[1]) { + add_edge(&e[n++], xy[i + i], xy[i + i + 1], xy[0], xy[1]); + } draw->polygon(im, n, e, ink, 0); free(e); } else { - /* Outline */ - for (i = 0; i < count-1; i++) - draw->line(im, xy[i+i], xy[i+i+1], xy[i+i+2], xy[i+i+3], ink); - draw->line(im, xy[i+i], xy[i+i+1], xy[0], xy[1], ink); - + for (i = 0; i < count - 1; i++) { + draw->line(im, xy[i + i], xy[i + i + 1], xy[i + i + 2], xy[i + i + 3], ink); + } + draw->line(im, xy[i + i], xy[i + i + 1], xy[0], xy[1], ink); } return 0; } int -ImagingDrawBitmap(Imaging im, int x0, int y0, Imaging bitmap, const void* ink, - int op) -{ +ImagingDrawBitmap(Imaging im, int x0, int y0, Imaging bitmap, const void *ink, int op) { return ImagingFill2( - im, ink, bitmap, - x0, y0, x0 + bitmap->xsize, y0 + bitmap->ysize - ); + im, ink, bitmap, x0, y0, x0 + bitmap->xsize, y0 + bitmap->ysize); } /* -------------------------------------------------------------------- */ /* standard shapes */ -#define ARC 0 -#define CHORD 1 -#define PIESLICE 2 +// Imagine 2D plane and ellipse with center in (0, 0) and semi-major axes a and b. +// Then quarter_* stuff approximates its top right quarter (x, y >= 0) with integer +// points from set {(2x+x0, 2y+y0) | x,y in Z} where x0, y0 are from {0, 1} and +// are such that point (a, b) is in the set. -static void -ellipsePoint(int cx, int cy, int w, int h, - float i, int *x, int *y) -{ - float i_cos, i_sin; - float x_f, y_f; - double modf_int; - i_cos = cos(i*M_PI/180); - i_sin = sin(i*M_PI/180); - x_f = (i_cos * w/2) + cx; - y_f = (i_sin * h/2) + cy; - if (modf(x_f, &modf_int) == 0.5) { - *x = i_cos > 0 ? FLOOR(x_f) : CEIL(x_f); +typedef struct { + int32_t a, b, cx, cy, ex, ey; + int64_t a2, b2, a2b2; + int8_t finished; +} quarter_state; + +void +quarter_init(quarter_state *s, int32_t a, int32_t b) { + if (a < 0 || b < 0) { + s->finished = 1; } else { - *x = FLOOR(x_f + 0.5); - } - if (modf(y_f, &modf_int) == 0.5) { - *y = i_sin > 0 ? FLOOR(y_f) : CEIL(y_f); - } else { - *y = FLOOR(y_f + 0.5); + s->a = a; + s->b = b; + s->cx = a; + s->cy = b % 2; + s->ex = a % 2; + s->ey = b; + s->a2 = a * a; + s->b2 = b * b; + s->a2b2 = s->a2 * s->b2; + s->finished = 0; } } -static int -ellipse(Imaging im, int x0, int y0, int x1, int y1, - float start, float end, const void* ink_, int fill, - int width, int mode, int op) -{ - float i; - int inner; - int n; - int maxEdgeCount; - int w, h; - int x, y; - int cx, cy; - int lx = 0, ly = 0; - int sx = 0, sy = 0; - int lx_inner = 0, ly_inner = 0; - int sx_inner = 0, sy_inner = 0; - DRAW* draw; - INT32 ink; - Edge* e; +// deviation of the point from ellipse curve, basically a substitution +// of the point into the ellipse equation +int64_t +quarter_delta(quarter_state *s, int64_t x, int64_t y) { + return llabs(s->a2 * y * y + s->b2 * x * x - s->a2b2); +} - DRAWINIT(); - - while (end < start) - end += 360; - - if (end - start > 360) { - // no need to go in loops - end = start + 361; +int8_t +quarter_next(quarter_state *s, int32_t *ret_x, int32_t *ret_y) { + if (s->finished) { + return -1; } - - w = x1 - x0; - h = y1 - y0; - if (w <= 0 || h <= 0) - return 0; - - cx = (x0 + x1) / 2; - cy = (y0 + y1) / 2; - - if (!fill && width <= 1) { - for (i = start; i < end+1; i++) { - if (i > end) { - i = end; - } - ellipsePoint(cx, cy, w, h, i, &x, &y); - if (i != start) - draw->line(im, lx, ly, x, y, ink); - else - sx = x, sy = y; - lx = x, ly = y; - } - - if (i != start) { - if (mode == PIESLICE) { - if (x != cx || y != cy) { - draw->line(im, x, y, cx, cy, ink); - draw->line(im, cx, cy, sx, sy, ink); - } - } else if (mode == CHORD) { - if (x != sx || y != sy) - draw->line(im, x, y, sx, sy, ink); - } - } + *ret_x = s->cx; + *ret_y = s->cy; + if (s->cx == s->ex && s->cy == s->ey) { + s->finished = 1; } else { - inner = (mode == ARC || !fill) ? 1 : 0; - - // Build edge list - // malloc check UNDONE, FLOAT? - maxEdgeCount = ceil(end - start); - if (inner) { - maxEdgeCount *= 2; - } - maxEdgeCount += 3; - e = calloc(maxEdgeCount, sizeof(Edge)); - if (!e) { - ImagingError_MemoryError(); - return -1; - } - - // Outer circle - n = 0; - for (i = start; i < end+1; i++) { - if (i > end) { - i = end; + // Bresenham's algorithm, possible optimization: only consider 2 of 3 + // next points depending on current slope + int32_t nx = s->cx; + int32_t ny = s->cy + 2; + int64_t ndelta = quarter_delta(s, nx, ny); + if (nx > 1) { + int64_t newdelta = quarter_delta(s, s->cx - 2, s->cy + 2); + if (ndelta > newdelta) { + nx = s->cx - 2; + ny = s->cy + 2; + ndelta = newdelta; } - ellipsePoint(cx, cy, w, h, i, &x, &y); - if (i == start) { - sx = x, sy = y; - } else { - add_edge(&e[n++], lx, ly, x, y); - } - lx = x, ly = y; - } - if (n == 0) - return 0; - - if (inner) { - // Inner circle - x0 += width - 1; - y0 += width - 1; - x1 -= width - 1; - y1 -= width - 1; - - w = x1 - x0; - h = y1 - y0; - if (w <= 0 || h <= 0) { - // ARC with no gap in the middle is a PIESLICE - mode = PIESLICE; - inner = 0; - } else { - for (i = start; i < end+1; i++) { - if (i > end) { - i = end; - } - ellipsePoint(cx, cy, w, h, i, &x, &y); - if (i == start) - sx_inner = x, sy_inner = y; - else - add_edge(&e[n++], lx_inner, ly_inner, x, y); - lx_inner = x, ly_inner = y; - } + newdelta = quarter_delta(s, s->cx - 2, s->cy); + if (ndelta > newdelta) { + nx = s->cx - 2; + ny = s->cy; } } - - if (end - start < 360) { - // Close polygon - if (mode == PIESLICE) { - if (x != cx || y != cy) { - add_edge(&e[n++], sx, sy, cx, cy); - add_edge(&e[n++], cx, cy, lx, ly); - if (inner) { - ImagingDrawWideLine(im, sx, sy, cx, cy, &ink, width, op); - ImagingDrawWideLine(im, cx, cy, lx, ly, &ink, width, op); - } - } - } else if (mode == CHORD) { - add_edge(&e[n++], sx, sy, lx, ly); - if (inner) { - add_edge(&e[n++], sx_inner, sy_inner, lx_inner, ly_inner); - } - } else if (mode == ARC) { - add_edge(&e[n++], sx, sy, sx_inner, sy_inner); - add_edge(&e[n++], lx, ly, lx_inner, ly_inner); - } - } - - draw->polygon(im, n, e, ink, 0); - - free(e); + s->cx = nx; + s->cy = ny; } - return 0; } +// quarter_* stuff can "draw" a quarter of an ellipse with thickness 1, great. +// Now we use ellipse_* stuff to join all four quarters of two different sized +// ellipses and receive horizontal segments of a complete ellipse with +// specified thickness. +// +// Still using integer grid with step 2 at this point (like in quarter_*) +// to ease angle clipping in future. + +typedef struct { + quarter_state st_o, st_i; + int32_t py, pl, pr; + int32_t cy[4], cl[4], cr[4]; + int8_t bufcnt; + int8_t finished; + int8_t leftmost; +} ellipse_state; + +void +ellipse_init(ellipse_state *s, int32_t a, int32_t b, int32_t w) { + s->bufcnt = 0; + s->leftmost = a % 2; + quarter_init(&s->st_o, a, b); + if (w < 1 || quarter_next(&s->st_o, &s->pr, &s->py) == -1) { + s->finished = 1; + } else { + s->finished = 0; + quarter_init(&s->st_i, a - 2 * (w - 1), b - 2 * (w - 1)); + s->pl = s->leftmost; + } +} + +int8_t +ellipse_next(ellipse_state *s, int32_t *ret_x0, int32_t *ret_y, int32_t *ret_x1) { + if (s->bufcnt == 0) { + if (s->finished) { + return -1; + } + int32_t y = s->py; + int32_t l = s->pl; + int32_t r = s->pr; + int32_t cx = 0, cy = 0; + int8_t next_ret; + while ((next_ret = quarter_next(&s->st_o, &cx, &cy)) != -1 && cy <= y) { + } + if (next_ret == -1) { + s->finished = 1; + } else { + s->pr = cx; + s->py = cy; + } + while ((next_ret = quarter_next(&s->st_i, &cx, &cy)) != -1 && cy <= y) { + l = cx; + } + s->pl = next_ret == -1 ? s->leftmost : cx; + + if ((l > 0 || l < r) && y > 0) { + s->cl[s->bufcnt] = l == 0 ? 2 : l; + s->cy[s->bufcnt] = y; + s->cr[s->bufcnt] = r; + ++s->bufcnt; + } + if (y > 0) { + s->cl[s->bufcnt] = -r; + s->cy[s->bufcnt] = y; + s->cr[s->bufcnt] = -l; + ++s->bufcnt; + } + if (l > 0 || l < r) { + s->cl[s->bufcnt] = l == 0 ? 2 : l; + s->cy[s->bufcnt] = -y; + s->cr[s->bufcnt] = r; + ++s->bufcnt; + } + s->cl[s->bufcnt] = -r; + s->cy[s->bufcnt] = -y; + s->cr[s->bufcnt] = -l; + ++s->bufcnt; + } + --s->bufcnt; + *ret_x0 = s->cl[s->bufcnt]; + *ret_y = s->cy[s->bufcnt]; + *ret_x1 = s->cr[s->bufcnt]; + return 0; +} + +// Clipping tree consists of half-plane clipping nodes and combining nodes. +// We can throw a horizontal segment in such a tree and collect an ordered set +// of resulting disjoint clipped segments organized into a sorted linked list +// of their end points. +typedef enum { + CT_AND, // intersection + CT_OR, // union + CT_CLIP // half-plane clipping +} clip_type; + +typedef struct clip_node { + clip_type type; + double a, b, c; // half-plane coeffs, only used in clipping nodes + struct clip_node *l; // child pointers, are only non-NULL in combining nodes + struct clip_node *r; +} clip_node; + +// Linked list for the ends of the clipped horizontal segments. +// Since the segment is always horizontal, we don't need to store Y coordinate. +typedef struct event_list { + int32_t x; + int8_t type; // used internally, 1 for the left end (smaller X), -1 for the + // right end; pointless in output since the output segments + // are disjoint, therefore the types would always come in pairs + // and interchange (1 -1 1 -1 ...) + struct event_list *next; +} event_list; + +// Mirrors all the clipping nodes of the tree relative to the y = x line. +void +clip_tree_transpose(clip_node *root) { + if (root != NULL) { + if (root->type == CT_CLIP) { + double t = root->a; + root->a = root->b; + root->b = t; + } + clip_tree_transpose(root->l); + clip_tree_transpose(root->r); + } +} + +// Outputs a sequence of open-close events (types -1 and 1) for +// non-intersecting segments sorted by X coordinate. +// Combining nodes (AND, OR) may also accept sequences for intersecting +// segments, i.e. something like correct bracket sequences. int -ImagingDrawArc(Imaging im, int x0, int y0, int x1, int y1, - float start, float end, const void* ink, int width, int op) -{ - return ellipse(im, x0, y0, x1, y1, start, end, ink, 0, width, ARC, op); +clip_tree_do_clip( + clip_node *root, int32_t x0, int32_t y, int32_t x1, event_list **ret) { + if (root == NULL) { + event_list *start = malloc(sizeof(event_list)); + if (!start) { + ImagingError_MemoryError(); + return -1; + } + event_list *end = malloc(sizeof(event_list)); + if (!end) { + free(start); + ImagingError_MemoryError(); + return -1; + } + start->x = x0; + start->type = 1; + start->next = end; + end->x = x1; + end->type = -1; + end->next = NULL; + *ret = start; + return 0; + } + if (root->type == CT_CLIP) { + double eps = 1e-9; + double A = root->a; + double B = root->b; + double C = root->c; + if (fabs(A) < eps) { + if (B * y + C < -eps) { + x0 = 1; + x1 = 0; + } + } else { + // X of intersection + double ix = -(B * y + C) / A; + if (A * x0 + B * y + C < eps) { + x0 = lround(fmax(x0, ix)); + } + if (A * x1 + B * y + C < eps) { + x1 = lround(fmin(x1, ix)); + } + } + if (x0 <= x1) { + event_list *start = malloc(sizeof(event_list)); + if (!start) { + ImagingError_MemoryError(); + return -1; + } + event_list *end = malloc(sizeof(event_list)); + if (!end) { + free(start); + ImagingError_MemoryError(); + return -1; + } + start->x = x0; + start->type = 1; + start->next = end; + end->x = x1; + end->type = -1; + end->next = NULL; + *ret = start; + } else { + *ret = NULL; + } + return 0; + } + if (root->type == CT_OR || root->type == CT_AND) { + event_list *l1; + event_list *l2; + if (clip_tree_do_clip(root->l, x0, y, x1, &l1) < 0) { + return -1; + } + if (clip_tree_do_clip(root->r, x0, y, x1, &l2) < 0) { + while (l1) { + l2 = l1->next; + free(l1); + l1 = l2; + } + return -1; + } + *ret = NULL; + event_list *tail = NULL; + int32_t k1 = 0; + int32_t k2 = 0; + while (l1 != NULL || l2 != NULL) { + event_list *t; + if (l2 == NULL || + (l1 != NULL && + (l1->x < l2->x || (l1->x == l2->x && l1->type > l2->type)))) { + t = l1; + k1 += t->type; + assert(k1 >= 0); + l1 = l1->next; + } else { + t = l2; + k2 += t->type; + assert(k2 >= 0); + l2 = l2->next; + } + t->next = NULL; + if ((root->type == CT_OR && + ((t->type == 1 && (tail == NULL || tail->type == -1)) || + (t->type == -1 && k1 == 0 && k2 == 0))) || + (root->type == CT_AND && + ((t->type == 1 && (tail == NULL || tail->type == -1) && k1 > 0 && + k2 > 0) || + (t->type == -1 && tail != NULL && tail->type == 1 && + (k1 == 0 || k2 == 0))))) { + if (tail == NULL) { + *ret = t; + } else { + tail->next = t; + } + tail = t; + } else { + free(t); + } + } + return 0; + } + *ret = NULL; + return 0; +} + +// One more layer of processing on top of the regular ellipse. +// Uses the clipping tree. +// Used for producing ellipse derivatives such as arc, chord, pie, etc. +typedef struct { + ellipse_state st; + clip_node *root; + clip_node nodes[7]; + int32_t node_count; + event_list *head; + int32_t y; +} clip_ellipse_state; + +typedef void (*clip_ellipse_init)( + clip_ellipse_state *, int32_t, int32_t, int32_t, float, float); + +void +debug_clip_tree(clip_node *root, int space) { + if (root == NULL) { + return; + } + if (root->type == CT_CLIP) { + int t = space; + while (t--) { + fputc(' ', stderr); + } + fprintf(stderr, "clip %+fx%+fy%+f > 0\n", root->a, root->b, root->c); + } else { + debug_clip_tree(root->l, space + 2); + int t = space; + while (t--) { + fputc(' ', stderr); + } + fprintf(stderr, "%s\n", root->type == CT_AND ? "and" : "or"); + debug_clip_tree(root->r, space + 2); + } + if (space == 0) { + fputc('\n', stderr); + } +} + +// Resulting angles will satisfy 0 <= al < 360, al <= ar <= al + 360 +void +normalize_angles(float *al, float *ar) { + if (*ar - *al >= 360) { + *al = 0; + *ar = 360; + } else { + *al = fmod(*al < 0 ? 360 - (fmod(-*al, 360)) : *al, 360); + *ar = *al + fmod(*ar < *al ? 360 - fmod(*al - *ar, 360) : *ar - *al, 360); + } +} + +// An arc with caps orthogonal to the ellipse curve. +void +arc_init(clip_ellipse_state *s, int32_t a, int32_t b, int32_t w, float al, float ar) { + if (a < b) { + // transpose the coordinate system + arc_init(s, b, a, w, 90 - ar, 90 - al); + ellipse_init(&s->st, a, b, w); + clip_tree_transpose(s->root); + } else { + // a >= b, based on "wide" ellipse + ellipse_init(&s->st, a, b, w); + + s->head = NULL; + s->node_count = 0; + normalize_angles(&al, &ar); + + // building clipping tree, a lot of different cases + if (ar == al + 360) { + s->root = NULL; + } else { + clip_node *lc = s->nodes + s->node_count++; + clip_node *rc = s->nodes + s->node_count++; + lc->l = lc->r = rc->l = rc->r = NULL; + lc->type = rc->type = CT_CLIP; + lc->a = -a * sin(al * M_PI / 180.0); + lc->b = b * cos(al * M_PI / 180.0); + lc->c = (a * a - b * b) * sin(al * M_PI / 90.0) / 2.0; + rc->a = a * sin(ar * M_PI / 180.0); + rc->b = -b * cos(ar * M_PI / 180.0); + rc->c = (b * b - a * a) * sin(ar * M_PI / 90.0) / 2.0; + if (fmod(al, 180) == 0 || fmod(ar, 180) == 0) { + s->root = s->nodes + s->node_count++; + s->root->l = lc; + s->root->r = rc; + s->root->type = ar - al < 180 ? CT_AND : CT_OR; + } else if (((int)(al / 180) + (int)(ar / 180)) % 2 == 1) { + s->root = s->nodes + s->node_count++; + s->root->l = s->nodes + s->node_count++; + s->root->l->l = s->nodes + s->node_count++; + s->root->l->r = lc; + s->root->r = s->nodes + s->node_count++; + s->root->r->l = s->nodes + s->node_count++; + s->root->r->r = rc; + s->root->type = CT_OR; + s->root->l->type = CT_AND; + s->root->r->type = CT_AND; + s->root->l->l->type = CT_CLIP; + s->root->r->l->type = CT_CLIP; + s->root->l->l->l = s->root->l->l->r = NULL; + s->root->r->l->l = s->root->r->l->r = NULL; + s->root->l->l->a = s->root->l->l->c = 0; + s->root->r->l->a = s->root->r->l->c = 0; + s->root->l->l->b = (int)(al / 180) % 2 == 0 ? 1 : -1; + s->root->r->l->b = (int)(ar / 180) % 2 == 0 ? 1 : -1; + } else { + s->root = s->nodes + s->node_count++; + s->root->l = s->nodes + s->node_count++; + s->root->r = s->nodes + s->node_count++; + s->root->type = s->root->l->type = ar - al < 180 ? CT_AND : CT_OR; + s->root->l->l = lc; + s->root->l->r = rc; + s->root->r->type = CT_CLIP; + s->root->r->l = s->root->r->r = NULL; + s->root->r->a = s->root->r->c = 0; + s->root->r->b = ar < 180 || ar > 540 ? 1 : -1; + } + } + } +} + +// A chord line. +void +chord_line_init( + clip_ellipse_state *s, int32_t a, int32_t b, int32_t w, float al, float ar) { + ellipse_init(&s->st, a, b, a + b + 1); + + s->head = NULL; + s->node_count = 0; + + // line equation for chord + double xl = a * cos(al * M_PI / 180.0), xr = a * cos(ar * M_PI / 180.0); + double yl = b * sin(al * M_PI / 180.0), yr = b * sin(ar * M_PI / 180.0); + s->root = s->nodes + s->node_count++; + s->root->l = s->nodes + s->node_count++; + s->root->r = s->nodes + s->node_count++; + s->root->type = CT_AND; + s->root->l->type = s->root->r->type = CT_CLIP; + s->root->l->l = s->root->l->r = s->root->r->l = s->root->r->r = NULL; + s->root->l->a = yr - yl; + s->root->l->b = xl - xr; + s->root->l->c = -(s->root->l->a * xl + s->root->l->b * yl); + s->root->r->a = -s->root->l->a; + s->root->r->b = -s->root->l->b; + s->root->r->c = + 2 * w * sqrt(pow(s->root->l->a, 2.0) + pow(s->root->l->b, 2.0)) - s->root->l->c; +} + +// Pie side. +void +pie_side_init( + clip_ellipse_state *s, int32_t a, int32_t b, int32_t w, float al, float _) { + ellipse_init(&s->st, a, b, a + b + 1); + + s->head = NULL; + s->node_count = 0; + + double xl = a * cos(al * M_PI / 180.0); + double yl = b * sin(al * M_PI / 180.0); + double a1 = -yl; + double b1 = xl; + double c1 = w * sqrt(a1 * a1 + b1 * b1); + + s->root = s->nodes + s->node_count++; + s->root->type = CT_AND; + s->root->l = s->nodes + s->node_count++; + s->root->l->type = CT_AND; + + clip_node *cnode; + cnode = s->nodes + s->node_count++; + cnode->l = cnode->r = NULL; + cnode->type = CT_CLIP; + cnode->a = a1; + cnode->b = b1; + cnode->c = c1; + s->root->l->l = cnode; + cnode = s->nodes + s->node_count++; + cnode->l = cnode->r = NULL; + cnode->type = CT_CLIP; + cnode->a = -a1; + cnode->b = -b1; + cnode->c = c1; + s->root->l->r = cnode; + cnode = s->nodes + s->node_count++; + cnode->l = cnode->r = NULL; + cnode->type = CT_CLIP; + cnode->a = b1; + cnode->b = -a1; + cnode->c = 0; + s->root->r = cnode; +} + +// A chord. +void +chord_init(clip_ellipse_state *s, int32_t a, int32_t b, int32_t w, float al, float ar) { + ellipse_init(&s->st, a, b, w); + + s->head = NULL; + s->node_count = 0; + + // line equation for chord + double xl = a * cos(al * M_PI / 180.0), xr = a * cos(ar * M_PI / 180.0); + double yl = b * sin(al * M_PI / 180.0), yr = b * sin(ar * M_PI / 180.0); + s->root = s->nodes + s->node_count++; + s->root->l = s->root->r = NULL; + s->root->type = CT_CLIP; + s->root->a = yr - yl; + s->root->b = xl - xr; + s->root->c = -(s->root->a * xl + s->root->b * yl); +} + +// A pie. Can also be used to draw an arc with ugly sharp caps. +void +pie_init(clip_ellipse_state *s, int32_t a, int32_t b, int32_t w, float al, float ar) { + ellipse_init(&s->st, a, b, w); + + s->head = NULL; + s->node_count = 0; + + // line equations for pie sides + double xl = a * cos(al * M_PI / 180.0), xr = a * cos(ar * M_PI / 180.0); + double yl = b * sin(al * M_PI / 180.0), yr = b * sin(ar * M_PI / 180.0); + + clip_node *lc = s->nodes + s->node_count++; + clip_node *rc = s->nodes + s->node_count++; + lc->l = lc->r = rc->l = rc->r = NULL; + lc->type = rc->type = CT_CLIP; + lc->a = -yl; + lc->b = xl; + lc->c = 0; + rc->a = yr; + rc->b = -xr; + rc->c = 0; + + s->root = s->nodes + s->node_count++; + s->root->l = lc; + s->root->r = rc; + s->root->type = ar - al < 180 ? CT_AND : CT_OR; +} + +void +clip_ellipse_free(clip_ellipse_state *s) { + while (s->head != NULL) { + event_list *t = s->head; + s->head = s->head->next; + free(t); + } +} + +int8_t +clip_ellipse_next( + clip_ellipse_state *s, int32_t *ret_x0, int32_t *ret_y, int32_t *ret_x1) { + int32_t x0, y, x1; + while (s->head == NULL && ellipse_next(&s->st, &x0, &y, &x1) >= 0) { + if (clip_tree_do_clip(s->root, x0, y, x1, &s->head) < 0) { + return -2; + } + s->y = y; + } + if (s->head != NULL) { + *ret_y = s->y; + event_list *t = s->head; + s->head = s->head->next; + *ret_x0 = t->x; + free(t); + t = s->head; + assert(t != NULL); + s->head = s->head->next; + *ret_x1 = t->x; + free(t); + return 0; + } + return -1; +} + +static int +ellipseNew( + Imaging im, + int x0, + int y0, + int x1, + int y1, + const void *ink_, + int fill, + int width, + int op) { + DRAW *draw; + INT32 ink; + DRAWINIT(); + + int a = x1 - x0; + int b = y1 - y0; + if (a < 0 || b < 0) { + return 0; + } + if (fill) { + width = a + b; + } + + ellipse_state st; + ellipse_init(&st, a, b, width); + int32_t X0, Y, X1; + while (ellipse_next(&st, &X0, &Y, &X1) != -1) { + draw->hline(im, x0 + (X0 + a) / 2, y0 + (Y + b) / 2, x0 + (X1 + a) / 2, ink); + } + return 0; +} + +static int +clipEllipseNew( + Imaging im, + int x0, + int y0, + int x1, + int y1, + float start, + float end, + const void *ink_, + int width, + int op, + clip_ellipse_init init) { + DRAW *draw; + INT32 ink; + DRAWINIT(); + + int a = x1 - x0; + int b = y1 - y0; + if (a < 0 || b < 0) { + return 0; + } + + clip_ellipse_state st; + init(&st, a, b, width, start, end); + // debug_clip_tree(st.root, 0); + int32_t X0, Y, X1; + int next_code; + while ((next_code = clip_ellipse_next(&st, &X0, &Y, &X1)) >= 0) { + draw->hline(im, x0 + (X0 + a) / 2, y0 + (Y + b) / 2, x0 + (X1 + a) / 2, ink); + } + clip_ellipse_free(&st); + return next_code == -1 ? 0 : -1; +} +static int +arcNew( + Imaging im, + int x0, + int y0, + int x1, + int y1, + float start, + float end, + const void *ink_, + int width, + int op) { + return clipEllipseNew(im, x0, y0, x1, y1, start, end, ink_, width, op, arc_init); +} + +static int +chordNew( + Imaging im, + int x0, + int y0, + int x1, + int y1, + float start, + float end, + const void *ink_, + int width, + int op) { + return clipEllipseNew(im, x0, y0, x1, y1, start, end, ink_, width, op, chord_init); +} + +static int +chordLineNew( + Imaging im, + int x0, + int y0, + int x1, + int y1, + float start, + float end, + const void *ink_, + int width, + int op) { + return clipEllipseNew( + im, x0, y0, x1, y1, start, end, ink_, width, op, chord_line_init); +} + +static int +pieNew( + Imaging im, + int x0, + int y0, + int x1, + int y1, + float start, + float end, + const void *ink_, + int width, + int op) { + return clipEllipseNew(im, x0, y0, x1, y1, start, end, ink_, width, op, pie_init); +} + +static int +pieSideNew( + Imaging im, + int x0, + int y0, + int x1, + int y1, + float start, + const void *ink_, + int width, + int op) { + return clipEllipseNew(im, x0, y0, x1, y1, start, 0, ink_, width, op, pie_side_init); } int -ImagingDrawChord(Imaging im, int x0, int y0, int x1, int y1, - float start, float end, const void* ink, int fill, - int width, int op) -{ - return ellipse(im, x0, y0, x1, y1, start, end, ink, fill, width, CHORD, op); +ImagingDrawEllipse( + Imaging im, + int x0, + int y0, + int x1, + int y1, + const void *ink, + int fill, + int width, + int op) { + return ellipseNew(im, x0, y0, x1, y1, ink, fill, width, op); } int -ImagingDrawEllipse(Imaging im, int x0, int y0, int x1, int y1, - const void* ink, int fill, int width, int op) -{ - return ellipse(im, x0, y0, x1, y1, 0, 360, ink, fill, width, CHORD, op); +ImagingDrawArc( + Imaging im, + int x0, + int y0, + int x1, + int y1, + float start, + float end, + const void *ink, + int width, + int op) { + normalize_angles(&start, &end); + if (start + 360 == end) { + return ImagingDrawEllipse(im, x0, y0, x1, y1, ink, 0, width, op); + } + if (start == end) { + return 0; + } + return arcNew(im, x0, y0, x1, y1, start, end, ink, width, op); } int -ImagingDrawPieslice(Imaging im, int x0, int y0, int x1, int y1, - float start, float end, const void* ink, int fill, - int width, int op) -{ - return ellipse(im, x0, y0, x1, y1, start, end, ink, fill, width, PIESLICE, op); +ImagingDrawChord( + Imaging im, + int x0, + int y0, + int x1, + int y1, + float start, + float end, + const void *ink, + int fill, + int width, + int op) { + normalize_angles(&start, &end); + if (start + 360 == end) { + return ImagingDrawEllipse(im, x0, y0, x1, y1, ink, fill, width, op); + } + if (start == end) { + return 0; + } + if (fill) { + return chordNew(im, x0, y0, x1, y1, start, end, ink, x1 - x0 + y1 - y0 + 1, op); + } else { + if (chordLineNew(im, x0, y0, x1, y1, start, end, ink, width, op)) { + return -1; + } + return chordNew(im, x0, y0, x1, y1, start, end, ink, width, op); + } +} + +int +ImagingDrawPieslice( + Imaging im, + int x0, + int y0, + int x1, + int y1, + float start, + float end, + const void *ink, + int fill, + int width, + int op) { + normalize_angles(&start, &end); + if (start + 360 == end) { + return ellipseNew(im, x0, y0, x1, y1, ink, fill, width, op); + } + if (start == end) { + return 0; + } + if (fill) { + return pieNew(im, x0, y0, x1, y1, start, end, ink, x1 + y1 - x0 - y0, op); + } else { + if (pieSideNew(im, x0, y0, x1, y1, start, ink, width, op)) { + return -1; + } + if (pieSideNew(im, x0, y0, x1, y1, end, ink, width, op)) { + return -1; + } + int xc = lround((x0 + x1 - width) / 2.0), yc = lround((y0 + y1 - width) / 2.0); + ellipseNew(im, xc, yc, xc + width - 1, yc + width - 1, ink, 1, 0, op); + return pieNew(im, x0, y0, x1, y1, start, end, ink, width, op); + } } /* -------------------------------------------------------------------- */ @@ -958,7 +1635,6 @@ ImagingDrawPieslice(Imaging im, int x0, int y0, int x1, int y1, itself */ struct ImagingOutlineInstance { - float x0, y0; float x, y; @@ -967,18 +1643,16 @@ struct ImagingOutlineInstance { Edge *edges; int size; - }; - ImagingOutline -ImagingOutlineNew(void) -{ +ImagingOutlineNew(void) { ImagingOutline outline; outline = calloc(1, sizeof(struct ImagingOutlineInstance)); - if (!outline) - return (ImagingOutline) ImagingError_MemoryError(); + if (!outline) { + return (ImagingOutline)ImagingError_MemoryError(); + } outline->edges = NULL; outline->count = outline->size = 0; @@ -989,22 +1663,21 @@ ImagingOutlineNew(void) } void -ImagingOutlineDelete(ImagingOutline outline) -{ - if (!outline) +ImagingOutlineDelete(ImagingOutline outline) { + if (!outline) { return; + } - if (outline->edges) + if (outline->edges) { free(outline->edges); + } free(outline); } - -static Edge* -allocate(ImagingOutline outline, int extra) -{ - Edge* e; +static Edge * +allocate(ImagingOutline outline, int extra) { + Edge *e; if (outline->count + extra > outline->size) { /* expand outline buffer */ @@ -1013,14 +1686,15 @@ allocate(ImagingOutline outline, int extra) /* malloc check ok, uses calloc for overflow */ e = calloc(outline->size, sizeof(Edge)); } else { - if (outline->size > INT_MAX / sizeof(Edge)) { + if (outline->size > INT_MAX / (int)sizeof(Edge)) { return NULL; } /* malloc check ok, overflow checked above */ e = realloc(outline->edges, outline->size * sizeof(Edge)); } - if (!e) + if (!e) { return NULL; + } outline->edges = e; } @@ -1032,8 +1706,7 @@ allocate(ImagingOutline outline, int extra) } int -ImagingOutlineMove(ImagingOutline outline, float x0, float y0) -{ +ImagingOutlineMove(ImagingOutline outline, float x0, float y0) { outline->x = outline->x0 = x0; outline->y = outline->y0 = y0; @@ -1041,15 +1714,15 @@ ImagingOutlineMove(ImagingOutline outline, float x0, float y0) } int -ImagingOutlineLine(ImagingOutline outline, float x1, float y1) -{ - Edge* e; +ImagingOutlineLine(ImagingOutline outline, float x1, float y1) { + Edge *e; e = allocate(outline, 1); - if (!e) + if (!e) { return -1; /* out of memory */ + } - add_edge(e, (int) outline->x, (int) outline->y, (int) x1, (int) y1); + add_edge(e, (int)outline->x, (int)outline->y, (int)x1, (int)y1); outline->x = x1; outline->y = y1; @@ -1058,18 +1731,24 @@ ImagingOutlineLine(ImagingOutline outline, float x1, float y1) } int -ImagingOutlineCurve(ImagingOutline outline, float x1, float y1, - float x2, float y2, float x3, float y3) -{ - Edge* e; +ImagingOutlineCurve( + ImagingOutline outline, + float x1, + float y1, + float x2, + float y2, + float x3, + float y3) { + Edge *e; int i; float xo, yo; #define STEPS 32 e = allocate(outline, STEPS); - if (!e) + if (!e) { return -1; /* out of memory */ + } xo = outline->x; yo = outline->y; @@ -1077,22 +1756,20 @@ ImagingOutlineCurve(ImagingOutline outline, float x1, float y1, /* flatten the bezier segment */ for (i = 1; i <= STEPS; i++) { - - float t = ((float) i) / STEPS; - float t2 = t*t; - float t3 = t2*t; + float t = ((float)i) / STEPS; + float t2 = t * t; + float t3 = t2 * t; float u = 1.0F - t; - float u2 = u*u; - float u3 = u2*u; + float u2 = u * u; + float u3 = u2 * u; - float x = outline->x*u3 + 3*(x1*t*u2 + x2*t2*u) + x3*t3 + 0.5; - float y = outline->y*u3 + 3*(y1*t*u2 + y2*t2*u) + y3*t3 + 0.5; + float x = outline->x * u3 + 3 * (x1 * t * u2 + x2 * t2 * u) + x3 * t3 + 0.5; + float y = outline->y * u3 + 3 * (y1 * t * u2 + y2 * t2 * u) + y3 * t3 + 0.5; - add_edge(e++, xo, yo, (int) x, (int) y); + add_edge(e++, xo, yo, (int)x, (int)y); xo = x, yo = y; - } outline->x = xo; @@ -1102,24 +1779,27 @@ ImagingOutlineCurve(ImagingOutline outline, float x1, float y1, } int -ImagingOutlineClose(ImagingOutline outline) -{ - if (outline->x == outline->x0 && outline->y == outline->y0) +ImagingOutlineClose(ImagingOutline outline) { + if (outline->x == outline->x0 && outline->y == outline->y0) { return 0; + } return ImagingOutlineLine(outline, outline->x0, outline->y0); } int -ImagingOutlineTransform(ImagingOutline outline, double a[6]) -{ +ImagingOutlineTransform(ImagingOutline outline, double a[6]) { Edge *eIn; Edge *eOut; int i, n; int x0, y0, x1, y1; int X0, Y0, X1, Y1; - double a0 = a[0]; double a1 = a[1]; double a2 = a[2]; - double a3 = a[3]; double a4 = a[4]; double a5 = a[5]; + double a0 = a[0]; + double a1 = a[1]; + double a2 = a[2]; + double a3 = a[3]; + double a4 = a[4]; + double a5 = a[5]; eIn = outline->edges; n = outline->count; @@ -1137,34 +1817,34 @@ ImagingOutlineTransform(ImagingOutline outline, double a[6]) } for (i = 0; i < n; i++) { - x0 = eIn->x0; y0 = eIn->y0; /* FIXME: ouch! */ - if (eIn->x0 == eIn->xmin) + if (eIn->x0 == eIn->xmin) { x1 = eIn->xmax; - else + } else { x1 = eIn->xmin; - if (eIn->y0 == eIn->ymin) + } + if (eIn->y0 == eIn->ymin) { y1 = eIn->ymax; - else + } else { y1 = eIn->ymin; + } /* full moon tonight! if this doesn't work, you may need to upgrade your compiler (make sure you have the right service pack) */ - X0 = (int) (a0*x0 + a1*y0 + a2); - Y0 = (int) (a3*x0 + a4*y0 + a5); - X1 = (int) (a0*x1 + a1*y1 + a2); - Y1 = (int) (a3*x1 + a4*y1 + a5); + X0 = (int)(a0 * x0 + a1 * y0 + a2); + Y0 = (int)(a3 * x0 + a4 * y0 + a5); + X1 = (int)(a0 * x1 + a1 * y1 + a2); + Y1 = (int)(a3 * x1 + a4 * y1 + a5); add_edge(eOut, X0, Y0, X1, Y1); eIn++; eOut++; - } free(eIn); @@ -1173,10 +1853,9 @@ ImagingOutlineTransform(ImagingOutline outline, double a[6]) } int -ImagingDrawOutline(Imaging im, ImagingOutline outline, const void* ink_, - int fill, int op) -{ - DRAW* draw; +ImagingDrawOutline( + Imaging im, ImagingOutline outline, const void *ink_, int fill, int op) { + DRAW *draw; INT32 ink; DRAWINIT(); diff --git a/src/libImaging/Effects.c b/src/libImaging/Effects.c index 7b4ff0b43..93e7af0bc 100644 --- a/src/libImaging/Effects.c +++ b/src/libImaging/Effects.c @@ -15,14 +15,12 @@ * See the README file for information on usage and redistribution. */ - #include "Imaging.h" #include Imaging -ImagingEffectMandelbrot(int xsize, int ysize, double extent[4], int quality) -{ +ImagingEffectMandelbrot(int xsize, int ysize, double extent[4], int quality) { /* Generate a Mandelbrot set covering the given extent */ Imaging im; @@ -32,33 +30,35 @@ ImagingEffectMandelbrot(int xsize, int ysize, double extent[4], int quality) double dr, di; /* Check arguments */ - width = extent[2] - extent[0]; + width = extent[2] - extent[0]; height = extent[3] - extent[1]; - if (width < 0.0 || height < 0.0 || quality < 2) - return (Imaging) ImagingError_ValueError(NULL); + if (width < 0.0 || height < 0.0 || quality < 2) { + return (Imaging)ImagingError_ValueError(NULL); + } im = ImagingNewDirty("L", xsize, ysize); - if (!im) + if (!im) { return NULL; + } - dr = width/(xsize-1); - di = height/(ysize-1); + dr = width / (xsize - 1); + di = height / (ysize - 1); radius = 100.0; for (y = 0; y < ysize; y++) { - UINT8* buf = im->image8[y]; + UINT8 *buf = im->image8[y]; for (x = 0; x < xsize; x++) { x1 = y1 = xi2 = yi2 = 0.0; - cr = x*dr + extent[0]; - ci = y*di + extent[1]; + cr = x * dr + extent[0]; + ci = y * di + extent[1]; for (k = 1;; k++) { - y1 = 2*x1*y1 + ci; + y1 = 2 * x1 * y1 + ci; x1 = xi2 - yi2 + cr; - xi2 = x1*x1; - yi2 = y1*y1; + xi2 = x1 * x1; + yi2 = y1 * y1; if ((xi2 + yi2) > radius) { - buf[x] = k*255/quality; + buf[x] = k * 255 / quality; break; } if (k > quality) { @@ -72,8 +72,7 @@ ImagingEffectMandelbrot(int xsize, int ysize, double extent[4], int quality) } Imaging -ImagingEffectNoise(int xsize, int ysize, float sigma) -{ +ImagingEffectNoise(int xsize, int ysize, float sigma) { /* Generate Gaussian noise centered around 128 */ Imaging imOut; @@ -82,14 +81,15 @@ ImagingEffectNoise(int xsize, int ysize, float sigma) double this, next; imOut = ImagingNewDirty("L", xsize, ysize); - if (!imOut) + if (!imOut) { return NULL; + } next = 0.0; nextok = 0; for (y = 0; y < imOut->ysize; y++) { - UINT8* out = imOut->image8[y]; + UINT8 *out = imOut->image8[y]; for (x = 0; x < imOut->xsize; x++) { if (nextok) { this = next; @@ -98,11 +98,11 @@ ImagingEffectNoise(int xsize, int ysize, float sigma) /* after numerical recipes */ double v1, v2, radius, factor; do { - v1 = rand()*(2.0/RAND_MAX) - 1.0; - v2 = rand()*(2.0/RAND_MAX) - 1.0; - radius= v1*v1 + v2*v2; + v1 = rand() * (2.0 / RAND_MAX) - 1.0; + v2 = rand() * (2.0 / RAND_MAX) - 1.0; + radius = v1 * v1 + v2 * v2; } while (radius >= 1.0); - factor = sqrt(-2.0*log(radius)/radius); + factor = sqrt(-2.0 * log(radius) / radius); this = factor * v1; next = factor * v2; } @@ -114,8 +114,7 @@ ImagingEffectNoise(int xsize, int ysize, float sigma) } Imaging -ImagingEffectSpread(Imaging imIn, int distance) -{ +ImagingEffectSpread(Imaging imIn, int distance) { /* Randomly spread pixels in an image */ Imaging imOut; @@ -123,20 +122,31 @@ ImagingEffectSpread(Imaging imIn, int distance) imOut = ImagingNewDirty(imIn->mode, imIn->xsize, imIn->ysize); - if (!imOut) + if (!imOut) { return NULL; + } -#define SPREAD(type, image)\ - for (y = 0; y < imOut->ysize; y++)\ - for (x = 0; x < imOut->xsize; x++) {\ - int xx = x + (rand() % distance) - distance/2;\ - int yy = y + (rand() % distance) - distance/2;\ - if (xx >= 0 && xx < imIn->xsize && yy >= 0 && yy < imIn->ysize) {\ - imOut->image[yy][xx] = imIn->image[y][x];\ - imOut->image[y][x] = imIn->image[yy][xx];\ - } else\ - imOut->image[y][x] = imIn->image[y][x];\ - } +#define SPREAD(type, image) \ + if (distance == 0) { \ + for (y = 0; y < imOut->ysize; y++) { \ + for (x = 0; x < imOut->xsize; x++) { \ + imOut->image[y][x] = imIn->image[y][x]; \ + } \ + } \ + } else { \ + for (y = 0; y < imOut->ysize; y++) { \ + for (x = 0; x < imOut->xsize; x++) { \ + int xx = x + (rand() % distance) - distance / 2; \ + int yy = y + (rand() % distance) - distance / 2; \ + if (xx >= 0 && xx < imIn->xsize && yy >= 0 && yy < imIn->ysize) { \ + imOut->image[yy][xx] = imIn->image[y][x]; \ + imOut->image[y][x] = imIn->image[yy][xx]; \ + } else { \ + imOut->image[y][x] = imIn->image[y][x]; \ + } \ + } \ + } \ + } if (imIn->image8) { SPREAD(UINT8, image8); diff --git a/src/libImaging/EpsEncode.c b/src/libImaging/EpsEncode.c index 45fab0a6e..3f2cb33b2 100644 --- a/src/libImaging/EpsEncode.c +++ b/src/libImaging/EpsEncode.c @@ -5,11 +5,11 @@ * encoder for EPS hex data * * history: - * 96-04-19 fl created - * 96-06-27 fl don't drop last block of encoded data + * 96-04-19 fl created + * 96-06-27 fl don't drop last block of encoded data * * notes: - * FIXME: rename to HexEncode.c ?? + * FIXME: rename to HexEncode.c ?? * * Copyright (c) Fredrik Lundh 1996. * Copyright (c) Secret Labs AB 1997. @@ -17,64 +17,61 @@ * See the README file for information on usage and redistribution. */ - #include "Imaging.h" - int -ImagingEpsEncode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) -{ - enum { HEXBYTE=1, NEWLINE }; +ImagingEpsEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) { + enum { HEXBYTE = 1, NEWLINE }; const char *hex = "0123456789abcdef"; - UINT8* ptr = buf; - UINT8* in, i; + UINT8 *ptr = buf; + UINT8 *in, i; if (!state->state) { - state->state = HEXBYTE; - state->xsize *= im->pixelsize; /* Hack! */ + state->state = HEXBYTE; + state->xsize *= im->pixelsize; /* Hack! */ } - in = (UINT8*) im->image[state->y]; + in = (UINT8 *)im->image[state->y]; for (;;) { + if (state->state == NEWLINE) { + if (bytes < 1) { + break; + } + *ptr++ = '\n'; + bytes--; + state->state = HEXBYTE; + } - if (state->state == NEWLINE) { - if (bytes < 1) - break; - *ptr++ = '\n'; - bytes--; - state->state = HEXBYTE; - } + if (bytes < 2) { + break; + } - if (bytes < 2) - break; + i = in[state->x++]; + *ptr++ = hex[(i >> 4) & 15]; + *ptr++ = hex[i & 15]; + bytes -= 2; - i = in[state->x++]; - *ptr++ = hex[(i>>4)&15]; - *ptr++ = hex[i&15]; - bytes -= 2; + /* Skip junk bytes */ + if (im->bands == 3 && (state->x & 3) == 3) { + state->x++; + } - /* Skip junk bytes */ - if (im->bands == 3 && (state->x & 3) == 3) - state->x++; - - if (++state->count >= 79/2) { - state->state = NEWLINE; - state->count = 0; - } - - if (state->x >= state->xsize) { - state->x = 0; - if (++state->y >= state->ysize) { - state->errcode = IMAGING_CODEC_END; - break; - } - in = (UINT8*) im->image[state->y]; - } + if (++state->count >= 79 / 2) { + state->state = NEWLINE; + state->count = 0; + } + if (state->x >= state->xsize) { + state->x = 0; + if (++state->y >= state->ysize) { + state->errcode = IMAGING_CODEC_END; + break; + } + in = (UINT8 *)im->image[state->y]; + } } return ptr - buf; - } diff --git a/src/libImaging/Except.c b/src/libImaging/Except.c index 515d85d1f..f42ff9aec 100644 --- a/src/libImaging/Except.c +++ b/src/libImaging/Except.c @@ -19,65 +19,54 @@ * See the README file for information on usage and redistribution. */ - #include "Imaging.h" - /* exception state */ void * -ImagingError_IOError(void) -{ +ImagingError_OSError(void) { fprintf(stderr, "*** exception: file access error\n"); return NULL; } void * -ImagingError_MemoryError(void) -{ +ImagingError_MemoryError(void) { fprintf(stderr, "*** exception: out of memory\n"); return NULL; } void * -ImagingError_ModeError(void) -{ +ImagingError_ModeError(void) { return ImagingError_ValueError("bad image mode"); - return NULL; } void * -ImagingError_Mismatch(void) -{ +ImagingError_Mismatch(void) { return ImagingError_ValueError("images don't match"); - return NULL; } void * -ImagingError_ValueError(const char *message) -{ - if (!message) - message = "exception: bad argument to function"; +ImagingError_ValueError(const char *message) { + if (!message) { + message = "exception: bad argument to function"; + } fprintf(stderr, "*** %s\n", message); return NULL; } void -ImagingError_Clear(void) -{ +ImagingError_Clear(void) { /* nop */; } /* thread state */ void -ImagingSectionEnter(ImagingSectionCookie* cookie) -{ +ImagingSectionEnter(ImagingSectionCookie *cookie) { /* pass */ } void -ImagingSectionLeave(ImagingSectionCookie* cookie) -{ +ImagingSectionLeave(ImagingSectionCookie *cookie) { /* pass */ } diff --git a/src/libImaging/File.c b/src/libImaging/File.c index 6f014c1f8..76d0abccc 100644 --- a/src/libImaging/File.c +++ b/src/libImaging/File.c @@ -15,51 +15,46 @@ * See the README file for information on usage and redistribution. */ - #include "Imaging.h" #include - int -ImagingSaveRaw(Imaging im, FILE* fp) -{ +ImagingSaveRaw(Imaging im, FILE *fp) { int x, y, i; if (strcmp(im->mode, "1") == 0 || strcmp(im->mode, "L") == 0) { - /* @PIL227: FIXME: for mode "1", map != 0 to 255 */ /* PGM "L" */ - for (y = 0; y < im->ysize; y++) + for (y = 0; y < im->ysize; y++) { fwrite(im->image[y], 1, im->xsize, fp); + } } else { - /* PPM "RGB" or other internal format */ - for (y = 0; y < im->ysize; y++) - for (x = i = 0; x < im->xsize; x++, i += im->pixelsize) - fwrite(im->image[y]+i, 1, im->bands, fp); - + for (y = 0; y < im->ysize; y++) { + for (x = i = 0; x < im->xsize; x++, i += im->pixelsize) { + fwrite(im->image[y] + i, 1, im->bands, fp); + } + } } return 1; } - int -ImagingSavePPM(Imaging im, const char* outfile) -{ - FILE* fp; +ImagingSavePPM(Imaging im, const char *outfile) { + FILE *fp; if (!im) { - (void) ImagingError_ValueError(NULL); + (void)ImagingError_ValueError(NULL); return 0; } fp = fopen(outfile, "wb"); if (!fp) { - (void) ImagingError_IOError(); + (void)ImagingError_OSError(); return 0; } @@ -71,7 +66,7 @@ ImagingSavePPM(Imaging im, const char* outfile) fprintf(fp, "P6\n%d %d\n255\n", im->xsize, im->ysize); } else { fclose(fp); - (void) ImagingError_ModeError(); + (void)ImagingError_ModeError(); return 0; } @@ -81,4 +76,3 @@ ImagingSavePPM(Imaging im, const char* outfile) return 1; } - diff --git a/src/libImaging/Fill.c b/src/libImaging/Fill.c index d641a5996..e4e4b4344 100644 --- a/src/libImaging/Fill.c +++ b/src/libImaging/Fill.c @@ -5,9 +5,9 @@ * fill image with constant pixel value * * history: - * 95-11-26 fl moved from Imaging.c - * 96-05-17 fl added radial fill, renamed wedge to linear - * 98-06-23 fl changed ImageFill signature + * 95-11-26 fl moved from Imaging.c + * 96-05-17 fl added radial fill, renamed wedge to linear + * 98-06-23 fl changed ImageFill signature * * Copyright (c) Secret Labs AB 1997-98. All rights reserved. * Copyright (c) Fredrik Lundh 1995-96. @@ -15,14 +15,12 @@ * See the README file for information on usage and redistribution. */ - #include "Imaging.h" #include "math.h" Imaging -ImagingFill(Imaging im, const void* colour) -{ +ImagingFill(Imaging im, const void *colour) { int x, y; ImagingSectionCookie cookie; @@ -30,27 +28,33 @@ ImagingFill(Imaging im, const void* colour) /* use generic API */ ImagingAccess access = ImagingAccessNew(im); if (access) { - for (y = 0; y < im->ysize; y++) - for (x = 0; x < im->xsize; x++) + for (y = 0; y < im->ysize; y++) { + for (x = 0; x < im->xsize; x++) { access->put_pixel(im, x, y, colour); + } + } ImagingAccessDelete(im, access); } else { /* wipe the image */ - for (y = 0; y < im->ysize; y++) + for (y = 0; y < im->ysize; y++) { memset(im->image[y], 0, im->linesize); + } } } else { INT32 c = 0L; ImagingSectionEnter(&cookie); memcpy(&c, colour, im->pixelsize); if (im->image32 && c != 0L) { - for (y = 0; y < im->ysize; y++) - for (x = 0; x < im->xsize; x++) + for (y = 0; y < im->ysize; y++) { + for (x = 0; x < im->xsize; x++) { im->image32[y][x] = c; + } + } } else { - unsigned char cc = (unsigned char) *(UINT8*) colour; - for (y = 0; y < im->ysize; y++) + unsigned char cc = (unsigned char)*(UINT8 *)colour; + for (y = 0; y < im->ysize; y++) { memset(im->image[y], cc, im->linesize); + } } ImagingSectionLeave(&cookie); } @@ -59,13 +63,12 @@ ImagingFill(Imaging im, const void* colour) } Imaging -ImagingFillLinearGradient(const char *mode) -{ +ImagingFillLinearGradient(const char *mode) { Imaging im; int y; if (strlen(mode) != 1) { - return (Imaging) ImagingError_ModeError(); + return (Imaging)ImagingError_ModeError(); } im = ImagingNewDirty(mode, 256, 256); @@ -74,21 +77,20 @@ ImagingFillLinearGradient(const char *mode) } for (y = 0; y < 256; y++) { - memset(im->image8[y], (unsigned char) y, 256); + memset(im->image8[y], (unsigned char)y, 256); } return im; } Imaging -ImagingFillRadialGradient(const char *mode) -{ +ImagingFillRadialGradient(const char *mode) { Imaging im; int x, y; int d; if (strlen(mode) != 1) { - return (Imaging) ImagingError_ModeError(); + return (Imaging)ImagingError_ModeError(); } im = ImagingNewDirty(mode, 256, 256); @@ -98,7 +100,8 @@ ImagingFillRadialGradient(const char *mode) for (y = 0; y < 256; y++) { for (x = 0; x < 256; x++) { - d = (int) sqrt((double) ((x-128)*(x-128) + (y-128)*(y-128)) * 2.0); + d = (int)sqrt( + (double)((x - 128) * (x - 128) + (y - 128) * (y - 128)) * 2.0); if (d >= 255) { im->image8[y][x] = 255; } else { diff --git a/src/libImaging/Filter.c b/src/libImaging/Filter.c index b033abf66..fab3b4948 100644 --- a/src/libImaging/Filter.c +++ b/src/libImaging/Filter.c @@ -26,48 +26,58 @@ #include "Imaging.h" - -static inline UINT8 clip8(float in) -{ - if (in <= 0.0) +static inline UINT8 +clip8(float in) { + if (in <= 0.0) { return 0; - if (in >= 255.0) + } + if (in >= 255.0) { return 255; - return (UINT8) in; + } + return (UINT8)in; } Imaging -ImagingExpand(Imaging imIn, int xmargin, int ymargin, int mode) -{ +ImagingExpand(Imaging imIn, int xmargin, int ymargin, int mode) { Imaging imOut; int x, y; ImagingSectionCookie cookie; - if (xmargin < 0 && ymargin < 0) - return (Imaging) ImagingError_ValueError("bad kernel size"); - - imOut = ImagingNewDirty( - imIn->mode, imIn->xsize+2*xmargin, imIn->ysize+2*ymargin); - if (!imOut) - return NULL; - -#define EXPAND_LINE(type, image, yin, yout) {\ - for (x = 0; x < xmargin; x++)\ - imOut->image[yout][x] = imIn->image[yin][0];\ - for (x = 0; x < imIn->xsize; x++)\ - imOut->image[yout][x+xmargin] = imIn->image[yin][x];\ - for (x = 0; x < xmargin; x++)\ - imOut->image[yout][xmargin+imIn->xsize+x] =\ - imIn->image[yin][imIn->xsize-1];\ + if (xmargin < 0 && ymargin < 0) { + return (Imaging)ImagingError_ValueError("bad kernel size"); } -#define EXPAND(type, image) {\ - for (y = 0; y < ymargin; y++)\ - EXPAND_LINE(type, image, 0, y);\ - for (y = 0; y < imIn->ysize; y++)\ - EXPAND_LINE(type, image, y, y+ymargin);\ - for (y = 0; y < ymargin; y++)\ - EXPAND_LINE(type, image, imIn->ysize-1, ymargin+imIn->ysize+y);\ + imOut = ImagingNewDirty( + imIn->mode, imIn->xsize + 2 * xmargin, imIn->ysize + 2 * ymargin); + if (!imOut) { + return NULL; + } + +#define EXPAND_LINE(type, image, yin, yout) \ + { \ + for (x = 0; x < xmargin; x++) { \ + imOut->image[yout][x] = imIn->image[yin][0]; \ + } \ + for (x = 0; x < imIn->xsize; x++) { \ + imOut->image[yout][x + xmargin] = imIn->image[yin][x]; \ + } \ + for (x = 0; x < xmargin; x++) { \ + imOut->image[yout][xmargin + imIn->xsize + x] = \ + imIn->image[yin][imIn->xsize - 1]; \ + } \ + } + +#define EXPAND(type, image) \ + { \ + for (y = 0; y < ymargin; y++) { \ + EXPAND_LINE(type, image, 0, y); \ + } \ + for (y = 0; y < imIn->ysize; y++) { \ + EXPAND_LINE(type, image, y, y + ymargin); \ + } \ + for (y = 0; y < ymargin; y++) { \ + EXPAND_LINE(type, image, imIn->ysize - 1, ymargin + imIn->ysize + y); \ + } \ } ImagingSectionEnter(&cookie); @@ -83,15 +93,11 @@ ImagingExpand(Imaging imIn, int xmargin, int ymargin, int mode) return imOut; } - void -ImagingFilter3x3(Imaging imOut, Imaging im, const float* kernel, - float offset) -{ -#define KERNEL1x3(in0, x, kernel, d) ( \ - _i2f((UINT8) in0[x-d]) * (kernel)[0] + \ - _i2f((UINT8) in0[x]) * (kernel)[1] + \ - _i2f((UINT8) in0[x+d]) * (kernel)[2]) +ImagingFilter3x3(Imaging imOut, Imaging im, const float *kernel, float offset) { +#define KERNEL1x3(in0, x, kernel, d) \ + (_i2f((UINT8)in0[x - d]) * (kernel)[0] + _i2f((UINT8)in0[x]) * (kernel)[1] + \ + _i2f((UINT8)in0[x + d]) * (kernel)[2]) int x = 0, y = 0; @@ -99,86 +105,84 @@ ImagingFilter3x3(Imaging imOut, Imaging im, const float* kernel, if (im->bands == 1) { // Add one time for rounding offset += 0.5; - for (y = 1; y < im->ysize-1; y++) { - UINT8* in_1 = (UINT8*) im->image[y-1]; - UINT8* in0 = (UINT8*) im->image[y]; - UINT8* in1 = (UINT8*) im->image[y+1]; - UINT8* out = (UINT8*) imOut->image[y]; + for (y = 1; y < im->ysize - 1; y++) { + UINT8 *in_1 = (UINT8 *)im->image[y - 1]; + UINT8 *in0 = (UINT8 *)im->image[y]; + UINT8 *in1 = (UINT8 *)im->image[y + 1]; + UINT8 *out = (UINT8 *)imOut->image[y]; out[0] = in0[0]; - for (x = 1; x < im->xsize-1; x++) { + for (x = 1; x < im->xsize - 1; x++) { float ss = offset; ss += KERNEL1x3(in1, x, &kernel[0], 1); ss += KERNEL1x3(in0, x, &kernel[3], 1); ss += KERNEL1x3(in_1, x, &kernel[6], 1); out[x] = clip8(ss); - } + } out[x] = in0[x]; } } else { // Add one time for rounding offset += 0.5; - for (y = 1; y < im->ysize-1; y++) { - UINT8* in_1 = (UINT8*) im->image[y-1]; - UINT8* in0 = (UINT8*) im->image[y]; - UINT8* in1 = (UINT8*) im->image[y+1]; - UINT8* out = (UINT8*) imOut->image[y]; + for (y = 1; y < im->ysize - 1; y++) { + UINT8 *in_1 = (UINT8 *)im->image[y - 1]; + UINT8 *in0 = (UINT8 *)im->image[y]; + UINT8 *in1 = (UINT8 *)im->image[y + 1]; + UINT8 *out = (UINT8 *)imOut->image[y]; memcpy(out, in0, sizeof(UINT32)); if (im->bands == 2) { - for (x = 1; x < im->xsize-1; x++) { + for (x = 1; x < im->xsize - 1; x++) { float ss0 = offset; float ss3 = offset; UINT32 v; - ss0 += KERNEL1x3(in1, x*4+0, &kernel[0], 4); - ss3 += KERNEL1x3(in1, x*4+3, &kernel[0], 4); - ss0 += KERNEL1x3(in0, x*4+0, &kernel[3], 4); - ss3 += KERNEL1x3(in0, x*4+3, &kernel[3], 4); - ss0 += KERNEL1x3(in_1, x*4+0, &kernel[6], 4); - ss3 += KERNEL1x3(in_1, x*4+3, &kernel[6], 4); + ss0 += KERNEL1x3(in1, x * 4 + 0, &kernel[0], 4); + ss3 += KERNEL1x3(in1, x * 4 + 3, &kernel[0], 4); + ss0 += KERNEL1x3(in0, x * 4 + 0, &kernel[3], 4); + ss3 += KERNEL1x3(in0, x * 4 + 3, &kernel[3], 4); + ss0 += KERNEL1x3(in_1, x * 4 + 0, &kernel[6], 4); + ss3 += KERNEL1x3(in_1, x * 4 + 3, &kernel[6], 4); v = MAKE_UINT32(clip8(ss0), 0, 0, clip8(ss3)); memcpy(out + x * sizeof(v), &v, sizeof(v)); } } else if (im->bands == 3) { - for (x = 1; x < im->xsize-1; x++) { + for (x = 1; x < im->xsize - 1; x++) { float ss0 = offset; float ss1 = offset; float ss2 = offset; UINT32 v; - ss0 += KERNEL1x3(in1, x*4+0, &kernel[0], 4); - ss1 += KERNEL1x3(in1, x*4+1, &kernel[0], 4); - ss2 += KERNEL1x3(in1, x*4+2, &kernel[0], 4); - ss0 += KERNEL1x3(in0, x*4+0, &kernel[3], 4); - ss1 += KERNEL1x3(in0, x*4+1, &kernel[3], 4); - ss2 += KERNEL1x3(in0, x*4+2, &kernel[3], 4); - ss0 += KERNEL1x3(in_1, x*4+0, &kernel[6], 4); - ss1 += KERNEL1x3(in_1, x*4+1, &kernel[6], 4); - ss2 += KERNEL1x3(in_1, x*4+2, &kernel[6], 4); - v = MAKE_UINT32( - clip8(ss0), clip8(ss1), clip8(ss2), 0); + ss0 += KERNEL1x3(in1, x * 4 + 0, &kernel[0], 4); + ss1 += KERNEL1x3(in1, x * 4 + 1, &kernel[0], 4); + ss2 += KERNEL1x3(in1, x * 4 + 2, &kernel[0], 4); + ss0 += KERNEL1x3(in0, x * 4 + 0, &kernel[3], 4); + ss1 += KERNEL1x3(in0, x * 4 + 1, &kernel[3], 4); + ss2 += KERNEL1x3(in0, x * 4 + 2, &kernel[3], 4); + ss0 += KERNEL1x3(in_1, x * 4 + 0, &kernel[6], 4); + ss1 += KERNEL1x3(in_1, x * 4 + 1, &kernel[6], 4); + ss2 += KERNEL1x3(in_1, x * 4 + 2, &kernel[6], 4); + v = MAKE_UINT32(clip8(ss0), clip8(ss1), clip8(ss2), 0); memcpy(out + x * sizeof(v), &v, sizeof(v)); } } else if (im->bands == 4) { - for (x = 1; x < im->xsize-1; x++) { + for (x = 1; x < im->xsize - 1; x++) { float ss0 = offset; float ss1 = offset; float ss2 = offset; float ss3 = offset; UINT32 v; - ss0 += KERNEL1x3(in1, x*4+0, &kernel[0], 4); - ss1 += KERNEL1x3(in1, x*4+1, &kernel[0], 4); - ss2 += KERNEL1x3(in1, x*4+2, &kernel[0], 4); - ss3 += KERNEL1x3(in1, x*4+3, &kernel[0], 4); - ss0 += KERNEL1x3(in0, x*4+0, &kernel[3], 4); - ss1 += KERNEL1x3(in0, x*4+1, &kernel[3], 4); - ss2 += KERNEL1x3(in0, x*4+2, &kernel[3], 4); - ss3 += KERNEL1x3(in0, x*4+3, &kernel[3], 4); - ss0 += KERNEL1x3(in_1, x*4+0, &kernel[6], 4); - ss1 += KERNEL1x3(in_1, x*4+1, &kernel[6], 4); - ss2 += KERNEL1x3(in_1, x*4+2, &kernel[6], 4); - ss3 += KERNEL1x3(in_1, x*4+3, &kernel[6], 4); - v = MAKE_UINT32( - clip8(ss0), clip8(ss1), clip8(ss2), clip8(ss3)); + ss0 += KERNEL1x3(in1, x * 4 + 0, &kernel[0], 4); + ss1 += KERNEL1x3(in1, x * 4 + 1, &kernel[0], 4); + ss2 += KERNEL1x3(in1, x * 4 + 2, &kernel[0], 4); + ss3 += KERNEL1x3(in1, x * 4 + 3, &kernel[0], 4); + ss0 += KERNEL1x3(in0, x * 4 + 0, &kernel[3], 4); + ss1 += KERNEL1x3(in0, x * 4 + 1, &kernel[3], 4); + ss2 += KERNEL1x3(in0, x * 4 + 2, &kernel[3], 4); + ss3 += KERNEL1x3(in0, x * 4 + 3, &kernel[3], 4); + ss0 += KERNEL1x3(in_1, x * 4 + 0, &kernel[6], 4); + ss1 += KERNEL1x3(in_1, x * 4 + 1, &kernel[6], 4); + ss2 += KERNEL1x3(in_1, x * 4 + 2, &kernel[6], 4); + ss3 += KERNEL1x3(in_1, x * 4 + 3, &kernel[6], 4); + v = MAKE_UINT32(clip8(ss0), clip8(ss1), clip8(ss2), clip8(ss3)); memcpy(out + x * sizeof(v), &v, sizeof(v)); } } @@ -188,17 +192,13 @@ ImagingFilter3x3(Imaging imOut, Imaging im, const float* kernel, memcpy(imOut->image[y], im->image[y], im->linesize); } - void -ImagingFilter5x5(Imaging imOut, Imaging im, const float* kernel, - float offset) -{ -#define KERNEL1x5(in0, x, kernel, d) ( \ - _i2f((UINT8) in0[x-d-d]) * (kernel)[0] + \ - _i2f((UINT8) in0[x-d]) * (kernel)[1] + \ - _i2f((UINT8) in0[x]) * (kernel)[2] + \ - _i2f((UINT8) in0[x+d]) * (kernel)[3] + \ - _i2f((UINT8) in0[x+d+d]) * (kernel)[4]) +ImagingFilter5x5(Imaging imOut, Imaging im, const float *kernel, float offset) { +#define KERNEL1x5(in0, x, kernel, d) \ + (_i2f((UINT8)in0[x - d - d]) * (kernel)[0] + \ + _i2f((UINT8)in0[x - d]) * (kernel)[1] + _i2f((UINT8)in0[x]) * (kernel)[2] + \ + _i2f((UINT8)in0[x + d]) * (kernel)[3] + \ + _i2f((UINT8)in0[x + d + d]) * (kernel)[4]) int x = 0, y = 0; @@ -207,17 +207,17 @@ ImagingFilter5x5(Imaging imOut, Imaging im, const float* kernel, if (im->bands == 1) { // Add one time for rounding offset += 0.5; - for (y = 2; y < im->ysize-2; y++) { - UINT8* in_2 = (UINT8*) im->image[y-2]; - UINT8* in_1 = (UINT8*) im->image[y-1]; - UINT8* in0 = (UINT8*) im->image[y]; - UINT8* in1 = (UINT8*) im->image[y+1]; - UINT8* in2 = (UINT8*) im->image[y+2]; - UINT8* out = (UINT8*) imOut->image[y]; + for (y = 2; y < im->ysize - 2; y++) { + UINT8 *in_2 = (UINT8 *)im->image[y - 2]; + UINT8 *in_1 = (UINT8 *)im->image[y - 1]; + UINT8 *in0 = (UINT8 *)im->image[y]; + UINT8 *in1 = (UINT8 *)im->image[y + 1]; + UINT8 *in2 = (UINT8 *)im->image[y + 2]; + UINT8 *out = (UINT8 *)imOut->image[y]; out[0] = in0[0]; out[1] = in0[1]; - for (x = 2; x < im->xsize-2; x++) { + for (x = 2; x < im->xsize - 2; x++) { float ss = offset; ss += KERNEL1x5(in2, x, &kernel[0], 1); ss += KERNEL1x5(in1, x, &kernel[5], 1); @@ -226,122 +226,123 @@ ImagingFilter5x5(Imaging imOut, Imaging im, const float* kernel, ss += KERNEL1x5(in_2, x, &kernel[20], 1); out[x] = clip8(ss); } - out[x+0] = in0[x+0]; - out[x+1] = in0[x+1]; + out[x + 0] = in0[x + 0]; + out[x + 1] = in0[x + 1]; } } else { // Add one time for rounding offset += 0.5; - for (y = 2; y < im->ysize-2; y++) { - UINT8* in_2 = (UINT8*) im->image[y-2]; - UINT8* in_1 = (UINT8*) im->image[y-1]; - UINT8* in0 = (UINT8*) im->image[y]; - UINT8* in1 = (UINT8*) im->image[y+1]; - UINT8* in2 = (UINT8*) im->image[y+2]; - UINT8* out = (UINT8*) imOut->image[y]; + for (y = 2; y < im->ysize - 2; y++) { + UINT8 *in_2 = (UINT8 *)im->image[y - 2]; + UINT8 *in_1 = (UINT8 *)im->image[y - 1]; + UINT8 *in0 = (UINT8 *)im->image[y]; + UINT8 *in1 = (UINT8 *)im->image[y + 1]; + UINT8 *in2 = (UINT8 *)im->image[y + 2]; + UINT8 *out = (UINT8 *)imOut->image[y]; memcpy(out, in0, sizeof(UINT32) * 2); if (im->bands == 2) { - for (x = 2; x < im->xsize-2; x++) { + for (x = 2; x < im->xsize - 2; x++) { float ss0 = offset; float ss3 = offset; UINT32 v; - ss0 += KERNEL1x5(in2, x*4+0, &kernel[0], 4); - ss3 += KERNEL1x5(in2, x*4+3, &kernel[0], 4); - ss0 += KERNEL1x5(in1, x*4+0, &kernel[5], 4); - ss3 += KERNEL1x5(in1, x*4+3, &kernel[5], 4); - ss0 += KERNEL1x5(in0, x*4+0, &kernel[10], 4); - ss3 += KERNEL1x5(in0, x*4+3, &kernel[10], 4); - ss0 += KERNEL1x5(in_1, x*4+0, &kernel[15], 4); - ss3 += KERNEL1x5(in_1, x*4+3, &kernel[15], 4); - ss0 += KERNEL1x5(in_2, x*4+0, &kernel[20], 4); - ss3 += KERNEL1x5(in_2, x*4+3, &kernel[20], 4); + ss0 += KERNEL1x5(in2, x * 4 + 0, &kernel[0], 4); + ss3 += KERNEL1x5(in2, x * 4 + 3, &kernel[0], 4); + ss0 += KERNEL1x5(in1, x * 4 + 0, &kernel[5], 4); + ss3 += KERNEL1x5(in1, x * 4 + 3, &kernel[5], 4); + ss0 += KERNEL1x5(in0, x * 4 + 0, &kernel[10], 4); + ss3 += KERNEL1x5(in0, x * 4 + 3, &kernel[10], 4); + ss0 += KERNEL1x5(in_1, x * 4 + 0, &kernel[15], 4); + ss3 += KERNEL1x5(in_1, x * 4 + 3, &kernel[15], 4); + ss0 += KERNEL1x5(in_2, x * 4 + 0, &kernel[20], 4); + ss3 += KERNEL1x5(in_2, x * 4 + 3, &kernel[20], 4); v = MAKE_UINT32(clip8(ss0), 0, 0, clip8(ss3)); memcpy(out + x * sizeof(v), &v, sizeof(v)); } } else if (im->bands == 3) { - for (x = 2; x < im->xsize-2; x++) { + for (x = 2; x < im->xsize - 2; x++) { float ss0 = offset; float ss1 = offset; float ss2 = offset; UINT32 v; - ss0 += KERNEL1x5(in2, x*4+0, &kernel[0], 4); - ss1 += KERNEL1x5(in2, x*4+1, &kernel[0], 4); - ss2 += KERNEL1x5(in2, x*4+2, &kernel[0], 4); - ss0 += KERNEL1x5(in1, x*4+0, &kernel[5], 4); - ss1 += KERNEL1x5(in1, x*4+1, &kernel[5], 4); - ss2 += KERNEL1x5(in1, x*4+2, &kernel[5], 4); - ss0 += KERNEL1x5(in0, x*4+0, &kernel[10], 4); - ss1 += KERNEL1x5(in0, x*4+1, &kernel[10], 4); - ss2 += KERNEL1x5(in0, x*4+2, &kernel[10], 4); - ss0 += KERNEL1x5(in_1, x*4+0, &kernel[15], 4); - ss1 += KERNEL1x5(in_1, x*4+1, &kernel[15], 4); - ss2 += KERNEL1x5(in_1, x*4+2, &kernel[15], 4); - ss0 += KERNEL1x5(in_2, x*4+0, &kernel[20], 4); - ss1 += KERNEL1x5(in_2, x*4+1, &kernel[20], 4); - ss2 += KERNEL1x5(in_2, x*4+2, &kernel[20], 4); - v = MAKE_UINT32( - clip8(ss0), clip8(ss1), clip8(ss2), 0); + ss0 += KERNEL1x5(in2, x * 4 + 0, &kernel[0], 4); + ss1 += KERNEL1x5(in2, x * 4 + 1, &kernel[0], 4); + ss2 += KERNEL1x5(in2, x * 4 + 2, &kernel[0], 4); + ss0 += KERNEL1x5(in1, x * 4 + 0, &kernel[5], 4); + ss1 += KERNEL1x5(in1, x * 4 + 1, &kernel[5], 4); + ss2 += KERNEL1x5(in1, x * 4 + 2, &kernel[5], 4); + ss0 += KERNEL1x5(in0, x * 4 + 0, &kernel[10], 4); + ss1 += KERNEL1x5(in0, x * 4 + 1, &kernel[10], 4); + ss2 += KERNEL1x5(in0, x * 4 + 2, &kernel[10], 4); + ss0 += KERNEL1x5(in_1, x * 4 + 0, &kernel[15], 4); + ss1 += KERNEL1x5(in_1, x * 4 + 1, &kernel[15], 4); + ss2 += KERNEL1x5(in_1, x * 4 + 2, &kernel[15], 4); + ss0 += KERNEL1x5(in_2, x * 4 + 0, &kernel[20], 4); + ss1 += KERNEL1x5(in_2, x * 4 + 1, &kernel[20], 4); + ss2 += KERNEL1x5(in_2, x * 4 + 2, &kernel[20], 4); + v = MAKE_UINT32(clip8(ss0), clip8(ss1), clip8(ss2), 0); memcpy(out + x * sizeof(v), &v, sizeof(v)); } } else if (im->bands == 4) { - for (x = 2; x < im->xsize-2; x++) { + for (x = 2; x < im->xsize - 2; x++) { float ss0 = offset; float ss1 = offset; float ss2 = offset; float ss3 = offset; UINT32 v; - ss0 += KERNEL1x5(in2, x*4+0, &kernel[0], 4); - ss1 += KERNEL1x5(in2, x*4+1, &kernel[0], 4); - ss2 += KERNEL1x5(in2, x*4+2, &kernel[0], 4); - ss3 += KERNEL1x5(in2, x*4+3, &kernel[0], 4); - ss0 += KERNEL1x5(in1, x*4+0, &kernel[5], 4); - ss1 += KERNEL1x5(in1, x*4+1, &kernel[5], 4); - ss2 += KERNEL1x5(in1, x*4+2, &kernel[5], 4); - ss3 += KERNEL1x5(in1, x*4+3, &kernel[5], 4); - ss0 += KERNEL1x5(in0, x*4+0, &kernel[10], 4); - ss1 += KERNEL1x5(in0, x*4+1, &kernel[10], 4); - ss2 += KERNEL1x5(in0, x*4+2, &kernel[10], 4); - ss3 += KERNEL1x5(in0, x*4+3, &kernel[10], 4); - ss0 += KERNEL1x5(in_1, x*4+0, &kernel[15], 4); - ss1 += KERNEL1x5(in_1, x*4+1, &kernel[15], 4); - ss2 += KERNEL1x5(in_1, x*4+2, &kernel[15], 4); - ss3 += KERNEL1x5(in_1, x*4+3, &kernel[15], 4); - ss0 += KERNEL1x5(in_2, x*4+0, &kernel[20], 4); - ss1 += KERNEL1x5(in_2, x*4+1, &kernel[20], 4); - ss2 += KERNEL1x5(in_2, x*4+2, &kernel[20], 4); - ss3 += KERNEL1x5(in_2, x*4+3, &kernel[20], 4); - v = MAKE_UINT32( - clip8(ss0), clip8(ss1), clip8(ss2), clip8(ss3)); + ss0 += KERNEL1x5(in2, x * 4 + 0, &kernel[0], 4); + ss1 += KERNEL1x5(in2, x * 4 + 1, &kernel[0], 4); + ss2 += KERNEL1x5(in2, x * 4 + 2, &kernel[0], 4); + ss3 += KERNEL1x5(in2, x * 4 + 3, &kernel[0], 4); + ss0 += KERNEL1x5(in1, x * 4 + 0, &kernel[5], 4); + ss1 += KERNEL1x5(in1, x * 4 + 1, &kernel[5], 4); + ss2 += KERNEL1x5(in1, x * 4 + 2, &kernel[5], 4); + ss3 += KERNEL1x5(in1, x * 4 + 3, &kernel[5], 4); + ss0 += KERNEL1x5(in0, x * 4 + 0, &kernel[10], 4); + ss1 += KERNEL1x5(in0, x * 4 + 1, &kernel[10], 4); + ss2 += KERNEL1x5(in0, x * 4 + 2, &kernel[10], 4); + ss3 += KERNEL1x5(in0, x * 4 + 3, &kernel[10], 4); + ss0 += KERNEL1x5(in_1, x * 4 + 0, &kernel[15], 4); + ss1 += KERNEL1x5(in_1, x * 4 + 1, &kernel[15], 4); + ss2 += KERNEL1x5(in_1, x * 4 + 2, &kernel[15], 4); + ss3 += KERNEL1x5(in_1, x * 4 + 3, &kernel[15], 4); + ss0 += KERNEL1x5(in_2, x * 4 + 0, &kernel[20], 4); + ss1 += KERNEL1x5(in_2, x * 4 + 1, &kernel[20], 4); + ss2 += KERNEL1x5(in_2, x * 4 + 2, &kernel[20], 4); + ss3 += KERNEL1x5(in_2, x * 4 + 3, &kernel[20], 4); + v = MAKE_UINT32(clip8(ss0), clip8(ss1), clip8(ss2), clip8(ss3)); memcpy(out + x * sizeof(v), &v, sizeof(v)); } } - memcpy(out + x * sizeof(UINT32), in0 + x * sizeof(UINT32), sizeof(UINT32) * 2); + memcpy( + out + x * sizeof(UINT32), in0 + x * sizeof(UINT32), sizeof(UINT32) * 2); } } memcpy(imOut->image[y], im->image[y], im->linesize); - memcpy(imOut->image[y+1], im->image[y+1], im->linesize); + memcpy(imOut->image[y + 1], im->image[y + 1], im->linesize); } Imaging -ImagingFilter(Imaging im, int xsize, int ysize, const FLOAT32* kernel, - FLOAT32 offset) -{ +ImagingFilter(Imaging im, int xsize, int ysize, const FLOAT32 *kernel, FLOAT32 offset) { Imaging imOut; ImagingSectionCookie cookie; - if ( ! im || im->type != IMAGING_TYPE_UINT8) - return (Imaging) ImagingError_ModeError(); + if (!im || im->type != IMAGING_TYPE_UINT8) { + return (Imaging)ImagingError_ModeError(); + } - if (im->xsize < xsize || im->ysize < ysize) + if (im->xsize < xsize || im->ysize < ysize) { return ImagingCopy(im); + } - if ((xsize != 3 && xsize != 5) || xsize != ysize) - return (Imaging) ImagingError_ValueError("bad kernel size"); + if ((xsize != 3 && xsize != 5) || xsize != ysize) { + return (Imaging)ImagingError_ValueError("bad kernel size"); + } imOut = ImagingNewDirty(im->mode, im->xsize, im->ysize); - if (!imOut) + if (!imOut) { return NULL; + } ImagingSectionEnter(&cookie); if (xsize == 3) { @@ -354,4 +355,3 @@ ImagingFilter(Imaging im, int xsize, int ysize, const FLOAT32* kernel, ImagingSectionLeave(&cookie); return imOut; } - diff --git a/src/libImaging/FliDecode.c b/src/libImaging/FliDecode.c index 6f48c07d4..e9000fc99 100644 --- a/src/libImaging/FliDecode.c +++ b/src/libImaging/FliDecode.c @@ -5,8 +5,8 @@ * decoder for Autodesk Animator FLI/FLC animations * * history: - * 97-01-03 fl Created - * 97-01-17 fl Added SS2 support (FLC) + * 97-01-03 fl Created + * 97-01-17 fl Added SS2 support (FLC) * * Copyright (c) Fredrik Lundh 1997. * Copyright (c) Secret Labs AB 1997. @@ -14,21 +14,21 @@ * See the README file for information on usage and redistribution. */ - #include "Imaging.h" +#define I16(ptr) ((ptr)[0] + ((ptr)[1] << 8)) -#define I16(ptr)\ - ((ptr)[0] + ((ptr)[1] << 8)) - -#define I32(ptr)\ - ((ptr)[0] + ((ptr)[1] << 8) + ((ptr)[2] << 16) + ((ptr)[3] << 24)) +#define I32(ptr) ((ptr)[0] + ((ptr)[1] << 8) + ((ptr)[2] << 16) + ((ptr)[3] << 24)) +#define ERR_IF_DATA_OOB(offset) \ + if ((data + (offset)) > ptr + bytes) { \ + state->errcode = IMAGING_CODEC_OVERRUN; \ + return -1; \ + } int -ImagingFliDecode(Imaging im, ImagingCodecState state, UINT8* buf, Py_ssize_t bytes) -{ - UINT8* ptr; +ImagingFliDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t bytes) { + UINT8 *ptr; int framesize; int c, chunks, advance; int l, lines; @@ -36,8 +36,9 @@ ImagingFliDecode(Imaging im, ImagingCodecState state, UINT8* buf, Py_ssize_t byt /* If not even the chunk size is present, we'd better leave */ - if (bytes < 4) - return 0; + if (bytes < 4) { + return 0; + } /* We don't decode anything unless we have a full chunk in the input buffer */ @@ -45,8 +46,9 @@ ImagingFliDecode(Imaging im, ImagingCodecState state, UINT8* buf, Py_ssize_t byt ptr = buf; framesize = I32(ptr); - if (framesize < I32(ptr)) - return 0; + if (framesize < I32(ptr)) { + return 0; + } /* Make sure this is a frame chunk. The Python driver takes case of other chunk types. */ @@ -55,161 +57,198 @@ ImagingFliDecode(Imaging im, ImagingCodecState state, UINT8* buf, Py_ssize_t byt state->errcode = IMAGING_CODEC_OVERRUN; return -1; } - if (I16(ptr+4) != 0xF1FA) { - state->errcode = IMAGING_CODEC_UNKNOWN; - return -1; + if (I16(ptr + 4) != 0xF1FA) { + state->errcode = IMAGING_CODEC_UNKNOWN; + return -1; } - chunks = I16(ptr+6); + chunks = I16(ptr + 6); ptr += 16; bytes -= 16; /* Process subchunks */ for (c = 0; c < chunks; c++) { - UINT8* data; - if (bytes < 10) { - state->errcode = IMAGING_CODEC_OVERRUN; - return -1; - } - data = ptr + 6; - switch (I16(ptr+4)) { - case 4: case 11: - /* FLI COLOR chunk */ - break; /* ignored; handled by Python code */ - case 7: - /* FLI SS2 chunk (word delta) */ - lines = I16(data); data += 2; - for (l = y = 0; l < lines && y < state->ysize; l++, y++) { - UINT8* buf = (UINT8*) im->image[y]; - int p, packets; - packets = I16(data); data += 2; - while (packets & 0x8000) { - /* flag word */ - if (packets & 0x4000) { - y += 65536 - packets; /* skip lines */ - if (y >= state->ysize) { - state->errcode = IMAGING_CODEC_OVERRUN; - return -1; - } - buf = (UINT8*) im->image[y]; - } else { - /* store last byte (used if line width is odd) */ - buf[state->xsize-1] = (UINT8) packets; - } - packets = I16(data); data += 2; - } - for (p = x = 0; p < packets; p++) { - x += data[0]; /* pixel skip */ - if (data[1] >= 128) { - i = 256-data[1]; /* run */ - if (x + i + i > state->xsize) - break; - for (j = 0; j < i; j++) { - buf[x++] = data[2]; - buf[x++] = data[3]; - } - data += 2 + 2; - } else { - i = 2 * (int) data[1]; /* chunk */ - if (x + i > state->xsize) - break; - memcpy(buf + x, data + 2, i); - data += 2 + i; - x += i; - } - } - if (p < packets) - break; /* didn't process all packets */ - } - if (l < lines) { - /* didn't process all lines */ - state->errcode = IMAGING_CODEC_OVERRUN; - return -1; - } - break; - case 12: - /* FLI LC chunk (byte delta) */ - y = I16(data); ymax = y + I16(data+2); data += 4; - for (; y < ymax && y < state->ysize; y++) { - UINT8* out = (UINT8*) im->image[y]; - int p, packets = *data++; - for (p = x = 0; p < packets; p++, x += i) { - x += data[0]; /* skip pixels */ - if (data[1] & 0x80) { - i = 256-data[1]; /* run */ - if (x + i > state->xsize) - break; - memset(out + x, data[2], i); - data += 3; - } else { - i = data[1]; /* chunk */ - if (x + i > state->xsize) - break; - memcpy(out + x, data + 2, i); - data += i + 2; - } - } - if (p < packets) - break; /* didn't process all packets */ - } - if (y < ymax) { - /* didn't process all lines */ - state->errcode = IMAGING_CODEC_OVERRUN; - return -1; - } - break; - case 13: - /* FLI BLACK chunk */ - for (y = 0; y < state->ysize; y++) - memset(im->image[y], 0, state->xsize); - break; - case 15: - /* FLI BRUN chunk */ - for (y = 0; y < state->ysize; y++) { - UINT8* out = (UINT8*) im->image[y]; - data += 1; /* ignore packetcount byte */ - for (x = 0; x < state->xsize; x += i) { - if (data[0] & 0x80) { - i = 256 - data[0]; - if (x + i > state->xsize) - break; /* safety first */ - memcpy(out + x, data + 1, i); - data += i + 1; - } else { - i = data[0]; - if (x + i > state->xsize) - break; /* safety first */ - memset(out + x, data[1], i); - data += 2; - } - } - if (x != state->xsize) { - /* didn't unpack whole line */ - state->errcode = IMAGING_CODEC_OVERRUN; - return -1; - } - } - break; - case 16: - /* COPY chunk */ - for (y = 0; y < state->ysize; y++) { - UINT8* buf = (UINT8*) im->image[y]; - memcpy(buf, data, state->xsize); - data += state->xsize; - } - break; - case 18: - /* PSTAMP chunk */ - break; /* ignored */ - default: - /* unknown chunk */ - /* printf("unknown FLI/FLC chunk: %d\n", I16(ptr+4)); */ - state->errcode = IMAGING_CODEC_UNKNOWN; - return -1; - } - advance = I32(ptr); - ptr += advance; - bytes -= advance; + UINT8 *data; + if (bytes < 10) { + state->errcode = IMAGING_CODEC_OVERRUN; + return -1; + } + data = ptr + 6; + switch (I16(ptr + 4)) { + case 4: + case 11: + /* FLI COLOR chunk */ + break; /* ignored; handled by Python code */ + case 7: + /* FLI SS2 chunk (word delta) */ + /* OOB ok, we've got 4 bytes min on entry */ + lines = I16(data); + data += 2; + for (l = y = 0; l < lines && y < state->ysize; l++, y++) { + UINT8 *local_buf = (UINT8 *)im->image[y]; + int p, packets; + ERR_IF_DATA_OOB(2) + packets = I16(data); + data += 2; + while (packets & 0x8000) { + /* flag word */ + if (packets & 0x4000) { + y += 65536 - packets; /* skip lines */ + if (y >= state->ysize) { + state->errcode = IMAGING_CODEC_OVERRUN; + return -1; + } + local_buf = (UINT8 *)im->image[y]; + } else { + /* store last byte (used if line width is odd) */ + local_buf[state->xsize - 1] = (UINT8)packets; + } + ERR_IF_DATA_OOB(2) + packets = I16(data); + data += 2; + } + for (p = x = 0; p < packets; p++) { + ERR_IF_DATA_OOB(2) + x += data[0]; /* pixel skip */ + if (data[1] >= 128) { + ERR_IF_DATA_OOB(4) + i = 256 - data[1]; /* run */ + if (x + i + i > state->xsize) { + break; + } + for (j = 0; j < i; j++) { + local_buf[x++] = data[2]; + local_buf[x++] = data[3]; + } + data += 2 + 2; + } else { + i = 2 * (int)data[1]; /* chunk */ + if (x + i > state->xsize) { + break; + } + ERR_IF_DATA_OOB(2 + i) + memcpy(local_buf + x, data + 2, i); + data += 2 + i; + x += i; + } + } + if (p < packets) { + break; /* didn't process all packets */ + } + } + if (l < lines) { + /* didn't process all lines */ + state->errcode = IMAGING_CODEC_OVERRUN; + return -1; + } + break; + case 12: + /* FLI LC chunk (byte delta) */ + /* OOB Check ok, we have 4 bytes min here */ + y = I16(data); + ymax = y + I16(data + 2); + data += 4; + for (; y < ymax && y < state->ysize; y++) { + UINT8 *out = (UINT8 *)im->image[y]; + ERR_IF_DATA_OOB(1) + int p, packets = *data++; + for (p = x = 0; p < packets; p++, x += i) { + ERR_IF_DATA_OOB(2) + x += data[0]; /* skip pixels */ + if (data[1] & 0x80) { + i = 256 - data[1]; /* run */ + if (x + i > state->xsize) { + break; + } + ERR_IF_DATA_OOB(3) + memset(out + x, data[2], i); + data += 3; + } else { + i = data[1]; /* chunk */ + if (x + i > state->xsize) { + break; + } + ERR_IF_DATA_OOB(2 + i) + memcpy(out + x, data + 2, i); + data += i + 2; + } + } + if (p < packets) { + break; /* didn't process all packets */ + } + } + if (y < ymax) { + /* didn't process all lines */ + state->errcode = IMAGING_CODEC_OVERRUN; + return -1; + } + break; + case 13: + /* FLI BLACK chunk */ + for (y = 0; y < state->ysize; y++) { + memset(im->image[y], 0, state->xsize); + } + break; + case 15: + /* FLI BRUN chunk */ + /* OOB, ok, we've got 4 bytes min on entry */ + for (y = 0; y < state->ysize; y++) { + UINT8 *out = (UINT8 *)im->image[y]; + data += 1; /* ignore packetcount byte */ + for (x = 0; x < state->xsize; x += i) { + ERR_IF_DATA_OOB(2) + if (data[0] & 0x80) { + i = 256 - data[0]; + if (x + i > state->xsize) { + break; /* safety first */ + } + ERR_IF_DATA_OOB(i + 1) + memcpy(out + x, data + 1, i); + data += i + 1; + } else { + i = data[0]; + if (x + i > state->xsize) { + break; /* safety first */ + } + memset(out + x, data[1], i); + data += 2; + } + } + if (x != state->xsize) { + /* didn't unpack whole line */ + state->errcode = IMAGING_CODEC_OVERRUN; + return -1; + } + } + break; + case 16: + /* COPY chunk */ + if (state->xsize > bytes / state->ysize) { + /* not enough data for frame */ + return ptr - buf; /* bytes consumed */ + } + for (y = 0; y < state->ysize; y++) { + UINT8 *local_buf = (UINT8 *)im->image[y]; + memcpy(local_buf, data, state->xsize); + data += state->xsize; + } + break; + case 18: + /* PSTAMP chunk */ + break; /* ignored */ + default: + /* unknown chunk */ + /* printf("unknown FLI/FLC chunk: %d\n", I16(ptr+4)); */ + state->errcode = IMAGING_CODEC_UNKNOWN; + return -1; + } + advance = I32(ptr); + if (advance < 0 || advance > bytes) { + state->errcode = IMAGING_CODEC_OVERRUN; + return -1; + } + ptr += advance; + bytes -= advance; } return -1; /* end of frame */ diff --git a/src/libImaging/Geometry.c b/src/libImaging/Geometry.c index fd5e25958..0c5915792 100644 --- a/src/libImaging/Geometry.c +++ b/src/libImaging/Geometry.c @@ -15,25 +15,27 @@ /* Transpose operations */ Imaging -ImagingFlipLeftRight(Imaging imOut, Imaging imIn) -{ +ImagingFlipLeftRight(Imaging imOut, Imaging imIn) { ImagingSectionCookie cookie; int x, y, xr; - if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0) - return (Imaging) ImagingError_ModeError(); - if (imIn->xsize != imOut->xsize || imIn->ysize != imOut->ysize) - return (Imaging) ImagingError_Mismatch(); + if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0) { + return (Imaging)ImagingError_ModeError(); + } + if (imIn->xsize != imOut->xsize || imIn->ysize != imOut->ysize) { + return (Imaging)ImagingError_Mismatch(); + } ImagingCopyPalette(imOut, imIn); -#define FLIP_LEFT_RIGHT(INT, image) \ - for (y = 0; y < imIn->ysize; y++) { \ - INT* in = (INT *)imIn->image[y]; \ - INT* out = (INT *)imOut->image[y]; \ - xr = imIn->xsize-1; \ - for (x = 0; x < imIn->xsize; x++, xr--) \ - out[xr] = in[x]; \ +#define FLIP_LEFT_RIGHT(INT, image) \ + for (y = 0; y < imIn->ysize; y++) { \ + INT *in = (INT *)imIn->image[y]; \ + INT *out = (INT *)imOut->image[y]; \ + xr = imIn->xsize - 1; \ + for (x = 0; x < imIn->xsize; x++, xr--) { \ + out[xr] = in[x]; \ + } \ } ImagingSectionEnter(&cookie); @@ -55,66 +57,71 @@ ImagingFlipLeftRight(Imaging imOut, Imaging imIn) return imOut; } - Imaging -ImagingFlipTopBottom(Imaging imOut, Imaging imIn) -{ +ImagingFlipTopBottom(Imaging imOut, Imaging imIn) { ImagingSectionCookie cookie; int y, yr; - if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0) - return (Imaging) ImagingError_ModeError(); - if (imIn->xsize != imOut->xsize || imIn->ysize != imOut->ysize) - return (Imaging) ImagingError_Mismatch(); + if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0) { + return (Imaging)ImagingError_ModeError(); + } + if (imIn->xsize != imOut->xsize || imIn->ysize != imOut->ysize) { + return (Imaging)ImagingError_Mismatch(); + } ImagingCopyPalette(imOut, imIn); ImagingSectionEnter(&cookie); yr = imIn->ysize - 1; - for (y = 0; y < imIn->ysize; y++, yr--) + for (y = 0; y < imIn->ysize; y++, yr--) { memcpy(imOut->image[yr], imIn->image[y], imIn->linesize); + } ImagingSectionLeave(&cookie); return imOut; } - Imaging -ImagingRotate90(Imaging imOut, Imaging imIn) -{ +ImagingRotate90(Imaging imOut, Imaging imIn) { ImagingSectionCookie cookie; int x, y, xx, yy, xr, xxsize, yysize; int xxx, yyy, xxxsize, yyysize; - if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0) - return (Imaging) ImagingError_ModeError(); - if (imIn->xsize != imOut->ysize || imIn->ysize != imOut->xsize) - return (Imaging) ImagingError_Mismatch(); + if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0) { + return (Imaging)ImagingError_ModeError(); + } + if (imIn->xsize != imOut->ysize || imIn->ysize != imOut->xsize) { + return (Imaging)ImagingError_Mismatch(); + } ImagingCopyPalette(imOut, imIn); -#define ROTATE_90(INT, image) \ - for (y = 0; y < imIn->ysize; y += ROTATE_CHUNK) { \ - for (x = 0; x < imIn->xsize; x += ROTATE_CHUNK) { \ +#define ROTATE_90(INT, image) \ + for (y = 0; y < imIn->ysize; y += ROTATE_CHUNK) { \ + for (x = 0; x < imIn->xsize; x += ROTATE_CHUNK) { \ yysize = y + ROTATE_CHUNK < imIn->ysize ? y + ROTATE_CHUNK : imIn->ysize; \ xxsize = x + ROTATE_CHUNK < imIn->xsize ? x + ROTATE_CHUNK : imIn->xsize; \ - for (yy = y; yy < yysize; yy += ROTATE_SMALL_CHUNK) { \ - for (xx = x; xx < xxsize; xx += ROTATE_SMALL_CHUNK) { \ - yyysize = yy + ROTATE_SMALL_CHUNK < imIn->ysize ? yy + ROTATE_SMALL_CHUNK : imIn->ysize; \ - xxxsize = xx + ROTATE_SMALL_CHUNK < imIn->xsize ? xx + ROTATE_SMALL_CHUNK : imIn->xsize; \ - for (yyy = yy; yyy < yyysize; yyy++) { \ - INT* in = (INT *)imIn->image[yyy]; \ - xr = imIn->xsize - 1 - xx; \ - for (xxx = xx; xxx < xxxsize; xxx++, xr--) { \ - INT* out = (INT *)imOut->image[xr]; \ - out[yyy] = in[xxx]; \ - } \ - } \ - } \ - } \ - } \ + for (yy = y; yy < yysize; yy += ROTATE_SMALL_CHUNK) { \ + for (xx = x; xx < xxsize; xx += ROTATE_SMALL_CHUNK) { \ + yyysize = yy + ROTATE_SMALL_CHUNK < imIn->ysize \ + ? yy + ROTATE_SMALL_CHUNK \ + : imIn->ysize; \ + xxxsize = xx + ROTATE_SMALL_CHUNK < imIn->xsize \ + ? xx + ROTATE_SMALL_CHUNK \ + : imIn->xsize; \ + for (yyy = yy; yyy < yyysize; yyy++) { \ + INT *in = (INT *)imIn->image[yyy]; \ + xr = imIn->xsize - 1 - xx; \ + for (xxx = xx; xxx < xxxsize; xxx++, xr--) { \ + INT *out = (INT *)imOut->image[xr]; \ + out[yyy] = in[xxx]; \ + } \ + } \ + } \ + } \ + } \ } ImagingSectionEnter(&cookie); @@ -136,40 +143,44 @@ ImagingRotate90(Imaging imOut, Imaging imIn) return imOut; } - Imaging -ImagingTranspose(Imaging imOut, Imaging imIn) -{ +ImagingTranspose(Imaging imOut, Imaging imIn) { ImagingSectionCookie cookie; int x, y, xx, yy, xxsize, yysize; int xxx, yyy, xxxsize, yyysize; - if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0) - return (Imaging) ImagingError_ModeError(); - if (imIn->xsize != imOut->ysize || imIn->ysize != imOut->xsize) - return (Imaging) ImagingError_Mismatch(); + if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0) { + return (Imaging)ImagingError_ModeError(); + } + if (imIn->xsize != imOut->ysize || imIn->ysize != imOut->xsize) { + return (Imaging)ImagingError_Mismatch(); + } ImagingCopyPalette(imOut, imIn); -#define TRANSPOSE(INT, image) \ - for (y = 0; y < imIn->ysize; y += ROTATE_CHUNK) { \ - for (x = 0; x < imIn->xsize; x += ROTATE_CHUNK) { \ +#define TRANSPOSE(INT, image) \ + for (y = 0; y < imIn->ysize; y += ROTATE_CHUNK) { \ + for (x = 0; x < imIn->xsize; x += ROTATE_CHUNK) { \ yysize = y + ROTATE_CHUNK < imIn->ysize ? y + ROTATE_CHUNK : imIn->ysize; \ xxsize = x + ROTATE_CHUNK < imIn->xsize ? x + ROTATE_CHUNK : imIn->xsize; \ - for (yy = y; yy < yysize; yy += ROTATE_SMALL_CHUNK) { \ - for (xx = x; xx < xxsize; xx += ROTATE_SMALL_CHUNK) { \ - yyysize = yy + ROTATE_SMALL_CHUNK < imIn->ysize ? yy + ROTATE_SMALL_CHUNK : imIn->ysize; \ - xxxsize = xx + ROTATE_SMALL_CHUNK < imIn->xsize ? xx + ROTATE_SMALL_CHUNK : imIn->xsize; \ - for (yyy = yy; yyy < yyysize; yyy++) { \ - INT* in = (INT *)imIn->image[yyy]; \ - for (xxx = xx; xxx < xxxsize; xxx++) { \ - INT* out = (INT *)imOut->image[xxx]; \ - out[yyy] = in[xxx]; \ - } \ - } \ - } \ - } \ - } \ + for (yy = y; yy < yysize; yy += ROTATE_SMALL_CHUNK) { \ + for (xx = x; xx < xxsize; xx += ROTATE_SMALL_CHUNK) { \ + yyysize = yy + ROTATE_SMALL_CHUNK < imIn->ysize \ + ? yy + ROTATE_SMALL_CHUNK \ + : imIn->ysize; \ + xxxsize = xx + ROTATE_SMALL_CHUNK < imIn->xsize \ + ? xx + ROTATE_SMALL_CHUNK \ + : imIn->xsize; \ + for (yyy = yy; yyy < yyysize; yyy++) { \ + INT *in = (INT *)imIn->image[yyy]; \ + for (xxx = xx; xxx < xxxsize; xxx++) { \ + INT *out = (INT *)imOut->image[xxx]; \ + out[yyy] = in[xxx]; \ + } \ + } \ + } \ + } \ + } \ } ImagingSectionEnter(&cookie); @@ -191,42 +202,46 @@ ImagingTranspose(Imaging imOut, Imaging imIn) return imOut; } - Imaging -ImagingTransverse(Imaging imOut, Imaging imIn) -{ +ImagingTransverse(Imaging imOut, Imaging imIn) { ImagingSectionCookie cookie; int x, y, xr, yr, xx, yy, xxsize, yysize; int xxx, yyy, xxxsize, yyysize; - if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0) - return (Imaging) ImagingError_ModeError(); - if (imIn->xsize != imOut->ysize || imIn->ysize != imOut->xsize) - return (Imaging) ImagingError_Mismatch(); + if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0) { + return (Imaging)ImagingError_ModeError(); + } + if (imIn->xsize != imOut->ysize || imIn->ysize != imOut->xsize) { + return (Imaging)ImagingError_Mismatch(); + } ImagingCopyPalette(imOut, imIn); -#define TRANSVERSE(INT, image) \ - for (y = 0; y < imIn->ysize; y += ROTATE_CHUNK) { \ - for (x = 0; x < imIn->xsize; x += ROTATE_CHUNK) { \ +#define TRANSVERSE(INT, image) \ + for (y = 0; y < imIn->ysize; y += ROTATE_CHUNK) { \ + for (x = 0; x < imIn->xsize; x += ROTATE_CHUNK) { \ yysize = y + ROTATE_CHUNK < imIn->ysize ? y + ROTATE_CHUNK : imIn->ysize; \ xxsize = x + ROTATE_CHUNK < imIn->xsize ? x + ROTATE_CHUNK : imIn->xsize; \ - for (yy = y; yy < yysize; yy += ROTATE_SMALL_CHUNK) { \ - for (xx = x; xx < xxsize; xx += ROTATE_SMALL_CHUNK) { \ - yyysize = yy + ROTATE_SMALL_CHUNK < imIn->ysize ? yy + ROTATE_SMALL_CHUNK : imIn->ysize; \ - xxxsize = xx + ROTATE_SMALL_CHUNK < imIn->xsize ? xx + ROTATE_SMALL_CHUNK : imIn->xsize; \ - yr = imIn->ysize - 1 - yy; \ - for (yyy = yy; yyy < yyysize; yyy++, yr--) { \ - INT* in = (INT *)imIn->image[yyy]; \ - xr = imIn->xsize - 1 - xx; \ - for (xxx = xx; xxx < xxxsize; xxx++, xr--) { \ - INT* out = (INT *)imOut->image[xr]; \ - out[yr] = in[xxx]; \ - } \ - } \ - } \ - } \ - } \ + for (yy = y; yy < yysize; yy += ROTATE_SMALL_CHUNK) { \ + for (xx = x; xx < xxsize; xx += ROTATE_SMALL_CHUNK) { \ + yyysize = yy + ROTATE_SMALL_CHUNK < imIn->ysize \ + ? yy + ROTATE_SMALL_CHUNK \ + : imIn->ysize; \ + xxxsize = xx + ROTATE_SMALL_CHUNK < imIn->xsize \ + ? xx + ROTATE_SMALL_CHUNK \ + : imIn->xsize; \ + yr = imIn->ysize - 1 - yy; \ + for (yyy = yy; yyy < yyysize; yyy++, yr--) { \ + INT *in = (INT *)imIn->image[yyy]; \ + xr = imIn->xsize - 1 - xx; \ + for (xxx = xx; xxx < xxxsize; xxx++, xr--) { \ + INT *out = (INT *)imOut->image[xr]; \ + out[yr] = in[xxx]; \ + } \ + } \ + } \ + } \ + } \ } ImagingSectionEnter(&cookie); @@ -248,32 +263,33 @@ ImagingTransverse(Imaging imOut, Imaging imIn) return imOut; } - Imaging -ImagingRotate180(Imaging imOut, Imaging imIn) -{ +ImagingRotate180(Imaging imOut, Imaging imIn) { ImagingSectionCookie cookie; int x, y, xr, yr; - if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0) - return (Imaging) ImagingError_ModeError(); - if (imIn->xsize != imOut->xsize || imIn->ysize != imOut->ysize) - return (Imaging) ImagingError_Mismatch(); + if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0) { + return (Imaging)ImagingError_ModeError(); + } + if (imIn->xsize != imOut->xsize || imIn->ysize != imOut->ysize) { + return (Imaging)ImagingError_Mismatch(); + } ImagingCopyPalette(imOut, imIn); -#define ROTATE_180(INT, image) \ - for (y = 0; y < imIn->ysize; y++, yr--) { \ - INT* in = (INT *)imIn->image[y]; \ - INT* out = (INT *)imOut->image[yr]; \ - xr = imIn->xsize-1; \ - for (x = 0; x < imIn->xsize; x++, xr--) \ - out[xr] = in[x]; \ +#define ROTATE_180(INT, image) \ + for (y = 0; y < imIn->ysize; y++, yr--) { \ + INT *in = (INT *)imIn->image[y]; \ + INT *out = (INT *)imOut->image[yr]; \ + xr = imIn->xsize - 1; \ + for (x = 0; x < imIn->xsize; x++, xr--) { \ + out[xr] = in[x]; \ + } \ } ImagingSectionEnter(&cookie); - yr = imIn->ysize-1; + yr = imIn->ysize - 1; if (imIn->image8) { if (strncmp(imIn->mode, "I;16", 4) == 0) { ROTATE_180(UINT16, image8) @@ -291,41 +307,45 @@ ImagingRotate180(Imaging imOut, Imaging imIn) return imOut; } - Imaging -ImagingRotate270(Imaging imOut, Imaging imIn) -{ +ImagingRotate270(Imaging imOut, Imaging imIn) { ImagingSectionCookie cookie; int x, y, xx, yy, yr, xxsize, yysize; int xxx, yyy, xxxsize, yyysize; - if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0) - return (Imaging) ImagingError_ModeError(); - if (imIn->xsize != imOut->ysize || imIn->ysize != imOut->xsize) - return (Imaging) ImagingError_Mismatch(); + if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0) { + return (Imaging)ImagingError_ModeError(); + } + if (imIn->xsize != imOut->ysize || imIn->ysize != imOut->xsize) { + return (Imaging)ImagingError_Mismatch(); + } ImagingCopyPalette(imOut, imIn); -#define ROTATE_270(INT, image) \ - for (y = 0; y < imIn->ysize; y += ROTATE_CHUNK) { \ - for (x = 0; x < imIn->xsize; x += ROTATE_CHUNK) { \ +#define ROTATE_270(INT, image) \ + for (y = 0; y < imIn->ysize; y += ROTATE_CHUNK) { \ + for (x = 0; x < imIn->xsize; x += ROTATE_CHUNK) { \ yysize = y + ROTATE_CHUNK < imIn->ysize ? y + ROTATE_CHUNK : imIn->ysize; \ xxsize = x + ROTATE_CHUNK < imIn->xsize ? x + ROTATE_CHUNK : imIn->xsize; \ - for (yy = y; yy < yysize; yy += ROTATE_SMALL_CHUNK) { \ - for (xx = x; xx < xxsize; xx += ROTATE_SMALL_CHUNK) { \ - yyysize = yy + ROTATE_SMALL_CHUNK < imIn->ysize ? yy + ROTATE_SMALL_CHUNK : imIn->ysize; \ - xxxsize = xx + ROTATE_SMALL_CHUNK < imIn->xsize ? xx + ROTATE_SMALL_CHUNK : imIn->xsize; \ - yr = imIn->ysize - 1 - yy; \ - for (yyy = yy; yyy < yyysize; yyy++, yr--) { \ - INT* in = (INT *)imIn->image[yyy]; \ - for (xxx = xx; xxx < xxxsize; xxx++) { \ - INT* out = (INT *)imOut->image[xxx]; \ - out[yr] = in[xxx]; \ - } \ - } \ - } \ - } \ - } \ + for (yy = y; yy < yysize; yy += ROTATE_SMALL_CHUNK) { \ + for (xx = x; xx < xxsize; xx += ROTATE_SMALL_CHUNK) { \ + yyysize = yy + ROTATE_SMALL_CHUNK < imIn->ysize \ + ? yy + ROTATE_SMALL_CHUNK \ + : imIn->ysize; \ + xxxsize = xx + ROTATE_SMALL_CHUNK < imIn->xsize \ + ? xx + ROTATE_SMALL_CHUNK \ + : imIn->xsize; \ + yr = imIn->ysize - 1 - yy; \ + for (yyy = yy; yyy < yyysize; yyy++, yr--) { \ + INT *in = (INT *)imIn->image[yyy]; \ + for (xxx = xx; xxx < xxxsize; xxx++) { \ + INT *out = (INT *)imOut->image[xxx]; \ + out[yr] = in[xxx]; \ + } \ + } \ + } \ + } \ + } \ } ImagingSectionEnter(&cookie); @@ -347,63 +367,74 @@ ImagingRotate270(Imaging imOut, Imaging imIn) return imOut; } - /* -------------------------------------------------------------------- */ /* Transforms */ /* transform primitives (ImagingTransformMap) */ static int -affine_transform(double* xout, double* yout, int x, int y, void* data) -{ +affine_transform(double *xout, double *yout, int x, int y, void *data) { /* full moon tonight. your compiler will generate bogus code for simple expressions, unless you reorganize the code, or install Service Pack 3 */ - double* a = (double*) data; - double a0 = a[0]; double a1 = a[1]; double a2 = a[2]; - double a3 = a[3]; double a4 = a[4]; double a5 = a[5]; + double *a = (double *)data; + double a0 = a[0]; + double a1 = a[1]; + double a2 = a[2]; + double a3 = a[3]; + double a4 = a[4]; + double a5 = a[5]; double xin = x + 0.5; double yin = y + 0.5; - xout[0] = a0*xin + a1*yin + a2; - yout[0] = a3*xin + a4*yin + a5; + xout[0] = a0 * xin + a1 * yin + a2; + yout[0] = a3 * xin + a4 * yin + a5; return 1; } static int -perspective_transform(double* xout, double* yout, int x, int y, void* data) -{ - double* a = (double*) data; - double a0 = a[0]; double a1 = a[1]; double a2 = a[2]; - double a3 = a[3]; double a4 = a[4]; double a5 = a[5]; - double a6 = a[6]; double a7 = a[7]; +perspective_transform(double *xout, double *yout, int x, int y, void *data) { + double *a = (double *)data; + double a0 = a[0]; + double a1 = a[1]; + double a2 = a[2]; + double a3 = a[3]; + double a4 = a[4]; + double a5 = a[5]; + double a6 = a[6]; + double a7 = a[7]; double xin = x + 0.5; double yin = y + 0.5; - xout[0] = (a0*xin + a1*yin + a2) / (a6*xin + a7*yin + 1); - yout[0] = (a3*xin + a4*yin + a5) / (a6*xin + a7*yin + 1); + xout[0] = (a0 * xin + a1 * yin + a2) / (a6 * xin + a7 * yin + 1); + yout[0] = (a3 * xin + a4 * yin + a5) / (a6 * xin + a7 * yin + 1); return 1; } static int -quad_transform(double* xout, double* yout, int x, int y, void* data) -{ +quad_transform(double *xout, double *yout, int x, int y, void *data) { /* quad warp: map quadrilateral to rectangle */ - double* a = (double*) data; - double a0 = a[0]; double a1 = a[1]; double a2 = a[2]; double a3 = a[3]; - double a4 = a[4]; double a5 = a[5]; double a6 = a[6]; double a7 = a[7]; + double *a = (double *)data; + double a0 = a[0]; + double a1 = a[1]; + double a2 = a[2]; + double a3 = a[3]; + double a4 = a[4]; + double a5 = a[5]; + double a6 = a[6]; + double a7 = a[7]; double xin = x + 0.5; double yin = y + 0.5; - xout[0] = a0 + a1*xin + a2*yin + a3*xin*yin; - yout[0] = a4 + a5*xin + a6*yin + a7*xin*yin; + xout[0] = a0 + a1 * xin + a2 * yin + a3 * xin * yin; + yout[0] = a4 + a5 * xin + a6 * yin + a7 * xin * yin; return 1; } @@ -411,84 +442,84 @@ quad_transform(double* xout, double* yout, int x, int y, void* data) /* transform filters (ImagingTransformFilter) */ static int -nearest_filter8(void* out, Imaging im, double xin, double yin) -{ +nearest_filter8(void *out, Imaging im, double xin, double yin) { int x = COORD(xin); int y = COORD(yin); - if (x < 0 || x >= im->xsize || y < 0 || y >= im->ysize) + if (x < 0 || x >= im->xsize || y < 0 || y >= im->ysize) { return 0; - ((UINT8*)out)[0] = im->image8[y][x]; + } + ((UINT8 *)out)[0] = im->image8[y][x]; return 1; } static int -nearest_filter16(void* out, Imaging im, double xin, double yin) -{ +nearest_filter16(void *out, Imaging im, double xin, double yin) { int x = COORD(xin); int y = COORD(yin); - if (x < 0 || x >= im->xsize || y < 0 || y >= im->ysize) + if (x < 0 || x >= im->xsize || y < 0 || y >= im->ysize) { return 0; + } memcpy(out, im->image8[y] + x * sizeof(INT16), sizeof(INT16)); return 1; } static int -nearest_filter32(void* out, Imaging im, double xin, double yin) -{ +nearest_filter32(void *out, Imaging im, double xin, double yin) { int x = COORD(xin); int y = COORD(yin); - if (x < 0 || x >= im->xsize || y < 0 || y >= im->ysize) + if (x < 0 || x >= im->xsize || y < 0 || y >= im->ysize) { return 0; + } memcpy(out, &im->image32[y][x], sizeof(INT32)); return 1; } -#define XCLIP(im, x) ( ((x) < 0) ? 0 : ((x) < im->xsize) ? (x) : im->xsize-1 ) -#define YCLIP(im, y) ( ((y) < 0) ? 0 : ((y) < im->ysize) ? (y) : im->ysize-1 ) +#define XCLIP(im, x) (((x) < 0) ? 0 : ((x) < im->xsize) ? (x) : im->xsize - 1) +#define YCLIP(im, y) (((y) < 0) ? 0 : ((y) < im->ysize) ? (y) : im->ysize - 1) -#define BILINEAR(v, a, b, d)\ - (v = (a) + ( (b) - (a) ) * (d)) +#define BILINEAR(v, a, b, d) (v = (a) + ((b) - (a)) * (d)) -#define BILINEAR_HEAD(type)\ - int x, y;\ - int x0, x1;\ - double v1, v2;\ - double dx, dy;\ - type* in;\ - if (xin < 0.0 || xin >= im->xsize || yin < 0.0 || yin >= im->ysize)\ - return 0;\ - xin -= 0.5;\ - yin -= 0.5;\ - x = FLOOR(xin);\ - y = FLOOR(yin);\ - dx = xin - x;\ +#define BILINEAR_HEAD(type) \ + int x, y; \ + int x0, x1; \ + double v1, v2; \ + double dx, dy; \ + type *in; \ + if (xin < 0.0 || xin >= im->xsize || yin < 0.0 || yin >= im->ysize) { \ + return 0; \ + } \ + xin -= 0.5; \ + yin -= 0.5; \ + x = FLOOR(xin); \ + y = FLOOR(yin); \ + dx = xin - x; \ dy = yin - y; -#define BILINEAR_BODY(type, image, step, offset) {\ - in = (type*) ((image)[YCLIP(im, y)] + offset);\ - x0 = XCLIP(im, x+0)*step;\ - x1 = XCLIP(im, x+1)*step;\ - BILINEAR(v1, in[x0], in[x1], dx);\ - if (y+1 >= 0 && y+1 < im->ysize) {\ - in = (type*) ((image)[y+1] + offset);\ - BILINEAR(v2, in[x0], in[x1], dx);\ - } else\ - v2 = v1;\ - BILINEAR(v1, v1, v2, dy);\ -} +#define BILINEAR_BODY(type, image, step, offset) \ + { \ + in = (type *)((image)[YCLIP(im, y)] + offset); \ + x0 = XCLIP(im, x + 0) * step; \ + x1 = XCLIP(im, x + 1) * step; \ + BILINEAR(v1, in[x0], in[x1], dx); \ + if (y + 1 >= 0 && y + 1 < im->ysize) { \ + in = (type *)((image)[y + 1] + offset); \ + BILINEAR(v2, in[x0], in[x1], dx); \ + } else { \ + v2 = v1; \ + } \ + BILINEAR(v1, v1, v2, dy); \ + } static int -bilinear_filter8(void* out, Imaging im, double xin, double yin) -{ +bilinear_filter8(void *out, Imaging im, double xin, double yin) { BILINEAR_HEAD(UINT8); BILINEAR_BODY(UINT8, im->image8, 1, 0); - ((UINT8*)out)[0] = (UINT8) v1; + ((UINT8 *)out)[0] = (UINT8)v1; return 1; } static int -bilinear_filter32I(void* out, Imaging im, double xin, double yin) -{ +bilinear_filter32I(void *out, Imaging im, double xin, double yin) { INT32 k; BILINEAR_HEAD(INT32); BILINEAR_BODY(INT32, im->image32, 1, 0); @@ -498,8 +529,7 @@ bilinear_filter32I(void* out, Imaging im, double xin, double yin) } static int -bilinear_filter32F(void* out, Imaging im, double xin, double yin) -{ +bilinear_filter32F(void *out, Imaging im, double xin, double yin) { FLOAT32 k; BILINEAR_HEAD(FLOAT32); BILINEAR_BODY(FLOAT32, im->image32, 1, 0); @@ -509,26 +539,24 @@ bilinear_filter32F(void* out, Imaging im, double xin, double yin) } static int -bilinear_filter32LA(void* out, Imaging im, double xin, double yin) -{ +bilinear_filter32LA(void *out, Imaging im, double xin, double yin) { BILINEAR_HEAD(UINT8); BILINEAR_BODY(UINT8, im->image, 4, 0); - ((UINT8*)out)[0] = (UINT8) v1; - ((UINT8*)out)[1] = (UINT8) v1; - ((UINT8*)out)[2] = (UINT8) v1; + ((UINT8 *)out)[0] = (UINT8)v1; + ((UINT8 *)out)[1] = (UINT8)v1; + ((UINT8 *)out)[2] = (UINT8)v1; BILINEAR_BODY(UINT8, im->image, 4, 3); - ((UINT8*)out)[3] = (UINT8) v1; + ((UINT8 *)out)[3] = (UINT8)v1; return 1; } static int -bilinear_filter32RGB(void* out, Imaging im, double xin, double yin) -{ +bilinear_filter32RGB(void *out, Imaging im, double xin, double yin) { int b; BILINEAR_HEAD(UINT8); for (b = 0; b < im->bands; b++) { BILINEAR_BODY(UINT8, im->image, 4, b); - ((UINT8*)out)[b] = (UINT8) v1; + ((UINT8 *)out)[b] = (UINT8)v1; } return 1; } @@ -537,74 +565,79 @@ bilinear_filter32RGB(void* out, Imaging im, double xin, double yin) #undef BILINEAR_HEAD #undef BILINEAR_BODY -#define BICUBIC(v, v1, v2, v3, v4, d) {\ - double p1 = v2;\ - double p2 = -v1 + v3;\ - double p3 = 2*(v1 - v2) + v3 - v4;\ - double p4 = -v1 + v2 - v3 + v4;\ - v = p1 + (d)*(p2 + (d)*(p3 + (d)*p4));\ -} +#define BICUBIC(v, v1, v2, v3, v4, d) \ + { \ + double p1 = v2; \ + double p2 = -v1 + v3; \ + double p3 = 2 * (v1 - v2) + v3 - v4; \ + double p4 = -v1 + v2 - v3 + v4; \ + v = p1 + (d) * (p2 + (d) * (p3 + (d)*p4)); \ + } -#define BICUBIC_HEAD(type)\ - int x = FLOOR(xin);\ - int y = FLOOR(yin);\ - int x0, x1, x2, x3;\ - double v1, v2, v3, v4;\ - double dx, dy;\ - type* in;\ - if (xin < 0.0 || xin >= im->xsize || yin < 0.0 || yin >= im->ysize)\ - return 0;\ - xin -= 0.5;\ - yin -= 0.5;\ - x = FLOOR(xin);\ - y = FLOOR(yin);\ - dx = xin - x;\ - dy = yin - y;\ - x--; y--; - -#define BICUBIC_BODY(type, image, step, offset) {\ - in = (type*) ((image)[YCLIP(im, y)] + offset);\ - x0 = XCLIP(im, x+0)*step;\ - x1 = XCLIP(im, x+1)*step;\ - x2 = XCLIP(im, x+2)*step;\ - x3 = XCLIP(im, x+3)*step;\ - BICUBIC(v1, in[x0], in[x1], in[x2], in[x3], dx);\ - if (y+1 >= 0 && y+1 < im->ysize) {\ - in = (type*) ((image)[y+1] + offset);\ - BICUBIC(v2, in[x0], in[x1], in[x2], in[x3], dx);\ - } else\ - v2 = v1;\ - if (y+2 >= 0 && y+2 < im->ysize) {\ - in = (type*) ((image)[y+2] + offset);\ - BICUBIC(v3, in[x0], in[x1], in[x2], in[x3], dx);\ - } else\ - v3 = v2;\ - if (y+3 >= 0 && y+3 < im->ysize) {\ - in = (type*) ((image)[y+3] + offset);\ - BICUBIC(v4, in[x0], in[x1], in[x2], in[x3], dx);\ - } else\ - v4 = v3;\ - BICUBIC(v1, v1, v2, v3, v4, dy);\ -} +#define BICUBIC_HEAD(type) \ + int x = FLOOR(xin); \ + int y = FLOOR(yin); \ + int x0, x1, x2, x3; \ + double v1, v2, v3, v4; \ + double dx, dy; \ + type *in; \ + if (xin < 0.0 || xin >= im->xsize || yin < 0.0 || yin >= im->ysize) { \ + return 0; \ + } \ + xin -= 0.5; \ + yin -= 0.5; \ + x = FLOOR(xin); \ + y = FLOOR(yin); \ + dx = xin - x; \ + dy = yin - y; \ + x--; \ + y--; +#define BICUBIC_BODY(type, image, step, offset) \ + { \ + in = (type *)((image)[YCLIP(im, y)] + offset); \ + x0 = XCLIP(im, x + 0) * step; \ + x1 = XCLIP(im, x + 1) * step; \ + x2 = XCLIP(im, x + 2) * step; \ + x3 = XCLIP(im, x + 3) * step; \ + BICUBIC(v1, in[x0], in[x1], in[x2], in[x3], dx); \ + if (y + 1 >= 0 && y + 1 < im->ysize) { \ + in = (type *)((image)[y + 1] + offset); \ + BICUBIC(v2, in[x0], in[x1], in[x2], in[x3], dx); \ + } else { \ + v2 = v1; \ + } \ + if (y + 2 >= 0 && y + 2 < im->ysize) { \ + in = (type *)((image)[y + 2] + offset); \ + BICUBIC(v3, in[x0], in[x1], in[x2], in[x3], dx); \ + } else { \ + v3 = v2; \ + } \ + if (y + 3 >= 0 && y + 3 < im->ysize) { \ + in = (type *)((image)[y + 3] + offset); \ + BICUBIC(v4, in[x0], in[x1], in[x2], in[x3], dx); \ + } else { \ + v4 = v3; \ + } \ + BICUBIC(v1, v1, v2, v3, v4, dy); \ + } static int -bicubic_filter8(void* out, Imaging im, double xin, double yin) -{ +bicubic_filter8(void *out, Imaging im, double xin, double yin) { BICUBIC_HEAD(UINT8); BICUBIC_BODY(UINT8, im->image8, 1, 0); - if (v1 <= 0.0) - ((UINT8*)out)[0] = 0; - else if (v1 >= 255.0) - ((UINT8*)out)[0] = 255; - else - ((UINT8*)out)[0] = (UINT8) v1; + if (v1 <= 0.0) { + ((UINT8 *)out)[0] = 0; + } else if (v1 >= 255.0) { + ((UINT8 *)out)[0] = 255; + } else { + ((UINT8 *)out)[0] = (UINT8)v1; + } return 1; } static int -bicubic_filter32I(void* out, Imaging im, double xin, double yin) -{ +bicubic_filter32I(void *out, Imaging im, double xin, double yin) { INT32 k; BICUBIC_HEAD(INT32); BICUBIC_BODY(INT32, im->image32, 1, 0); @@ -614,8 +647,7 @@ bicubic_filter32I(void* out, Imaging im, double xin, double yin) } static int -bicubic_filter32F(void* out, Imaging im, double xin, double yin) -{ +bicubic_filter32F(void *out, Imaging im, double xin, double yin) { FLOAT32 k; BICUBIC_HEAD(FLOAT32); BICUBIC_BODY(FLOAT32, im->image32, 1, 0); @@ -625,46 +657,46 @@ bicubic_filter32F(void* out, Imaging im, double xin, double yin) } static int -bicubic_filter32LA(void* out, Imaging im, double xin, double yin) -{ +bicubic_filter32LA(void *out, Imaging im, double xin, double yin) { BICUBIC_HEAD(UINT8); BICUBIC_BODY(UINT8, im->image, 4, 0); if (v1 <= 0.0) { - ((UINT8*)out)[0] = 0; - ((UINT8*)out)[1] = 0; - ((UINT8*)out)[2] = 0; + ((UINT8 *)out)[0] = 0; + ((UINT8 *)out)[1] = 0; + ((UINT8 *)out)[2] = 0; } else if (v1 >= 255.0) { - ((UINT8*)out)[0] = 255; - ((UINT8*)out)[1] = 255; - ((UINT8*)out)[2] = 255; + ((UINT8 *)out)[0] = 255; + ((UINT8 *)out)[1] = 255; + ((UINT8 *)out)[2] = 255; } else { - ((UINT8*)out)[0] = (UINT8) v1; - ((UINT8*)out)[1] = (UINT8) v1; - ((UINT8*)out)[2] = (UINT8) v1; + ((UINT8 *)out)[0] = (UINT8)v1; + ((UINT8 *)out)[1] = (UINT8)v1; + ((UINT8 *)out)[2] = (UINT8)v1; } BICUBIC_BODY(UINT8, im->image, 4, 3); - if (v1 <= 0.0) - ((UINT8*)out)[3] = 0; - else if (v1 >= 255.0) - ((UINT8*)out)[3] = 255; - else - ((UINT8*)out)[3] = (UINT8) v1; + if (v1 <= 0.0) { + ((UINT8 *)out)[3] = 0; + } else if (v1 >= 255.0) { + ((UINT8 *)out)[3] = 255; + } else { + ((UINT8 *)out)[3] = (UINT8)v1; + } return 1; } static int -bicubic_filter32RGB(void* out, Imaging im, double xin, double yin) -{ +bicubic_filter32RGB(void *out, Imaging im, double xin, double yin) { int b; BICUBIC_HEAD(UINT8); for (b = 0; b < im->bands; b++) { BICUBIC_BODY(UINT8, im->image, 4, b); - if (v1 <= 0.0) - ((UINT8*)out)[b] = 0; - else if (v1 >= 255.0) - ((UINT8*)out)[b] = 255; - else - ((UINT8*)out)[b] = (UINT8) v1; + if (v1 <= 0.0) { + ((UINT8 *)out)[b] = 0; + } else if (v1 >= 255.0) { + ((UINT8 *)out)[b] = 255; + } else { + ((UINT8 *)out)[b] = (UINT8)v1; + } } return 1; } @@ -674,61 +706,63 @@ bicubic_filter32RGB(void* out, Imaging im, double xin, double yin) #undef BICUBIC_BODY static ImagingTransformFilter -getfilter(Imaging im, int filterid) -{ +getfilter(Imaging im, int filterid) { switch (filterid) { - case IMAGING_TRANSFORM_NEAREST: - if (im->image8) - switch (im->type) { - case IMAGING_TYPE_UINT8: - return nearest_filter8; - case IMAGING_TYPE_SPECIAL: - switch (im->pixelsize) { - case 1: - return nearest_filter8; - case 2: - return nearest_filter16; - case 4: - return nearest_filter32; + case IMAGING_TRANSFORM_NEAREST: + if (im->image8) { + switch (im->type) { + case IMAGING_TYPE_UINT8: + return nearest_filter8; + case IMAGING_TYPE_SPECIAL: + switch (im->pixelsize) { + case 1: + return nearest_filter8; + case 2: + return nearest_filter16; + case 4: + return nearest_filter32; + } + } + } else { + return nearest_filter32; + } + break; + case IMAGING_TRANSFORM_BILINEAR: + if (im->image8) { + return bilinear_filter8; + } else if (im->image32) { + switch (im->type) { + case IMAGING_TYPE_UINT8: + if (im->bands == 2) { + return bilinear_filter32LA; + } else { + return bilinear_filter32RGB; + } + case IMAGING_TYPE_INT32: + return bilinear_filter32I; + case IMAGING_TYPE_FLOAT32: + return bilinear_filter32F; } } - else - return nearest_filter32; - break; - case IMAGING_TRANSFORM_BILINEAR: - if (im->image8) - return bilinear_filter8; - else if (im->image32) { - switch (im->type) { - case IMAGING_TYPE_UINT8: - if (im->bands == 2) - return bilinear_filter32LA; - else - return bilinear_filter32RGB; - case IMAGING_TYPE_INT32: - return bilinear_filter32I; - case IMAGING_TYPE_FLOAT32: - return bilinear_filter32F; + break; + case IMAGING_TRANSFORM_BICUBIC: + if (im->image8) { + return bicubic_filter8; + } else if (im->image32) { + switch (im->type) { + case IMAGING_TYPE_UINT8: + if (im->bands == 2) { + return bicubic_filter32LA; + } else { + return bicubic_filter32RGB; + } + case IMAGING_TYPE_INT32: + return bicubic_filter32I; + case IMAGING_TYPE_FLOAT32: + return bicubic_filter32F; + } } - } - break; - case IMAGING_TRANSFORM_BICUBIC: - if (im->image8) - return bicubic_filter8; - else if (im->image32) { - switch (im->type) { - case IMAGING_TYPE_UINT8: - if (im->bands == 2) - return bicubic_filter32LA; - else - return bicubic_filter32RGB; - case IMAGING_TYPE_INT32: - return bicubic_filter32I; - case IMAGING_TYPE_FLOAT32: - return bicubic_filter32F; - } - } - break; + break; } /* no such filter */ return NULL; @@ -738,10 +772,16 @@ getfilter(Imaging im, int filterid) Imaging ImagingGenericTransform( - Imaging imOut, Imaging imIn, int x0, int y0, int x1, int y1, - ImagingTransformMap transform, void* transform_data, - int filterid, int fill) -{ + Imaging imOut, + Imaging imIn, + int x0, + int y0, + int x1, + int y1, + ImagingTransformMap transform, + void *transform_data, + int filterid, + int fill) { /* slow generic transformation. use ImagingTransformAffine or ImagingScaleAffine where possible. */ @@ -751,32 +791,39 @@ ImagingGenericTransform( double xx, yy; ImagingTransformFilter filter = getfilter(imIn, filterid); - if (!filter) - return (Imaging) ImagingError_ValueError("bad filter number"); + if (!filter) { + return (Imaging)ImagingError_ValueError("bad filter number"); + } - if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0) - return (Imaging) ImagingError_ModeError(); + if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0) { + return (Imaging)ImagingError_ModeError(); + } ImagingCopyPalette(imOut, imIn); ImagingSectionEnter(&cookie); - if (x0 < 0) + if (x0 < 0) { x0 = 0; - if (y0 < 0) + } + if (y0 < 0) { y0 = 0; - if (x1 > imOut->xsize) + } + if (x1 > imOut->xsize) { x1 = imOut->xsize; - if (y1 > imOut->ysize) + } + if (y1 > imOut->ysize) { y1 = imOut->ysize; + } for (y = y0; y < y1; y++) { - out = imOut->image[y] + x0*imOut->pixelsize; + out = imOut->image[y] + x0 * imOut->pixelsize; for (x = x0; x < x1; x++) { - if ( ! transform(&xx, &yy, x-x0, y-y0, transform_data) || - ! filter(out, imIn, xx, yy)) { - if (fill) + if (!transform(&xx, &yy, x - x0, y - y0, transform_data) || + !filter(out, imIn, xx, yy)) { + if (fill) { memset(out, 0, imOut->pixelsize); + } } out += imOut->pixelsize; } @@ -788,10 +835,15 @@ ImagingGenericTransform( } static Imaging -ImagingScaleAffine(Imaging imOut, Imaging imIn, - int x0, int y0, int x1, int y1, - double a[6], int fill) -{ +ImagingScaleAffine( + Imaging imOut, + Imaging imIn, + int x0, + int y0, + int x1, + int y1, + double a[6], + int fill) { /* scale, nearest neighbour resampling */ ImagingSectionCookie cookie; @@ -801,25 +853,30 @@ ImagingScaleAffine(Imaging imOut, Imaging imIn, int xmin, xmax; int *xintab; - if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0) - return (Imaging) ImagingError_ModeError(); + if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0) { + return (Imaging)ImagingError_ModeError(); + } ImagingCopyPalette(imOut, imIn); - if (x0 < 0) + if (x0 < 0) { x0 = 0; - if (y0 < 0) + } + if (y0 < 0) { y0 = 0; - if (x1 > imOut->xsize) + } + if (x1 > imOut->xsize) { x1 = imOut->xsize; - if (y1 > imOut->ysize) + } + if (y1 > imOut->ysize) { y1 = imOut->ysize; + } /* malloc check ok, uses calloc for overflow */ - xintab = (int*) calloc(imOut->xsize, sizeof(int)); + xintab = (int *)calloc(imOut->xsize, sizeof(int)); if (!xintab) { ImagingDelete(imOut); - return (Imaging) ImagingError_MemoryError(); + return (Imaging)ImagingError_MemoryError(); } xo = a[2] + a[0] * 0.5; @@ -831,28 +888,31 @@ ImagingScaleAffine(Imaging imOut, Imaging imIn, /* Pretabulate horizontal pixel positions */ for (x = x0; x < x1; x++) { xin = COORD(xo); - if (xin >= 0 && xin < (int) imIn->xsize) { - xmax = x+1; - if (x < xmin) + if (xin >= 0 && xin < (int)imIn->xsize) { + xmax = x + 1; + if (x < xmin) { xmin = x; + } xintab[x] = xin; } xo += a[0]; } -#define AFFINE_SCALE(pixel, image)\ - for (y = y0; y < y1; y++) {\ - int yi = COORD(yo);\ - pixel *in, *out;\ - out = imOut->image[y];\ - if (fill && x1 > x0)\ - memset(out+x0, 0, (x1-x0)*sizeof(pixel));\ - if (yi >= 0 && yi < imIn->ysize) {\ - in = imIn->image[yi];\ - for (x = xmin; x < xmax; x++)\ - out[x] = in[xintab[x]];\ - }\ - yo += a[4];\ +#define AFFINE_SCALE(pixel, image) \ + for (y = y0; y < y1; y++) { \ + int yi = COORD(yo); \ + pixel *in, *out; \ + out = imOut->image[y]; \ + if (fill && x1 > x0) { \ + memset(out + x0, 0, (x1 - x0) * sizeof(pixel)); \ + } \ + if (yi >= 0 && yi < imIn->ysize) { \ + in = imIn->image[yi]; \ + for (x = xmin; x < xmax; x++) { \ + out[x] = in[xintab[x]]; \ + } \ + } \ + yo += a[4]; \ } ImagingSectionEnter(&cookie); @@ -873,17 +933,23 @@ ImagingScaleAffine(Imaging imOut, Imaging imIn, } static inline int -check_fixed(double a[6], int x, int y) -{ - return (fabs(x*a[0] + y*a[1] + a[2]) < 32768.0 && - fabs(x*a[3] + y*a[4] + a[5]) < 32768.0); +check_fixed(double a[6], int x, int y) { + return ( + fabs(x * a[0] + y * a[1] + a[2]) < 32768.0 && + fabs(x * a[3] + y * a[4] + a[5]) < 32768.0); } static inline Imaging -affine_fixed(Imaging imOut, Imaging imIn, - int x0, int y0, int x1, int y1, - double a[6], int filterid, int fill) -{ +affine_fixed( + Imaging imOut, + Imaging imIn, + int x0, + int y0, + int x1, + int y1, + double a[6], + int filterid, + int fill) { /* affine transform, nearest neighbour resampling, fixed point arithmetics */ @@ -896,47 +962,52 @@ affine_fixed(Imaging imOut, Imaging imIn, ImagingCopyPalette(imOut, imIn); - xsize = (int) imIn->xsize; - ysize = (int) imIn->ysize; + xsize = (int)imIn->xsize; + ysize = (int)imIn->ysize; /* use 16.16 fixed point arithmetics */ #define FIX(v) FLOOR((v)*65536.0 + 0.5) - a0 = FIX(a[0]); a1 = FIX(a[1]); - a3 = FIX(a[3]); a4 = FIX(a[4]); + a0 = FIX(a[0]); + a1 = FIX(a[1]); + a3 = FIX(a[3]); + a4 = FIX(a[4]); a2 = FIX(a[2] + a[0] * 0.5 + a[1] * 0.5); a5 = FIX(a[5] + a[3] * 0.5 + a[4] * 0.5); #undef FIX -#define AFFINE_TRANSFORM_FIXED(pixel, image)\ - for (y = y0; y < y1; y++) {\ - pixel *out;\ - xx = a2;\ - yy = a5;\ - out = imOut->image[y];\ - if (fill && x1 > x0)\ - memset(out+x0, 0, (x1-x0)*sizeof(pixel));\ - for (x = x0; x < x1; x++, out++) {\ - xin = xx >> 16;\ - if (xin >= 0 && xin < xsize) {\ - yin = yy >> 16;\ - if (yin >= 0 && yin < ysize)\ - *out = imIn->image[yin][xin];\ - }\ - xx += a0;\ - yy += a3;\ - }\ - a2 += a1;\ - a5 += a4;\ +#define AFFINE_TRANSFORM_FIXED(pixel, image) \ + for (y = y0; y < y1; y++) { \ + pixel *out; \ + xx = a2; \ + yy = a5; \ + out = imOut->image[y]; \ + if (fill && x1 > x0) { \ + memset(out + x0, 0, (x1 - x0) * sizeof(pixel)); \ + } \ + for (x = x0; x < x1; x++, out++) { \ + xin = xx >> 16; \ + if (xin >= 0 && xin < xsize) { \ + yin = yy >> 16; \ + if (yin >= 0 && yin < ysize) { \ + *out = imIn->image[yin][xin]; \ + } \ + } \ + xx += a0; \ + yy += a3; \ + } \ + a2 += a1; \ + a5 += a4; \ } ImagingSectionEnter(&cookie); - if (imIn->image8) + if (imIn->image8) { AFFINE_TRANSFORM_FIXED(UINT8, image8) - else + } else { AFFINE_TRANSFORM_FIXED(INT32, image32) + } ImagingSectionLeave(&cookie); @@ -946,10 +1017,16 @@ affine_fixed(Imaging imOut, Imaging imIn, } Imaging -ImagingTransformAffine(Imaging imOut, Imaging imIn, - int x0, int y0, int x1, int y1, - double a[6], int filterid, int fill) -{ +ImagingTransformAffine( + Imaging imOut, + Imaging imIn, + int x0, + int y0, + int x1, + int y1, + double a[6], + int filterid, + int fill) { /* affine transform, nearest neighbour resampling, floating point arithmetics*/ @@ -962,10 +1039,7 @@ ImagingTransformAffine(Imaging imOut, Imaging imIn, if (filterid || imIn->type == IMAGING_TYPE_SPECIAL) { return ImagingGenericTransform( - imOut, imIn, - x0, y0, x1, y1, - affine_transform, a, - filterid, fill); + imOut, imIn, x0, y0, x1, y1, affine_transform, a, filterid, fill); } if (a[1] == 0 && a[3] == 0) { @@ -973,24 +1047,30 @@ ImagingTransformAffine(Imaging imOut, Imaging imIn, return ImagingScaleAffine(imOut, imIn, x0, y0, x1, y1, a, fill); } - if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0) - return (Imaging) ImagingError_ModeError(); + if (!imOut || !imIn || strcmp(imIn->mode, imOut->mode) != 0) { + return (Imaging)ImagingError_ModeError(); + } - if (x0 < 0) + if (x0 < 0) { x0 = 0; - if (y0 < 0) + } + if (y0 < 0) { y0 = 0; - if (x1 > imOut->xsize) + } + if (x1 > imOut->xsize) { x1 = imOut->xsize; - if (y1 > imOut->ysize) + } + if (y1 > imOut->ysize) { y1 = imOut->ysize; + } /* translate all four corners to check if they are within the range that can be represented by the fixed point arithmetics */ - if (check_fixed(a, 0, 0) && check_fixed(a, x1-x0, y1-y0) && - check_fixed(a, 0, y1-y0) && check_fixed(a, x1-x0, 0)) + if (check_fixed(a, 0, 0) && check_fixed(a, x1 - x0, y1 - y0) && + check_fixed(a, 0, y1 - y0) && check_fixed(a, x1 - x0, 0)) { return affine_fixed(imOut, imIn, x0, y0, x1, y1, a, filterid, fill); + } /* FIXME: cannot really think of any reasonable case when the following code is used. maybe we should fall back on the slow @@ -998,40 +1078,43 @@ ImagingTransformAffine(Imaging imOut, Imaging imIn, ImagingCopyPalette(imOut, imIn); - xsize = (int) imIn->xsize; - ysize = (int) imIn->ysize; + xsize = (int)imIn->xsize; + ysize = (int)imIn->ysize; xo = a[2] + a[1] * 0.5 + a[0] * 0.5; yo = a[5] + a[4] * 0.5 + a[3] * 0.5; -#define AFFINE_TRANSFORM(pixel, image)\ - for (y = y0; y < y1; y++) {\ - pixel *out;\ - xx = xo;\ - yy = yo;\ - out = imOut->image[y];\ - if (fill && x1 > x0)\ - memset(out+x0, 0, (x1-x0)*sizeof(pixel));\ - for (x = x0; x < x1; x++, out++) {\ - xin = COORD(xx);\ - if (xin >= 0 && xin < xsize) {\ - yin = COORD(yy);\ - if (yin >= 0 && yin < ysize)\ - *out = imIn->image[yin][xin];\ - }\ - xx += a[0];\ - yy += a[3];\ - }\ - xo += a[1];\ - yo += a[4];\ +#define AFFINE_TRANSFORM(pixel, image) \ + for (y = y0; y < y1; y++) { \ + pixel *out; \ + xx = xo; \ + yy = yo; \ + out = imOut->image[y]; \ + if (fill && x1 > x0) { \ + memset(out + x0, 0, (x1 - x0) * sizeof(pixel)); \ + } \ + for (x = x0; x < x1; x++, out++) { \ + xin = COORD(xx); \ + if (xin >= 0 && xin < xsize) { \ + yin = COORD(yy); \ + if (yin >= 0 && yin < ysize) { \ + *out = imIn->image[yin][xin]; \ + } \ + } \ + xx += a[0]; \ + yy += a[3]; \ + } \ + xo += a[1]; \ + yo += a[4]; \ } ImagingSectionEnter(&cookie); - if (imIn->image8) + if (imIn->image8) { AFFINE_TRANSFORM(UINT8, image8) - else + } else { AFFINE_TRANSFORM(INT32, image32) + } ImagingSectionLeave(&cookie); @@ -1041,29 +1124,34 @@ ImagingTransformAffine(Imaging imOut, Imaging imIn, } Imaging -ImagingTransform(Imaging imOut, Imaging imIn, int method, - int x0, int y0, int x1, int y1, - double a[8], int filterid, int fill) -{ +ImagingTransform( + Imaging imOut, + Imaging imIn, + int method, + int x0, + int y0, + int x1, + int y1, + double a[8], + int filterid, + int fill) { ImagingTransformMap transform; - switch(method) { - case IMAGING_TRANSFORM_AFFINE: - return ImagingTransformAffine( - imOut, imIn, x0, y0, x1, y1, a, filterid, fill); - break; - case IMAGING_TRANSFORM_PERSPECTIVE: - transform = perspective_transform; - break; - case IMAGING_TRANSFORM_QUAD: - transform = quad_transform; - break; - default: - return (Imaging) ImagingError_ValueError("bad transform method"); + switch (method) { + case IMAGING_TRANSFORM_AFFINE: + return ImagingTransformAffine( + imOut, imIn, x0, y0, x1, y1, a, filterid, fill); + break; + case IMAGING_TRANSFORM_PERSPECTIVE: + transform = perspective_transform; + break; + case IMAGING_TRANSFORM_QUAD: + transform = quad_transform; + break; + default: + return (Imaging)ImagingError_ValueError("bad transform method"); } return ImagingGenericTransform( - imOut, imIn, - x0, y0, x1, y1, - transform, a, filterid, fill); + imOut, imIn, x0, y0, x1, y1, transform, a, filterid, fill); } diff --git a/src/libImaging/GetBBox.c b/src/libImaging/GetBBox.c index b63888f87..e73153600 100644 --- a/src/libImaging/GetBBox.c +++ b/src/libImaging/GetBBox.c @@ -16,13 +16,10 @@ * See the README file for details on usage and redistribution. */ - #include "Imaging.h" - int -ImagingGetBBox(Imaging im, int bbox[4]) -{ +ImagingGetBBox(Imaging im, int bbox[4]) { /* Get the bounding box for any non-zero data in the image.*/ int x, y; @@ -33,44 +30,57 @@ ImagingGetBBox(Imaging im, int bbox[4]) bbox[1] = -1; bbox[2] = bbox[3] = 0; -#define GETBBOX(image, mask)\ - for (y = 0; y < im->ysize; y++) {\ - has_data = 0;\ - for (x = 0; x < im->xsize; x++)\ - if (im->image[y][x] & mask) {\ - has_data = 1;\ - if (x < bbox[0])\ - bbox[0] = x;\ - if (x >= bbox[2])\ - bbox[2] = x+1;\ - }\ - if (has_data) {\ - if (bbox[1] < 0)\ - bbox[1] = y;\ - bbox[3] = y+1;\ - }\ +#define GETBBOX(image, mask) \ + for (y = 0; y < im->ysize; y++) { \ + has_data = 0; \ + for (x = 0; x < im->xsize; x++) { \ + if (im->image[y][x] & mask) { \ + has_data = 1; \ + if (x < bbox[0]) { \ + bbox[0] = x; \ + } \ + if (x >= bbox[2]) { \ + bbox[2] = x + 1; \ + } \ + } \ + } \ + if (has_data) { \ + if (bbox[1] < 0) { \ + bbox[1] = y; \ + } \ + bbox[3] = y + 1; \ + } \ } if (im->image8) { - GETBBOX(image8, 0xff); + GETBBOX(image8, 0xff); } else { - INT32 mask = 0xffffffff; - if (im->bands == 3) - ((UINT8*) &mask)[3] = 0; - GETBBOX(image32, mask); + INT32 mask = 0xffffffff; + if (im->bands == 3) { + ((UINT8 *)&mask)[3] = 0; + } else if ( + strcmp(im->mode, "RGBa") == 0 || strcmp(im->mode, "RGBA") == 0 || + strcmp(im->mode, "La") == 0 || strcmp(im->mode, "LA") == 0 || + strcmp(im->mode, "PA") == 0) { +#ifdef WORDS_BIGENDIAN + mask = 0x000000ff; +#else + mask = 0xff000000; +#endif + } + GETBBOX(image32, mask); } /* Check that we got a box */ - if (bbox[1] < 0) - return 0; /* no data */ + if (bbox[1] < 0) { + return 0; /* no data */ + } return 1; /* ok */ } - int -ImagingGetProjection(Imaging im, UINT8* xproj, UINT8* yproj) -{ +ImagingGetProjection(Imaging im, UINT8 *xproj, UINT8 *yproj) { /* Get projection arrays for non-zero data in the image.*/ int x, y; @@ -80,138 +90,152 @@ ImagingGetProjection(Imaging im, UINT8* xproj, UINT8* yproj) memset(xproj, 0, im->xsize); memset(yproj, 0, im->ysize); -#define GETPROJ(image, mask)\ - for (y = 0; y < im->ysize; y++) {\ - has_data = 0;\ - for (x = 0; x < im->xsize; x++)\ - if (im->image[y][x] & mask) {\ - has_data = 1;\ - xproj[x] = 1;\ - }\ - if (has_data)\ - yproj[y] = 1;\ +#define GETPROJ(image, mask) \ + for (y = 0; y < im->ysize; y++) { \ + has_data = 0; \ + for (x = 0; x < im->xsize; x++) { \ + if (im->image[y][x] & mask) { \ + has_data = 1; \ + xproj[x] = 1; \ + } \ + } \ + if (has_data) { \ + yproj[y] = 1; \ + } \ } if (im->image8) { - GETPROJ(image8, 0xff); + GETPROJ(image8, 0xff); } else { - INT32 mask = 0xffffffff; - if (im->bands == 3) - ((UINT8*) &mask)[3] = 0; - GETPROJ(image32, mask); + INT32 mask = 0xffffffff; + if (im->bands == 3) { + ((UINT8 *)&mask)[3] = 0; + } + GETPROJ(image32, mask); } return 1; /* ok */ } - int -ImagingGetExtrema(Imaging im, void *extrema) -{ +ImagingGetExtrema(Imaging im, void *extrema) { int x, y; INT32 imin, imax; FLOAT32 fmin, fmax; if (im->bands != 1) { - (void) ImagingError_ModeError(); + (void)ImagingError_ModeError(); return -1; /* mismatch */ } - if (!im->xsize || !im->ysize) + if (!im->xsize || !im->ysize) { return 0; /* zero size */ + } switch (im->type) { - case IMAGING_TYPE_UINT8: - imin = imax = im->image8[0][0]; - for (y = 0; y < im->ysize; y++) { - UINT8* in = im->image8[y]; - for (x = 0; x < im->xsize; x++) { - if (imin > in[x]) - imin = in[x]; - else if (imax < in[x]) - imax = in[x]; + case IMAGING_TYPE_UINT8: + imin = imax = im->image8[0][0]; + for (y = 0; y < im->ysize; y++) { + UINT8 *in = im->image8[y]; + for (x = 0; x < im->xsize; x++) { + if (imin > in[x]) { + imin = in[x]; + } else if (imax < in[x]) { + imax = in[x]; + } + } } - } - ((UINT8*) extrema)[0] = (UINT8) imin; - ((UINT8*) extrema)[1] = (UINT8) imax; - break; - case IMAGING_TYPE_INT32: - imin = imax = im->image32[0][0]; - for (y = 0; y < im->ysize; y++) { - INT32* in = im->image32[y]; - for (x = 0; x < im->xsize; x++) { - if (imin > in[x]) - imin = in[x]; - else if (imax < in[x]) - imax = in[x]; + ((UINT8 *)extrema)[0] = (UINT8)imin; + ((UINT8 *)extrema)[1] = (UINT8)imax; + break; + case IMAGING_TYPE_INT32: + imin = imax = im->image32[0][0]; + for (y = 0; y < im->ysize; y++) { + INT32 *in = im->image32[y]; + for (x = 0; x < im->xsize; x++) { + if (imin > in[x]) { + imin = in[x]; + } else if (imax < in[x]) { + imax = in[x]; + } + } } - } - memcpy(extrema, &imin, sizeof(imin)); - memcpy(((char*)extrema) + sizeof(imin), &imax, sizeof(imax)); - break; - case IMAGING_TYPE_FLOAT32: - fmin = fmax = ((FLOAT32*) im->image32[0])[0]; - for (y = 0; y < im->ysize; y++) { - FLOAT32* in = (FLOAT32*) im->image32[y]; - for (x = 0; x < im->xsize; x++) { - if (fmin > in[x]) - fmin = in[x]; - else if (fmax < in[x]) - fmax = in[x]; + memcpy(extrema, &imin, sizeof(imin)); + memcpy(((char *)extrema) + sizeof(imin), &imax, sizeof(imax)); + break; + case IMAGING_TYPE_FLOAT32: + fmin = fmax = ((FLOAT32 *)im->image32[0])[0]; + for (y = 0; y < im->ysize; y++) { + FLOAT32 *in = (FLOAT32 *)im->image32[y]; + for (x = 0; x < im->xsize; x++) { + if (fmin > in[x]) { + fmin = in[x]; + } else if (fmax < in[x]) { + fmax = in[x]; + } + } } - } - memcpy(extrema, &fmin, sizeof(fmin)); - memcpy(((char*)extrema) + sizeof(fmin), &fmax, sizeof(fmax)); - break; - case IMAGING_TYPE_SPECIAL: - if (strcmp(im->mode, "I;16") == 0) { - UINT16 v; - memcpy(&v, *im->image8, sizeof(v)); - imin = imax = v; - for (y = 0; y < im->ysize; y++) { - for (x = 0; x < im->xsize; x++) { - memcpy(&v, im->image[y] + x * sizeof(v), sizeof(v)); - if (imin > v) - imin = v; - else if (imax < v) - imax = v; - } - } - v = (UINT16) imin; - memcpy(extrema, &v, sizeof(v)); - v = (UINT16) imax; - memcpy(((char*)extrema) + sizeof(v), &v, sizeof(v)); - break; - } - /* FALL THROUGH */ - default: - (void) ImagingError_ModeError(); - return -1; + memcpy(extrema, &fmin, sizeof(fmin)); + memcpy(((char *)extrema) + sizeof(fmin), &fmax, sizeof(fmax)); + break; + case IMAGING_TYPE_SPECIAL: + if (strcmp(im->mode, "I;16") == 0) { + UINT16 v; + UINT8 *pixel = *im->image8; +#ifdef WORDS_BIGENDIAN + v = pixel[0] + (pixel[1] << 8); +#else + memcpy(&v, pixel, sizeof(v)); +#endif + imin = imax = v; + for (y = 0; y < im->ysize; y++) { + for (x = 0; x < im->xsize; x++) { + pixel = (UINT8 *)im->image[y] + x * sizeof(v); +#ifdef WORDS_BIGENDIAN + v = pixel[0] + (pixel[1] << 8); +#else + memcpy(&v, pixel, sizeof(v)); +#endif + if (imin > v) { + imin = v; + } else if (imax < v) { + imax = v; + } + } + } + v = (UINT16)imin; + memcpy(extrema, &v, sizeof(v)); + v = (UINT16)imax; + memcpy(((char *)extrema) + sizeof(v), &v, sizeof(v)); + break; + } + /* FALL THROUGH */ + default: + (void)ImagingError_ModeError(); + return -1; } return 1; /* ok */ } - /* static ImagingColorItem* getcolors8(Imaging im, int maxcolors, int* size);*/ -static ImagingColorItem* getcolors32(Imaging im, int maxcolors, int* size); +static ImagingColorItem * +getcolors32(Imaging im, int maxcolors, int *size); -ImagingColorItem* -ImagingGetColors(Imaging im, int maxcolors, int* size) -{ +ImagingColorItem * +ImagingGetColors(Imaging im, int maxcolors, int *size) { /* FIXME: add support for 8-bit images */ return getcolors32(im, maxcolors, size); } -static ImagingColorItem* -getcolors32(Imaging im, int maxcolors, int* size) -{ +static ImagingColorItem * +getcolors32(Imaging im, int maxcolors, int *size) { unsigned int h; unsigned int i, incr; int colors; INT32 pixel_mask; int x, y; - ImagingColorItem* table; - ImagingColorItem* v; + ImagingColorItem *table; + ImagingColorItem *v; unsigned int code_size; unsigned int code_poly; @@ -222,19 +246,19 @@ getcolors32(Imaging im, int maxcolors, int* size) Python's Unicode property database (written by yours truly) /F */ static int SIZES[] = { - 4,3, 8,3, 16,3, 32,5, 64,3, 128,3, 256,29, 512,17, 1024,9, 2048,5, - 4096,83, 8192,27, 16384,43, 32768,3, 65536,45, 131072,9, 262144,39, - 524288,39, 1048576,9, 2097152,5, 4194304,3, 8388608,33, 16777216,27, - 33554432,9, 67108864,71, 134217728,39, 268435456,9, 536870912,5, - 1073741824,83, 0 - }; + 4, 3, 8, 3, 16, 3, 32, 5, 64, 3, + 128, 3, 256, 29, 512, 17, 1024, 9, 2048, 5, + 4096, 83, 8192, 27, 16384, 43, 32768, 3, 65536, 45, + 131072, 9, 262144, 39, 524288, 39, 1048576, 9, 2097152, 5, + 4194304, 3, 8388608, 33, 16777216, 27, 33554432, 9, 67108864, 71, + 134217728, 39, 268435456, 9, 536870912, 5, 1073741824, 83, 0}; code_size = code_poly = code_mask = 0; for (i = 0; SIZES[i]; i += 2) { if (SIZES[i] > maxcolors) { code_size = SIZES[i]; - code_poly = SIZES[i+1]; + code_poly = SIZES[i + 1]; code_mask = code_size - 1; break; } @@ -243,24 +267,28 @@ getcolors32(Imaging im, int maxcolors, int* size) /* printf("code_size=%d\n", code_size); */ /* printf("code_poly=%d\n", code_poly); */ - if (!code_size) - return ImagingError_MemoryError(); /* just give up */ + if (!code_size) { + return ImagingError_MemoryError(); /* just give up */ + } - if (!im->image32) - return ImagingError_ModeError(); + if (!im->image32) { + return ImagingError_ModeError(); + } table = calloc(code_size + 1, sizeof(ImagingColorItem)); - if (!table) - return ImagingError_MemoryError(); + if (!table) { + return ImagingError_MemoryError(); + } pixel_mask = 0xffffffff; - if (im->bands == 3) - ((UINT8*) &pixel_mask)[3] = 0; + if (im->bands == 3) { + ((UINT8 *)&pixel_mask)[3] = 0; + } colors = 0; for (y = 0; y < im->ysize; y++) { - INT32* p = im->image32[y]; + INT32 *p = im->image32[y]; for (x = 0; x < im->xsize; x++) { INT32 pixel = p[x] & pixel_mask; h = (pixel); /* null hashing */ @@ -268,9 +296,11 @@ getcolors32(Imaging im, int maxcolors, int* size) v = &table[i]; if (!v->count) { /* add to table */ - if (colors++ == maxcolors) + if (colors++ == maxcolors) { goto overflow; - v->x = x; v->y = y; + } + v->x = x; + v->y = y; v->pixel = pixel; v->count = 1; continue; @@ -279,16 +309,19 @@ getcolors32(Imaging im, int maxcolors, int* size) continue; } incr = (h ^ (h >> 3)) & code_mask; - if (!incr) + if (!incr) { incr = code_mask; + } for (;;) { i = (i + incr) & code_mask; v = &table[i]; if (!v->count) { /* add to table */ - if (colors++ == maxcolors) + if (colors++ == maxcolors) { goto overflow; - v->x = x; v->y = y; + } + v->x = x; + v->y = y; v->pixel = pixel; v->count = 1; break; @@ -297,8 +330,9 @@ getcolors32(Imaging im, int maxcolors, int* size) break; } incr = incr << 1; - if (incr > code_mask) + if (incr > code_mask) { incr = incr ^ code_poly; + } } } } @@ -306,10 +340,11 @@ getcolors32(Imaging im, int maxcolors, int* size) overflow: /* pack the table */ - for (x = y = 0; x < (int) code_size; x++) + for (x = y = 0; x < (int)code_size; x++) if (table[x].count) { - if (x != y) + if (x != y) { table[y] = table[x]; + } y++; } table[y].count = 0; /* mark end of table */ diff --git a/src/libImaging/Gif.h b/src/libImaging/Gif.h index 2cb95efd2..a85ce2b6e 100644 --- a/src/libImaging/Gif.h +++ b/src/libImaging/Gif.h @@ -7,17 +7,14 @@ * Copyright (c) Fredrik Lundh 1995-96. */ - /* Max size for a LZW code word. */ -#define GIFBITS 12 - -#define GIFTABLE (1< -#include /* memcpy() */ +#include /* memcpy() */ #include "Gif.h" - -#define NEWLINE(state, context) {\ - state->x = 0;\ - state->y += context->step;\ - while (state->y >= state->ysize)\ - switch (context->interlace) {\ - case 1:\ - context->repeat = state->y = 4;\ - context->interlace = 2;\ - break;\ - case 2:\ - context->step = 4;\ - context->repeat = state->y = 2;\ - context->interlace = 3;\ - break;\ - case 3:\ - context->step = 2;\ - context->repeat = state->y = 1;\ - context->interlace = 0;\ - break;\ - default:\ - return -1;\ - }\ - if (state->y < state->ysize)\ - out = im->image8[state->y + state->yoff] + state->xoff;\ -} - +#define NEWLINE(state, context) \ + { \ + state->x = 0; \ + state->y += context->step; \ + while (state->y >= state->ysize) switch (context->interlace) { \ + case 1: \ + context->repeat = state->y = 4; \ + context->interlace = 2; \ + break; \ + case 2: \ + context->step = 4; \ + context->repeat = state->y = 2; \ + context->interlace = 3; \ + break; \ + case 3: \ + context->step = 2; \ + context->repeat = state->y = 1; \ + context->interlace = 0; \ + break; \ + default: \ + return -1; \ + } \ + if (state->y < state->ysize) { \ + out = im->image8[state->y + state->yoff] + state->xoff; \ + } \ + } int -ImagingGifDecode(Imaging im, ImagingCodecState state, UINT8* buffer, Py_ssize_t bytes) -{ - UINT8* p; - UINT8* out; +ImagingGifDecode(Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes) { + UINT8 *p; + UINT8 *out; int c, i; int thiscode; - GIFDECODERSTATE *context = (GIFDECODERSTATE*) state->context; + GIFDECODERSTATE *context = (GIFDECODERSTATE *)state->context; UINT8 *ptr = buffer; if (!state->state) { + /* Initialise state */ + if (context->bits < 0 || context->bits > 12) { + state->errcode = IMAGING_CODEC_CONFIG; + return -1; + } - /* Initialise state */ - if (context->bits < 0 || context->bits > 12) { - state->errcode = IMAGING_CODEC_CONFIG; - return -1; - } + /* Clear code */ + context->clear = 1 << context->bits; - /* Clear code */ - context->clear = 1 << context->bits; + /* End code */ + context->end = context->clear + 1; - /* End code */ - context->end = context->clear + 1; + /* Interlace */ + if (context->interlace) { + context->interlace = 1; + context->step = context->repeat = 8; + } else { + context->step = 1; + } - /* Interlace */ - if (context->interlace) { - context->interlace = 1; - context->step = context->repeat = 8; - } else - context->step = 1; - - state->state = 1; + state->state = 1; } out = im->image8[state->y + state->yoff] + state->xoff + state->x; for (;;) { + if (state->state == 1) { + /* First free entry in table */ + context->next = context->clear + 2; - if (state->state == 1) { + /* Initial code size */ + context->codesize = context->bits + 1; + context->codemask = (1 << context->codesize) - 1; - /* First free entry in table */ - context->next = context->clear + 2; + /* Buffer pointer. We fill the buffer from right, which + allows us to return all of it in one operation. */ + context->bufferindex = GIFBUFFER; - /* Initial code size */ - context->codesize = context->bits + 1; - context->codemask = (1 << context->codesize) - 1; + state->state = 2; + } - /* Buffer pointer. We fill the buffer from right, which - allows us to return all of it in one operation. */ - context->bufferindex = GIFBUFFER; + if (context->bufferindex < GIFBUFFER) { + /* Return whole buffer in one chunk */ + i = GIFBUFFER - context->bufferindex; + p = &context->buffer[context->bufferindex]; - state->state = 2; - } + context->bufferindex = GIFBUFFER; - if (context->bufferindex < GIFBUFFER) { + } else { + /* Get current symbol */ - /* Return whole buffer in one chunk */ - i = GIFBUFFER - context->bufferindex; - p = &context->buffer[context->bufferindex]; + while (context->bitcount < context->codesize) { + if (context->blocksize > 0) { + /* Read next byte */ + c = *ptr++; + bytes--; - context->bufferindex = GIFBUFFER; + context->blocksize--; - } else { + /* New bits are shifted in from from the left. */ + context->bitbuffer |= (INT32)c << context->bitcount; + context->bitcount += 8; - /* Get current symbol */ + } else { + /* New GIF block */ - while (context->bitcount < context->codesize) { + /* We don't start decoding unless we have a full block */ + if (bytes < 1) { + return ptr - buffer; + } + c = *ptr; + if (bytes < c + 1) { + return ptr - buffer; + } - if (context->blocksize > 0) { + context->blocksize = c; - /* Read next byte */ - c = *ptr++; bytes--; + ptr++; + bytes--; + } + } - context->blocksize--; + /* Extract current symbol from bit buffer. */ + c = (int)context->bitbuffer & context->codemask; - /* New bits are shifted in from from the left. */ - context->bitbuffer |= (INT32) c << context->bitcount; - context->bitcount += 8; + /* Adjust buffer */ + context->bitbuffer >>= context->codesize; + context->bitcount -= context->codesize; - } else { + /* If c is less than "clear", it's a data byte. Otherwise, + it's either clear/end or a code symbol which should be + expanded. */ - /* New GIF block */ + if (c == context->clear) { + if (state->state != 2) { + state->state = 1; + } + continue; + } - /* We don't start decoding unless we have a full block */ - if (bytes < 1) - return ptr - buffer; - c = *ptr; - if (bytes < c+1) - return ptr - buffer; + if (c == context->end) { + break; + } - context->blocksize = c; + i = 1; + p = &context->lastdata; - ptr++; bytes--; + if (state->state == 2) { + /* First valid symbol after clear; use as is */ + if (c > context->clear) { + state->errcode = IMAGING_CODEC_BROKEN; + return -1; + } - } - } + context->lastdata = context->lastcode = c; + state->state = 3; - /* Extract current symbol from bit buffer. */ - c = (int) context->bitbuffer & context->codemask; + } else { + thiscode = c; - /* Adjust buffer */ - context->bitbuffer >>= context->codesize; - context->bitcount -= context->codesize; + if (c > context->next) { + state->errcode = IMAGING_CODEC_BROKEN; + return -1; + } - /* If c is less than "clear", it's a data byte. Otherwise, - it's either clear/end or a code symbol which should be - expanded. */ + if (c == context->next) { + /* c == next is allowed. not sure why. */ - if (c == context->clear) { - if (state->state != 2) - state->state = 1; - continue; - } + if (context->bufferindex <= 0) { + state->errcode = IMAGING_CODEC_BROKEN; + return -1; + } - if (c == context->end) - break; + context->buffer[--context->bufferindex] = context->lastdata; - i = 1; - p = &context->lastdata; + c = context->lastcode; + } - if (state->state == 2) { + while (c >= context->clear) { + /* Copy data string to buffer (beginning from right) */ - /* First valid symbol after clear; use as is */ - if (c > context->clear) { - state->errcode = IMAGING_CODEC_BROKEN; - return -1; - } + if (context->bufferindex <= 0 || c >= GIFTABLE) { + state->errcode = IMAGING_CODEC_BROKEN; + return -1; + } - context->lastdata = context->lastcode = c; - state->state = 3; + context->buffer[--context->bufferindex] = context->data[c]; - } else { + c = context->link[c]; + } - thiscode = c; + context->lastdata = c; - if (c > context->next) { - state->errcode = IMAGING_CODEC_BROKEN; - return -1; - } + if (context->next < GIFTABLE) { + /* We'll only add this symbol if we have room + for it (take advise, Netscape!) */ + context->data[context->next] = c; + context->link[context->next] = context->lastcode; - if (c == context->next) { + if (context->next == context->codemask && + context->codesize < GIFBITS) { + /* Expand code size */ + context->codesize++; + context->codemask = (1 << context->codesize) - 1; + } - /* c == next is allowed. not sure why. */ + context->next++; + } - if (context->bufferindex <= 0) { - state->errcode = IMAGING_CODEC_BROKEN; - return -1; - } + context->lastcode = thiscode; + } + } - context->buffer[--context->bufferindex] = - context->lastdata; + /* Copy the bytes into the image */ + if (state->y >= state->ysize) { + state->errcode = IMAGING_CODEC_OVERRUN; + return -1; + } - c = context->lastcode; + /* To squeeze some extra pixels out of this loop, we test for + some common cases and handle them separately. */ - } + /* FIXME: should we handle the transparency index in here??? */ - while (c >= context->clear) { - - /* Copy data string to buffer (beginning from right) */ - - if (context->bufferindex <= 0 || c >= GIFTABLE) { - state->errcode = IMAGING_CODEC_BROKEN; - return -1; - } - - context->buffer[--context->bufferindex] = - context->data[c]; - - c = context->link[c]; - } - - context->lastdata = c; - - if (context->next < GIFTABLE) { - - /* We'll only add this symbol if we have room - for it (take advise, Netscape!) */ - context->data[context->next] = c; - context->link[context->next] = context->lastcode; - - if (context->next == context->codemask && - context->codesize < GIFBITS) { - - /* Expand code size */ - context->codesize++; - context->codemask = (1 << context->codesize) - 1; - } - - context->next++; - - } - - context->lastcode = thiscode; - - } - } - - /* Copy the bytes into the image */ - if (state->y >= state->ysize) { - state->errcode = IMAGING_CODEC_OVERRUN; - return -1; - } - - /* To squeeze some extra pixels out of this loop, we test for - some common cases and handle them separately. */ - - /* FIXME: should we handle the transparency index in here??? */ - - if (i == 1) { - if (state->x < state->xsize-1) { - /* Single pixel, not at the end of the line. */ - *out++ = p[0]; - state->x++; - continue; - } - } else if (state->x + i <= state->xsize) { - /* This string fits into current line. */ - memcpy(out, p, i); + if (i == 1) { + if (state->x < state->xsize - 1) { + /* Single pixel, not at the end of the line. */ + *out++ = p[0]; + state->x++; + continue; + } + } else if (state->x + i <= state->xsize) { + /* This string fits into current line. */ + memcpy(out, p, i); out += i; - state->x += i; - if (state->x == state->xsize) { - NEWLINE(state, context); - } - continue; - } + state->x += i; + if (state->x == state->xsize) { + NEWLINE(state, context); + } + continue; + } - /* No shortcut, copy pixel by pixel */ - for (c = 0; c < i; c++) { - *out++ = p[c]; - if (++state->x >= state->xsize) { - NEWLINE(state, context); - } - } + /* No shortcut, copy pixel by pixel */ + for (c = 0; c < i; c++) { + *out++ = p[c]; + if (++state->x >= state->xsize) { + NEWLINE(state, context); + } + } } return ptr - buffer; diff --git a/src/libImaging/GifEncode.c b/src/libImaging/GifEncode.c index f211814ed..14fd07cdd 100644 --- a/src/libImaging/GifEncode.c +++ b/src/libImaging/GifEncode.c @@ -5,11 +5,11 @@ * encoder for uncompressed GIF data * * history: - * 97-01-05 fl created (writes uncompressed data) - * 97-08-27 fl fixed off-by-one error in buffer size test - * 98-07-09 fl added interlace write support - * 99-02-07 fl rewritten, now uses a run-length encoding strategy - * 99-02-08 fl improved run-length encoding for long runs + * 97-01-05 fl created (writes uncompressed data) + * 97-08-27 fl fixed off-by-one error in buffer size test + * 98-07-09 fl added interlace write support + * 99-02-07 fl rewritten, now uses a run-length encoding strategy + * 99-02-08 fl improved run-length encoding for long runs * * Copyright (c) Secret Labs AB 1997-99. * Copyright (c) Fredrik Lundh 1997. @@ -35,12 +35,11 @@ enum { INIT, ENCODE, ENCODE_EOF, FLUSH, EXIT }; necessary. */ static inline int -emit(GIFENCODERSTATE *context, int byte) -{ +emit(GIFENCODERSTATE *context, int byte) { /* write a byte to the output buffer */ if (!context->block || context->block->size == 255) { - GIFENCODERBLOCK* block; + GIFENCODERBLOCK *block; /* no room in the current block (or no current block); allocate a new one */ @@ -48,12 +47,14 @@ emit(GIFENCODERSTATE *context, int byte) /* add current block to end of flush queue */ if (context->block) { block = context->flush; - while (block && block->next) + while (block && block->next) { block = block->next; - if (block) + } + if (block) { block->next = context->block; - else + } else { context->flush = context->block; + } } /* get a new block */ @@ -63,15 +64,15 @@ emit(GIFENCODERSTATE *context, int byte) } else { /* malloc check ok, small constant allocation */ block = malloc(sizeof(GIFENCODERBLOCK)); - if (!block) + if (!block) { return 0; + } } block->size = 0; block->next = NULL; context->block = block; - } /* write new byte to block */ @@ -83,238 +84,239 @@ emit(GIFENCODERSTATE *context, int byte) /* write a code word to the current block. this is a macro to make sure it's inlined on all platforms */ -#define EMIT(code) {\ - context->bitbuffer |= ((INT32) (code)) << context->bitcount;\ - context->bitcount += 9;\ - while (context->bitcount >= 8) {\ - if (!emit(context, (UINT8) context->bitbuffer)) {\ - state->errcode = IMAGING_CODEC_MEMORY;\ - return 0;\ - }\ - context->bitbuffer >>= 8;\ - context->bitcount -= 8;\ - }\ -} +#define EMIT(code) \ + { \ + context->bitbuffer |= ((INT32)(code)) << context->bitcount; \ + context->bitcount += 9; \ + while (context->bitcount >= 8) { \ + if (!emit(context, (UINT8)context->bitbuffer)) { \ + state->errcode = IMAGING_CODEC_MEMORY; \ + return 0; \ + } \ + context->bitbuffer >>= 8; \ + context->bitcount -= 8; \ + } \ + } /* write a run. we use a combination of literals and combinations of literals. this can give quite decent compression for images with long stretches of identical pixels. but remember: if you want really good compression, use another file format. */ -#define EMIT_RUN(label) {\ -label:\ - while (context->count > 0) {\ - int run = 2;\ - EMIT(context->last);\ - context->count--;\ - if (state->count++ == LAST_CODE) {\ - EMIT(CLEAR_CODE);\ - state->count = FIRST_CODE;\ - goto label;\ - }\ - while (context->count >= run) {\ - EMIT(state->count - 1);\ - context->count -= run;\ - run++;\ - if (state->count++ == LAST_CODE) {\ - EMIT(CLEAR_CODE);\ - state->count = FIRST_CODE;\ - goto label;\ - }\ - }\ - if (context->count > 1) {\ - EMIT(state->count - 1 - (run - context->count));\ - context->count = 0;\ - if (state->count++ == LAST_CODE) {\ - EMIT(CLEAR_CODE);\ - state->count = FIRST_CODE;\ - }\ - break;\ - }\ - }\ -} +#define EMIT_RUN(label) \ + { \ + label: \ + while (context->count > 0) { \ + int run = 2; \ + EMIT(context->last); \ + context->count--; \ + if (state->count++ == LAST_CODE) { \ + EMIT(CLEAR_CODE); \ + state->count = FIRST_CODE; \ + goto label; \ + } \ + while (context->count >= run) { \ + EMIT(state->count - 1); \ + context->count -= run; \ + run++; \ + if (state->count++ == LAST_CODE) { \ + EMIT(CLEAR_CODE); \ + state->count = FIRST_CODE; \ + goto label; \ + } \ + } \ + if (context->count > 1) { \ + EMIT(state->count - 1 - (run - context->count)); \ + context->count = 0; \ + if (state->count++ == LAST_CODE) { \ + EMIT(CLEAR_CODE); \ + state->count = FIRST_CODE; \ + } \ + break; \ + } \ + } \ + } int -ImagingGifEncode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) -{ - UINT8* ptr; +ImagingGifEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) { + UINT8 *ptr; int this; - GIFENCODERBLOCK* block; - GIFENCODERSTATE *context = (GIFENCODERSTATE*) state->context; + GIFENCODERBLOCK *block; + GIFENCODERSTATE *context = (GIFENCODERSTATE *)state->context; if (!state->state) { + /* place a clear code in the output buffer */ + context->bitbuffer = CLEAR_CODE; + context->bitcount = 9; - /* place a clear code in the output buffer */ - context->bitbuffer = CLEAR_CODE; - context->bitcount = 9; + state->count = FIRST_CODE; - state->count = FIRST_CODE; - - if (context->interlace) { - context->interlace = 1; - context->step = 8; - } else - context->step = 1; + if (context->interlace) { + context->interlace = 1; + context->step = 8; + } else { + context->step = 1; + } context->last = -1; /* sanity check */ - if (state->xsize <= 0 || state->ysize <= 0) + if (state->xsize <= 0 || state->ysize <= 0) { state->state = ENCODE_EOF; - + } } ptr = buf; - for (;;) + for (;;) switch (state->state) { + case INIT: + case ENCODE: - switch (state->state) { + /* identify and store a run of pixels */ - case INIT: - case ENCODE: - - /* identify and store a run of pixels */ - - if (state->x == 0 || state->x >= state->xsize) { - - if (!context->interlace && state->y >= state->ysize) { - state->state = ENCODE_EOF; - break; - } - - if (context->flush) { - state->state = FLUSH; - break; - } - - /* get another line of data */ - state->shuffle( - state->buffer, - (UINT8*) im->image[state->y + state->yoff] + - state->xoff * im->pixelsize, state->xsize - ); - - state->x = 0; - - if (state->state == INIT) { - /* preload the run-length buffer and get going */ - context->last = state->buffer[0]; - context->count = state->x = 1; - state->state = ENCODE; - } - - /* step forward, according to the interlace settings */ - state->y += context->step; - while (context->interlace && state->y >= state->ysize) - switch (context->interlace) { - case 1: - state->y = 4; - context->interlace = 2; + if (state->x == 0 || state->x >= state->xsize) { + if (!context->interlace && state->y >= state->ysize) { + state->state = ENCODE_EOF; break; - case 2: - context->step = 4; - state->y = 2; - context->interlace = 3; - break; - case 3: - context->step = 2; - state->y = 1; - context->interlace = 0; - break; - default: - /* just make sure we don't loop forever */ - context->interlace = 0; } - } + if (context->flush) { + state->state = FLUSH; + break; + } - this = state->buffer[state->x++]; + /* get another line of data */ + state->shuffle( + state->buffer, + (UINT8 *)im->image[state->y + state->yoff] + + state->xoff * im->pixelsize, + state->xsize); - if (this == context->last) - context->count++; - else { - EMIT_RUN(label1); - context->last = this; - context->count = 1; - } - break; + state->x = 0; + if (state->state == INIT) { + /* preload the run-length buffer and get going */ + context->last = state->buffer[0]; + context->count = state->x = 1; + state->state = ENCODE; + } - case ENCODE_EOF: - - /* write the final run */ - EMIT_RUN(label2); - - /* write an end of image marker */ - EMIT(EOF_CODE); - - /* empty the bit buffer */ - while (context->bitcount > 0) { - if (!emit(context, (UINT8) context->bitbuffer)) { - state->errcode = IMAGING_CODEC_MEMORY; - return 0; + /* step forward, according to the interlace settings */ + state->y += context->step; + while (context->interlace && state->y >= state->ysize) + switch (context->interlace) { + case 1: + state->y = 4; + context->interlace = 2; + break; + case 2: + context->step = 4; + state->y = 2; + context->interlace = 3; + break; + case 3: + context->step = 2; + state->y = 1; + context->interlace = 0; + break; + default: + /* just make sure we don't loop forever */ + context->interlace = 0; + } } - context->bitbuffer >>= 8; - context->bitcount -= 8; - } - - /* flush the last block, and exit */ - if (context->block) { - GIFENCODERBLOCK* block; - block = context->flush; - while (block && block->next) - block = block->next; - if (block) - block->next = context->block; - else - context->flush = context->block; - context->block = NULL; - } - - state->state = EXIT; - - /* fall through... */ - - case EXIT: - case FLUSH: - - while (context->flush) { - - /* get a block from the flush queue */ - block = context->flush; - - if (block->size > 0) { - - /* make sure it fits into the output buffer */ - if (bytes < block->size+1) - return ptr - buf; - - ptr[0] = block->size; - memcpy(ptr+1, block->data, block->size); - - ptr += block->size+1; - bytes -= block->size+1; - + /* Potential special case for xsize==1 */ + if (state->x < state->xsize) { + this = state->buffer[state->x++]; + } else { + EMIT_RUN(label0); + break; } - context->flush = block->next; + if (this == context->last) { + context->count++; + } else { + EMIT_RUN(label1); + context->last = this; + context->count = 1; + } + break; - if (context->free) - free(context->free); - context->free = block; + case ENCODE_EOF: - } + /* write the final run */ + EMIT_RUN(label2); - if (state->state == EXIT) { - /* this was the last block! */ - if (context->free) - free(context->free); - state->errcode = IMAGING_CODEC_END; - return ptr - buf; - } + /* write an end of image marker */ + EMIT(EOF_CODE); - state->state = ENCODE; - break; + /* empty the bit buffer */ + while (context->bitcount > 0) { + if (!emit(context, (UINT8)context->bitbuffer)) { + state->errcode = IMAGING_CODEC_MEMORY; + return 0; + } + context->bitbuffer >>= 8; + context->bitcount -= 8; + } + + /* flush the last block, and exit */ + if (context->block) { + GIFENCODERBLOCK *block; + block = context->flush; + while (block && block->next) { + block = block->next; + } + if (block) { + block->next = context->block; + } else { + context->flush = context->block; + } + context->block = NULL; + } + + state->state = EXIT; + + /* fall through... */ + + case EXIT: + case FLUSH: + + while (context->flush) { + /* get a block from the flush queue */ + block = context->flush; + + if (block->size > 0) { + /* make sure it fits into the output buffer */ + if (bytes < block->size + 1) { + return ptr - buf; + } + + ptr[0] = block->size; + memcpy(ptr + 1, block->data, block->size); + + ptr += block->size + 1; + bytes -= block->size + 1; + } + + context->flush = block->next; + + if (context->free) { + free(context->free); + } + context->free = block; + } + + if (state->state == EXIT) { + /* this was the last block! */ + if (context->free) { + free(context->free); + } + state->errcode = IMAGING_CODEC_END; + return ptr - buf; + } + + state->state = ENCODE; + break; } } diff --git a/src/libImaging/HexDecode.c b/src/libImaging/HexDecode.c index 8bd9bf67f..bd16cdbe1 100644 --- a/src/libImaging/HexDecode.c +++ b/src/libImaging/HexDecode.c @@ -5,7 +5,7 @@ * decoder for hex encoded image data * * history: - * 96-05-16 fl Created + * 96-05-16 fl Created * * Copyright (c) Fredrik Lundh 1996. * Copyright (c) Secret Labs AB 1997. @@ -13,55 +13,51 @@ * See the README file for information on usage and redistribution. */ - #include "Imaging.h" -#define HEX(v) ((v >= '0' && v <= '9') ? v - '0' :\ - (v >= 'a' && v <= 'f') ? v - 'a' + 10 :\ - (v >= 'A' && v <= 'F') ? v - 'A' + 10 : -1) +#define HEX(v) \ + ((v >= '0' && v <= '9') ? v - '0' \ + : (v >= 'a' && v <= 'f') ? v - 'a' + 10 \ + : (v >= 'A' && v <= 'F') ? v - 'A' + 10 \ + : -1) int -ImagingHexDecode(Imaging im, ImagingCodecState state, UINT8* buf, Py_ssize_t bytes) -{ - UINT8* ptr; +ImagingHexDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t bytes) { + UINT8 *ptr; int a, b; ptr = buf; for (;;) { + if (bytes < 2) { + return ptr - buf; + } - if (bytes < 2) - return ptr - buf; + a = HEX(ptr[0]); + b = HEX(ptr[1]); - a = HEX(ptr[0]); - b = HEX(ptr[1]); + if (a < 0 || b < 0) { + ptr++; + bytes--; - if (a < 0 || b < 0) { + } else { + ptr += 2; + bytes -= 2; - ptr++; - bytes--; + state->buffer[state->x] = (a << 4) + b; - } else { + if (++state->x >= state->bytes) { + /* Got a full line, unpack it */ + state->shuffle( + (UINT8 *)im->image[state->y], state->buffer, state->xsize); - ptr += 2; - bytes -= 2; + state->x = 0; - state->buffer[state->x] = (a<<4) + b; - - if (++state->x >= state->bytes) { - - /* Got a full line, unpack it */ - state->shuffle((UINT8*) im->image[state->y], state->buffer, - state->xsize); - - state->x = 0; - - if (++state->y >= state->ysize) { - /* End of file (errcode = 0) */ - return -1; - } - } - - } + if (++state->y >= state->ysize) { + /* End of file (errcode = 0) */ + return -1; + } + } + } } } diff --git a/src/libImaging/Histo.c b/src/libImaging/Histo.c index 5c2824ab0..c5a547a64 100644 --- a/src/libImaging/Histo.c +++ b/src/libImaging/Histo.c @@ -16,10 +16,8 @@ * See the README file for information on usage and redistribution. */ - #include "Imaging.h" - /* HISTOGRAM */ /* -------------------------------------------------------------------- * Take a histogram of an image. Returns a histogram object containing @@ -27,148 +25,174 @@ */ void -ImagingHistogramDelete(ImagingHistogram h) -{ - if (h->histogram) - free(h->histogram); - free(h); +ImagingHistogramDelete(ImagingHistogram h) { + if (h) { + if (h->histogram) { + free(h->histogram); + } + free(h); + } } ImagingHistogram -ImagingHistogramNew(Imaging im) -{ +ImagingHistogramNew(Imaging im) { ImagingHistogram h; /* Create histogram descriptor */ h = calloc(1, sizeof(struct ImagingHistogramInstance)); - strncpy(h->mode, im->mode, IMAGING_MODE_LENGTH-1); - h->mode[IMAGING_MODE_LENGTH-1] = 0; + if (!h) { + return (ImagingHistogram)ImagingError_MemoryError(); + } + strncpy(h->mode, im->mode, IMAGING_MODE_LENGTH - 1); + h->mode[IMAGING_MODE_LENGTH - 1] = 0; h->bands = im->bands; h->histogram = calloc(im->pixelsize, 256 * sizeof(long)); + if (!h->histogram) { + free(h); + return (ImagingHistogram)ImagingError_MemoryError(); + } return h; } ImagingHistogram -ImagingGetHistogram(Imaging im, Imaging imMask, void* minmax) -{ +ImagingGetHistogram(Imaging im, Imaging imMask, void *minmax) { ImagingSectionCookie cookie; int x, y, i; ImagingHistogram h; INT32 imin, imax; FLOAT32 fmin, fmax, scale; - if (!im) - return ImagingError_ModeError(); + if (!im) { + return ImagingError_ModeError(); + } if (imMask) { - /* Validate mask */ - if (im->xsize != imMask->xsize || im->ysize != imMask->ysize) - return ImagingError_Mismatch(); - if (strcmp(imMask->mode, "1") != 0 && strcmp(imMask->mode, "L") != 0) - return ImagingError_ValueError("bad transparency mask"); + /* Validate mask */ + if (im->xsize != imMask->xsize || im->ysize != imMask->ysize) { + return ImagingError_Mismatch(); + } + if (strcmp(imMask->mode, "1") != 0 && strcmp(imMask->mode, "L") != 0) { + return ImagingError_ValueError("bad transparency mask"); + } } h = ImagingHistogramNew(im); + if (!h) { + return NULL; + } if (imMask) { - /* mask */ - if (im->image8) { + /* mask */ + if (im->image8) { ImagingSectionEnter(&cookie); - for (y = 0; y < im->ysize; y++) - for (x = 0; x < im->xsize; x++) - if (imMask->image8[y][x] != 0) - h->histogram[im->image8[y][x]]++; + for (y = 0; y < im->ysize; y++) { + for (x = 0; x < im->xsize; x++) { + if (imMask->image8[y][x] != 0) { + h->histogram[im->image8[y][x]]++; + } + } + } ImagingSectionLeave(&cookie); - } else { /* yes, we need the braces. C isn't Python! */ + } else { /* yes, we need the braces. C isn't Python! */ if (im->type != IMAGING_TYPE_UINT8) { ImagingHistogramDelete(h); return ImagingError_ModeError(); } ImagingSectionEnter(&cookie); - for (y = 0; y < im->ysize; y++) { - UINT8* in = (UINT8*) im->image32[y]; - for (x = 0; x < im->xsize; x++) - if (imMask->image8[y][x] != 0) { - h->histogram[(*in++)]++; - h->histogram[(*in++)+256]++; - h->histogram[(*in++)+512]++; - h->histogram[(*in++)+768]++; - } else - in += 4; - } - ImagingSectionLeave(&cookie); - } - } else { - /* mask not given; process pixels in image */ - if (im->image8) { - ImagingSectionEnter(&cookie); - for (y = 0; y < im->ysize; y++) - for (x = 0; x < im->xsize; x++) - h->histogram[im->image8[y][x]]++; - ImagingSectionLeave(&cookie); - } else { - switch (im->type) { - case IMAGING_TYPE_UINT8: - ImagingSectionEnter(&cookie); - for (y = 0; y < im->ysize; y++) { - UINT8* in = (UINT8*) im->image[y]; - for (x = 0; x < im->xsize; x++) { + for (y = 0; y < im->ysize; y++) { + UINT8 *in = (UINT8 *)im->image32[y]; + for (x = 0; x < im->xsize; x++) { + if (imMask->image8[y][x] != 0) { h->histogram[(*in++)]++; - h->histogram[(*in++)+256]++; - h->histogram[(*in++)+512]++; - h->histogram[(*in++)+768]++; + h->histogram[(*in++) + 256]++; + h->histogram[(*in++) + 512]++; + h->histogram[(*in++) + 768]++; + } else { + in += 4; } } - ImagingSectionLeave(&cookie); - break; - case IMAGING_TYPE_INT32: - if (!minmax) { - ImagingHistogramDelete(h); - return ImagingError_ValueError("min/max not given"); + } + ImagingSectionLeave(&cookie); + } + } else { + /* mask not given; process pixels in image */ + if (im->image8) { + ImagingSectionEnter(&cookie); + for (y = 0; y < im->ysize; y++) { + for (x = 0; x < im->xsize; x++) { + h->histogram[im->image8[y][x]]++; } - if (!im->xsize || !im->ysize) - break; - memcpy(&imin, minmax, sizeof(imin)); - memcpy(&imax, ((char*)minmax) + sizeof(imin), sizeof(imax)); - if (imin >= imax) - break; - ImagingSectionEnter(&cookie); - scale = 255.0F / (imax - imin); - for (y = 0; y < im->ysize; y++) { - INT32* in = im->image32[y]; - for (x = 0; x < im->xsize; x++) { - i = (int) (((*in++)-imin)*scale); - if (i >= 0 && i < 256) - h->histogram[i]++; + } + ImagingSectionLeave(&cookie); + } else { + switch (im->type) { + case IMAGING_TYPE_UINT8: + ImagingSectionEnter(&cookie); + for (y = 0; y < im->ysize; y++) { + UINT8 *in = (UINT8 *)im->image[y]; + for (x = 0; x < im->xsize; x++) { + h->histogram[(*in++)]++; + h->histogram[(*in++) + 256]++; + h->histogram[(*in++) + 512]++; + h->histogram[(*in++) + 768]++; + } } - } - ImagingSectionLeave(&cookie); - break; - case IMAGING_TYPE_FLOAT32: - if (!minmax) { - ImagingHistogramDelete(h); - return ImagingError_ValueError("min/max not given"); - } - if (!im->xsize || !im->ysize) + ImagingSectionLeave(&cookie); break; - memcpy(&fmin, minmax, sizeof(fmin)); - memcpy(&fmax, ((char*)minmax) + sizeof(fmin), sizeof(fmax)); - if (fmin >= fmax) - break; - ImagingSectionEnter(&cookie); - scale = 255.0F / (fmax - fmin); - for (y = 0; y < im->ysize; y++) { - FLOAT32* in = (FLOAT32*) im->image32[y]; - for (x = 0; x < im->xsize; x++) { - i = (int) (((*in++)-fmin)*scale); - if (i >= 0 && i < 256) - h->histogram[i]++; + case IMAGING_TYPE_INT32: + if (!minmax) { + ImagingHistogramDelete(h); + return ImagingError_ValueError("min/max not given"); } - } - ImagingSectionLeave(&cookie); - break; + if (!im->xsize || !im->ysize) { + break; + } + memcpy(&imin, minmax, sizeof(imin)); + memcpy(&imax, ((char *)minmax) + sizeof(imin), sizeof(imax)); + if (imin >= imax) { + break; + } + ImagingSectionEnter(&cookie); + scale = 255.0F / (imax - imin); + for (y = 0; y < im->ysize; y++) { + INT32 *in = im->image32[y]; + for (x = 0; x < im->xsize; x++) { + i = (int)(((*in++) - imin) * scale); + if (i >= 0 && i < 256) { + h->histogram[i]++; + } + } + } + ImagingSectionLeave(&cookie); + break; + case IMAGING_TYPE_FLOAT32: + if (!minmax) { + ImagingHistogramDelete(h); + return ImagingError_ValueError("min/max not given"); + } + if (!im->xsize || !im->ysize) { + break; + } + memcpy(&fmin, minmax, sizeof(fmin)); + memcpy(&fmax, ((char *)minmax) + sizeof(fmin), sizeof(fmax)); + if (fmin >= fmax) { + break; + } + ImagingSectionEnter(&cookie); + scale = 255.0F / (fmax - fmin); + for (y = 0; y < im->ysize; y++) { + FLOAT32 *in = (FLOAT32 *)im->image32[y]; + for (x = 0; x < im->xsize; x++) { + i = (int)(((*in++) - fmin) * scale); + if (i >= 0 && i < 256) { + h->histogram[i]++; + } + } + } + ImagingSectionLeave(&cookie); + break; } } } diff --git a/src/libImaging/ImDib.h b/src/libImaging/ImDib.h index e5a2cc0f6..91ff3f322 100644 --- a/src/libImaging/ImDib.h +++ b/src/libImaging/ImDib.h @@ -35,20 +35,27 @@ struct ImagingDIBInstance { ImagingShuffler unpack; }; -typedef struct ImagingDIBInstance* ImagingDIB; +typedef struct ImagingDIBInstance *ImagingDIB; -extern char* ImagingGetModeDIB(int size_out[2]); +extern char * +ImagingGetModeDIB(int size_out[2]); -extern ImagingDIB ImagingNewDIB(const char *mode, int xsize, int ysize); +extern ImagingDIB +ImagingNewDIB(const char *mode, int xsize, int ysize); -extern void ImagingDeleteDIB(ImagingDIB im); +extern void +ImagingDeleteDIB(ImagingDIB im); -extern void ImagingDrawDIB(ImagingDIB dib, void *dc, int dst[4], int src[4]); -extern void ImagingExposeDIB(ImagingDIB dib, void *dc); +extern void +ImagingDrawDIB(ImagingDIB dib, void *dc, int dst[4], int src[4]); +extern void +ImagingExposeDIB(ImagingDIB dib, void *dc); -extern int ImagingQueryPaletteDIB(ImagingDIB dib, void *dc); +extern int +ImagingQueryPaletteDIB(ImagingDIB dib, void *dc); -extern void ImagingPasteDIB(ImagingDIB dib, Imaging im, int xy[4]); +extern void +ImagingPasteDIB(ImagingDIB dib, Imaging im, int xy[4]); #if defined(__cplusplus) } diff --git a/src/libImaging/ImPlatform.h b/src/libImaging/ImPlatform.h index b2d4db785..9a2060edf 100644 --- a/src/libImaging/ImPlatform.h +++ b/src/libImaging/ImPlatform.h @@ -10,7 +10,8 @@ #include "Python.h" /* Workaround issue #2479 */ -#if PY_VERSION_HEX < 0x03070000 && defined(PySlice_GetIndicesEx) && !defined(PYPY_VERSION) +#if PY_VERSION_HEX < 0x03070000 && defined(PySlice_GetIndicesEx) && \ + !defined(PYPY_VERSION) #undef PySlice_GetIndicesEx #endif @@ -39,48 +40,46 @@ /* For System that are not Windows, we'll need to define these. */ #if SIZEOF_SHORT == 2 -#define INT16 short +#define INT16 short #elif SIZEOF_INT == 2 -#define INT16 int +#define INT16 int #else -#define INT16 short /* most things works just fine anyway... */ +#define INT16 short /* most things works just fine anyway... */ #endif #if SIZEOF_SHORT == 4 -#define INT32 short +#define INT32 short #elif SIZEOF_INT == 4 -#define INT32 int +#define INT32 int #elif SIZEOF_LONG == 4 -#define INT32 long +#define INT32 long #else #error Cannot find required 32-bit integer type #endif #if SIZEOF_LONG == 8 -#define INT64 long +#define INT64 long #elif SIZEOF_LONG_LONG == 8 -#define INT64 long +#define INT64 long #endif -#define INT8 signed char -#define UINT8 unsigned char +#define INT8 signed char +#define UINT8 unsigned char -#define UINT16 unsigned INT16 -#define UINT32 unsigned INT32 +#define UINT16 unsigned INT16 +#define UINT32 unsigned INT32 #endif /* assume IEEE; tweak if necessary (patches are welcome) */ -#define FLOAT16 UINT16 -#define FLOAT32 float -#define FLOAT64 double +#define FLOAT16 UINT16 +#define FLOAT32 float +#define FLOAT64 double #ifdef _MSC_VER -typedef signed __int64 int64_t; +typedef signed __int64 int64_t; #endif #ifdef __GNUC__ - #define GCC_VERSION (__GNUC__ * 10000 \ - + __GNUC_MINOR__ * 100 \ - + __GNUC_PATCHLEVEL__) +#define GCC_VERSION (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) #endif diff --git a/src/libImaging/Imaging.h b/src/libImaging/Imaging.h index 9023e4b06..ea000d3a1 100644 --- a/src/libImaging/Imaging.h +++ b/src/libImaging/Imaging.h @@ -10,20 +10,16 @@ * See the README file for information on usage and redistribution. */ - #include "ImPlatform.h" - #if defined(__cplusplus) extern "C" { #endif - #ifndef M_PI -#define M_PI 3.1415926535897932384626433832795 +#define M_PI 3.1415926535897932384626433832795 #endif - /* -------------------------------------------------------------------- */ /* @@ -57,12 +53,12 @@ extern "C" { /* Handles */ -typedef struct ImagingMemoryInstance* Imaging; +typedef struct ImagingMemoryInstance *Imaging; -typedef struct ImagingAccessInstance* ImagingAccess; -typedef struct ImagingHistogramInstance* ImagingHistogram; -typedef struct ImagingOutlineInstance* ImagingOutline; -typedef struct ImagingPaletteInstance* ImagingPalette; +typedef struct ImagingAccessInstance *ImagingAccess; +typedef struct ImagingHistogramInstance *ImagingHistogram; +typedef struct ImagingOutlineInstance *ImagingOutline; +typedef struct ImagingPaletteInstance *ImagingPalette; /* handle magics (used with PyCObject). */ #define IMAGING_MAGIC "PIL Imaging" @@ -73,7 +69,8 @@ typedef struct ImagingPaletteInstance* ImagingPalette; #define IMAGING_TYPE_FLOAT32 2 #define IMAGING_TYPE_SPECIAL 3 /* check mode for details */ -#define IMAGING_MODE_LENGTH 6+1 /* Band names ("1", "L", "P", "RGB", "RGBA", "CMYK", "YCbCr", "BGR;xy") */ +#define IMAGING_MODE_LENGTH \ + 6 + 1 /* Band names ("1", "L", "P", "RGB", "RGBA", "CMYK", "YCbCr", "BGR;xy") */ typedef struct { char *ptr; @@ -81,13 +78,13 @@ typedef struct { } ImagingMemoryBlock; struct ImagingMemoryInstance { - /* Format */ - char mode[IMAGING_MODE_LENGTH]; /* Band names ("1", "L", "P", "RGB", "RGBA", "CMYK", "YCbCr", "BGR;xy") */ - int type; /* Data type (IMAGING_TYPE_*) */ - int depth; /* Depth (ignored in this version) */ - int bands; /* Number of bands (1, 2, 3, or 4) */ - int xsize; /* Image dimension. */ + char mode[IMAGING_MODE_LENGTH]; /* Band names ("1", "L", "P", "RGB", "RGBA", "CMYK", + "YCbCr", "BGR;xy") */ + int type; /* Data type (IMAGING_TYPE_*) */ + int depth; /* Depth (ignored in this version) */ + int bands; /* Number of bands (1, 2, 3, or 4) */ + int xsize; /* Image dimension. */ int ysize; int readonly; @@ -95,147 +92,165 @@ struct ImagingMemoryInstance { ImagingPalette palette; /* Data pointers */ - UINT8 **image8; /* Set for 8-bit images (pixelsize=1). */ - INT32 **image32; /* Set for 32-bit images (pixelsize=4). */ + UINT8 **image8; /* Set for 8-bit images (pixelsize=1). */ + INT32 **image32; /* Set for 32-bit images (pixelsize=4). */ /* Internals */ - char **image; /* Actual raster data. */ - char *block; /* Set if data is allocated in a single block. */ - ImagingMemoryBlock *blocks; /* Memory blocks for pixel storage */ + char **image; /* Actual raster data. */ + char *block; /* Set if data is allocated in a single block. */ + ImagingMemoryBlock *blocks; /* Memory blocks for pixel storage */ - int pixelsize; /* Size of a pixel, in bytes (1, 2 or 4) */ - int linesize; /* Size of a line, in bytes (xsize * pixelsize) */ + int pixelsize; /* Size of a pixel, in bytes (1, 2 or 4) */ + int linesize; /* Size of a line, in bytes (xsize * pixelsize) */ /* Virtual methods */ void (*destroy)(Imaging im); }; +#define IMAGING_PIXEL_1(im, x, y) ((im)->image8[(y)][(x)]) +#define IMAGING_PIXEL_L(im, x, y) ((im)->image8[(y)][(x)]) +#define IMAGING_PIXEL_LA(im, x, y) ((im)->image[(y)][(x)*4]) +#define IMAGING_PIXEL_P(im, x, y) ((im)->image8[(y)][(x)]) +#define IMAGING_PIXEL_PA(im, x, y) ((im)->image[(y)][(x)*4]) +#define IMAGING_PIXEL_I(im, x, y) ((im)->image32[(y)][(x)]) +#define IMAGING_PIXEL_F(im, x, y) (((FLOAT32 *)(im)->image32[y])[x]) +#define IMAGING_PIXEL_RGB(im, x, y) ((im)->image[(y)][(x)*4]) +#define IMAGING_PIXEL_RGBA(im, x, y) ((im)->image[(y)][(x)*4]) +#define IMAGING_PIXEL_CMYK(im, x, y) ((im)->image[(y)][(x)*4]) +#define IMAGING_PIXEL_YCbCr(im, x, y) ((im)->image[(y)][(x)*4]) -#define IMAGING_PIXEL_1(im,x,y) ((im)->image8[(y)][(x)]) -#define IMAGING_PIXEL_L(im,x,y) ((im)->image8[(y)][(x)]) -#define IMAGING_PIXEL_LA(im,x,y) ((im)->image[(y)][(x)*4]) -#define IMAGING_PIXEL_P(im,x,y) ((im)->image8[(y)][(x)]) -#define IMAGING_PIXEL_PA(im,x,y) ((im)->image[(y)][(x)*4]) -#define IMAGING_PIXEL_I(im,x,y) ((im)->image32[(y)][(x)]) -#define IMAGING_PIXEL_F(im,x,y) (((FLOAT32*)(im)->image32[y])[x]) -#define IMAGING_PIXEL_RGB(im,x,y) ((im)->image[(y)][(x)*4]) -#define IMAGING_PIXEL_RGBA(im,x,y) ((im)->image[(y)][(x)*4]) -#define IMAGING_PIXEL_CMYK(im,x,y) ((im)->image[(y)][(x)*4]) -#define IMAGING_PIXEL_YCbCr(im,x,y) ((im)->image[(y)][(x)*4]) - -#define IMAGING_PIXEL_UINT8(im,x,y) ((im)->image8[(y)][(x)]) -#define IMAGING_PIXEL_INT32(im,x,y) ((im)->image32[(y)][(x)]) -#define IMAGING_PIXEL_FLOAT32(im,x,y) (((FLOAT32*)(im)->image32[y])[x]) +#define IMAGING_PIXEL_UINT8(im, x, y) ((im)->image8[(y)][(x)]) +#define IMAGING_PIXEL_INT32(im, x, y) ((im)->image32[(y)][(x)]) +#define IMAGING_PIXEL_FLOAT32(im, x, y) (((FLOAT32 *)(im)->image32[y])[x]) struct ImagingAccessInstance { - const char* mode; - void* (*line)(Imaging im, int x, int y); - void (*get_pixel)(Imaging im, int x, int y, void* pixel); - void (*put_pixel)(Imaging im, int x, int y, const void* pixel); + const char *mode; + void *(*line)(Imaging im, int x, int y); + void (*get_pixel)(Imaging im, int x, int y, void *pixel); + void (*put_pixel)(Imaging im, int x, int y, const void *pixel); }; struct ImagingHistogramInstance { - /* Format */ - char mode[IMAGING_MODE_LENGTH]; /* Band names (of corresponding source image) */ - int bands; /* Number of bands (1, 3, or 4) */ + char mode[IMAGING_MODE_LENGTH]; /* Band names (of corresponding source image) */ + int bands; /* Number of bands (1, 3, or 4) */ /* Data */ - long *histogram; /* Histogram (bands*256 longs) */ - + long *histogram; /* Histogram (bands*256 longs) */ }; - struct ImagingPaletteInstance { - /* Format */ - char mode[IMAGING_MODE_LENGTH]; /* Band names */ + char mode[IMAGING_MODE_LENGTH]; /* Band names */ /* Data */ - UINT8 palette[1024];/* Palette data (same format as image data) */ - - INT16* cache; /* Palette cache (used for predefined palettes) */ - int keep_cache; /* This palette will be reused; keep cache */ + UINT8 palette[1024]; /* Palette data (same format as image data) */ + INT16 *cache; /* Palette cache (used for predefined palettes) */ + int keep_cache; /* This palette will be reused; keep cache */ }; typedef struct ImagingMemoryArena { - int alignment; /* Alignment in memory of each line of an image */ - int block_size; /* Preferred block size, bytes */ - int blocks_max; /* Maximum number of cached blocks */ - int blocks_cached; /* Current number of blocks not associated with images */ + int alignment; /* Alignment in memory of each line of an image */ + int block_size; /* Preferred block size, bytes */ + int blocks_max; /* Maximum number of cached blocks */ + int blocks_cached; /* Current number of blocks not associated with images */ ImagingMemoryBlock *blocks_pool; - int stats_new_count; /* Number of new allocated images */ - int stats_allocated_blocks; /* Number of allocated blocks */ - int stats_reused_blocks; /* Number of blocks which were retrieved from a pool */ - int stats_reallocated_blocks; /* Number of blocks which were actually reallocated after retrieving */ - int stats_freed_blocks; /* Number of freed blocks */ -} *ImagingMemoryArena; - + int stats_new_count; /* Number of new allocated images */ + int stats_allocated_blocks; /* Number of allocated blocks */ + int stats_reused_blocks; /* Number of blocks which were retrieved from a pool */ + int stats_reallocated_blocks; /* Number of blocks which were actually reallocated + after retrieving */ + int stats_freed_blocks; /* Number of freed blocks */ +} * ImagingMemoryArena; /* Objects */ /* ------- */ extern struct ImagingMemoryArena ImagingDefaultArena; -extern int ImagingMemorySetBlocksMax(ImagingMemoryArena arena, int blocks_max); -extern void ImagingMemoryClearCache(ImagingMemoryArena arena, int new_size); +extern int +ImagingMemorySetBlocksMax(ImagingMemoryArena arena, int blocks_max); +extern void +ImagingMemoryClearCache(ImagingMemoryArena arena, int new_size); -extern Imaging ImagingNew(const char* mode, int xsize, int ysize); -extern Imaging ImagingNewDirty(const char* mode, int xsize, int ysize); -extern Imaging ImagingNew2Dirty(const char* mode, Imaging imOut, Imaging imIn); -extern void ImagingDelete(Imaging im); +extern Imaging +ImagingNew(const char *mode, int xsize, int ysize); +extern Imaging +ImagingNewDirty(const char *mode, int xsize, int ysize); +extern Imaging +ImagingNew2Dirty(const char *mode, Imaging imOut, Imaging imIn); +extern void +ImagingDelete(Imaging im); -extern Imaging ImagingNewBlock(const char* mode, int xsize, int ysize); -extern Imaging ImagingNewMap(const char* filename, int readonly, - const char* mode, int xsize, int ysize); +extern Imaging +ImagingNewBlock(const char *mode, int xsize, int ysize); -extern Imaging ImagingNewPrologue(const char *mode, - int xsize, int ysize); -extern Imaging ImagingNewPrologueSubtype(const char *mode, - int xsize, int ysize, - int structure_size); +extern Imaging +ImagingNewPrologue(const char *mode, int xsize, int ysize); +extern Imaging +ImagingNewPrologueSubtype(const char *mode, int xsize, int ysize, int structure_size); -extern void ImagingCopyPalette(Imaging destination, Imaging source); +extern void +ImagingCopyPalette(Imaging destination, Imaging source); -extern void ImagingHistogramDelete(ImagingHistogram histogram); +extern void +ImagingHistogramDelete(ImagingHistogram histogram); -extern void ImagingAccessInit(void); -extern ImagingAccess ImagingAccessNew(Imaging im); -extern void _ImagingAccessDelete(Imaging im, ImagingAccess access); +extern void +ImagingAccessInit(void); +extern ImagingAccess +ImagingAccessNew(Imaging im); +extern void +_ImagingAccessDelete(Imaging im, ImagingAccess access); #define ImagingAccessDelete(im, access) /* nop, for now */ -extern ImagingPalette ImagingPaletteNew(const char *mode); -extern ImagingPalette ImagingPaletteNewBrowser(void); -extern ImagingPalette ImagingPaletteDuplicate(ImagingPalette palette); -extern void ImagingPaletteDelete(ImagingPalette palette); +extern ImagingPalette +ImagingPaletteNew(const char *mode); +extern ImagingPalette +ImagingPaletteNewBrowser(void); +extern ImagingPalette +ImagingPaletteDuplicate(ImagingPalette palette); +extern void +ImagingPaletteDelete(ImagingPalette palette); -extern int ImagingPaletteCachePrepare(ImagingPalette palette); -extern void ImagingPaletteCacheUpdate(ImagingPalette palette, - int r, int g, int b); -extern void ImagingPaletteCacheDelete(ImagingPalette palette); +extern int +ImagingPaletteCachePrepare(ImagingPalette palette); +extern void +ImagingPaletteCacheUpdate(ImagingPalette palette, int r, int g, int b); +extern void +ImagingPaletteCacheDelete(ImagingPalette palette); -#define ImagingPaletteCache(p, r, g, b)\ - p->cache[(r>>2) + (g>>2)*64 + (b>>2)*64*64] +#define ImagingPaletteCache(p, r, g, b) \ + p->cache[(r >> 2) + (g >> 2) * 64 + (b >> 2) * 64 * 64] -extern Imaging ImagingQuantize(Imaging im, int colours, int mode, int kmeans); +extern Imaging +ImagingQuantize(Imaging im, int colours, int mode, int kmeans); /* Threading */ /* --------- */ -typedef void* ImagingSectionCookie; +typedef void *ImagingSectionCookie; -extern void ImagingSectionEnter(ImagingSectionCookie* cookie); -extern void ImagingSectionLeave(ImagingSectionCookie* cookie); +extern void +ImagingSectionEnter(ImagingSectionCookie *cookie); +extern void +ImagingSectionLeave(ImagingSectionCookie *cookie); /* Exceptions */ /* ---------- */ -extern void* ImagingError_IOError(void); -extern void* ImagingError_MemoryError(void); -extern void* ImagingError_ModeError(void); /* maps to ValueError by default */ -extern void* ImagingError_Mismatch(void); /* maps to ValueError by default */ -extern void* ImagingError_ValueError(const char* message); -extern void ImagingError_Clear(void); +extern void * +ImagingError_OSError(void); +extern void * +ImagingError_MemoryError(void); +extern void * +ImagingError_ModeError(void); /* maps to ValueError by default */ +extern void * +ImagingError_Mismatch(void); /* maps to ValueError by default */ +extern void * +ImagingError_ValueError(const char *message); +extern void +ImagingError_Clear(void); /* Transform callbacks */ /* ------------------- */ @@ -245,7 +260,6 @@ extern void ImagingError_Clear(void); #define IMAGING_TRANSFORM_PERSPECTIVE 2 #define IMAGING_TRANSFORM_QUAD 3 - /* standard filters */ #define IMAGING_TRANSFORM_NEAREST 0 #define IMAGING_TRANSFORM_BOX 4 @@ -254,257 +268,391 @@ extern void ImagingError_Clear(void); #define IMAGING_TRANSFORM_BICUBIC 3 #define IMAGING_TRANSFORM_LANCZOS 1 -typedef int (*ImagingTransformMap)(double* X, double* Y, - int x, int y, void* data); -typedef int (*ImagingTransformFilter)(void* out, Imaging im, - double x, double y); +typedef int (*ImagingTransformMap)(double *X, double *Y, int x, int y, void *data); +typedef int (*ImagingTransformFilter)(void *out, Imaging im, double x, double y); /* Image Manipulation Methods */ /* -------------------------- */ -extern Imaging ImagingAlphaComposite(Imaging imIn1, Imaging imIn2); -extern Imaging ImagingBlend(Imaging imIn1, Imaging imIn2, float alpha); -extern Imaging ImagingCopy(Imaging im); -extern Imaging ImagingConvert(Imaging im, const char* mode, ImagingPalette palette, int dither); -extern Imaging ImagingConvertInPlace(Imaging im, const char* mode); -extern Imaging ImagingConvertMatrix(Imaging im, const char *mode, float m[]); -extern Imaging ImagingConvertTransparent(Imaging im, const char *mode, int r, int g, int b); -extern Imaging ImagingCrop(Imaging im, int x0, int y0, int x1, int y1); -extern Imaging ImagingExpand(Imaging im, int x, int y, int mode); -extern Imaging ImagingFill(Imaging im, const void* ink); -extern int ImagingFill2( - Imaging into, const void* ink, Imaging mask, - int x0, int y0, int x1, int y1); -extern Imaging ImagingFillBand(Imaging im, int band, int color); -extern Imaging ImagingFillLinearGradient(const char* mode); -extern Imaging ImagingFillRadialGradient(const char* mode); -extern Imaging ImagingFilter( - Imaging im, int xsize, int ysize, const FLOAT32* kernel, - FLOAT32 offset); -extern Imaging ImagingFlipLeftRight(Imaging imOut, Imaging imIn); -extern Imaging ImagingFlipTopBottom(Imaging imOut, Imaging imIn); -extern Imaging ImagingGaussianBlur(Imaging imOut, Imaging imIn, float radius, - int passes); -extern Imaging ImagingGetBand(Imaging im, int band); -extern Imaging ImagingMerge(const char* mode, Imaging bands[4]); -extern int ImagingSplit(Imaging im, Imaging bands[4]); -extern int ImagingGetBBox(Imaging im, int bbox[4]); -typedef struct { int x, y; INT32 count; INT32 pixel; } ImagingColorItem; -extern ImagingColorItem* ImagingGetColors(Imaging im, int maxcolors, - int *colors); -extern int ImagingGetExtrema(Imaging im, void *extrema); -extern int ImagingGetProjection(Imaging im, UINT8* xproj, UINT8* yproj); -extern ImagingHistogram ImagingGetHistogram( - Imaging im, Imaging mask, void *extrema); -extern Imaging ImagingModeFilter(Imaging im, int size); -extern Imaging ImagingNegative(Imaging im); -extern Imaging ImagingOffset(Imaging im, int xoffset, int yoffset); -extern int ImagingPaste( - Imaging into, Imaging im, Imaging mask, - int x0, int y0, int x1, int y1); -extern Imaging ImagingPoint( - Imaging im, const char* tablemode, const void* table); -extern Imaging ImagingPointTransform( - Imaging imIn, double scale, double offset); -extern Imaging ImagingPutBand(Imaging im, Imaging imIn, int band); -extern Imaging ImagingRankFilter(Imaging im, int size, int rank); -extern Imaging ImagingRotate90(Imaging imOut, Imaging imIn); -extern Imaging ImagingRotate180(Imaging imOut, Imaging imIn); -extern Imaging ImagingRotate270(Imaging imOut, Imaging imIn); -extern Imaging ImagingTranspose(Imaging imOut, Imaging imIn); -extern Imaging ImagingTransverse(Imaging imOut, Imaging imIn); -extern Imaging ImagingResample(Imaging imIn, int xsize, int ysize, int filter, float box[4]); -extern Imaging ImagingReduce(Imaging imIn, int xscale, int yscale, int box[4]); -extern Imaging ImagingTransform( - Imaging imOut, Imaging imIn, int method, int x0, int y0, int x1, int y1, - double *a, int filter, int fill); -extern Imaging ImagingUnsharpMask( - Imaging imOut, Imaging im, float radius, int percent, int threshold); -extern Imaging ImagingBoxBlur(Imaging imOut, Imaging imIn, float radius, int n); -extern Imaging ImagingColorLUT3D_linear(Imaging imOut, Imaging imIn, - int table_channels, int size1D, int size2D, int size3D, INT16* table); +extern Imaging +ImagingAlphaComposite(Imaging imIn1, Imaging imIn2); +extern Imaging +ImagingBlend(Imaging imIn1, Imaging imIn2, float alpha); +extern Imaging +ImagingCopy(Imaging im); +extern Imaging +ImagingConvert(Imaging im, const char *mode, ImagingPalette palette, int dither); +extern Imaging +ImagingConvertInPlace(Imaging im, const char *mode); +extern Imaging +ImagingConvertMatrix(Imaging im, const char *mode, float m[]); +extern Imaging +ImagingConvertTransparent(Imaging im, const char *mode, int r, int g, int b); +extern Imaging +ImagingCrop(Imaging im, int x0, int y0, int x1, int y1); +extern Imaging +ImagingExpand(Imaging im, int x, int y, int mode); +extern Imaging +ImagingFill(Imaging im, const void *ink); +extern int +ImagingFill2( + Imaging into, const void *ink, Imaging mask, int x0, int y0, int x1, int y1); +extern Imaging +ImagingFillBand(Imaging im, int band, int color); +extern Imaging +ImagingFillLinearGradient(const char *mode); +extern Imaging +ImagingFillRadialGradient(const char *mode); +extern Imaging +ImagingFilter(Imaging im, int xsize, int ysize, const FLOAT32 *kernel, FLOAT32 offset); +extern Imaging +ImagingFlipLeftRight(Imaging imOut, Imaging imIn); +extern Imaging +ImagingFlipTopBottom(Imaging imOut, Imaging imIn); +extern Imaging +ImagingGaussianBlur(Imaging imOut, Imaging imIn, float radius, int passes); +extern Imaging +ImagingGetBand(Imaging im, int band); +extern Imaging +ImagingMerge(const char *mode, Imaging bands[4]); +extern int +ImagingSplit(Imaging im, Imaging bands[4]); +extern int +ImagingGetBBox(Imaging im, int bbox[4]); +typedef struct { + int x, y; + INT32 count; + INT32 pixel; +} ImagingColorItem; +extern ImagingColorItem * +ImagingGetColors(Imaging im, int maxcolors, int *colors); +extern int +ImagingGetExtrema(Imaging im, void *extrema); +extern int +ImagingGetProjection(Imaging im, UINT8 *xproj, UINT8 *yproj); +extern ImagingHistogram +ImagingGetHistogram(Imaging im, Imaging mask, void *extrema); +extern Imaging +ImagingModeFilter(Imaging im, int size); +extern Imaging +ImagingNegative(Imaging im); +extern Imaging +ImagingOffset(Imaging im, int xoffset, int yoffset); +extern int +ImagingPaste(Imaging into, Imaging im, Imaging mask, int x0, int y0, int x1, int y1); +extern Imaging +ImagingPoint(Imaging im, const char *tablemode, const void *table); +extern Imaging +ImagingPointTransform(Imaging imIn, double scale, double offset); +extern Imaging +ImagingPutBand(Imaging im, Imaging imIn, int band); +extern Imaging +ImagingRankFilter(Imaging im, int size, int rank); +extern Imaging +ImagingRotate90(Imaging imOut, Imaging imIn); +extern Imaging +ImagingRotate180(Imaging imOut, Imaging imIn); +extern Imaging +ImagingRotate270(Imaging imOut, Imaging imIn); +extern Imaging +ImagingTranspose(Imaging imOut, Imaging imIn); +extern Imaging +ImagingTransverse(Imaging imOut, Imaging imIn); +extern Imaging +ImagingResample(Imaging imIn, int xsize, int ysize, int filter, float box[4]); +extern Imaging +ImagingReduce(Imaging imIn, int xscale, int yscale, int box[4]); +extern Imaging +ImagingTransform( + Imaging imOut, + Imaging imIn, + int method, + int x0, + int y0, + int x1, + int y1, + double *a, + int filter, + int fill); +extern Imaging +ImagingUnsharpMask(Imaging imOut, Imaging im, float radius, int percent, int threshold); +extern Imaging +ImagingBoxBlur(Imaging imOut, Imaging imIn, float radius, int n); +extern Imaging +ImagingColorLUT3D_linear( + Imaging imOut, + Imaging imIn, + int table_channels, + int size1D, + int size2D, + int size3D, + INT16 *table); -extern Imaging ImagingCopy2(Imaging imOut, Imaging imIn); -extern Imaging ImagingConvert2(Imaging imOut, Imaging imIn); +extern Imaging +ImagingCopy2(Imaging imOut, Imaging imIn); +extern Imaging +ImagingConvert2(Imaging imOut, Imaging imIn); /* Channel operations */ /* any mode, except "F" */ -extern Imaging ImagingChopLighter(Imaging imIn1, Imaging imIn2); -extern Imaging ImagingChopDarker(Imaging imIn1, Imaging imIn2); -extern Imaging ImagingChopDifference(Imaging imIn1, Imaging imIn2); -extern Imaging ImagingChopMultiply(Imaging imIn1, Imaging imIn2); -extern Imaging ImagingChopScreen(Imaging imIn1, Imaging imIn2); -extern Imaging ImagingChopAdd( - Imaging imIn1, Imaging imIn2, float scale, int offset); -extern Imaging ImagingChopSubtract( - Imaging imIn1, Imaging imIn2, float scale, int offset); -extern Imaging ImagingChopAddModulo(Imaging imIn1, Imaging imIn2); -extern Imaging ImagingChopSubtractModulo(Imaging imIn1, Imaging imIn2); +extern Imaging +ImagingChopLighter(Imaging imIn1, Imaging imIn2); +extern Imaging +ImagingChopDarker(Imaging imIn1, Imaging imIn2); +extern Imaging +ImagingChopDifference(Imaging imIn1, Imaging imIn2); +extern Imaging +ImagingChopMultiply(Imaging imIn1, Imaging imIn2); +extern Imaging +ImagingChopScreen(Imaging imIn1, Imaging imIn2); +extern Imaging +ImagingChopAdd(Imaging imIn1, Imaging imIn2, float scale, int offset); +extern Imaging +ImagingChopSubtract(Imaging imIn1, Imaging imIn2, float scale, int offset); +extern Imaging +ImagingChopAddModulo(Imaging imIn1, Imaging imIn2); +extern Imaging +ImagingChopSubtractModulo(Imaging imIn1, Imaging imIn2); +extern Imaging +ImagingChopSoftLight(Imaging imIn1, Imaging imIn2); +extern Imaging +ImagingChopHardLight(Imaging imIn1, Imaging imIn2); +extern Imaging +ImagingOverlay(Imaging imIn1, Imaging imIn2); /* "1" images only */ -extern Imaging ImagingChopAnd(Imaging imIn1, Imaging imIn2); -extern Imaging ImagingChopOr(Imaging imIn1, Imaging imIn2); -extern Imaging ImagingChopXor(Imaging imIn1, Imaging imIn2); - -/* Image measurement */ -extern void ImagingCrack(Imaging im, int x0, int y0); +extern Imaging +ImagingChopAnd(Imaging imIn1, Imaging imIn2); +extern Imaging +ImagingChopOr(Imaging imIn1, Imaging imIn2); +extern Imaging +ImagingChopXor(Imaging imIn1, Imaging imIn2); /* Graphics */ -extern int ImagingDrawArc(Imaging im, int x0, int y0, int x1, int y1, - float start, float end, const void* ink, int width, - int op); -extern int ImagingDrawBitmap(Imaging im, int x0, int y0, Imaging bitmap, - const void* ink, int op); -extern int ImagingDrawChord(Imaging im, int x0, int y0, int x1, int y1, - float start, float end, const void* ink, int fill, - int width, int op); -extern int ImagingDrawEllipse(Imaging im, int x0, int y0, int x1, int y1, - const void* ink, int fill, int width, int op); -extern int ImagingDrawLine(Imaging im, int x0, int y0, int x1, int y1, - const void* ink, int op); -extern int ImagingDrawWideLine(Imaging im, int x0, int y0, int x1, int y1, - const void* ink, int width, int op); -extern int ImagingDrawPieslice(Imaging im, int x0, int y0, int x1, int y1, - float start, float end, const void* ink, int fill, - int width, int op); -extern int ImagingDrawPoint(Imaging im, int x, int y, const void* ink, int op); -extern int ImagingDrawPolygon(Imaging im, int points, int *xy, - const void* ink, int fill, int op); -extern int ImagingDrawRectangle(Imaging im, int x0, int y0, int x1, int y1, - const void* ink, int fill, int width, int op); +extern int +ImagingDrawArc( + Imaging im, + int x0, + int y0, + int x1, + int y1, + float start, + float end, + const void *ink, + int width, + int op); +extern int +ImagingDrawBitmap(Imaging im, int x0, int y0, Imaging bitmap, const void *ink, int op); +extern int +ImagingDrawChord( + Imaging im, + int x0, + int y0, + int x1, + int y1, + float start, + float end, + const void *ink, + int fill, + int width, + int op); +extern int +ImagingDrawEllipse( + Imaging im, + int x0, + int y0, + int x1, + int y1, + const void *ink, + int fill, + int width, + int op); +extern int +ImagingDrawLine(Imaging im, int x0, int y0, int x1, int y1, const void *ink, int op); +extern int +ImagingDrawWideLine( + Imaging im, int x0, int y0, int x1, int y1, const void *ink, int width, int op); +extern int +ImagingDrawPieslice( + Imaging im, + int x0, + int y0, + int x1, + int y1, + float start, + float end, + const void *ink, + int fill, + int width, + int op); +extern int +ImagingDrawPoint(Imaging im, int x, int y, const void *ink, int op); +extern int +ImagingDrawPolygon(Imaging im, int points, int *xy, const void *ink, int fill, int op); +extern int +ImagingDrawRectangle( + Imaging im, + int x0, + int y0, + int x1, + int y1, + const void *ink, + int fill, + int width, + int op); /* Level 2 graphics (WORK IN PROGRESS) */ -extern ImagingOutline ImagingOutlineNew(void); -extern void ImagingOutlineDelete(ImagingOutline outline); +extern ImagingOutline +ImagingOutlineNew(void); +extern void +ImagingOutlineDelete(ImagingOutline outline); -extern int ImagingDrawOutline(Imaging im, ImagingOutline outline, - const void* ink, int fill, int op); +extern int +ImagingDrawOutline( + Imaging im, ImagingOutline outline, const void *ink, int fill, int op); -extern int ImagingOutlineMove(ImagingOutline outline, float x, float y); -extern int ImagingOutlineLine(ImagingOutline outline, float x, float y); -extern int ImagingOutlineCurve(ImagingOutline outline, float x1, float y1, - float x2, float y2, float x3, float y3); -extern int ImagingOutlineTransform(ImagingOutline outline, double a[6]); +extern int +ImagingOutlineMove(ImagingOutline outline, float x, float y); +extern int +ImagingOutlineLine(ImagingOutline outline, float x, float y); +extern int +ImagingOutlineCurve( + ImagingOutline outline, float x1, float y1, float x2, float y2, float x3, float y3); +extern int +ImagingOutlineTransform(ImagingOutline outline, double a[6]); -extern int ImagingOutlineClose(ImagingOutline outline); +extern int +ImagingOutlineClose(ImagingOutline outline); /* Special effects */ -extern Imaging ImagingEffectSpread(Imaging imIn, int distance); -extern Imaging ImagingEffectNoise(int xsize, int ysize, float sigma); -extern Imaging ImagingEffectMandelbrot(int xsize, int ysize, - double extent[4], int quality); - -/* Obsolete */ -extern int ImagingToString(Imaging im, int orientation, char *buffer); -extern int ImagingFromString(Imaging im, int orientation, char *buffer); - +extern Imaging +ImagingEffectSpread(Imaging imIn, int distance); +extern Imaging +ImagingEffectNoise(int xsize, int ysize, float sigma); +extern Imaging +ImagingEffectMandelbrot(int xsize, int ysize, double extent[4], int quality); /* File I/O */ /* -------- */ /* Built-in drivers */ -extern Imaging ImagingOpenPPM(const char* filename); -extern int ImagingSavePPM(Imaging im, const char* filename); - -/* Utility functions */ -extern UINT32 ImagingCRC32(UINT32 crc, UINT8* buffer, int bytes); +extern Imaging +ImagingOpenPPM(const char *filename); +extern int +ImagingSavePPM(Imaging im, const char *filename); /* Codecs */ typedef struct ImagingCodecStateInstance *ImagingCodecState; -typedef int (*ImagingCodec)(Imaging im, ImagingCodecState state, - UINT8* buffer, int bytes); +typedef int (*ImagingCodec)( + Imaging im, ImagingCodecState state, UINT8 *buffer, int bytes); -extern int ImagingBcnDecode(Imaging im, ImagingCodecState state, - UINT8* buffer, Py_ssize_t bytes); -extern int ImagingBitDecode(Imaging im, ImagingCodecState state, - UINT8* buffer, Py_ssize_t bytes); -extern int ImagingEpsEncode(Imaging im, ImagingCodecState state, - UINT8* buffer, int bytes); -extern int ImagingFliDecode(Imaging im, ImagingCodecState state, - UINT8* buffer, Py_ssize_t bytes); -extern int ImagingGifDecode(Imaging im, ImagingCodecState state, - UINT8* buffer, Py_ssize_t bytes); -extern int ImagingGifEncode(Imaging im, ImagingCodecState state, - UINT8* buffer, int bytes); -extern int ImagingHexDecode(Imaging im, ImagingCodecState state, - UINT8* buffer, Py_ssize_t bytes); -#ifdef HAVE_LIBJPEG -extern int ImagingJpegDecode(Imaging im, ImagingCodecState state, - UINT8* buffer, Py_ssize_t bytes); -extern int ImagingJpegDecodeCleanup(ImagingCodecState state); -extern int ImagingJpegUseJCSExtensions(void); +extern int +ImagingBcnDecode(Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes); +extern int +ImagingBitDecode(Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes); +extern int +ImagingEpsEncode(Imaging im, ImagingCodecState state, UINT8 *buffer, int bytes); +extern int +ImagingFliDecode(Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes); +extern int +ImagingGifDecode(Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes); +extern int +ImagingGifEncode(Imaging im, ImagingCodecState state, UINT8 *buffer, int bytes); +extern int +ImagingHexDecode(Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes); +#ifdef HAVE_LIBJPEG +extern int +ImagingJpegDecode(Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes); +extern int +ImagingJpegDecodeCleanup(ImagingCodecState state); +extern int +ImagingJpegUseJCSExtensions(void); -extern int ImagingJpegEncode(Imaging im, ImagingCodecState state, - UINT8* buffer, int bytes); +extern int +ImagingJpegEncode(Imaging im, ImagingCodecState state, UINT8 *buffer, int bytes); #endif #ifdef HAVE_OPENJPEG -extern int ImagingJpeg2KDecode(Imaging im, ImagingCodecState state, - UINT8* buffer, Py_ssize_t bytes); -extern int ImagingJpeg2KDecodeCleanup(ImagingCodecState state); -extern int ImagingJpeg2KEncode(Imaging im, ImagingCodecState state, - UINT8* buffer, int bytes); -extern int ImagingJpeg2KEncodeCleanup(ImagingCodecState state); +extern int +ImagingJpeg2KDecode( + Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes); +extern int +ImagingJpeg2KDecodeCleanup(ImagingCodecState state); +extern int +ImagingJpeg2KEncode(Imaging im, ImagingCodecState state, UINT8 *buffer, int bytes); +extern int +ImagingJpeg2KEncodeCleanup(ImagingCodecState state); #endif -#ifdef HAVE_LIBTIFF -extern int ImagingLibTiffDecode(Imaging im, ImagingCodecState state, - UINT8* buffer, Py_ssize_t bytes); -extern int ImagingLibTiffEncode(Imaging im, ImagingCodecState state, - UINT8* buffer, int bytes); +#ifdef HAVE_LIBTIFF +extern int +ImagingLibTiffDecode( + Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes); +extern int +ImagingLibTiffEncode(Imaging im, ImagingCodecState state, UINT8 *buffer, int bytes); #endif -#ifdef HAVE_LIBMPEG -extern int ImagingMpegDecode(Imaging im, ImagingCodecState state, - UINT8* buffer, Py_ssize_t bytes); +#ifdef HAVE_LIBMPEG +extern int +ImagingMpegDecode(Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes); #endif -extern int ImagingMspDecode(Imaging im, ImagingCodecState state, - UINT8* buffer, Py_ssize_t bytes); -extern int ImagingPackbitsDecode(Imaging im, ImagingCodecState state, - UINT8* buffer, Py_ssize_t bytes); -extern int ImagingPcdDecode(Imaging im, ImagingCodecState state, - UINT8* buffer, Py_ssize_t bytes); -extern int ImagingPcxDecode(Imaging im, ImagingCodecState state, - UINT8* buffer, Py_ssize_t bytes); -extern int ImagingPcxEncode(Imaging im, ImagingCodecState state, - UINT8* buffer, int bytes); -extern int ImagingRawDecode(Imaging im, ImagingCodecState state, - UINT8* buffer, Py_ssize_t bytes); -extern int ImagingRawEncode(Imaging im, ImagingCodecState state, - UINT8* buffer, int bytes); -extern int ImagingSgiRleDecode(Imaging im, ImagingCodecState state, - UINT8* buffer, Py_ssize_t bytes); -extern int ImagingSgiRleDecodeCleanup(ImagingCodecState state); -extern int ImagingSunRleDecode(Imaging im, ImagingCodecState state, - UINT8* buffer, Py_ssize_t bytes); -extern int ImagingTgaRleDecode(Imaging im, ImagingCodecState state, - UINT8* buffer, Py_ssize_t bytes); -extern int ImagingTgaRleEncode(Imaging im, ImagingCodecState state, - UINT8* buffer, int bytes); -extern int ImagingXbmDecode(Imaging im, ImagingCodecState state, - UINT8* buffer, Py_ssize_t bytes); -extern int ImagingXbmEncode(Imaging im, ImagingCodecState state, - UINT8* buffer, int bytes); -#ifdef HAVE_LIBZ -extern int ImagingZipDecode(Imaging im, ImagingCodecState state, - UINT8* buffer, Py_ssize_t bytes); -extern int ImagingZipDecodeCleanup(ImagingCodecState state); -extern int ImagingZipEncode(Imaging im, ImagingCodecState state, - UINT8* buffer, int bytes); -extern int ImagingZipEncodeCleanup(ImagingCodecState state); +extern int +ImagingMspDecode(Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes); +extern int +ImagingPackbitsDecode( + Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes); +extern int +ImagingPcdDecode(Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes); +extern int +ImagingPcxDecode(Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes); +extern int +ImagingPcxEncode(Imaging im, ImagingCodecState state, UINT8 *buffer, int bytes); +extern int +ImagingRawDecode(Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes); +extern int +ImagingRawEncode(Imaging im, ImagingCodecState state, UINT8 *buffer, int bytes); +extern int +ImagingSgiRleDecode( + Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes); +extern int +ImagingSunRleDecode( + Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes); +extern int +ImagingTgaRleDecode( + Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes); +extern int +ImagingTgaRleEncode(Imaging im, ImagingCodecState state, UINT8 *buffer, int bytes); +extern int +ImagingXbmDecode(Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes); +extern int +ImagingXbmEncode(Imaging im, ImagingCodecState state, UINT8 *buffer, int bytes); +#ifdef HAVE_LIBZ +extern int +ImagingZipDecode(Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes); +extern int +ImagingZipDecodeCleanup(ImagingCodecState state); +extern int +ImagingZipEncode(Imaging im, ImagingCodecState state, UINT8 *buffer, int bytes); +extern int +ImagingZipEncodeCleanup(ImagingCodecState state); #endif -typedef void (*ImagingShuffler)(UINT8* out, const UINT8* in, int pixels); +typedef void (*ImagingShuffler)(UINT8 *out, const UINT8 *in, int pixels); /* Public shufflers */ -extern void ImagingPackBGR(UINT8* out, const UINT8* in, int pixels); -extern void ImagingUnpackYCC(UINT8* out, const UINT8* in, int pixels); -extern void ImagingUnpackYCCA(UINT8* out, const UINT8* in, int pixels); +extern void +ImagingPackBGR(UINT8 *out, const UINT8 *in, int pixels); +extern void +ImagingUnpackYCC(UINT8 *out, const UINT8 *in, int pixels); +extern void +ImagingUnpackYCCA(UINT8 *out, const UINT8 *in, int pixels); -extern void ImagingConvertRGB2YCbCr(UINT8* out, const UINT8* in, int pixels); -extern void ImagingConvertYCbCr2RGB(UINT8* out, const UINT8* in, int pixels); +extern void +ImagingConvertRGB2YCbCr(UINT8 *out, const UINT8 *in, int pixels); +extern void +ImagingConvertYCbCr2RGB(UINT8 *out, const UINT8 *in, int pixels); -extern ImagingShuffler ImagingFindUnpacker(const char* mode, - const char* rawmode, int* bits_out); -extern ImagingShuffler ImagingFindPacker(const char* mode, - const char* rawmode, int* bits_out); +extern ImagingShuffler +ImagingFindUnpacker(const char *mode, const char *rawmode, int *bits_out); +extern ImagingShuffler +ImagingFindPacker(const char *mode, const char *rawmode, int *bits_out); struct ImagingCodecStateInstance { int count; @@ -520,30 +668,27 @@ struct ImagingCodecStateInstance { PyObject *fd; }; - - /* Codec read/write python fd */ -extern Py_ssize_t _imaging_read_pyFd(PyObject *fd, char* dest, Py_ssize_t bytes); -extern Py_ssize_t _imaging_write_pyFd(PyObject *fd, char* src, Py_ssize_t bytes); -extern int _imaging_seek_pyFd(PyObject *fd, Py_ssize_t offset, int whence); -extern Py_ssize_t _imaging_tell_pyFd(PyObject *fd); - - +extern Py_ssize_t +_imaging_read_pyFd(PyObject *fd, char *dest, Py_ssize_t bytes); +extern Py_ssize_t +_imaging_write_pyFd(PyObject *fd, char *src, Py_ssize_t bytes); +extern int +_imaging_seek_pyFd(PyObject *fd, Py_ssize_t offset, int whence); +extern Py_ssize_t +_imaging_tell_pyFd(PyObject *fd); /* Errcodes */ -#define IMAGING_CODEC_END 1 -#define IMAGING_CODEC_OVERRUN -1 -#define IMAGING_CODEC_BROKEN -2 -#define IMAGING_CODEC_UNKNOWN -3 -#define IMAGING_CODEC_CONFIG -8 -#define IMAGING_CODEC_MEMORY -9 - - +#define IMAGING_CODEC_END 1 +#define IMAGING_CODEC_OVERRUN -1 +#define IMAGING_CODEC_BROKEN -2 +#define IMAGING_CODEC_UNKNOWN -3 +#define IMAGING_CODEC_CONFIG -8 +#define IMAGING_CODEC_MEMORY -9 #include "ImagingUtils.h" extern UINT8 *clip8_lookups; - #if defined(__cplusplus) } #endif diff --git a/src/libImaging/ImagingUtils.h b/src/libImaging/ImagingUtils.h index 21c2688d8..ad6f280ac 100644 --- a/src/libImaging/ImagingUtils.h +++ b/src/libImaging/ImagingUtils.h @@ -1,47 +1,42 @@ #ifdef WORDS_BIGENDIAN - #define MAKE_UINT32(u0, u1, u2, u3) ((UINT32)(u3) | ((UINT32)(u2)<<8) | ((UINT32)(u1)<<16) | ((UINT32)(u0)<<24)) - #define MASK_UINT32_CHANNEL_0 0xff000000 - #define MASK_UINT32_CHANNEL_1 0x00ff0000 - #define MASK_UINT32_CHANNEL_2 0x0000ff00 - #define MASK_UINT32_CHANNEL_3 0x000000ff +#define MAKE_UINT32(u0, u1, u2, u3) \ + ((UINT32)(u3) | ((UINT32)(u2) << 8) | ((UINT32)(u1) << 16) | ((UINT32)(u0) << 24)) +#define MASK_UINT32_CHANNEL_0 0xff000000 +#define MASK_UINT32_CHANNEL_1 0x00ff0000 +#define MASK_UINT32_CHANNEL_2 0x0000ff00 +#define MASK_UINT32_CHANNEL_3 0x000000ff #else - #define MAKE_UINT32(u0, u1, u2, u3) ((UINT32)(u0) | ((UINT32)(u1)<<8) | ((UINT32)(u2)<<16) | ((UINT32)(u3)<<24)) - #define MASK_UINT32_CHANNEL_0 0x000000ff - #define MASK_UINT32_CHANNEL_1 0x0000ff00 - #define MASK_UINT32_CHANNEL_2 0x00ff0000 - #define MASK_UINT32_CHANNEL_3 0xff000000 +#define MAKE_UINT32(u0, u1, u2, u3) \ + ((UINT32)(u0) | ((UINT32)(u1) << 8) | ((UINT32)(u2) << 16) | ((UINT32)(u3) << 24)) +#define MASK_UINT32_CHANNEL_0 0x000000ff +#define MASK_UINT32_CHANNEL_1 0x0000ff00 +#define MASK_UINT32_CHANNEL_2 0x00ff0000 +#define MASK_UINT32_CHANNEL_3 0xff000000 #endif - -#define SHIFTFORDIV255(a)\ - ((((a) >> 8) + a) >> 8) +#define SHIFTFORDIV255(a) ((((a) >> 8) + a) >> 8) /* like (a * b + 127) / 255), but much faster on most platforms */ -#define MULDIV255(a, b, tmp)\ - (tmp = (a) * (b) + 128, SHIFTFORDIV255(tmp)) +#define MULDIV255(a, b, tmp) (tmp = (a) * (b) + 128, SHIFTFORDIV255(tmp)) -#define DIV255(a, tmp)\ - (tmp = (a) + 128, SHIFTFORDIV255(tmp)) +#define DIV255(a, tmp) (tmp = (a) + 128, SHIFTFORDIV255(tmp)) -#define BLEND(mask, in1, in2, tmp1)\ - DIV255(in1 * (255 - mask) + in2 * mask, tmp1) - -#define PREBLEND(mask, in1, in2, tmp1)\ - (MULDIV255(in1, (255 - mask), tmp1) + in2) +#define BLEND(mask, in1, in2, tmp1) DIV255(in1 *(255 - mask) + in2 * mask, tmp1) +#define PREBLEND(mask, in1, in2, tmp1) (MULDIV255(in1, (255 - mask), tmp1) + in2) #define CLIP8(v) ((v) <= 0 ? 0 : (v) < 256 ? (v) : 255) /* This is to work around a bug in GCC prior 4.9 in 64 bit mode. GCC generates code with partial dependency which is 3 times slower. See: http://stackoverflow.com/a/26588074/253146 */ -#if defined(__x86_64__) && defined(__SSE__) && ! defined(__NO_INLINE__) && \ - ! defined(__clang__) && defined(GCC_VERSION) && (GCC_VERSION < 40900) +#if defined(__x86_64__) && defined(__SSE__) && !defined(__NO_INLINE__) && \ + !defined(__clang__) && defined(GCC_VERSION) && (GCC_VERSION < 40900) static float __attribute__((always_inline)) inline _i2f(int v) { float x; - __asm__("xorps %0, %0; cvtsi2ss %1, %0" : "=x"(x) : "r"(v) ); + __asm__("xorps %0, %0; cvtsi2ss %1, %0" : "=x"(x) : "r"(v)); return x; } #else -static float inline _i2f(int v) { return (float) v; } +static float inline _i2f(int v) { return (float)v; } #endif diff --git a/src/libImaging/Jpeg.h b/src/libImaging/Jpeg.h index 82e1b449f..a876d3bb6 100644 --- a/src/libImaging/Jpeg.h +++ b/src/libImaging/Jpeg.h @@ -12,15 +12,13 @@ #include - typedef struct { - struct jpeg_error_mgr pub; /* "public" fields */ - jmp_buf setjmp_buffer; /* for return to caller */ + struct jpeg_error_mgr pub; /* "public" fields */ + jmp_buf setjmp_buffer; /* for return to caller */ } JPEGERROR; - /* -------------------------------------------------------------------- */ -/* Decoder */ +/* Decoder */ typedef struct { struct jpeg_source_mgr pub; @@ -28,15 +26,14 @@ typedef struct { } JPEGSOURCE; typedef struct { - /* CONFIGURATION */ /* Jpeg file mode (empty if not known) */ - char jpegmode[8+1]; + char jpegmode[8 + 1]; /* Converter output mode (input to the shuffler). If empty, convert conversions are disabled */ - char rawmode[8+1]; + char rawmode[8 + 1]; /* If set, trade quality for speed */ int draft; @@ -54,9 +51,8 @@ typedef struct { } JPEGSTATE; - /* -------------------------------------------------------------------- */ -/* Encoder */ +/* Encoder */ typedef struct { struct jpeg_destination_mgr pub; @@ -64,10 +60,9 @@ typedef struct { } JPEGDESTINATION; typedef struct { - /* CONFIGURATION */ - /* Quality (1-100, 0 means default) */ + /* Quality (0-100, -1 means default) */ int quality; /* Progressive mode */ @@ -89,7 +84,7 @@ typedef struct { int subsampling; /* Converter input mode (input to the shuffler) */ - char rawmode[8+1]; + char rawmode[8 + 1]; /* Custom quantization tables () */ unsigned int *qtables; @@ -98,7 +93,8 @@ typedef struct { int qtablesLen; /* Extra data (to be injected after header) */ - char* extra; int extra_size; + char *extra; + int extra_size; /* PRIVATE CONTEXT (set by encoder) */ @@ -110,7 +106,7 @@ typedef struct { int extra_offset; - int rawExifLen; /* EXIF data length */ - char* rawExif; /* EXIF buffer pointer */ + size_t rawExifLen; /* EXIF data length */ + char *rawExif; /* EXIF buffer pointer */ } JPEGENCODERSTATE; diff --git a/src/libImaging/Jpeg2K.h b/src/libImaging/Jpeg2K.h index 7bb14eb2c..f749ecfb2 100644 --- a/src/libImaging/Jpeg2K.h +++ b/src/libImaging/Jpeg2K.h @@ -14,7 +14,7 @@ #define BUFFER_SIZE OPJ_J2K_STREAM_CHUNK_SIZE /* -------------------------------------------------------------------- */ -/* Decoder */ +/* Decoder */ /* -------------------------------------------------------------------- */ typedef struct { @@ -24,7 +24,7 @@ typedef struct { int fd; /* File pointer, when opened */ - FILE * pfile; + FILE *pfile; /* Length of data, if available; otherwise, -1 */ off_t length; @@ -33,54 +33,54 @@ typedef struct { OPJ_CODEC_FORMAT format; /* Set to divide image resolution by 2**reduce. */ - int reduce; + int reduce; /* Set to limit the number of quality layers to decode (0 = all layers) */ - int layers; + int layers; /* PRIVATE CONTEXT (set by decoder) */ - const char *error_msg; + const char *error_msg; } JPEG2KDECODESTATE; /* -------------------------------------------------------------------- */ -/* Encoder */ +/* Encoder */ /* -------------------------------------------------------------------- */ typedef struct { /* CONFIGURATION */ /* File descriptor, if available; otherwise, -1 */ - int fd; + int fd; /* File pointer, when opened */ - FILE * pfile; + FILE *pfile; /* Specify the desired format */ OPJ_CODEC_FORMAT format; /* Image offset */ - int offset_x, offset_y; + int offset_x, offset_y; /* Tile information */ - int tile_offset_x, tile_offset_y; - int tile_size_x, tile_size_y; + int tile_offset_x, tile_offset_y; + int tile_size_x, tile_size_y; /* Quality layers (a sequence of numbers giving *either* rates or dB) */ - int quality_is_in_db; - PyObject *quality_layers; + int quality_is_in_db; + PyObject *quality_layers; /* Number of resolutions (DWT decompositions + 1 */ - int num_resolutions; + int num_resolutions; /* Code block size */ - int cblk_width, cblk_height; + int cblk_width, cblk_height; /* Precinct size */ - int precinct_width, precinct_height; + int precinct_width, precinct_height; /* Compression style */ - int irreversible; + int irreversible; /* Progression order (LRCP/RLCP/RPCL/PCRL/CPRL) */ OPJ_PROG_ORDER progression; @@ -89,8 +89,7 @@ typedef struct { OPJ_CINEMA_MODE cinema_mode; /* PRIVATE CONTEXT (set by decoder) */ - const char *error_msg; - + const char *error_msg; } JPEG2KENCODESTATE; diff --git a/src/libImaging/Jpeg2KDecode.c b/src/libImaging/Jpeg2KDecode.c index f2e437dda..a2a7354db 100644 --- a/src/libImaging/Jpeg2KDecode.c +++ b/src/libImaging/Jpeg2KDecode.c @@ -23,7 +23,7 @@ typedef struct { OPJ_UINT32 tile_index; OPJ_UINT32 data_size; - OPJ_INT32 x0, y0, x1, y1; + OPJ_INT32 x0, y0, x1, y1; OPJ_UINT32 nb_comps; } JPEG2KTILEINFO; @@ -32,9 +32,8 @@ typedef struct { /* -------------------------------------------------------------------- */ static void -j2k_error(const char *msg, void *client_data) -{ - JPEG2KDECODESTATE *state = (JPEG2KDECODESTATE *) client_data; +j2k_error(const char *msg, void *client_data) { + JPEG2KDECODESTATE *state = (JPEG2KDECODESTATE *)client_data; free((void *)state->error_msg); state->error_msg = strdup(msg); } @@ -44,8 +43,7 @@ j2k_error(const char *msg, void *client_data) /* -------------------------------------------------------------------- */ static OPJ_SIZE_T -j2k_read(void *p_buffer, OPJ_SIZE_T p_nb_bytes, void *p_user_data) -{ +j2k_read(void *p_buffer, OPJ_SIZE_T p_nb_bytes, void *p_user_data) { ImagingCodecState state = (ImagingCodecState)p_user_data; size_t len = _imaging_read_pyFd(state->fd, p_buffer, p_nb_bytes); @@ -54,8 +52,7 @@ j2k_read(void *p_buffer, OPJ_SIZE_T p_nb_bytes, void *p_user_data) } static OPJ_OFF_T -j2k_skip(OPJ_OFF_T p_nb_bytes, void *p_user_data) -{ +j2k_skip(OPJ_OFF_T p_nb_bytes, void *p_user_data) { off_t pos; ImagingCodecState state = (ImagingCodecState)p_user_data; @@ -69,31 +66,31 @@ j2k_skip(OPJ_OFF_T p_nb_bytes, void *p_user_data) /* Unpackers */ /* -------------------------------------------------------------------- */ -typedef void (*j2k_unpacker_t)(opj_image_t *in, - const JPEG2KTILEINFO *tileInfo, - const UINT8 *data, - Imaging im); +typedef void (*j2k_unpacker_t)( + opj_image_t *in, const JPEG2KTILEINFO *tileInfo, const UINT8 *data, Imaging im); struct j2k_decode_unpacker { - const char *mode; - OPJ_COLOR_SPACE color_space; - unsigned components; - j2k_unpacker_t unpacker; + const char *mode; + OPJ_COLOR_SPACE color_space; + unsigned components; + j2k_unpacker_t unpacker; }; -static inline -unsigned j2ku_shift(unsigned x, int n) -{ - if (n < 0) +static inline unsigned +j2ku_shift(unsigned x, int n) { + if (n < 0) { return x >> -n; - else + } else { return x << n; + } } static void -j2ku_gray_l(opj_image_t *in, const JPEG2KTILEINFO *tileinfo, - const UINT8 *tiledata, Imaging im) -{ +j2ku_gray_l( + opj_image_t *in, + const JPEG2KTILEINFO *tileinfo, + const UINT8 *tiledata, + Imaging im) { unsigned x0 = tileinfo->x0 - in->x0, y0 = tileinfo->y0 - in->y0; unsigned w = tileinfo->x1 - tileinfo->x0; unsigned h = tileinfo->y1 - tileinfo->y0; @@ -104,45 +101,52 @@ j2ku_gray_l(opj_image_t *in, const JPEG2KTILEINFO *tileinfo, unsigned x, y; - if (csiz == 3) + if (csiz == 3) { csiz = 4; + } - if (shift < 0) + if (shift < 0) { offset += 1 << (-shift - 1); + } + /* csiz*h*w + offset = tileinfo.datasize */ switch (csiz) { - case 1: - for (y = 0; y < h; ++y) { - const UINT8 *data = &tiledata[y * w]; - UINT8 *row = (UINT8 *)im->image[y0 + y] + x0; - for (x = 0; x < w; ++x) - *row++ = j2ku_shift(offset + *data++, shift); - } - break; - case 2: - for (y = 0; y < h; ++y) { - const UINT16 *data = (const UINT16 *)&tiledata[2 * y * w]; - UINT8 *row = (UINT8 *)im->image[y0 + y] + x0; - for (x = 0; x < w; ++x) - *row++ = j2ku_shift(offset + *data++, shift); - } - break; - case 4: - for (y = 0; y < h; ++y) { - const UINT32 *data = (const UINT32 *)&tiledata[4 * y * w]; - UINT8 *row = (UINT8 *)im->image[y0 + y] + x0; - for (x = 0; x < w; ++x) - *row++ = j2ku_shift(offset + *data++, shift); - } - break; + case 1: + for (y = 0; y < h; ++y) { + const UINT8 *data = &tiledata[y * w]; + UINT8 *row = (UINT8 *)im->image[y0 + y] + x0; + for (x = 0; x < w; ++x) { + *row++ = j2ku_shift(offset + *data++, shift); + } + } + break; + case 2: + for (y = 0; y < h; ++y) { + const UINT16 *data = (const UINT16 *)&tiledata[2 * y * w]; + UINT8 *row = (UINT8 *)im->image[y0 + y] + x0; + for (x = 0; x < w; ++x) { + *row++ = j2ku_shift(offset + *data++, shift); + } + } + break; + case 4: + for (y = 0; y < h; ++y) { + const UINT32 *data = (const UINT32 *)&tiledata[4 * y * w]; + UINT8 *row = (UINT8 *)im->image[y0 + y] + x0; + for (x = 0; x < w; ++x) { + *row++ = j2ku_shift(offset + *data++, shift); + } + } + break; } } - static void -j2ku_gray_i(opj_image_t *in, const JPEG2KTILEINFO *tileinfo, - const UINT8 *tiledata, Imaging im) -{ +j2ku_gray_i( + opj_image_t *in, + const JPEG2KTILEINFO *tileinfo, + const UINT8 *tiledata, + Imaging im) { unsigned x0 = tileinfo->x0 - in->x0, y0 = tileinfo->y0 - in->y0; unsigned w = tileinfo->x1 - tileinfo->x0; unsigned h = tileinfo->y1 - tileinfo->y0; @@ -153,45 +157,51 @@ j2ku_gray_i(opj_image_t *in, const JPEG2KTILEINFO *tileinfo, unsigned x, y; - if (csiz == 3) + if (csiz == 3) { csiz = 4; + } - if (shift < 0) + if (shift < 0) { offset += 1 << (-shift - 1); + } switch (csiz) { - case 1: - for (y = 0; y < h; ++y) { - const UINT8 *data = &tiledata[y * w]; - UINT16 *row = (UINT16 *)im->image[y0 + y] + x0; - for (x = 0; x < w; ++x) - *row++ = j2ku_shift(offset + *data++, shift); - } - break; - case 2: - for (y = 0; y < h; ++y) { - const UINT16 *data = (const UINT16 *)&tiledata[2 * y * w]; - UINT16 *row = (UINT16 *)im->image[y0 + y] + x0; - for (x = 0; x < w; ++x) - *row++ = j2ku_shift(offset + *data++, shift); - } - break; - case 4: - for (y = 0; y < h; ++y) { - const UINT32 *data = (const UINT32 *)&tiledata[4 * y * w]; - UINT16 *row = (UINT16 *)im->image[y0 + y] + x0; - for (x = 0; x < w; ++x) - *row++ = j2ku_shift(offset + *data++, shift); - } - break; + case 1: + for (y = 0; y < h; ++y) { + const UINT8 *data = &tiledata[y * w]; + UINT16 *row = (UINT16 *)im->image[y0 + y] + x0; + for (x = 0; x < w; ++x) { + *row++ = j2ku_shift(offset + *data++, shift); + } + } + break; + case 2: + for (y = 0; y < h; ++y) { + const UINT16 *data = (const UINT16 *)&tiledata[2 * y * w]; + UINT16 *row = (UINT16 *)im->image[y0 + y] + x0; + for (x = 0; x < w; ++x) { + *row++ = j2ku_shift(offset + *data++, shift); + } + } + break; + case 4: + for (y = 0; y < h; ++y) { + const UINT32 *data = (const UINT32 *)&tiledata[4 * y * w]; + UINT16 *row = (UINT16 *)im->image[y0 + y] + x0; + for (x = 0; x < w; ++x) { + *row++ = j2ku_shift(offset + *data++, shift); + } + } + break; } } - static void -j2ku_gray_rgb(opj_image_t *in, const JPEG2KTILEINFO *tileinfo, - const UINT8 *tiledata, Imaging im) -{ +j2ku_gray_rgb( + opj_image_t *in, + const JPEG2KTILEINFO *tileinfo, + const UINT8 *tiledata, + Imaging im) { unsigned x0 = tileinfo->x0 - in->x0, y0 = tileinfo->y0 - in->y0; unsigned w = tileinfo->x1 - tileinfo->x0; unsigned h = tileinfo->y1 - tileinfo->y0; @@ -202,56 +212,60 @@ j2ku_gray_rgb(opj_image_t *in, const JPEG2KTILEINFO *tileinfo, unsigned x, y; - if (shift < 0) + if (shift < 0) { offset += 1 << (-shift - 1); + } - if (csiz == 3) + if (csiz == 3) { csiz = 4; + } switch (csiz) { - case 1: - for (y = 0; y < h; ++y) { - const UINT8 *data = &tiledata[y * w]; - UINT8 *row = (UINT8 *)im->image[y0 + y] + x0; - for (x = 0; x < w; ++x) { - UINT8 byte = j2ku_shift(offset + *data++, shift); - row[0] = row[1] = row[2] = byte; - row[3] = 0xff; - row += 4; + case 1: + for (y = 0; y < h; ++y) { + const UINT8 *data = &tiledata[y * w]; + UINT8 *row = (UINT8 *)im->image[y0 + y] + x0; + for (x = 0; x < w; ++x) { + UINT8 byte = j2ku_shift(offset + *data++, shift); + row[0] = row[1] = row[2] = byte; + row[3] = 0xff; + row += 4; + } } - } - break; - case 2: - for (y = 0; y < h; ++y) { - const UINT16 *data = (UINT16 *)&tiledata[2 * y * w]; - UINT8 *row = (UINT8 *)im->image[y0 + y] + x0; - for (x = 0; x < w; ++x) { - UINT8 byte = j2ku_shift(offset + *data++, shift); - row[0] = row[1] = row[2] = byte; - row[3] = 0xff; - row += 4; + break; + case 2: + for (y = 0; y < h; ++y) { + const UINT16 *data = (UINT16 *)&tiledata[2 * y * w]; + UINT8 *row = (UINT8 *)im->image[y0 + y] + x0; + for (x = 0; x < w; ++x) { + UINT8 byte = j2ku_shift(offset + *data++, shift); + row[0] = row[1] = row[2] = byte; + row[3] = 0xff; + row += 4; + } } - } - break; - case 4: - for (y = 0; y < h; ++y) { - const UINT32 *data = (UINT32 *)&tiledata[4 * y * w]; - UINT8 *row = (UINT8 *)im->image[y0 + y] + x0; - for (x = 0; x < w; ++x) { - UINT8 byte = j2ku_shift(offset + *data++, shift); - row[0] = row[1] = row[2] = byte; - row[3] = 0xff; - row += 4; + break; + case 4: + for (y = 0; y < h; ++y) { + const UINT32 *data = (UINT32 *)&tiledata[4 * y * w]; + UINT8 *row = (UINT8 *)im->image[y0 + y] + x0; + for (x = 0; x < w; ++x) { + UINT8 byte = j2ku_shift(offset + *data++, shift); + row[0] = row[1] = row[2] = byte; + row[3] = 0xff; + row += 4; + } } - } - break; + break; } } static void -j2ku_graya_la(opj_image_t *in, const JPEG2KTILEINFO *tileinfo, - const UINT8 *tiledata, Imaging im) -{ +j2ku_graya_la( + opj_image_t *in, + const JPEG2KTILEINFO *tileinfo, + const UINT8 *tiledata, + Imaging im) { unsigned x0 = tileinfo->x0 - in->x0, y0 = tileinfo->y0 - in->y0; unsigned w = tileinfo->x1 - tileinfo->x0; unsigned h = tileinfo->y1 - tileinfo->y0; @@ -266,15 +280,19 @@ j2ku_graya_la(opj_image_t *in, const JPEG2KTILEINFO *tileinfo, unsigned x, y; - if (csiz == 3) + if (csiz == 3) { csiz = 4; - if (acsiz == 3) + } + if (acsiz == 3) { acsiz = 4; + } - if (shift < 0) + if (shift < 0) { offset += 1 << (-shift - 1); - if (ashift < 0) + } + if (ashift < 0) { aoffset += 1 << (-ashift - 1); + } atiledata = tiledata + csiz * w * h; @@ -286,15 +304,31 @@ j2ku_graya_la(opj_image_t *in, const JPEG2KTILEINFO *tileinfo, UINT32 word = 0, aword = 0, byte; switch (csiz) { - case 1: word = *data++; break; - case 2: word = *(const UINT16 *)data; data += 2; break; - case 4: word = *(const UINT32 *)data; data += 4; break; + case 1: + word = *data++; + break; + case 2: + word = *(const UINT16 *)data; + data += 2; + break; + case 4: + word = *(const UINT32 *)data; + data += 4; + break; } switch (acsiz) { - case 1: aword = *adata++; break; - case 2: aword = *(const UINT16 *)adata; adata += 2; break; - case 4: aword = *(const UINT32 *)adata; adata += 4; break; + case 1: + aword = *adata++; + break; + case 2: + aword = *(const UINT16 *)adata; + adata += 2; + break; + case 4: + aword = *(const UINT32 *)adata; + adata += 4; + break; } byte = j2ku_shift(offset + word, shift); @@ -306,9 +340,11 @@ j2ku_graya_la(opj_image_t *in, const JPEG2KTILEINFO *tileinfo, } static void -j2ku_srgb_rgb(opj_image_t *in, const JPEG2KTILEINFO *tileinfo, - const UINT8 *tiledata, Imaging im) -{ +j2ku_srgb_rgb( + opj_image_t *in, + const JPEG2KTILEINFO *tileinfo, + const UINT8 *tiledata, + Imaging im) { unsigned x0 = tileinfo->x0 - in->x0, y0 = tileinfo->y0 - in->y0; unsigned w = tileinfo->x1 - tileinfo->x0; unsigned h = tileinfo->y1 - tileinfo->y0; @@ -324,11 +360,13 @@ j2ku_srgb_rgb(opj_image_t *in, const JPEG2KTILEINFO *tileinfo, offsets[n] = in->comps[n].sgnd ? 1 << (in->comps[n].prec - 1) : 0; csiz[n] = (in->comps[n].prec + 7) >> 3; - if (csiz[n] == 3) + if (csiz[n] == 3) { csiz[n] = 4; + } - if (shifts[n] < 0) + if (shifts[n] < 0) { offsets[n] += 1 << (-shifts[n] - 1); + } cptr += csiz[n] * w * h; } @@ -336,17 +374,26 @@ j2ku_srgb_rgb(opj_image_t *in, const JPEG2KTILEINFO *tileinfo, for (y = 0; y < h; ++y) { const UINT8 *data[3]; UINT8 *row = (UINT8 *)im->image[y0 + y] + x0 * 4; - for (n = 0; n < 3; ++n) + for (n = 0; n < 3; ++n) { data[n] = &cdata[n][csiz[n] * y * w]; + } for (x = 0; x < w; ++x) { for (n = 0; n < 3; ++n) { UINT32 word = 0; switch (csiz[n]) { - case 1: word = *data[n]++; break; - case 2: word = *(const UINT16 *)data[n]; data[n] += 2; break; - case 4: word = *(const UINT32 *)data[n]; data[n] += 4; break; + case 1: + word = *data[n]++; + break; + case 2: + word = *(const UINT16 *)data[n]; + data[n] += 2; + break; + case 4: + word = *(const UINT32 *)data[n]; + data[n] += 4; + break; } row[n] = j2ku_shift(offsets[n] + word, shifts[n]); @@ -358,9 +405,11 @@ j2ku_srgb_rgb(opj_image_t *in, const JPEG2KTILEINFO *tileinfo, } static void -j2ku_sycc_rgb(opj_image_t *in, const JPEG2KTILEINFO *tileinfo, - const UINT8 *tiledata, Imaging im) -{ +j2ku_sycc_rgb( + opj_image_t *in, + const JPEG2KTILEINFO *tileinfo, + const UINT8 *tiledata, + Imaging im) { unsigned x0 = tileinfo->x0 - in->x0, y0 = tileinfo->y0 - in->y0; unsigned w = tileinfo->x1 - tileinfo->x0; unsigned h = tileinfo->y1 - tileinfo->y0; @@ -376,11 +425,13 @@ j2ku_sycc_rgb(opj_image_t *in, const JPEG2KTILEINFO *tileinfo, offsets[n] = in->comps[n].sgnd ? 1 << (in->comps[n].prec - 1) : 0; csiz[n] = (in->comps[n].prec + 7) >> 3; - if (csiz[n] == 3) + if (csiz[n] == 3) { csiz[n] = 4; + } - if (shifts[n] < 0) + if (shifts[n] < 0) { offsets[n] += 1 << (-shifts[n] - 1); + } cptr += csiz[n] * w * h; } @@ -389,17 +440,26 @@ j2ku_sycc_rgb(opj_image_t *in, const JPEG2KTILEINFO *tileinfo, const UINT8 *data[3]; UINT8 *row = (UINT8 *)im->image[y0 + y] + x0 * 4; UINT8 *row_start = row; - for (n = 0; n < 3; ++n) + for (n = 0; n < 3; ++n) { data[n] = &cdata[n][csiz[n] * y * w]; + } for (x = 0; x < w; ++x) { for (n = 0; n < 3; ++n) { UINT32 word = 0; switch (csiz[n]) { - case 1: word = *data[n]++; break; - case 2: word = *(const UINT16 *)data[n]; data[n] += 2; break; - case 4: word = *(const UINT32 *)data[n]; data[n] += 4; break; + case 1: + word = *data[n]++; + break; + case 2: + word = *(const UINT16 *)data[n]; + data[n] += 2; + break; + case 4: + word = *(const UINT32 *)data[n]; + data[n] += 4; + break; } row[n] = j2ku_shift(offsets[n] + word, shifts[n]); @@ -413,9 +473,11 @@ j2ku_sycc_rgb(opj_image_t *in, const JPEG2KTILEINFO *tileinfo, } static void -j2ku_srgba_rgba(opj_image_t *in, const JPEG2KTILEINFO *tileinfo, - const UINT8 *tiledata, Imaging im) -{ +j2ku_srgba_rgba( + opj_image_t *in, + const JPEG2KTILEINFO *tileinfo, + const UINT8 *tiledata, + Imaging im) { unsigned x0 = tileinfo->x0 - in->x0, y0 = tileinfo->y0 - in->y0; unsigned w = tileinfo->x1 - tileinfo->x0; unsigned h = tileinfo->y1 - tileinfo->y0; @@ -431,11 +493,13 @@ j2ku_srgba_rgba(opj_image_t *in, const JPEG2KTILEINFO *tileinfo, offsets[n] = in->comps[n].sgnd ? 1 << (in->comps[n].prec - 1) : 0; csiz[n] = (in->comps[n].prec + 7) >> 3; - if (csiz[n] == 3) + if (csiz[n] == 3) { csiz[n] = 4; + } - if (shifts[n] < 0) + if (shifts[n] < 0) { offsets[n] += 1 << (-shifts[n] - 1); + } cptr += csiz[n] * w * h; } @@ -443,17 +507,26 @@ j2ku_srgba_rgba(opj_image_t *in, const JPEG2KTILEINFO *tileinfo, for (y = 0; y < h; ++y) { const UINT8 *data[4]; UINT8 *row = (UINT8 *)im->image[y0 + y] + x0 * 4; - for (n = 0; n < 4; ++n) + for (n = 0; n < 4; ++n) { data[n] = &cdata[n][csiz[n] * y * w]; + } for (x = 0; x < w; ++x) { for (n = 0; n < 4; ++n) { UINT32 word = 0; switch (csiz[n]) { - case 1: word = *data[n]++; break; - case 2: word = *(const UINT16 *)data[n]; data[n] += 2; break; - case 4: word = *(const UINT32 *)data[n]; data[n] += 4; break; + case 1: + word = *data[n]++; + break; + case 2: + word = *(const UINT16 *)data[n]; + data[n] += 2; + break; + case 4: + word = *(const UINT32 *)data[n]; + data[n] += 4; + break; } row[n] = j2ku_shift(offsets[n] + word, shifts[n]); @@ -464,9 +537,11 @@ j2ku_srgba_rgba(opj_image_t *in, const JPEG2KTILEINFO *tileinfo, } static void -j2ku_sycca_rgba(opj_image_t *in, const JPEG2KTILEINFO *tileinfo, - const UINT8 *tiledata, Imaging im) -{ +j2ku_sycca_rgba( + opj_image_t *in, + const JPEG2KTILEINFO *tileinfo, + const UINT8 *tiledata, + Imaging im) { unsigned x0 = tileinfo->x0 - in->x0, y0 = tileinfo->y0 - in->y0; unsigned w = tileinfo->x1 - tileinfo->x0; unsigned h = tileinfo->y1 - tileinfo->y0; @@ -482,11 +557,13 @@ j2ku_sycca_rgba(opj_image_t *in, const JPEG2KTILEINFO *tileinfo, offsets[n] = in->comps[n].sgnd ? 1 << (in->comps[n].prec - 1) : 0; csiz[n] = (in->comps[n].prec + 7) >> 3; - if (csiz[n] == 3) + if (csiz[n] == 3) { csiz[n] = 4; + } - if (shifts[n] < 0) + if (shifts[n] < 0) { offsets[n] += 1 << (-shifts[n] - 1); + } cptr += csiz[n] * w * h; } @@ -495,17 +572,26 @@ j2ku_sycca_rgba(opj_image_t *in, const JPEG2KTILEINFO *tileinfo, const UINT8 *data[4]; UINT8 *row = (UINT8 *)im->image[y0 + y] + x0 * 4; UINT8 *row_start = row; - for (n = 0; n < 4; ++n) + for (n = 0; n < 4; ++n) { data[n] = &cdata[n][csiz[n] * y * w]; + } for (x = 0; x < w; ++x) { for (n = 0; n < 4; ++n) { UINT32 word = 0; switch (csiz[n]) { - case 1: word = *data[n]++; break; - case 2: word = *(const UINT16 *)data[n]; data[n] += 2; break; - case 4: word = *(const UINT32 *)data[n]; data[n] += 4; break; + case 1: + word = *data[n]++; + break; + case 2: + word = *(const UINT16 *)data[n]; + data[n] += 2; + break; + case 4: + word = *(const UINT32 *)data[n]; + data[n] += 4; + break; } row[n] = j2ku_shift(offsets[n] + word, shifts[n]); @@ -518,22 +604,22 @@ j2ku_sycca_rgba(opj_image_t *in, const JPEG2KTILEINFO *tileinfo, } static const struct j2k_decode_unpacker j2k_unpackers[] = { - { "L", OPJ_CLRSPC_GRAY, 1, j2ku_gray_l }, - { "I;16", OPJ_CLRSPC_GRAY, 1, j2ku_gray_i }, - { "I;16B", OPJ_CLRSPC_GRAY, 1, j2ku_gray_i }, - { "LA", OPJ_CLRSPC_GRAY, 2, j2ku_graya_la }, - { "RGB", OPJ_CLRSPC_GRAY, 1, j2ku_gray_rgb }, - { "RGB", OPJ_CLRSPC_GRAY, 2, j2ku_gray_rgb }, - { "RGB", OPJ_CLRSPC_SRGB, 3, j2ku_srgb_rgb }, - { "RGB", OPJ_CLRSPC_SYCC, 3, j2ku_sycc_rgb }, - { "RGB", OPJ_CLRSPC_SRGB, 4, j2ku_srgb_rgb }, - { "RGB", OPJ_CLRSPC_SYCC, 4, j2ku_sycc_rgb }, - { "RGBA", OPJ_CLRSPC_GRAY, 1, j2ku_gray_rgb }, - { "RGBA", OPJ_CLRSPC_GRAY, 2, j2ku_graya_la }, - { "RGBA", OPJ_CLRSPC_SRGB, 3, j2ku_srgb_rgb }, - { "RGBA", OPJ_CLRSPC_SYCC, 3, j2ku_sycc_rgb }, - { "RGBA", OPJ_CLRSPC_SRGB, 4, j2ku_srgba_rgba }, - { "RGBA", OPJ_CLRSPC_SYCC, 4, j2ku_sycca_rgba }, + {"L", OPJ_CLRSPC_GRAY, 1, j2ku_gray_l}, + {"I;16", OPJ_CLRSPC_GRAY, 1, j2ku_gray_i}, + {"I;16B", OPJ_CLRSPC_GRAY, 1, j2ku_gray_i}, + {"LA", OPJ_CLRSPC_GRAY, 2, j2ku_graya_la}, + {"RGB", OPJ_CLRSPC_GRAY, 1, j2ku_gray_rgb}, + {"RGB", OPJ_CLRSPC_GRAY, 2, j2ku_gray_rgb}, + {"RGB", OPJ_CLRSPC_SRGB, 3, j2ku_srgb_rgb}, + {"RGB", OPJ_CLRSPC_SYCC, 3, j2ku_sycc_rgb}, + {"RGB", OPJ_CLRSPC_SRGB, 4, j2ku_srgb_rgb}, + {"RGB", OPJ_CLRSPC_SYCC, 4, j2ku_sycc_rgb}, + {"RGBA", OPJ_CLRSPC_GRAY, 1, j2ku_gray_rgb}, + {"RGBA", OPJ_CLRSPC_GRAY, 2, j2ku_graya_la}, + {"RGBA", OPJ_CLRSPC_SRGB, 3, j2ku_srgb_rgb}, + {"RGBA", OPJ_CLRSPC_SYCC, 3, j2ku_sycc_rgb}, + {"RGBA", OPJ_CLRSPC_SRGB, 4, j2ku_srgba_rgba}, + {"RGBA", OPJ_CLRSPC_SYCC, 4, j2ku_sycca_rgba}, }; /* -------------------------------------------------------------------- */ @@ -548,17 +634,17 @@ enum { }; static int -j2k_decode_entry(Imaging im, ImagingCodecState state) -{ - JPEG2KDECODESTATE *context = (JPEG2KDECODESTATE *) state->context; +j2k_decode_entry(Imaging im, ImagingCodecState state) { + JPEG2KDECODESTATE *context = (JPEG2KDECODESTATE *)state->context; opj_stream_t *stream = NULL; opj_image_t *image = NULL; opj_codec_t *codec = NULL; opj_dparameters_t params; OPJ_COLOR_SPACE color_space; j2k_unpacker_t unpack = NULL; - size_t buffer_size = 0; - unsigned n; + size_t buffer_size = 0, tile_bytes = 0; + unsigned n, tile_height, tile_width; + int components; stream = opj_stream_create(BUFFER_SIZE, OPJ_TRUE); @@ -581,10 +667,11 @@ j2k_decode_entry(Imaging im, ImagingCodecState state) possibly support is 4GB. We can't go larger than this, because OpenJPEG truncates this value for the final box in the file, and the box lengths in OpenJPEG are currently 32 bit. */ - if (context->length < 0) + if (context->length < 0) { opj_stream_set_user_data_length(stream, 0xffffffff); - else + } else { opj_stream_set_user_data_length(stream, context->length); + } #endif /* Setup decompression context */ @@ -612,8 +699,8 @@ j2k_decode_entry(Imaging im, ImagingCodecState state) } /* Check that this image is something we can handle */ - if (image->numcomps < 1 || image->numcomps > 4 - || image->color_space == OPJ_CLRSPC_UNKNOWN) { + if (image->numcomps < 1 || image->numcomps > 4 || + image->color_space == OPJ_CLRSPC_UNKNOWN) { state->errcode = IMAGING_CODEC_BROKEN; state->state = J2K_STATE_FAILED; goto quick_exit; @@ -653,15 +740,21 @@ j2k_decode_entry(Imaging im, ImagingCodecState state) if (color_space == OPJ_CLRSPC_UNSPECIFIED) { switch (image->numcomps) { - case 1: case 2: color_space = OPJ_CLRSPC_GRAY; break; - case 3: case 4: color_space = OPJ_CLRSPC_SRGB; break; + case 1: + case 2: + color_space = OPJ_CLRSPC_GRAY; + break; + case 3: + case 4: + color_space = OPJ_CLRSPC_SRGB; + break; } } - for (n = 0; n < sizeof(j2k_unpackers) / sizeof (j2k_unpackers[0]); ++n) { - if (color_space == j2k_unpackers[n].color_space - && image->numcomps == j2k_unpackers[n].components - && strcmp (im->mode, j2k_unpackers[n].mode) == 0) { + for (n = 0; n < sizeof(j2k_unpackers) / sizeof(j2k_unpackers[0]); ++n) { + if (color_space == j2k_unpackers[n].color_space && + image->numcomps == j2k_unpackers[n].components && + strcmp(im->mode, j2k_unpackers[n].mode) == 0) { unpack = j2k_unpackers[n].unpacker; break; } @@ -680,21 +773,25 @@ j2k_decode_entry(Imaging im, ImagingCodecState state) OPJ_BOOL should_continue; unsigned correction = (1 << params.cp_reduce) - 1; - if (!opj_read_tile_header(codec, - stream, - &tile_info.tile_index, - &tile_info.data_size, - &tile_info.x0, &tile_info.y0, - &tile_info.x1, &tile_info.y1, - &tile_info.nb_comps, - &should_continue)) { + if (!opj_read_tile_header( + codec, + stream, + &tile_info.tile_index, + &tile_info.data_size, + &tile_info.x0, + &tile_info.y0, + &tile_info.x1, + &tile_info.y1, + &tile_info.nb_comps, + &should_continue)) { state->errcode = IMAGING_CODEC_BROKEN; state->state = J2K_STATE_FAILED; goto quick_exit; } - if (!should_continue) + if (!should_continue) { break; + } /* Adjust the tile co-ordinates based on the reduction (OpenJPEG doesn't do this for us) */ @@ -703,9 +800,45 @@ j2k_decode_entry(Imaging im, ImagingCodecState state) tile_info.x1 = (tile_info.x1 + correction) >> context->reduce; tile_info.y1 = (tile_info.y1 + correction) >> context->reduce; + /* Check the tile bounds; if the tile is outside the image area, + or if it has a negative width or height (i.e. the coordinates are + swapped), bail. */ + if (tile_info.x0 >= tile_info.x1 || tile_info.y0 >= tile_info.y1 || + tile_info.x0 < 0 || tile_info.y0 < 0 || + (OPJ_UINT32)tile_info.x0 < image->x0 || + (OPJ_UINT32)tile_info.y0 < image->y0 || + (OPJ_INT32)(tile_info.x1 - image->x0) > im->xsize || + (OPJ_INT32)(tile_info.y1 - image->y0) > im->ysize) { + state->errcode = IMAGING_CODEC_BROKEN; + state->state = J2K_STATE_FAILED; + goto quick_exit; + } + + /* Sometimes the tile_info.datasize we get back from openjpeg + is less than numcomps*w*h, and we overflow in the + shuffle stage */ + + tile_width = tile_info.x1 - tile_info.x0; + tile_height = tile_info.y1 - tile_info.y0; + components = tile_info.nb_comps == 3 ? 4 : tile_info.nb_comps; + if ((tile_width > UINT_MAX / components) || + (tile_height > UINT_MAX / components) || + (tile_width > UINT_MAX / (tile_height * components)) || + (tile_height > UINT_MAX / (tile_width * components))) { + state->errcode = IMAGING_CODEC_BROKEN; + state->state = J2K_STATE_FAILED; + goto quick_exit; + } + + tile_bytes = tile_width * tile_height * components; + + if (tile_bytes > tile_info.data_size) { + tile_info.data_size = tile_bytes; + } + if (buffer_size < tile_info.data_size) { - /* malloc check ok, tile_info.data_size from openjpeg */ - UINT8 *new = realloc (state->buffer, tile_info.data_size); + /* malloc check ok, overflow and tile size sanity check above */ + UINT8 *new = realloc(state->buffer, tile_info.data_size); if (!new) { state->errcode = IMAGING_CODEC_MEMORY; state->state = J2K_STATE_FAILED; @@ -715,25 +848,12 @@ j2k_decode_entry(Imaging im, ImagingCodecState state) buffer_size = tile_info.data_size; } - if (!opj_decode_tile_data(codec, - tile_info.tile_index, - (OPJ_BYTE *)state->buffer, - tile_info.data_size, - stream)) { - state->errcode = IMAGING_CODEC_BROKEN; - state->state = J2K_STATE_FAILED; - goto quick_exit; - } - - /* Check the tile bounds; if the tile is outside the image area, - or if it has a negative width or height (i.e. the coordinates are - swapped), bail. */ - if (tile_info.x0 >= tile_info.x1 - || tile_info.y0 >= tile_info.y1 - || tile_info.x0 < image->x0 - || tile_info.y0 < image->y0 - || tile_info.x1 - image->x0 > im->xsize - || tile_info.y1 - image->y0 > im->ysize) { + if (!opj_decode_tile_data( + codec, + tile_info.tile_index, + (OPJ_BYTE *)state->buffer, + tile_info.data_size, + stream)) { state->errcode = IMAGING_CODEC_BROKEN; state->state = J2K_STATE_FAILED; goto quick_exit; @@ -752,34 +872,36 @@ j2k_decode_entry(Imaging im, ImagingCodecState state) state->errcode = IMAGING_CODEC_END; if (context->pfile) { - if(fclose(context->pfile)){ + if (fclose(context->pfile)) { context->pfile = NULL; } } - quick_exit: - if (codec) +quick_exit: + if (codec) { opj_destroy_codec(codec); - if (image) + } + if (image) { opj_image_destroy(image); - if (stream) + } + if (stream) { opj_stream_destroy(stream); + } return -1; } int -ImagingJpeg2KDecode(Imaging im, ImagingCodecState state, UINT8* buf, Py_ssize_t bytes) -{ - - if (bytes){ +ImagingJpeg2KDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t bytes) { + if (bytes) { state->errcode = IMAGING_CODEC_BROKEN; state->state = J2K_STATE_FAILED; return -1; } - if (state->state == J2K_STATE_DONE || state->state == J2K_STATE_FAILED) + if (state->state == J2K_STATE_DONE || state->state == J2K_STATE_FAILED) { return -1; + } if (state->state == J2K_STATE_START) { state->state = J2K_STATE_DECODING; @@ -804,7 +926,7 @@ ImagingJpeg2KDecodeCleanup(ImagingCodecState state) { JPEG2KDECODESTATE *context = (JPEG2KDECODESTATE *)state->context; if (context->error_msg) { - free ((void *)context->error_msg); + free((void *)context->error_msg); } context->error_msg = NULL; @@ -813,8 +935,7 @@ ImagingJpeg2KDecodeCleanup(ImagingCodecState state) { } const char * -ImagingJpeg2KVersion(void) -{ +ImagingJpeg2KVersion(void) { return opj_version(); } diff --git a/src/libImaging/Jpeg2KEncode.c b/src/libImaging/Jpeg2KEncode.c index aef1a4b94..5829cf37f 100644 --- a/src/libImaging/Jpeg2KEncode.c +++ b/src/libImaging/Jpeg2KEncode.c @@ -19,26 +19,24 @@ #include "Jpeg2K.h" -#define CINEMA_24_CS_LENGTH 1302083 -#define CINEMA_48_CS_LENGTH 651041 +#define CINEMA_24_CS_LENGTH 1302083 +#define CINEMA_48_CS_LENGTH 651041 #define COMP_24_CS_MAX_LENGTH 1041666 -#define COMP_48_CS_MAX_LENGTH 520833 +#define COMP_48_CS_MAX_LENGTH 520833 /* -------------------------------------------------------------------- */ /* Error handler */ /* -------------------------------------------------------------------- */ static void -j2k_error(const char *msg, void *client_data) -{ - JPEG2KENCODESTATE *state = (JPEG2KENCODESTATE *) client_data; +j2k_error(const char *msg, void *client_data) { + JPEG2KENCODESTATE *state = (JPEG2KENCODESTATE *)client_data; free((void *)state->error_msg); state->error_msg = strdup(msg); } static void -j2k_warn(const char *msg, void *client_data) -{ +j2k_warn(const char *msg, void *client_data) { // Null handler } @@ -47,26 +45,23 @@ j2k_warn(const char *msg, void *client_data) /* -------------------------------------------------------------------- */ static OPJ_SIZE_T -j2k_write(void *p_buffer, OPJ_SIZE_T p_nb_bytes, void *p_user_data) -{ +j2k_write(void *p_buffer, OPJ_SIZE_T p_nb_bytes, void *p_user_data) { ImagingCodecState state = (ImagingCodecState)p_user_data; - int result; + unsigned int result; result = _imaging_write_pyFd(state->fd, p_buffer, p_nb_bytes); return result ? result : (OPJ_SIZE_T)-1; } - static OPJ_OFF_T -j2k_skip(OPJ_OFF_T p_nb_bytes, void *p_user_data) -{ +j2k_skip(OPJ_OFF_T p_nb_bytes, void *p_user_data) { ImagingCodecState state = (ImagingCodecState)p_user_data; char *buffer; int result; /* Explicitly write zeros */ - buffer = calloc(p_nb_bytes,1); + buffer = calloc(p_nb_bytes, 1); if (!buffer) { return (OPJ_OFF_T)-1; } @@ -79,8 +74,7 @@ j2k_skip(OPJ_OFF_T p_nb_bytes, void *p_user_data) } static OPJ_BOOL -j2k_seek(OPJ_OFF_T p_nb_bytes, void *p_user_data) -{ +j2k_seek(OPJ_OFF_T p_nb_bytes, void *p_user_data) { ImagingCodecState state = (ImagingCodecState)p_user_data; off_t pos = 0; @@ -94,29 +88,25 @@ j2k_seek(OPJ_OFF_T p_nb_bytes, void *p_user_data) /* Encoder */ /* -------------------------------------------------------------------- */ -typedef void (*j2k_pack_tile_t)(Imaging im, UINT8 *buf, - unsigned x0, unsigned y0, - unsigned w, unsigned h); +typedef void (*j2k_pack_tile_t)( + Imaging im, UINT8 *buf, unsigned x0, unsigned y0, unsigned w, unsigned h); static void -j2k_pack_l(Imaging im, UINT8 *buf, - unsigned x0, unsigned y0, unsigned w, unsigned h) -{ +j2k_pack_l(Imaging im, UINT8 *buf, unsigned x0, unsigned y0, unsigned w, unsigned h) { UINT8 *ptr = buf; - unsigned x,y; + unsigned x, y; for (y = 0; y < h; ++y) { UINT8 *data = (UINT8 *)(im->image[y + y0] + x0); - for (x = 0; x < w; ++x) + for (x = 0; x < w; ++x) { *ptr++ = *data++; + } } } static void -j2k_pack_i16(Imaging im, UINT8 *buf, - unsigned x0, unsigned y0, unsigned w, unsigned h) -{ +j2k_pack_i16(Imaging im, UINT8 *buf, unsigned x0, unsigned y0, unsigned w, unsigned h) { UINT8 *ptr = buf; - unsigned x,y; + unsigned x, y; for (y = 0; y < h; ++y) { UINT8 *data = (UINT8 *)(im->image[y + y0] + x0); for (x = 0; x < w; ++x) { @@ -126,14 +116,11 @@ j2k_pack_i16(Imaging im, UINT8 *buf, } } - static void -j2k_pack_la(Imaging im, UINT8 *buf, - unsigned x0, unsigned y0, unsigned w, unsigned h) -{ +j2k_pack_la(Imaging im, UINT8 *buf, unsigned x0, unsigned y0, unsigned w, unsigned h) { UINT8 *ptr = buf; UINT8 *ptra = buf + w * h; - unsigned x,y; + unsigned x, y; for (y = 0; y < h; ++y) { UINT8 *data = (UINT8 *)(im->image[y + y0] + 4 * x0); for (x = 0; x < w; ++x) { @@ -145,13 +132,11 @@ j2k_pack_la(Imaging im, UINT8 *buf, } static void -j2k_pack_rgb(Imaging im, UINT8 *buf, - unsigned x0, unsigned y0, unsigned w, unsigned h) -{ +j2k_pack_rgb(Imaging im, UINT8 *buf, unsigned x0, unsigned y0, unsigned w, unsigned h) { UINT8 *pr = buf; UINT8 *pg = pr + w * h; UINT8 *pb = pg + w * h; - unsigned x,y; + unsigned x, y; for (y = 0; y < h; ++y) { UINT8 *data = (UINT8 *)(im->image[y + y0] + 4 * x0); for (x = 0; x < w; ++x) { @@ -164,14 +149,13 @@ j2k_pack_rgb(Imaging im, UINT8 *buf, } static void -j2k_pack_rgba(Imaging im, UINT8 *buf, - unsigned x0, unsigned y0, unsigned w, unsigned h) -{ +j2k_pack_rgba( + Imaging im, UINT8 *buf, unsigned x0, unsigned y0, unsigned w, unsigned h) { UINT8 *pr = buf; UINT8 *pg = pr + w * h; UINT8 *pb = pg + w * h; UINT8 *pa = pb + w * h; - unsigned x,y; + unsigned x, y; for (y = 0; y < h; ++y) { UINT8 *data = (UINT8 *)(im->image[y + y0] + 4 * x0); for (x = 0; x < w; ++x) { @@ -191,8 +175,7 @@ enum { }; static void -j2k_set_cinema_params(Imaging im, int components, opj_cparameters_t *params) -{ +j2k_set_cinema_params(Imaging im, int components, opj_cparameters_t *params) { float rate; int n; @@ -214,8 +197,9 @@ j2k_set_cinema_params(Imaging im, int components, opj_cparameters_t *params) params->irreversible = 1; if (params->cp_cinema == OPJ_CINEMA4K_24) { - float max_rate = ((float)(components * im->xsize * im->ysize * 8) - / (CINEMA_24_CS_LENGTH * 8)); + float max_rate = + ((float)(components * im->xsize * im->ysize * 8) / + (CINEMA_24_CS_LENGTH * 8)); params->POC[0].tile = 1; params->POC[0].resno0 = 0; @@ -238,27 +222,32 @@ j2k_set_cinema_params(Imaging im, int components, opj_cparameters_t *params) if (params->tcp_rates[0] == 0) { params->tcp_rates[n] = max_rate; } else { - rate = ((float)(components * im->xsize * im->ysize * 8) - / (params->tcp_rates[n] * 8)); - if (rate > CINEMA_24_CS_LENGTH) + rate = + ((float)(components * im->xsize * im->ysize * 8) / + (params->tcp_rates[n] * 8)); + if (rate > CINEMA_24_CS_LENGTH) { params->tcp_rates[n] = max_rate; + } } } params->max_comp_size = COMP_24_CS_MAX_LENGTH; } else { - float max_rate = ((float)(components * im->xsize * im->ysize * 8) - / (CINEMA_48_CS_LENGTH * 8)); + float max_rate = + ((float)(components * im->xsize * im->ysize * 8) / + (CINEMA_48_CS_LENGTH * 8)); for (n = 0; n < params->tcp_numlayers; ++n) { rate = 0; if (params->tcp_rates[0] == 0) { params->tcp_rates[n] = max_rate; } else { - rate = ((float)(components * im->xsize * im->ysize * 8) - / (params->tcp_rates[n] * 8)); - if (rate > CINEMA_48_CS_LENGTH) + rate = + ((float)(components * im->xsize * im->ysize * 8) / + (params->tcp_rates[n] * 8)); + if (rate > CINEMA_48_CS_LENGTH) { params->tcp_rates[n] = max_rate; + } } } @@ -267,8 +256,7 @@ j2k_set_cinema_params(Imaging im, int components, opj_cparameters_t *params) } static int -j2k_encode_entry(Imaging im, ImagingCodecState state) -{ +j2k_encode_entry(Imaging im, ImagingCodecState state) { JPEG2KENCODESTATE *context = (JPEG2KENCODESTATE *)state->context; opj_stream_t *stream = NULL; opj_image_t *image = NULL; @@ -309,35 +297,35 @@ j2k_encode_entry(Imaging im, ImagingCodecState state) #endif /* Setup an opj_image */ - if (strcmp (im->mode, "L") == 0) { + if (strcmp(im->mode, "L") == 0) { components = 1; color_space = OPJ_CLRSPC_GRAY; pack = j2k_pack_l; - } else if (strcmp (im->mode, "I;16") == 0){ + } else if (strcmp(im->mode, "I;16") == 0) { components = 1; color_space = OPJ_CLRSPC_GRAY; pack = j2k_pack_i16; prec = 16; bpp = 12; - } else if (strcmp (im->mode, "I;16B") == 0){ + } else if (strcmp(im->mode, "I;16B") == 0) { components = 1; color_space = OPJ_CLRSPC_GRAY; pack = j2k_pack_i16; prec = 16; bpp = 12; - } else if (strcmp (im->mode, "LA") == 0) { + } else if (strcmp(im->mode, "LA") == 0) { components = 2; color_space = OPJ_CLRSPC_GRAY; pack = j2k_pack_la; - } else if (strcmp (im->mode, "RGB") == 0) { + } else if (strcmp(im->mode, "RGB") == 0) { components = 3; color_space = OPJ_CLRSPC_SRGB; pack = j2k_pack_rgb; - } else if (strcmp (im->mode, "YCbCr") == 0) { + } else if (strcmp(im->mode, "YCbCr") == 0) { components = 3; color_space = OPJ_CLRSPC_SYCC; pack = j2k_pack_rgb; - } else if (strcmp (im->mode, "RGBA") == 0) { + } else if (strcmp(im->mode, "RGBA") == 0) { components = 4; color_space = OPJ_CLRSPC_SRGB; pack = j2k_pack_rgba; @@ -396,9 +384,11 @@ j2k_encode_entry(Imaging im, ImagingCodecState state) Py_ssize_t n; float *pq; - if (len) { - if (len > sizeof(params.tcp_rates) / sizeof(params.tcp_rates[0])) - len = sizeof(params.tcp_rates)/sizeof(params.tcp_rates[0]); + if (len > 0) { + if ((unsigned)len > + sizeof(params.tcp_rates) / sizeof(params.tcp_rates[0])) { + len = sizeof(params.tcp_rates) / sizeof(params.tcp_rates[0]); + } params.tcp_numlayers = (int)len; @@ -423,19 +413,20 @@ j2k_encode_entry(Imaging im, ImagingCodecState state) params.cp_disto_alloc = 1; } - if (context->num_resolutions) + if (context->num_resolutions) { params.numresolution = context->num_resolutions; + } - if (context->cblk_width >= 4 && context->cblk_width <= 1024 - && context->cblk_height >= 4 && context->cblk_height <= 1024 - && context->cblk_width * context->cblk_height <= 4096) { + if (context->cblk_width >= 4 && context->cblk_width <= 1024 && + context->cblk_height >= 4 && context->cblk_height <= 1024 && + context->cblk_width * context->cblk_height <= 4096) { params.cblockw_init = context->cblk_width; params.cblockh_init = context->cblk_height; } - if (context->precinct_width >= 4 && context->precinct_height >= 4 - && context->precinct_width >= context->cblk_width - && context->precinct_height > context->cblk_height) { + if (context->precinct_width >= 4 && context->precinct_height >= 4 && + context->precinct_width >= context->cblk_width && + context->precinct_height > context->cblk_height) { params.prcw_init[0] = context->precinct_width; params.prch_init[0] = context->precinct_height; params.res_spec = 1; @@ -449,24 +440,27 @@ j2k_encode_entry(Imaging im, ImagingCodecState state) params.cp_cinema = context->cinema_mode; switch (params.cp_cinema) { - case OPJ_OFF: - params.cp_rsiz = OPJ_STD_RSIZ; - break; - case OPJ_CINEMA2K_24: - case OPJ_CINEMA2K_48: - params.cp_rsiz = OPJ_CINEMA2K; - if (params.numresolution > 6) - params.numresolution = 6; - break; - case OPJ_CINEMA4K_24: - params.cp_rsiz = OPJ_CINEMA4K; - if (params.numresolution > 7) - params.numresolution = 7; - break; + case OPJ_OFF: + params.cp_rsiz = OPJ_STD_RSIZ; + break; + case OPJ_CINEMA2K_24: + case OPJ_CINEMA2K_48: + params.cp_rsiz = OPJ_CINEMA2K; + if (params.numresolution > 6) { + params.numresolution = 6; + } + break; + case OPJ_CINEMA4K_24: + params.cp_rsiz = OPJ_CINEMA4K; + if (params.numresolution > 7) { + params.numresolution = 7; + } + break; } - if (context->cinema_mode != OPJ_OFF) + if (context->cinema_mode != OPJ_OFF) { j2k_set_cinema_params(im, components, ¶ms); + } /* Set up the reference grid in the image */ image->x0 = params.image_offset_x0; @@ -496,24 +490,24 @@ j2k_encode_entry(Imaging im, ImagingCodecState state) } /* Write each tile */ - tiles_x = (im->xsize + (params.image_offset_x0 - params.cp_tx0) - + tile_width - 1) / tile_width; - tiles_y = (im->ysize + (params.image_offset_y0 - params.cp_ty0) - + tile_height - 1) / tile_height; + tiles_x = (im->xsize + (params.image_offset_x0 - params.cp_tx0) + tile_width - 1) / + tile_width; + tiles_y = (im->ysize + (params.image_offset_y0 - params.cp_ty0) + tile_height - 1) / + tile_height; /* check for integer overflow for the malloc line, checking any expression that may multiply either tile_width or tile_height */ _overflow_scale_factor = components * prec; - if (( tile_width > UINT_MAX / _overflow_scale_factor ) || - ( tile_height > UINT_MAX / _overflow_scale_factor ) || - ( tile_width > UINT_MAX / (tile_height * _overflow_scale_factor )) || - ( tile_height > UINT_MAX / (tile_width * _overflow_scale_factor ))) { + if ((tile_width > UINT_MAX / _overflow_scale_factor) || + (tile_height > UINT_MAX / _overflow_scale_factor) || + (tile_width > UINT_MAX / (tile_height * _overflow_scale_factor)) || + (tile_height > UINT_MAX / (tile_width * _overflow_scale_factor))) { state->errcode = IMAGING_CODEC_BROKEN; state->state = J2K_STATE_FAILED; goto quick_exit; } /* malloc check ok, checked for overflow above */ - state->buffer = malloc (tile_width * tile_height * components * prec / 8); + state->buffer = malloc(tile_width * tile_height * components * prec / 8); if (!state->buffer) { state->errcode = IMAGING_CODEC_BROKEN; state->state = J2K_STATE_FAILED; @@ -526,10 +520,12 @@ j2k_encode_entry(Imaging im, ImagingCodecState state) unsigned ty1 = ty0 + tile_height; unsigned pixy, pixh; - if (ty0 < params.image_offset_y0) + if (ty0 < params.image_offset_y0) { ty0 = params.image_offset_y0; - if (ty1 > ysiz) + } + if (ty1 > ysiz) { ty1 = ysiz; + } pixy = ty0 - params.image_offset_y0; pixh = ty1 - ty0; @@ -540,10 +536,12 @@ j2k_encode_entry(Imaging im, ImagingCodecState state) unsigned pixx, pixw; unsigned data_size; - if (tx0 < params.image_offset_x0) + if (tx0 < params.image_offset_x0) { tx0 = params.image_offset_x0; - if (tx1 > xsiz) + } + if (tx1 > xsiz) { tx1 = xsiz; + } pixx = tx0 - params.image_offset_x0; pixw = tx1 - tx0; @@ -552,8 +550,7 @@ j2k_encode_entry(Imaging im, ImagingCodecState state) data_size = pixw * pixh * components * prec / 8; - if (!opj_write_tile(codec, tile_ndx++, state->buffer, - data_size, stream)) { + if (!opj_write_tile(codec, tile_ndx++, state->buffer, data_size, stream)) { state->errcode = IMAGING_CODEC_BROKEN; state->state = J2K_STATE_FAILED; goto quick_exit; @@ -571,25 +568,27 @@ j2k_encode_entry(Imaging im, ImagingCodecState state) state->state = J2K_STATE_DONE; ret = -1; - quick_exit: - if (codec) +quick_exit: + if (codec) { opj_destroy_codec(codec); - if (image) + } + if (image) { opj_image_destroy(image); - if (stream) + } + if (stream) { opj_stream_destroy(stream); + } return ret; } int -ImagingJpeg2KEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) -{ - if (state->state == J2K_STATE_FAILED) +ImagingJpeg2KEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) { + if (state->state == J2K_STATE_FAILED) { return -1; + } if (state->state == J2K_STATE_START) { - state->state = J2K_STATE_ENCODING; return j2k_encode_entry(im, state); @@ -611,12 +610,12 @@ ImagingJpeg2KEncodeCleanup(ImagingCodecState state) { context->quality_layers = NULL; } - if (context->error_msg) - free ((void *)context->error_msg); + if (context->error_msg) { + free((void *)context->error_msg); + } context->error_msg = NULL; - return -1; } diff --git a/src/libImaging/JpegDecode.c b/src/libImaging/JpegDecode.c index 39d96de53..55d10a81a 100644 --- a/src/libImaging/JpegDecode.c +++ b/src/libImaging/JpegDecode.c @@ -21,10 +21,9 @@ * See the README file for details on usage and redistribution. */ - #include "Imaging.h" -#ifdef HAVE_LIBJPEG +#ifdef HAVE_LIBJPEG #undef HAVE_PROTOTYPES #undef HAVE_STDLIB_H @@ -37,7 +36,6 @@ #include "Jpeg.h" - #define STRINGIFY(x) #x #define TOSTRING(x) STRINGIFY(x) @@ -50,20 +48,19 @@ char *libjpeg_turbo_version = NULL; #endif int -ImagingJpegUseJCSExtensions() -{ +ImagingJpegUseJCSExtensions() { int use_jcs_extensions = 0; - #ifdef JCS_EXTENSIONS - #if defined(LIBJPEG_TURBO_VERSION_NUMBER) - #if LIBJPEG_TURBO_VERSION_NUMBER >= 1002010 - use_jcs_extensions = 1; - #endif - #else - if (libjpeg_turbo_version) { - use_jcs_extensions = strcmp(libjpeg_turbo_version, "1.2.1") >= 0; - } - #endif - #endif +#ifdef JCS_EXTENSIONS +#if defined(LIBJPEG_TURBO_VERSION_NUMBER) +#if LIBJPEG_TURBO_VERSION_NUMBER >= 1002010 + use_jcs_extensions = 1; +#endif +#else + if (libjpeg_turbo_version) { + use_jcs_extensions = strcmp(libjpeg_turbo_version, "1.2.1") >= 0; + } +#endif +#endif return use_jcs_extensions; } @@ -72,24 +69,19 @@ ImagingJpegUseJCSExtensions() /* -------------------------------------------------------------------- */ METHODDEF(void) -stub(j_decompress_ptr cinfo) -{ - /* empty */ -} +stub(j_decompress_ptr cinfo) { /* empty */ } METHODDEF(boolean) -fill_input_buffer(j_decompress_ptr cinfo) -{ +fill_input_buffer(j_decompress_ptr cinfo) { /* Suspension */ return FALSE; } METHODDEF(void) -skip_input_data(j_decompress_ptr cinfo, long num_bytes) -{ - JPEGSOURCE* source = (JPEGSOURCE*) cinfo->src; +skip_input_data(j_decompress_ptr cinfo, long num_bytes) { + JPEGSOURCE *source = (JPEGSOURCE *)cinfo->src; - if (num_bytes > (long) source->pub.bytes_in_buffer) { + if (num_bytes > (long)source->pub.bytes_in_buffer) { /* We need to skip more data than we have in the buffer. This will force the JPEG library to suspend decoding. */ source->skip = num_bytes - source->pub.bytes_in_buffer; @@ -103,50 +95,42 @@ skip_input_data(j_decompress_ptr cinfo, long num_bytes) } } - GLOBAL(void) -jpeg_buffer_src(j_decompress_ptr cinfo, JPEGSOURCE* source) -{ - cinfo->src = (void*) source; +jpeg_buffer_src(j_decompress_ptr cinfo, JPEGSOURCE *source) { + cinfo->src = (void *)source; - /* Prepare for suspending reader */ - source->pub.init_source = stub; - source->pub.fill_input_buffer = fill_input_buffer; - source->pub.skip_input_data = skip_input_data; - source->pub.resync_to_restart = jpeg_resync_to_restart; - source->pub.term_source = stub; - source->pub.bytes_in_buffer = 0; /* forces fill_input_buffer on first read */ + /* Prepare for suspending reader */ + source->pub.init_source = stub; + source->pub.fill_input_buffer = fill_input_buffer; + source->pub.skip_input_data = skip_input_data; + source->pub.resync_to_restart = jpeg_resync_to_restart; + source->pub.term_source = stub; + source->pub.bytes_in_buffer = 0; /* forces fill_input_buffer on first read */ - source->skip = 0; + source->skip = 0; } - /* -------------------------------------------------------------------- */ /* Error handler */ /* -------------------------------------------------------------------- */ METHODDEF(void) -error(j_common_ptr cinfo) -{ - JPEGERROR* error; - error = (JPEGERROR*) cinfo->err; - longjmp(error->setjmp_buffer, 1); +error(j_common_ptr cinfo) { + JPEGERROR *error; + error = (JPEGERROR *)cinfo->err; + longjmp(error->setjmp_buffer, 1); } METHODDEF(void) -output(j_common_ptr cinfo) -{ - /* nothing */ -} +output(j_common_ptr cinfo) { /* nothing */ } /* -------------------------------------------------------------------- */ /* Decoder */ /* -------------------------------------------------------------------- */ int -ImagingJpegDecode(Imaging im, ImagingCodecState state, UINT8* buf, Py_ssize_t bytes) -{ - JPEGSTATE* context = (JPEGSTATE*) state->context; +ImagingJpegDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t bytes) { + JPEGSTATE *context = (JPEGSTATE *)state->context; int ok; if (setjmp(context->error.setjmp_buffer)) { @@ -157,7 +141,6 @@ ImagingJpegDecode(Imaging im, ImagingCodecState state, UINT8* buf, Py_ssize_t by } if (!state->state) { - /* Setup decompression context */ context->cinfo.err = jpeg_std_error(&context->error.pub); context->error.pub.error_exit = error; @@ -167,7 +150,6 @@ ImagingJpegDecode(Imaging im, ImagingCodecState state, UINT8* buf, Py_ssize_t by /* Ready to decode */ state->state = 1; - } /* Load the source buffer */ @@ -176,140 +158,147 @@ ImagingJpegDecode(Imaging im, ImagingCodecState state, UINT8* buf, Py_ssize_t by if (context->source.skip > 0) { skip_input_data(&context->cinfo, context->source.skip); - if (context->source.skip > 0) + if (context->source.skip > 0) { return context->source.pub.next_input_byte - buf; + } } switch (state->state) { + case 1: - case 1: + /* Read JPEG header, until we find an image body. */ + do { + /* Note that we cannot return unless we have decoded + as much data as possible. */ + ok = jpeg_read_header(&context->cinfo, FALSE); - /* Read JPEG header, until we find an image body. */ - do { + } while (ok == JPEG_HEADER_TABLES_ONLY); - /* Note that we cannot return unless we have decoded - as much data as possible. */ - ok = jpeg_read_header(&context->cinfo, FALSE); + if (ok == JPEG_SUSPENDED) { + break; + } - } while (ok == JPEG_HEADER_TABLES_ONLY); + /* Decoder settings */ - if (ok == JPEG_SUSPENDED) - break; + /* jpegmode indicates whats in the file; if not set, we'll + trust the decoder */ + if (strcmp(context->jpegmode, "L") == 0) { + context->cinfo.jpeg_color_space = JCS_GRAYSCALE; + } else if (strcmp(context->jpegmode, "RGB") == 0) { + context->cinfo.jpeg_color_space = JCS_RGB; + } else if (strcmp(context->jpegmode, "CMYK") == 0) { + context->cinfo.jpeg_color_space = JCS_CMYK; + } else if (strcmp(context->jpegmode, "YCbCr") == 0) { + context->cinfo.jpeg_color_space = JCS_YCbCr; + } else if (strcmp(context->jpegmode, "YCbCrK") == 0) { + context->cinfo.jpeg_color_space = JCS_YCCK; + } - /* Decoder settings */ - - /* jpegmode indicates whats in the file; if not set, we'll - trust the decoder */ - if (strcmp(context->jpegmode, "L") == 0) - context->cinfo.jpeg_color_space = JCS_GRAYSCALE; - else if (strcmp(context->jpegmode, "RGB") == 0) - context->cinfo.jpeg_color_space = JCS_RGB; - else if (strcmp(context->jpegmode, "CMYK") == 0) - context->cinfo.jpeg_color_space = JCS_CMYK; - else if (strcmp(context->jpegmode, "YCbCr") == 0) - context->cinfo.jpeg_color_space = JCS_YCbCr; - else if (strcmp(context->jpegmode, "YCbCrK") == 0) { - context->cinfo.jpeg_color_space = JCS_YCCK; - } - - /* rawmode indicates what we want from the decoder. if not - set, conversions are disabled */ - if (strcmp(context->rawmode, "L") == 0) - context->cinfo.out_color_space = JCS_GRAYSCALE; - else if (strcmp(context->rawmode, "RGB") == 0) - context->cinfo.out_color_space = JCS_RGB; - #ifdef JCS_EXTENSIONS - else if (strcmp(context->rawmode, "RGBX") == 0) + /* rawmode indicates what we want from the decoder. if not + set, conversions are disabled */ + if (strcmp(context->rawmode, "L") == 0) { + context->cinfo.out_color_space = JCS_GRAYSCALE; + } else if (strcmp(context->rawmode, "RGB") == 0) { + context->cinfo.out_color_space = JCS_RGB; + } +#ifdef JCS_EXTENSIONS + else if (strcmp(context->rawmode, "RGBX") == 0) { context->cinfo.out_color_space = JCS_EXT_RGBX; - #endif - else if (strcmp(context->rawmode, "CMYK") == 0 || - strcmp(context->rawmode, "CMYK;I") == 0) - context->cinfo.out_color_space = JCS_CMYK; - else if (strcmp(context->rawmode, "YCbCr") == 0) - context->cinfo.out_color_space = JCS_YCbCr; - else if (strcmp(context->rawmode, "YCbCrK") == 0) - context->cinfo.out_color_space = JCS_YCCK; - else { - /* Disable decoder conversions */ - context->cinfo.jpeg_color_space = JCS_UNKNOWN; - context->cinfo.out_color_space = JCS_UNKNOWN; - } + } +#endif + else if ( + strcmp(context->rawmode, "CMYK") == 0 || + strcmp(context->rawmode, "CMYK;I") == 0) { + context->cinfo.out_color_space = JCS_CMYK; + } else if (strcmp(context->rawmode, "YCbCr") == 0) { + context->cinfo.out_color_space = JCS_YCbCr; + } else if (strcmp(context->rawmode, "YCbCrK") == 0) { + context->cinfo.out_color_space = JCS_YCCK; + } else { + /* Disable decoder conversions */ + context->cinfo.jpeg_color_space = JCS_UNKNOWN; + context->cinfo.out_color_space = JCS_UNKNOWN; + } - if (context->scale > 1) { - context->cinfo.scale_num = 1; - context->cinfo.scale_denom = context->scale; - } - if (context->draft) { - context->cinfo.do_fancy_upsampling = FALSE; - context->cinfo.dct_method = JDCT_FASTEST; - } + if (context->scale > 1) { + context->cinfo.scale_num = 1; + context->cinfo.scale_denom = context->scale; + } + if (context->draft) { + context->cinfo.do_fancy_upsampling = FALSE; + context->cinfo.dct_method = JDCT_FASTEST; + } - state->state++; - /* fall through */ + state->state++; + /* fall through */ - case 2: + case 2: - /* Set things up for decompression (this processes the entire - file if necessary to return data line by line) */ - if (!jpeg_start_decompress(&context->cinfo)) - break; - - state->state++; - /* fall through */ - - case 3: - - /* Decompress a single line of data */ - ok = 1; - while (state->y < state->ysize) { - ok = jpeg_read_scanlines(&context->cinfo, &state->buffer, 1); - if (ok != 1) + /* Set things up for decompression (this processes the entire + file if necessary to return data line by line) */ + if (!jpeg_start_decompress(&context->cinfo)) { break; - state->shuffle((UINT8*) im->image[state->y + state->yoff] + - state->xoff * im->pixelsize, state->buffer, - state->xsize); - state->y++; - } - if (ok != 1) - break; - state->state++; - /* fall through */ + } - case 4: + state->state++; + /* fall through */ - /* Finish decompression */ - if (!jpeg_finish_decompress(&context->cinfo)) { - /* FIXME: add strictness mode test */ - if (state->y < state->ysize) + case 3: + + /* Decompress a single line of data */ + ok = 1; + while (state->y < state->ysize) { + ok = jpeg_read_scanlines(&context->cinfo, &state->buffer, 1); + if (ok != 1) { + break; + } + state->shuffle( + (UINT8 *)im->image[state->y + state->yoff] + + state->xoff * im->pixelsize, + state->buffer, + state->xsize); + state->y++; + } + if (ok != 1) { break; - } + } + state->state++; + /* fall through */ - /* Clean up */ - jpeg_destroy_decompress(&context->cinfo); - /* if (jerr.pub.num_warnings) return BROKEN; */ - return -1; + case 4: + /* Finish decompression */ + if (!jpeg_finish_decompress(&context->cinfo)) { + /* FIXME: add strictness mode test */ + if (state->y < state->ysize) { + break; + } + } + + /* Clean up */ + jpeg_destroy_decompress(&context->cinfo); + /* if (jerr.pub.num_warnings) return BROKEN; */ + return -1; } /* Return number of bytes consumed */ return context->source.pub.next_input_byte - buf; - } /* -------------------------------------------------------------------- */ /* Cleanup */ /* -------------------------------------------------------------------- */ -int ImagingJpegDecodeCleanup(ImagingCodecState state){ - /* called to free the decompression engine when the decode terminates - due to a corrupt or truncated image - */ - JPEGSTATE* context = (JPEGSTATE*) state->context; +int +ImagingJpegDecodeCleanup(ImagingCodecState state) { + /* called to free the decompression engine when the decode terminates + due to a corrupt or truncated image + */ + JPEGSTATE *context = (JPEGSTATE *)state->context; - /* Clean up */ - jpeg_destroy_decompress(&context->cinfo); - return -1; + /* Clean up */ + jpeg_destroy_decompress(&context->cinfo); + return -1; } #endif - diff --git a/src/libImaging/JpegEncode.c b/src/libImaging/JpegEncode.c index 10ad886e0..a44debcaf 100644 --- a/src/libImaging/JpegEncode.c +++ b/src/libImaging/JpegEncode.c @@ -19,10 +19,9 @@ * See the README file for details on usage and redistribution. */ - #include "Imaging.h" -#ifdef HAVE_LIBJPEG +#ifdef HAVE_LIBJPEG #undef HAVE_PROTOTYPES #undef HAVE_STDLIB_H @@ -40,73 +39,62 @@ /* -------------------------------------------------------------------- */ METHODDEF(void) -stub(j_compress_ptr cinfo) -{ - /* empty */ -} +stub(j_compress_ptr cinfo) { /* empty */ } METHODDEF(boolean) -empty_output_buffer (j_compress_ptr cinfo) -{ +empty_output_buffer(j_compress_ptr cinfo) { /* Suspension */ return FALSE; } GLOBAL(void) -jpeg_buffer_dest(j_compress_ptr cinfo, JPEGDESTINATION* destination) -{ - cinfo->dest = (void*) destination; +jpeg_buffer_dest(j_compress_ptr cinfo, JPEGDESTINATION *destination) { + cinfo->dest = (void *)destination; destination->pub.init_destination = stub; destination->pub.empty_output_buffer = empty_output_buffer; destination->pub.term_destination = stub; } - /* -------------------------------------------------------------------- */ /* Error handler */ /* -------------------------------------------------------------------- */ METHODDEF(void) -error(j_common_ptr cinfo) -{ - JPEGERROR* error; - error = (JPEGERROR*) cinfo->err; - (*cinfo->err->output_message) (cinfo); +error(j_common_ptr cinfo) { + JPEGERROR *error; + error = (JPEGERROR *)cinfo->err; + (*cinfo->err->output_message)(cinfo); longjmp(error->setjmp_buffer, 1); } - /* -------------------------------------------------------------------- */ -/* Encoder */ +/* Encoder */ /* -------------------------------------------------------------------- */ int -ImagingJpegEncode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) -{ - JPEGENCODERSTATE* context = (JPEGENCODERSTATE*) state->context; +ImagingJpegEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) { + JPEGENCODERSTATE *context = (JPEGENCODERSTATE *)state->context; int ok; if (setjmp(context->error.setjmp_buffer)) { - /* JPEG error handler */ - jpeg_destroy_compress(&context->cinfo); - state->errcode = IMAGING_CODEC_BROKEN; - return -1; + /* JPEG error handler */ + jpeg_destroy_compress(&context->cinfo); + state->errcode = IMAGING_CODEC_BROKEN; + return -1; } if (!state->state) { - - /* Setup compression context (very similar to the decoder) */ - context->cinfo.err = jpeg_std_error(&context->error.pub); - context->error.pub.error_exit = error; - jpeg_create_compress(&context->cinfo); - jpeg_buffer_dest(&context->cinfo, &context->destination); + /* Setup compression context (very similar to the decoder) */ + context->cinfo.err = jpeg_std_error(&context->error.pub); + context->error.pub.error_exit = error; + jpeg_create_compress(&context->cinfo); + jpeg_buffer_dest(&context->cinfo, &context->destination); context->extra_offset = 0; - /* Ready to encode */ - state->state = 1; - + /* Ready to encode */ + state->state = 1; } /* Load the destination buffer */ @@ -114,224 +102,239 @@ ImagingJpegEncode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) context->destination.pub.free_in_buffer = bytes; switch (state->state) { + case 1: - case 1: + context->cinfo.image_width = state->xsize; + context->cinfo.image_height = state->ysize; - context->cinfo.image_width = state->xsize; - context->cinfo.image_height = state->ysize; + switch (state->bits) { + case 8: + context->cinfo.input_components = 1; + context->cinfo.in_color_space = JCS_GRAYSCALE; + break; + case 24: + context->cinfo.input_components = 3; + if (strcmp(im->mode, "YCbCr") == 0) { + context->cinfo.in_color_space = JCS_YCbCr; + } else { + context->cinfo.in_color_space = JCS_RGB; + } + break; + case 32: + context->cinfo.input_components = 4; + context->cinfo.in_color_space = JCS_CMYK; +#ifdef JCS_EXTENSIONS + if (strcmp(context->rawmode, "RGBX") == 0) { + context->cinfo.in_color_space = JCS_EXT_RGBX; + } +#endif + break; + default: + state->errcode = IMAGING_CODEC_CONFIG; + return -1; + } - switch (state->bits) { - case 8: - context->cinfo.input_components = 1; - context->cinfo.in_color_space = JCS_GRAYSCALE; - break; - case 24: - context->cinfo.input_components = 3; - if (strcmp(im->mode, "YCbCr") == 0) - context->cinfo.in_color_space = JCS_YCbCr; - else - context->cinfo.in_color_space = JCS_RGB; - break; - case 32: - context->cinfo.input_components = 4; - context->cinfo.in_color_space = JCS_CMYK; - #ifdef JCS_EXTENSIONS - if (strcmp(context->rawmode, "RGBX") == 0) - context->cinfo.in_color_space = JCS_EXT_RGBX; - #endif - break; - default: - state->errcode = IMAGING_CODEC_CONFIG; - return -1; - } + /* Compressor configuration */ + jpeg_set_defaults(&context->cinfo); - /* Compressor configuration */ - jpeg_set_defaults(&context->cinfo); + /* Use custom quantization tables */ + if (context->qtables) { + int i; + int quality = 100; + int last_q = 0; + if (context->quality != -1) { + quality = context->quality; + } + for (i = 0; i < context->qtablesLen; i++) { + jpeg_add_quant_table( + &context->cinfo, + i, + &context->qtables[i * DCTSIZE2], + quality, + FALSE); + context->cinfo.comp_info[i].quant_tbl_no = i; + last_q = i; + } + if (context->qtablesLen == 1) { + // jpeg_set_defaults created two qtables internally, but we only + // wanted one. + jpeg_add_quant_table( + &context->cinfo, 1, &context->qtables[0], quality, FALSE); + } + for (i = last_q; i < context->cinfo.num_components; i++) { + context->cinfo.comp_info[i].quant_tbl_no = last_q; + } + } else if (context->quality != -1) { + jpeg_set_quality(&context->cinfo, context->quality, TRUE); + } - /* Use custom quantization tables */ - if (context->qtables) { - int i; - int quality = 100; - int last_q = 0; - if (context->quality > 0) { - quality = context->quality; - } - for (i = 0; i < context->qtablesLen; i++) { - // TODO: Should add support for none baseline - jpeg_add_quant_table(&context->cinfo, i, &context->qtables[i * DCTSIZE2], - quality, TRUE); - context->cinfo.comp_info[i].quant_tbl_no = i; - last_q = i; - } - if (context->qtablesLen == 1) { - // jpeg_set_defaults created two qtables internally, but we only wanted one. - jpeg_add_quant_table(&context->cinfo, 1, &context->qtables[0], - quality, TRUE); - } - for (i = last_q; i < context->cinfo.num_components; i++) { - context->cinfo.comp_info[i].quant_tbl_no = last_q; - } - } else if (context->quality > 0) { - jpeg_set_quality(&context->cinfo, context->quality, 1); - } + /* Set subsampling options */ + switch (context->subsampling) { + case 0: /* 1x1 1x1 1x1 (4:4:4) : None */ + { + context->cinfo.comp_info[0].h_samp_factor = 1; + context->cinfo.comp_info[0].v_samp_factor = 1; + context->cinfo.comp_info[1].h_samp_factor = 1; + context->cinfo.comp_info[1].v_samp_factor = 1; + context->cinfo.comp_info[2].h_samp_factor = 1; + context->cinfo.comp_info[2].v_samp_factor = 1; + break; + } + case 1: /* 2x1, 1x1, 1x1 (4:2:2) : Medium */ + { + context->cinfo.comp_info[0].h_samp_factor = 2; + context->cinfo.comp_info[0].v_samp_factor = 1; + context->cinfo.comp_info[1].h_samp_factor = 1; + context->cinfo.comp_info[1].v_samp_factor = 1; + context->cinfo.comp_info[2].h_samp_factor = 1; + context->cinfo.comp_info[2].v_samp_factor = 1; + break; + } + case 2: /* 2x2, 1x1, 1x1 (4:2:0) : High */ + { + context->cinfo.comp_info[0].h_samp_factor = 2; + context->cinfo.comp_info[0].v_samp_factor = 2; + context->cinfo.comp_info[1].h_samp_factor = 1; + context->cinfo.comp_info[1].v_samp_factor = 1; + context->cinfo.comp_info[2].h_samp_factor = 1; + context->cinfo.comp_info[2].v_samp_factor = 1; + break; + } + default: { + /* Use the lib's default */ + break; + } + } + if (context->progressive) { + jpeg_simple_progression(&context->cinfo); + } + context->cinfo.smoothing_factor = context->smooth; + context->cinfo.optimize_coding = (boolean)context->optimize; + if (context->xdpi > 0 && context->ydpi > 0) { + context->cinfo.write_JFIF_header = TRUE; + context->cinfo.density_unit = 1; /* dots per inch */ + context->cinfo.X_density = context->xdpi; + context->cinfo.Y_density = context->ydpi; + } + switch (context->streamtype) { + case 1: + /* tables only -- not yet implemented */ + state->errcode = IMAGING_CODEC_CONFIG; + return -1; + case 2: + /* image only */ + jpeg_suppress_tables(&context->cinfo, TRUE); + jpeg_start_compress(&context->cinfo, FALSE); + /* suppress extra section */ + context->extra_offset = context->extra_size; + break; + default: + /* interchange stream */ + jpeg_start_compress(&context->cinfo, TRUE); + break; + } + state->state++; + /* fall through */ - /* Set subsampling options */ - switch (context->subsampling) - { - case 0: /* 1x1 1x1 1x1 (4:4:4) : None */ - { - context->cinfo.comp_info[0].h_samp_factor = 1; - context->cinfo.comp_info[0].v_samp_factor = 1; - context->cinfo.comp_info[1].h_samp_factor = 1; - context->cinfo.comp_info[1].v_samp_factor = 1; - context->cinfo.comp_info[2].h_samp_factor = 1; - context->cinfo.comp_info[2].v_samp_factor = 1; - break; - } - case 1: /* 2x1, 1x1, 1x1 (4:2:2) : Medium */ - { - context->cinfo.comp_info[0].h_samp_factor = 2; - context->cinfo.comp_info[0].v_samp_factor = 1; - context->cinfo.comp_info[1].h_samp_factor = 1; - context->cinfo.comp_info[1].v_samp_factor = 1; - context->cinfo.comp_info[2].h_samp_factor = 1; - context->cinfo.comp_info[2].v_samp_factor = 1; - break; - } - case 2: /* 2x2, 1x1, 1x1 (4:2:0) : High */ - { - context->cinfo.comp_info[0].h_samp_factor = 2; - context->cinfo.comp_info[0].v_samp_factor = 2; - context->cinfo.comp_info[1].h_samp_factor = 1; - context->cinfo.comp_info[1].v_samp_factor = 1; - context->cinfo.comp_info[2].h_samp_factor = 1; - context->cinfo.comp_info[2].v_samp_factor = 1; - break; - } - default: - { - /* Use the lib's default */ - break; - } - } - if (context->progressive) - jpeg_simple_progression(&context->cinfo); - context->cinfo.smoothing_factor = context->smooth; - context->cinfo.optimize_coding = (boolean) context->optimize; - if (context->xdpi > 0 && context->ydpi > 0) { - context->cinfo.density_unit = 1; /* dots per inch */ - context->cinfo.X_density = context->xdpi; - context->cinfo.Y_density = context->ydpi; - } - switch (context->streamtype) { - case 1: - /* tables only -- not yet implemented */ - state->errcode = IMAGING_CODEC_CONFIG; - return -1; - case 2: - /* image only */ - jpeg_suppress_tables(&context->cinfo, TRUE); - jpeg_start_compress(&context->cinfo, FALSE); - /* suppress extra section */ - context->extra_offset = context->extra_size; - break; - default: - /* interchange stream */ - jpeg_start_compress(&context->cinfo, TRUE); - break; - } - state->state++; - /* fall through */ - - case 2: - // check for exif len + 'APP1' header bytes - if (context->rawExifLen + 5 > context->destination.pub.free_in_buffer){ - break; - } - //add exif header - if (context->rawExifLen > 0){ - jpeg_write_marker(&context->cinfo, JPEG_APP0+1, - (unsigned char*)context->rawExif, context->rawExifLen); - } - - state->state++; - /* fall through */ - case 3: - - if (context->extra) { - /* copy extra buffer to output buffer */ - unsigned int n = context->extra_size - context->extra_offset; - if (n > context->destination.pub.free_in_buffer) - n = context->destination.pub.free_in_buffer; - memcpy(context->destination.pub.next_output_byte, - context->extra + context->extra_offset, n); - context->destination.pub.next_output_byte += n; - context->destination.pub.free_in_buffer -= n; - context->extra_offset += n; - if (context->extra_offset >= context->extra_size) - state->state++; - else + case 2: + // check for exif len + 'APP1' header bytes + if (context->rawExifLen + 5 > context->destination.pub.free_in_buffer) { break; - } else - state->state++; + } + // add exif header + if (context->rawExifLen > 0) { + jpeg_write_marker( + &context->cinfo, + JPEG_APP0 + 1, + (unsigned char *)context->rawExif, + context->rawExifLen); + } - case 4: - if (1024 > context->destination.pub.free_in_buffer){ + state->state++; + /* fall through */ + case 3: + + if (context->extra) { + /* copy extra buffer to output buffer */ + unsigned int n = context->extra_size - context->extra_offset; + if (n > context->destination.pub.free_in_buffer) { + n = context->destination.pub.free_in_buffer; + } + memcpy( + context->destination.pub.next_output_byte, + context->extra + context->extra_offset, + n); + context->destination.pub.next_output_byte += n; + context->destination.pub.free_in_buffer -= n; + context->extra_offset += n; + if (context->extra_offset >= context->extra_size) { + state->state++; + } else { + break; + } + } else { + state->state++; + } + + case 4: + if (1024 > context->destination.pub.free_in_buffer) { + break; + } + + ok = 1; + while (state->y < state->ysize) { + state->shuffle( + state->buffer, + (UINT8 *)im->image[state->y + state->yoff] + + state->xoff * im->pixelsize, + state->xsize); + ok = jpeg_write_scanlines(&context->cinfo, &state->buffer, 1); + if (ok != 1) { + break; + } + state->y++; + } + + if (ok != 1) { + break; + } + state->state++; + /* fall through */ + + case 5: + + /* Finish compression */ + if (context->destination.pub.free_in_buffer < 100) { + break; + } + jpeg_finish_compress(&context->cinfo); + + /* Clean up */ + if (context->extra) { + free(context->extra); + context->extra = NULL; + } + if (context->rawExif) { + free(context->rawExif); + context->rawExif = NULL; + } + if (context->qtables) { + free(context->qtables); + context->qtables = NULL; + } + + jpeg_destroy_compress(&context->cinfo); + /* if (jerr.pub.num_warnings) return BROKEN; */ + state->errcode = IMAGING_CODEC_END; break; - } - - ok = 1; - while (state->y < state->ysize) { - state->shuffle(state->buffer, - (UINT8*) im->image[state->y + state->yoff] + - state->xoff * im->pixelsize, state->xsize); - ok = jpeg_write_scanlines(&context->cinfo, &state->buffer, 1); - if (ok != 1) - break; - state->y++; - } - - if (ok != 1) - break; - state->state++; - /* fall through */ - - case 5: - - /* Finish compression */ - if (context->destination.pub.free_in_buffer < 100) - break; - jpeg_finish_compress(&context->cinfo); - - /* Clean up */ - if (context->extra) { - free(context->extra); - context->extra = NULL; - } - if (context->rawExif) { - free(context->rawExif); - context->rawExif = NULL; - } - if (context->qtables) { - free(context->qtables); - context->qtables = NULL; - } - - jpeg_destroy_compress(&context->cinfo); - /* if (jerr.pub.num_warnings) return BROKEN; */ - state->errcode = IMAGING_CODEC_END; - break; - } /* Return number of bytes in output buffer */ return context->destination.pub.next_output_byte - buf; - } -const char* -ImagingJpegVersion(void) -{ +const char * +ImagingJpegVersion(void) { static char version[20]; sprintf(version, "%d.%d", JPEG_LIB_VERSION / 10, JPEG_LIB_VERSION % 10); return version; diff --git a/src/libImaging/Matrix.c b/src/libImaging/Matrix.c index 5cc7795a4..137ed242a 100644 --- a/src/libImaging/Matrix.c +++ b/src/libImaging/Matrix.c @@ -13,62 +13,61 @@ * See the README file for information on usage and redistribution. */ - #include "Imaging.h" - -#define CLIPF(v) ((v <= 0.0) ? 0 : (v >= 255.0F) ? 255 : (UINT8) v) - +#define CLIPF(v) ((v <= 0.0) ? 0 : (v >= 255.0F) ? 255 : (UINT8)v) Imaging -ImagingConvertMatrix(Imaging im, const char *mode, float m[]) -{ +ImagingConvertMatrix(Imaging im, const char *mode, float m[]) { Imaging imOut; int x, y; /* Assume there's enough data in the buffer */ - if (!im) - return (Imaging) ImagingError_ModeError(); + if (!im) { + return (Imaging)ImagingError_ModeError(); + } if (strcmp(mode, "L") == 0 && im->bands == 3) { + imOut = ImagingNewDirty("L", im->xsize, im->ysize); + if (!imOut) { + return NULL; + } - imOut = ImagingNewDirty("L", im->xsize, im->ysize); - if (!imOut) - return NULL; + for (y = 0; y < im->ysize; y++) { + UINT8 *in = (UINT8 *)im->image[y]; + UINT8 *out = (UINT8 *)imOut->image[y]; - for (y = 0; y < im->ysize; y++) { - UINT8* in = (UINT8*) im->image[y]; - UINT8* out = (UINT8*) imOut->image[y]; - - for (x = 0; x < im->xsize; x++) { - float v = m[0]*in[0] + m[1]*in[1] + m[2]*in[2] + m[3] + 0.5; - out[x] = CLIPF(v); - in += 4; - } - } + for (x = 0; x < im->xsize; x++) { + float v = m[0] * in[0] + m[1] * in[1] + m[2] * in[2] + m[3] + 0.5; + out[x] = CLIPF(v); + in += 4; + } + } } else if (strlen(mode) == 3 && im->bands == 3) { + imOut = ImagingNewDirty(mode, im->xsize, im->ysize); + if (!imOut) { + return NULL; + } - imOut = ImagingNewDirty(mode, im->xsize, im->ysize); - if (!imOut) - return NULL; + for (y = 0; y < im->ysize; y++) { + UINT8 *in = (UINT8 *)im->image[y]; + UINT8 *out = (UINT8 *)imOut->image[y]; - for (y = 0; y < im->ysize; y++) { - UINT8* in = (UINT8*) im->image[y]; - UINT8* out = (UINT8*) imOut->image[y]; - - for (x = 0; x < im->xsize; x++) { - float v0 = m[0]*in[0] + m[1]*in[1] + m[2]*in[2] + m[3] + 0.5; - float v1 = m[4]*in[0] + m[5]*in[1] + m[6]*in[2] + m[7] + 0.5; - float v2 = m[8]*in[0] + m[9]*in[1] + m[10]*in[2] + m[11] + 0.5; - out[0] = CLIPF(v0); - out[1] = CLIPF(v1); - out[2] = CLIPF(v2); - in += 4; out += 4; - } - } - } else - return (Imaging) ImagingError_ModeError(); + for (x = 0; x < im->xsize; x++) { + float v0 = m[0] * in[0] + m[1] * in[1] + m[2] * in[2] + m[3] + 0.5; + float v1 = m[4] * in[0] + m[5] * in[1] + m[6] * in[2] + m[7] + 0.5; + float v2 = m[8] * in[0] + m[9] * in[1] + m[10] * in[2] + m[11] + 0.5; + out[0] = CLIPF(v0); + out[1] = CLIPF(v1); + out[2] = CLIPF(v2); + in += 4; + out += 4; + } + } + } else { + return (Imaging)ImagingError_ModeError(); + } return imOut; } diff --git a/src/libImaging/ModeFilter.c b/src/libImaging/ModeFilter.c index 5237d0732..757cbc3fb 100644 --- a/src/libImaging/ModeFilter.c +++ b/src/libImaging/ModeFilter.c @@ -16,8 +16,7 @@ #include "Imaging.h" Imaging -ImagingModeFilter(Imaging im, int size) -{ +ImagingModeFilter(Imaging im, int size) { Imaging imOut; int x, y, i; int xx, yy; @@ -25,19 +24,20 @@ ImagingModeFilter(Imaging im, int size) UINT8 maxpixel; int histogram[256]; - if (!im || im->bands != 1 || im->type != IMAGING_TYPE_UINT8) - return (Imaging) ImagingError_ModeError(); + if (!im || im->bands != 1 || im->type != IMAGING_TYPE_UINT8) { + return (Imaging)ImagingError_ModeError(); + } imOut = ImagingNewDirty(im->mode, im->xsize, im->ysize); - if (!imOut) + if (!imOut) { return NULL; + } size = size / 2; for (y = 0; y < imOut->ysize; y++) { - UINT8* out = &IMAGING_PIXEL_L(imOut, 0, y); + UINT8 *out = &IMAGING_PIXEL_L(imOut, 0, y); for (x = 0; x < imOut->xsize; x++) { - /* calculate histogram over current area */ /* FIXME: brute force! to improve, update the histogram @@ -46,30 +46,33 @@ ImagingModeFilter(Imaging im, int size) the added complexity... */ memset(histogram, 0, sizeof(histogram)); - for (yy = y - size; yy <= y + size; yy++) + for (yy = y - size; yy <= y + size; yy++) { if (yy >= 0 && yy < imOut->ysize) { - UINT8* in = &IMAGING_PIXEL_L(im, 0, yy); - for (xx = x - size; xx <= x + size; xx++) - if (xx >= 0 && xx < imOut->xsize) + UINT8 *in = &IMAGING_PIXEL_L(im, 0, yy); + for (xx = x - size; xx <= x + size; xx++) { + if (xx >= 0 && xx < imOut->xsize) { histogram[in[xx]]++; + } + } } + } /* find most frequent pixel value in this region */ maxpixel = 0; maxcount = histogram[maxpixel]; - for (i = 1; i < 256; i++) + for (i = 1; i < 256; i++) { if (histogram[i] > maxcount) { maxcount = histogram[i]; - maxpixel = (UINT8) i; + maxpixel = (UINT8)i; } + } - if (maxcount > 2) + if (maxcount > 2) { out[x] = maxpixel; - else + } else { out[x] = IMAGING_PIXEL_L(im, x, y); - + } } - } ImagingCopyPalette(imOut, im); diff --git a/src/libImaging/Negative.c b/src/libImaging/Negative.c index 4dedcb245..70b96c397 100644 --- a/src/libImaging/Negative.c +++ b/src/libImaging/Negative.c @@ -8,7 +8,7 @@ * FIXME: Maybe this should be implemented using ImagingPoint() * * history: - * 95-11-27 fl: Created + * 95-11-27 fl: Created * * Copyright (c) Fredrik Lundh 1995. * Copyright (c) Secret Labs AB 1997. @@ -16,27 +16,27 @@ * See the README file for information on usage and redistribution. */ - #include "Imaging.h" - Imaging -ImagingNegative(Imaging im) -{ +ImagingNegative(Imaging im) { Imaging imOut; int x, y; - if (!im) - return (Imaging) ImagingError_ModeError(); + if (!im) { + return (Imaging)ImagingError_ModeError(); + } imOut = ImagingNewDirty(im->mode, im->xsize, im->ysize); - if (!imOut) - return NULL; + if (!imOut) { + return NULL; + } - for (y = 0; y < im->ysize; y++) - for (x = 0; x < im->linesize; x++) - imOut->image[y][x] = ~im->image[y][x]; + for (y = 0; y < im->ysize; y++) { + for (x = 0; x < im->linesize; x++) { + imOut->image[y][x] = ~im->image[y][x]; + } + } return imOut; } - diff --git a/src/libImaging/Offset.c b/src/libImaging/Offset.c index b3d9425fb..91ee91083 100644 --- a/src/libImaging/Offset.c +++ b/src/libImaging/Offset.c @@ -5,7 +5,7 @@ * offset an image in x and y directions * * history: - * 96-07-22 fl: Created + * 96-07-22 fl: Created * 98-11-01 cgw@pgt.com: Fixed negative-array index bug * * Copyright (c) Fredrik Lundh 1996. @@ -14,48 +14,51 @@ * See the README file for information on usage and redistribution. */ - #include "Imaging.h" - Imaging -ImagingOffset(Imaging im, int xoffset, int yoffset) -{ +ImagingOffset(Imaging im, int xoffset, int yoffset) { int x, y; Imaging imOut; - if (!im) - return (Imaging) ImagingError_ModeError(); + if (!im) { + return (Imaging)ImagingError_ModeError(); + } imOut = ImagingNewDirty(im->mode, im->xsize, im->ysize); - if (!imOut) - return NULL; + if (!imOut) { + return NULL; + } ImagingCopyPalette(imOut, im); /* make offsets positive to avoid negative coordinates */ xoffset %= im->xsize; xoffset = im->xsize - xoffset; - if (xoffset < 0) - xoffset += im->xsize; + if (xoffset < 0) { + xoffset += im->xsize; + } yoffset %= im->ysize; yoffset = im->ysize - yoffset; - if (yoffset < 0) - yoffset += im->ysize; + if (yoffset < 0) { + yoffset += im->ysize; + } -#define OFFSET(image)\ - for (y = 0; y < im->ysize; y++)\ - for (x = 0; x < im->xsize; x++) {\ - int yi = (y + yoffset) % im->ysize;\ - int xi = (x + xoffset) % im->xsize;\ - imOut->image[y][x] = im->image[yi][xi];\ - } +#define OFFSET(image) \ + for (y = 0; y < im->ysize; y++) { \ + for (x = 0; x < im->xsize; x++) { \ + int yi = (y + yoffset) % im->ysize; \ + int xi = (x + xoffset) % im->xsize; \ + imOut->image[y][x] = im->image[yi][xi]; \ + } \ + } - if (im->image8) - OFFSET(image8) - else - OFFSET(image32) + if (im->image8) { + OFFSET(image8) + } else { + OFFSET(image32) + } return imOut; } diff --git a/src/libImaging/Pack.c b/src/libImaging/Pack.c index a239464d4..2fdee919f 100644 --- a/src/libImaging/Pack.c +++ b/src/libImaging/Pack.c @@ -1,4 +1,4 @@ - /* +/* * The Python Imaging Library. * $Id$ * @@ -25,7 +25,6 @@ * See the README file for information on usage and redistribution. */ - #include "Imaging.h" #define R 0 @@ -41,20 +40,28 @@ /* byte swapping macros */ -#define C16N\ - (out[0]=tmp[0], out[1]=tmp[1]); -#define C16S\ - (out[1]=tmp[0], out[0]=tmp[1]); -#define C32N\ - (out[0]=tmp[0], out[1]=tmp[1], out[2]=tmp[2], out[3]=tmp[3]); -#define C32S\ - (out[3]=tmp[0], out[2]=tmp[1], out[1]=tmp[2], out[0]=tmp[3]); -#define C64N\ - (out[0]=tmp[0], out[1]=tmp[1], out[2]=tmp[2], out[3]=tmp[3],\ - out[4]=tmp[4], out[5]=tmp[5], out[6]=tmp[6], out[7]=tmp[7]); -#define C64S\ - (out[7]=tmp[0], out[6]=tmp[1], out[5]=tmp[2], out[4]=tmp[3],\ - out[3]=tmp[4], out[2]=tmp[5], out[1]=tmp[6], out[0]=tmp[7]); +#define C16N (out[0] = tmp[0], out[1] = tmp[1]); +#define C16S (out[1] = tmp[0], out[0] = tmp[1]); +#define C32N (out[0] = tmp[0], out[1] = tmp[1], out[2] = tmp[2], out[3] = tmp[3]); +#define C32S (out[3] = tmp[0], out[2] = tmp[1], out[1] = tmp[2], out[0] = tmp[3]); +#define C64N \ + (out[0] = tmp[0], \ + out[1] = tmp[1], \ + out[2] = tmp[2], \ + out[3] = tmp[3], \ + out[4] = tmp[4], \ + out[5] = tmp[5], \ + out[6] = tmp[6], \ + out[7] = tmp[7]); +#define C64S \ + (out[7] = tmp[0], \ + out[6] = tmp[1], \ + out[5] = tmp[2], \ + out[4] = tmp[3], \ + out[3] = tmp[4], \ + out[2] = tmp[5], \ + out[1] = tmp[6], \ + out[0] = tmp[7]); #ifdef WORDS_BIGENDIAN #define C16B C16N @@ -72,134 +79,138 @@ #define C64L C64N #endif - static void -pack1(UINT8* out, const UINT8* in, int pixels) -{ +pack1(UINT8 *out, const UINT8 *in, int pixels) { int i, m, b; /* bilevel (black is 0) */ - b = 0; m = 128; + b = 0; + m = 128; for (i = 0; i < pixels; i++) { - if (in[i] != 0) + if (in[i] != 0) { b |= m; + } m >>= 1; if (m == 0) { *out++ = b; - b = 0; m = 128; + b = 0; + m = 128; } } - if (m != 128) + if (m != 128) { *out++ = b; + } } static void -pack1I(UINT8* out, const UINT8* in, int pixels) -{ +pack1I(UINT8 *out, const UINT8 *in, int pixels) { int i, m, b; /* bilevel (black is 1) */ - b = 0; m = 128; + b = 0; + m = 128; for (i = 0; i < pixels; i++) { - if (in[i] == 0) + if (in[i] == 0) { b |= m; + } m >>= 1; if (m == 0) { *out++ = b; - b = 0; m = 128; + b = 0; + m = 128; } } - if (m != 128) + if (m != 128) { *out++ = b; + } } static void -pack1R(UINT8* out, const UINT8* in, int pixels) -{ +pack1R(UINT8 *out, const UINT8 *in, int pixels) { int i, m, b; /* bilevel, lsb first (black is 0) */ - b = 0; m = 1; + b = 0; + m = 1; for (i = 0; i < pixels; i++) { - if (in[i] != 0) + if (in[i] != 0) { b |= m; + } m <<= 1; - if (m == 256){ + if (m == 256) { *out++ = b; - b = 0; m = 1; + b = 0; + m = 1; } } - if (m != 1) + if (m != 1) { *out++ = b; + } } static void -pack1IR(UINT8* out, const UINT8* in, int pixels) -{ +pack1IR(UINT8 *out, const UINT8 *in, int pixels) { int i, m, b; /* bilevel, lsb first (black is 1) */ - b = 0; m = 1; + b = 0; + m = 1; for (i = 0; i < pixels; i++) { - if (in[i] == 0) + if (in[i] == 0) { b |= m; + } m <<= 1; - if (m == 256){ + if (m == 256) { *out++ = b; - b = 0; m = 1; + b = 0; + m = 1; } } - if (m != 1) + if (m != 1) { *out++ = b; + } } static void -pack1L(UINT8* out, const UINT8* in, int pixels) -{ +pack1L(UINT8 *out, const UINT8 *in, int pixels) { int i; /* bilevel, stored as bytes */ - for (i = 0; i < pixels; i++) + for (i = 0; i < pixels; i++) { out[i] = (in[i] != 0) ? 255 : 0; + } } static void -packP4(UINT8* out, const UINT8* in, int pixels) -{ +packP4(UINT8 *out, const UINT8 *in, int pixels) { while (pixels >= 2) { - *out++ = (in[0] << 4) | - (in[1] & 15); - in += 2; pixels -= 2; + *out++ = (in[0] << 4) | (in[1] & 15); + in += 2; + pixels -= 2; } - if (pixels) + if (pixels) { out[0] = (in[0] << 4); + } } static void -packP2(UINT8* out, const UINT8* in, int pixels) -{ +packP2(UINT8 *out, const UINT8 *in, int pixels) { while (pixels >= 4) { - *out++ = (in[0] << 6) | - ((in[1] & 3) << 4) | - ((in[2] & 3) << 2) | - (in[3] & 3); - in += 4; pixels -= 4; + *out++ = (in[0] << 6) | ((in[1] & 3) << 4) | ((in[2] & 3) << 2) | (in[3] & 3); + in += 4; + pixels -= 4; } switch (pixels) { - case 3: - out[0] = (in[0] << 6) | - ((in[1] & 3) << 4) | - ((in[2] & 3) << 2); - break; - case 2: - out[0] = (in[0] << 6) | - ((in[1] & 3) << 4); - break; - case 1: - out[0] = (in[0] << 6); + case 3: + out[0] = (in[0] << 6) | ((in[1] & 3) << 4) | ((in[2] & 3) << 2); + break; + case 2: + out[0] = (in[0] << 6) | ((in[1] & 3) << 4); + break; + case 1: + out[0] = (in[0] << 6); } } static void -packL16(UINT8* out, const UINT8* in, int pixels) -{ +packL16(UINT8 *out, const UINT8 *in, int pixels) { int i; /* L -> L;16, e.g: \xff77 -> \x00\xff\x00\x77 */ for (i = 0; i < pixels; i++) { @@ -210,8 +221,7 @@ packL16(UINT8* out, const UINT8* in, int pixels) } static void -packL16B(UINT8* out, const UINT8* in, int pixels) -{ +packL16B(UINT8 *out, const UINT8 *in, int pixels) { int i; /* L -> L;16B, e.g: \xff77 -> \xff\x00\x77\x00 */ for (i = 0; i < pixels; i++) { @@ -221,34 +231,31 @@ packL16B(UINT8* out, const UINT8* in, int pixels) } } - static void -packLA(UINT8* out, const UINT8* in, int pixels) -{ +packLA(UINT8 *out, const UINT8 *in, int pixels) { int i; /* LA, pixel interleaved */ for (i = 0; i < pixels; i++) { out[0] = in[R]; out[1] = in[A]; - out += 2; in += 4; + out += 2; + in += 4; } } static void -packLAL(UINT8* out, const UINT8* in, int pixels) -{ +packLAL(UINT8 *out, const UINT8 *in, int pixels) { int i; /* LA, line interleaved */ for (i = 0; i < pixels; i++) { out[i] = in[R]; - out[i+pixels] = in[A]; + out[i + pixels] = in[A]; in += 4; } } void -ImagingPackRGB(UINT8* out, const UINT8* in, int pixels) -{ +ImagingPackRGB(UINT8 *out, const UINT8 *in, int pixels) { int i = 0; /* RGB triplets */ #ifdef __sparc @@ -257,25 +264,25 @@ ImagingPackRGB(UINT8* out, const UINT8* in, int pixels) out[0] = in[R]; out[1] = in[G]; out[2] = in[B]; - out += 3; in += 4; + out += 3; + in += 4; } #else - for (; i < pixels-1; i++) { + for (; i < pixels - 1; i++) { memcpy(out, in + i * 4, 4); out += 3; } for (; i < pixels; i++) { - out[0] = in[i*4+R]; - out[1] = in[i*4+G]; - out[2] = in[i*4+B]; + out[0] = in[i * 4 + R]; + out[1] = in[i * 4 + G]; + out[2] = in[i * 4 + B]; out += 3; } #endif } void -ImagingPackXRGB(UINT8* out, const UINT8* in, int pixels) -{ +ImagingPackXRGB(UINT8 *out, const UINT8 *in, int pixels) { int i; /* XRGB, triplets with left padding */ for (i = 0; i < pixels; i++) { @@ -283,26 +290,26 @@ ImagingPackXRGB(UINT8* out, const UINT8* in, int pixels) out[1] = in[R]; out[2] = in[G]; out[3] = in[B]; - out += 4; in += 4; + out += 4; + in += 4; } } void -ImagingPackBGR(UINT8* out, const UINT8* in, int pixels) -{ +ImagingPackBGR(UINT8 *out, const UINT8 *in, int pixels) { int i; /* RGB, reversed bytes */ for (i = 0; i < pixels; i++) { out[0] = in[B]; out[1] = in[G]; out[2] = in[R]; - out += 3; in += 4; + out += 3; + in += 4; } } void -ImagingPackBGRX(UINT8* out, const UINT8* in, int pixels) -{ +ImagingPackBGRX(UINT8 *out, const UINT8 *in, int pixels) { int i; /* BGRX, reversed bytes with right padding */ for (i = 0; i < pixels; i++) { @@ -310,13 +317,13 @@ ImagingPackBGRX(UINT8* out, const UINT8* in, int pixels) out[1] = in[G]; out[2] = in[R]; out[3] = 0; - out += 4; in += 4; + out += 4; + in += 4; } } void -ImagingPackXBGR(UINT8* out, const UINT8* in, int pixels) -{ +ImagingPackXBGR(UINT8 *out, const UINT8 *in, int pixels) { int i; /* XBGR, reversed bytes with left padding */ for (i = 0; i < pixels; i++) { @@ -324,13 +331,13 @@ ImagingPackXBGR(UINT8* out, const UINT8* in, int pixels) out[1] = in[B]; out[2] = in[G]; out[3] = in[R]; - out += 4; in += 4; + out += 4; + in += 4; } } void -ImagingPackBGRA(UINT8* out, const UINT8* in, int pixels) -{ +ImagingPackBGRA(UINT8 *out, const UINT8 *in, int pixels) { int i; /* BGRX, reversed bytes with right padding */ for (i = 0; i < pixels; i++) { @@ -338,13 +345,13 @@ ImagingPackBGRA(UINT8* out, const UINT8* in, int pixels) out[1] = in[G]; out[2] = in[R]; out[3] = in[A]; - out += 4; in += 4; + out += 4; + in += 4; } } void -ImagingPackABGR(UINT8* out, const UINT8* in, int pixels) -{ +ImagingPackABGR(UINT8 *out, const UINT8 *in, int pixels) { int i; /* XBGR, reversed bytes with left padding */ for (i = 0; i < pixels; i++) { @@ -352,13 +359,13 @@ ImagingPackABGR(UINT8* out, const UINT8* in, int pixels) out[1] = in[B]; out[2] = in[G]; out[3] = in[R]; - out += 4; in += 4; + out += 4; + in += 4; } } void -ImagingPackBGRa(UINT8* out, const UINT8* in, int pixels) -{ +ImagingPackBGRa(UINT8 *out, const UINT8 *in, int pixels) { int i; /* BGRa, reversed bytes with premultiplied alpha */ for (i = 0; i < pixels; i++) { @@ -367,318 +374,314 @@ ImagingPackBGRa(UINT8* out, const UINT8* in, int pixels) out[0] = MULDIV255(in[B], alpha, tmp); out[1] = MULDIV255(in[G], alpha, tmp); out[2] = MULDIV255(in[R], alpha, tmp); - out += 4; in += 4; + out += 4; + in += 4; } } static void -packRGBL(UINT8* out, const UINT8* in, int pixels) -{ +packRGBL(UINT8 *out, const UINT8 *in, int pixels) { int i; /* RGB, line interleaved */ for (i = 0; i < pixels; i++) { out[i] = in[R]; - out[i+pixels] = in[G]; - out[i+pixels+pixels] = in[B]; + out[i + pixels] = in[G]; + out[i + pixels + pixels] = in[B]; in += 4; } } static void -packRGBXL(UINT8* out, const UINT8* in, int pixels) -{ +packRGBXL(UINT8 *out, const UINT8 *in, int pixels) { int i; /* RGBX, line interleaved */ for (i = 0; i < pixels; i++) { out[i] = in[R]; - out[i+pixels] = in[G]; - out[i+pixels+pixels] = in[B]; - out[i+pixels+pixels+pixels] = in[X]; + out[i + pixels] = in[G]; + out[i + pixels + pixels] = in[B]; + out[i + pixels + pixels + pixels] = in[X]; in += 4; } } static void -packI16B(UINT8* out, const UINT8* in_, int pixels) -{ +packI16B(UINT8 *out, const UINT8 *in_, int pixels) { int i; UINT16 tmp_; - UINT8* tmp = (UINT8*) &tmp_; + UINT8 *tmp = (UINT8 *)&tmp_; for (i = 0; i < pixels; i++) { INT32 in; memcpy(&in, in_, sizeof(in)); - if (in <= 0) + if (in <= 0) { tmp_ = 0; - else if (in > 65535) + } else if (in > 65535) { tmp_ = 65535; - else + } else { tmp_ = in; + } C16B; - out += 2; in_ += sizeof(in); + out += 2; + in_ += sizeof(in); } } static void -packI16N_I16B(UINT8* out, const UINT8* in, int pixels){ +packI16N_I16B(UINT8 *out, const UINT8 *in, int pixels) { int i; - UINT8* tmp = (UINT8*) in; + UINT8 *tmp = (UINT8 *)in; for (i = 0; i < pixels; i++) { C16B; - out += 2; tmp += 2; + out += 2; + tmp += 2; } - } static void -packI16N_I16(UINT8* out, const UINT8* in, int pixels){ +packI16N_I16(UINT8 *out, const UINT8 *in, int pixels) { int i; - UINT8* tmp = (UINT8*) in; + UINT8 *tmp = (UINT8 *)in; for (i = 0; i < pixels; i++) { C16L; - out += 2; tmp += 2; + out += 2; + tmp += 2; } } - static void -packI32S(UINT8* out, const UINT8* in, int pixels) -{ +packI32S(UINT8 *out, const UINT8 *in, int pixels) { int i; - UINT8* tmp = (UINT8*) in; + UINT8 *tmp = (UINT8 *)in; for (i = 0; i < pixels; i++) { C32L; - out += 4; tmp += 4; + out += 4; + tmp += 4; } } void -ImagingPackLAB(UINT8* out, const UINT8* in, int pixels) -{ +ImagingPackLAB(UINT8 *out, const UINT8 *in, int pixels) { int i; /* LAB triplets */ for (i = 0; i < pixels; i++) { out[0] = in[0]; out[1] = in[1] ^ 128; /* signed in outside world */ out[2] = in[2] ^ 128; - out += 3; in += 4; + out += 3; + in += 4; } } static void -copy1(UINT8* out, const UINT8* in, int pixels) -{ +copy1(UINT8 *out, const UINT8 *in, int pixels) { /* L, P */ memcpy(out, in, pixels); } static void -copy2(UINT8* out, const UINT8* in, int pixels) -{ +copy2(UINT8 *out, const UINT8 *in, int pixels) { /* I;16, etc */ - memcpy(out, in, pixels*2); + memcpy(out, in, pixels * 2); } static void -copy3(UINT8* out, const UINT8* in, int pixels) -{ +copy3(UINT8 *out, const UINT8 *in, int pixels) { /* BGR;24, etc */ - memcpy(out, in, pixels*3); + memcpy(out, in, pixels * 3); } static void -copy4(UINT8* out, const UINT8* in, int pixels) -{ +copy4(UINT8 *out, const UINT8 *in, int pixels) { /* RGBA, CMYK quadruples */ - memcpy(out, in, 4*pixels); + memcpy(out, in, 4 * pixels); } static void -copy4I(UINT8* out, const UINT8* in, int pixels) -{ +copy4I(UINT8 *out, const UINT8 *in, int pixels) { /* RGBA, CMYK quadruples, inverted */ int i; - for (i = 0; i < pixels*4; i++) + for (i = 0; i < pixels * 4; i++) { out[i] = ~in[i]; + } } static void -band0(UINT8* out, const UINT8* in, int pixels) -{ +band0(UINT8 *out, const UINT8 *in, int pixels) { int i; - for (i = 0; i < pixels; i++, in += 4) + for (i = 0; i < pixels; i++, in += 4) { out[i] = in[0]; + } } static void -band1(UINT8* out, const UINT8* in, int pixels) -{ +band1(UINT8 *out, const UINT8 *in, int pixels) { int i; - for (i = 0; i < pixels; i++, in += 4) + for (i = 0; i < pixels; i++, in += 4) { out[i] = in[1]; + } } static void -band2(UINT8* out, const UINT8* in, int pixels) -{ +band2(UINT8 *out, const UINT8 *in, int pixels) { int i; - for (i = 0; i < pixels; i++, in += 4) + for (i = 0; i < pixels; i++, in += 4) { out[i] = in[2]; + } } static void -band3(UINT8* out, const UINT8* in, int pixels) -{ +band3(UINT8 *out, const UINT8 *in, int pixels) { int i; - for (i = 0; i < pixels; i++, in += 4) + for (i = 0; i < pixels; i++, in += 4) { out[i] = in[3]; + } } static struct { - const char* mode; - const char* rawmode; + const char *mode; + const char *rawmode; int bits; ImagingShuffler pack; } packers[] = { /* bilevel */ - {"1", "1", 1, pack1}, - {"1", "1;I", 1, pack1I}, - {"1", "1;R", 1, pack1R}, - {"1", "1;IR", 1, pack1IR}, - {"1", "L", 8, pack1L}, + {"1", "1", 1, pack1}, + {"1", "1;I", 1, pack1I}, + {"1", "1;R", 1, pack1R}, + {"1", "1;IR", 1, pack1IR}, + {"1", "L", 8, pack1L}, /* greyscale */ - {"L", "L", 8, copy1}, - {"L", "L;16", 16, packL16}, - {"L", "L;16B", 16, packL16B}, + {"L", "L", 8, copy1}, + {"L", "L;16", 16, packL16}, + {"L", "L;16B", 16, packL16B}, /* greyscale w. alpha */ - {"LA", "LA", 16, packLA}, - {"LA", "LA;L", 16, packLAL}, + {"LA", "LA", 16, packLA}, + {"LA", "LA;L", 16, packLAL}, /* greyscale w. alpha premultiplied */ - {"La", "La", 16, packLA}, + {"La", "La", 16, packLA}, /* palette */ - {"P", "P;1", 1, pack1}, - {"P", "P;2", 2, packP2}, - {"P", "P;4", 4, packP4}, - {"P", "P", 8, copy1}, + {"P", "P;1", 1, pack1}, + {"P", "P;2", 2, packP2}, + {"P", "P;4", 4, packP4}, + {"P", "P", 8, copy1}, /* palette w. alpha */ - {"PA", "PA", 16, packLA}, - {"PA", "PA;L", 16, packLAL}, + {"PA", "PA", 16, packLA}, + {"PA", "PA;L", 16, packLAL}, /* true colour */ - {"RGB", "RGB", 24, ImagingPackRGB}, - {"RGB", "RGBX", 32, copy4}, - {"RGB", "XRGB", 32, ImagingPackXRGB}, - {"RGB", "BGR", 24, ImagingPackBGR}, - {"RGB", "BGRX", 32, ImagingPackBGRX}, - {"RGB", "XBGR", 32, ImagingPackXBGR}, - {"RGB", "RGB;L", 24, packRGBL}, - {"RGB", "R", 8, band0}, - {"RGB", "G", 8, band1}, - {"RGB", "B", 8, band2}, + {"RGB", "RGB", 24, ImagingPackRGB}, + {"RGB", "RGBX", 32, copy4}, + {"RGB", "XRGB", 32, ImagingPackXRGB}, + {"RGB", "BGR", 24, ImagingPackBGR}, + {"RGB", "BGRX", 32, ImagingPackBGRX}, + {"RGB", "XBGR", 32, ImagingPackXBGR}, + {"RGB", "RGB;L", 24, packRGBL}, + {"RGB", "R", 8, band0}, + {"RGB", "G", 8, band1}, + {"RGB", "B", 8, band2}, /* true colour w. alpha */ - {"RGBA", "RGBA", 32, copy4}, - {"RGBA", "RGBA;L", 32, packRGBXL}, - {"RGBA", "RGB", 24, ImagingPackRGB}, - {"RGBA", "BGR", 24, ImagingPackBGR}, - {"RGBA", "BGRA", 32, ImagingPackBGRA}, - {"RGBA", "ABGR", 32, ImagingPackABGR}, - {"RGBA", "BGRa", 32, ImagingPackBGRa}, - {"RGBA", "R", 8, band0}, - {"RGBA", "G", 8, band1}, - {"RGBA", "B", 8, band2}, - {"RGBA", "A", 8, band3}, + {"RGBA", "RGBA", 32, copy4}, + {"RGBA", "RGBA;L", 32, packRGBXL}, + {"RGBA", "RGB", 24, ImagingPackRGB}, + {"RGBA", "BGR", 24, ImagingPackBGR}, + {"RGBA", "BGRA", 32, ImagingPackBGRA}, + {"RGBA", "ABGR", 32, ImagingPackABGR}, + {"RGBA", "BGRa", 32, ImagingPackBGRa}, + {"RGBA", "R", 8, band0}, + {"RGBA", "G", 8, band1}, + {"RGBA", "B", 8, band2}, + {"RGBA", "A", 8, band3}, /* true colour w. alpha premultiplied */ - {"RGBa", "RGBa", 32, copy4}, - {"RGBa", "BGRa", 32, ImagingPackBGRA}, - {"RGBa", "aBGR", 32, ImagingPackABGR}, + {"RGBa", "RGBa", 32, copy4}, + {"RGBa", "BGRa", 32, ImagingPackBGRA}, + {"RGBa", "aBGR", 32, ImagingPackABGR}, /* true colour w. padding */ - {"RGBX", "RGBX", 32, copy4}, - {"RGBX", "RGBX;L", 32, packRGBXL}, - {"RGBX", "RGB", 24, ImagingPackRGB}, - {"RGBX", "BGR", 24, ImagingPackBGR}, - {"RGBX", "BGRX", 32, ImagingPackBGRX}, - {"RGBX", "XBGR", 32, ImagingPackXBGR}, - {"RGBX", "R", 8, band0}, - {"RGBX", "G", 8, band1}, - {"RGBX", "B", 8, band2}, - {"RGBX", "X", 8, band3}, + {"RGBX", "RGBX", 32, copy4}, + {"RGBX", "RGBX;L", 32, packRGBXL}, + {"RGBX", "RGB", 24, ImagingPackRGB}, + {"RGBX", "BGR", 24, ImagingPackBGR}, + {"RGBX", "BGRX", 32, ImagingPackBGRX}, + {"RGBX", "XBGR", 32, ImagingPackXBGR}, + {"RGBX", "R", 8, band0}, + {"RGBX", "G", 8, band1}, + {"RGBX", "B", 8, band2}, + {"RGBX", "X", 8, band3}, /* colour separation */ - {"CMYK", "CMYK", 32, copy4}, - {"CMYK", "CMYK;I", 32, copy4I}, - {"CMYK", "CMYK;L", 32, packRGBXL}, - {"CMYK", "C", 8, band0}, - {"CMYK", "M", 8, band1}, - {"CMYK", "Y", 8, band2}, - {"CMYK", "K", 8, band3}, + {"CMYK", "CMYK", 32, copy4}, + {"CMYK", "CMYK;I", 32, copy4I}, + {"CMYK", "CMYK;L", 32, packRGBXL}, + {"CMYK", "C", 8, band0}, + {"CMYK", "M", 8, band1}, + {"CMYK", "Y", 8, band2}, + {"CMYK", "K", 8, band3}, /* video (YCbCr) */ - {"YCbCr", "YCbCr", 24, ImagingPackRGB}, - {"YCbCr", "YCbCr;L", 24, packRGBL}, - {"YCbCr", "YCbCrX", 32, copy4}, - {"YCbCr", "YCbCrK", 32, copy4}, - {"YCbCr", "Y", 8, band0}, - {"YCbCr", "Cb", 8, band1}, - {"YCbCr", "Cr", 8, band2}, + {"YCbCr", "YCbCr", 24, ImagingPackRGB}, + {"YCbCr", "YCbCr;L", 24, packRGBL}, + {"YCbCr", "YCbCrX", 32, copy4}, + {"YCbCr", "YCbCrK", 32, copy4}, + {"YCbCr", "Y", 8, band0}, + {"YCbCr", "Cb", 8, band1}, + {"YCbCr", "Cr", 8, band2}, /* LAB Color */ - {"LAB", "LAB", 24, ImagingPackLAB}, - {"LAB", "L", 8, band0}, - {"LAB", "A", 8, band1}, - {"LAB", "B", 8, band2}, + {"LAB", "LAB", 24, ImagingPackLAB}, + {"LAB", "L", 8, band0}, + {"LAB", "A", 8, band1}, + {"LAB", "B", 8, band2}, /* HSV */ - {"HSV", "HSV", 24, ImagingPackRGB}, - {"HSV", "H", 8, band0}, - {"HSV", "S", 8, band1}, - {"HSV", "V", 8, band2}, + {"HSV", "HSV", 24, ImagingPackRGB}, + {"HSV", "H", 8, band0}, + {"HSV", "S", 8, band1}, + {"HSV", "V", 8, band2}, /* integer */ - {"I", "I", 32, copy4}, - {"I", "I;16B", 16, packI16B}, - {"I", "I;32S", 32, packI32S}, - {"I", "I;32NS", 32, copy4}, + {"I", "I", 32, copy4}, + {"I", "I;16B", 16, packI16B}, + {"I", "I;32S", 32, packI32S}, + {"I", "I;32NS", 32, copy4}, /* floating point */ - {"F", "F", 32, copy4}, - {"F", "F;32F", 32, packI32S}, - {"F", "F;32NF", 32, copy4}, + {"F", "F", 32, copy4}, + {"F", "F;32F", 32, packI32S}, + {"F", "F;32NF", 32, copy4}, /* storage modes */ - {"I;16", "I;16", 16, copy2}, - {"I;16", "I;16B", 16, packI16N_I16B}, - {"I;16B", "I;16B", 16, copy2}, - {"I;16L", "I;16L", 16, copy2}, - {"I;16", "I;16N", 16, packI16N_I16}, // LibTiff native->image endian. - {"I;16L", "I;16N", 16, packI16N_I16}, - {"I;16B", "I;16N", 16, packI16N_I16B}, - {"BGR;15", "BGR;15", 16, copy2}, - {"BGR;16", "BGR;16", 16, copy2}, - {"BGR;24", "BGR;24", 24, copy3}, + {"I;16", "I;16", 16, copy2}, + {"I;16", "I;16B", 16, packI16N_I16B}, + {"I;16B", "I;16B", 16, copy2}, + {"I;16L", "I;16L", 16, copy2}, + {"I;16", "I;16N", 16, packI16N_I16}, // LibTiff native->image endian. + {"I;16L", "I;16N", 16, packI16N_I16}, + {"I;16B", "I;16N", 16, packI16N_I16B}, + {"BGR;15", "BGR;15", 16, copy2}, + {"BGR;16", "BGR;16", 16, copy2}, + {"BGR;24", "BGR;24", 24, copy3}, {NULL} /* sentinel */ }; - ImagingShuffler -ImagingFindPacker(const char* mode, const char* rawmode, int* bits_out) -{ +ImagingFindPacker(const char *mode, const char *rawmode, int *bits_out) { int i; /* find a suitable pixel packer */ - for (i = 0; packers[i].rawmode; i++) + for (i = 0; packers[i].rawmode; i++) { if (strcmp(packers[i].mode, mode) == 0 && strcmp(packers[i].rawmode, rawmode) == 0) { - if (bits_out) + if (bits_out) { *bits_out = packers[i].bits; + } return packers[i].pack; } + } return NULL; } diff --git a/src/libImaging/PackDecode.c b/src/libImaging/PackDecode.c index ef54f3c9a..7dd432b91 100644 --- a/src/libImaging/PackDecode.c +++ b/src/libImaging/PackDecode.c @@ -5,7 +5,7 @@ * decoder for PackBits image data. * * history: - * 96-04-19 fl Created + * 96-04-19 fl Created * * Copyright (c) Fredrik Lundh 1996. * Copyright (c) Secret Labs AB 1997. @@ -13,80 +13,80 @@ * See the README file for information on usage and redistribution. */ - #include "Imaging.h" int -ImagingPackbitsDecode(Imaging im, ImagingCodecState state, - UINT8* buf, Py_ssize_t bytes) -{ +ImagingPackbitsDecode( + Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t bytes) { UINT8 n; - UINT8* ptr; + UINT8 *ptr; int i; ptr = buf; for (;;) { + if (bytes < 1) { + return ptr - buf; + } - if (bytes < 1) - return ptr - buf; + if (ptr[0] & 0x80) { + if (ptr[0] == 0x80) { + /* Nop */ + ptr++; + bytes--; + continue; + } - if (ptr[0] & 0x80) { + /* Run */ + if (bytes < 2) { + return ptr - buf; + } - if (ptr[0] == 0x80) { - /* Nop */ - ptr++; bytes--; - continue; - } + for (n = 257 - ptr[0]; n > 0; n--) { + if (state->x >= state->bytes) { + /* state->errcode = IMAGING_CODEC_OVERRUN; */ + break; + } + state->buffer[state->x++] = ptr[1]; + } - /* Run */ - if (bytes < 2) - return ptr - buf; + ptr += 2; + bytes -= 2; - for (n = 257 - ptr[0]; n > 0; n--) { - if (state->x >= state->bytes) { - /* state->errcode = IMAGING_CODEC_OVERRUN; */ - break; - } - state->buffer[state->x++] = ptr[1]; - } + } else { + /* Literal */ + n = ptr[0] + 2; - ptr += 2; bytes -= 2; + if (bytes < n) { + return ptr - buf; + } - } else { + for (i = 1; i < n; i++) { + if (state->x >= state->bytes) { + /* state->errcode = IMAGING_CODEC_OVERRUN; */ + break; + } + state->buffer[state->x++] = ptr[i]; + } - /* Literal */ - n = ptr[0]+2; + ptr += n; + bytes -= n; + } - if (bytes < n) - return ptr - buf; + if (state->x >= state->bytes) { + /* Got a full line, unpack it */ + state->shuffle( + (UINT8 *)im->image[state->y + state->yoff] + + state->xoff * im->pixelsize, + state->buffer, + state->xsize); - for (i = 1; i < n; i++) { - if (state->x >= state->bytes) { - /* state->errcode = IMAGING_CODEC_OVERRUN; */ - break; - } - state->buffer[state->x++] = ptr[i]; - } - - ptr += n; bytes -= n; - - } - - if (state->x >= state->bytes) { - - /* Got a full line, unpack it */ - state->shuffle((UINT8*) im->image[state->y + state->yoff] + - state->xoff * im->pixelsize, state->buffer, - state->xsize); - - state->x = 0; - - if (++state->y >= state->ysize) { - /* End of file (errcode = 0) */ - return -1; - } - } + state->x = 0; + if (++state->y >= state->ysize) { + /* End of file (errcode = 0) */ + return -1; + } + } } } diff --git a/src/libImaging/Palette.c b/src/libImaging/Palette.c index 7aee6e8ee..43bea61e3 100644 --- a/src/libImaging/Palette.c +++ b/src/libImaging/Palette.c @@ -16,98 +16,97 @@ * See the README file for information on usage and redistribution. */ - #include "Imaging.h" #include - ImagingPalette -ImagingPaletteNew(const char* mode) -{ +ImagingPaletteNew(const char *mode) { /* Create a palette object */ int i; ImagingPalette palette; - if (strcmp(mode, "RGB") && strcmp(mode, "RGBA")) - return (ImagingPalette) ImagingError_ModeError(); + if (strcmp(mode, "RGB") && strcmp(mode, "RGBA")) { + return (ImagingPalette)ImagingError_ModeError(); + } palette = calloc(1, sizeof(struct ImagingPaletteInstance)); - if (!palette) - return (ImagingPalette) ImagingError_MemoryError(); + if (!palette) { + return (ImagingPalette)ImagingError_MemoryError(); + } - strncpy(palette->mode, mode, IMAGING_MODE_LENGTH-1); - palette->mode[IMAGING_MODE_LENGTH-1] = 0; + strncpy(palette->mode, mode, IMAGING_MODE_LENGTH - 1); + palette->mode[IMAGING_MODE_LENGTH - 1] = 0; /* Initialize to ramp */ for (i = 0; i < 256; i++) { - palette->palette[i*4+0] = - palette->palette[i*4+1] = - palette->palette[i*4+2] = (UINT8) i; - palette->palette[i*4+3] = 255; /* opaque */ + palette->palette[i * 4 + 0] = palette->palette[i * 4 + 1] = + palette->palette[i * 4 + 2] = (UINT8)i; + palette->palette[i * 4 + 3] = 255; /* opaque */ } return palette; } ImagingPalette -ImagingPaletteNewBrowser(void) -{ +ImagingPaletteNewBrowser(void) { /* Create a standard "browser" palette object */ int i, r, g, b; ImagingPalette palette; palette = ImagingPaletteNew("RGB"); - if (!palette) + if (!palette) { return NULL; + } /* Blank out unused entries */ /* FIXME: Add 10-level windows palette here? */ for (i = 0; i < 10; i++) { - palette->palette[i*4+0] = - palette->palette[i*4+1] = - palette->palette[i*4+2] = 0; + palette->palette[i * 4 + 0] = palette->palette[i * 4 + 1] = + palette->palette[i * 4 + 2] = 0; } /* Simple 6x6x6 colour cube */ - for (b = 0; b < 256; b += 51) - for (g = 0; g < 256; g += 51) + for (b = 0; b < 256; b += 51) { + for (g = 0; g < 256; g += 51) { for (r = 0; r < 256; r += 51) { - palette->palette[i*4+0] = r; - palette->palette[i*4+1] = g; - palette->palette[i*4+2] = b; + palette->palette[i * 4 + 0] = r; + palette->palette[i * 4 + 1] = g; + palette->palette[i * 4 + 2] = b; i++; } + } + } /* Blank out unused entries */ /* FIXME: add 30-level greyscale wedge here? */ for (; i < 256; i++) { - palette->palette[i*4+0] = - palette->palette[i*4+1] = - palette->palette[i*4+2] = 0; + palette->palette[i * 4 + 0] = palette->palette[i * 4 + 1] = + palette->palette[i * 4 + 2] = 0; } return palette; } ImagingPalette -ImagingPaletteDuplicate(ImagingPalette palette) -{ +ImagingPaletteDuplicate(ImagingPalette palette) { /* Duplicate palette descriptor */ ImagingPalette new_palette; - if (!palette) + if (!palette) { return NULL; + } /* malloc check ok, small constant allocation */ new_palette = malloc(sizeof(struct ImagingPaletteInstance)); - if (!new_palette) - return (ImagingPalette) ImagingError_MemoryError(); + if (!new_palette) { + return (ImagingPalette)ImagingError_MemoryError(); + } memcpy(new_palette, palette, sizeof(struct ImagingPaletteInstance)); @@ -118,18 +117,17 @@ ImagingPaletteDuplicate(ImagingPalette palette) } void -ImagingPaletteDelete(ImagingPalette palette) -{ +ImagingPaletteDelete(ImagingPalette palette) { /* Destroy palette object */ if (palette) { - if (palette->cache) + if (palette->cache) { free(palette->cache); + } free(palette); } } - /* -------------------------------------------------------------------- */ /* Colour mapping */ /* -------------------------------------------------------------------- */ @@ -147,27 +145,26 @@ ImagingPaletteDelete(ImagingPalette palette) #define DIST(a, b, s) (a - b) * (a - b) * s /* Colour weights (no scaling, for now) */ -#define RSCALE 1 -#define GSCALE 1 -#define BSCALE 1 +#define RSCALE 1 +#define GSCALE 1 +#define BSCALE 1 /* Calculated scaled distances */ -#define RDIST(a, b) DIST(a, b, RSCALE*RSCALE) -#define GDIST(a, b) DIST(a, b, GSCALE*GSCALE) -#define BDIST(a, b) DIST(a, b, BSCALE*BSCALE) +#define RDIST(a, b) DIST(a, b, RSCALE *RSCALE) +#define GDIST(a, b) DIST(a, b, GSCALE *GSCALE) +#define BDIST(a, b) DIST(a, b, BSCALE *BSCALE) /* Incremental steps */ -#define RSTEP (4 * RSCALE) -#define GSTEP (4 * GSCALE) -#define BSTEP (4 * BSCALE) +#define RSTEP (4 * RSCALE) +#define GSTEP (4 * GSCALE) +#define BSTEP (4 * BSCALE) -#define BOX 8 +#define BOX 8 -#define BOXVOLUME BOX*BOX*BOX +#define BOXVOLUME BOX *BOX *BOX void -ImagingPaletteCacheUpdate(ImagingPalette palette, int r, int g, int b) -{ +ImagingPaletteCacheUpdate(ImagingPalette palette, int r, int g, int b) { int i, j; unsigned int dmin[256], dmax; int r0, g0, b0; @@ -179,39 +176,44 @@ ImagingPaletteCacheUpdate(ImagingPalette palette, int r, int g, int b) /* Get box boundaries for the given (r,g,b)-triplet. Each box covers eight cache slots (32 colour values, that is). */ - r0 = r & 0xe0; r1 = r0 + 0x1f; rc = (r0 + r1) / 2; - g0 = g & 0xe0; g1 = g0 + 0x1f; gc = (g0 + g1) / 2; - b0 = b & 0xe0; b1 = b0 + 0x1f; bc = (b0 + b1) / 2; + r0 = r & 0xe0; + r1 = r0 + 0x1f; + rc = (r0 + r1) / 2; + g0 = g & 0xe0; + g1 = g0 + 0x1f; + gc = (g0 + g1) / 2; + b0 = b & 0xe0; + b1 = b0 + 0x1f; + bc = (b0 + b1) / 2; /* Step 1 -- Select relevant palette entries (after Heckbert) */ /* For each palette entry, calculate the min and max distances to * any position in the box given by the colour we're looking for. */ - dmax = (unsigned int) ~0; + dmax = (unsigned int)~0; for (i = 0; i < 256; i++) { - int r, g, b; unsigned int tmin, tmax; /* Find min and max distances to any point in the box */ - r = palette->palette[i*4+0]; + r = palette->palette[i * 4 + 0]; tmin = (r < r0) ? RDIST(r, r1) : (r > r1) ? RDIST(r, r0) : 0; tmax = (r <= rc) ? RDIST(r, r1) : RDIST(r, r0); - g = palette->palette[i*4+1]; + g = palette->palette[i * 4 + 1]; tmin += (g < g0) ? GDIST(g, g1) : (g > g1) ? GDIST(g, g0) : 0; tmax += (g <= gc) ? GDIST(g, g1) : GDIST(g, g0); - b = palette->palette[i*4+2]; + b = palette->palette[i * 4 + 2]; tmin += (b < b0) ? BDIST(b, b1) : (b > b1) ? BDIST(b, b0) : 0; tmax += (b <= bc) ? BDIST(b, b1) : BDIST(b, b0); dmin[i] = tmin; - if (tmax < dmax) + if (tmax < dmax) { dmax = tmax; /* keep the smallest max distance only */ - + } } /* Step 2 -- Incrementally update cache slot (after Thomas) */ @@ -220,22 +222,21 @@ ImagingPaletteCacheUpdate(ImagingPalette palette, int r, int g, int b) * all slots in that box. We only check boxes for which the min * distance is less than or equal the smallest max distance */ - for (i = 0; i < BOXVOLUME; i++) - d[i] = (unsigned int) ~0; - - for (i = 0; i < 256; i++) + for (i = 0; i < BOXVOLUME; i++) { + d[i] = (unsigned int)~0; + } + for (i = 0; i < 256; i++) { if (dmin[i] <= dmax) { - int rd, gd, bd; int ri, gi, bi; int rx, gx, bx; - ri = (r0 - palette->palette[i*4+0]) * RSCALE; - gi = (g0 - palette->palette[i*4+1]) * GSCALE; - bi = (b0 - palette->palette[i*4+2]) * BSCALE; + ri = (r0 - palette->palette[i * 4 + 0]) * RSCALE; + gi = (g0 - palette->palette[i * 4 + 1]) * GSCALE; + bi = (b0 - palette->palette[i * 4 + 2]) * BSCALE; - rd = ri*ri + gi*gi + bi*bi; + rd = ri * ri + gi * gi + bi * bi; ri = ri * (2 * RSTEP) + RSTEP * RSTEP; gi = gi * (2 * GSTEP) + GSTEP * GSTEP; @@ -243,13 +244,15 @@ ImagingPaletteCacheUpdate(ImagingPalette palette, int r, int g, int b) rx = ri; for (r = j = 0; r < BOX; r++) { - gd = rd; gx = gi; + gd = rd; + gx = gi; for (g = 0; g < BOX; g++) { - bd = gd; bx = bi; + bd = gd; + bx = bi; for (b = 0; b < BOX; b++) { - if ((unsigned int) bd < d[j]) { + if ((unsigned int)bd < d[j]) { d[j] = bd; - c[j] = (UINT8) i; + c[j] = (UINT8)i; } bd += bx; bx += 2 * BSTEP * BSTEP; @@ -262,6 +265,7 @@ ImagingPaletteCacheUpdate(ImagingPalette palette, int r, int g, int b) rx += 2 * RSTEP * RSTEP; } } + } /* Step 3 -- Update cache */ @@ -269,46 +273,44 @@ ImagingPaletteCacheUpdate(ImagingPalette palette, int r, int g, int b) * cache slot in the box. Update the cache. */ j = 0; - for (r = r0; r < r1; r+=4) - for (g = g0; g < g1; g+=4) - for (b = b0; b < b1; b+=4) + for (r = r0; r < r1; r += 4) { + for (g = g0; g < g1; g += 4) { + for (b = b0; b < b1; b += 4) { ImagingPaletteCache(palette, r, g, b) = c[j++]; + } + } + } } - int -ImagingPaletteCachePrepare(ImagingPalette palette) -{ +ImagingPaletteCachePrepare(ImagingPalette palette) { /* Add a colour cache to a palette */ int i; - int entries = 64*64*64; + int entries = 64 * 64 * 64; if (palette->cache == NULL) { - /* The cache is 512k. It might be a good idea to break it up into a pointer array (e.g. an 8-bit image?) */ /* malloc check ok, small constant allocation */ - palette->cache = (INT16*) malloc(entries * sizeof(INT16)); + palette->cache = (INT16 *)malloc(entries * sizeof(INT16)); if (!palette->cache) { - (void) ImagingError_MemoryError(); + (void)ImagingError_MemoryError(); return -1; } /* Mark all entries as empty */ - for (i = 0; i < entries; i++) + for (i = 0; i < entries; i++) { palette->cache[i] = 0x100; - + } } return 0; } - void -ImagingPaletteCacheDelete(ImagingPalette palette) -{ +ImagingPaletteCacheDelete(ImagingPalette palette) { /* Release the colour cache, if any */ if (palette && palette->cache) { diff --git a/src/libImaging/Paste.c b/src/libImaging/Paste.c index 0bda25739..03b17f571 100644 --- a/src/libImaging/Paste.c +++ b/src/libImaging/Paste.c @@ -23,11 +23,17 @@ #include "Imaging.h" - static inline void -paste(Imaging imOut, Imaging imIn, int dx, int dy, int sx, int sy, - int xsize, int ysize, int pixelsize) -{ +paste( + Imaging imOut, + Imaging imIn, + int dx, + int dy, + int sx, + int sy, + int xsize, + int ysize, + int pixelsize) { /* paste opaque region */ int y; @@ -37,41 +43,49 @@ paste(Imaging imOut, Imaging imIn, int dx, int dy, int sx, int sy, xsize *= pixelsize; - for (y = 0; y < ysize; y++) - memcpy(imOut->image[y+dy]+dx, imIn->image[y+sy]+sx, xsize); + for (y = 0; y < ysize; y++) { + memcpy(imOut->image[y + dy] + dx, imIn->image[y + sy] + sx, xsize); + } } static inline void -paste_mask_1(Imaging imOut, Imaging imIn, Imaging imMask, - int dx, int dy, int sx, int sy, - int xsize, int ysize, int pixelsize) -{ +paste_mask_1( + Imaging imOut, + Imaging imIn, + Imaging imMask, + int dx, + int dy, + int sx, + int sy, + int xsize, + int ysize, + int pixelsize) { /* paste with mode "1" mask */ int x, y; if (imOut->image8) { - for (y = 0; y < ysize; y++) { - UINT8* out = imOut->image8[y+dy]+dx; - UINT8* in = imIn->image8[y+sy]+sx; - UINT8* mask = imMask->image8[y+sy]+sx; + UINT8 *out = imOut->image8[y + dy] + dx; + UINT8 *in = imIn->image8[y + sy] + sx; + UINT8 *mask = imMask->image8[y + sy] + sx; for (x = 0; x < xsize; x++) { - if (*mask++) + if (*mask++) { *out = *in; + } out++, in++; } } } else { - for (y = 0; y < ysize; y++) { - INT32* out = imOut->image32[y+dy]+dx; - INT32* in = imIn->image32[y+sy]+sx; - UINT8* mask = imMask->image8[y+sy]+sx; + INT32 *out = imOut->image32[y + dy] + dx; + INT32 *in = imIn->image32[y + sy] + sx; + UINT8 *mask = imMask->image8[y + sy] + sx; for (x = 0; x < xsize; x++) { - if (*mask++) + if (*mask++) { *out = *in; + } out++, in++; } } @@ -79,21 +93,27 @@ paste_mask_1(Imaging imOut, Imaging imIn, Imaging imMask, } static inline void -paste_mask_L(Imaging imOut, Imaging imIn, Imaging imMask, - int dx, int dy, int sx, int sy, - int xsize, int ysize, int pixelsize) -{ +paste_mask_L( + Imaging imOut, + Imaging imIn, + Imaging imMask, + int dx, + int dy, + int sx, + int sy, + int xsize, + int ysize, + int pixelsize) { /* paste with mode "L" matte */ int x, y; unsigned int tmp1; if (imOut->image8) { - for (y = 0; y < ysize; y++) { - UINT8* out = imOut->image8[y+dy]+dx; - UINT8* in = imIn->image8[y+sy]+sx; - UINT8* mask = imMask->image8[y+sy]+sx; + UINT8 *out = imOut->image8[y + dy] + dx; + UINT8 *in = imIn->image8[y + sy] + sx; + UINT8 *mask = imMask->image8[y + sy] + sx; for (x = 0; x < xsize; x++) { *out = BLEND(*mask, *out, *in, tmp1); out++, in++, mask++; @@ -101,39 +121,46 @@ paste_mask_L(Imaging imOut, Imaging imIn, Imaging imMask, } } else { - for (y = 0; y < ysize; y++) { - UINT8* out = (UINT8*) (imOut->image32[y + dy] + dx); - UINT8* in = (UINT8*) (imIn->image32[y + sy] + sx); - UINT8* mask = (UINT8*) (imMask->image8[y+sy] + sx); + UINT8 *out = (UINT8 *)(imOut->image32[y + dy] + dx); + UINT8 *in = (UINT8 *)(imIn->image32[y + sy] + sx); + UINT8 *mask = (UINT8 *)(imMask->image8[y + sy] + sx); for (x = 0; x < xsize; x++) { UINT8 a = mask[0]; out[0] = BLEND(a, out[0], in[0], tmp1); out[1] = BLEND(a, out[1], in[1], tmp1); out[2] = BLEND(a, out[2], in[2], tmp1); out[3] = BLEND(a, out[3], in[3], tmp1); - out += 4; in += 4; mask ++; + out += 4; + in += 4; + mask++; } } } } static inline void -paste_mask_RGBA(Imaging imOut, Imaging imIn, Imaging imMask, - int dx, int dy, int sx, int sy, - int xsize, int ysize, int pixelsize) -{ +paste_mask_RGBA( + Imaging imOut, + Imaging imIn, + Imaging imMask, + int dx, + int dy, + int sx, + int sy, + int xsize, + int ysize, + int pixelsize) { /* paste with mode "RGBA" matte */ int x, y; unsigned int tmp1; if (imOut->image8) { - for (y = 0; y < ysize; y++) { - UINT8* out = imOut->image8[y+dy]+dx; - UINT8* in = imIn->image8[y+sy]+sx; - UINT8* mask = (UINT8*) imMask->image[y+sy]+sx*4+3; + UINT8 *out = imOut->image8[y + dy] + dx; + UINT8 *in = imIn->image8[y + sy] + sx; + UINT8 *mask = (UINT8 *)imMask->image[y + sy] + sx * 4 + 3; for (x = 0; x < xsize; x++) { *out = BLEND(*mask, *out, *in, tmp1); out++, in++, mask += 4; @@ -141,40 +168,46 @@ paste_mask_RGBA(Imaging imOut, Imaging imIn, Imaging imMask, } } else { - for (y = 0; y < ysize; y++) { - UINT8* out = (UINT8*) (imOut->image32[y + dy] + dx); - UINT8* in = (UINT8*) (imIn->image32[y + sy] + sx); - UINT8* mask = (UINT8*) (imMask->image32[y+sy] + sx); + UINT8 *out = (UINT8 *)(imOut->image32[y + dy] + dx); + UINT8 *in = (UINT8 *)(imIn->image32[y + sy] + sx); + UINT8 *mask = (UINT8 *)(imMask->image32[y + sy] + sx); for (x = 0; x < xsize; x++) { UINT8 a = mask[3]; out[0] = BLEND(a, out[0], in[0], tmp1); out[1] = BLEND(a, out[1], in[1], tmp1); out[2] = BLEND(a, out[2], in[2], tmp1); out[3] = BLEND(a, out[3], in[3], tmp1); - out += 4; in += 4; mask += 4; + out += 4; + in += 4; + mask += 4; } } } } - static inline void -paste_mask_RGBa(Imaging imOut, Imaging imIn, Imaging imMask, - int dx, int dy, int sx, int sy, - int xsize, int ysize, int pixelsize) -{ +paste_mask_RGBa( + Imaging imOut, + Imaging imIn, + Imaging imMask, + int dx, + int dy, + int sx, + int sy, + int xsize, + int ysize, + int pixelsize) { /* paste with mode "RGBa" matte */ int x, y; unsigned int tmp1; if (imOut->image8) { - for (y = 0; y < ysize; y++) { - UINT8* out = imOut->image8[y+dy]+dx; - UINT8* in = imIn->image8[y+sy]+sx; - UINT8* mask = (UINT8*) imMask->image[y+sy]+sx*4+3; + UINT8 *out = imOut->image8[y + dy] + dx; + UINT8 *in = imIn->image8[y + sy] + sx; + UINT8 *mask = (UINT8 *)imMask->image[y + sy] + sx * 4 + 3; for (x = 0; x < xsize; x++) { *out = PREBLEND(*mask, *out, *in, tmp1); out++, in++, mask += 4; @@ -182,34 +215,34 @@ paste_mask_RGBa(Imaging imOut, Imaging imIn, Imaging imMask, } } else { - for (y = 0; y < ysize; y++) { - UINT8* out = (UINT8*) (imOut->image32[y + dy] + dx); - UINT8* in = (UINT8*) (imIn->image32[y + sy] + sx); - UINT8* mask = (UINT8*) (imMask->image32[y+sy] + sx); + UINT8 *out = (UINT8 *)(imOut->image32[y + dy] + dx); + UINT8 *in = (UINT8 *)(imIn->image32[y + sy] + sx); + UINT8 *mask = (UINT8 *)(imMask->image32[y + sy] + sx); for (x = 0; x < xsize; x++) { UINT8 a = mask[3]; out[0] = PREBLEND(a, out[0], in[0], tmp1); out[1] = PREBLEND(a, out[1], in[1], tmp1); out[2] = PREBLEND(a, out[2], in[2], tmp1); out[3] = PREBLEND(a, out[3], in[3], tmp1); - out += 4; in += 4; mask += 4; + out += 4; + in += 4; + mask += 4; } } } } int -ImagingPaste(Imaging imOut, Imaging imIn, Imaging imMask, - int dx0, int dy0, int dx1, int dy1) -{ +ImagingPaste( + Imaging imOut, Imaging imIn, Imaging imMask, int dx0, int dy0, int dx1, int dy1) { int xsize, ysize; int pixelsize; int sx0, sy0; ImagingSectionCookie cookie; if (!imOut || !imIn) { - (void) ImagingError_ModeError(); + (void)ImagingError_ModeError(); return -1; } @@ -218,30 +251,34 @@ ImagingPaste(Imaging imOut, Imaging imIn, Imaging imMask, xsize = dx1 - dx0; ysize = dy1 - dy0; - if (xsize != imIn->xsize || ysize != imIn->ysize || - pixelsize != imIn->pixelsize) { - (void) ImagingError_Mismatch(); + if (xsize != imIn->xsize || ysize != imIn->ysize || pixelsize != imIn->pixelsize) { + (void)ImagingError_Mismatch(); return -1; } if (imMask && (xsize != imMask->xsize || ysize != imMask->ysize)) { - (void) ImagingError_Mismatch(); + (void)ImagingError_Mismatch(); return -1; } /* Determine which region to copy */ sx0 = sy0 = 0; - if (dx0 < 0) + if (dx0 < 0) { xsize += dx0, sx0 = -dx0, dx0 = 0; - if (dx0 + xsize > imOut->xsize) + } + if (dx0 + xsize > imOut->xsize) { xsize = imOut->xsize - dx0; - if (dy0 < 0) + } + if (dy0 < 0) { ysize += dy0, sy0 = -dy0, dy0 = 0; - if (dy0 + ysize > imOut->ysize) + } + if (dy0 + ysize > imOut->ysize) { ysize = imOut->ysize - dy0; + } - if (xsize <= 0 || ysize <= 0) + if (xsize <= 0 || ysize <= 0) { return 0; + } if (!imMask) { ImagingSectionEnter(&cookie); @@ -250,30 +287,28 @@ ImagingPaste(Imaging imOut, Imaging imIn, Imaging imMask, } else if (strcmp(imMask->mode, "1") == 0) { ImagingSectionEnter(&cookie); - paste_mask_1(imOut, imIn, imMask, dx0, dy0, sx0, sy0, - xsize, ysize, pixelsize); + paste_mask_1(imOut, imIn, imMask, dx0, dy0, sx0, sy0, xsize, ysize, pixelsize); ImagingSectionLeave(&cookie); } else if (strcmp(imMask->mode, "L") == 0) { ImagingSectionEnter(&cookie); - paste_mask_L(imOut, imIn, imMask, dx0, dy0, sx0, sy0, - xsize, ysize, pixelsize); + paste_mask_L(imOut, imIn, imMask, dx0, dy0, sx0, sy0, xsize, ysize, pixelsize); ImagingSectionLeave(&cookie); } else if (strcmp(imMask->mode, "RGBA") == 0) { ImagingSectionEnter(&cookie); - paste_mask_RGBA(imOut, imIn, imMask, dx0, dy0, sx0, sy0, - xsize, ysize, pixelsize); + paste_mask_RGBA( + imOut, imIn, imMask, dx0, dy0, sx0, sy0, xsize, ysize, pixelsize); ImagingSectionLeave(&cookie); } else if (strcmp(imMask->mode, "RGBa") == 0) { ImagingSectionEnter(&cookie); - paste_mask_RGBa(imOut, imIn, imMask, dx0, dy0, sx0, sy0, - xsize, ysize, pixelsize); + paste_mask_RGBa( + imOut, imIn, imMask, dx0, dy0, sx0, sy0, xsize, ysize, pixelsize); ImagingSectionLeave(&cookie); } else { - (void) ImagingError_ValueError("bad transparency mask"); + (void)ImagingError_ValueError("bad transparency mask"); return -1; } @@ -281,9 +316,14 @@ ImagingPaste(Imaging imOut, Imaging imIn, Imaging imMask, } static inline void -fill(Imaging imOut, const void* ink_, int dx, int dy, - int xsize, int ysize, int pixelsize) -{ +fill( + Imaging imOut, + const void *ink_, + int dx, + int dy, + int xsize, + int ysize, + int pixelsize) { /* fill opaque region */ int x, y; @@ -294,28 +334,34 @@ fill(Imaging imOut, const void* ink_, int dx, int dy, memcpy(&ink8, ink_, sizeof(ink8)); if (imOut->image8 || ink32 == 0L) { - dx *= pixelsize; xsize *= pixelsize; - for (y = 0; y < ysize; y++) - memset(imOut->image[y+dy]+dx, ink8, xsize); - - } else { - for (y = 0; y < ysize; y++) { - INT32* out = imOut->image32[y+dy]+dx; - for (x = 0; x < xsize; x++) - out[x] = ink32; + memset(imOut->image[y + dy] + dx, ink8, xsize); } + } else { + for (y = 0; y < ysize; y++) { + INT32 *out = imOut->image32[y + dy] + dx; + for (x = 0; x < xsize; x++) { + out[x] = ink32; + } + } } } static inline void -fill_mask_1(Imaging imOut, const void* ink_, Imaging imMask, - int dx, int dy, int sx, int sy, - int xsize, int ysize, int pixelsize) -{ +fill_mask_1( + Imaging imOut, + const void *ink_, + Imaging imMask, + int dx, + int dy, + int sx, + int sy, + int xsize, + int ysize, + int pixelsize) { /* fill with mode "1" mask */ int x, y; @@ -326,25 +372,25 @@ fill_mask_1(Imaging imOut, const void* ink_, Imaging imMask, memcpy(&ink8, ink_, sizeof(ink8)); if (imOut->image8) { - for (y = 0; y < ysize; y++) { - UINT8* out = imOut->image8[y+dy]+dx; - UINT8* mask = imMask->image8[y+sy]+sx; + UINT8 *out = imOut->image8[y + dy] + dx; + UINT8 *mask = imMask->image8[y + sy] + sx; for (x = 0; x < xsize; x++) { - if (*mask++) + if (*mask++) { *out = ink8; + } out++; } } } else { - for (y = 0; y < ysize; y++) { - INT32* out = imOut->image32[y+dy]+dx; - UINT8* mask = imMask->image8[y+sy]+sx; + INT32 *out = imOut->image32[y + dy] + dx; + UINT8 *mask = imMask->image8[y + sy] + sx; for (x = 0; x < xsize; x++) { - if (*mask++) + if (*mask++) { *out = ink32; + } out++; } } @@ -352,20 +398,26 @@ fill_mask_1(Imaging imOut, const void* ink_, Imaging imMask, } static inline void -fill_mask_L(Imaging imOut, const UINT8* ink, Imaging imMask, - int dx, int dy, int sx, int sy, - int xsize, int ysize, int pixelsize) -{ +fill_mask_L( + Imaging imOut, + const UINT8 *ink, + Imaging imMask, + int dx, + int dy, + int sx, + int sy, + int xsize, + int ysize, + int pixelsize) { /* fill with mode "L" matte */ int x, y, i; unsigned int tmp1; if (imOut->image8) { - for (y = 0; y < ysize; y++) { - UINT8* out = imOut->image8[y+dy]+dx; - UINT8* mask = imMask->image8[y+sy]+sx; + UINT8 *out = imOut->image8[y + dy] + dx; + UINT8 *mask = imMask->image8[y + sy] + sx; for (x = 0; x < xsize; x++) { *out = BLEND(*mask, *out, ink[0], tmp1); out++, mask++; @@ -373,15 +425,24 @@ fill_mask_L(Imaging imOut, const UINT8* ink, Imaging imMask, } } else { - for (y = 0; y < ysize; y++) { - UINT8* out = (UINT8*) imOut->image[y+dy]+dx*pixelsize; - UINT8* mask = (UINT8*) imMask->image[y+sy]+sx; + UINT8 *out = (UINT8 *)imOut->image[y + dy] + dx * pixelsize; + UINT8 *mask = (UINT8 *)imMask->image[y + sy] + sx; for (x = 0; x < xsize; x++) { for (i = 0; i < pixelsize; i++) { - *out = BLEND(*mask, *out, ink[i], tmp1); - out++; + UINT8 channel_mask = *mask; + if ((strcmp(imOut->mode, "RGBa") == 0 || + strcmp(imOut->mode, "RGBA") == 0 || + strcmp(imOut->mode, "La") == 0 || + strcmp(imOut->mode, "LA") == 0 || + strcmp(imOut->mode, "PA") == 0) && + i != 3) { + channel_mask = + 255 - (255 - channel_mask) * (1 - (255 - out[3]) / 255); + } + out[i] = BLEND(channel_mask, out[i], ink[i], tmp1); } + out += pixelsize; mask++; } } @@ -389,21 +450,27 @@ fill_mask_L(Imaging imOut, const UINT8* ink, Imaging imMask, } static inline void -fill_mask_RGBA(Imaging imOut, const UINT8* ink, Imaging imMask, - int dx, int dy, int sx, int sy, - int xsize, int ysize, int pixelsize) -{ +fill_mask_RGBA( + Imaging imOut, + const UINT8 *ink, + Imaging imMask, + int dx, + int dy, + int sx, + int sy, + int xsize, + int ysize, + int pixelsize) { /* fill with mode "RGBA" matte */ int x, y, i; unsigned int tmp1; if (imOut->image8) { - - sx = sx*4+3; + sx = sx * 4 + 3; for (y = 0; y < ysize; y++) { - UINT8* out = imOut->image8[y+dy]+dx; - UINT8* mask = (UINT8*) imMask->image[y+sy]+sx; + UINT8 *out = imOut->image8[y + dy] + dx; + UINT8 *mask = (UINT8 *)imMask->image[y + sy] + sx; for (x = 0; x < xsize; x++) { *out = BLEND(*mask, *out, ink[0], tmp1); out++, mask += 4; @@ -411,12 +478,11 @@ fill_mask_RGBA(Imaging imOut, const UINT8* ink, Imaging imMask, } } else { - dx *= pixelsize; - sx = sx*4 + 3; + sx = sx * 4 + 3; for (y = 0; y < ysize; y++) { - UINT8* out = (UINT8*) imOut->image[y+dy]+dx; - UINT8* mask = (UINT8*) imMask->image[y+sy]+sx; + UINT8 *out = (UINT8 *)imOut->image[y + dy] + dx; + UINT8 *mask = (UINT8 *)imMask->image[y + sy] + sx; for (x = 0; x < xsize; x++) { for (i = 0; i < pixelsize; i++) { *out = BLEND(*mask, *out, ink[i], tmp1); @@ -429,21 +495,27 @@ fill_mask_RGBA(Imaging imOut, const UINT8* ink, Imaging imMask, } static inline void -fill_mask_RGBa(Imaging imOut, const UINT8* ink, Imaging imMask, - int dx, int dy, int sx, int sy, - int xsize, int ysize, int pixelsize) -{ +fill_mask_RGBa( + Imaging imOut, + const UINT8 *ink, + Imaging imMask, + int dx, + int dy, + int sx, + int sy, + int xsize, + int ysize, + int pixelsize) { /* fill with mode "RGBa" matte */ int x, y, i; unsigned int tmp1; if (imOut->image8) { - - sx = sx*4 + 3; + sx = sx * 4 + 3; for (y = 0; y < ysize; y++) { - UINT8* out = imOut->image8[y+dy]+dx; - UINT8* mask = (UINT8*) imMask->image[y+sy]+sx; + UINT8 *out = imOut->image8[y + dy] + dx; + UINT8 *mask = (UINT8 *)imMask->image[y + sy] + sx; for (x = 0; x < xsize; x++) { *out = PREBLEND(*mask, *out, ink[0], tmp1); out++, mask += 4; @@ -451,12 +523,11 @@ fill_mask_RGBa(Imaging imOut, const UINT8* ink, Imaging imMask, } } else { - dx *= pixelsize; - sx = sx*4 + 3; + sx = sx * 4 + 3; for (y = 0; y < ysize; y++) { - UINT8* out = (UINT8*) imOut->image[y+dy]+dx; - UINT8* mask = (UINT8*) imMask->image[y+sy]+sx; + UINT8 *out = (UINT8 *)imOut->image[y + dy] + dx; + UINT8 *mask = (UINT8 *)imMask->image[y + sy] + sx; for (x = 0; x < xsize; x++) { for (i = 0; i < pixelsize; i++) { *out = PREBLEND(*mask, *out, ink[i], tmp1); @@ -469,16 +540,21 @@ fill_mask_RGBa(Imaging imOut, const UINT8* ink, Imaging imMask, } int -ImagingFill2(Imaging imOut, const void* ink, Imaging imMask, - int dx0, int dy0, int dx1, int dy1) -{ +ImagingFill2( + Imaging imOut, + const void *ink, + Imaging imMask, + int dx0, + int dy0, + int dx1, + int dy1) { ImagingSectionCookie cookie; int xsize, ysize; int pixelsize; int sx0, sy0; if (!imOut || !ink) { - (void) ImagingError_ModeError(); + (void)ImagingError_ModeError(); return -1; } @@ -488,23 +564,28 @@ ImagingFill2(Imaging imOut, const void* ink, Imaging imMask, ysize = dy1 - dy0; if (imMask && (xsize != imMask->xsize || ysize != imMask->ysize)) { - (void) ImagingError_Mismatch(); + (void)ImagingError_Mismatch(); return -1; } /* Determine which region to fill */ sx0 = sy0 = 0; - if (dx0 < 0) + if (dx0 < 0) { xsize += dx0, sx0 = -dx0, dx0 = 0; - if (dx0 + xsize > imOut->xsize) + } + if (dx0 + xsize > imOut->xsize) { xsize = imOut->xsize - dx0; - if (dy0 < 0) + } + if (dy0 < 0) { ysize += dy0, sy0 = -dy0, dy0 = 0; - if (dy0 + ysize > imOut->ysize) + } + if (dy0 + ysize > imOut->ysize) { ysize = imOut->ysize - dy0; + } - if (xsize <= 0 || ysize <= 0) + if (xsize <= 0 || ysize <= 0) { return 0; + } if (!imMask) { ImagingSectionEnter(&cookie); @@ -513,30 +594,26 @@ ImagingFill2(Imaging imOut, const void* ink, Imaging imMask, } else if (strcmp(imMask->mode, "1") == 0) { ImagingSectionEnter(&cookie); - fill_mask_1(imOut, ink, imMask, dx0, dy0, sx0, sy0, - xsize, ysize, pixelsize); + fill_mask_1(imOut, ink, imMask, dx0, dy0, sx0, sy0, xsize, ysize, pixelsize); ImagingSectionLeave(&cookie); } else if (strcmp(imMask->mode, "L") == 0) { ImagingSectionEnter(&cookie); - fill_mask_L(imOut, ink, imMask, dx0, dy0, sx0, sy0, - xsize, ysize, pixelsize); + fill_mask_L(imOut, ink, imMask, dx0, dy0, sx0, sy0, xsize, ysize, pixelsize); ImagingSectionLeave(&cookie); } else if (strcmp(imMask->mode, "RGBA") == 0) { ImagingSectionEnter(&cookie); - fill_mask_RGBA(imOut, ink, imMask, dx0, dy0, sx0, sy0, - xsize, ysize, pixelsize); + fill_mask_RGBA(imOut, ink, imMask, dx0, dy0, sx0, sy0, xsize, ysize, pixelsize); ImagingSectionLeave(&cookie); } else if (strcmp(imMask->mode, "RGBa") == 0) { ImagingSectionEnter(&cookie); - fill_mask_RGBa(imOut, ink, imMask, dx0, dy0, sx0, sy0, - xsize, ysize, pixelsize); + fill_mask_RGBa(imOut, ink, imMask, dx0, dy0, sx0, sy0, xsize, ysize, pixelsize); ImagingSectionLeave(&cookie); } else { - (void) ImagingError_ValueError("bad transparency mask"); + (void)ImagingError_ValueError("bad transparency mask"); return -1; } diff --git a/src/libImaging/PcdDecode.c b/src/libImaging/PcdDecode.c index 8ff264edf..f13803cb6 100644 --- a/src/libImaging/PcdDecode.c +++ b/src/libImaging/PcdDecode.c @@ -5,13 +5,13 @@ * decoder for uncompressed PCD image data. * * history: - * 96-05-10 fl Created - * 96-05-18 fl New tables - * 97-01-25 fl Use PhotoYCC unpacker + * 96-05-10 fl Created + * 96-05-18 fl New tables + * 97-01-25 fl Use PhotoYCC unpacker * * notes: - * This driver supports uncompressed PCD modes only - * (resolutions up to 768x512). + * This driver supports uncompressed PCD modes only + * (resolutions up to 768x512). * * Copyright (c) Fredrik Lundh 1996-97. * Copyright (c) Secret Labs AB 1997. @@ -19,60 +19,56 @@ * See the README file for information on usage and redistribution. */ - #include "Imaging.h" - int -ImagingPcdDecode(Imaging im, ImagingCodecState state, UINT8* buf, Py_ssize_t bytes) -{ +ImagingPcdDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t bytes) { int x; int chunk; - UINT8* out; - UINT8* ptr; + UINT8 *out; + UINT8 *ptr; ptr = buf; chunk = 3 * state->xsize; for (;;) { + /* We need data for two full lines before we can do anything */ + if (bytes < chunk) { + return ptr - buf; + } - /* We need data for two full lines before we can do anything */ - if (bytes < chunk) - return ptr - buf; + /* Unpack first line */ + out = state->buffer; + for (x = 0; x < state->xsize; x++) { + out[0] = ptr[x]; + out[1] = ptr[(x + 4 * state->xsize) / 2]; + out[2] = ptr[(x + 5 * state->xsize) / 2]; + out += 3; + } - /* Unpack first line */ - out = state->buffer; - for (x = 0; x < state->xsize; x++) { - out[0] = ptr[x]; - out[1] = ptr[(x+4*state->xsize)/2]; - out[2] = ptr[(x+5*state->xsize)/2]; - out += 3; - } + state->shuffle((UINT8 *)im->image[state->y], state->buffer, state->xsize); - state->shuffle((UINT8*) im->image[state->y], - state->buffer, state->xsize); + if (++state->y >= state->ysize) { + return -1; /* This can hardly happen */ + } - if (++state->y >= state->ysize) - return -1; /* This can hardly happen */ + /* Unpack second line */ + out = state->buffer; + for (x = 0; x < state->xsize; x++) { + out[0] = ptr[x + state->xsize]; + out[1] = ptr[(x + 4 * state->xsize) / 2]; + out[2] = ptr[(x + 5 * state->xsize) / 2]; + out += 3; + } - /* Unpack second line */ - out = state->buffer; - for (x = 0; x < state->xsize; x++) { - out[0] = ptr[x+state->xsize]; - out[1] = ptr[(x+4*state->xsize)/2]; - out[2] = ptr[(x+5*state->xsize)/2]; - out += 3; - } + state->shuffle((UINT8 *)im->image[state->y], state->buffer, state->xsize); - state->shuffle((UINT8*) im->image[state->y], - state->buffer, state->xsize); - - if (++state->y >= state->ysize) - return -1; - - ptr += chunk; - bytes -= chunk; + if (++state->y >= state->ysize) { + return -1; + } + ptr += chunk; + bytes -= chunk; } } diff --git a/src/libImaging/PcxDecode.c b/src/libImaging/PcxDecode.c index 9e9504ce5..c95ffc869 100644 --- a/src/libImaging/PcxDecode.c +++ b/src/libImaging/PcxDecode.c @@ -5,7 +5,7 @@ * decoder for PCX image data. * * history: - * 95-09-14 fl Created + * 95-09-14 fl Created * * Copyright (c) Fredrik Lundh 1995. * Copyright (c) Secret Labs AB 1997. @@ -13,19 +13,14 @@ * See the README file for information on usage and redistribution. */ - #include "Imaging.h" int -ImagingPcxDecode(Imaging im, ImagingCodecState state, UINT8* buf, Py_ssize_t bytes) -{ +ImagingPcxDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t bytes) { UINT8 n; - UINT8* ptr; + UINT8 *ptr; - if (strcmp(im->mode, "1") == 0 && state->xsize > state->bytes * 8) { - state->errcode = IMAGING_CODEC_OVERRUN; - return -1; - } else if (strcmp(im->mode, "P") == 0 && state->xsize > state->bytes) { + if ((state->xsize * state->bits + 7) / 8 > state->bytes) { state->errcode = IMAGING_CODEC_OVERRUN; return -1; } @@ -33,60 +28,62 @@ ImagingPcxDecode(Imaging im, ImagingCodecState state, UINT8* buf, Py_ssize_t byt ptr = buf; for (;;) { + if (bytes < 1) { + return ptr - buf; + } - if (bytes < 1) - return ptr - buf; + if ((*ptr & 0xC0) == 0xC0) { + /* Run */ + if (bytes < 2) { + return ptr - buf; + } - if ((*ptr & 0xC0) == 0xC0) { + n = ptr[0] & 0x3F; - /* Run */ - if (bytes < 2) - return ptr - buf; + while (n > 0) { + if (state->x >= state->bytes) { + state->errcode = IMAGING_CODEC_OVERRUN; + break; + } + state->buffer[state->x++] = ptr[1]; + n--; + } - n = ptr[0] & 0x3F; + ptr += 2; + bytes -= 2; - while (n > 0) { - if (state->x >= state->bytes) { - state->errcode = IMAGING_CODEC_OVERRUN; - break; - } - state->buffer[state->x++] = ptr[1]; - n--; - } + } else { + /* Literal */ + state->buffer[state->x++] = ptr[0]; + ptr++; + bytes--; + } - ptr += 2; bytes -= 2; - - } else { - - /* Literal */ - state->buffer[state->x++] = ptr[0]; - ptr++; bytes--; - - } - - if (state->x >= state->bytes) { - if (state->bytes % state->xsize && state->bytes > state->xsize) { - int bands = state->bytes / state->xsize; - int stride = state->bytes / bands; - int i; - for (i=1; i< bands; i++) { // note -- skipping first band - memmove(&state->buffer[i*state->xsize], - &state->buffer[i*stride], + if (state->x >= state->bytes) { + if (state->bytes % state->xsize && state->bytes > state->xsize) { + int bands = state->bytes / state->xsize; + int stride = state->bytes / bands; + int i; + for (i = 1; i < bands; i++) { // note -- skipping first band + memmove( + &state->buffer[i * state->xsize], + &state->buffer[i * stride], state->xsize); + } + } + /* Got a full line, unpack it */ + state->shuffle( + (UINT8 *)im->image[state->y + state->yoff] + + state->xoff * im->pixelsize, + state->buffer, + state->xsize); + + state->x = 0; + + if (++state->y >= state->ysize) { + /* End of file (errcode = 0) */ + return -1; } } - /* Got a full line, unpack it */ - state->shuffle((UINT8*) im->image[state->y + state->yoff] + - state->xoff * im->pixelsize, state->buffer, - state->xsize); - - state->x = 0; - - if (++state->y >= state->ysize) { - /* End of file (errcode = 0) */ - return -1; - } - } - } } diff --git a/src/libImaging/PcxEncode.c b/src/libImaging/PcxEncode.c index 163b09b13..549614bfd 100644 --- a/src/libImaging/PcxEncode.c +++ b/src/libImaging/PcxEncode.c @@ -13,7 +13,6 @@ * See the README file for information on usage and redistribution. */ - #include "Imaging.h" enum { INIT, FETCH, ENCODE }; @@ -22,9 +21,8 @@ enum { INIT, FETCH, ENCODE }; #define LAST ystep int -ImagingPcxEncode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) -{ - UINT8* ptr; +ImagingPcxEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) { + UINT8 *ptr; int this; int bytes_per_line = 0; int padding = 0; @@ -45,12 +43,12 @@ ImagingPcxEncode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) } bpp = state->bits; - if (state->bits == 24){ + if (state->bits == 24) { planes = 3; bpp = 8; } - bytes_per_line = (state->xsize*bpp + 7) / 8; + bytes_per_line = (state->xsize * bpp + 7) / 8; /* The stride here needs to be kept in sync with the version in PcxImagePlugin.py. If it's not, the header and the body of the image will be out of sync and bad things will happen on decode. @@ -59,133 +57,131 @@ ImagingPcxEncode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) padding = stride - bytes_per_line; - for (;;) { - switch (state->state) { - case FETCH: + case FETCH: - /* get a line of data */ - if (state->y >= state->ysize) { - state->errcode = IMAGING_CODEC_END; - return ptr - buf; - } + /* get a line of data */ + if (state->y >= state->ysize) { + state->errcode = IMAGING_CODEC_END; + return ptr - buf; + } - state->shuffle(state->buffer, - (UINT8*) im->image[state->y + state->yoff] + - state->xoff * im->pixelsize, state->xsize); + state->shuffle( + state->buffer, + (UINT8 *)im->image[state->y + state->yoff] + + state->xoff * im->pixelsize, + state->xsize); - state->y += 1; + state->y += 1; - state->count = 1; - state->LAST = state->buffer[0]; + state->count = 1; + state->LAST = state->buffer[0]; - state->x = 1; + state->x = 1; - state->state = ENCODE; - /* fall through */ + state->state = ENCODE; + /* fall through */ - case ENCODE: - /* compress this line */ + case ENCODE: + /* compress this line */ - /* when we arrive here, "count" contains the number of - bytes having the value of "LAST" that we've already - seen */ - do { - /* If we're encoding an odd width file, and we've - got more than one plane, we need to pad each - color row with padding bytes at the end. Since - The pixels are stored RRRRRGGGGGBBBBB, so we need - to have the padding be RRRRRPGGGGGPBBBBBP. Hence - the double loop - */ - while (state->x % bytes_per_line) { - - if (state->count == 63) { - /* this run is full; flush it */ - if (bytes < 2) { - return ptr - buf; - } - ptr[0] = 0xff; - ptr[1] = state->LAST; - ptr += 2; - bytes -= 2; - - state->count = 0; - } - - this = state->buffer[state->x]; - - if (this == state->LAST) { - /* extend the current run */ - state->x += 1; - state->count += 1; - - } else { - /* start a new run */ - if (state->count == 1 && (state->LAST < 0xc0)) { - if (bytes < 1) { + /* when we arrive here, "count" contains the number of + bytes having the value of "LAST" that we've already + seen */ + do { + /* If we're encoding an odd width file, and we've + got more than one plane, we need to pad each + color row with padding bytes at the end. Since + The pixels are stored RRRRRGGGGGBBBBB, so we need + to have the padding be RRRRRPGGGGGPBBBBBP. Hence + the double loop + */ + while (state->x % bytes_per_line) { + if (state->count == 63) { + /* this run is full; flush it */ + if (bytes < 2) { return ptr - buf; } - ptr[0] = state->LAST; - ptr += 1; - bytes -= 1; + ptr[0] = 0xff; + ptr[1] = state->LAST; + ptr += 2; + bytes -= 2; + + state->count = 0; + } + + this = state->buffer[state->x]; + + if (this == state->LAST) { + /* extend the current run */ + state->x += 1; + state->count += 1; + } else { - if (state->count > 0) { - if (bytes < 2) { + /* start a new run */ + if (state->count == 1 && (state->LAST < 0xc0)) { + if (bytes < 1) { return ptr - buf; } - ptr[0] = 0xc0 | state->count; - ptr[1] = state->LAST; - ptr += 2; - bytes -= 2; + ptr[0] = state->LAST; + ptr += 1; + bytes -= 1; + } else { + if (state->count > 0) { + if (bytes < 2) { + return ptr - buf; + } + ptr[0] = 0xc0 | state->count; + ptr[1] = state->LAST; + ptr += 2; + bytes -= 2; + } } + + state->LAST = this; + state->count = 1; + + state->x += 1; } - - state->LAST = this; - state->count = 1; - - state->x += 1; } - } - /* end of line; flush the current run */ - if (state->count == 1 && (state->LAST < 0xc0)) { - if (bytes < 1 + padding) { - return ptr - buf; - } - ptr[0] = state->LAST; - ptr += 1; - bytes -= 1; - } else { - if (state->count > 0) { - if (bytes < 2 + padding) { + /* end of line; flush the current run */ + if (state->count == 1 && (state->LAST < 0xc0)) { + if (bytes < 1 + padding) { return ptr - buf; } - ptr[0] = 0xc0 | state->count; - ptr[1] = state->LAST; - ptr += 2; - bytes -= 2; + ptr[0] = state->LAST; + ptr += 1; + bytes -= 1; + } else { + if (state->count > 0) { + if (bytes < 2 + padding) { + return ptr - buf; + } + ptr[0] = 0xc0 | state->count; + ptr[1] = state->LAST; + ptr += 2; + bytes -= 2; + } } - } - /* add the padding */ - for (i = 0; i < padding; i++) { - ptr[0] = 0; - ptr += 1; - bytes -= 1; - } - /* reset for the next color plane. */ - if (state->x < planes * bytes_per_line) { - state->count = 1; - state->LAST = state->buffer[state->x]; - state->x += 1; - } - } while (state->x < planes * bytes_per_line); + /* add the padding */ + for (i = 0; i < padding; i++) { + ptr[0] = 0; + ptr += 1; + bytes -= 1; + } + /* reset for the next color plane. */ + if (state->x < planes * bytes_per_line) { + state->count = 1; + state->LAST = state->buffer[state->x]; + state->x += 1; + } + } while (state->x < planes * bytes_per_line); - /* read next line */ - state->state = FETCH; - break; + /* read next line */ + state->state = FETCH; + break; } } } - diff --git a/src/libImaging/Point.c b/src/libImaging/Point.c index 9b4bf6b75..8883578cb 100644 --- a/src/libImaging/Point.c +++ b/src/libImaging/Point.c @@ -19,166 +19,171 @@ * See the README file for information on usage and redistribution. */ - #include "Imaging.h" typedef struct { - const void* table; + const void *table; } im_point_context; static void -im_point_8_8(Imaging imOut, Imaging imIn, im_point_context* context) -{ +im_point_8_8(Imaging imOut, Imaging imIn, im_point_context *context) { int x, y; /* 8-bit source, 8-bit destination */ - UINT8* table = (UINT8*) context->table; + UINT8 *table = (UINT8 *)context->table; for (y = 0; y < imIn->ysize; y++) { - UINT8* in = imIn->image8[y]; - UINT8* out = imOut->image8[y]; - for (x = 0; x < imIn->xsize; x++) + UINT8 *in = imIn->image8[y]; + UINT8 *out = imOut->image8[y]; + for (x = 0; x < imIn->xsize; x++) { out[x] = table[in[x]]; + } } } static void -im_point_2x8_2x8(Imaging imOut, Imaging imIn, im_point_context* context) -{ +im_point_2x8_2x8(Imaging imOut, Imaging imIn, im_point_context *context) { int x, y; /* 2x8-bit source, 2x8-bit destination */ - UINT8* table = (UINT8*) context->table; + UINT8 *table = (UINT8 *)context->table; for (y = 0; y < imIn->ysize; y++) { - UINT8* in = (UINT8*) imIn->image[y]; - UINT8* out = (UINT8*) imOut->image[y]; + UINT8 *in = (UINT8 *)imIn->image[y]; + UINT8 *out = (UINT8 *)imOut->image[y]; for (x = 0; x < imIn->xsize; x++) { out[0] = table[in[0]]; - out[3] = table[in[3]+256]; - in += 4; out += 4; + out[3] = table[in[3] + 256]; + in += 4; + out += 4; } } } static void -im_point_3x8_3x8(Imaging imOut, Imaging imIn, im_point_context* context) -{ +im_point_3x8_3x8(Imaging imOut, Imaging imIn, im_point_context *context) { int x, y; /* 3x8-bit source, 3x8-bit destination */ - UINT8* table = (UINT8*) context->table; + UINT8 *table = (UINT8 *)context->table; for (y = 0; y < imIn->ysize; y++) { - UINT8* in = (UINT8*) imIn->image[y]; - UINT8* out = (UINT8*) imOut->image[y]; + UINT8 *in = (UINT8 *)imIn->image[y]; + UINT8 *out = (UINT8 *)imOut->image[y]; for (x = 0; x < imIn->xsize; x++) { out[0] = table[in[0]]; - out[1] = table[in[1]+256]; - out[2] = table[in[2]+512]; - in += 4; out += 4; + out[1] = table[in[1] + 256]; + out[2] = table[in[2] + 512]; + in += 4; + out += 4; } } } static void -im_point_4x8_4x8(Imaging imOut, Imaging imIn, im_point_context* context) -{ +im_point_4x8_4x8(Imaging imOut, Imaging imIn, im_point_context *context) { int x, y; /* 4x8-bit source, 4x8-bit destination */ - UINT8* table = (UINT8*) context->table; + UINT8 *table = (UINT8 *)context->table; for (y = 0; y < imIn->ysize; y++) { - UINT8* in = (UINT8*) imIn->image[y]; - UINT8* out = (UINT8*) imOut->image[y]; + UINT8 *in = (UINT8 *)imIn->image[y]; + UINT8 *out = (UINT8 *)imOut->image[y]; for (x = 0; x < imIn->xsize; x++) { out[0] = table[in[0]]; - out[1] = table[in[1]+256]; - out[2] = table[in[2]+512]; - out[3] = table[in[3]+768]; - in += 4; out += 4; + out[1] = table[in[1] + 256]; + out[2] = table[in[2] + 512]; + out[3] = table[in[3] + 768]; + in += 4; + out += 4; } } } static void -im_point_8_32(Imaging imOut, Imaging imIn, im_point_context* context) -{ +im_point_8_32(Imaging imOut, Imaging imIn, im_point_context *context) { int x, y; /* 8-bit source, 32-bit destination */ - char* table = (char*) context->table; + char *table = (char *)context->table; for (y = 0; y < imIn->ysize; y++) { - UINT8* in = imIn->image8[y]; - INT32* out = imOut->image32[y]; - for (x = 0; x < imIn->xsize; x++) + UINT8 *in = imIn->image8[y]; + INT32 *out = imOut->image32[y]; + for (x = 0; x < imIn->xsize; x++) { memcpy(out + x, table + in[x] * sizeof(INT32), sizeof(INT32)); + } } } static void -im_point_32_8(Imaging imOut, Imaging imIn, im_point_context* context) -{ +im_point_32_8(Imaging imOut, Imaging imIn, im_point_context *context) { int x, y; /* 32-bit source, 8-bit destination */ - UINT8* table = (UINT8*) context->table; + UINT8 *table = (UINT8 *)context->table; for (y = 0; y < imIn->ysize; y++) { - INT32* in = imIn->image32[y]; - UINT8* out = imOut->image8[y]; + INT32 *in = imIn->image32[y]; + UINT8 *out = imOut->image8[y]; for (x = 0; x < imIn->xsize; x++) { int v = in[x]; - if (v < 0) + if (v < 0) { v = 0; - else if (v > 65535) + } else if (v > 65535) { v = 65535; + } out[x] = table[v]; } } } Imaging -ImagingPoint(Imaging imIn, const char* mode, const void* table) -{ +ImagingPoint(Imaging imIn, const char *mode, const void *table) { /* lookup table transform */ ImagingSectionCookie cookie; Imaging imOut; im_point_context context; - void (*point)(Imaging imIn, Imaging imOut, im_point_context* context); + void (*point)(Imaging imIn, Imaging imOut, im_point_context * context); - if (!imIn) - return (Imaging) ImagingError_ModeError(); + if (!imIn) { + return (Imaging)ImagingError_ModeError(); + } - if (!mode) + if (!mode) { mode = imIn->mode; + } if (imIn->type != IMAGING_TYPE_UINT8) { - if (imIn->type != IMAGING_TYPE_INT32 || strcmp(mode, "L") != 0) + if (imIn->type != IMAGING_TYPE_INT32 || strcmp(mode, "L") != 0) { goto mode_mismatch; - } else if (!imIn->image8 && strcmp(imIn->mode, mode) != 0) + } + } else if (!imIn->image8 && strcmp(imIn->mode, mode) != 0) { goto mode_mismatch; + } imOut = ImagingNew(mode, imIn->xsize, imIn->ysize); - if (!imOut) - return NULL; + if (!imOut) { + return NULL; + } /* find appropriate handler */ if (imIn->type == IMAGING_TYPE_UINT8) { if (imIn->bands == imOut->bands && imIn->type == imOut->type) { switch (imIn->bands) { - case 1: - point = im_point_8_8; - break; - case 2: - point = im_point_2x8_2x8; - break; - case 3: - point = im_point_3x8_3x8; - break; - case 4: - point = im_point_4x8_4x8; - break; - default: - /* this cannot really happen */ - point = im_point_8_8; - break; + case 1: + point = im_point_8_8; + break; + case 2: + point = im_point_2x8_2x8; + break; + case 3: + point = im_point_3x8_3x8; + break; + case 4: + point = im_point_4x8_4x8; + break; + default: + /* this cannot really happen */ + point = im_point_8_8; + break; } - } else + } else { point = im_point_8_32; - } else + } + } else { point = im_point_32_8; + } ImagingCopyPalette(imOut, imIn); @@ -191,74 +196,74 @@ ImagingPoint(Imaging imIn, const char* mode, const void* table) return imOut; - mode_mismatch: - return (Imaging) ImagingError_ValueError( - "point operation not supported for this mode" - ); +mode_mismatch: + return (Imaging)ImagingError_ValueError( + "point operation not supported for this mode"); } - Imaging -ImagingPointTransform(Imaging imIn, double scale, double offset) -{ +ImagingPointTransform(Imaging imIn, double scale, double offset) { /* scale/offset transform */ ImagingSectionCookie cookie; Imaging imOut; int x, y; - if (!imIn || (strcmp(imIn->mode, "I") != 0 && - strcmp(imIn->mode, "I;16") != 0 && - strcmp(imIn->mode, "F") != 0)) - return (Imaging) ImagingError_ModeError(); + if (!imIn || (strcmp(imIn->mode, "I") != 0 && strcmp(imIn->mode, "I;16") != 0 && + strcmp(imIn->mode, "F") != 0)) { + return (Imaging)ImagingError_ModeError(); + } imOut = ImagingNew(imIn->mode, imIn->xsize, imIn->ysize); - if (!imOut) - return NULL; + if (!imOut) { + return NULL; + } switch (imIn->type) { - case IMAGING_TYPE_INT32: - ImagingSectionEnter(&cookie); - for (y = 0; y < imIn->ysize; y++) { - INT32* in = imIn->image32[y]; - INT32* out = imOut->image32[y]; - /* FIXME: add clipping? */ - for (x = 0; x < imIn->xsize; x++) - out[x] = in[x] * scale + offset; - } - ImagingSectionLeave(&cookie); - break; - case IMAGING_TYPE_FLOAT32: - ImagingSectionEnter(&cookie); - for (y = 0; y < imIn->ysize; y++) { - FLOAT32* in = (FLOAT32*) imIn->image32[y]; - FLOAT32* out = (FLOAT32*) imOut->image32[y]; - for (x = 0; x < imIn->xsize; x++) - out[x] = in[x] * scale + offset; - } - ImagingSectionLeave(&cookie); - break; - case IMAGING_TYPE_SPECIAL: - if (strcmp(imIn->mode,"I;16") == 0) { + case IMAGING_TYPE_INT32: ImagingSectionEnter(&cookie); for (y = 0; y < imIn->ysize; y++) { - char* in = (char*)imIn->image[y]; - char* out = (char*)imOut->image[y]; + INT32 *in = imIn->image32[y]; + INT32 *out = imOut->image32[y]; /* FIXME: add clipping? */ for (x = 0; x < imIn->xsize; x++) { - UINT16 v; - memcpy(&v, in + x * sizeof(v), sizeof(v)); - v = v * scale + offset; - memcpy(out + x * sizeof(UINT16), &v, sizeof(v)); + out[x] = in[x] * scale + offset; } } ImagingSectionLeave(&cookie); break; - } - /* FALL THROUGH */ - default: - ImagingDelete(imOut); - return (Imaging) ImagingError_ValueError("internal error"); + case IMAGING_TYPE_FLOAT32: + ImagingSectionEnter(&cookie); + for (y = 0; y < imIn->ysize; y++) { + FLOAT32 *in = (FLOAT32 *)imIn->image32[y]; + FLOAT32 *out = (FLOAT32 *)imOut->image32[y]; + for (x = 0; x < imIn->xsize; x++) { + out[x] = in[x] * scale + offset; + } + } + ImagingSectionLeave(&cookie); + break; + case IMAGING_TYPE_SPECIAL: + if (strcmp(imIn->mode, "I;16") == 0) { + ImagingSectionEnter(&cookie); + for (y = 0; y < imIn->ysize; y++) { + char *in = (char *)imIn->image[y]; + char *out = (char *)imOut->image[y]; + /* FIXME: add clipping? */ + for (x = 0; x < imIn->xsize; x++) { + UINT16 v; + memcpy(&v, in + x * sizeof(v), sizeof(v)); + v = v * scale + offset; + memcpy(out + x * sizeof(UINT16), &v, sizeof(v)); + } + } + ImagingSectionLeave(&cookie); + break; + } + /* FALL THROUGH */ + default: + ImagingDelete(imOut); + return (Imaging)ImagingError_ValueError("internal error"); } return imOut; diff --git a/src/libImaging/Quant.c b/src/libImaging/Quant.c index b94dc6e1d..bee5e5599 100644 --- a/src/libImaging/Quant.c +++ b/src/libImaging/Quant.c @@ -43,165 +43,155 @@ typedef struct { } PixelHashData; typedef struct _PixelList { - struct _PixelList *next[3],*prev[3]; + struct _PixelList *next[3], *prev[3]; Pixel p; - unsigned int flag:1; + unsigned int flag : 1; int count; } PixelList; typedef struct _BoxNode { - struct _BoxNode *l,*r; - PixelList *head[3],*tail[3]; + struct _BoxNode *l, *r; + PixelList *head[3], *tail[3]; int axis; int volume; uint32_t pixelCount; } BoxNode; -#define _SQR(x) ((x)*(x)) -#define _DISTSQR(p1,p2) \ - _SQR((int)((p1)->c.r)-(int)((p2)->c.r))+ \ - _SQR((int)((p1)->c.g)-(int)((p2)->c.g))+ \ - _SQR((int)((p1)->c.b)-(int)((p2)->c.b)) +#define _SQR(x) ((x) * (x)) +#define _DISTSQR(p1, p2) \ + _SQR((int)((p1)->c.r) - (int)((p2)->c.r)) + \ + _SQR((int)((p1)->c.g) - (int)((p2)->c.g)) + \ + _SQR((int)((p1)->c.b) - (int)((p2)->c.b)) #define MAX_HASH_ENTRIES 65536 -#define PIXEL_HASH(r,g,b) \ - (((unsigned int)(r) )*463 ^ \ - ((unsigned int)(g)<< 8)*10069 ^ \ - ((unsigned int)(b)<<16)*64997) +#define PIXEL_HASH(r, g, b) \ + (((unsigned int)(r)) * 463 ^ ((unsigned int)(g) << 8) * 10069 ^ \ + ((unsigned int)(b) << 16) * 64997) -#define PIXEL_UNSCALE(p,q,s) \ - ((q)->c.r=(p)->c.r<<(s)), \ - ((q)->c.g=(p)->c.g<<(s)), \ - ((q)->c.b=(p)->c.b<<(s)) +#define PIXEL_UNSCALE(p, q, s) \ + ((q)->c.r = (p)->c.r << (s)), ((q)->c.g = (p)->c.g << (s)), \ + ((q)->c.b = (p)->c.b << (s)) -#define PIXEL_SCALE(p,q,s)\ - ((q)->c.r=(p)->c.r>>(s)), \ - ((q)->c.g=(p)->c.g>>(s)), \ - ((q)->c.b=(p)->c.b>>(s)) +#define PIXEL_SCALE(p, q, s) \ + ((q)->c.r = (p)->c.r >> (s)), ((q)->c.g = (p)->c.g >> (s)), \ + ((q)->c.b = (p)->c.b >> (s)) static uint32_t -unshifted_pixel_hash(const HashTable *h, const Pixel pixel) -{ - return PIXEL_HASH(pixel.c.r, pixel.c.g, pixel.c.b); +unshifted_pixel_hash(const HashTable *h, const Pixel pixel) { + return PIXEL_HASH(pixel.c.r, pixel.c.g, pixel.c.b); } static int -unshifted_pixel_cmp(const HashTable *h, const Pixel pixel1, const Pixel pixel2) -{ - if (pixel1.c.r==pixel2.c.r) { - if (pixel1.c.g==pixel2.c.g) { - if (pixel1.c.b==pixel2.c.b) { +unshifted_pixel_cmp(const HashTable *h, const Pixel pixel1, const Pixel pixel2) { + if (pixel1.c.r == pixel2.c.r) { + if (pixel1.c.g == pixel2.c.g) { + if (pixel1.c.b == pixel2.c.b) { return 0; } else { - return (int)(pixel1.c.b)-(int)(pixel2.c.b); + return (int)(pixel1.c.b) - (int)(pixel2.c.b); } } else { - return (int)(pixel1.c.g)-(int)(pixel2.c.g); + return (int)(pixel1.c.g) - (int)(pixel2.c.g); } } else { - return (int)(pixel1.c.r)-(int)(pixel2.c.r); + return (int)(pixel1.c.r) - (int)(pixel2.c.r); } } static uint32_t -pixel_hash(const HashTable *h,const Pixel pixel) -{ - PixelHashData *d=(PixelHashData *)hashtable_get_user_data(h); - return PIXEL_HASH(pixel.c.r>>d->scale, pixel.c.g>>d->scale, pixel.c.b>>d->scale); +pixel_hash(const HashTable *h, const Pixel pixel) { + PixelHashData *d = (PixelHashData *)hashtable_get_user_data(h); + return PIXEL_HASH( + pixel.c.r >> d->scale, pixel.c.g >> d->scale, pixel.c.b >> d->scale); } static int -pixel_cmp(const HashTable *h,const Pixel pixel1, const Pixel pixel2) -{ - PixelHashData *d=(PixelHashData *)hashtable_get_user_data(h); - uint32_t A,B; - A=PIXEL_HASH(pixel1.c.r>>d->scale, pixel1.c.g>>d->scale, pixel1.c.b>>d->scale); - B=PIXEL_HASH(pixel2.c.r>>d->scale, pixel2.c.g>>d->scale, pixel2.c.b>>d->scale); - return (A==B)?0:((A> d->scale, pixel1.c.g >> d->scale, pixel1.c.b >> d->scale); + B = PIXEL_HASH( + pixel2.c.r >> d->scale, pixel2.c.g >> d->scale, pixel2.c.b >> d->scale); + return (A == B) ? 0 : ((A < B) ? -1 : 1); } static void -exists_count_func(const HashTable *h, const Pixel key, uint32_t *val) -{ - *val+=1; +exists_count_func(const HashTable *h, const Pixel key, uint32_t *val) { + *val += 1; } static void -new_count_func(const HashTable *h, const Pixel key, uint32_t *val) -{ - *val=1; +new_count_func(const HashTable *h, const Pixel key, uint32_t *val) { + *val = 1; } static void -rehash_collide(const HashTable *h, - Pixel *keyp, - uint32_t *valp, - Pixel newkey, - uint32_t newval) -{ +rehash_collide( + const HashTable *h, Pixel *keyp, uint32_t *valp, Pixel newkey, uint32_t newval) { *valp += newval; } /* %% */ static HashTable * -create_pixel_hash(Pixel *pixelData,uint32_t nPixels) -{ - PixelHashData *d; - HashTable *hash; - uint32_t i; +create_pixel_hash(Pixel *pixelData, uint32_t nPixels) { + PixelHashData *d; + HashTable *hash; + uint32_t i; #ifndef NO_OUTPUT - uint32_t timer,timer2,timer3; + uint32_t timer, timer2, timer3; #endif - /* malloc check ok, small constant allocation */ - d=malloc(sizeof(PixelHashData)); - if (!d) return NULL; - hash=hashtable_new(pixel_hash,pixel_cmp); - hashtable_set_user_data(hash,d); - d->scale=0; + /* malloc check ok, small constant allocation */ + d = malloc(sizeof(PixelHashData)); + if (!d) { + return NULL; + } + hash = hashtable_new(pixel_hash, pixel_cmp); + hashtable_set_user_data(hash, d); + d->scale = 0; #ifndef NO_OUTPUT - timer=timer3=clock(); + timer = timer3 = clock(); #endif - for (i=0;iMAX_HASH_ENTRIES) { - d->scale++; + for (i = 0; i < nPixels; i++) { + if (!hashtable_insert_or_update_computed( + hash, pixelData[i], new_count_func, exists_count_func)) { + ; + } + while (hashtable_get_count(hash) > MAX_HASH_ENTRIES) { + d->scale++; #ifndef NO_OUTPUT - printf ("rehashing - new scale: %d\n",(int)d->scale); - timer2=clock(); + printf("rehashing - new scale: %d\n", (int)d->scale); + timer2 = clock(); #endif - hashtable_rehash_compute(hash,rehash_collide); + hashtable_rehash_compute(hash, rehash_collide); #ifndef NO_OUTPUT - timer2=clock()-timer2; - printf ("rehash took %f sec\n",timer2/(double)CLOCKS_PER_SEC); - timer+=timer2; + timer2 = clock() - timer2; + printf("rehash took %f sec\n", timer2 / (double)CLOCKS_PER_SEC); + timer += timer2; #endif - } - } + } + } #ifndef NO_OUTPUT - printf ("inserts took %f sec\n",(clock()-timer)/(double)CLOCKS_PER_SEC); + printf("inserts took %f sec\n", (clock() - timer) / (double)CLOCKS_PER_SEC); #endif #ifndef NO_OUTPUT - printf ("total %f sec\n",(clock()-timer3)/(double)CLOCKS_PER_SEC); + printf("total %f sec\n", (clock() - timer3) / (double)CLOCKS_PER_SEC); #endif - return hash; + return hash; } static void -destroy_pixel_hash(HashTable *hash) -{ - PixelHashData *d=(PixelHashData *)hashtable_get_user_data(hash); - if (d) free(d); - hashtable_free(hash); +destroy_pixel_hash(HashTable *hash) { + PixelHashData *d = (PixelHashData *)hashtable_get_user_data(hash); + if (d) { + free(d); + } + hashtable_free(hash); } - /* 1. hash quantized pixels. */ /* 2. create R,G,B lists of sorted quantized pixels. */ /* 3. median cut. */ @@ -211,627 +201,662 @@ destroy_pixel_hash(HashTable *hash) /* 7. map each pixel to nearest average. */ static int -compute_box_volume(BoxNode *b) -{ - unsigned char rl,rh,gl,gh,bl,bh; - if (b->volume>=0) return b->volume; - if (!b->head[0]) { - b->volume=0; - } else { - rh=b->head[0]->p.c.r; - rl=b->tail[0]->p.c.r; - gh=b->head[1]->p.c.g; - gl=b->tail[1]->p.c.g; - bh=b->head[2]->p.c.b; - bl=b->tail[2]->p.c.b; - b->volume=(rh-rl+1)*(gh-gl+1)*(bh-bl+1); - } - return b->volume; +compute_box_volume(BoxNode *b) { + unsigned char rl, rh, gl, gh, bl, bh; + if (b->volume >= 0) { + return b->volume; + } + if (!b->head[0]) { + b->volume = 0; + } else { + rh = b->head[0]->p.c.r; + rl = b->tail[0]->p.c.r; + gh = b->head[1]->p.c.g; + gl = b->tail[1]->p.c.g; + bh = b->head[2]->p.c.b; + bl = b->tail[2]->p.c.b; + b->volume = (rh - rl + 1) * (gh - gl + 1) * (bh - bl + 1); + } + return b->volume; } static void -hash_to_list(const HashTable *h, const Pixel pixel, const uint32_t count, void *u) -{ - PixelHashData *d=(PixelHashData *)hashtable_get_user_data(h); - PixelList **pl=(PixelList **)u; - PixelList *p; - int i; - Pixel q; +hash_to_list(const HashTable *h, const Pixel pixel, const uint32_t count, void *u) { + PixelHashData *d = (PixelHashData *)hashtable_get_user_data(h); + PixelList **pl = (PixelList **)u; + PixelList *p; + int i; + Pixel q; - PIXEL_SCALE(&pixel,&q,d->scale); + PIXEL_SCALE(&pixel, &q, d->scale); - /* malloc check ok, small constant allocation */ - p=malloc(sizeof(PixelList)); - if (!p) return; + /* malloc check ok, small constant allocation */ + p = malloc(sizeof(PixelList)); + if (!p) { + return; + } - p->flag=0; - p->p=q; - p->count=count; - for (i=0;i<3;i++) { - p->next[i]=pl[i]; - p->prev[i]=NULL; - if (pl[i]) pl[i]->prev[i]=p; - pl[i]=p; - } + p->flag = 0; + p->p = q; + p->count = count; + for (i = 0; i < 3; i++) { + p->next[i] = pl[i]; + p->prev[i] = NULL; + if (pl[i]) { + pl[i]->prev[i] = p; + } + pl[i] = p; + } } static PixelList * -mergesort_pixels(PixelList *head, int i) -{ - PixelList *c,*t,*a,*b,*p; - if (!head||!head->next[i]) { - if (head) { - head->next[i]=NULL; - head->prev[i]=NULL; - } - return head; - } - for (c=t=head;c&&t;c=c->next[i],t=(t->next[i])?t->next[i]->next[i]:NULL); - if (c) { - if (c->prev[i]) c->prev[i]->next[i]=NULL; - c->prev[i]=NULL; - } - a=mergesort_pixels(head,i); - b=mergesort_pixels(c,i); - head=NULL; - p=NULL; - while (a&&b) { - if (a->p.a.v[i]>b->p.a.v[i]) { - c=a; - a=a->next[i]; - } else { - c=b; - b=b->next[i]; - } - c->prev[i]=p; - c->next[i]=NULL; - if (p) p->next[i]=c; - p=c; - if (!head) head=c; - } - if (a) { - c->next[i]=a; - a->prev[i]=c; - } else if (b) { - c->next[i]=b; - b->prev[i]=c; - } - return head; +mergesort_pixels(PixelList *head, int i) { + PixelList *c, *t, *a, *b, *p; + if (!head || !head->next[i]) { + if (head) { + head->next[i] = NULL; + head->prev[i] = NULL; + } + return head; + } + for (c = t = head; c && t; + c = c->next[i], t = (t->next[i]) ? t->next[i]->next[i] : NULL) + ; + if (c) { + if (c->prev[i]) { + c->prev[i]->next[i] = NULL; + } + c->prev[i] = NULL; + } + a = mergesort_pixels(head, i); + b = mergesort_pixels(c, i); + head = NULL; + p = NULL; + while (a && b) { + if (a->p.a.v[i] > b->p.a.v[i]) { + c = a; + a = a->next[i]; + } else { + c = b; + b = b->next[i]; + } + c->prev[i] = p; + c->next[i] = NULL; + if (p) { + p->next[i] = c; + } + p = c; + if (!head) { + head = c; + } + } + if (a) { + c->next[i] = a; + a->prev[i] = c; + } else if (b) { + c->next[i] = b; + b->prev[i] = c; + } + return head; } #if defined(TEST_MERGESORT) || defined(TEST_SORTED) static int -test_sorted(PixelList *pl[3]) -{ - int i,n,l; - PixelList *t; +test_sorted(PixelList *pl[3]) { + int i, n, l; + PixelList *t; - for(i=0;i<3;i++) { - n=0; - l=256; - for (t=pl[i];t;t=t->next[i]) { - if (lp.a.v[i]) return 0; - l=t->p.a.v[i]; - } - } - return 1; + for (i = 0; i < 3; i++) { + n = 0; + l = 256; + for (t = pl[i]; t; t = t->next[i]) { + if (l < t->p.a.v[i]) + return 0; + l = t->p.a.v[i]; + } + } + return 1; } #endif static int -box_heap_cmp(const Heap *h, const void *A, const void *B) -{ - BoxNode *a=(BoxNode *)A; - BoxNode *b=(BoxNode *)B; - return (int)a->pixelCount-(int)b->pixelCount; +box_heap_cmp(const Heap *h, const void *A, const void *B) { + BoxNode *a = (BoxNode *)A; + BoxNode *b = (BoxNode *)B; + return (int)a->pixelCount - (int)b->pixelCount; } -#define LUMINANCE(p) (77*(p)->c.r+150*(p)->c.g+29*(p)->c.b) +#define LUMINANCE(p) (77 * (p)->c.r + 150 * (p)->c.g + 29 * (p)->c.b) static int -splitlists(PixelList *h[3], - PixelList *t[3], - PixelList *nh[2][3], - PixelList *nt[2][3], - uint32_t nCount[2], - int axis, - uint32_t pixelCount) -{ - uint32_t left; +splitlists( + PixelList *h[3], + PixelList *t[3], + PixelList *nh[2][3], + PixelList *nt[2][3], + uint32_t nCount[2], + int axis, + uint32_t pixelCount) { + uint32_t left; - PixelList *l,*r,*c,*n; - int i; - int nRight,nLeft; - int splitColourVal; + PixelList *l, *r, *c, *n; + int i; + int nRight, nLeft; + int splitColourVal; #ifdef TEST_SPLIT - { - PixelList *_prevTest,*_nextTest; - int _i,_nextCount[3],_prevCount[3]; - for (_i=0;_i<3;_i++) { - for (_nextCount[_i]=0,_nextTest=h[_i];_nextTest&&_nextTest->next[_i];_nextTest=_nextTest->next[_i],_nextCount[_i]++); - for (_prevCount[_i]=0,_prevTest=t[_i];_prevTest&&_prevTest->prev[_i];_prevTest=_prevTest->prev[_i],_prevCount[_i]++); - if (_nextTest!=t[_i]) { - printf ("next-list of axis %d does not end at tail\n",_i); - exit(1); - } - if (_prevTest!=h[_i]) { - printf ("prev-list of axis %d does not end at head\n",_i); - exit(1); - } - for (;_nextTest&&_nextTest->prev[_i];_nextTest=_nextTest->prev[_i]); - for (;_prevTest&&_prevTest->next[_i];_prevTest=_prevTest->next[_i]); - if (_nextTest!=h[_i]) { - printf ("next-list of axis %d does not loop back to head\n",_i); - exit(1); - } - if (_prevTest!=t[_i]) { - printf ("prev-list of axis %d does not loop back to tail\n",_i); - exit(1); - } - } - for (_i=1;_i<3;_i++) { - if (_prevCount[_i]!=_prevCount[_i-1] || - _nextCount[_i]!=_nextCount[_i-1] || - _prevCount[_i]!=_nextCount[_i]) { - printf ("{%d %d %d} {%d %d %d}\n", + { + PixelList *_prevTest, *_nextTest; + int _i, _nextCount[3], _prevCount[3]; + for (_i = 0; _i < 3; _i++) { + for (_nextCount[_i] = 0, _nextTest = h[_i]; + _nextTest && _nextTest->next[_i]; + _nextTest = _nextTest->next[_i], _nextCount[_i]++) + ; + for (_prevCount[_i] = 0, _prevTest = t[_i]; + _prevTest && _prevTest->prev[_i]; + _prevTest = _prevTest->prev[_i], _prevCount[_i]++) + ; + if (_nextTest != t[_i]) { + printf("next-list of axis %d does not end at tail\n", _i); + exit(1); + } + if (_prevTest != h[_i]) { + printf("prev-list of axis %d does not end at head\n", _i); + exit(1); + } + for (; _nextTest && _nextTest->prev[_i]; _nextTest = _nextTest->prev[_i]) + ; + for (; _prevTest && _prevTest->next[_i]; _prevTest = _prevTest->next[_i]) + ; + if (_nextTest != h[_i]) { + printf("next-list of axis %d does not loop back to head\n", _i); + exit(1); + } + if (_prevTest != t[_i]) { + printf("prev-list of axis %d does not loop back to tail\n", _i); + exit(1); + } + } + for (_i = 1; _i < 3; _i++) { + if (_prevCount[_i] != _prevCount[_i - 1] || + _nextCount[_i] != _nextCount[_i - 1] || + _prevCount[_i] != _nextCount[_i]) { + printf( + "{%d %d %d} {%d %d %d}\n", _prevCount[0], _prevCount[1], _prevCount[2], _nextCount[0], _nextCount[1], _nextCount[2]); - exit(1); - } - } + exit(1); + } + } } #endif - nCount[0]=nCount[1]=0; - nLeft=nRight=0; - for (left=0,c=h[axis];c;) { - left=left+c->count; - nCount[0]+=c->count; - c->flag=0; - nLeft++; - c=c->next[axis]; - if (left*2>pixelCount) { - break; - } - } - if (c) { - splitColourVal=c->prev[axis]->p.a.v[axis]; - for (;c;c=c->next[axis]) { - if (splitColourVal!=c->p.a.v[axis]) { + nCount[0] = nCount[1] = 0; + nLeft = nRight = 0; + for (left = 0, c = h[axis]; c;) { + left = left + c->count; + nCount[0] += c->count; + c->flag = 0; + nLeft++; + c = c->next[axis]; + if (left * 2 > pixelCount) { break; - } - c->flag=0; - nLeft++; - nCount[0]+=c->count; - } - } - for (;c;c=c->next[axis]) { - c->flag=1; - nRight++; - nCount[1]+=c->count; - } - if (!nRight) { - for (c=t[axis],splitColourVal=t[axis]->p.a.v[axis];c;c=c->prev[axis]) { - if (splitColourVal!=c->p.a.v[axis]) { - break; - } - c->flag=1; - nRight++; - nLeft--; - nCount[0]-=c->count; - nCount[1]+=c->count; - } - } + } + } + if (c) { + splitColourVal = c->prev[axis]->p.a.v[axis]; + for (; c; c = c->next[axis]) { + if (splitColourVal != c->p.a.v[axis]) { + break; + } + c->flag = 0; + nLeft++; + nCount[0] += c->count; + } + } + for (; c; c = c->next[axis]) { + c->flag = 1; + nRight++; + nCount[1] += c->count; + } + if (!nRight) { + for (c = t[axis], splitColourVal = t[axis]->p.a.v[axis]; c; c = c->prev[axis]) { + if (splitColourVal != c->p.a.v[axis]) { + break; + } + c->flag = 1; + nRight++; + nLeft--; + nCount[0] -= c->count; + nCount[1] += c->count; + } + } #ifndef NO_OUTPUT - if (!nLeft) { - for (c=h[axis];c;c=c->next[axis]) { - printf ("[%d %d %d]\n",c->p.c.r,c->p.c.g,c->p.c.b); - } - printf ("warning... trivial split\n"); - } + if (!nLeft) { + for (c = h[axis]; c; c = c->next[axis]) { + printf("[%d %d %d]\n", c->p.c.r, c->p.c.g, c->p.c.b); + } + printf("warning... trivial split\n"); + } #endif - for (i=0;i<3;i++) { - l=r=NULL; - nh[0][i]=nt[0][i]=NULL; - nh[1][i]=nt[1][i]=NULL; - for (c=h[i];c;c=n) { - n=c->next[i]; - if (c->flag) { /* move pixel to right list*/ - if (r) r->next[i]=c; else nh[1][i]=c; - c->prev[i]=r; - r=c; - } else { /* move pixel to left list */ - if (l) l->next[i]=c; else nh[0][i]=c; - c->prev[i]=l; - l=c; - } - } - if (l) l->next[i]=NULL; - if (r) r->next[i]=NULL; - nt[0][i]=l; - nt[1][i]=r; - } - return 1; + for (i = 0; i < 3; i++) { + l = r = NULL; + nh[0][i] = nt[0][i] = NULL; + nh[1][i] = nt[1][i] = NULL; + for (c = h[i]; c; c = n) { + n = c->next[i]; + if (c->flag) { /* move pixel to right list*/ + if (r) { + r->next[i] = c; + } else { + nh[1][i] = c; + } + c->prev[i] = r; + r = c; + } else { /* move pixel to left list */ + if (l) { + l->next[i] = c; + } else { + nh[0][i] = c; + } + c->prev[i] = l; + l = c; + } + } + if (l) { + l->next[i] = NULL; + } + if (r) { + r->next[i] = NULL; + } + nt[0][i] = l; + nt[1][i] = r; + } + return 1; } static int -split(BoxNode *node) -{ - unsigned char rl,rh,gl,gh,bl,bh; - int f[3]; - int best,axis; - int i; - PixelList *heads[2][3]; - PixelList *tails[2][3]; - uint32_t newCounts[2]; - BoxNode *left,*right; +split(BoxNode *node) { + unsigned char rl, rh, gl, gh, bl, bh; + int f[3]; + int best, axis; + int i; + PixelList *heads[2][3]; + PixelList *tails[2][3]; + uint32_t newCounts[2]; + BoxNode *left, *right; - rh=node->head[0]->p.c.r; - rl=node->tail[0]->p.c.r; - gh=node->head[1]->p.c.g; - gl=node->tail[1]->p.c.g; - bh=node->head[2]->p.c.b; - bl=node->tail[2]->p.c.b; + rh = node->head[0]->p.c.r; + rl = node->tail[0]->p.c.r; + gh = node->head[1]->p.c.g; + gl = node->tail[1]->p.c.g; + bh = node->head[2]->p.c.b; + bl = node->tail[2]->p.c.b; #ifdef TEST_SPLIT - printf ("splitting node [%d %d %d] [%d %d %d] ",rl,gl,bl,rh,gh,bh); + printf("splitting node [%d %d %d] [%d %d %d] ", rl, gl, bl, rh, gh, bh); #endif - f[0]=(rh-rl)*77; - f[1]=(gh-gl)*150; - f[2]=(bh-bl)*29; + f[0] = (rh - rl) * 77; + f[1] = (gh - gl) * 150; + f[2] = (bh - bl) * 29; - best=f[0]; - axis=0; - for (i=1;i<3;i++) { - if (besttail[_i]->next[_i]) { - printf ("tail is not tail\n"); - printf ("node->tail[%d]->next[%d]=%p\n",_i,_i,node->tail[_i]->next[_i]); - } - if (node->head[_i]->prev[_i]) { - printf ("head is not head\n"); - printf ("node->head[%d]->prev[%d]=%p\n",_i,_i,node->head[_i]->prev[_i]); - } - } + { + PixelList *_prevTest, *_nextTest; + int _i, _nextCount[3], _prevCount[3]; + for (_i = 0; _i < 3; _i++) { + if (node->tail[_i]->next[_i]) { + printf("tail is not tail\n"); + printf( + "node->tail[%d]->next[%d]=%p\n", _i, _i, node->tail[_i]->next[_i]); + } + if (node->head[_i]->prev[_i]) { + printf("head is not head\n"); + printf( + "node->head[%d]->prev[%d]=%p\n", _i, _i, node->head[_i]->prev[_i]); + } + } - for (_i=0;_i<3;_i++) { - for (_nextCount[_i]=0,_nextTest=node->head[_i];_nextTest&&_nextTest->next[_i];_nextTest=_nextTest->next[_i],_nextCount[_i]++); - for (_prevCount[_i]=0,_prevTest=node->tail[_i];_prevTest&&_prevTest->prev[_i];_prevTest=_prevTest->prev[_i],_prevCount[_i]++); - if (_nextTest!=node->tail[_i]) { - printf ("next-list of axis %d does not end at tail\n",_i); - } - if (_prevTest!=node->head[_i]) { - printf ("prev-list of axis %d does not end at head\n",_i); - } - for (;_nextTest&&_nextTest->prev[_i];_nextTest=_nextTest->prev[_i]); - for (;_prevTest&&_prevTest->next[_i];_prevTest=_prevTest->next[_i]); - if (_nextTest!=node->head[_i]) { - printf ("next-list of axis %d does not loop back to head\n",_i); - } - if (_prevTest!=node->tail[_i]) { - printf ("prev-list of axis %d does not loop back to tail\n",_i); - } - } - for (_i=1;_i<3;_i++) { - if (_prevCount[_i]!=_prevCount[_i-1] || - _nextCount[_i]!=_nextCount[_i-1] || - _prevCount[_i]!=_nextCount[_i]) { - printf ("{%d %d %d} {%d %d %d}\n", + for (_i = 0; _i < 3; _i++) { + for (_nextCount[_i] = 0, _nextTest = node->head[_i]; + _nextTest && _nextTest->next[_i]; + _nextTest = _nextTest->next[_i], _nextCount[_i]++) + ; + for (_prevCount[_i] = 0, _prevTest = node->tail[_i]; + _prevTest && _prevTest->prev[_i]; + _prevTest = _prevTest->prev[_i], _prevCount[_i]++) + ; + if (_nextTest != node->tail[_i]) { + printf("next-list of axis %d does not end at tail\n", _i); + } + if (_prevTest != node->head[_i]) { + printf("prev-list of axis %d does not end at head\n", _i); + } + for (; _nextTest && _nextTest->prev[_i]; _nextTest = _nextTest->prev[_i]) + ; + for (; _prevTest && _prevTest->next[_i]; _prevTest = _prevTest->next[_i]) + ; + if (_nextTest != node->head[_i]) { + printf("next-list of axis %d does not loop back to head\n", _i); + } + if (_prevTest != node->tail[_i]) { + printf("prev-list of axis %d does not loop back to tail\n", _i); + } + } + for (_i = 1; _i < 3; _i++) { + if (_prevCount[_i] != _prevCount[_i - 1] || + _nextCount[_i] != _nextCount[_i - 1] || + _prevCount[_i] != _nextCount[_i]) { + printf( + "{%d %d %d} {%d %d %d}\n", _prevCount[0], _prevCount[1], _prevCount[2], _nextCount[0], _nextCount[1], _nextCount[2]); - } - } + } + } } #endif - node->axis=axis; - if (!splitlists(node->head, - node->tail, - heads, - tails, - newCounts, - axis, - node->pixelCount)) { + node->axis = axis; + if (!splitlists( + node->head, node->tail, heads, tails, newCounts, axis, node->pixelCount)) { #ifndef NO_OUTPUT - printf ("list split failed.\n"); + printf("list split failed.\n"); #endif - return 0; - } + return 0; + } #ifdef TEST_SPLIT - if (!test_sorted(heads[0])) { - printf ("bug in split"); - exit(1); - } - if (!test_sorted(heads[1])) { - printf ("bug in split"); - exit(1); - } + if (!test_sorted(heads[0])) { + printf("bug in split"); + exit(1); + } + if (!test_sorted(heads[1])) { + printf("bug in split"); + exit(1); + } #endif - /* malloc check ok, small constant allocation */ - left=malloc(sizeof(BoxNode)); - right=malloc(sizeof(BoxNode)); - if (!left||!right) { - free(left); - free(right); - return 0; - } - for(i=0;i<3;i++) { - left->head[i]=heads[0][i]; - left->tail[i]=tails[0][i]; - right->head[i]=heads[1][i]; - right->tail[i]=tails[1][i]; - node->head[i]=NULL; - node->tail[i]=NULL; - } + /* malloc check ok, small constant allocation */ + left = malloc(sizeof(BoxNode)); + right = malloc(sizeof(BoxNode)); + if (!left || !right) { + free(left); + free(right); + return 0; + } + for (i = 0; i < 3; i++) { + left->head[i] = heads[0][i]; + left->tail[i] = tails[0][i]; + right->head[i] = heads[1][i]; + right->tail[i] = tails[1][i]; + node->head[i] = NULL; + node->tail[i] = NULL; + } #ifdef TEST_SPLIT - if (left->head[0]) { - rh=left->head[0]->p.c.r; - rl=left->tail[0]->p.c.r; - gh=left->head[1]->p.c.g; - gl=left->tail[1]->p.c.g; - bh=left->head[2]->p.c.b; - bl=left->tail[2]->p.c.b; - printf (" left node [%3d %3d %3d] [%3d %3d %3d]\n",rl,gl,bl,rh,gh,bh); - } - if (right->head[0]) { - rh=right->head[0]->p.c.r; - rl=right->tail[0]->p.c.r; - gh=right->head[1]->p.c.g; - gl=right->tail[1]->p.c.g; - bh=right->head[2]->p.c.b; - bl=right->tail[2]->p.c.b; - printf (" right node [%3d %3d %3d] [%3d %3d %3d]\n",rl,gl,bl,rh,gh,bh); - } + if (left->head[0]) { + rh = left->head[0]->p.c.r; + rl = left->tail[0]->p.c.r; + gh = left->head[1]->p.c.g; + gl = left->tail[1]->p.c.g; + bh = left->head[2]->p.c.b; + bl = left->tail[2]->p.c.b; + printf(" left node [%3d %3d %3d] [%3d %3d %3d]\n", rl, gl, bl, rh, gh, bh); + } + if (right->head[0]) { + rh = right->head[0]->p.c.r; + rl = right->tail[0]->p.c.r; + gh = right->head[1]->p.c.g; + gl = right->tail[1]->p.c.g; + bh = right->head[2]->p.c.b; + bl = right->tail[2]->p.c.b; + printf(" right node [%3d %3d %3d] [%3d %3d %3d]\n", rl, gl, bl, rh, gh, bh); + } #endif - left->l=left->r=NULL; - right->l=right->r=NULL; - left->axis=right->axis=-1; - left->volume=right->volume=-1; - left->pixelCount=newCounts[0]; - right->pixelCount=newCounts[1]; - node->l=left; - node->r=right; - return 1; + left->l = left->r = NULL; + right->l = right->r = NULL; + left->axis = right->axis = -1; + left->volume = right->volume = -1; + left->pixelCount = newCounts[0]; + right->pixelCount = newCounts[1]; + node->l = left; + node->r = right; + return 1; } static BoxNode * -median_cut(PixelList *hl[3], - uint32_t imPixelCount, - int nPixels) -{ - PixelList *tl[3]; - int i; - BoxNode *root; - Heap* h; - BoxNode *thisNode; +median_cut(PixelList *hl[3], uint32_t imPixelCount, int nPixels) { + PixelList *tl[3]; + int i; + BoxNode *root; + Heap *h; + BoxNode *thisNode; - h=ImagingQuantHeapNew(box_heap_cmp); - /* malloc check ok, small constant allocation */ - root=malloc(sizeof(BoxNode)); - if (!root) { ImagingQuantHeapFree(h); return NULL; } - for(i=0;i<3;i++) { - for (tl[i]=hl[i];tl[i]&&tl[i]->next[i];tl[i]=tl[i]->next[i]); - root->head[i]=hl[i]; - root->tail[i]=tl[i]; - } - root->l=root->r=NULL; - root->axis=-1; - root->volume=-1; - root->pixelCount=imPixelCount; + h = ImagingQuantHeapNew(box_heap_cmp); + /* malloc check ok, small constant allocation */ + root = malloc(sizeof(BoxNode)); + if (!root) { + ImagingQuantHeapFree(h); + return NULL; + } + for (i = 0; i < 3; i++) { + for (tl[i] = hl[i]; tl[i] && tl[i]->next[i]; tl[i] = tl[i]->next[i]) + ; + root->head[i] = hl[i]; + root->tail[i] = tl[i]; + } + root->l = root->r = NULL; + root->axis = -1; + root->volume = -1; + root->pixelCount = imPixelCount; - ImagingQuantHeapAdd(h,(void *)root); - while (--nPixels) { - do { - if (!ImagingQuantHeapRemove(h,(void **)&thisNode)) { - goto done; - } - } while (compute_box_volume(thisNode)==1); - if (!split(thisNode)) { + ImagingQuantHeapAdd(h, (void *)root); + while (--nPixels) { + do { + if (!ImagingQuantHeapRemove(h, (void **)&thisNode)) { + goto done; + } + } while (compute_box_volume(thisNode) == 1); + if (!split(thisNode)) { #ifndef NO_OUTPUT - printf ("Oops, split failed...\n"); + printf("Oops, split failed...\n"); #endif - exit (1); - } - ImagingQuantHeapAdd(h,(void *)(thisNode->l)); - ImagingQuantHeapAdd(h,(void *)(thisNode->r)); - } + exit(1); + } + ImagingQuantHeapAdd(h, (void *)(thisNode->l)); + ImagingQuantHeapAdd(h, (void *)(thisNode->r)); + } done: - ImagingQuantHeapFree(h); - return root; + ImagingQuantHeapFree(h); + return root; } static void -free_box_tree(BoxNode *n) -{ - PixelList *p,*pp; - if (n->l) free_box_tree(n->l); - if (n->r) free_box_tree(n->r); - for (p=n->head[0];p;p=pp) { - pp=p->next[0]; - free(p); - } - free(n); +free_box_tree(BoxNode *n) { + PixelList *p, *pp; + if (n->l) { + free_box_tree(n->l); + } + if (n->r) { + free_box_tree(n->r); + } + for (p = n->head[0]; p; p = pp) { + pp = p->next[0]; + free(p); + } + free(n); } #ifdef TEST_SPLIT_INTEGRITY static int -checkContained(BoxNode *n,Pixel *pp) -{ - if (n->l&&n->r) { - return checkContained(n->l,pp)+checkContained(n->r,pp); - } - if (n->l||n->r) { +checkContained(BoxNode *n, Pixel *pp) { + if (n->l && n->r) { + return checkContained(n->l, pp) + checkContained(n->r, pp); + } + if (n->l || n->r) { #ifndef NO_OUTPUT - printf ("box tree is dead\n"); + printf("box tree is dead\n"); #endif - return 0; - } - if ( - pp->c.r<=n->head[0]->p.c.r && - pp->c.r>=n->tail[0]->p.c.r && - pp->c.g<=n->head[1]->p.c.g && - pp->c.g>=n->tail[1]->p.c.g && - pp->c.b<=n->head[2]->p.c.b && - pp->c.b>=n->tail[2]->p.c.b) { - return 1; - } - return 0; + return 0; + } + if (pp->c.r <= n->head[0]->p.c.r && pp->c.r >= n->tail[0]->p.c.r && + pp->c.g <= n->head[1]->p.c.g && pp->c.g >= n->tail[1]->p.c.g && + pp->c.b <= n->head[2]->p.c.b && pp->c.b >= n->tail[2]->p.c.b) { + return 1; + } + return 0; } #endif static int -annotate_hash_table(BoxNode *n,HashTable *h,uint32_t *box) -{ - PixelList *p; - PixelHashData *d=(PixelHashData *)hashtable_get_user_data(h); - Pixel q; - if (n->l&&n->r) { - return annotate_hash_table(n->l,h,box) && annotate_hash_table(n->r,h,box); - } - if (n->l||n->r) { +annotate_hash_table(BoxNode *n, HashTable *h, uint32_t *box) { + PixelList *p; + PixelHashData *d = (PixelHashData *)hashtable_get_user_data(h); + Pixel q; + if (n->l && n->r) { + return annotate_hash_table(n->l, h, box) && annotate_hash_table(n->r, h, box); + } + if (n->l || n->r) { #ifndef NO_OUTPUT - printf ("box tree is dead\n"); + printf("box tree is dead\n"); #endif - return 0; - } - for (p=n->head[0];p;p=p->next[0]) { - PIXEL_UNSCALE(&(p->p),&q,d->scale); - if (!hashtable_insert(h,q,*box)) { + return 0; + } + for (p = n->head[0]; p; p = p->next[0]) { + PIXEL_UNSCALE(&(p->p), &q, d->scale); + if (!hashtable_insert(h, q, *box)) { #ifndef NO_OUTPUT - printf ("hashtable insert failed\n"); + printf("hashtable insert failed\n"); #endif - return 0; - } - } - if (n->head[0]) (*box)++; - return 1; + return 0; + } + } + if (n->head[0]) { + (*box)++; + } + return 1; } static int -_sort_ulong_ptr_keys(const void *a, const void *b) -{ - uint32_t A=**(uint32_t **)a; - uint32_t B=**(uint32_t **)b; - return (A==B)?0:((A*(skRow[k]));k--) { - skRow[k]=skRow[k-1]; - } - if (k!=j) skRow[k]=skElt; - } - } - return 1; + for (i = 0; i < nEntries; i++) { + avgDist[i * nEntries + i] = 0; + for (j = 0; j < i; j++) { + avgDist[j * nEntries + i] = avgDist[i * nEntries + j] = + _DISTSQR(p + i, p + j); + } + } + for (i = 0; i < nEntries; i++) { + skRow = avgDistSortKey + i * nEntries; + for (j = 1; j < nEntries; j++) { + skElt = skRow[j]; + for (k = j; k && (*(skRow[k - 1]) > *(skRow[k])); k--) { + skRow[k] = skRow[k - 1]; + } + if (k != j) { + skRow[k] = skElt; + } + } + } + return 1; } static int -build_distance_tables(uint32_t *avgDist, - uint32_t **avgDistSortKey, - Pixel *p, - uint32_t nEntries) -{ - uint32_t i,j; +build_distance_tables( + uint32_t *avgDist, uint32_t **avgDistSortKey, Pixel *p, uint32_t nEntries) { + uint32_t i, j; - for (i=0;i1) { - printf ("pixel in two boxes\n"); - for(i=0;i<3;i++) free (avg[i]); - free(count); - return 0; - } + if (!(i % 100)) { + printf("%05d\r", i); + fflush(stdout); + } + if (checkContained(root, pixelData + i) > 1) { + printf("pixel in two boxes\n"); + for (i = 0; i < 3; i++) { + free(avg[i]); + } + free(count); + return 0; + } #endif - if (!hashtable_lookup(medianBoxHash,pixelData[i],&paletteEntry)) { + if (!hashtable_lookup(medianBoxHash, pixelData[i], &paletteEntry)) { #ifndef NO_OUTPUT - printf ("pixel lookup failed\n"); + printf("pixel lookup failed\n"); #endif - for(i=0;i<3;i++) free (avg[i]); - free(count); - return 0; - } - if (paletteEntry>=nPaletteEntries) { + for (i = 0; i < 3; i++) { + free(avg[i]); + } + free(count); + return 0; + } + if (paletteEntry >= nPaletteEntries) { #ifndef NO_OUTPUT - printf ("panic - paletteEntry>=nPaletteEntries (%d>=%d)\n",(int)paletteEntry,(int)nPaletteEntries); + printf( + "panic - paletteEntry>=nPaletteEntries (%d>=%d)\n", + (int)paletteEntry, + (int)nPaletteEntries); #endif - for(i=0;i<3;i++) free (avg[i]); - free(count); - return 0; - } - avg[0][paletteEntry]+=pixelData[i].c.r; - avg[1][paletteEntry]+=pixelData[i].c.g; - avg[2][paletteEntry]+=pixelData[i].c.b; - count[paletteEntry]++; - } - /* malloc check ok, using calloc */ - p=calloc(nPaletteEntries, sizeof(Pixel)); - if (!p) { - for(i=0;i<3;i++) free (avg[i]); - free(count); - return 0; - } - for (i=0;i=nPaletteEntries) { + memset(count, 0, sizeof(uint32_t) * nPaletteEntries); + for (i = 0; i < 3; i++) { + memset(avg[i], 0, sizeof(uint32_t) * nPaletteEntries); + } + for (i = 0; i < nPixels; i++) { + if (qp[i] >= nPaletteEntries) { #ifndef NO_OUTPUT - printf ("scream\n"); + printf("scream\n"); #endif - return 0; - } - avg[0][qp[i]]+=pixelData[i].c.r; - avg[1][qp[i]]+=pixelData[i].c.g; - avg[2][qp[i]]+=pixelData[i].c.b; - count[qp[i]]++; - } - for (i=0;i UINT32_MAX / (sizeof(uint32_t))) { - return 0; - } - /* malloc check ok, using calloc */ - if (!(count=calloc(nPaletteEntries, sizeof(uint32_t)))) { - return 0; - } - for(i=0;i<3;i++) { - avg[i]=NULL; - } - for(i=0;i<3;i++) { - /* malloc check ok, using calloc */ - if (!(avg[i]=calloc(nPaletteEntries, sizeof(uint32_t)))) { - goto error_1; - } - } + if (nPaletteEntries > UINT32_MAX / (sizeof(uint32_t))) { + return 0; + } + /* malloc check ok, using calloc */ + if (!(count = calloc(nPaletteEntries, sizeof(uint32_t)))) { + return 0; + } + for (i = 0; i < 3; i++) { + avg[i] = NULL; + } + for (i = 0; i < 3; i++) { + /* malloc check ok, using calloc */ + if (!(avg[i] = calloc(nPaletteEntries, sizeof(uint32_t)))) { + goto error_1; + } + } - /* this is enough of a check, since the multiplication n*size is done above */ - if (nPaletteEntries > UINT32_MAX / nPaletteEntries) { - goto error_1; - } - /* malloc check ok, using calloc, checking n*n above */ - avgDist=calloc(nPaletteEntries*nPaletteEntries, sizeof(uint32_t)); - if (!avgDist) { goto error_1; } + /* this is enough of a check, since the multiplication n*size is done above */ + if (nPaletteEntries > UINT32_MAX / nPaletteEntries) { + goto error_1; + } + /* malloc check ok, using calloc, checking n*n above */ + avgDist = calloc(nPaletteEntries * nPaletteEntries, sizeof(uint32_t)); + if (!avgDist) { + goto error_1; + } - /* malloc check ok, using calloc, checking n*n above */ - avgDistSortKey=calloc(nPaletteEntries*nPaletteEntries, sizeof(uint32_t *)); - if (!avgDistSortKey) { goto error_2; } + /* malloc check ok, using calloc, checking n*n above */ + avgDistSortKey = calloc(nPaletteEntries * nPaletteEntries, sizeof(uint32_t *)); + if (!avgDistSortKey) { + goto error_2; + } #ifndef NO_OUTPUT - printf("[");fflush(stdout); + printf("["); + fflush(stdout); #endif - while (1) { - if (!built) { - compute_palette_from_quantized_pixels(pixelData,nPixels,paletteData,nPaletteEntries,avg,count,qp); - build_distance_tables(avgDist,avgDistSortKey,paletteData,nPaletteEntries); - built=1; - } else { - recompute_palette_from_averages(paletteData,nPaletteEntries,avg,count); - resort_distance_tables(avgDist,avgDistSortKey,paletteData,nPaletteEntries); - } - changes=map_image_pixels_from_quantized_pixels(pixelData, - nPixels, - paletteData, - nPaletteEntries, - avgDist, - avgDistSortKey, - qp, - avg, - count); - if (changes<0) { - goto error_3; - } + while (1) { + if (!built) { + compute_palette_from_quantized_pixels( + pixelData, nPixels, paletteData, nPaletteEntries, avg, count, qp); + build_distance_tables( + avgDist, avgDistSortKey, paletteData, nPaletteEntries); + built = 1; + } else { + recompute_palette_from_averages(paletteData, nPaletteEntries, avg, count); + resort_distance_tables( + avgDist, avgDistSortKey, paletteData, nPaletteEntries); + } + changes = map_image_pixels_from_quantized_pixels( + pixelData, + nPixels, + paletteData, + nPaletteEntries, + avgDist, + avgDistSortKey, + qp, + avg, + count); + if (changes < 0) { + goto error_3; + } #ifndef NO_OUTPUT - printf (".(%d)",changes);fflush(stdout); + printf(".(%d)", changes); + fflush(stdout); #endif - if (changes<=threshold) break; - } + if (changes <= threshold) { + break; + } + } #ifndef NO_OUTPUT - printf("]\n"); + printf("]\n"); #endif - if (avgDistSortKey) free(avgDistSortKey); - if (avgDist) free(avgDist); - for(i=0;i<3;i++) if (avg[i]) free (avg[i]); - if (count) free(count); - return 1; + if (avgDistSortKey) { + free(avgDistSortKey); + } + if (avgDist) { + free(avgDist); + } + for (i = 0; i < 3; i++) { + if (avg[i]) { + free(avg[i]); + } + } + if (count) { + free(count); + } + return 1; error_3: - if (avgDistSortKey) free(avgDistSortKey); + if (avgDistSortKey) { + free(avgDistSortKey); + } error_2: - if (avgDist) free(avgDist); + if (avgDist) { + free(avgDist); + } error_1: - for(i=0;i<3;i++) if (avg[i]) free (avg[i]); - if (count) free(count); - return 0; + for (i = 0; i < 3; i++) { + if (avg[i]) { + free(avg[i]); + } + } + if (count) { + free(count); + } + return 0; } int -quantize(Pixel *pixelData, - uint32_t nPixels, - uint32_t nQuantPixels, - Pixel **palette, - uint32_t *paletteLength, - uint32_t **quantizedPixels, - int kmeans) -{ - PixelList *hl[3]; - HashTable *h; - BoxNode *root; - uint32_t i; - uint32_t *qp; - uint32_t nPaletteEntries; +quantize( + Pixel *pixelData, + uint32_t nPixels, + uint32_t nQuantPixels, + Pixel **palette, + uint32_t *paletteLength, + uint32_t **quantizedPixels, + int kmeans) { + PixelList *hl[3]; + HashTable *h; + BoxNode *root; + uint32_t i; + uint32_t *qp; + uint32_t nPaletteEntries; - uint32_t *avgDist; - uint32_t **avgDistSortKey; - Pixel *p; + uint32_t *avgDist; + uint32_t **avgDistSortKey; + Pixel *p; #ifndef NO_OUTPUT - uint32_t timer,timer2; + uint32_t timer, timer2; #endif #ifndef NO_OUTPUT - timer2=clock(); - printf ("create hash table..."); fflush(stdout); timer=clock(); + timer2 = clock(); + printf("create hash table..."); + fflush(stdout); + timer = clock(); #endif - h=create_pixel_hash(pixelData,nPixels); + h = create_pixel_hash(pixelData, nPixels); #ifndef NO_OUTPUT - printf ("done (%f)\n",(clock()-timer)/(double)CLOCKS_PER_SEC); + printf("done (%f)\n", (clock() - timer) / (double)CLOCKS_PER_SEC); #endif - if (!h) { - goto error_0; - } + if (!h) { + goto error_0; + } #ifndef NO_OUTPUT - printf ("create lists from hash table..."); fflush(stdout); timer=clock(); + printf("create lists from hash table..."); + fflush(stdout); + timer = clock(); #endif - hl[0]=hl[1]=hl[2]=NULL; - hashtable_foreach(h,hash_to_list,hl); + hl[0] = hl[1] = hl[2] = NULL; + hashtable_foreach(h, hash_to_list, hl); #ifndef NO_OUTPUT - printf ("done (%f)\n",(clock()-timer)/(double)CLOCKS_PER_SEC); + printf("done (%f)\n", (clock() - timer) / (double)CLOCKS_PER_SEC); #endif - if (!hl[0]) { - goto error_1; - } + if (!hl[0]) { + goto error_1; + } #ifndef NO_OUTPUT - printf ("mergesort lists..."); fflush(stdout); timer=clock(); + printf("mergesort lists..."); + fflush(stdout); + timer = clock(); #endif - for(i=0;i<3;i++) { - hl[i]=mergesort_pixels(hl[i],i); - } + for (i = 0; i < 3; i++) { + hl[i] = mergesort_pixels(hl[i], i); + } #ifdef TEST_MERGESORT - if (!test_sorted(hl)) { - printf ("bug in mergesort\n"); - goto error_1; - } + if (!test_sorted(hl)) { + printf("bug in mergesort\n"); + goto error_1; + } #endif #ifndef NO_OUTPUT - printf ("done (%f)\n",(clock()-timer)/(double)CLOCKS_PER_SEC); + printf("done (%f)\n", (clock() - timer) / (double)CLOCKS_PER_SEC); #endif #ifndef NO_OUTPUT - printf ("median cut..."); fflush(stdout); timer=clock(); + printf("median cut..."); + fflush(stdout); + timer = clock(); #endif - root=median_cut(hl,nPixels,nQuantPixels); + root = median_cut(hl, nPixels, nQuantPixels); #ifndef NO_OUTPUT - printf ("done (%f)\n",(clock()-timer)/(double)CLOCKS_PER_SEC); + printf("done (%f)\n", (clock() - timer) / (double)CLOCKS_PER_SEC); #endif - if (!root) { - goto error_1; - } - nPaletteEntries=0; + if (!root) { + goto error_1; + } + nPaletteEntries = 0; #ifndef NO_OUTPUT - printf ("median cut tree to hash table..."); fflush(stdout); timer=clock(); + printf("median cut tree to hash table..."); + fflush(stdout); + timer = clock(); #endif - annotate_hash_table(root,h,&nPaletteEntries); + annotate_hash_table(root, h, &nPaletteEntries); #ifndef NO_OUTPUT - printf ("done (%f)\n",(clock()-timer)/(double)CLOCKS_PER_SEC); + printf("done (%f)\n", (clock() - timer) / (double)CLOCKS_PER_SEC); #endif #ifndef NO_OUTPUT - printf ("compute palette...\n"); fflush(stdout); timer=clock(); + printf("compute palette...\n"); + fflush(stdout); + timer = clock(); #endif - if (!compute_palette_from_median_cut(pixelData,nPixels,h,&p,nPaletteEntries)) { - goto error_3; - } + if (!compute_palette_from_median_cut(pixelData, nPixels, h, &p, nPaletteEntries)) { + goto error_3; + } #ifndef NO_OUTPUT - printf ("done (%f)\n",(clock()-timer)/(double)CLOCKS_PER_SEC); + printf("done (%f)\n", (clock() - timer) / (double)CLOCKS_PER_SEC); #endif - free_box_tree(root); - root=NULL; + free_box_tree(root); + root = NULL; - /* malloc check ok, using calloc for overflow */ - qp=calloc(nPixels, sizeof(uint32_t)); - if (!qp) { goto error_4; } + /* malloc check ok, using calloc for overflow */ + qp = calloc(nPixels, sizeof(uint32_t)); + if (!qp) { + goto error_4; + } - if (nPaletteEntries > UINT32_MAX / nPaletteEntries ) { - goto error_5; - } - /* malloc check ok, using calloc for overflow, check of n*n above */ - avgDist=calloc(nPaletteEntries*nPaletteEntries, sizeof(uint32_t)); - if (!avgDist) { goto error_5; } + if (nPaletteEntries > UINT32_MAX / nPaletteEntries) { + goto error_5; + } + /* malloc check ok, using calloc for overflow, check of n*n above */ + avgDist = calloc(nPaletteEntries * nPaletteEntries, sizeof(uint32_t)); + if (!avgDist) { + goto error_5; + } - /* malloc check ok, using calloc for overflow, check of n*n above */ - avgDistSortKey=calloc(nPaletteEntries*nPaletteEntries, sizeof(uint32_t *)); - if (!avgDistSortKey) { goto error_6; } + /* malloc check ok, using calloc for overflow, check of n*n above */ + avgDistSortKey = calloc(nPaletteEntries * nPaletteEntries, sizeof(uint32_t *)); + if (!avgDistSortKey) { + goto error_6; + } - if (!build_distance_tables(avgDist,avgDistSortKey,p,nPaletteEntries)) { - goto error_7; - } + if (!build_distance_tables(avgDist, avgDistSortKey, p, nPaletteEntries)) { + goto error_7; + } - if (!map_image_pixels_from_median_box(pixelData,nPixels,p,nPaletteEntries,h,avgDist,avgDistSortKey,qp)) { - goto error_7; - } + if (!map_image_pixels_from_median_box( + pixelData, nPixels, p, nPaletteEntries, h, avgDist, avgDistSortKey, qp)) { + goto error_7; + } #ifdef TEST_NEAREST_NEIGHBOUR #include - { - uint32_t bestmatch,bestdist,dist; - HashTable *h2; - printf ("nearest neighbour search (full search)..."); fflush(stdout); timer=clock(); - h2=hashtable_new(unshifted_pixel_hash,unshifted_pixel_cmp); - for (i=0;inew),&pixel); - if (data->secondPixel || newDistdata->furthestDistance) { - data->furthestDistance=oldDist; - data->furthest.v=pixel.v; - } +compute_distances(const HashTable *h, const Pixel pixel, uint32_t *dist, void *u) { + DistanceData *data = (DistanceData *)u; + uint32_t oldDist = *dist; + uint32_t newDist; + newDist = _DISTSQR(&(data->new), &pixel); + if (data->secondPixel || newDist < oldDist) { + *dist = newDist; + oldDist = newDist; + } + if (oldDist > data->furthestDistance) { + data->furthestDistance = oldDist; + data->furthest.v = pixel.v; + } } int -quantize2(Pixel *pixelData, - uint32_t nPixels, - uint32_t nQuantPixels, - Pixel **palette, - uint32_t *paletteLength, - uint32_t **quantizedPixels, - int kmeans) -{ - HashTable *h; - uint32_t i; - uint32_t mean[3]; - Pixel *p; - DistanceData data; +quantize2( + Pixel *pixelData, + uint32_t nPixels, + uint32_t nQuantPixels, + Pixel **palette, + uint32_t *paletteLength, + uint32_t **quantizedPixels, + int kmeans) { + HashTable *h; + uint32_t i; + uint32_t mean[3]; + Pixel *p; + DistanceData data; - uint32_t *qp; - uint32_t *avgDist; - uint32_t **avgDistSortKey; + uint32_t *qp; + uint32_t *avgDist; + uint32_t **avgDistSortKey; - /* malloc check ok, using calloc */ - p=calloc(nQuantPixels, sizeof(Pixel)); - if (!p) return 0; - mean[0]=mean[1]=mean[2]=0; - h=hashtable_new(unshifted_pixel_hash,unshifted_pixel_cmp); - for (i=0;i UINT32_MAX / nQuantPixels ) { - goto error_2; - } + if (nQuantPixels > UINT32_MAX / nQuantPixels) { + goto error_2; + } - /* malloc check ok, using calloc for overflow, check of n*n above */ - avgDist=calloc(nQuantPixels*nQuantPixels, sizeof(uint32_t)); - if (!avgDist) { goto error_2; } + /* malloc check ok, using calloc for overflow, check of n*n above */ + avgDist = calloc(nQuantPixels * nQuantPixels, sizeof(uint32_t)); + if (!avgDist) { + goto error_2; + } - /* malloc check ok, using calloc for overflow, check of n*n above */ - avgDistSortKey=calloc(nQuantPixels*nQuantPixels, sizeof(uint32_t *)); - if (!avgDistSortKey) { goto error_3; } + /* malloc check ok, using calloc for overflow, check of n*n above */ + avgDistSortKey = calloc(nQuantPixels * nQuantPixels, sizeof(uint32_t *)); + if (!avgDistSortKey) { + goto error_3; + } - if (!build_distance_tables(avgDist,avgDistSortKey,p,nQuantPixels)) { - goto error_4; - } + if (!build_distance_tables(avgDist, avgDistSortKey, p, nQuantPixels)) { + goto error_4; + } - if (!map_image_pixels(pixelData,nPixels,p,nQuantPixels,avgDist,avgDistSortKey,qp)) { - goto error_4; - } - if (kmeans) k_means(pixelData,nPixels,p,nQuantPixels,qp,kmeans-1); + if (!map_image_pixels( + pixelData, nPixels, p, nQuantPixels, avgDist, avgDistSortKey, qp)) { + goto error_4; + } + if (kmeans) { + k_means(pixelData, nPixels, p, nQuantPixels, qp, kmeans - 1); + } - *paletteLength=nQuantPixels; - *palette=p; - *quantizedPixels=qp; - free(avgDistSortKey); - free(avgDist); - return 1; + *paletteLength = nQuantPixels; + *palette = p; + *quantizedPixels = qp; + free(avgDistSortKey); + free(avgDist); + return 1; error_4: - free(avgDistSortKey); + free(avgDistSortKey); error_3: - free(avgDist); + free(avgDist); error_2: - free(qp); + free(qp); error_1: - free(p); - return 0; + free(p); + return 0; } Imaging -ImagingQuantize(Imaging im, int colors, int mode, int kmeans) -{ +ImagingQuantize(Imaging im, int colors, int mode, int kmeans) { int i, j; int x, y, v; - UINT8* pp; - Pixel* p; - Pixel* palette; + UINT8 *pp; + Pixel *p; + Pixel *palette; uint32_t paletteLength; int result; - uint32_t* newData; + uint32_t *newData; Imaging imOut; int withAlpha = 0; ImagingSectionCookie cookie; - if (!im) + if (!im) { return ImagingError_ModeError(); - if (colors < 1 || colors > 256) + } + if (colors < 1 || colors > 256) { /* FIXME: for colors > 256, consider returning an RGB image instead (see @PIL205) */ - return (Imaging) ImagingError_ValueError("bad number of colors"); + return (Imaging)ImagingError_ValueError("bad number of colors"); + } if (strcmp(im->mode, "L") != 0 && strcmp(im->mode, "P") != 0 && - strcmp(im->mode, "RGB") != 0 && strcmp(im->mode, "RGBA") !=0) + strcmp(im->mode, "RGB") != 0 && strcmp(im->mode, "RGBA") != 0) { return ImagingError_ModeError(); + } /* only octree and imagequant supports RGBA */ - if (!strcmp(im->mode, "RGBA") && mode != 2 && mode != 3) - return ImagingError_ModeError(); + if (!strcmp(im->mode, "RGBA") && mode != 2 && mode != 3) { + return ImagingError_ModeError(); + } if (im->xsize > INT_MAX / im->ysize) { return ImagingError_MemoryError(); } /* malloc check ok, using calloc for final overflow, x*y above */ p = calloc(im->xsize * im->ysize, sizeof(Pixel)); - if (!p) + if (!p) { return ImagingError_MemoryError(); + } /* collect statistics */ @@ -1543,101 +1663,101 @@ ImagingQuantize(Imaging im, int colors, int mode, int kmeans) /* FIXME: converting a "L" image to "P" with 256 colors should be done by a simple copy... */ - for (i = y = 0; y < im->ysize; y++) + for (i = y = 0; y < im->ysize; y++) { for (x = 0; x < im->xsize; x++, i++) { p[i].c.r = p[i].c.g = p[i].c.b = im->image8[y][x]; p[i].c.a = 255; } + } } else if (!strcmp(im->mode, "P")) { /* palette */ pp = im->palette->palette; - for (i = y = 0; y < im->ysize; y++) + for (i = y = 0; y < im->ysize; y++) { for (x = 0; x < im->xsize; x++, i++) { v = im->image8[y][x]; - p[i].c.r = pp[v*4+0]; - p[i].c.g = pp[v*4+1]; - p[i].c.b = pp[v*4+2]; - p[i].c.a = pp[v*4+3]; + p[i].c.r = pp[v * 4 + 0]; + p[i].c.g = pp[v * 4 + 1]; + p[i].c.b = pp[v * 4 + 2]; + p[i].c.a = pp[v * 4 + 3]; } + } } else if (!strcmp(im->mode, "RGB") || !strcmp(im->mode, "RGBA")) { /* true colour */ - for (i = y = 0; y < im->ysize; y++) - for (x = 0; x < im->xsize; x++, i++) + for (i = y = 0; y < im->ysize; y++) { + for (x = 0; x < im->xsize; x++, i++) { p[i].v = im->image32[y][x]; + } + } } else { free(p); - return (Imaging) ImagingError_ValueError("internal error"); + return (Imaging)ImagingError_ValueError("internal error"); } ImagingSectionEnter(&cookie); switch (mode) { - case 0: - /* median cut */ - result = quantize( - p, - im->xsize*im->ysize, - colors, - &palette, - &paletteLength, - &newData, - kmeans - ); - break; - case 1: - /* maximum coverage */ - result = quantize2( - p, - im->xsize*im->ysize, - colors, - &palette, - &paletteLength, - &newData, - kmeans - ); - break; - case 2: - if (!strcmp(im->mode, "RGBA")) { - withAlpha = 1; - } - result = quantize_octree( - p, - im->xsize*im->ysize, - colors, - &palette, - &paletteLength, - &newData, - withAlpha - ); - break; - case 3: + case 0: + /* median cut */ + result = quantize( + p, + im->xsize * im->ysize, + colors, + &palette, + &paletteLength, + &newData, + kmeans); + break; + case 1: + /* maximum coverage */ + result = quantize2( + p, + im->xsize * im->ysize, + colors, + &palette, + &paletteLength, + &newData, + kmeans); + break; + case 2: + if (!strcmp(im->mode, "RGBA")) { + withAlpha = 1; + } + result = quantize_octree( + p, + im->xsize * im->ysize, + colors, + &palette, + &paletteLength, + &newData, + withAlpha); + break; + case 3: #ifdef HAVE_LIBIMAGEQUANT - if (!strcmp(im->mode, "RGBA")) { - withAlpha = 1; - } - result = quantize_pngquant( - p, - im->xsize, - im->ysize, - colors, - &palette, - &paletteLength, - &newData, - withAlpha - ); + if (!strcmp(im->mode, "RGBA")) { + withAlpha = 1; + } + result = quantize_pngquant( + p, + im->xsize, + im->ysize, + colors, + &palette, + &paletteLength, + &newData, + withAlpha); #else - result = -1; + result = -1; #endif - break; - default: - result = 0; - break; + break; + default: + result = 0; + break; } free(p); @@ -1647,22 +1767,24 @@ ImagingQuantize(Imaging im, int colors, int mode, int kmeans) imOut = ImagingNewDirty("P", im->xsize, im->ysize); ImagingSectionEnter(&cookie); - for (i = y = 0; y < im->ysize; y++) - for (x = 0; x < im->xsize; x++) - imOut->image8[y][x] = (unsigned char) newData[i++]; + for (i = y = 0; y < im->ysize; y++) { + for (x = 0; x < im->xsize; x++) { + imOut->image8[y][x] = (unsigned char)newData[i++]; + } + } free(newData); pp = imOut->palette->palette; - for (i = j = 0; i < (int) paletteLength; i++) { + for (i = j = 0; i < (int)paletteLength; i++) { *pp++ = palette[i].c.r; *pp++ = palette[i].c.g; *pp++ = palette[i].c.b; if (withAlpha) { - *pp++ = palette[i].c.a; + *pp++ = palette[i].c.a; } else { - *pp++ = 255; + *pp++ = 255; } } for (; i < 256; i++) { @@ -1682,14 +1804,12 @@ ImagingQuantize(Imaging im, int colors, int mode, int kmeans) return imOut; } else { - if (result == -1) { - return (Imaging) ImagingError_ValueError( + return (Imaging)ImagingError_ValueError( "dependency required by this method was not " "enabled at compile time"); } - return (Imaging) ImagingError_ValueError("quantization error"); - + return (Imaging)ImagingError_ValueError("quantization error"); } } diff --git a/src/libImaging/QuantHash.c b/src/libImaging/QuantHash.c index 3fcbf3c02..ea75d6037 100644 --- a/src/libImaging/QuantHash.c +++ b/src/libImaging/QuantHash.c @@ -24,280 +24,313 @@ #include "QuantHash.h" typedef struct _HashNode { - struct _HashNode *next; - HashKey_t key; - HashVal_t value; + struct _HashNode *next; + HashKey_t key; + HashVal_t value; } HashNode; struct _HashTable { - HashNode **table; - uint32_t length; - uint32_t count; - HashFunc hashFunc; - HashCmpFunc cmpFunc; - void *userData; + HashNode **table; + uint32_t length; + uint32_t count; + HashFunc hashFunc; + HashCmpFunc cmpFunc; + void *userData; }; #define MIN_LENGTH 11 #define RESIZE_FACTOR 3 -static int _hashtable_insert_node(HashTable *,HashNode *,int,int,CollisionFunc); +static int +_hashtable_insert_node(HashTable *, HashNode *, int, int, CollisionFunc); -HashTable *hashtable_new(HashFunc hf,HashCmpFunc cf) { - HashTable *h; - h=malloc(sizeof(HashTable)); - if (!h) { return NULL; } - h->hashFunc=hf; - h->cmpFunc=cf; - h->length=MIN_LENGTH; - h->count=0; - h->userData=NULL; - h->table=malloc(sizeof(HashNode *)*h->length); - if (!h->table) { free(h); return NULL; } - memset (h->table,0,sizeof(HashNode *)*h->length); - return h; +HashTable * +hashtable_new(HashFunc hf, HashCmpFunc cf) { + HashTable *h; + h = malloc(sizeof(HashTable)); + if (!h) { + return NULL; + } + h->hashFunc = hf; + h->cmpFunc = cf; + h->length = MIN_LENGTH; + h->count = 0; + h->userData = NULL; + h->table = malloc(sizeof(HashNode *) * h->length); + if (!h->table) { + free(h); + return NULL; + } + memset(h->table, 0, sizeof(HashNode *) * h->length); + return h; } -static uint32_t _findPrime(uint32_t start,int dir) { - static int unit[]={0,1,0,1,0,0,0,1,0,1,0,1,0,1,0,0}; - uint32_t t; - while (start>1) { - if (!unit[start&0x0f]) { - start+=dir; - continue; - } - for (t=2;t=sqrt((double)start)) { - break; - } - start+=dir; - } - return start; +static uint32_t +_findPrime(uint32_t start, int dir) { + static int unit[] = {0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0}; + uint32_t t; + while (start > 1) { + if (!unit[start & 0x0f]) { + start += dir; + continue; + } + for (t = 2; t < sqrt((double)start); t++) { + if (!start % t) { + break; + } + } + if (t >= sqrt((double)start)) { + break; + } + start += dir; + } + return start; } -static void _hashtable_rehash(HashTable *h,CollisionFunc cf,uint32_t newSize) { - HashNode **oldTable=h->table; - uint32_t i; - HashNode *n,*nn; - uint32_t oldSize; - oldSize=h->length; - h->table=malloc(sizeof(HashNode *)*newSize); - if (!h->table) { - h->table=oldTable; - return; - } - h->length=newSize; - h->count=0; - memset (h->table,0,sizeof(HashNode *)*h->length); - for (i=0;inext; - _hashtable_insert_node(h,n,0,0,cf); - } - } - free(oldTable); +static void +_hashtable_rehash(HashTable *h, CollisionFunc cf, uint32_t newSize) { + HashNode **oldTable = h->table; + uint32_t i; + HashNode *n, *nn; + uint32_t oldSize; + oldSize = h->length; + h->table = malloc(sizeof(HashNode *) * newSize); + if (!h->table) { + h->table = oldTable; + return; + } + h->length = newSize; + h->count = 0; + memset(h->table, 0, sizeof(HashNode *) * h->length); + for (i = 0; i < oldSize; i++) { + for (n = oldTable[i]; n; n = nn) { + nn = n->next; + _hashtable_insert_node(h, n, 0, 0, cf); + } + } + free(oldTable); } -static void _hashtable_resize(HashTable *h) { - uint32_t newSize; - uint32_t oldSize; - oldSize=h->length; - newSize=oldSize; - if (h->count*RESIZE_FACTORlength) { - newSize=_findPrime(h->length/2-1,-1); - } else if (h->length*RESIZE_FACTORcount) { - newSize=_findPrime(h->length*2+1,+1); - } - if (newSizelength; + newSize = oldSize; + if (h->count * RESIZE_FACTOR < h->length) { + newSize = _findPrime(h->length / 2 - 1, -1); + } else if (h->length * RESIZE_FACTOR < h->count) { + newSize = _findPrime(h->length * 2 + 1, +1); + } + if (newSize < MIN_LENGTH) { + newSize = oldSize; + } + if (newSize != oldSize) { + _hashtable_rehash(h, NULL, newSize); + } } -static int _hashtable_insert_node(HashTable *h,HashNode *node,int resize,int update,CollisionFunc cf) { - uint32_t hash=h->hashFunc(h,node->key)%h->length; - HashNode **n,*nv; - int i; +static int +_hashtable_insert_node( + HashTable *h, HashNode *node, int resize, int update, CollisionFunc cf) { + uint32_t hash = h->hashFunc(h, node->key) % h->length; + HashNode **n, *nv; + int i; - for (n=&(h->table[hash]);*n;n=&((*n)->next)) { - nv=*n; - i=h->cmpFunc(h,nv->key,node->key); - if (!i) { - if (cf) { - nv->key=node->key; - cf(h,&(nv->key),&(nv->value),node->key,node->value); - free(node); + for (n = &(h->table[hash]); *n; n = &((*n)->next)) { + nv = *n; + i = h->cmpFunc(h, nv->key, node->key); + if (!i) { + if (cf) { + nv->key = node->key; + cf(h, &(nv->key), &(nv->value), node->key, node->value); + free(node); + return 1; + } else { + nv->key = node->key; + nv->value = node->value; + free(node); + return 1; + } + } else if (i > 0) { + break; + } + } + if (!update) { + node->next = *n; + *n = node; + h->count++; + if (resize) { + _hashtable_resize(h); + } + return 1; + } else { + return 0; + } +} + +static int +_hashtable_insert(HashTable *h, HashKey_t key, HashVal_t val, int resize, int update) { + HashNode **n, *nv; + HashNode *t; + int i; + uint32_t hash = h->hashFunc(h, key) % h->length; + + for (n = &(h->table[hash]); *n; n = &((*n)->next)) { + nv = *n; + i = h->cmpFunc(h, nv->key, key); + if (!i) { + nv->value = val; return 1; - } else { - nv->key=node->key; - nv->value=node->value; - free(node); - return 1; - } - } else if (i>0) { - break; - } - } - if (!update) { - node->next=*n; - *n=node; - h->count++; - if (resize) _hashtable_resize(h); - return 1; - } else { - return 0; - } -} - -static int _hashtable_insert(HashTable *h,HashKey_t key,HashVal_t val,int resize,int update) { - HashNode **n,*nv; - HashNode *t; - int i; - uint32_t hash=h->hashFunc(h,key)%h->length; - - for (n=&(h->table[hash]);*n;n=&((*n)->next)) { - nv=*n; - i=h->cmpFunc(h,nv->key,key); - if (!i) { - nv->value=val; - return 1; - } else if (i>0) { - break; - } - } - if (!update) { - t=malloc(sizeof(HashNode)); - if (!t) return 0; - t->next=*n; - *n=t; - t->key=key; - t->value=val; - h->count++; - if (resize) _hashtable_resize(h); - return 1; - } else { - return 0; - } -} - -int hashtable_insert_or_update_computed(HashTable *h, - HashKey_t key, - ComputeFunc newFunc, - ComputeFunc existsFunc) { - HashNode **n,*nv; - HashNode *t; - int i; - uint32_t hash=h->hashFunc(h,key)%h->length; - - for (n=&(h->table[hash]);*n;n=&((*n)->next)) { - nv=*n; - i=h->cmpFunc(h,nv->key,key); - if (!i) { - if (existsFunc) { - existsFunc(h,nv->key,&(nv->value)); - } else { + } else if (i > 0) { + break; + } + } + if (!update) { + t = malloc(sizeof(HashNode)); + if (!t) { return 0; - } - return 1; - } else if (i>0) { - break; - } - } - t=malloc(sizeof(HashNode)); - if (!t) return 0; - t->key=key; - t->next=*n; - *n=t; - if (newFunc) { - newFunc(h,t->key,&(t->value)); - } else { - free(t); - return 0; - } - h->count++; - _hashtable_resize(h); - return 1; + } + t->next = *n; + *n = t; + t->key = key; + t->value = val; + h->count++; + if (resize) { + _hashtable_resize(h); + } + return 1; + } else { + return 0; + } } -int hashtable_insert(HashTable *h,HashKey_t key,HashVal_t val) { - return _hashtable_insert(h,key,val,1,0); +int +hashtable_insert_or_update_computed( + HashTable *h, HashKey_t key, ComputeFunc newFunc, ComputeFunc existsFunc) { + HashNode **n, *nv; + HashNode *t; + int i; + uint32_t hash = h->hashFunc(h, key) % h->length; + + for (n = &(h->table[hash]); *n; n = &((*n)->next)) { + nv = *n; + i = h->cmpFunc(h, nv->key, key); + if (!i) { + if (existsFunc) { + existsFunc(h, nv->key, &(nv->value)); + } else { + return 0; + } + return 1; + } else if (i > 0) { + break; + } + } + t = malloc(sizeof(HashNode)); + if (!t) { + return 0; + } + t->key = key; + t->next = *n; + *n = t; + if (newFunc) { + newFunc(h, t->key, &(t->value)); + } else { + free(t); + return 0; + } + h->count++; + _hashtable_resize(h); + return 1; } -void hashtable_foreach_update(HashTable *h,IteratorUpdateFunc i,void *u) { - HashNode *n; - uint32_t x; - - if (h->table) { - for (x=0;xlength;x++) { - for (n=h->table[x];n;n=n->next) { - i(h,n->key,&(n->value),u); - } - } - } +int +hashtable_insert(HashTable *h, HashKey_t key, HashVal_t val) { + return _hashtable_insert(h, key, val, 1, 0); } -void hashtable_foreach(HashTable *h,IteratorFunc i,void *u) { - HashNode *n; - uint32_t x; +void +hashtable_foreach_update(HashTable *h, IteratorUpdateFunc i, void *u) { + HashNode *n; + uint32_t x; - if (h->table) { - for (x=0;xlength;x++) { - for (n=h->table[x];n;n=n->next) { - i(h,n->key,n->value,u); - } - } - } + if (h->table) { + for (x = 0; x < h->length; x++) { + for (n = h->table[x]; n; n = n->next) { + i(h, n->key, &(n->value), u); + } + } + } } -void hashtable_free(HashTable *h) { - HashNode *n,*nn; - uint32_t i; +void +hashtable_foreach(HashTable *h, IteratorFunc i, void *u) { + HashNode *n; + uint32_t x; - if (h->table) { - for (i=0;ilength;i++) { - for (n=h->table[i];n;n=nn) { - nn=n->next; - free(n); - } - } - free(h->table); - } - free(h); + if (h->table) { + for (x = 0; x < h->length; x++) { + for (n = h->table[x]; n; n = n->next) { + i(h, n->key, n->value, u); + } + } + } } -void hashtable_rehash_compute(HashTable *h,CollisionFunc cf) { - _hashtable_rehash(h,cf,h->length); +void +hashtable_free(HashTable *h) { + HashNode *n, *nn; + uint32_t i; + + if (h->table) { + for (i = 0; i < h->length; i++) { + for (n = h->table[i]; n; n = nn) { + nn = n->next; + free(n); + } + } + free(h->table); + } + free(h); } -int hashtable_lookup(const HashTable *h,const HashKey_t key,HashVal_t *valp) { - uint32_t hash=h->hashFunc(h,key)%h->length; - HashNode *n; - int i; - - for (n=h->table[hash];n;n=n->next) { - i=h->cmpFunc(h,n->key,key); - if (!i) { - *valp=n->value; - return 1; - } else if (i>0) { - break; - } - } - return 0; +void +hashtable_rehash_compute(HashTable *h, CollisionFunc cf) { + _hashtable_rehash(h, cf, h->length); } -uint32_t hashtable_get_count(const HashTable *h) { - return h->count; +int +hashtable_lookup(const HashTable *h, const HashKey_t key, HashVal_t *valp) { + uint32_t hash = h->hashFunc(h, key) % h->length; + HashNode *n; + int i; + + for (n = h->table[hash]; n; n = n->next) { + i = h->cmpFunc(h, n->key, key); + if (!i) { + *valp = n->value; + return 1; + } else if (i > 0) { + break; + } + } + return 0; } -void *hashtable_get_user_data(const HashTable *h) { - return h->userData; +uint32_t +hashtable_get_count(const HashTable *h) { + return h->count; } -void *hashtable_set_user_data(HashTable *h,void *data) { - void *r=h->userData; - h->userData=data; - return r; +void * +hashtable_get_user_data(const HashTable *h) { + return h->userData; +} + +void * +hashtable_set_user_data(HashTable *h, void *data) { + void *r = h->userData; + h->userData = data; + return r; } diff --git a/src/libImaging/QuantHash.h b/src/libImaging/QuantHash.h index 9874114e5..fc1a99003 100644 --- a/src/libImaging/QuantHash.h +++ b/src/libImaging/QuantHash.h @@ -18,23 +18,38 @@ typedef struct _HashTable HashTable; typedef Pixel HashKey_t; typedef uint32_t HashVal_t; -typedef uint32_t (*HashFunc)(const HashTable *,const HashKey_t); -typedef int (*HashCmpFunc)(const HashTable *,const HashKey_t,const HashKey_t); -typedef void (*IteratorFunc)(const HashTable *,const HashKey_t,const HashVal_t,void *); -typedef void (*IteratorUpdateFunc)(const HashTable *,const HashKey_t,HashVal_t *,void *); -typedef void (*ComputeFunc)(const HashTable *,const HashKey_t,HashVal_t *); -typedef void (*CollisionFunc)(const HashTable *,HashKey_t *,HashVal_t *,HashKey_t,HashVal_t); +typedef uint32_t (*HashFunc)(const HashTable *, const HashKey_t); +typedef int (*HashCmpFunc)(const HashTable *, const HashKey_t, const HashKey_t); +typedef void (*IteratorFunc)( + const HashTable *, const HashKey_t, const HashVal_t, void *); +typedef void (*IteratorUpdateFunc)( + const HashTable *, const HashKey_t, HashVal_t *, void *); +typedef void (*ComputeFunc)(const HashTable *, const HashKey_t, HashVal_t *); +typedef void (*CollisionFunc)( + const HashTable *, HashKey_t *, HashVal_t *, HashKey_t, HashVal_t); -HashTable * hashtable_new(HashFunc hf,HashCmpFunc cf); -void hashtable_free(HashTable *h); -void hashtable_foreach(HashTable *h,IteratorFunc i,void *u); -void hashtable_foreach_update(HashTable *h,IteratorUpdateFunc i,void *u); -int hashtable_insert(HashTable *h,HashKey_t key,HashVal_t val); -int hashtable_lookup(const HashTable *h,const HashKey_t key,HashVal_t *valp); -int hashtable_insert_or_update_computed(HashTable *h,HashKey_t key,ComputeFunc newFunc,ComputeFunc existsFunc); -void *hashtable_set_user_data(HashTable *h,void *data); -void *hashtable_get_user_data(const HashTable *h); -uint32_t hashtable_get_count(const HashTable *h); -void hashtable_rehash_compute(HashTable *h,CollisionFunc cf); +HashTable * +hashtable_new(HashFunc hf, HashCmpFunc cf); +void +hashtable_free(HashTable *h); +void +hashtable_foreach(HashTable *h, IteratorFunc i, void *u); +void +hashtable_foreach_update(HashTable *h, IteratorUpdateFunc i, void *u); +int +hashtable_insert(HashTable *h, HashKey_t key, HashVal_t val); +int +hashtable_lookup(const HashTable *h, const HashKey_t key, HashVal_t *valp); +int +hashtable_insert_or_update_computed( + HashTable *h, HashKey_t key, ComputeFunc newFunc, ComputeFunc existsFunc); +void * +hashtable_set_user_data(HashTable *h, void *data); +void * +hashtable_get_user_data(const HashTable *h); +uint32_t +hashtable_get_count(const HashTable *h); +void +hashtable_rehash_compute(HashTable *h, CollisionFunc cf); -#endif // __QUANTHASH_H__ +#endif // __QUANTHASH_H__ diff --git a/src/libImaging/QuantHeap.c b/src/libImaging/QuantHeap.c index 121b87275..6fb52d890 100644 --- a/src/libImaging/QuantHeap.c +++ b/src/libImaging/QuantHeap.c @@ -25,10 +25,10 @@ #include "QuantHeap.h" struct _Heap { - void **heap; - int heapsize; - int heapcount; - HeapCmpFunc cf; + void **heap; + unsigned int heapsize; + unsigned int heapcount; + HeapCmpFunc cf; }; #define INITIAL_SIZE 256 @@ -36,119 +36,141 @@ struct _Heap { // #define DEBUG #ifdef DEBUG -static int _heap_test(Heap *); +static int +_heap_test(Heap *); #endif -void ImagingQuantHeapFree(Heap *h) { - free(h->heap); - free(h); +void +ImagingQuantHeapFree(Heap *h) { + free(h->heap); + free(h); } -static int _heap_grow(Heap *h,int newsize) { - void *newheap; - if (!newsize) newsize=h->heapsize<<1; - if (newsizeheapsize) return 0; - if (newsize > INT_MAX / sizeof(void *)){ - return 0; - } - /* malloc check ok, using calloc for overflow, also checking - above due to memcpy below*/ - newheap=calloc(newsize, sizeof(void *)); - if (!newheap) return 0; - memcpy(newheap,h->heap,sizeof(void *)*h->heapsize); - free(h->heap); - h->heap=newheap; - h->heapsize=newsize; - return 1; +static int +_heap_grow(Heap *h, unsigned int newsize) { + void *newheap; + if (!newsize) { + newsize = h->heapsize << 1; + } + if (newsize < h->heapsize) { + return 0; + } + if (newsize > INT_MAX / sizeof(void *)) { + return 0; + } + /* malloc check ok, using calloc for overflow, also checking + above due to memcpy below*/ + newheap = calloc(newsize, sizeof(void *)); + if (!newheap) { + return 0; + } + memcpy(newheap, h->heap, sizeof(void *) * h->heapsize); + free(h->heap); + h->heap = newheap; + h->heapsize = newsize; + return 1; } #ifdef DEBUG -static int _heap_test(Heap *h) { - int k; - for (k=1;k*2<=h->heapcount;k++) { - if (h->cf(h,h->heap[k],h->heap[k*2])<0) { - printf ("heap is bad\n"); - return 0; - } - if (k*2+1<=h->heapcount && h->cf(h,h->heap[k],h->heap[k*2+1])<0) { - printf ("heap is bad\n"); - return 0; - } - } - return 1; +static int +_heap_test(Heap *h) { + unsigned int k; + for (k = 1; k * 2 <= h->heapcount; k++) { + if (h->cf(h, h->heap[k], h->heap[k * 2]) < 0) { + printf("heap is bad\n"); + return 0; + } + if (k * 2 + 1 <= h->heapcount && h->cf(h, h->heap[k], h->heap[k * 2 + 1]) < 0) { + printf("heap is bad\n"); + return 0; + } + } + return 1; } #endif -int ImagingQuantHeapRemove(Heap* h,void **r) { - int k,l; - void *v; +int +ImagingQuantHeapRemove(Heap *h, void **r) { + unsigned int k, l; + void *v; - if (!h->heapcount) { - return 0; - } - *r=h->heap[1]; - v=h->heap[h->heapcount--]; - for (k=1;k*2<=h->heapcount;k=l) { - l=k*2; - if (lheapcount) { - if (h->cf(h,h->heap[l],h->heap[l+1])<0) { - l++; - } - } - if (h->cf(h,v,h->heap[l])>0) { - break; - } - h->heap[k]=h->heap[l]; - } - h->heap[k]=v; + if (!h->heapcount) { + return 0; + } + *r = h->heap[1]; + v = h->heap[h->heapcount--]; + for (k = 1; k * 2 <= h->heapcount; k = l) { + l = k * 2; + if (l < h->heapcount) { + if (h->cf(h, h->heap[l], h->heap[l + 1]) < 0) { + l++; + } + } + if (h->cf(h, v, h->heap[l]) > 0) { + break; + } + h->heap[k] = h->heap[l]; + } + h->heap[k] = v; #ifdef DEBUG - if (!_heap_test(h)) { printf ("oops - heap_remove messed up the heap\n"); exit(1); } + if (!_heap_test(h)) { + printf("oops - heap_remove messed up the heap\n"); + exit(1); + } #endif - return 1; + return 1; } -int ImagingQuantHeapAdd(Heap *h,void *val) { - int k; - if (h->heapcount==h->heapsize-1) { - _heap_grow(h,0); - } - k=++h->heapcount; - while (k!=1) { - if (h->cf(h,val,h->heap[k/2])<=0) { - break; - } - h->heap[k]=h->heap[k/2]; - k>>=1; - } - h->heap[k]=val; +int +ImagingQuantHeapAdd(Heap *h, void *val) { + int k; + if (h->heapcount == h->heapsize - 1) { + _heap_grow(h, 0); + } + k = ++h->heapcount; + while (k != 1) { + if (h->cf(h, val, h->heap[k / 2]) <= 0) { + break; + } + h->heap[k] = h->heap[k / 2]; + k >>= 1; + } + h->heap[k] = val; #ifdef DEBUG - if (!_heap_test(h)) { printf ("oops - heap_add messed up the heap\n"); exit(1); } + if (!_heap_test(h)) { + printf("oops - heap_add messed up the heap\n"); + exit(1); + } #endif - return 1; + return 1; } -int ImagingQuantHeapTop(Heap *h,void **r) { - if (!h->heapcount) { - return 0; - } - *r=h->heap[1]; - return 1; +int +ImagingQuantHeapTop(Heap *h, void **r) { + if (!h->heapcount) { + return 0; + } + *r = h->heap[1]; + return 1; } -Heap *ImagingQuantHeapNew(HeapCmpFunc cf) { - Heap *h; +Heap * +ImagingQuantHeapNew(HeapCmpFunc cf) { + Heap *h; - /* malloc check ok, small constant allocation */ - h=malloc(sizeof(Heap)); - if (!h) return NULL; - h->heapsize=INITIAL_SIZE; - /* malloc check ok, using calloc for overflow */ - h->heap=calloc(h->heapsize, sizeof(void *)); - if (!h->heap) { - free(h); - return NULL; - } - h->heapcount=0; - h->cf=cf; - return h; + /* malloc check ok, small constant allocation */ + h = malloc(sizeof(Heap)); + if (!h) { + return NULL; + } + h->heapsize = INITIAL_SIZE; + /* malloc check ok, using calloc for overflow */ + h->heap = calloc(h->heapsize, sizeof(void *)); + if (!h->heap) { + free(h); + return NULL; + } + h->heapcount = 0; + h->cf = cf; + return h; } diff --git a/src/libImaging/QuantHeap.h b/src/libImaging/QuantHeap.h index 77bf0d9d5..c5286dff2 100644 --- a/src/libImaging/QuantHeap.h +++ b/src/libImaging/QuantHeap.h @@ -16,12 +16,16 @@ typedef struct _Heap Heap; -typedef int (*HeapCmpFunc)(const Heap *,const void *,const void *); +typedef int (*HeapCmpFunc)(const Heap *, const void *, const void *); -void ImagingQuantHeapFree(Heap *); -int ImagingQuantHeapRemove(Heap *,void **); -int ImagingQuantHeapAdd(Heap *,void *); -int ImagingQuantHeapTop(Heap *,void **); +void +ImagingQuantHeapFree(Heap *); +int +ImagingQuantHeapRemove(Heap *, void **); +int +ImagingQuantHeapAdd(Heap *, void *); +int +ImagingQuantHeapTop(Heap *, void **); Heap *ImagingQuantHeapNew(HeapCmpFunc); -#endif // __QUANTHEAP_H__ +#endif // __QUANTHEAP_H__ diff --git a/src/libImaging/QuantOctree.c b/src/libImaging/QuantOctree.c index 6c0f605c9..b8d4d1d7c 100644 --- a/src/libImaging/QuantOctree.c +++ b/src/libImaging/QuantOctree.c @@ -28,463 +28,511 @@ #include #include +#include "ImagingUtils.h" #include "QuantOctree.h" -typedef struct _ColorBucket{ - /* contains palette index when used for look up cube */ - uint32_t count; - uint64_t r; - uint64_t g; - uint64_t b; - uint64_t a; -} *ColorBucket; +typedef struct _ColorBucket { + /* contains palette index when used for look up cube */ + uint32_t count; + uint64_t r; + uint64_t g; + uint64_t b; + uint64_t a; +} * ColorBucket; -typedef struct _ColorCube{ - unsigned int rBits, gBits, bBits, aBits; - unsigned int rWidth, gWidth, bWidth, aWidth; - unsigned int rOffset, gOffset, bOffset, aOffset; +typedef struct _ColorCube { + unsigned int rBits, gBits, bBits, aBits; + unsigned int rWidth, gWidth, bWidth, aWidth; + unsigned int rOffset, gOffset, bOffset, aOffset; - long size; - ColorBucket buckets; -} *ColorCube; + unsigned long size; + ColorBucket buckets; +} * ColorCube; -#define MAX(a, b) (a)>(b) ? (a) : (b) +#define MAX(a, b) (a) > (b) ? (a) : (b) static ColorCube new_color_cube(int r, int g, int b, int a) { - ColorCube cube; + ColorCube cube; - /* malloc check ok, small constant allocation */ - cube = malloc(sizeof(struct _ColorCube)); - if (!cube) return NULL; + /* malloc check ok, small constant allocation */ + cube = malloc(sizeof(struct _ColorCube)); + if (!cube) { + return NULL; + } - cube->rBits = MAX(r, 0); - cube->gBits = MAX(g, 0); - cube->bBits = MAX(b, 0); - cube->aBits = MAX(a, 0); + cube->rBits = MAX(r, 0); + cube->gBits = MAX(g, 0); + cube->bBits = MAX(b, 0); + cube->aBits = MAX(a, 0); - /* overflow check for size multiplication below */ - if (cube->rBits + cube->gBits + cube->bBits + cube->aBits > 31) { - free(cube); - return NULL; - } + /* overflow check for size multiplication below */ + if (cube->rBits + cube->gBits + cube->bBits + cube->aBits > 31) { + free(cube); + return NULL; + } - /* the width of the cube for each dimension */ - cube->rWidth = 1<rBits; - cube->gWidth = 1<gBits; - cube->bWidth = 1<bBits; - cube->aWidth = 1<aBits; + /* the width of the cube for each dimension */ + cube->rWidth = 1 << cube->rBits; + cube->gWidth = 1 << cube->gBits; + cube->bWidth = 1 << cube->bBits; + cube->aWidth = 1 << cube->aBits; - /* the offsets of each color */ + /* the offsets of each color */ - cube->rOffset = cube->gBits + cube->bBits + cube->aBits; - cube->gOffset = cube->bBits + cube->aBits; - cube->bOffset = cube->aBits; - cube->aOffset = 0; + cube->rOffset = cube->gBits + cube->bBits + cube->aBits; + cube->gOffset = cube->bBits + cube->aBits; + cube->bOffset = cube->aBits; + cube->aOffset = 0; - /* the number of color buckets */ - cube->size = cube->rWidth * cube->gWidth * cube->bWidth * cube->aWidth; - /* malloc check ok, overflow checked above */ - cube->buckets = calloc(cube->size, sizeof(struct _ColorBucket)); + /* the number of color buckets */ + cube->size = cube->rWidth * cube->gWidth * cube->bWidth * cube->aWidth; + /* malloc check ok, overflow checked above */ + cube->buckets = calloc(cube->size, sizeof(struct _ColorBucket)); - if (!cube->buckets) { - free(cube); - return NULL; - } - return cube; + if (!cube->buckets) { + free(cube); + return NULL; + } + return cube; } static void free_color_cube(ColorCube cube) { - if (cube != NULL) { - free(cube->buckets); - free(cube); - } + if (cube != NULL) { + free(cube->buckets); + free(cube); + } } static long -color_bucket_offset_pos(const ColorCube cube, - unsigned int r, unsigned int g, unsigned int b, unsigned int a) -{ - return r<rOffset | g<gOffset | b<bOffset | a<aOffset; +color_bucket_offset_pos( + const ColorCube cube, + unsigned int r, + unsigned int g, + unsigned int b, + unsigned int a) { + return r << cube->rOffset | g << cube->gOffset | b << cube->bOffset | + a << cube->aOffset; } static long color_bucket_offset(const ColorCube cube, const Pixel *p) { - unsigned int r = p->c.r>>(8-cube->rBits); - unsigned int g = p->c.g>>(8-cube->gBits); - unsigned int b = p->c.b>>(8-cube->bBits); - unsigned int a = p->c.a>>(8-cube->aBits); - return color_bucket_offset_pos(cube, r, g, b, a); + unsigned int r = p->c.r >> (8 - cube->rBits); + unsigned int g = p->c.g >> (8 - cube->gBits); + unsigned int b = p->c.b >> (8 - cube->bBits); + unsigned int a = p->c.a >> (8 - cube->aBits); + return color_bucket_offset_pos(cube, r, g, b, a); } static ColorBucket color_bucket_from_cube(const ColorCube cube, const Pixel *p) { - unsigned int offset = color_bucket_offset(cube, p); - return &cube->buckets[offset]; + unsigned int offset = color_bucket_offset(cube, p); + return &cube->buckets[offset]; } static void add_color_to_color_cube(const ColorCube cube, const Pixel *p) { - ColorBucket bucket = color_bucket_from_cube(cube, p); - bucket->count += 1; - bucket->r += p->c.r; - bucket->g += p->c.g; - bucket->b += p->c.b; - bucket->a += p->c.a; + ColorBucket bucket = color_bucket_from_cube(cube, p); + bucket->count += 1; + bucket->r += p->c.r; + bucket->g += p->c.g; + bucket->b += p->c.b; + bucket->a += p->c.a; } -static long +static unsigned long count_used_color_buckets(const ColorCube cube) { - long usedBuckets = 0; - long i; - for (i=0; i < cube->size; i++) { - if (cube->buckets[i].count > 0) { - usedBuckets += 1; - } - } - return usedBuckets; + unsigned long usedBuckets = 0; + unsigned long i; + for (i = 0; i < cube->size; i++) { + if (cube->buckets[i].count > 0) { + usedBuckets += 1; + } + } + return usedBuckets; } static void avg_color_from_color_bucket(const ColorBucket bucket, Pixel *dst) { - float count = bucket->count; - if (count != 0) { - dst->c.r = (int)(bucket->r / count); - dst->c.g = (int)(bucket->g / count); - dst->c.b = (int)(bucket->b / count); - dst->c.a = (int)(bucket->a / count); - } else { - dst->c.r = 0; - dst->c.g = 0; - dst->c.b = 0; - dst->c.a = 0; - } + float count = bucket->count; + if (count != 0) { + dst->c.r = CLIP8((int)(bucket->r / count)); + dst->c.g = CLIP8((int)(bucket->g / count)); + dst->c.b = CLIP8((int)(bucket->b / count)); + dst->c.a = CLIP8((int)(bucket->a / count)); + } else { + dst->c.r = 0; + dst->c.g = 0; + dst->c.b = 0; + dst->c.a = 0; + } } static int compare_bucket_count(const ColorBucket a, const ColorBucket b) { - return b->count - a->count; + return b->count - a->count; } static ColorBucket create_sorted_color_palette(const ColorCube cube) { - ColorBucket buckets; - if (cube->size > LONG_MAX / sizeof(struct _ColorBucket)) { - return NULL; - } - /* malloc check ok, calloc + overflow check above for memcpy */ - buckets = calloc(cube->size, sizeof(struct _ColorBucket)); - if (!buckets) return NULL; - memcpy(buckets, cube->buckets, sizeof(struct _ColorBucket)*cube->size); + ColorBucket buckets; + if (cube->size > LONG_MAX / sizeof(struct _ColorBucket)) { + return NULL; + } + /* malloc check ok, calloc + overflow check above for memcpy */ + buckets = calloc(cube->size, sizeof(struct _ColorBucket)); + if (!buckets) { + return NULL; + } + memcpy(buckets, cube->buckets, sizeof(struct _ColorBucket) * cube->size); - qsort(buckets, cube->size, sizeof(struct _ColorBucket), - (int (*)(void const *, void const *))&compare_bucket_count); + qsort( + buckets, + cube->size, + sizeof(struct _ColorBucket), + (int (*)(void const *, void const *)) & compare_bucket_count); - return buckets; + return buckets; } -void add_bucket_values(ColorBucket src, ColorBucket dst) { - dst->count += src->count; - dst->r += src->r; - dst->g += src->g; - dst->b += src->b; - dst->a += src->a; +void +add_bucket_values(ColorBucket src, ColorBucket dst) { + dst->count += src->count; + dst->r += src->r; + dst->g += src->g; + dst->b += src->b; + dst->a += src->a; } /* expand or shrink a given cube to level */ -static ColorCube copy_color_cube(const ColorCube cube, - int rBits, int gBits, int bBits, int aBits) -{ - unsigned int r, g, b, a; - long src_pos, dst_pos; - unsigned int src_reduce[4] = {0}, dst_reduce[4] = {0}; - unsigned int width[4]; - ColorCube result; +static ColorCube +copy_color_cube( + const ColorCube cube, + unsigned int rBits, + unsigned int gBits, + unsigned int bBits, + unsigned int aBits) { + unsigned int r, g, b, a; + long src_pos, dst_pos; + unsigned int src_reduce[4] = {0}, dst_reduce[4] = {0}; + unsigned int width[4]; + ColorCube result; - result = new_color_cube(rBits, gBits, bBits, aBits); - if (!result) return NULL; + result = new_color_cube(rBits, gBits, bBits, aBits); + if (!result) { + return NULL; + } - if (cube->rBits > rBits) { - dst_reduce[0] = cube->rBits - result->rBits; - width[0] = cube->rWidth; - } else { - src_reduce[0] = result->rBits - cube->rBits; - width[0] = result->rWidth; - } - if (cube->gBits > gBits) { - dst_reduce[1] = cube->gBits - result->gBits; - width[1] = cube->gWidth; - } else { - src_reduce[1] = result->gBits - cube->gBits; - width[1] = result->gWidth; - } - if (cube->bBits > bBits) { - dst_reduce[2] = cube->bBits - result->bBits; - width[2] = cube->bWidth; - } else { - src_reduce[2] = result->bBits - cube->bBits; - width[2] = result->bWidth; - } - if (cube->aBits > aBits) { - dst_reduce[3] = cube->aBits - result->aBits; - width[3] = cube->aWidth; - } else { - src_reduce[3] = result->aBits - cube->aBits; - width[3] = result->aWidth; - } + if (cube->rBits > rBits) { + dst_reduce[0] = cube->rBits - result->rBits; + width[0] = cube->rWidth; + } else { + src_reduce[0] = result->rBits - cube->rBits; + width[0] = result->rWidth; + } + if (cube->gBits > gBits) { + dst_reduce[1] = cube->gBits - result->gBits; + width[1] = cube->gWidth; + } else { + src_reduce[1] = result->gBits - cube->gBits; + width[1] = result->gWidth; + } + if (cube->bBits > bBits) { + dst_reduce[2] = cube->bBits - result->bBits; + width[2] = cube->bWidth; + } else { + src_reduce[2] = result->bBits - cube->bBits; + width[2] = result->bWidth; + } + if (cube->aBits > aBits) { + dst_reduce[3] = cube->aBits - result->aBits; + width[3] = cube->aWidth; + } else { + src_reduce[3] = result->aBits - cube->aBits; + width[3] = result->aWidth; + } - for (r=0; r>src_reduce[0], - g>>src_reduce[1], - b>>src_reduce[2], - a>>src_reduce[3]); - dst_pos = color_bucket_offset_pos(result, - r>>dst_reduce[0], - g>>dst_reduce[1], - b>>dst_reduce[2], - a>>dst_reduce[3]); - add_bucket_values( - &cube->buckets[src_pos], - &result->buckets[dst_pos] - ); + for (r = 0; r < width[0]; r++) { + for (g = 0; g < width[1]; g++) { + for (b = 0; b < width[2]; b++) { + for (a = 0; a < width[3]; a++) { + src_pos = color_bucket_offset_pos( + cube, + r >> src_reduce[0], + g >> src_reduce[1], + b >> src_reduce[2], + a >> src_reduce[3]); + dst_pos = color_bucket_offset_pos( + result, + r >> dst_reduce[0], + g >> dst_reduce[1], + b >> dst_reduce[2], + a >> dst_reduce[3]); + add_bucket_values( + &cube->buckets[src_pos], &result->buckets[dst_pos]); + } } - } - } - } - return result; + } + } + return result; } void subtract_color_buckets(ColorCube cube, ColorBucket buckets, long nBuckets) { - ColorBucket minuend, subtrahend; - long i; - Pixel p; - for (i=0; icount == 0) continue; + // If the subtrahend contains no buckets, there is nothing to subtract. + if (subtrahend->count == 0) { + continue; + } - avg_color_from_color_bucket(subtrahend, &p); - minuend = color_bucket_from_cube(cube, &p); - minuend->count -= subtrahend->count; - minuend->r -= subtrahend->r; - minuend->g -= subtrahend->g; - minuend->b -= subtrahend->b; - minuend->a -= subtrahend->a; - } + avg_color_from_color_bucket(subtrahend, &p); + minuend = color_bucket_from_cube(cube, &p); + minuend->count -= subtrahend->count; + minuend->r -= subtrahend->r; + minuend->g -= subtrahend->g; + minuend->b -= subtrahend->b; + minuend->a -= subtrahend->a; + } } static void set_lookup_value(const ColorCube cube, const Pixel *p, long value) { - ColorBucket bucket = color_bucket_from_cube(cube, p); - bucket->count = value; + ColorBucket bucket = color_bucket_from_cube(cube, p); + bucket->count = value; } uint64_t lookup_color(const ColorCube cube, const Pixel *p) { - ColorBucket bucket = color_bucket_from_cube(cube, p); - return bucket->count; + ColorBucket bucket = color_bucket_from_cube(cube, p); + return bucket->count; } -void add_lookup_buckets(ColorCube cube, ColorBucket palette, long nColors, long offset) { - long i; - Pixel p; - for (i=offset; i LONG_MAX - nBucketsB || - (nBucketsA+nBucketsB) > LONG_MAX / sizeof(struct _ColorBucket)) { - return NULL; - } - /* malloc check ok, overflow check above */ - result = calloc(nBucketsA + nBucketsB, sizeof(struct _ColorBucket)); - if (!result) { - return NULL; - } - memcpy(result, bucketsA, sizeof(struct _ColorBucket) * nBucketsA); - memcpy(&result[nBucketsA], bucketsB, sizeof(struct _ColorBucket) * nBucketsB); - return result; +combined_palette( + ColorBucket bucketsA, + unsigned long nBucketsA, + ColorBucket bucketsB, + unsigned long nBucketsB) { + ColorBucket result; + if (nBucketsA > LONG_MAX - nBucketsB || + (nBucketsA + nBucketsB) > LONG_MAX / sizeof(struct _ColorBucket)) { + return NULL; + } + /* malloc check ok, overflow check above */ + result = calloc(nBucketsA + nBucketsB, sizeof(struct _ColorBucket)); + if (!result) { + return NULL; + } + memcpy(result, bucketsA, sizeof(struct _ColorBucket) * nBucketsA); + memcpy(&result[nBucketsA], bucketsB, sizeof(struct _ColorBucket) * nBucketsB); + return result; } static Pixel * create_palette_array(const ColorBucket palette, unsigned int paletteLength) { - Pixel *paletteArray; - unsigned int i; + Pixel *paletteArray; + unsigned int i; - /* malloc check ok, calloc for overflow */ - paletteArray = calloc(paletteLength, sizeof(Pixel)); - if (!paletteArray) return NULL; + /* malloc check ok, calloc for overflow */ + paletteArray = calloc(paletteLength, sizeof(Pixel)); + if (!paletteArray) { + return NULL; + } - for (i=0; i 64). + /* + Create two color cubes, one fine grained with 8x16x8=1024 + colors buckets and a coarse with 4x4x4=64 color buckets. + The coarse one guarantees that there are color buckets available for + the whole color range (assuming nQuantPixels > 64). - For a quantization to 256 colors all 64 coarse colors will be used - plus the 192 most used color buckets from the fine color cube. - The average of all colors within one bucket is used as the actual - color for that bucket. + For a quantization to 256 colors all 64 coarse colors will be used + plus the 192 most used color buckets from the fine color cube. + The average of all colors within one bucket is used as the actual + color for that bucket. - For images with alpha the cubes gets a forth dimension, - 8x16x8x8 and 4x4x4x4. - */ + For images with alpha the cubes gets a forth dimension, + 8x16x8x8 and 4x4x4x4. + */ - /* create fine cube */ - fineCube = new_color_cube(cubeBits[0], cubeBits[1], - cubeBits[2], cubeBits[3]); - if (!fineCube) goto error; - for (i=0; i nQuantPixels) - nCoarseColors = nQuantPixels; + /* limit to nQuantPixels */ + if (nCoarseColors > nQuantPixels) { + nCoarseColors = nQuantPixels; + } - /* how many space do we have in our palette for fine colors? */ - nFineColors = nQuantPixels - nCoarseColors; + /* how many space do we have in our palette for fine colors? */ + nFineColors = nQuantPixels - nCoarseColors; - /* create fine color palette */ - paletteBucketsFine = create_sorted_color_palette(fineCube); - if (!paletteBucketsFine) goto error; + /* create fine color palette */ + paletteBucketsFine = create_sorted_color_palette(fineCube); + if (!paletteBucketsFine) { + goto error; + } - /* remove the used fine colors from the coarse cube */ - subtract_color_buckets(coarseCube, paletteBucketsFine, nFineColors); + /* remove the used fine colors from the coarse cube */ + subtract_color_buckets(coarseCube, paletteBucketsFine, nFineColors); - /* did the subtraction cleared one or more coarse bucket? */ - while (nCoarseColors > count_used_color_buckets(coarseCube)) { - /* then we can use the free buckets for fine colors */ - nAlreadySubtracted = nFineColors; - nCoarseColors = count_used_color_buckets(coarseCube); - nFineColors = nQuantPixels - nCoarseColors; - subtract_color_buckets(coarseCube, &paletteBucketsFine[nAlreadySubtracted], - nFineColors-nAlreadySubtracted); - } + /* did the subtraction cleared one or more coarse bucket? */ + while (nCoarseColors > count_used_color_buckets(coarseCube)) { + /* then we can use the free buckets for fine colors */ + nAlreadySubtracted = nFineColors; + nCoarseColors = count_used_color_buckets(coarseCube); + nFineColors = nQuantPixels - nCoarseColors; + subtract_color_buckets( + coarseCube, + &paletteBucketsFine[nAlreadySubtracted], + nFineColors - nAlreadySubtracted); + } - /* create our palette buckets with fine and coarse combined */ - paletteBucketsCoarse = create_sorted_color_palette(coarseCube); - if (!paletteBucketsCoarse) goto error; - paletteBuckets = combined_palette(paletteBucketsCoarse, nCoarseColors, - paletteBucketsFine, nFineColors); + /* create our palette buckets with fine and coarse combined */ + paletteBucketsCoarse = create_sorted_color_palette(coarseCube); + if (!paletteBucketsCoarse) { + goto error; + } + paletteBuckets = combined_palette( + paletteBucketsCoarse, nCoarseColors, paletteBucketsFine, nFineColors); - free(paletteBucketsFine); - paletteBucketsFine = NULL; - free(paletteBucketsCoarse); - paletteBucketsCoarse = NULL; - if (!paletteBuckets) goto error; + free(paletteBucketsFine); + paletteBucketsFine = NULL; + free(paletteBucketsCoarse); + paletteBucketsCoarse = NULL; + if (!paletteBuckets) { + goto error; + } - /* add all coarse colors to our coarse lookup cube. */ - coarseLookupCube = new_color_cube(cubeBits[4], cubeBits[5], - cubeBits[6], cubeBits[7]); - if (!coarseLookupCube) goto error; - add_lookup_buckets(coarseLookupCube, paletteBuckets, nCoarseColors, 0); + /* add all coarse colors to our coarse lookup cube. */ + coarseLookupCube = + new_color_cube(cubeBits[4], cubeBits[5], cubeBits[6], cubeBits[7]); + if (!coarseLookupCube) { + goto error; + } + add_lookup_buckets(coarseLookupCube, paletteBuckets, nCoarseColors, 0); - /* expand coarse cube (64) to larger fine cube (4k). the value of each - coarse bucket is then present in the according 64 fine buckets. */ - lookupCube = copy_color_cube(coarseLookupCube, cubeBits[0], cubeBits[1], - cubeBits[2], cubeBits[3]); - if (!lookupCube) goto error; + /* expand coarse cube (64) to larger fine cube (4k). the value of each + coarse bucket is then present in the according 64 fine buckets. */ + lookupCube = copy_color_cube( + coarseLookupCube, cubeBits[0], cubeBits[1], cubeBits[2], cubeBits[3]); + if (!lookupCube) { + goto error; + } - /* add fine colors to the lookup cube */ - add_lookup_buckets(lookupCube, paletteBuckets, nFineColors, nCoarseColors); + /* add fine colors to the lookup cube */ + add_lookup_buckets(lookupCube, paletteBuckets, nFineColors, nCoarseColors); - /* create result pixels and map palette indices */ - /* malloc check ok, calloc for overflow */ - qp = calloc(nPixels, sizeof(Pixel)); - if (!qp) goto error; - map_image_pixels(pixelData, nPixels, lookupCube, qp); + /* create result pixels and map palette indices */ + /* malloc check ok, calloc for overflow */ + qp = calloc(nPixels, sizeof(Pixel)); + if (!qp) { + goto error; + } + map_image_pixels(pixelData, nPixels, lookupCube, qp); - /* convert palette buckets to RGB pixel palette */ - *palette = create_palette_array(paletteBuckets, nQuantPixels); - if (!(*palette)) goto error; + /* convert palette buckets to RGB pixel palette */ + *palette = create_palette_array(paletteBuckets, nQuantPixels); + if (!(*palette)) { + goto error; + } - *quantizedPixels = qp; - *paletteLength = nQuantPixels; + *quantizedPixels = qp; + *paletteLength = nQuantPixels; - free_color_cube(coarseCube); - free_color_cube(fineCube); - free_color_cube(lookupCube); - free_color_cube(coarseLookupCube); - free(paletteBuckets); - return 1; + free_color_cube(coarseCube); + free_color_cube(fineCube); + free_color_cube(lookupCube); + free_color_cube(coarseLookupCube); + free(paletteBuckets); + return 1; error: - /* everything is initialized to NULL - so we are safe to call free */ - free(qp); - free_color_cube(lookupCube); - free_color_cube(coarseLookupCube); - free(paletteBuckets); - free(paletteBucketsCoarse); - free(paletteBucketsFine); - free_color_cube(coarseCube); - free_color_cube(fineCube); - return 0; + /* everything is initialized to NULL + so we are safe to call free */ + free(qp); + free_color_cube(lookupCube); + free_color_cube(coarseLookupCube); + free(paletteBuckets); + free(paletteBucketsCoarse); + free(paletteBucketsFine); + free_color_cube(coarseCube); + free_color_cube(fineCube); + return 0; } diff --git a/src/libImaging/QuantOctree.h b/src/libImaging/QuantOctree.h index 968644eda..e1c504074 100644 --- a/src/libImaging/QuantOctree.h +++ b/src/libImaging/QuantOctree.h @@ -3,12 +3,7 @@ #include "QuantTypes.h" -int quantize_octree(Pixel *, - uint32_t, - uint32_t, - Pixel **, - uint32_t *, - uint32_t **, - int); +int +quantize_octree(Pixel *, uint32_t, uint32_t, Pixel **, uint32_t *, uint32_t **, int); #endif diff --git a/src/libImaging/QuantPngQuant.c b/src/libImaging/QuantPngQuant.c index a9a547540..7a36300e4 100644 --- a/src/libImaging/QuantPngQuant.c +++ b/src/libImaging/QuantPngQuant.c @@ -20,14 +20,13 @@ int quantize_pngquant( Pixel *pixelData, - int width, - int height, + unsigned int width, + unsigned int height, uint32_t quantPixels, Pixel **palette, uint32_t *paletteLength, uint32_t **quantizedPixels, - int withAlpha) -{ + int withAlpha) { int result = 0; liq_image *image = NULL; liq_attr *attr = NULL; @@ -41,23 +40,24 @@ quantize_pngquant( /* configure pngquant */ attr = liq_attr_create(); - if (!attr) { goto err; } + if (!attr) { + goto err; + } if (quantPixels) { liq_set_max_colors(attr, quantPixels); } /* prepare input image */ - image = liq_image_create_rgba( - attr, - pixelData, - width, - height, - 0.45455 /* gamma */); - if (!image) { goto err; } + image = liq_image_create_rgba(attr, pixelData, width, height, 0.45455 /* gamma */); + if (!image) { + goto err; + } /* quantize the image */ remap = liq_quantize_image(attr, image); - if (!remap) { goto err; } + if (!remap) { + goto err; + } liq_set_output_gamma(remap, 0.45455); liq_set_dithering_level(remap, 1); @@ -65,7 +65,9 @@ quantize_pngquant( const liq_palette *l_palette = liq_get_palette(remap); *paletteLength = l_palette->count; *palette = malloc(sizeof(Pixel) * l_palette->count); - if (!*palette) { goto err; } + if (!*palette) { + goto err; + } for (i = 0; i < l_palette->count; i++) { (*palette)[i].c.b = l_palette->entries[i].b; (*palette)[i].c.g = l_palette->entries[i].g; @@ -75,9 +77,13 @@ quantize_pngquant( /* write output pixels (pngquant uses char array) */ charMatrix = malloc(width * height); - if (!charMatrix) { goto err; } - charMatrixRows = malloc(height * sizeof(unsigned char*)); - if (!charMatrixRows) { goto err; } + if (!charMatrix) { + goto err; + } + charMatrixRows = malloc(height * sizeof(unsigned char *)); + if (!charMatrixRows) { + goto err; + } for (y = 0; y < height; y++) { charMatrixRows[y] = &charMatrix[y * width]; } @@ -87,7 +93,9 @@ quantize_pngquant( /* transcribe output pixels (pillow uses uint32_t array) */ *quantizedPixels = malloc(sizeof(uint32_t) * width * height); - if (!*quantizedPixels) { goto err; } + if (!*quantizedPixels) { + goto err; + } for (i = 0; i < width * height; i++) { (*quantizedPixels)[i] = charMatrix[i]; } @@ -95,16 +103,30 @@ quantize_pngquant( result = 1; err: - if (attr) liq_attr_destroy(attr); - if (image) liq_image_destroy(image); - if (remap) liq_result_destroy(remap); + if (attr) { + liq_attr_destroy(attr); + } + if (image) { + liq_image_destroy(image); + } + if (remap) { + liq_result_destroy(remap); + } free(charMatrix); free(charMatrixRows); - if (!result) { + if (!result) { free(*quantizedPixels); free(*palette); } return result; } +const char * +ImagingImageQuantVersion(void) { + static char version[20]; + int number = liq_version(); + sprintf(version, "%d.%d.%d", number / 10000, (number / 100) % 100, number % 100); + return version; +} + #endif diff --git a/src/libImaging/QuantPngQuant.h b/src/libImaging/QuantPngQuant.h index d539a7a0d..d65e42590 100644 --- a/src/libImaging/QuantPngQuant.h +++ b/src/libImaging/QuantPngQuant.h @@ -3,9 +3,11 @@ #include "QuantTypes.h" -int quantize_pngquant(Pixel *, - int, - int, +int +quantize_pngquant( + Pixel *, + unsigned int, + unsigned int, uint32_t, Pixel **, uint32_t *, diff --git a/src/libImaging/QuantTypes.h b/src/libImaging/QuantTypes.h index 411485498..986b70806 100644 --- a/src/libImaging/QuantTypes.h +++ b/src/libImaging/QuantTypes.h @@ -20,13 +20,13 @@ typedef unsigned __int64 uint64_t; #endif typedef union { - struct { - unsigned char r,g,b,a; - } c; - struct { - unsigned char v[4]; - } a; - uint32_t v; + struct { + unsigned char r, g, b, a; + } c; + struct { + unsigned char v[4]; + } a; + uint32_t v; } Pixel; #endif diff --git a/src/libImaging/RankFilter.c b/src/libImaging/RankFilter.c index 0164861bb..73a6baecb 100644 --- a/src/libImaging/RankFilter.c +++ b/src/libImaging/RankFilter.c @@ -17,90 +17,109 @@ /* Fast rank algorithm (due to Wirth), based on public domain code by Nicolas Devillard, available at http://ndevilla.free.fr */ -#define SWAP(type,a,b) { register type t=(a);(a)=(b);(b)=t; } +#define SWAP(type, a, b) \ + { \ + register type t = (a); \ + (a) = (b); \ + (b) = t; \ + } -#define MakeRankFunction(type)\ -static type Rank##type(type a[], int n, int k)\ -{\ - register int i, j, l, m;\ - register type x;\ - l = 0; m = n-1;\ - while (l < m) {\ - x = a[k];\ - i = l;\ - j = m;\ - do {\ - while (a[i] < x) i++;\ - while (x < a[j]) j--;\ - if (i <= j) {\ - SWAP(type, a[i], a[j]);\ - i++; j--;\ - }\ - } while (i <= j);\ - if (j < k) l = i;\ - if (k < i) m = j;\ - }\ - return a[k];\ -} +#define MakeRankFunction(type) \ + static type Rank##type(type a[], int n, int k) { \ + register int i, j, l, m; \ + register type x; \ + l = 0; \ + m = n - 1; \ + while (l < m) { \ + x = a[k]; \ + i = l; \ + j = m; \ + do { \ + while (a[i] < x) { \ + i++; \ + } \ + while (x < a[j]) { \ + j--; \ + } \ + if (i <= j) { \ + SWAP(type, a[i], a[j]); \ + i++; \ + j--; \ + } \ + } while (i <= j); \ + if (j < k) { \ + l = i; \ + } \ + if (k < i) { \ + m = j; \ + } \ + } \ + return a[k]; \ + } -MakeRankFunction(UINT8) -MakeRankFunction(INT32) -MakeRankFunction(FLOAT32) +MakeRankFunction(UINT8) MakeRankFunction(INT32) MakeRankFunction(FLOAT32) -Imaging -ImagingRankFilter(Imaging im, int size, int rank) -{ + Imaging ImagingRankFilter(Imaging im, int size, int rank) { Imaging imOut = NULL; int x, y; int i, margin, size2; - if (!im || im->bands != 1 || im->type == IMAGING_TYPE_SPECIAL) - return (Imaging) ImagingError_ModeError(); + if (!im || im->bands != 1 || im->type == IMAGING_TYPE_SPECIAL) { + return (Imaging)ImagingError_ModeError(); + } - if (!(size & 1)) - return (Imaging) ImagingError_ValueError("bad filter size"); + if (!(size & 1)) { + return (Imaging)ImagingError_ValueError("bad filter size"); + } /* malloc check ok, for overflow in the define below */ - if (size > INT_MAX / size || - size > INT_MAX / (size * sizeof(FLOAT32))) { - return (Imaging) ImagingError_ValueError("filter size too large"); + if (size > INT_MAX / size || size > INT_MAX / (size * (int)sizeof(FLOAT32))) { + return (Imaging)ImagingError_ValueError("filter size too large"); } size2 = size * size; - margin = (size-1) / 2; + margin = (size - 1) / 2; - if (rank < 0 || rank >= size2) - return (Imaging) ImagingError_ValueError("bad rank value"); + if (rank < 0 || rank >= size2) { + return (Imaging)ImagingError_ValueError("bad rank value"); + } - imOut = ImagingNew(im->mode, im->xsize - 2*margin, im->ysize - 2*margin); - if (!imOut) + imOut = ImagingNew(im->mode, im->xsize - 2 * margin, im->ysize - 2 * margin); + if (!imOut) { return NULL; + } /* malloc check ok, checked above */ -#define RANK_BODY(type) do {\ - type* buf = malloc(size2 * sizeof(type));\ - if (!buf)\ - goto nomemory;\ - for (y = 0; y < imOut->ysize; y++)\ - for (x = 0; x < imOut->xsize; x++) {\ - for (i = 0; i < size; i++)\ - memcpy(buf + i*size, &IMAGING_PIXEL_##type(im, x, y+i),\ - size * sizeof(type));\ - IMAGING_PIXEL_##type(imOut, x, y) = Rank##type(buf, size2, rank);\ - }\ - free(buf); \ -} while (0) +#define RANK_BODY(type) \ + do { \ + type *buf = malloc(size2 * sizeof(type)); \ + if (!buf) { \ + goto nomemory; \ + } \ + for (y = 0; y < imOut->ysize; y++) { \ + for (x = 0; x < imOut->xsize; x++) { \ + for (i = 0; i < size; i++) { \ + memcpy( \ + buf + i * size, \ + &IMAGING_PIXEL_##type(im, x, y + i), \ + size * sizeof(type)); \ + } \ + IMAGING_PIXEL_##type(imOut, x, y) = Rank##type(buf, size2, rank); \ + } \ + } \ + free(buf); \ + } while (0) - if (im->image8) + if (im->image8) { RANK_BODY(UINT8); - else if (im->type == IMAGING_TYPE_INT32) + } else if (im->type == IMAGING_TYPE_INT32) { RANK_BODY(INT32); - else if (im->type == IMAGING_TYPE_FLOAT32) + } else if (im->type == IMAGING_TYPE_FLOAT32) { RANK_BODY(FLOAT32); - else { + } else { /* safety net (we shouldn't end up here) */ ImagingDelete(imOut); - return (Imaging) ImagingError_ModeError(); + return (Imaging)ImagingError_ModeError(); } ImagingCopyPalette(imOut, im); @@ -109,5 +128,5 @@ ImagingRankFilter(Imaging im, int size, int rank) nomemory: ImagingDelete(imOut); - return (Imaging) ImagingError_MemoryError(); + return (Imaging)ImagingError_MemoryError(); } diff --git a/src/libImaging/Raw.h b/src/libImaging/Raw.h index 4d28fa546..ab718837f 100644 --- a/src/libImaging/Raw.h +++ b/src/libImaging/Raw.h @@ -1,7 +1,6 @@ /* Raw.h */ typedef struct { - /* CONFIGURATION */ /* Distance between lines (0=no padding) */ diff --git a/src/libImaging/RawDecode.c b/src/libImaging/RawDecode.c index c069bdb88..24abe4804 100644 --- a/src/libImaging/RawDecode.c +++ b/src/libImaging/RawDecode.c @@ -5,7 +5,7 @@ * decoder for raw (uncompressed) image data * * history: - * 96-03-07 fl rewritten + * 96-03-07 fl rewritten * * Copyright (c) Fredrik Lundh 1996. * Copyright (c) Secret Labs AB 1997. @@ -13,84 +13,79 @@ * See the README file for information on usage and redistribution. */ - #include "Imaging.h" #include "Raw.h" - int -ImagingRawDecode(Imaging im, ImagingCodecState state, UINT8* buf, Py_ssize_t bytes) -{ +ImagingRawDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t bytes) { enum { LINE = 1, SKIP }; - RAWSTATE* rawstate = state->context; + RAWSTATE *rawstate = state->context; - UINT8* ptr; + UINT8 *ptr; if (state->state == 0) { + /* Initialize context variables */ - /* Initialize context variables */ + /* get size of image data and padding */ + state->bytes = (state->xsize * state->bits + 7) / 8; + if (rawstate->stride) { + rawstate->skip = rawstate->stride - state->bytes; + if (rawstate->skip < 0) { + state->errcode = IMAGING_CODEC_CONFIG; + return -1; + } + } else { + rawstate->skip = 0; + } - /* get size of image data and padding */ - state->bytes = (state->xsize * state->bits + 7) / 8; - if (rawstate->stride) { - rawstate->skip = rawstate->stride - state->bytes; - if (rawstate->skip < 0) { - state->errcode = IMAGING_CODEC_CONFIG; - return -1; - } - } else { - rawstate->skip = 0; - } - - /* check image orientation */ - if (state->ystep < 0) { - state->y = state->ysize-1; - state->ystep = -1; - } else - state->ystep = 1; - - state->state = LINE; + /* check image orientation */ + if (state->ystep < 0) { + state->y = state->ysize - 1; + state->ystep = -1; + } else { + state->ystep = 1; + } + state->state = LINE; } ptr = buf; for (;;) { + if (state->state == SKIP) { + /* Skip padding between lines */ - if (state->state == SKIP) { + if (bytes < rawstate->skip) { + return ptr - buf; + } - /* Skip padding between lines */ + ptr += rawstate->skip; + bytes -= rawstate->skip; - if (bytes < rawstate->skip) - return ptr - buf; + state->state = LINE; + } - ptr += rawstate->skip; - bytes -= rawstate->skip; + if (bytes < state->bytes) { + return ptr - buf; + } - state->state = LINE; + /* Unpack data */ + state->shuffle( + (UINT8 *)im->image[state->y + state->yoff] + state->xoff * im->pixelsize, + ptr, + state->xsize); - } + ptr += state->bytes; + bytes -= state->bytes; - if (bytes < state->bytes) - return ptr - buf; + state->y += state->ystep; - /* Unpack data */ - state->shuffle((UINT8*) im->image[state->y + state->yoff] + - state->xoff * im->pixelsize, ptr, state->xsize); - - ptr += state->bytes; - bytes -= state->bytes; - - state->y += state->ystep; - - if (state->y < 0 || state->y >= state->ysize) { - /* End of file (errcode = 0) */ - return -1; - } - - state->state = SKIP; + if (state->y < 0 || state->y >= state->ysize) { + /* End of file (errcode = 0) */ + return -1; + } + state->state = SKIP; } - } diff --git a/src/libImaging/RawEncode.c b/src/libImaging/RawEncode.c index a3b74b8cf..50de8d982 100644 --- a/src/libImaging/RawEncode.c +++ b/src/libImaging/RawEncode.c @@ -9,81 +9,79 @@ * in ImageFile.py, but it should be solved here instead. * * history: - * 96-04-30 fl created - * 97-01-03 fl fixed padding + * 96-04-30 fl created + * 97-01-03 fl fixed padding * * Copyright (c) Fredrik Lundh 1996-97. * Copyright (c) Secret Labs AB 1997. * * See the README file for information on usage and redistribution. */ - #include "Imaging.h" int -ImagingRawEncode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) -{ - UINT8* ptr; +ImagingRawEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) { + UINT8 *ptr; if (!state->state) { + /* The "count" field holds the stride, if specified. Fix + things up so "bytes" is the full size, and "count" the + packed size */ - /* The "count" field holds the stride, if specified. Fix - things up so "bytes" is the full size, and "count" the - packed size */ + if (state->count > 0) { + int bytes = state->count; - if (state->count > 0) { - int bytes = state->count; + /* stride must not be less than real size */ + if (state->count < state->bytes) { + state->errcode = IMAGING_CODEC_CONFIG; + return -1; + } + state->count = state->bytes; + state->bytes = bytes; + } else { + state->count = state->bytes; + } - /* stride must not be less than real size */ - if (state->count < state->bytes) { - state->errcode = IMAGING_CODEC_CONFIG; - return -1; - } - state->count = state->bytes; - state->bytes = bytes; - } else - state->count = state->bytes; + /* The "ystep" field specifies the orientation */ - /* The "ystep" field specifies the orientation */ - - if (state->ystep < 0) { - state->y = state->ysize-1; - state->ystep = -1; - } else - state->ystep = 1; - - state->state = 1; + if (state->ystep < 0) { + state->y = state->ysize - 1; + state->ystep = -1; + } else { + state->ystep = 1; + } + state->state = 1; } if (bytes < state->bytes) { - state->errcode = IMAGING_CODEC_CONFIG; - return 0; + state->errcode = IMAGING_CODEC_CONFIG; + return 0; } ptr = buf; while (bytes >= state->bytes) { + state->shuffle( + ptr, + (UINT8 *)im->image[state->y + state->yoff] + state->xoff * im->pixelsize, + state->xsize); - state->shuffle(ptr, (UINT8*) im->image[state->y + state->yoff] + - state->xoff * im->pixelsize, state->xsize); + if (state->bytes > state->count) { + /* zero-pad the buffer, if necessary */ + memset(ptr + state->count, 0, state->bytes - state->count); + } - if (state->bytes > state->count) - /* zero-pad the buffer, if necessary */ - memset(ptr + state->count, 0, state->bytes - state->count); + ptr += state->bytes; + bytes -= state->bytes; - ptr += state->bytes; - bytes -= state->bytes; - - state->y += state->ystep; - - if (state->y < 0 || state->y >= state->ysize) { - state->errcode = IMAGING_CODEC_END; - break; - } + state->y += state->ystep; + if (state->y < 0 || state->y >= state->ysize) { + state->errcode = IMAGING_CODEC_END; + break; + } } return ptr - buf; - } diff --git a/src/libImaging/Reduce.c b/src/libImaging/Reduce.c index d6ef92f5b..60928d2bc 100644 --- a/src/libImaging/Reduce.c +++ b/src/libImaging/Reduce.c @@ -2,39 +2,35 @@ #include -#define ROUND_UP(f) ((int) ((f) >= 0.0 ? (f) + 0.5F : (f) - 0.5F)) - +#define ROUND_UP(f) ((int)((f) >= 0.0 ? (f) + 0.5F : (f)-0.5F)) UINT32 -division_UINT32(int divider, int result_bits) -{ +division_UINT32(int divider, int result_bits) { UINT32 max_dividend = (1 << result_bits) * divider; float max_int = (1 << 30) * 4.0; - return (UINT32) (max_int / max_dividend); + return (UINT32)(max_int / max_dividend); } - void -ImagingReduceNxN(Imaging imOut, Imaging imIn, int box[4], int xscale, int yscale) -{ +ImagingReduceNxN(Imaging imOut, Imaging imIn, int box[4], int xscale, int yscale) { /* The most general implementation for any xscale and yscale - */ + */ int x, y, xx, yy; UINT32 multiplier = division_UINT32(yscale * xscale, 8); UINT32 amend = yscale * xscale / 2; if (imIn->image8) { for (y = 0; y < box[3] / yscale; y++) { - int yy_from = box[1] + y*yscale; + int yy_from = box[1] + y * yscale; for (x = 0; x < box[2] / xscale; x++) { - int xx_from = box[0] + x*xscale; + int xx_from = box[0] + x * xscale; UINT32 ss = amend; for (yy = yy_from; yy < yy_from + yscale - 1; yy += 2) { UINT8 *line0 = (UINT8 *)imIn->image8[yy]; UINT8 *line1 = (UINT8 *)imIn->image8[yy + 1]; for (xx = xx_from; xx < xx_from + xscale - 1; xx += 2) { - ss += line0[xx + 0] + line0[xx + 1] + - line1[xx + 0] + line1[xx + 1]; + ss += line0[xx + 0] + line0[xx + 1] + line1[xx + 0] + + line1[xx + 1]; } if (xscale & 0x01) { ss += line0[xx + 0] + line1[xx + 0]; @@ -54,125 +50,128 @@ ImagingReduceNxN(Imaging imOut, Imaging imIn, int box[4], int xscale, int yscale } } else { for (y = 0; y < box[3] / yscale; y++) { - int yy_from = box[1] + y*yscale; + int yy_from = box[1] + y * yscale; if (imIn->bands == 2) { for (x = 0; x < box[2] / xscale; x++) { - int xx_from = box[0] + x*xscale; + int xx_from = box[0] + x * xscale; UINT32 v; UINT32 ss0 = amend, ss3 = amend; for (yy = yy_from; yy < yy_from + yscale - 1; yy += 2) { UINT8 *line0 = (UINT8 *)imIn->image[yy]; UINT8 *line1 = (UINT8 *)imIn->image[yy + 1]; for (xx = xx_from; xx < xx_from + xscale - 1; xx += 2) { - ss0 += line0[xx*4 + 0] + line0[xx*4 + 4] + - line1[xx*4 + 0] + line1[xx*4 + 4]; - ss3 += line0[xx*4 + 3] + line0[xx*4 + 7] + - line1[xx*4 + 3] + line1[xx*4 + 7]; + ss0 += line0[xx * 4 + 0] + line0[xx * 4 + 4] + + line1[xx * 4 + 0] + line1[xx * 4 + 4]; + ss3 += line0[xx * 4 + 3] + line0[xx * 4 + 7] + + line1[xx * 4 + 3] + line1[xx * 4 + 7]; } if (xscale & 0x01) { - ss0 += line0[xx*4 + 0] + line1[xx*4 + 0]; - ss3 += line0[xx*4 + 3] + line1[xx*4 + 3]; + ss0 += line0[xx * 4 + 0] + line1[xx * 4 + 0]; + ss3 += line0[xx * 4 + 3] + line1[xx * 4 + 3]; } } if (yscale & 0x01) { UINT8 *line = (UINT8 *)imIn->image[yy]; for (xx = xx_from; xx < xx_from + xscale - 1; xx += 2) { - ss0 += line[xx*4 + 0] + line[xx*4 + 4]; - ss3 += line[xx*4 + 3] + line[xx*4 + 7]; + ss0 += line[xx * 4 + 0] + line[xx * 4 + 4]; + ss3 += line[xx * 4 + 3] + line[xx * 4 + 7]; } if (xscale & 0x01) { - ss0 += line[xx*4 + 0]; - ss3 += line[xx*4 + 3]; + ss0 += line[xx * 4 + 0]; + ss3 += line[xx * 4 + 3]; } } v = MAKE_UINT32( - (ss0 * multiplier) >> 24, 0, - 0, (ss3 * multiplier) >> 24); + (ss0 * multiplier) >> 24, 0, 0, (ss3 * multiplier) >> 24); memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v)); } } else if (imIn->bands == 3) { for (x = 0; x < box[2] / xscale; x++) { - int xx_from = box[0] + x*xscale; + int xx_from = box[0] + x * xscale; UINT32 v; UINT32 ss0 = amend, ss1 = amend, ss2 = amend; for (yy = yy_from; yy < yy_from + yscale - 1; yy += 2) { UINT8 *line0 = (UINT8 *)imIn->image[yy]; UINT8 *line1 = (UINT8 *)imIn->image[yy + 1]; for (xx = xx_from; xx < xx_from + xscale - 1; xx += 2) { - ss0 += line0[xx*4 + 0] + line0[xx*4 + 4] + - line1[xx*4 + 0] + line1[xx*4 + 4]; - ss1 += line0[xx*4 + 1] + line0[xx*4 + 5] + - line1[xx*4 + 1] + line1[xx*4 + 5]; - ss2 += line0[xx*4 + 2] + line0[xx*4 + 6] + - line1[xx*4 + 2] + line1[xx*4 + 6]; + ss0 += line0[xx * 4 + 0] + line0[xx * 4 + 4] + + line1[xx * 4 + 0] + line1[xx * 4 + 4]; + ss1 += line0[xx * 4 + 1] + line0[xx * 4 + 5] + + line1[xx * 4 + 1] + line1[xx * 4 + 5]; + ss2 += line0[xx * 4 + 2] + line0[xx * 4 + 6] + + line1[xx * 4 + 2] + line1[xx * 4 + 6]; } if (xscale & 0x01) { - ss0 += line0[xx*4 + 0] + line1[xx*4 + 0]; - ss1 += line0[xx*4 + 1] + line1[xx*4 + 1]; - ss2 += line0[xx*4 + 2] + line1[xx*4 + 2]; + ss0 += line0[xx * 4 + 0] + line1[xx * 4 + 0]; + ss1 += line0[xx * 4 + 1] + line1[xx * 4 + 1]; + ss2 += line0[xx * 4 + 2] + line1[xx * 4 + 2]; } } if (yscale & 0x01) { UINT8 *line = (UINT8 *)imIn->image[yy]; for (xx = xx_from; xx < xx_from + xscale - 1; xx += 2) { - ss0 += line[xx*4 + 0] + line[xx*4 + 4]; - ss1 += line[xx*4 + 1] + line[xx*4 + 5]; - ss2 += line[xx*4 + 2] + line[xx*4 + 6]; + ss0 += line[xx * 4 + 0] + line[xx * 4 + 4]; + ss1 += line[xx * 4 + 1] + line[xx * 4 + 5]; + ss2 += line[xx * 4 + 2] + line[xx * 4 + 6]; } if (xscale & 0x01) { - ss0 += line[xx*4 + 0]; - ss1 += line[xx*4 + 1]; - ss2 += line[xx*4 + 2]; + ss0 += line[xx * 4 + 0]; + ss1 += line[xx * 4 + 1]; + ss2 += line[xx * 4 + 2]; } } v = MAKE_UINT32( - (ss0 * multiplier) >> 24, (ss1 * multiplier) >> 24, - (ss2 * multiplier) >> 24, 0); + (ss0 * multiplier) >> 24, + (ss1 * multiplier) >> 24, + (ss2 * multiplier) >> 24, + 0); memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v)); } } else { // bands == 4 for (x = 0; x < box[2] / xscale; x++) { - int xx_from = box[0] + x*xscale; + int xx_from = box[0] + x * xscale; UINT32 v; UINT32 ss0 = amend, ss1 = amend, ss2 = amend, ss3 = amend; for (yy = yy_from; yy < yy_from + yscale - 1; yy += 2) { UINT8 *line0 = (UINT8 *)imIn->image[yy]; UINT8 *line1 = (UINT8 *)imIn->image[yy + 1]; for (xx = xx_from; xx < xx_from + xscale - 1; xx += 2) { - ss0 += line0[xx*4 + 0] + line0[xx*4 + 4] + - line1[xx*4 + 0] + line1[xx*4 + 4]; - ss1 += line0[xx*4 + 1] + line0[xx*4 + 5] + - line1[xx*4 + 1] + line1[xx*4 + 5]; - ss2 += line0[xx*4 + 2] + line0[xx*4 + 6] + - line1[xx*4 + 2] + line1[xx*4 + 6]; - ss3 += line0[xx*4 + 3] + line0[xx*4 + 7] + - line1[xx*4 + 3] + line1[xx*4 + 7]; + ss0 += line0[xx * 4 + 0] + line0[xx * 4 + 4] + + line1[xx * 4 + 0] + line1[xx * 4 + 4]; + ss1 += line0[xx * 4 + 1] + line0[xx * 4 + 5] + + line1[xx * 4 + 1] + line1[xx * 4 + 5]; + ss2 += line0[xx * 4 + 2] + line0[xx * 4 + 6] + + line1[xx * 4 + 2] + line1[xx * 4 + 6]; + ss3 += line0[xx * 4 + 3] + line0[xx * 4 + 7] + + line1[xx * 4 + 3] + line1[xx * 4 + 7]; } if (xscale & 0x01) { - ss0 += line0[xx*4 + 0] + line1[xx*4 + 0]; - ss1 += line0[xx*4 + 1] + line1[xx*4 + 1]; - ss2 += line0[xx*4 + 2] + line1[xx*4 + 2]; - ss3 += line0[xx*4 + 3] + line1[xx*4 + 3]; + ss0 += line0[xx * 4 + 0] + line1[xx * 4 + 0]; + ss1 += line0[xx * 4 + 1] + line1[xx * 4 + 1]; + ss2 += line0[xx * 4 + 2] + line1[xx * 4 + 2]; + ss3 += line0[xx * 4 + 3] + line1[xx * 4 + 3]; } } if (yscale & 0x01) { UINT8 *line = (UINT8 *)imIn->image[yy]; for (xx = xx_from; xx < xx_from + xscale - 1; xx += 2) { - ss0 += line[xx*4 + 0] + line[xx*4 + 4]; - ss1 += line[xx*4 + 1] + line[xx*4 + 5]; - ss2 += line[xx*4 + 2] + line[xx*4 + 6]; - ss3 += line[xx*4 + 3] + line[xx*4 + 7]; + ss0 += line[xx * 4 + 0] + line[xx * 4 + 4]; + ss1 += line[xx * 4 + 1] + line[xx * 4 + 5]; + ss2 += line[xx * 4 + 2] + line[xx * 4 + 6]; + ss3 += line[xx * 4 + 3] + line[xx * 4 + 7]; } if (xscale & 0x01) { - ss0 += line[xx*4 + 0]; - ss1 += line[xx*4 + 1]; - ss2 += line[xx*4 + 2]; - ss3 += line[xx*4 + 3]; + ss0 += line[xx * 4 + 0]; + ss1 += line[xx * 4 + 1]; + ss2 += line[xx * 4 + 2]; + ss3 += line[xx * 4 + 3]; } } v = MAKE_UINT32( - (ss0 * multiplier) >> 24, (ss1 * multiplier) >> 24, - (ss2 * multiplier) >> 24, (ss3 * multiplier) >> 24); + (ss0 * multiplier) >> 24, + (ss1 * multiplier) >> 24, + (ss2 * multiplier) >> 24, + (ss3 * multiplier) >> 24); memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v)); } } @@ -180,12 +179,10 @@ ImagingReduceNxN(Imaging imOut, Imaging imIn, int box[4], int xscale, int yscale } } - void -ImagingReduce1xN(Imaging imOut, Imaging imIn, int box[4], int yscale) -{ +ImagingReduce1xN(Imaging imOut, Imaging imIn, int box[4], int yscale) { /* Optimized implementation for xscale = 1. - */ + */ int x, y, yy; int xscale = 1; UINT32 multiplier = division_UINT32(yscale * xscale, 8); @@ -193,9 +190,9 @@ ImagingReduce1xN(Imaging imOut, Imaging imIn, int box[4], int yscale) if (imIn->image8) { for (y = 0; y < box[3] / yscale; y++) { - int yy_from = box[1] + y*yscale; + int yy_from = box[1] + y * yscale; for (x = 0; x < box[2] / xscale; x++) { - int xx = box[0] + x*xscale; + int xx = box[0] + x * xscale; UINT32 ss = amend; for (yy = yy_from; yy < yy_from + yscale - 1; yy += 2) { UINT8 *line0 = (UINT8 *)imIn->image8[yy]; @@ -211,74 +208,77 @@ ImagingReduce1xN(Imaging imOut, Imaging imIn, int box[4], int yscale) } } else { for (y = 0; y < box[3] / yscale; y++) { - int yy_from = box[1] + y*yscale; + int yy_from = box[1] + y * yscale; if (imIn->bands == 2) { for (x = 0; x < box[2] / xscale; x++) { - int xx = box[0] + x*xscale; + int xx = box[0] + x * xscale; UINT32 v; UINT32 ss0 = amend, ss3 = amend; for (yy = yy_from; yy < yy_from + yscale - 1; yy += 2) { UINT8 *line0 = (UINT8 *)imIn->image[yy]; UINT8 *line1 = (UINT8 *)imIn->image[yy + 1]; - ss0 += line0[xx*4 + 0] + line1[xx*4 + 0]; - ss3 += line0[xx*4 + 3] + line1[xx*4 + 3]; + ss0 += line0[xx * 4 + 0] + line1[xx * 4 + 0]; + ss3 += line0[xx * 4 + 3] + line1[xx * 4 + 3]; } if (yscale & 0x01) { UINT8 *line = (UINT8 *)imIn->image[yy]; - ss0 += line[xx*4 + 0]; - ss3 += line[xx*4 + 3]; + ss0 += line[xx * 4 + 0]; + ss3 += line[xx * 4 + 3]; } v = MAKE_UINT32( - (ss0 * multiplier) >> 24, 0, - 0, (ss3 * multiplier) >> 24); + (ss0 * multiplier) >> 24, 0, 0, (ss3 * multiplier) >> 24); memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v)); } } else if (imIn->bands == 3) { for (x = 0; x < box[2] / xscale; x++) { - int xx = box[0] + x*xscale; + int xx = box[0] + x * xscale; UINT32 v; UINT32 ss0 = amend, ss1 = amend, ss2 = amend; for (yy = yy_from; yy < yy_from + yscale - 1; yy += 2) { UINT8 *line0 = (UINT8 *)imIn->image[yy]; UINT8 *line1 = (UINT8 *)imIn->image[yy + 1]; - ss0 += line0[xx*4 + 0] + line1[xx*4 + 0]; - ss1 += line0[xx*4 + 1] + line1[xx*4 + 1]; - ss2 += line0[xx*4 + 2] + line1[xx*4 + 2]; + ss0 += line0[xx * 4 + 0] + line1[xx * 4 + 0]; + ss1 += line0[xx * 4 + 1] + line1[xx * 4 + 1]; + ss2 += line0[xx * 4 + 2] + line1[xx * 4 + 2]; } if (yscale & 0x01) { UINT8 *line = (UINT8 *)imIn->image[yy]; - ss0 += line[xx*4 + 0]; - ss1 += line[xx*4 + 1]; - ss2 += line[xx*4 + 2]; + ss0 += line[xx * 4 + 0]; + ss1 += line[xx * 4 + 1]; + ss2 += line[xx * 4 + 2]; } v = MAKE_UINT32( - (ss0 * multiplier) >> 24, (ss1 * multiplier) >> 24, - (ss2 * multiplier) >> 24, 0); + (ss0 * multiplier) >> 24, + (ss1 * multiplier) >> 24, + (ss2 * multiplier) >> 24, + 0); memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v)); } } else { // bands == 4 for (x = 0; x < box[2] / xscale; x++) { - int xx = box[0] + x*xscale; + int xx = box[0] + x * xscale; UINT32 v; UINT32 ss0 = amend, ss1 = amend, ss2 = amend, ss3 = amend; for (yy = yy_from; yy < yy_from + yscale - 1; yy += 2) { UINT8 *line0 = (UINT8 *)imIn->image[yy]; UINT8 *line1 = (UINT8 *)imIn->image[yy + 1]; - ss0 += line0[xx*4 + 0] + line1[xx*4 + 0]; - ss1 += line0[xx*4 + 1] + line1[xx*4 + 1]; - ss2 += line0[xx*4 + 2] + line1[xx*4 + 2]; - ss3 += line0[xx*4 + 3] + line1[xx*4 + 3]; + ss0 += line0[xx * 4 + 0] + line1[xx * 4 + 0]; + ss1 += line0[xx * 4 + 1] + line1[xx * 4 + 1]; + ss2 += line0[xx * 4 + 2] + line1[xx * 4 + 2]; + ss3 += line0[xx * 4 + 3] + line1[xx * 4 + 3]; } if (yscale & 0x01) { UINT8 *line = (UINT8 *)imIn->image[yy]; - ss0 += line[xx*4 + 0]; - ss1 += line[xx*4 + 1]; - ss2 += line[xx*4 + 2]; - ss3 += line[xx*4 + 3]; + ss0 += line[xx * 4 + 0]; + ss1 += line[xx * 4 + 1]; + ss2 += line[xx * 4 + 2]; + ss3 += line[xx * 4 + 3]; } v = MAKE_UINT32( - (ss0 * multiplier) >> 24, (ss1 * multiplier) >> 24, - (ss2 * multiplier) >> 24, (ss3 * multiplier) >> 24); + (ss0 * multiplier) >> 24, + (ss1 * multiplier) >> 24, + (ss2 * multiplier) >> 24, + (ss3 * multiplier) >> 24); memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v)); } } @@ -286,12 +286,10 @@ ImagingReduce1xN(Imaging imOut, Imaging imIn, int box[4], int yscale) } } - void -ImagingReduceNx1(Imaging imOut, Imaging imIn, int box[4], int xscale) -{ +ImagingReduceNx1(Imaging imOut, Imaging imIn, int box[4], int xscale) { /* Optimized implementation for yscale = 1. - */ + */ int x, y, xx; int yscale = 1; UINT32 multiplier = division_UINT32(yscale * xscale, 8); @@ -299,10 +297,10 @@ ImagingReduceNx1(Imaging imOut, Imaging imIn, int box[4], int xscale) if (imIn->image8) { for (y = 0; y < box[3] / yscale; y++) { - int yy = box[1] + y*yscale; + int yy = box[1] + y * yscale; UINT8 *line = (UINT8 *)imIn->image8[yy]; for (x = 0; x < box[2] / xscale; x++) { - int xx_from = box[0] + x*xscale; + int xx_from = box[0] + x * xscale; UINT32 ss = amend; for (xx = xx_from; xx < xx_from + xscale - 1; xx += 2) { ss += line[xx + 0] + line[xx + 1]; @@ -315,66 +313,69 @@ ImagingReduceNx1(Imaging imOut, Imaging imIn, int box[4], int xscale) } } else { for (y = 0; y < box[3] / yscale; y++) { - int yy = box[1] + y*yscale; + int yy = box[1] + y * yscale; UINT8 *line = (UINT8 *)imIn->image[yy]; if (imIn->bands == 2) { for (x = 0; x < box[2] / xscale; x++) { - int xx_from = box[0] + x*xscale; + int xx_from = box[0] + x * xscale; UINT32 v; UINT32 ss0 = amend, ss3 = amend; for (xx = xx_from; xx < xx_from + xscale - 1; xx += 2) { - ss0 += line[xx*4 + 0] + line[xx*4 + 4]; - ss3 += line[xx*4 + 3] + line[xx*4 + 7]; + ss0 += line[xx * 4 + 0] + line[xx * 4 + 4]; + ss3 += line[xx * 4 + 3] + line[xx * 4 + 7]; } if (xscale & 0x01) { - ss0 += line[xx*4 + 0]; - ss3 += line[xx*4 + 3]; + ss0 += line[xx * 4 + 0]; + ss3 += line[xx * 4 + 3]; } v = MAKE_UINT32( - (ss0 * multiplier) >> 24, 0, - 0, (ss3 * multiplier) >> 24); + (ss0 * multiplier) >> 24, 0, 0, (ss3 * multiplier) >> 24); memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v)); } } else if (imIn->bands == 3) { for (x = 0; x < box[2] / xscale; x++) { - int xx_from = box[0] + x*xscale; + int xx_from = box[0] + x * xscale; UINT32 v; UINT32 ss0 = amend, ss1 = amend, ss2 = amend; for (xx = xx_from; xx < xx_from + xscale - 1; xx += 2) { - ss0 += line[xx*4 + 0] + line[xx*4 + 4]; - ss1 += line[xx*4 + 1] + line[xx*4 + 5]; - ss2 += line[xx*4 + 2] + line[xx*4 + 6]; + ss0 += line[xx * 4 + 0] + line[xx * 4 + 4]; + ss1 += line[xx * 4 + 1] + line[xx * 4 + 5]; + ss2 += line[xx * 4 + 2] + line[xx * 4 + 6]; } if (xscale & 0x01) { - ss0 += line[xx*4 + 0]; - ss1 += line[xx*4 + 1]; - ss2 += line[xx*4 + 2]; + ss0 += line[xx * 4 + 0]; + ss1 += line[xx * 4 + 1]; + ss2 += line[xx * 4 + 2]; } v = MAKE_UINT32( - (ss0 * multiplier) >> 24, (ss1 * multiplier) >> 24, - (ss2 * multiplier) >> 24, 0); + (ss0 * multiplier) >> 24, + (ss1 * multiplier) >> 24, + (ss2 * multiplier) >> 24, + 0); memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v)); } } else { // bands == 4 for (x = 0; x < box[2] / xscale; x++) { - int xx_from = box[0] + x*xscale; + int xx_from = box[0] + x * xscale; UINT32 v; UINT32 ss0 = amend, ss1 = amend, ss2 = amend, ss3 = amend; for (xx = xx_from; xx < xx_from + xscale - 1; xx += 2) { - ss0 += line[xx*4 + 0] + line[xx*4 + 4]; - ss1 += line[xx*4 + 1] + line[xx*4 + 5]; - ss2 += line[xx*4 + 2] + line[xx*4 + 6]; - ss3 += line[xx*4 + 3] + line[xx*4 + 7]; + ss0 += line[xx * 4 + 0] + line[xx * 4 + 4]; + ss1 += line[xx * 4 + 1] + line[xx * 4 + 5]; + ss2 += line[xx * 4 + 2] + line[xx * 4 + 6]; + ss3 += line[xx * 4 + 3] + line[xx * 4 + 7]; } if (xscale & 0x01) { - ss0 += line[xx*4 + 0]; - ss1 += line[xx*4 + 1]; - ss2 += line[xx*4 + 2]; - ss3 += line[xx*4 + 3]; + ss0 += line[xx * 4 + 0]; + ss1 += line[xx * 4 + 1]; + ss2 += line[xx * 4 + 2]; + ss3 += line[xx * 4 + 3]; } v = MAKE_UINT32( - (ss0 * multiplier) >> 24, (ss1 * multiplier) >> 24, - (ss2 * multiplier) >> 24, (ss3 * multiplier) >> 24); + (ss0 * multiplier) >> 24, + (ss1 * multiplier) >> 24, + (ss2 * multiplier) >> 24, + (ss3 * multiplier) >> 24); memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v)); } } @@ -383,10 +384,9 @@ ImagingReduceNx1(Imaging imOut, Imaging imIn, int box[4], int xscale) } void -ImagingReduce1x2(Imaging imOut, Imaging imIn, int box[4]) -{ +ImagingReduce1x2(Imaging imOut, Imaging imIn, int box[4]) { /* Optimized implementation for xscale = 1 and yscale = 2. - */ + */ int xscale = 1, yscale = 2; int x, y; UINT32 ss0, ss1, ss2, ss3; @@ -394,61 +394,53 @@ ImagingReduce1x2(Imaging imOut, Imaging imIn, int box[4]) if (imIn->image8) { for (y = 0; y < box[3] / yscale; y++) { - int yy = box[1] + y*yscale; + int yy = box[1] + y * yscale; UINT8 *line0 = (UINT8 *)imIn->image8[yy + 0]; UINT8 *line1 = (UINT8 *)imIn->image8[yy + 1]; for (x = 0; x < box[2] / xscale; x++) { - int xx = box[0] + x*xscale; - ss0 = line0[xx + 0] + - line1[xx + 0]; + int xx = box[0] + x * xscale; + ss0 = line0[xx + 0] + line1[xx + 0]; imOut->image8[y][x] = (ss0 + amend) >> 1; } } } else { for (y = 0; y < box[3] / yscale; y++) { - int yy = box[1] + y*yscale; + int yy = box[1] + y * yscale; UINT8 *line0 = (UINT8 *)imIn->image[yy + 0]; UINT8 *line1 = (UINT8 *)imIn->image[yy + 1]; if (imIn->bands == 2) { for (x = 0; x < box[2] / xscale; x++) { - int xx = box[0] + x*xscale; + int xx = box[0] + x * xscale; UINT32 v; - ss0 = line0[xx*4 + 0] + - line1[xx*4 + 0]; - ss3 = line0[xx*4 + 3] + - line1[xx*4 + 3]; - v = MAKE_UINT32((ss0 + amend) >> 1, 0, - 0, (ss3 + amend) >> 1); + ss0 = line0[xx * 4 + 0] + line1[xx * 4 + 0]; + ss3 = line0[xx * 4 + 3] + line1[xx * 4 + 3]; + v = MAKE_UINT32((ss0 + amend) >> 1, 0, 0, (ss3 + amend) >> 1); memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v)); } } else if (imIn->bands == 3) { for (x = 0; x < box[2] / xscale; x++) { - int xx = box[0] + x*xscale; + int xx = box[0] + x * xscale; UINT32 v; - ss0 = line0[xx*4 + 0] + - line1[xx*4 + 0]; - ss1 = line0[xx*4 + 1] + - line1[xx*4 + 1]; - ss2 = line0[xx*4 + 2] + - line1[xx*4 + 2]; - v = MAKE_UINT32((ss0 + amend) >> 1, (ss1 + amend) >> 1, - (ss2 + amend) >> 1, 0); + ss0 = line0[xx * 4 + 0] + line1[xx * 4 + 0]; + ss1 = line0[xx * 4 + 1] + line1[xx * 4 + 1]; + ss2 = line0[xx * 4 + 2] + line1[xx * 4 + 2]; + v = MAKE_UINT32( + (ss0 + amend) >> 1, (ss1 + amend) >> 1, (ss2 + amend) >> 1, 0); memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v)); } } else { // bands == 4 for (x = 0; x < box[2] / xscale; x++) { - int xx = box[0] + x*xscale; + int xx = box[0] + x * xscale; UINT32 v; - ss0 = line0[xx*4 + 0] + - line1[xx*4 + 0]; - ss1 = line0[xx*4 + 1] + - line1[xx*4 + 1]; - ss2 = line0[xx*4 + 2] + - line1[xx*4 + 2]; - ss3 = line0[xx*4 + 3] + - line1[xx*4 + 3]; - v = MAKE_UINT32((ss0 + amend) >> 1, (ss1 + amend) >> 1, - (ss2 + amend) >> 1, (ss3 + amend) >> 1); + ss0 = line0[xx * 4 + 0] + line1[xx * 4 + 0]; + ss1 = line0[xx * 4 + 1] + line1[xx * 4 + 1]; + ss2 = line0[xx * 4 + 2] + line1[xx * 4 + 2]; + ss3 = line0[xx * 4 + 3] + line1[xx * 4 + 3]; + v = MAKE_UINT32( + (ss0 + amend) >> 1, + (ss1 + amend) >> 1, + (ss2 + amend) >> 1, + (ss3 + amend) >> 1); memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v)); } } @@ -456,12 +448,10 @@ ImagingReduce1x2(Imaging imOut, Imaging imIn, int box[4]) } } - void -ImagingReduce2x1(Imaging imOut, Imaging imIn, int box[4]) -{ +ImagingReduce2x1(Imaging imOut, Imaging imIn, int box[4]) { /* Optimized implementation for xscale = 2 and yscale = 1. - */ + */ int xscale = 2, yscale = 1; int x, y; UINT32 ss0, ss1, ss2, ss3; @@ -469,49 +459,51 @@ ImagingReduce2x1(Imaging imOut, Imaging imIn, int box[4]) if (imIn->image8) { for (y = 0; y < box[3] / yscale; y++) { - int yy = box[1] + y*yscale; + int yy = box[1] + y * yscale; UINT8 *line0 = (UINT8 *)imIn->image8[yy + 0]; for (x = 0; x < box[2] / xscale; x++) { - int xx = box[0] + x*xscale; + int xx = box[0] + x * xscale; ss0 = line0[xx + 0] + line0[xx + 1]; imOut->image8[y][x] = (ss0 + amend) >> 1; } } } else { for (y = 0; y < box[3] / yscale; y++) { - int yy = box[1] + y*yscale; + int yy = box[1] + y * yscale; UINT8 *line0 = (UINT8 *)imIn->image[yy + 0]; if (imIn->bands == 2) { for (x = 0; x < box[2] / xscale; x++) { - int xx = box[0] + x*xscale; + int xx = box[0] + x * xscale; UINT32 v; - ss0 = line0[xx*4 + 0] + line0[xx*4 + 4]; - ss3 = line0[xx*4 + 3] + line0[xx*4 + 7]; - v = MAKE_UINT32((ss0 + amend) >> 1, 0, - 0, (ss3 + amend) >> 1); + ss0 = line0[xx * 4 + 0] + line0[xx * 4 + 4]; + ss3 = line0[xx * 4 + 3] + line0[xx * 4 + 7]; + v = MAKE_UINT32((ss0 + amend) >> 1, 0, 0, (ss3 + amend) >> 1); memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v)); } } else if (imIn->bands == 3) { for (x = 0; x < box[2] / xscale; x++) { - int xx = box[0] + x*xscale; + int xx = box[0] + x * xscale; UINT32 v; - ss0 = line0[xx*4 + 0] + line0[xx*4 + 4]; - ss1 = line0[xx*4 + 1] + line0[xx*4 + 5]; - ss2 = line0[xx*4 + 2] + line0[xx*4 + 6]; - v = MAKE_UINT32((ss0 + amend) >> 1, (ss1 + amend) >> 1, - (ss2 + amend) >> 1, 0); + ss0 = line0[xx * 4 + 0] + line0[xx * 4 + 4]; + ss1 = line0[xx * 4 + 1] + line0[xx * 4 + 5]; + ss2 = line0[xx * 4 + 2] + line0[xx * 4 + 6]; + v = MAKE_UINT32( + (ss0 + amend) >> 1, (ss1 + amend) >> 1, (ss2 + amend) >> 1, 0); memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v)); } } else { // bands == 4 for (x = 0; x < box[2] / xscale; x++) { - int xx = box[0] + x*xscale; + int xx = box[0] + x * xscale; UINT32 v; - ss0 = line0[xx*4 + 0] + line0[xx*4 + 4]; - ss1 = line0[xx*4 + 1] + line0[xx*4 + 5]; - ss2 = line0[xx*4 + 2] + line0[xx*4 + 6]; - ss3 = line0[xx*4 + 3] + line0[xx*4 + 7]; - v = MAKE_UINT32((ss0 + amend) >> 1, (ss1 + amend) >> 1, - (ss2 + amend) >> 1, (ss3 + amend) >> 1); + ss0 = line0[xx * 4 + 0] + line0[xx * 4 + 4]; + ss1 = line0[xx * 4 + 1] + line0[xx * 4 + 5]; + ss2 = line0[xx * 4 + 2] + line0[xx * 4 + 6]; + ss3 = line0[xx * 4 + 3] + line0[xx * 4 + 7]; + v = MAKE_UINT32( + (ss0 + amend) >> 1, + (ss1 + amend) >> 1, + (ss2 + amend) >> 1, + (ss3 + amend) >> 1); memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v)); } } @@ -519,12 +511,10 @@ ImagingReduce2x1(Imaging imOut, Imaging imIn, int box[4]) } } - void -ImagingReduce2x2(Imaging imOut, Imaging imIn, int box[4]) -{ +ImagingReduce2x2(Imaging imOut, Imaging imIn, int box[4]) { /* Optimized implementation for xscale = 2 and yscale = 2. - */ + */ int xscale = 2, yscale = 2; int x, y; UINT32 ss0, ss1, ss2, ss3; @@ -532,61 +522,62 @@ ImagingReduce2x2(Imaging imOut, Imaging imIn, int box[4]) if (imIn->image8) { for (y = 0; y < box[3] / yscale; y++) { - int yy = box[1] + y*yscale; + int yy = box[1] + y * yscale; UINT8 *line0 = (UINT8 *)imIn->image8[yy + 0]; UINT8 *line1 = (UINT8 *)imIn->image8[yy + 1]; for (x = 0; x < box[2] / xscale; x++) { - int xx = box[0] + x*xscale; - ss0 = line0[xx + 0] + line0[xx + 1] + - line1[xx + 0] + line1[xx + 1]; + int xx = box[0] + x * xscale; + ss0 = line0[xx + 0] + line0[xx + 1] + line1[xx + 0] + line1[xx + 1]; imOut->image8[y][x] = (ss0 + amend) >> 2; } } } else { for (y = 0; y < box[3] / yscale; y++) { - int yy = box[1] + y*yscale; + int yy = box[1] + y * yscale; UINT8 *line0 = (UINT8 *)imIn->image[yy + 0]; UINT8 *line1 = (UINT8 *)imIn->image[yy + 1]; if (imIn->bands == 2) { for (x = 0; x < box[2] / xscale; x++) { - int xx = box[0] + x*xscale; + int xx = box[0] + x * xscale; UINT32 v; - ss0 = line0[xx*4 + 0] + line0[xx*4 + 4] + - line1[xx*4 + 0] + line1[xx*4 + 4]; - ss3 = line0[xx*4 + 3] + line0[xx*4 + 7] + - line1[xx*4 + 3] + line1[xx*4 + 7]; - v = MAKE_UINT32((ss0 + amend) >> 2, 0, - 0, (ss3 + amend) >> 2); + ss0 = line0[xx * 4 + 0] + line0[xx * 4 + 4] + line1[xx * 4 + 0] + + line1[xx * 4 + 4]; + ss3 = line0[xx * 4 + 3] + line0[xx * 4 + 7] + line1[xx * 4 + 3] + + line1[xx * 4 + 7]; + v = MAKE_UINT32((ss0 + amend) >> 2, 0, 0, (ss3 + amend) >> 2); memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v)); } } else if (imIn->bands == 3) { for (x = 0; x < box[2] / xscale; x++) { - int xx = box[0] + x*xscale; + int xx = box[0] + x * xscale; UINT32 v; - ss0 = line0[xx*4 + 0] + line0[xx*4 + 4] + - line1[xx*4 + 0] + line1[xx*4 + 4]; - ss1 = line0[xx*4 + 1] + line0[xx*4 + 5] + - line1[xx*4 + 1] + line1[xx*4 + 5]; - ss2 = line0[xx*4 + 2] + line0[xx*4 + 6] + - line1[xx*4 + 2] + line1[xx*4 + 6]; - v = MAKE_UINT32((ss0 + amend) >> 2, (ss1 + amend) >> 2, - (ss2 + amend) >> 2, 0); + ss0 = line0[xx * 4 + 0] + line0[xx * 4 + 4] + line1[xx * 4 + 0] + + line1[xx * 4 + 4]; + ss1 = line0[xx * 4 + 1] + line0[xx * 4 + 5] + line1[xx * 4 + 1] + + line1[xx * 4 + 5]; + ss2 = line0[xx * 4 + 2] + line0[xx * 4 + 6] + line1[xx * 4 + 2] + + line1[xx * 4 + 6]; + v = MAKE_UINT32( + (ss0 + amend) >> 2, (ss1 + amend) >> 2, (ss2 + amend) >> 2, 0); memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v)); } } else { // bands == 4 for (x = 0; x < box[2] / xscale; x++) { - int xx = box[0] + x*xscale; + int xx = box[0] + x * xscale; UINT32 v; - ss0 = line0[xx*4 + 0] + line0[xx*4 + 4] + - line1[xx*4 + 0] + line1[xx*4 + 4]; - ss1 = line0[xx*4 + 1] + line0[xx*4 + 5] + - line1[xx*4 + 1] + line1[xx*4 + 5]; - ss2 = line0[xx*4 + 2] + line0[xx*4 + 6] + - line1[xx*4 + 2] + line1[xx*4 + 6]; - ss3 = line0[xx*4 + 3] + line0[xx*4 + 7] + - line1[xx*4 + 3] + line1[xx*4 + 7]; - v = MAKE_UINT32((ss0 + amend) >> 2, (ss1 + amend) >> 2, - (ss2 + amend) >> 2, (ss3 + amend) >> 2); + ss0 = line0[xx * 4 + 0] + line0[xx * 4 + 4] + line1[xx * 4 + 0] + + line1[xx * 4 + 4]; + ss1 = line0[xx * 4 + 1] + line0[xx * 4 + 5] + line1[xx * 4 + 1] + + line1[xx * 4 + 5]; + ss2 = line0[xx * 4 + 2] + line0[xx * 4 + 6] + line1[xx * 4 + 2] + + line1[xx * 4 + 6]; + ss3 = line0[xx * 4 + 3] + line0[xx * 4 + 7] + line1[xx * 4 + 3] + + line1[xx * 4 + 7]; + v = MAKE_UINT32( + (ss0 + amend) >> 2, + (ss1 + amend) >> 2, + (ss2 + amend) >> 2, + (ss3 + amend) >> 2); memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v)); } } @@ -594,12 +585,10 @@ ImagingReduce2x2(Imaging imOut, Imaging imIn, int box[4]) } } - void -ImagingReduce1x3(Imaging imOut, Imaging imIn, int box[4]) -{ +ImagingReduce1x3(Imaging imOut, Imaging imIn, int box[4]) { /* Optimized implementation for xscale = 1 and yscale = 3. - */ + */ int xscale = 1, yscale = 3; int x, y; UINT32 ss0, ss1, ss2, ss3; @@ -608,76 +597,62 @@ ImagingReduce1x3(Imaging imOut, Imaging imIn, int box[4]) if (imIn->image8) { for (y = 0; y < box[3] / yscale; y++) { - int yy = box[1] + y*yscale; + int yy = box[1] + y * yscale; UINT8 *line0 = (UINT8 *)imIn->image8[yy + 0]; UINT8 *line1 = (UINT8 *)imIn->image8[yy + 1]; UINT8 *line2 = (UINT8 *)imIn->image8[yy + 2]; for (x = 0; x < box[2] / xscale; x++) { - int xx = box[0] + x*xscale; - ss0 = line0[xx + 0] + - line1[xx + 0] + - line2[xx + 0]; + int xx = box[0] + x * xscale; + ss0 = line0[xx + 0] + line1[xx + 0] + line2[xx + 0]; imOut->image8[y][x] = ((ss0 + amend) * multiplier) >> 24; } } } else { for (y = 0; y < box[3] / yscale; y++) { - int yy = box[1] + y*yscale; + int yy = box[1] + y * yscale; UINT8 *line0 = (UINT8 *)imIn->image[yy + 0]; UINT8 *line1 = (UINT8 *)imIn->image[yy + 1]; UINT8 *line2 = (UINT8 *)imIn->image[yy + 2]; if (imIn->bands == 2) { for (x = 0; x < box[2] / xscale; x++) { - int xx = box[0] + x*xscale; + int xx = box[0] + x * xscale; UINT32 v; - ss0 = line0[xx*4 + 0] + - line1[xx*4 + 0] + - line2[xx*4 + 0]; - ss3 = line0[xx*4 + 3] + - line1[xx*4 + 3] + - line2[xx*4 + 3]; + ss0 = line0[xx * 4 + 0] + line1[xx * 4 + 0] + line2[xx * 4 + 0]; + ss3 = line0[xx * 4 + 3] + line1[xx * 4 + 3] + line2[xx * 4 + 3]; v = MAKE_UINT32( - ((ss0 + amend) * multiplier) >> 24, 0, - 0, ((ss3 + amend) * multiplier) >> 24); + ((ss0 + amend) * multiplier) >> 24, + 0, + 0, + ((ss3 + amend) * multiplier) >> 24); memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v)); } } else if (imIn->bands == 3) { for (x = 0; x < box[2] / xscale; x++) { - int xx = box[0] + x*xscale; + int xx = box[0] + x * xscale; UINT32 v; - ss0 = line0[xx*4 + 0] + - line1[xx*4 + 0] + - line2[xx*4 + 0]; - ss1 = line0[xx*4 + 1] + - line1[xx*4 + 1] + - line2[xx*4 + 1]; - ss2 = line0[xx*4 + 2] + - line1[xx*4 + 2] + - line2[xx*4 + 2]; + ss0 = line0[xx * 4 + 0] + line1[xx * 4 + 0] + line2[xx * 4 + 0]; + ss1 = line0[xx * 4 + 1] + line1[xx * 4 + 1] + line2[xx * 4 + 1]; + ss2 = line0[xx * 4 + 2] + line1[xx * 4 + 2] + line2[xx * 4 + 2]; v = MAKE_UINT32( - ((ss0 + amend) * multiplier) >> 24, ((ss1 + amend) * multiplier) >> 24, - ((ss2 + amend) * multiplier) >> 24, 0); + ((ss0 + amend) * multiplier) >> 24, + ((ss1 + amend) * multiplier) >> 24, + ((ss2 + amend) * multiplier) >> 24, + 0); memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v)); } } else { // bands == 4 for (x = 0; x < box[2] / xscale; x++) { - int xx = box[0] + x*xscale; + int xx = box[0] + x * xscale; UINT32 v; - ss0 = line0[xx*4 + 0] + - line1[xx*4 + 0] + - line2[xx*4 + 0]; - ss1 = line0[xx*4 + 1] + - line1[xx*4 + 1] + - line2[xx*4 + 1]; - ss2 = line0[xx*4 + 2] + - line1[xx*4 + 2] + - line2[xx*4 + 2]; - ss3 = line0[xx*4 + 3] + - line1[xx*4 + 3] + - line2[xx*4 + 3]; + ss0 = line0[xx * 4 + 0] + line1[xx * 4 + 0] + line2[xx * 4 + 0]; + ss1 = line0[xx * 4 + 1] + line1[xx * 4 + 1] + line2[xx * 4 + 1]; + ss2 = line0[xx * 4 + 2] + line1[xx * 4 + 2] + line2[xx * 4 + 2]; + ss3 = line0[xx * 4 + 3] + line1[xx * 4 + 3] + line2[xx * 4 + 3]; v = MAKE_UINT32( - ((ss0 + amend) * multiplier) >> 24, ((ss1 + amend) * multiplier) >> 24, - ((ss2 + amend) * multiplier) >> 24, ((ss3 + amend) * multiplier) >> 24); + ((ss0 + amend) * multiplier) >> 24, + ((ss1 + amend) * multiplier) >> 24, + ((ss2 + amend) * multiplier) >> 24, + ((ss3 + amend) * multiplier) >> 24); memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v)); } } @@ -685,12 +660,10 @@ ImagingReduce1x3(Imaging imOut, Imaging imIn, int box[4]) } } - void -ImagingReduce3x1(Imaging imOut, Imaging imIn, int box[4]) -{ +ImagingReduce3x1(Imaging imOut, Imaging imIn, int box[4]) { /* Optimized implementation for xscale = 3 and yscale = 1. - */ + */ int xscale = 3, yscale = 1; int x, y; UINT32 ss0, ss1, ss2, ss3; @@ -699,52 +672,58 @@ ImagingReduce3x1(Imaging imOut, Imaging imIn, int box[4]) if (imIn->image8) { for (y = 0; y < box[3] / yscale; y++) { - int yy = box[1] + y*yscale; + int yy = box[1] + y * yscale; UINT8 *line0 = (UINT8 *)imIn->image8[yy + 0]; for (x = 0; x < box[2] / xscale; x++) { - int xx = box[0] + x*xscale; + int xx = box[0] + x * xscale; ss0 = line0[xx + 0] + line0[xx + 1] + line0[xx + 2]; imOut->image8[y][x] = ((ss0 + amend) * multiplier) >> 24; } } } else { for (y = 0; y < box[3] / yscale; y++) { - int yy = box[1] + y*yscale; + int yy = box[1] + y * yscale; UINT8 *line0 = (UINT8 *)imIn->image[yy + 0]; if (imIn->bands == 2) { for (x = 0; x < box[2] / xscale; x++) { - int xx = box[0] + x*xscale; + int xx = box[0] + x * xscale; UINT32 v; - ss0 = line0[xx*4 + 0] + line0[xx*4 + 4] + line0[xx*4 + 8]; - ss3 = line0[xx*4 + 3] + line0[xx*4 + 7] + line0[xx*4 + 11]; + ss0 = line0[xx * 4 + 0] + line0[xx * 4 + 4] + line0[xx * 4 + 8]; + ss3 = line0[xx * 4 + 3] + line0[xx * 4 + 7] + line0[xx * 4 + 11]; v = MAKE_UINT32( - ((ss0 + amend) * multiplier) >> 24, 0, - 0, ((ss3 + amend) * multiplier) >> 24); + ((ss0 + amend) * multiplier) >> 24, + 0, + 0, + ((ss3 + amend) * multiplier) >> 24); memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v)); } } else if (imIn->bands == 3) { for (x = 0; x < box[2] / xscale; x++) { - int xx = box[0] + x*xscale; + int xx = box[0] + x * xscale; UINT32 v; - ss0 = line0[xx*4 + 0] + line0[xx*4 + 4] + line0[xx*4 + 8]; - ss1 = line0[xx*4 + 1] + line0[xx*4 + 5] + line0[xx*4 + 9]; - ss2 = line0[xx*4 + 2] + line0[xx*4 + 6] + line0[xx*4 + 10]; + ss0 = line0[xx * 4 + 0] + line0[xx * 4 + 4] + line0[xx * 4 + 8]; + ss1 = line0[xx * 4 + 1] + line0[xx * 4 + 5] + line0[xx * 4 + 9]; + ss2 = line0[xx * 4 + 2] + line0[xx * 4 + 6] + line0[xx * 4 + 10]; v = MAKE_UINT32( - ((ss0 + amend) * multiplier) >> 24, ((ss1 + amend) * multiplier) >> 24, - ((ss2 + amend) * multiplier) >> 24, 0); + ((ss0 + amend) * multiplier) >> 24, + ((ss1 + amend) * multiplier) >> 24, + ((ss2 + amend) * multiplier) >> 24, + 0); memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v)); } } else { // bands == 4 for (x = 0; x < box[2] / xscale; x++) { - int xx = box[0] + x*xscale; + int xx = box[0] + x * xscale; UINT32 v; - ss0 = line0[xx*4 + 0] + line0[xx*4 + 4] + line0[xx*4 + 8]; - ss1 = line0[xx*4 + 1] + line0[xx*4 + 5] + line0[xx*4 + 9]; - ss2 = line0[xx*4 + 2] + line0[xx*4 + 6] + line0[xx*4 + 10]; - ss3 = line0[xx*4 + 3] + line0[xx*4 + 7] + line0[xx*4 + 11]; + ss0 = line0[xx * 4 + 0] + line0[xx * 4 + 4] + line0[xx * 4 + 8]; + ss1 = line0[xx * 4 + 1] + line0[xx * 4 + 5] + line0[xx * 4 + 9]; + ss2 = line0[xx * 4 + 2] + line0[xx * 4 + 6] + line0[xx * 4 + 10]; + ss3 = line0[xx * 4 + 3] + line0[xx * 4 + 7] + line0[xx * 4 + 11]; v = MAKE_UINT32( - ((ss0 + amend) * multiplier) >> 24, ((ss1 + amend) * multiplier) >> 24, - ((ss2 + amend) * multiplier) >> 24, ((ss3 + amend) * multiplier) >> 24); + ((ss0 + amend) * multiplier) >> 24, + ((ss1 + amend) * multiplier) >> 24, + ((ss2 + amend) * multiplier) >> 24, + ((ss3 + amend) * multiplier) >> 24); memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v)); } } @@ -752,12 +731,10 @@ ImagingReduce3x1(Imaging imOut, Imaging imIn, int box[4]) } } - void -ImagingReduce3x3(Imaging imOut, Imaging imIn, int box[4]) -{ +ImagingReduce3x3(Imaging imOut, Imaging imIn, int box[4]) { /* Optimized implementation for xscale = 3 and yscale = 3. - */ + */ int xscale = 3, yscale = 3; int x, y; UINT32 ss0, ss1, ss2, ss3; @@ -766,76 +743,82 @@ ImagingReduce3x3(Imaging imOut, Imaging imIn, int box[4]) if (imIn->image8) { for (y = 0; y < box[3] / yscale; y++) { - int yy = box[1] + y*yscale; + int yy = box[1] + y * yscale; UINT8 *line0 = (UINT8 *)imIn->image8[yy + 0]; UINT8 *line1 = (UINT8 *)imIn->image8[yy + 1]; UINT8 *line2 = (UINT8 *)imIn->image8[yy + 2]; for (x = 0; x < box[2] / xscale; x++) { - int xx = box[0] + x*xscale; - ss0 = line0[xx + 0] + line0[xx + 1] + line0[xx + 2] + - line1[xx + 0] + line1[xx + 1] + line1[xx + 2] + - line2[xx + 0] + line2[xx + 1] + line2[xx + 2]; + int xx = box[0] + x * xscale; + ss0 = line0[xx + 0] + line0[xx + 1] + line0[xx + 2] + line1[xx + 0] + + line1[xx + 1] + line1[xx + 2] + line2[xx + 0] + line2[xx + 1] + + line2[xx + 2]; imOut->image8[y][x] = ((ss0 + amend) * multiplier) >> 24; } } } else { for (y = 0; y < box[3] / yscale; y++) { - int yy = box[1] + y*yscale; + int yy = box[1] + y * yscale; UINT8 *line0 = (UINT8 *)imIn->image[yy + 0]; UINT8 *line1 = (UINT8 *)imIn->image[yy + 1]; UINT8 *line2 = (UINT8 *)imIn->image[yy + 2]; if (imIn->bands == 2) { for (x = 0; x < box[2] / xscale; x++) { - int xx = box[0] + x*xscale; + int xx = box[0] + x * xscale; UINT32 v; - ss0 = line0[xx*4 + 0] + line0[xx*4 + 4] + line0[xx*4 + 8] + - line1[xx*4 + 0] + line1[xx*4 + 4] + line1[xx*4 + 8] + - line2[xx*4 + 0] + line2[xx*4 + 4] + line2[xx*4 + 8]; - ss3 = line0[xx*4 + 3] + line0[xx*4 + 7] + line0[xx*4 + 11] + - line1[xx*4 + 3] + line1[xx*4 + 7] + line1[xx*4 + 11] + - line2[xx*4 + 3] + line2[xx*4 + 7] + line2[xx*4 + 11]; + ss0 = line0[xx * 4 + 0] + line0[xx * 4 + 4] + line0[xx * 4 + 8] + + line1[xx * 4 + 0] + line1[xx * 4 + 4] + line1[xx * 4 + 8] + + line2[xx * 4 + 0] + line2[xx * 4 + 4] + line2[xx * 4 + 8]; + ss3 = line0[xx * 4 + 3] + line0[xx * 4 + 7] + line0[xx * 4 + 11] + + line1[xx * 4 + 3] + line1[xx * 4 + 7] + line1[xx * 4 + 11] + + line2[xx * 4 + 3] + line2[xx * 4 + 7] + line2[xx * 4 + 11]; v = MAKE_UINT32( - ((ss0 + amend) * multiplier) >> 24, 0, - 0, ((ss3 + amend) * multiplier) >> 24); + ((ss0 + amend) * multiplier) >> 24, + 0, + 0, + ((ss3 + amend) * multiplier) >> 24); memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v)); } } else if (imIn->bands == 3) { for (x = 0; x < box[2] / xscale; x++) { - int xx = box[0] + x*xscale; + int xx = box[0] + x * xscale; UINT32 v; - ss0 = line0[xx*4 + 0] + line0[xx*4 + 4] + line0[xx*4 + 8] + - line1[xx*4 + 0] + line1[xx*4 + 4] + line1[xx*4 + 8] + - line2[xx*4 + 0] + line2[xx*4 + 4] + line2[xx*4 + 8]; - ss1 = line0[xx*4 + 1] + line0[xx*4 + 5] + line0[xx*4 + 9] + - line1[xx*4 + 1] + line1[xx*4 + 5] + line1[xx*4 + 9] + - line2[xx*4 + 1] + line2[xx*4 + 5] + line2[xx*4 + 9]; - ss2 = line0[xx*4 + 2] + line0[xx*4 + 6] + line0[xx*4 + 10] + - line1[xx*4 + 2] + line1[xx*4 + 6] + line1[xx*4 + 10] + - line2[xx*4 + 2] + line2[xx*4 + 6] + line2[xx*4 + 10]; + ss0 = line0[xx * 4 + 0] + line0[xx * 4 + 4] + line0[xx * 4 + 8] + + line1[xx * 4 + 0] + line1[xx * 4 + 4] + line1[xx * 4 + 8] + + line2[xx * 4 + 0] + line2[xx * 4 + 4] + line2[xx * 4 + 8]; + ss1 = line0[xx * 4 + 1] + line0[xx * 4 + 5] + line0[xx * 4 + 9] + + line1[xx * 4 + 1] + line1[xx * 4 + 5] + line1[xx * 4 + 9] + + line2[xx * 4 + 1] + line2[xx * 4 + 5] + line2[xx * 4 + 9]; + ss2 = line0[xx * 4 + 2] + line0[xx * 4 + 6] + line0[xx * 4 + 10] + + line1[xx * 4 + 2] + line1[xx * 4 + 6] + line1[xx * 4 + 10] + + line2[xx * 4 + 2] + line2[xx * 4 + 6] + line2[xx * 4 + 10]; v = MAKE_UINT32( - ((ss0 + amend) * multiplier) >> 24, ((ss1 + amend) * multiplier) >> 24, - ((ss2 + amend) * multiplier) >> 24, 0); + ((ss0 + amend) * multiplier) >> 24, + ((ss1 + amend) * multiplier) >> 24, + ((ss2 + amend) * multiplier) >> 24, + 0); memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v)); } } else { // bands == 4 for (x = 0; x < box[2] / xscale; x++) { - int xx = box[0] + x*xscale; + int xx = box[0] + x * xscale; UINT32 v; - ss0 = line0[xx*4 + 0] + line0[xx*4 + 4] + line0[xx*4 + 8] + - line1[xx*4 + 0] + line1[xx*4 + 4] + line1[xx*4 + 8] + - line2[xx*4 + 0] + line2[xx*4 + 4] + line2[xx*4 + 8]; - ss1 = line0[xx*4 + 1] + line0[xx*4 + 5] + line0[xx*4 + 9] + - line1[xx*4 + 1] + line1[xx*4 + 5] + line1[xx*4 + 9] + - line2[xx*4 + 1] + line2[xx*4 + 5] + line2[xx*4 + 9]; - ss2 = line0[xx*4 + 2] + line0[xx*4 + 6] + line0[xx*4 + 10] + - line1[xx*4 + 2] + line1[xx*4 + 6] + line1[xx*4 + 10] + - line2[xx*4 + 2] + line2[xx*4 + 6] + line2[xx*4 + 10]; - ss3 = line0[xx*4 + 3] + line0[xx*4 + 7] + line0[xx*4 + 11] + - line1[xx*4 + 3] + line1[xx*4 + 7] + line1[xx*4 + 11] + - line2[xx*4 + 3] + line2[xx*4 + 7] + line2[xx*4 + 11]; + ss0 = line0[xx * 4 + 0] + line0[xx * 4 + 4] + line0[xx * 4 + 8] + + line1[xx * 4 + 0] + line1[xx * 4 + 4] + line1[xx * 4 + 8] + + line2[xx * 4 + 0] + line2[xx * 4 + 4] + line2[xx * 4 + 8]; + ss1 = line0[xx * 4 + 1] + line0[xx * 4 + 5] + line0[xx * 4 + 9] + + line1[xx * 4 + 1] + line1[xx * 4 + 5] + line1[xx * 4 + 9] + + line2[xx * 4 + 1] + line2[xx * 4 + 5] + line2[xx * 4 + 9]; + ss2 = line0[xx * 4 + 2] + line0[xx * 4 + 6] + line0[xx * 4 + 10] + + line1[xx * 4 + 2] + line1[xx * 4 + 6] + line1[xx * 4 + 10] + + line2[xx * 4 + 2] + line2[xx * 4 + 6] + line2[xx * 4 + 10]; + ss3 = line0[xx * 4 + 3] + line0[xx * 4 + 7] + line0[xx * 4 + 11] + + line1[xx * 4 + 3] + line1[xx * 4 + 7] + line1[xx * 4 + 11] + + line2[xx * 4 + 3] + line2[xx * 4 + 7] + line2[xx * 4 + 11]; v = MAKE_UINT32( - ((ss0 + amend) * multiplier) >> 24, ((ss1 + amend) * multiplier) >> 24, - ((ss2 + amend) * multiplier) >> 24, ((ss3 + amend) * multiplier) >> 24); + ((ss0 + amend) * multiplier) >> 24, + ((ss1 + amend) * multiplier) >> 24, + ((ss2 + amend) * multiplier) >> 24, + ((ss3 + amend) * multiplier) >> 24); memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v)); } } @@ -844,10 +827,9 @@ ImagingReduce3x3(Imaging imOut, Imaging imIn, int box[4]) } void -ImagingReduce4x4(Imaging imOut, Imaging imIn, int box[4]) -{ +ImagingReduce4x4(Imaging imOut, Imaging imIn, int box[4]) { /* Optimized implementation for xscale = 4 and yscale = 4. - */ + */ int xscale = 4, yscale = 4; int x, y; UINT32 ss0, ss1, ss2, ss3; @@ -855,13 +837,13 @@ ImagingReduce4x4(Imaging imOut, Imaging imIn, int box[4]) if (imIn->image8) { for (y = 0; y < box[3] / yscale; y++) { - int yy = box[1] + y*yscale; + int yy = box[1] + y * yscale; UINT8 *line0 = (UINT8 *)imIn->image8[yy + 0]; UINT8 *line1 = (UINT8 *)imIn->image8[yy + 1]; UINT8 *line2 = (UINT8 *)imIn->image8[yy + 2]; UINT8 *line3 = (UINT8 *)imIn->image8[yy + 3]; for (x = 0; x < box[2] / xscale; x++) { - int xx = box[0] + x*xscale; + int xx = box[0] + x * xscale; ss0 = line0[xx + 0] + line0[xx + 1] + line0[xx + 2] + line0[xx + 3] + line1[xx + 0] + line1[xx + 1] + line1[xx + 2] + line1[xx + 3] + line2[xx + 0] + line2[xx + 1] + line2[xx + 2] + line2[xx + 3] + @@ -871,69 +853,89 @@ ImagingReduce4x4(Imaging imOut, Imaging imIn, int box[4]) } } else { for (y = 0; y < box[3] / yscale; y++) { - int yy = box[1] + y*yscale; + int yy = box[1] + y * yscale; UINT8 *line0 = (UINT8 *)imIn->image[yy + 0]; UINT8 *line1 = (UINT8 *)imIn->image[yy + 1]; UINT8 *line2 = (UINT8 *)imIn->image[yy + 2]; UINT8 *line3 = (UINT8 *)imIn->image[yy + 3]; if (imIn->bands == 2) { for (x = 0; x < box[2] / xscale; x++) { - int xx = box[0] + x*xscale; + int xx = box[0] + x * xscale; UINT32 v; - ss0 = line0[xx*4 + 0] + line0[xx*4 + 4] + line0[xx*4 + 8] + line0[xx*4 + 12] + - line1[xx*4 + 0] + line1[xx*4 + 4] + line1[xx*4 + 8] + line1[xx*4 + 12] + - line2[xx*4 + 0] + line2[xx*4 + 4] + line2[xx*4 + 8] + line2[xx*4 + 12] + - line3[xx*4 + 0] + line3[xx*4 + 4] + line3[xx*4 + 8] + line3[xx*4 + 12]; - ss3 = line0[xx*4 + 3] + line0[xx*4 + 7] + line0[xx*4 + 11] + line0[xx*4 + 15] + - line1[xx*4 + 3] + line1[xx*4 + 7] + line1[xx*4 + 11] + line1[xx*4 + 15] + - line2[xx*4 + 3] + line2[xx*4 + 7] + line2[xx*4 + 11] + line2[xx*4 + 15] + - line3[xx*4 + 3] + line3[xx*4 + 7] + line3[xx*4 + 11] + line3[xx*4 + 15]; - v = MAKE_UINT32((ss0 + amend) >> 4, 0, - 0, (ss3 + amend) >> 4); + ss0 = line0[xx * 4 + 0] + line0[xx * 4 + 4] + line0[xx * 4 + 8] + + line0[xx * 4 + 12] + line1[xx * 4 + 0] + line1[xx * 4 + 4] + + line1[xx * 4 + 8] + line1[xx * 4 + 12] + line2[xx * 4 + 0] + + line2[xx * 4 + 4] + line2[xx * 4 + 8] + line2[xx * 4 + 12] + + line3[xx * 4 + 0] + line3[xx * 4 + 4] + line3[xx * 4 + 8] + + line3[xx * 4 + 12]; + ss3 = line0[xx * 4 + 3] + line0[xx * 4 + 7] + line0[xx * 4 + 11] + + line0[xx * 4 + 15] + line1[xx * 4 + 3] + line1[xx * 4 + 7] + + line1[xx * 4 + 11] + line1[xx * 4 + 15] + line2[xx * 4 + 3] + + line2[xx * 4 + 7] + line2[xx * 4 + 11] + line2[xx * 4 + 15] + + line3[xx * 4 + 3] + line3[xx * 4 + 7] + line3[xx * 4 + 11] + + line3[xx * 4 + 15]; + v = MAKE_UINT32((ss0 + amend) >> 4, 0, 0, (ss3 + amend) >> 4); memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v)); } } else if (imIn->bands == 3) { for (x = 0; x < box[2] / xscale; x++) { - int xx = box[0] + x*xscale; + int xx = box[0] + x * xscale; UINT32 v; - ss0 = line0[xx*4 + 0] + line0[xx*4 + 4] + line0[xx*4 + 8] + line0[xx*4 + 12] + - line1[xx*4 + 0] + line1[xx*4 + 4] + line1[xx*4 + 8] + line1[xx*4 + 12] + - line2[xx*4 + 0] + line2[xx*4 + 4] + line2[xx*4 + 8] + line2[xx*4 + 12] + - line3[xx*4 + 0] + line3[xx*4 + 4] + line3[xx*4 + 8] + line3[xx*4 + 12]; - ss1 = line0[xx*4 + 1] + line0[xx*4 + 5] + line0[xx*4 + 9] + line0[xx*4 + 13] + - line1[xx*4 + 1] + line1[xx*4 + 5] + line1[xx*4 + 9] + line1[xx*4 + 13] + - line2[xx*4 + 1] + line2[xx*4 + 5] + line2[xx*4 + 9] + line2[xx*4 + 13] + - line3[xx*4 + 1] + line3[xx*4 + 5] + line3[xx*4 + 9] + line3[xx*4 + 13]; - ss2 = line0[xx*4 + 2] + line0[xx*4 + 6] + line0[xx*4 + 10] + line0[xx*4 + 14] + - line1[xx*4 + 2] + line1[xx*4 + 6] + line1[xx*4 + 10] + line1[xx*4 + 14] + - line2[xx*4 + 2] + line2[xx*4 + 6] + line2[xx*4 + 10] + line2[xx*4 + 14] + - line3[xx*4 + 2] + line3[xx*4 + 6] + line3[xx*4 + 10] + line3[xx*4 + 14]; - v = MAKE_UINT32((ss0 + amend) >> 4, (ss1 + amend) >> 4, - (ss2 + amend) >> 4, 0); + ss0 = line0[xx * 4 + 0] + line0[xx * 4 + 4] + line0[xx * 4 + 8] + + line0[xx * 4 + 12] + line1[xx * 4 + 0] + line1[xx * 4 + 4] + + line1[xx * 4 + 8] + line1[xx * 4 + 12] + line2[xx * 4 + 0] + + line2[xx * 4 + 4] + line2[xx * 4 + 8] + line2[xx * 4 + 12] + + line3[xx * 4 + 0] + line3[xx * 4 + 4] + line3[xx * 4 + 8] + + line3[xx * 4 + 12]; + ss1 = line0[xx * 4 + 1] + line0[xx * 4 + 5] + line0[xx * 4 + 9] + + line0[xx * 4 + 13] + line1[xx * 4 + 1] + line1[xx * 4 + 5] + + line1[xx * 4 + 9] + line1[xx * 4 + 13] + line2[xx * 4 + 1] + + line2[xx * 4 + 5] + line2[xx * 4 + 9] + line2[xx * 4 + 13] + + line3[xx * 4 + 1] + line3[xx * 4 + 5] + line3[xx * 4 + 9] + + line3[xx * 4 + 13]; + ss2 = line0[xx * 4 + 2] + line0[xx * 4 + 6] + line0[xx * 4 + 10] + + line0[xx * 4 + 14] + line1[xx * 4 + 2] + line1[xx * 4 + 6] + + line1[xx * 4 + 10] + line1[xx * 4 + 14] + line2[xx * 4 + 2] + + line2[xx * 4 + 6] + line2[xx * 4 + 10] + line2[xx * 4 + 14] + + line3[xx * 4 + 2] + line3[xx * 4 + 6] + line3[xx * 4 + 10] + + line3[xx * 4 + 14]; + v = MAKE_UINT32( + (ss0 + amend) >> 4, (ss1 + amend) >> 4, (ss2 + amend) >> 4, 0); memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v)); } } else { // bands == 4 for (x = 0; x < box[2] / xscale; x++) { - int xx = box[0] + x*xscale; + int xx = box[0] + x * xscale; UINT32 v; - ss0 = line0[xx*4 + 0] + line0[xx*4 + 4] + line0[xx*4 + 8] + line0[xx*4 + 12] + - line1[xx*4 + 0] + line1[xx*4 + 4] + line1[xx*4 + 8] + line1[xx*4 + 12] + - line2[xx*4 + 0] + line2[xx*4 + 4] + line2[xx*4 + 8] + line2[xx*4 + 12] + - line3[xx*4 + 0] + line3[xx*4 + 4] + line3[xx*4 + 8] + line3[xx*4 + 12]; - ss1 = line0[xx*4 + 1] + line0[xx*4 + 5] + line0[xx*4 + 9] + line0[xx*4 + 13] + - line1[xx*4 + 1] + line1[xx*4 + 5] + line1[xx*4 + 9] + line1[xx*4 + 13] + - line2[xx*4 + 1] + line2[xx*4 + 5] + line2[xx*4 + 9] + line2[xx*4 + 13] + - line3[xx*4 + 1] + line3[xx*4 + 5] + line3[xx*4 + 9] + line3[xx*4 + 13]; - ss2 = line0[xx*4 + 2] + line0[xx*4 + 6] + line0[xx*4 + 10] + line0[xx*4 + 14] + - line1[xx*4 + 2] + line1[xx*4 + 6] + line1[xx*4 + 10] + line1[xx*4 + 14] + - line2[xx*4 + 2] + line2[xx*4 + 6] + line2[xx*4 + 10] + line2[xx*4 + 14] + - line3[xx*4 + 2] + line3[xx*4 + 6] + line3[xx*4 + 10] + line3[xx*4 + 14]; - ss3 = line0[xx*4 + 3] + line0[xx*4 + 7] + line0[xx*4 + 11] + line0[xx*4 + 15] + - line1[xx*4 + 3] + line1[xx*4 + 7] + line1[xx*4 + 11] + line1[xx*4 + 15] + - line2[xx*4 + 3] + line2[xx*4 + 7] + line2[xx*4 + 11] + line2[xx*4 + 15] + - line3[xx*4 + 3] + line3[xx*4 + 7] + line3[xx*4 + 11] + line3[xx*4 + 15]; - v = MAKE_UINT32((ss0 + amend) >> 4, (ss1 + amend) >> 4, - (ss2 + amend) >> 4, (ss3 + amend) >> 4); + ss0 = line0[xx * 4 + 0] + line0[xx * 4 + 4] + line0[xx * 4 + 8] + + line0[xx * 4 + 12] + line1[xx * 4 + 0] + line1[xx * 4 + 4] + + line1[xx * 4 + 8] + line1[xx * 4 + 12] + line2[xx * 4 + 0] + + line2[xx * 4 + 4] + line2[xx * 4 + 8] + line2[xx * 4 + 12] + + line3[xx * 4 + 0] + line3[xx * 4 + 4] + line3[xx * 4 + 8] + + line3[xx * 4 + 12]; + ss1 = line0[xx * 4 + 1] + line0[xx * 4 + 5] + line0[xx * 4 + 9] + + line0[xx * 4 + 13] + line1[xx * 4 + 1] + line1[xx * 4 + 5] + + line1[xx * 4 + 9] + line1[xx * 4 + 13] + line2[xx * 4 + 1] + + line2[xx * 4 + 5] + line2[xx * 4 + 9] + line2[xx * 4 + 13] + + line3[xx * 4 + 1] + line3[xx * 4 + 5] + line3[xx * 4 + 9] + + line3[xx * 4 + 13]; + ss2 = line0[xx * 4 + 2] + line0[xx * 4 + 6] + line0[xx * 4 + 10] + + line0[xx * 4 + 14] + line1[xx * 4 + 2] + line1[xx * 4 + 6] + + line1[xx * 4 + 10] + line1[xx * 4 + 14] + line2[xx * 4 + 2] + + line2[xx * 4 + 6] + line2[xx * 4 + 10] + line2[xx * 4 + 14] + + line3[xx * 4 + 2] + line3[xx * 4 + 6] + line3[xx * 4 + 10] + + line3[xx * 4 + 14]; + ss3 = line0[xx * 4 + 3] + line0[xx * 4 + 7] + line0[xx * 4 + 11] + + line0[xx * 4 + 15] + line1[xx * 4 + 3] + line1[xx * 4 + 7] + + line1[xx * 4 + 11] + line1[xx * 4 + 15] + line2[xx * 4 + 3] + + line2[xx * 4 + 7] + line2[xx * 4 + 11] + line2[xx * 4 + 15] + + line3[xx * 4 + 3] + line3[xx * 4 + 7] + line3[xx * 4 + 11] + + line3[xx * 4 + 15]; + v = MAKE_UINT32( + (ss0 + amend) >> 4, + (ss1 + amend) >> 4, + (ss2 + amend) >> 4, + (ss3 + amend) >> 4); memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v)); } } @@ -941,12 +943,10 @@ ImagingReduce4x4(Imaging imOut, Imaging imIn, int box[4]) } } - void -ImagingReduce5x5(Imaging imOut, Imaging imIn, int box[4]) -{ +ImagingReduce5x5(Imaging imOut, Imaging imIn, int box[4]) { /* Fast special case for xscale = 5 and yscale = 5. - */ + */ int xscale = 5, yscale = 5; int x, y; UINT32 ss0, ss1, ss2, ss3; @@ -955,25 +955,27 @@ ImagingReduce5x5(Imaging imOut, Imaging imIn, int box[4]) if (imIn->image8) { for (y = 0; y < box[3] / yscale; y++) { - int yy = box[1] + y*yscale; + int yy = box[1] + y * yscale; UINT8 *line0 = (UINT8 *)imIn->image8[yy + 0]; UINT8 *line1 = (UINT8 *)imIn->image8[yy + 1]; UINT8 *line2 = (UINT8 *)imIn->image8[yy + 2]; UINT8 *line3 = (UINT8 *)imIn->image8[yy + 3]; UINT8 *line4 = (UINT8 *)imIn->image8[yy + 4]; for (x = 0; x < box[2] / xscale; x++) { - int xx = box[0] + x*xscale; - ss0 = line0[xx + 0] + line0[xx + 1] + line0[xx + 2] + line0[xx + 3] + line0[xx + 4] + - line1[xx + 0] + line1[xx + 1] + line1[xx + 2] + line1[xx + 3] + line1[xx + 4] + - line2[xx + 0] + line2[xx + 1] + line2[xx + 2] + line2[xx + 3] + line2[xx + 4] + - line3[xx + 0] + line3[xx + 1] + line3[xx + 2] + line3[xx + 3] + line3[xx + 4] + - line4[xx + 0] + line4[xx + 1] + line4[xx + 2] + line4[xx + 3] + line4[xx + 4]; + int xx = box[0] + x * xscale; + ss0 = line0[xx + 0] + line0[xx + 1] + line0[xx + 2] + line0[xx + 3] + + line0[xx + 4] + line1[xx + 0] + line1[xx + 1] + line1[xx + 2] + + line1[xx + 3] + line1[xx + 4] + line2[xx + 0] + line2[xx + 1] + + line2[xx + 2] + line2[xx + 3] + line2[xx + 4] + line3[xx + 0] + + line3[xx + 1] + line3[xx + 2] + line3[xx + 3] + line3[xx + 4] + + line4[xx + 0] + line4[xx + 1] + line4[xx + 2] + line4[xx + 3] + + line4[xx + 4]; imOut->image8[y][x] = ((ss0 + amend) * multiplier) >> 24; } } } else { for (y = 0; y < box[3] / yscale; y++) { - int yy = box[1] + y*yscale; + int yy = box[1] + y * yscale; UINT8 *line0 = (UINT8 *)imIn->image[yy + 0]; UINT8 *line1 = (UINT8 *)imIn->image[yy + 1]; UINT8 *line2 = (UINT8 *)imIn->image[yy + 2]; @@ -981,74 +983,116 @@ ImagingReduce5x5(Imaging imOut, Imaging imIn, int box[4]) UINT8 *line4 = (UINT8 *)imIn->image[yy + 4]; if (imIn->bands == 2) { for (x = 0; x < box[2] / xscale; x++) { - int xx = box[0] + x*xscale; + int xx = box[0] + x * xscale; UINT32 v; - ss0 = line0[xx*4 + 0] + line0[xx*4 + 4] + line0[xx*4 + 8] + line0[xx*4 + 12] + line0[xx*4 + 16] + - line1[xx*4 + 0] + line1[xx*4 + 4] + line1[xx*4 + 8] + line1[xx*4 + 12] + line1[xx*4 + 16] + - line2[xx*4 + 0] + line2[xx*4 + 4] + line2[xx*4 + 8] + line2[xx*4 + 12] + line2[xx*4 + 16] + - line3[xx*4 + 0] + line3[xx*4 + 4] + line3[xx*4 + 8] + line3[xx*4 + 12] + line3[xx*4 + 16] + - line4[xx*4 + 0] + line4[xx*4 + 4] + line4[xx*4 + 8] + line4[xx*4 + 12] + line4[xx*4 + 16]; - ss3 = line0[xx*4 + 3] + line0[xx*4 + 7] + line0[xx*4 + 11] + line0[xx*4 + 15] + line0[xx*4 + 19] + - line1[xx*4 + 3] + line1[xx*4 + 7] + line1[xx*4 + 11] + line1[xx*4 + 15] + line1[xx*4 + 19] + - line2[xx*4 + 3] + line2[xx*4 + 7] + line2[xx*4 + 11] + line2[xx*4 + 15] + line2[xx*4 + 19] + - line3[xx*4 + 3] + line3[xx*4 + 7] + line3[xx*4 + 11] + line3[xx*4 + 15] + line3[xx*4 + 19] + - line4[xx*4 + 3] + line4[xx*4 + 7] + line4[xx*4 + 11] + line4[xx*4 + 15] + line4[xx*4 + 19]; + ss0 = line0[xx * 4 + 0] + line0[xx * 4 + 4] + line0[xx * 4 + 8] + + line0[xx * 4 + 12] + line0[xx * 4 + 16] + line1[xx * 4 + 0] + + line1[xx * 4 + 4] + line1[xx * 4 + 8] + line1[xx * 4 + 12] + + line1[xx * 4 + 16] + line2[xx * 4 + 0] + line2[xx * 4 + 4] + + line2[xx * 4 + 8] + line2[xx * 4 + 12] + line2[xx * 4 + 16] + + line3[xx * 4 + 0] + line3[xx * 4 + 4] + line3[xx * 4 + 8] + + line3[xx * 4 + 12] + line3[xx * 4 + 16] + line4[xx * 4 + 0] + + line4[xx * 4 + 4] + line4[xx * 4 + 8] + line4[xx * 4 + 12] + + line4[xx * 4 + 16]; + ss3 = line0[xx * 4 + 3] + line0[xx * 4 + 7] + line0[xx * 4 + 11] + + line0[xx * 4 + 15] + line0[xx * 4 + 19] + line1[xx * 4 + 3] + + line1[xx * 4 + 7] + line1[xx * 4 + 11] + line1[xx * 4 + 15] + + line1[xx * 4 + 19] + line2[xx * 4 + 3] + line2[xx * 4 + 7] + + line2[xx * 4 + 11] + line2[xx * 4 + 15] + line2[xx * 4 + 19] + + line3[xx * 4 + 3] + line3[xx * 4 + 7] + line3[xx * 4 + 11] + + line3[xx * 4 + 15] + line3[xx * 4 + 19] + line4[xx * 4 + 3] + + line4[xx * 4 + 7] + line4[xx * 4 + 11] + line4[xx * 4 + 15] + + line4[xx * 4 + 19]; v = MAKE_UINT32( - ((ss0 + amend) * multiplier) >> 24, 0, - 0, ((ss3 + amend) * multiplier) >> 24); + ((ss0 + amend) * multiplier) >> 24, + 0, + 0, + ((ss3 + amend) * multiplier) >> 24); memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v)); } } else if (imIn->bands == 3) { for (x = 0; x < box[2] / xscale; x++) { - int xx = box[0] + x*xscale; + int xx = box[0] + x * xscale; UINT32 v; - ss0 = line0[xx*4 + 0] + line0[xx*4 + 4] + line0[xx*4 + 8] + line0[xx*4 + 12] + line0[xx*4 + 16] + - line1[xx*4 + 0] + line1[xx*4 + 4] + line1[xx*4 + 8] + line1[xx*4 + 12] + line1[xx*4 + 16] + - line2[xx*4 + 0] + line2[xx*4 + 4] + line2[xx*4 + 8] + line2[xx*4 + 12] + line2[xx*4 + 16] + - line3[xx*4 + 0] + line3[xx*4 + 4] + line3[xx*4 + 8] + line3[xx*4 + 12] + line3[xx*4 + 16] + - line4[xx*4 + 0] + line4[xx*4 + 4] + line4[xx*4 + 8] + line4[xx*4 + 12] + line4[xx*4 + 16]; - ss1 = line0[xx*4 + 1] + line0[xx*4 + 5] + line0[xx*4 + 9] + line0[xx*4 + 13] + line0[xx*4 + 17] + - line1[xx*4 + 1] + line1[xx*4 + 5] + line1[xx*4 + 9] + line1[xx*4 + 13] + line1[xx*4 + 17] + - line2[xx*4 + 1] + line2[xx*4 + 5] + line2[xx*4 + 9] + line2[xx*4 + 13] + line2[xx*4 + 17] + - line3[xx*4 + 1] + line3[xx*4 + 5] + line3[xx*4 + 9] + line3[xx*4 + 13] + line3[xx*4 + 17] + - line4[xx*4 + 1] + line4[xx*4 + 5] + line4[xx*4 + 9] + line4[xx*4 + 13] + line4[xx*4 + 17]; - ss2 = line0[xx*4 + 2] + line0[xx*4 + 6] + line0[xx*4 + 10] + line0[xx*4 + 14] + line0[xx*4 + 18] + - line1[xx*4 + 2] + line1[xx*4 + 6] + line1[xx*4 + 10] + line1[xx*4 + 14] + line1[xx*4 + 18] + - line2[xx*4 + 2] + line2[xx*4 + 6] + line2[xx*4 + 10] + line2[xx*4 + 14] + line2[xx*4 + 18] + - line3[xx*4 + 2] + line3[xx*4 + 6] + line3[xx*4 + 10] + line3[xx*4 + 14] + line3[xx*4 + 18] + - line4[xx*4 + 2] + line4[xx*4 + 6] + line4[xx*4 + 10] + line4[xx*4 + 14] + line4[xx*4 + 18]; + ss0 = line0[xx * 4 + 0] + line0[xx * 4 + 4] + line0[xx * 4 + 8] + + line0[xx * 4 + 12] + line0[xx * 4 + 16] + line1[xx * 4 + 0] + + line1[xx * 4 + 4] + line1[xx * 4 + 8] + line1[xx * 4 + 12] + + line1[xx * 4 + 16] + line2[xx * 4 + 0] + line2[xx * 4 + 4] + + line2[xx * 4 + 8] + line2[xx * 4 + 12] + line2[xx * 4 + 16] + + line3[xx * 4 + 0] + line3[xx * 4 + 4] + line3[xx * 4 + 8] + + line3[xx * 4 + 12] + line3[xx * 4 + 16] + line4[xx * 4 + 0] + + line4[xx * 4 + 4] + line4[xx * 4 + 8] + line4[xx * 4 + 12] + + line4[xx * 4 + 16]; + ss1 = line0[xx * 4 + 1] + line0[xx * 4 + 5] + line0[xx * 4 + 9] + + line0[xx * 4 + 13] + line0[xx * 4 + 17] + line1[xx * 4 + 1] + + line1[xx * 4 + 5] + line1[xx * 4 + 9] + line1[xx * 4 + 13] + + line1[xx * 4 + 17] + line2[xx * 4 + 1] + line2[xx * 4 + 5] + + line2[xx * 4 + 9] + line2[xx * 4 + 13] + line2[xx * 4 + 17] + + line3[xx * 4 + 1] + line3[xx * 4 + 5] + line3[xx * 4 + 9] + + line3[xx * 4 + 13] + line3[xx * 4 + 17] + line4[xx * 4 + 1] + + line4[xx * 4 + 5] + line4[xx * 4 + 9] + line4[xx * 4 + 13] + + line4[xx * 4 + 17]; + ss2 = line0[xx * 4 + 2] + line0[xx * 4 + 6] + line0[xx * 4 + 10] + + line0[xx * 4 + 14] + line0[xx * 4 + 18] + line1[xx * 4 + 2] + + line1[xx * 4 + 6] + line1[xx * 4 + 10] + line1[xx * 4 + 14] + + line1[xx * 4 + 18] + line2[xx * 4 + 2] + line2[xx * 4 + 6] + + line2[xx * 4 + 10] + line2[xx * 4 + 14] + line2[xx * 4 + 18] + + line3[xx * 4 + 2] + line3[xx * 4 + 6] + line3[xx * 4 + 10] + + line3[xx * 4 + 14] + line3[xx * 4 + 18] + line4[xx * 4 + 2] + + line4[xx * 4 + 6] + line4[xx * 4 + 10] + line4[xx * 4 + 14] + + line4[xx * 4 + 18]; v = MAKE_UINT32( - ((ss0 + amend) * multiplier) >> 24, ((ss1 + amend) * multiplier) >> 24, - ((ss2 + amend) * multiplier) >> 24, 0); + ((ss0 + amend) * multiplier) >> 24, + ((ss1 + amend) * multiplier) >> 24, + ((ss2 + amend) * multiplier) >> 24, + 0); memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v)); } } else { // bands == 4 for (x = 0; x < box[2] / xscale; x++) { - int xx = box[0] + x*xscale; + int xx = box[0] + x * xscale; UINT32 v; - ss0 = line0[xx*4 + 0] + line0[xx*4 + 4] + line0[xx*4 + 8] + line0[xx*4 + 12] + line0[xx*4 + 16] + - line1[xx*4 + 0] + line1[xx*4 + 4] + line1[xx*4 + 8] + line1[xx*4 + 12] + line1[xx*4 + 16] + - line2[xx*4 + 0] + line2[xx*4 + 4] + line2[xx*4 + 8] + line2[xx*4 + 12] + line2[xx*4 + 16] + - line3[xx*4 + 0] + line3[xx*4 + 4] + line3[xx*4 + 8] + line3[xx*4 + 12] + line3[xx*4 + 16] + - line4[xx*4 + 0] + line4[xx*4 + 4] + line4[xx*4 + 8] + line4[xx*4 + 12] + line4[xx*4 + 16]; - ss1 = line0[xx*4 + 1] + line0[xx*4 + 5] + line0[xx*4 + 9] + line0[xx*4 + 13] + line0[xx*4 + 17] + - line1[xx*4 + 1] + line1[xx*4 + 5] + line1[xx*4 + 9] + line1[xx*4 + 13] + line1[xx*4 + 17] + - line2[xx*4 + 1] + line2[xx*4 + 5] + line2[xx*4 + 9] + line2[xx*4 + 13] + line2[xx*4 + 17] + - line3[xx*4 + 1] + line3[xx*4 + 5] + line3[xx*4 + 9] + line3[xx*4 + 13] + line3[xx*4 + 17] + - line4[xx*4 + 1] + line4[xx*4 + 5] + line4[xx*4 + 9] + line4[xx*4 + 13] + line4[xx*4 + 17]; - ss2 = line0[xx*4 + 2] + line0[xx*4 + 6] + line0[xx*4 + 10] + line0[xx*4 + 14] + line0[xx*4 + 18] + - line1[xx*4 + 2] + line1[xx*4 + 6] + line1[xx*4 + 10] + line1[xx*4 + 14] + line1[xx*4 + 18] + - line2[xx*4 + 2] + line2[xx*4 + 6] + line2[xx*4 + 10] + line2[xx*4 + 14] + line2[xx*4 + 18] + - line3[xx*4 + 2] + line3[xx*4 + 6] + line3[xx*4 + 10] + line3[xx*4 + 14] + line3[xx*4 + 18] + - line4[xx*4 + 2] + line4[xx*4 + 6] + line4[xx*4 + 10] + line4[xx*4 + 14] + line4[xx*4 + 18]; - ss3 = line0[xx*4 + 3] + line0[xx*4 + 7] + line0[xx*4 + 11] + line0[xx*4 + 15] + line0[xx*4 + 19] + - line1[xx*4 + 3] + line1[xx*4 + 7] + line1[xx*4 + 11] + line1[xx*4 + 15] + line1[xx*4 + 19] + - line2[xx*4 + 3] + line2[xx*4 + 7] + line2[xx*4 + 11] + line2[xx*4 + 15] + line2[xx*4 + 19] + - line3[xx*4 + 3] + line3[xx*4 + 7] + line3[xx*4 + 11] + line3[xx*4 + 15] + line3[xx*4 + 19] + - line4[xx*4 + 3] + line4[xx*4 + 7] + line4[xx*4 + 11] + line4[xx*4 + 15] + line4[xx*4 + 19]; + ss0 = line0[xx * 4 + 0] + line0[xx * 4 + 4] + line0[xx * 4 + 8] + + line0[xx * 4 + 12] + line0[xx * 4 + 16] + line1[xx * 4 + 0] + + line1[xx * 4 + 4] + line1[xx * 4 + 8] + line1[xx * 4 + 12] + + line1[xx * 4 + 16] + line2[xx * 4 + 0] + line2[xx * 4 + 4] + + line2[xx * 4 + 8] + line2[xx * 4 + 12] + line2[xx * 4 + 16] + + line3[xx * 4 + 0] + line3[xx * 4 + 4] + line3[xx * 4 + 8] + + line3[xx * 4 + 12] + line3[xx * 4 + 16] + line4[xx * 4 + 0] + + line4[xx * 4 + 4] + line4[xx * 4 + 8] + line4[xx * 4 + 12] + + line4[xx * 4 + 16]; + ss1 = line0[xx * 4 + 1] + line0[xx * 4 + 5] + line0[xx * 4 + 9] + + line0[xx * 4 + 13] + line0[xx * 4 + 17] + line1[xx * 4 + 1] + + line1[xx * 4 + 5] + line1[xx * 4 + 9] + line1[xx * 4 + 13] + + line1[xx * 4 + 17] + line2[xx * 4 + 1] + line2[xx * 4 + 5] + + line2[xx * 4 + 9] + line2[xx * 4 + 13] + line2[xx * 4 + 17] + + line3[xx * 4 + 1] + line3[xx * 4 + 5] + line3[xx * 4 + 9] + + line3[xx * 4 + 13] + line3[xx * 4 + 17] + line4[xx * 4 + 1] + + line4[xx * 4 + 5] + line4[xx * 4 + 9] + line4[xx * 4 + 13] + + line4[xx * 4 + 17]; + ss2 = line0[xx * 4 + 2] + line0[xx * 4 + 6] + line0[xx * 4 + 10] + + line0[xx * 4 + 14] + line0[xx * 4 + 18] + line1[xx * 4 + 2] + + line1[xx * 4 + 6] + line1[xx * 4 + 10] + line1[xx * 4 + 14] + + line1[xx * 4 + 18] + line2[xx * 4 + 2] + line2[xx * 4 + 6] + + line2[xx * 4 + 10] + line2[xx * 4 + 14] + line2[xx * 4 + 18] + + line3[xx * 4 + 2] + line3[xx * 4 + 6] + line3[xx * 4 + 10] + + line3[xx * 4 + 14] + line3[xx * 4 + 18] + line4[xx * 4 + 2] + + line4[xx * 4 + 6] + line4[xx * 4 + 10] + line4[xx * 4 + 14] + + line4[xx * 4 + 18]; + ss3 = line0[xx * 4 + 3] + line0[xx * 4 + 7] + line0[xx * 4 + 11] + + line0[xx * 4 + 15] + line0[xx * 4 + 19] + line1[xx * 4 + 3] + + line1[xx * 4 + 7] + line1[xx * 4 + 11] + line1[xx * 4 + 15] + + line1[xx * 4 + 19] + line2[xx * 4 + 3] + line2[xx * 4 + 7] + + line2[xx * 4 + 11] + line2[xx * 4 + 15] + line2[xx * 4 + 19] + + line3[xx * 4 + 3] + line3[xx * 4 + 7] + line3[xx * 4 + 11] + + line3[xx * 4 + 15] + line3[xx * 4 + 19] + line4[xx * 4 + 3] + + line4[xx * 4 + 7] + line4[xx * 4 + 11] + line4[xx * 4 + 15] + + line4[xx * 4 + 19]; v = MAKE_UINT32( - ((ss0 + amend) * multiplier) >> 24, ((ss1 + amend) * multiplier) >> 24, - ((ss2 + amend) * multiplier) >> 24, ((ss3 + amend) * multiplier) >> 24); + ((ss0 + amend) * multiplier) >> 24, + ((ss1 + amend) * multiplier) >> 24, + ((ss2 + amend) * multiplier) >> 24, + ((ss3 + amend) * multiplier) >> 24); memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v)); } } @@ -1056,12 +1100,10 @@ ImagingReduce5x5(Imaging imOut, Imaging imIn, int box[4]) } } - void -ImagingReduceCorners(Imaging imOut, Imaging imIn, int box[4], int xscale, int yscale) -{ +ImagingReduceCorners(Imaging imOut, Imaging imIn, int box[4], int xscale, int yscale) { /* Fill the last row and the last column for any xscale and yscale. - */ + */ int x, y, xx, yy; if (imIn->image8) { @@ -1070,13 +1112,13 @@ ImagingReduceCorners(Imaging imOut, Imaging imIn, int box[4], int xscale, int ys UINT32 multiplier = division_UINT32(scale, 8); UINT32 amend = scale / 2; for (y = 0; y < box[3] / yscale; y++) { - int yy_from = box[1] + y*yscale; + int yy_from = box[1] + y * yscale; UINT32 ss = amend; x = box[2] / xscale; for (yy = yy_from; yy < yy_from + yscale; yy++) { UINT8 *line = (UINT8 *)imIn->image8[yy]; - for (xx = box[0] + x*xscale; xx < box[0] + box[2]; xx++) { + for (xx = box[0] + x * xscale; xx < box[0] + box[2]; xx++) { ss += line[xx + 0]; } } @@ -1089,9 +1131,9 @@ ImagingReduceCorners(Imaging imOut, Imaging imIn, int box[4], int xscale, int ys UINT32 amend = scale / 2; y = box[3] / yscale; for (x = 0; x < box[2] / xscale; x++) { - int xx_from = box[0] + x*xscale; + int xx_from = box[0] + x * xscale; UINT32 ss = amend; - for (yy = box[1] + y*yscale; yy < box[1] + box[3]; yy++) { + for (yy = box[1] + y * yscale; yy < box[1] + box[3]; yy++) { UINT8 *line = (UINT8 *)imIn->image8[yy]; for (xx = xx_from; xx < xx_from + xscale; xx++) { ss += line[xx + 0]; @@ -1107,9 +1149,9 @@ ImagingReduceCorners(Imaging imOut, Imaging imIn, int box[4], int xscale, int ys UINT32 ss = amend; x = box[2] / xscale; y = box[3] / yscale; - for (yy = box[1] + y*yscale; yy < box[1] + box[3]; yy++) { + for (yy = box[1] + y * yscale; yy < box[1] + box[3]; yy++) { UINT8 *line = (UINT8 *)imIn->image8[yy]; - for (xx = box[0] + x*xscale; xx < box[0] + box[2]; xx++) { + for (xx = box[0] + x * xscale; xx < box[0] + box[2]; xx++) { ss += line[xx + 0]; } } @@ -1121,23 +1163,25 @@ ImagingReduceCorners(Imaging imOut, Imaging imIn, int box[4], int xscale, int ys UINT32 multiplier = division_UINT32(scale, 8); UINT32 amend = scale / 2; for (y = 0; y < box[3] / yscale; y++) { - int yy_from = box[1] + y*yscale; + int yy_from = box[1] + y * yscale; UINT32 v; UINT32 ss0 = amend, ss1 = amend, ss2 = amend, ss3 = amend; x = box[2] / xscale; for (yy = yy_from; yy < yy_from + yscale; yy++) { UINT8 *line = (UINT8 *)imIn->image[yy]; - for (xx = box[0] + x*xscale; xx < box[0] + box[2]; xx++) { - ss0 += line[xx*4 + 0]; - ss1 += line[xx*4 + 1]; - ss2 += line[xx*4 + 2]; - ss3 += line[xx*4 + 3]; + for (xx = box[0] + x * xscale; xx < box[0] + box[2]; xx++) { + ss0 += line[xx * 4 + 0]; + ss1 += line[xx * 4 + 1]; + ss2 += line[xx * 4 + 2]; + ss3 += line[xx * 4 + 3]; } } v = MAKE_UINT32( - (ss0 * multiplier) >> 24, (ss1 * multiplier) >> 24, - (ss2 * multiplier) >> 24, (ss3 * multiplier) >> 24); + (ss0 * multiplier) >> 24, + (ss1 * multiplier) >> 24, + (ss2 * multiplier) >> 24, + (ss3 * multiplier) >> 24); memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v)); } } @@ -1147,21 +1191,23 @@ ImagingReduceCorners(Imaging imOut, Imaging imIn, int box[4], int xscale, int ys UINT32 amend = scale / 2; y = box[3] / yscale; for (x = 0; x < box[2] / xscale; x++) { - int xx_from = box[0] + x*xscale; + int xx_from = box[0] + x * xscale; UINT32 v; UINT32 ss0 = amend, ss1 = amend, ss2 = amend, ss3 = amend; - for (yy = box[1] + y*yscale; yy < box[1] + box[3]; yy++) { + for (yy = box[1] + y * yscale; yy < box[1] + box[3]; yy++) { UINT8 *line = (UINT8 *)imIn->image[yy]; for (xx = xx_from; xx < xx_from + xscale; xx++) { - ss0 += line[xx*4 + 0]; - ss1 += line[xx*4 + 1]; - ss2 += line[xx*4 + 2]; - ss3 += line[xx*4 + 3]; + ss0 += line[xx * 4 + 0]; + ss1 += line[xx * 4 + 1]; + ss2 += line[xx * 4 + 2]; + ss3 += line[xx * 4 + 3]; } } v = MAKE_UINT32( - (ss0 * multiplier) >> 24, (ss1 * multiplier) >> 24, - (ss2 * multiplier) >> 24, (ss3 * multiplier) >> 24); + (ss0 * multiplier) >> 24, + (ss1 * multiplier) >> 24, + (ss2 * multiplier) >> 24, + (ss3 * multiplier) >> 24); memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v)); } } @@ -1173,263 +1219,262 @@ ImagingReduceCorners(Imaging imOut, Imaging imIn, int box[4], int xscale, int ys UINT32 ss0 = amend, ss1 = amend, ss2 = amend, ss3 = amend; x = box[2] / xscale; y = box[3] / yscale; - for (yy = box[1] + y*yscale; yy < box[1] + box[3]; yy++) { + for (yy = box[1] + y * yscale; yy < box[1] + box[3]; yy++) { UINT8 *line = (UINT8 *)imIn->image[yy]; - for (xx = box[0] + x*xscale; xx < box[0] + box[2]; xx++) { - ss0 += line[xx*4 + 0]; - ss1 += line[xx*4 + 1]; - ss2 += line[xx*4 + 2]; - ss3 += line[xx*4 + 3]; + for (xx = box[0] + x * xscale; xx < box[0] + box[2]; xx++) { + ss0 += line[xx * 4 + 0]; + ss1 += line[xx * 4 + 1]; + ss2 += line[xx * 4 + 2]; + ss3 += line[xx * 4 + 3]; } } v = MAKE_UINT32( - (ss0 * multiplier) >> 24, (ss1 * multiplier) >> 24, - (ss2 * multiplier) >> 24, (ss3 * multiplier) >> 24); + (ss0 * multiplier) >> 24, + (ss1 * multiplier) >> 24, + (ss2 * multiplier) >> 24, + (ss3 * multiplier) >> 24); memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v)); } } } - void -ImagingReduceNxN_32bpc(Imaging imOut, Imaging imIn, int box[4], int xscale, int yscale) -{ +ImagingReduceNxN_32bpc( + Imaging imOut, Imaging imIn, int box[4], int xscale, int yscale) { /* The most general implementation for any xscale and yscale - */ + */ int x, y, xx, yy; double multiplier = 1.0 / (yscale * xscale); - switch(imIn->type) { - case IMAGING_TYPE_INT32: - for (y = 0; y < box[3] / yscale; y++) { - int yy_from = box[1] + y*yscale; - for (x = 0; x < box[2] / xscale; x++) { - int xx_from = box[0] + x*xscale; - double ss = 0; - for (yy = yy_from; yy < yy_from + yscale - 1; yy += 2) { - INT32 *line0 = (INT32 *)imIn->image32[yy]; - INT32 *line1 = (INT32 *)imIn->image32[yy + 1]; - for (xx = xx_from; xx < xx_from + xscale - 1; xx += 2) { - ss += line0[xx + 0] + line0[xx + 1] + - line1[xx + 0] + line1[xx + 1]; + switch (imIn->type) { + case IMAGING_TYPE_INT32: + for (y = 0; y < box[3] / yscale; y++) { + int yy_from = box[1] + y * yscale; + for (x = 0; x < box[2] / xscale; x++) { + int xx_from = box[0] + x * xscale; + double ss = 0; + for (yy = yy_from; yy < yy_from + yscale - 1; yy += 2) { + INT32 *line0 = (INT32 *)imIn->image32[yy]; + INT32 *line1 = (INT32 *)imIn->image32[yy + 1]; + for (xx = xx_from; xx < xx_from + xscale - 1; xx += 2) { + ss += line0[xx + 0] + line0[xx + 1] + line1[xx + 0] + + line1[xx + 1]; + } + if (xscale & 0x01) { + ss += line0[xx + 0] + line1[xx + 0]; + } } - if (xscale & 0x01) { - ss += line0[xx + 0] + line1[xx + 0]; + if (yscale & 0x01) { + INT32 *line = (INT32 *)imIn->image32[yy]; + for (xx = xx_from; xx < xx_from + xscale - 1; xx += 2) { + ss += line[xx + 0] + line[xx + 1]; + } + if (xscale & 0x01) { + ss += line[xx + 0]; + } } + IMAGING_PIXEL_I(imOut, x, y) = ROUND_UP(ss * multiplier); } - if (yscale & 0x01) { - INT32 *line = (INT32 *)imIn->image32[yy]; - for (xx = xx_from; xx < xx_from + xscale - 1; xx += 2) { - ss += line[xx + 0] + line[xx + 1]; - } - if (xscale & 0x01) { - ss += line[xx + 0]; - } - } - IMAGING_PIXEL_I(imOut, x, y) = ROUND_UP(ss * multiplier); } - } - break; + break; - case IMAGING_TYPE_FLOAT32: - for (y = 0; y < box[3] / yscale; y++) { - int yy_from = box[1] + y*yscale; - for (x = 0; x < box[2] / xscale; x++) { - int xx_from = box[0] + x*xscale; - double ss = 0; - for (yy = yy_from; yy < yy_from + yscale - 1; yy += 2) { - FLOAT32 *line0 = (FLOAT32 *)imIn->image32[yy]; - FLOAT32 *line1 = (FLOAT32 *)imIn->image32[yy + 1]; - for (xx = xx_from; xx < xx_from + xscale - 1; xx += 2) { - ss += line0[xx + 0] + line0[xx + 1] + - line1[xx + 0] + line1[xx + 1]; + case IMAGING_TYPE_FLOAT32: + for (y = 0; y < box[3] / yscale; y++) { + int yy_from = box[1] + y * yscale; + for (x = 0; x < box[2] / xscale; x++) { + int xx_from = box[0] + x * xscale; + double ss = 0; + for (yy = yy_from; yy < yy_from + yscale - 1; yy += 2) { + FLOAT32 *line0 = (FLOAT32 *)imIn->image32[yy]; + FLOAT32 *line1 = (FLOAT32 *)imIn->image32[yy + 1]; + for (xx = xx_from; xx < xx_from + xscale - 1; xx += 2) { + ss += line0[xx + 0] + line0[xx + 1] + line1[xx + 0] + + line1[xx + 1]; + } + if (xscale & 0x01) { + ss += line0[xx + 0] + line1[xx + 0]; + } } - if (xscale & 0x01) { - ss += line0[xx + 0] + line1[xx + 0]; + if (yscale & 0x01) { + FLOAT32 *line = (FLOAT32 *)imIn->image32[yy]; + for (xx = xx_from; xx < xx_from + xscale - 1; xx += 2) { + ss += line[xx + 0] + line[xx + 1]; + } + if (xscale & 0x01) { + ss += line[xx + 0]; + } } + IMAGING_PIXEL_F(imOut, x, y) = ss * multiplier; } - if (yscale & 0x01) { - FLOAT32 *line = (FLOAT32 *)imIn->image32[yy]; - for (xx = xx_from; xx < xx_from + xscale - 1; xx += 2) { - ss += line[xx + 0] + line[xx + 1]; - } - if (xscale & 0x01) { - ss += line[xx + 0]; - } - } - IMAGING_PIXEL_F(imOut, x, y) = ss * multiplier; } - } - break; + break; } } - void -ImagingReduceCorners_32bpc(Imaging imOut, Imaging imIn, int box[4], int xscale, int yscale) -{ +ImagingReduceCorners_32bpc( + Imaging imOut, Imaging imIn, int box[4], int xscale, int yscale) { /* Fill the last row and the last column for any xscale and yscale. - */ + */ int x, y, xx, yy; - switch(imIn->type) { - case IMAGING_TYPE_INT32: - if (box[2] % xscale) { - double multiplier = 1.0 / ((box[2] % xscale) * yscale); - for (y = 0; y < box[3] / yscale; y++) { - int yy_from = box[1] + y*yscale; + switch (imIn->type) { + case IMAGING_TYPE_INT32: + if (box[2] % xscale) { + double multiplier = 1.0 / ((box[2] % xscale) * yscale); + for (y = 0; y < box[3] / yscale; y++) { + int yy_from = box[1] + y * yscale; + double ss = 0; + x = box[2] / xscale; + for (yy = yy_from; yy < yy_from + yscale; yy++) { + INT32 *line = (INT32 *)imIn->image32[yy]; + for (xx = box[0] + x * xscale; xx < box[0] + box[2]; xx++) { + ss += line[xx + 0]; + } + } + IMAGING_PIXEL_I(imOut, x, y) = ROUND_UP(ss * multiplier); + } + } + if (box[3] % yscale) { + double multiplier = 1.0 / (xscale * (box[3] % yscale)); + y = box[3] / yscale; + for (x = 0; x < box[2] / xscale; x++) { + int xx_from = box[0] + x * xscale; + double ss = 0; + for (yy = box[1] + y * yscale; yy < box[1] + box[3]; yy++) { + INT32 *line = (INT32 *)imIn->image32[yy]; + for (xx = xx_from; xx < xx_from + xscale; xx++) { + ss += line[xx + 0]; + } + } + IMAGING_PIXEL_I(imOut, x, y) = ROUND_UP(ss * multiplier); + } + } + if (box[2] % xscale && box[3] % yscale) { + double multiplier = 1.0 / ((box[2] % xscale) * (box[3] % yscale)); double ss = 0; x = box[2] / xscale; - for (yy = yy_from; yy < yy_from + yscale; yy++) { + y = box[3] / yscale; + for (yy = box[1] + y * yscale; yy < box[1] + box[3]; yy++) { INT32 *line = (INT32 *)imIn->image32[yy]; - for (xx = box[0] + x*xscale; xx < box[0] + box[2]; xx++) { + for (xx = box[0] + x * xscale; xx < box[0] + box[2]; xx++) { ss += line[xx + 0]; } } IMAGING_PIXEL_I(imOut, x, y) = ROUND_UP(ss * multiplier); } - } - if (box[3] % yscale) { - double multiplier = 1.0 / (xscale * (box[3] % yscale)); - y = box[3] / yscale; - for (x = 0; x < box[2] / xscale; x++) { - int xx_from = box[0] + x*xscale; - double ss = 0; - for (yy = box[1] + y*yscale; yy < box[1] + box[3]; yy++) { - INT32 *line = (INT32 *)imIn->image32[yy]; - for (xx = xx_from; xx < xx_from + xscale; xx++) { - ss += line[xx + 0]; - } - } - IMAGING_PIXEL_I(imOut, x, y) = ROUND_UP(ss * multiplier); - } - } - if (box[2] % xscale && box[3] % yscale) { - double multiplier = 1.0 / ((box[2] % xscale) * (box[3] % yscale)); - double ss = 0; - x = box[2] / xscale; - y = box[3] / yscale; - for (yy = box[1] + y*yscale; yy < box[1] + box[3]; yy++) { - INT32 *line = (INT32 *)imIn->image32[yy]; - for (xx = box[0] + x*xscale; xx < box[0] + box[2]; xx++) { - ss += line[xx + 0]; - } - } - IMAGING_PIXEL_I(imOut, x, y) = ROUND_UP(ss * multiplier); - } - break; + break; - case IMAGING_TYPE_FLOAT32: - if (box[2] % xscale) { - double multiplier = 1.0 / ((box[2] % xscale) * yscale); - for (y = 0; y < box[3] / yscale; y++) { - int yy_from = box[1] + y*yscale; + case IMAGING_TYPE_FLOAT32: + if (box[2] % xscale) { + double multiplier = 1.0 / ((box[2] % xscale) * yscale); + for (y = 0; y < box[3] / yscale; y++) { + int yy_from = box[1] + y * yscale; + double ss = 0; + x = box[2] / xscale; + for (yy = yy_from; yy < yy_from + yscale; yy++) { + FLOAT32 *line = (FLOAT32 *)imIn->image32[yy]; + for (xx = box[0] + x * xscale; xx < box[0] + box[2]; xx++) { + ss += line[xx + 0]; + } + } + IMAGING_PIXEL_F(imOut, x, y) = ss * multiplier; + } + } + if (box[3] % yscale) { + double multiplier = 1.0 / (xscale * (box[3] % yscale)); + y = box[3] / yscale; + for (x = 0; x < box[2] / xscale; x++) { + int xx_from = box[0] + x * xscale; + double ss = 0; + for (yy = box[1] + y * yscale; yy < box[1] + box[3]; yy++) { + FLOAT32 *line = (FLOAT32 *)imIn->image32[yy]; + for (xx = xx_from; xx < xx_from + xscale; xx++) { + ss += line[xx + 0]; + } + } + IMAGING_PIXEL_F(imOut, x, y) = ss * multiplier; + } + } + if (box[2] % xscale && box[3] % yscale) { + double multiplier = 1.0 / ((box[2] % xscale) * (box[3] % yscale)); double ss = 0; x = box[2] / xscale; - for (yy = yy_from; yy < yy_from + yscale; yy++) { + y = box[3] / yscale; + for (yy = box[1] + y * yscale; yy < box[1] + box[3]; yy++) { FLOAT32 *line = (FLOAT32 *)imIn->image32[yy]; - for (xx = box[0] + x*xscale; xx < box[0] + box[2]; xx++) { + for (xx = box[0] + x * xscale; xx < box[0] + box[2]; xx++) { ss += line[xx + 0]; } } IMAGING_PIXEL_F(imOut, x, y) = ss * multiplier; } - } - if (box[3] % yscale) { - double multiplier = 1.0 / (xscale * (box[3] % yscale)); - y = box[3] / yscale; - for (x = 0; x < box[2] / xscale; x++) { - int xx_from = box[0] + x*xscale; - double ss = 0; - for (yy = box[1] + y*yscale; yy < box[1] + box[3]; yy++) { - FLOAT32 *line = (FLOAT32 *)imIn->image32[yy]; - for (xx = xx_from; xx < xx_from + xscale; xx++) { - ss += line[xx + 0]; - } - } - IMAGING_PIXEL_F(imOut, x, y) = ss * multiplier; - } - } - if (box[2] % xscale && box[3] % yscale) { - double multiplier = 1.0 / ((box[2] % xscale) * (box[3] % yscale)); - double ss = 0; - x = box[2] / xscale; - y = box[3] / yscale; - for (yy = box[1] + y*yscale; yy < box[1] + box[3]; yy++) { - FLOAT32 *line = (FLOAT32 *)imIn->image32[yy]; - for (xx = box[0] + x*xscale; xx < box[0] + box[2]; xx++) { - ss += line[xx + 0]; - } - } - IMAGING_PIXEL_F(imOut, x, y) = ss * multiplier; - } - break; + break; } } - Imaging -ImagingReduce(Imaging imIn, int xscale, int yscale, int box[4]) -{ +ImagingReduce(Imaging imIn, int xscale, int yscale, int box[4]) { ImagingSectionCookie cookie; Imaging imOut = NULL; - if (strcmp(imIn->mode, "P") == 0 || strcmp(imIn->mode, "1") == 0) - return (Imaging) ImagingError_ModeError(); + if (strcmp(imIn->mode, "P") == 0 || strcmp(imIn->mode, "1") == 0) { + return (Imaging)ImagingError_ModeError(); + } - if (imIn->type == IMAGING_TYPE_SPECIAL) - return (Imaging) ImagingError_ModeError(); + if (imIn->type == IMAGING_TYPE_SPECIAL) { + return (Imaging)ImagingError_ModeError(); + } - imOut = ImagingNewDirty(imIn->mode, - (box[2] + xscale - 1) / xscale, - (box[3] + yscale - 1) / yscale); - if ( ! imOut) { + imOut = ImagingNewDirty( + imIn->mode, (box[2] + xscale - 1) / xscale, (box[3] + yscale - 1) / yscale); + if (!imOut) { return NULL; } ImagingSectionEnter(&cookie); - switch(imIn->type) { - case IMAGING_TYPE_UINT8: - if (xscale == 1) { - if (yscale == 2) { - ImagingReduce1x2(imOut, imIn, box); - } else if (yscale == 3) { - ImagingReduce1x3(imOut, imIn, box); + switch (imIn->type) { + case IMAGING_TYPE_UINT8: + if (xscale == 1) { + if (yscale == 2) { + ImagingReduce1x2(imOut, imIn, box); + } else if (yscale == 3) { + ImagingReduce1x3(imOut, imIn, box); + } else { + ImagingReduce1xN(imOut, imIn, box, yscale); + } + } else if (yscale == 1) { + if (xscale == 2) { + ImagingReduce2x1(imOut, imIn, box); + } else if (xscale == 3) { + ImagingReduce3x1(imOut, imIn, box); + } else { + ImagingReduceNx1(imOut, imIn, box, xscale); + } + } else if (xscale == yscale && xscale <= 5) { + if (xscale == 2) { + ImagingReduce2x2(imOut, imIn, box); + } else if (xscale == 3) { + ImagingReduce3x3(imOut, imIn, box); + } else if (xscale == 4) { + ImagingReduce4x4(imOut, imIn, box); + } else { + ImagingReduce5x5(imOut, imIn, box); + } } else { - ImagingReduce1xN(imOut, imIn, box, yscale); + ImagingReduceNxN(imOut, imIn, box, xscale, yscale); } - } else if (yscale == 1) { - if (xscale == 2) { - ImagingReduce2x1(imOut, imIn, box); - } else if (xscale == 3) { - ImagingReduce3x1(imOut, imIn, box); - } else { - ImagingReduceNx1(imOut, imIn, box, xscale); - } - } else if (xscale == yscale && xscale <= 5) { - if (xscale == 2) { - ImagingReduce2x2(imOut, imIn, box); - } else if (xscale == 3) { - ImagingReduce3x3(imOut, imIn, box); - } else if (xscale == 4) { - ImagingReduce4x4(imOut, imIn, box); - } else { - ImagingReduce5x5(imOut, imIn, box); - } - } else { - ImagingReduceNxN(imOut, imIn, box, xscale, yscale); - } - ImagingReduceCorners(imOut, imIn, box, xscale, yscale); - break; + ImagingReduceCorners(imOut, imIn, box, xscale, yscale); + break; - case IMAGING_TYPE_INT32: - case IMAGING_TYPE_FLOAT32: - ImagingReduceNxN_32bpc(imOut, imIn, box, xscale, yscale); + case IMAGING_TYPE_INT32: + case IMAGING_TYPE_FLOAT32: + ImagingReduceNxN_32bpc(imOut, imIn, box, xscale, yscale); - ImagingReduceCorners_32bpc(imOut, imIn, box, xscale, yscale); - break; + ImagingReduceCorners_32bpc(imOut, imIn, box, xscale, yscale); + break; } ImagingSectionLeave(&cookie); diff --git a/src/libImaging/Resample.c b/src/libImaging/Resample.c index 0dc08611d..cf79d8a4e 100644 --- a/src/libImaging/Resample.c +++ b/src/libImaging/Resample.c @@ -2,79 +2,88 @@ #include - -#define ROUND_UP(f) ((int) ((f) >= 0.0 ? (f) + 0.5F : (f) - 0.5F)) - +#define ROUND_UP(f) ((int)((f) >= 0.0 ? (f) + 0.5F : (f)-0.5F)) struct filter { double (*filter)(double x); double support; }; -static inline double box_filter(double x) -{ - if (x > -0.5 && x <= 0.5) +static inline double +box_filter(double x) { + if (x > -0.5 && x <= 0.5) { return 1.0; + } return 0.0; } -static inline double bilinear_filter(double x) -{ - if (x < 0.0) +static inline double +bilinear_filter(double x) { + if (x < 0.0) { x = -x; - if (x < 1.0) - return 1.0-x; + } + if (x < 1.0) { + return 1.0 - x; + } return 0.0; } -static inline double hamming_filter(double x) -{ - if (x < 0.0) +static inline double +hamming_filter(double x) { + if (x < 0.0) { x = -x; - if (x == 0.0) + } + if (x == 0.0) { return 1.0; - if (x >= 1.0) + } + if (x >= 1.0) { return 0.0; + } x = x * M_PI; return sin(x) / x * (0.54f + 0.46f * cos(x)); } -static inline double bicubic_filter(double x) -{ - /* https://en.wikipedia.org/wiki/Bicubic_interpolation#Bicubic_convolution_algorithm */ +static inline double +bicubic_filter(double x) { + /* https://en.wikipedia.org/wiki/Bicubic_interpolation#Bicubic_convolution_algorithm + */ #define a -0.5 - if (x < 0.0) + if (x < 0.0) { x = -x; - if (x < 1.0) - return ((a + 2.0) * x - (a + 3.0)) * x*x + 1; - if (x < 2.0) + } + if (x < 1.0) { + return ((a + 2.0) * x - (a + 3.0)) * x * x + 1; + } + if (x < 2.0) { return (((x - 5) * x + 8) * x - 4) * a; + } return 0.0; #undef a } -static inline double sinc_filter(double x) -{ - if (x == 0.0) +static inline double +sinc_filter(double x) { + if (x == 0.0) { return 1.0; + } x = x * M_PI; return sin(x) / x; } -static inline double lanczos_filter(double x) -{ +static inline double +lanczos_filter(double x) { /* truncated sinc */ - if (-3.0 <= x && x < 3.0) - return sinc_filter(x) * sinc_filter(x/3); + if (-3.0 <= x && x < 3.0) { + return sinc_filter(x) * sinc_filter(x / 3); + } return 0.0; } -static struct filter BOX = { box_filter, 0.5 }; -static struct filter BILINEAR = { bilinear_filter, 1.0 }; -static struct filter HAMMING = { hamming_filter, 1.0 }; -static struct filter BICUBIC = { bicubic_filter, 2.0 }; -static struct filter LANCZOS = { lanczos_filter, 3.0 }; - +static struct filter BOX = {box_filter, 0.5}; +static struct filter BILINEAR = {bilinear_filter, 1.0}; +static struct filter HAMMING = {hamming_filter, 1.0}; +static struct filter BICUBIC = {bicubic_filter, 2.0}; +static struct filter LANCZOS = {lanczos_filter, 3.0}; /* 8 bits for result. Filter can have negative areas. In one cases the sum of the coefficients will be negative, @@ -82,102 +91,102 @@ static struct filter LANCZOS = { lanczos_filter, 3.0 }; two extra bits for overflow and int type. */ #define PRECISION_BITS (32 - 8 - 2) - /* Handles values form -640 to 639. */ UINT8 _clip8_lookups[1280] = { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, - 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, - 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, - 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, - 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, - 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, - 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, - 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, - 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, - 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, - 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, - 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, - 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, - 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, - 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, - 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, + 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, + 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, + 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, + 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, + 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, + 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, + 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, + 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, + 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, + 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, + 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, + 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, + 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, }; UINT8 *clip8_lookups = &_clip8_lookups[640]; -static inline UINT8 clip8(int in) -{ +static inline UINT8 +clip8(int in) { return clip8_lookups[in >> PRECISION_BITS]; } - int -precompute_coeffs(int inSize, float in0, float in1, int outSize, - struct filter *filterp, int **boundsp, double **kkp) { +precompute_coeffs( + int inSize, + float in0, + float in1, + int outSize, + struct filter *filterp, + int **boundsp, + double **kkp) { double support, scale, filterscale; double center, ww, ss; int xx, x, ksize, xmin, xmax; @@ -185,7 +194,7 @@ precompute_coeffs(int inSize, float in0, float in1, int outSize, double *kk, *k; /* prepare for horizontal stretch */ - filterscale = scale = (double) (in1 - in0) / outSize; + filterscale = scale = (double)(in1 - in0) / outSize; if (filterscale < 1.0) { filterscale = 1.0; } @@ -194,10 +203,10 @@ precompute_coeffs(int inSize, float in0, float in1, int outSize, support = filterp->support * filterscale; /* maximum number of coeffs */ - ksize = (int) ceil(support) * 2 + 1; + ksize = (int)ceil(support) * 2 + 1; // check for overflow - if (outSize > INT_MAX / (ksize * sizeof(double))) { + if (outSize > INT_MAX / (ksize * (int)sizeof(double))) { ImagingError_MemoryError(); return 0; } @@ -205,14 +214,14 @@ precompute_coeffs(int inSize, float in0, float in1, int outSize, /* coefficient buffer */ /* malloc check ok, overflow checked above */ kk = malloc(outSize * ksize * sizeof(double)); - if ( ! kk) { + if (!kk) { ImagingError_MemoryError(); return 0; } /* malloc check ok, ksize*sizeof(double) > 2*sizeof(int) */ bounds = malloc(outSize * 2 * sizeof(int)); - if ( ! bounds) { + if (!bounds) { free(kk); ImagingError_MemoryError(); return 0; @@ -223,13 +232,15 @@ precompute_coeffs(int inSize, float in0, float in1, int outSize, ww = 0.0; ss = 1.0 / filterscale; // Round the value - xmin = (int) (center - support + 0.5); - if (xmin < 0) + xmin = (int)(center - support + 0.5); + if (xmin < 0) { xmin = 0; + } // Round the value - xmax = (int) (center + support + 0.5); - if (xmax > inSize) + xmax = (int)(center + support + 0.5); + if (xmax > inSize) { xmax = inSize; + } xmax -= xmin; k = &kk[xx * ksize]; for (x = 0; x < xmax; x++) { @@ -238,8 +249,9 @@ precompute_coeffs(int inSize, float in0, float in1, int outSize, ww += w; } for (x = 0; x < xmax; x++) { - if (ww != 0.0) + if (ww != 0.0) { k[x] /= ww; + } } // Remaining values should stay empty if they are used despite of xmax. for (; x < ksize; x++) { @@ -253,38 +265,33 @@ precompute_coeffs(int inSize, float in0, float in1, int outSize, return ksize; } - void -normalize_coeffs_8bpc(int outSize, int ksize, double *prekk) -{ +normalize_coeffs_8bpc(int outSize, int ksize, double *prekk) { int x; INT32 *kk; // use the same buffer for normalized coefficients - kk = (INT32 *) prekk; + kk = (INT32 *)prekk; for (x = 0; x < outSize * ksize; x++) { if (prekk[x] < 0) { - kk[x] = (int) (-0.5 + prekk[x] * (1 << PRECISION_BITS)); + kk[x] = (int)(-0.5 + prekk[x] * (1 << PRECISION_BITS)); } else { - kk[x] = (int) (0.5 + prekk[x] * (1 << PRECISION_BITS)); + kk[x] = (int)(0.5 + prekk[x] * (1 << PRECISION_BITS)); } } } - - void -ImagingResampleHorizontal_8bpc(Imaging imOut, Imaging imIn, int offset, - int ksize, int *bounds, double *prekk) -{ +ImagingResampleHorizontal_8bpc( + Imaging imOut, Imaging imIn, int offset, int ksize, int *bounds, double *prekk) { ImagingSectionCookie cookie; int ss0, ss1, ss2, ss3; int xx, yy, x, xmin, xmax; INT32 *k, *kk; // use the same buffer for normalized coefficients - kk = (INT32 *) prekk; + kk = (INT32 *)prekk; normalize_coeffs_8bpc(imOut->xsize, ksize, prekk); ImagingSectionEnter(&cookie); @@ -294,9 +301,10 @@ ImagingResampleHorizontal_8bpc(Imaging imOut, Imaging imIn, int offset, xmin = bounds[xx * 2 + 0]; xmax = bounds[xx * 2 + 1]; k = &kk[xx * ksize]; - ss0 = 1 << (PRECISION_BITS -1); - for (x = 0; x < xmax; x++) - ss0 += ((UINT8) imIn->image8[yy + offset][x + xmin]) * k[x]; + ss0 = 1 << (PRECISION_BITS - 1); + for (x = 0; x < xmax; x++) { + ss0 += ((UINT8)imIn->image8[yy + offset][x + xmin]) * k[x]; + } imOut->image8[yy][xx] = clip8(ss0); } } @@ -308,10 +316,12 @@ ImagingResampleHorizontal_8bpc(Imaging imOut, Imaging imIn, int offset, xmin = bounds[xx * 2 + 0]; xmax = bounds[xx * 2 + 1]; k = &kk[xx * ksize]; - ss0 = ss3 = 1 << (PRECISION_BITS -1); + ss0 = ss3 = 1 << (PRECISION_BITS - 1); for (x = 0; x < xmax; x++) { - ss0 += ((UINT8) imIn->image[yy + offset][(x + xmin)*4 + 0]) * k[x]; - ss3 += ((UINT8) imIn->image[yy + offset][(x + xmin)*4 + 3]) * k[x]; + ss0 += ((UINT8)imIn->image[yy + offset][(x + xmin) * 4 + 0]) * + k[x]; + ss3 += ((UINT8)imIn->image[yy + offset][(x + xmin) * 4 + 3]) * + k[x]; } v = MAKE_UINT32(clip8(ss0), 0, 0, clip8(ss3)); memcpy(imOut->image[yy] + xx * sizeof(v), &v, sizeof(v)); @@ -324,11 +334,14 @@ ImagingResampleHorizontal_8bpc(Imaging imOut, Imaging imIn, int offset, xmin = bounds[xx * 2 + 0]; xmax = bounds[xx * 2 + 1]; k = &kk[xx * ksize]; - ss0 = ss1 = ss2 = 1 << (PRECISION_BITS -1); + ss0 = ss1 = ss2 = 1 << (PRECISION_BITS - 1); for (x = 0; x < xmax; x++) { - ss0 += ((UINT8) imIn->image[yy + offset][(x + xmin)*4 + 0]) * k[x]; - ss1 += ((UINT8) imIn->image[yy + offset][(x + xmin)*4 + 1]) * k[x]; - ss2 += ((UINT8) imIn->image[yy + offset][(x + xmin)*4 + 2]) * k[x]; + ss0 += ((UINT8)imIn->image[yy + offset][(x + xmin) * 4 + 0]) * + k[x]; + ss1 += ((UINT8)imIn->image[yy + offset][(x + xmin) * 4 + 1]) * + k[x]; + ss2 += ((UINT8)imIn->image[yy + offset][(x + xmin) * 4 + 2]) * + k[x]; } v = MAKE_UINT32(clip8(ss0), clip8(ss1), clip8(ss2), 0); memcpy(imOut->image[yy] + xx * sizeof(v), &v, sizeof(v)); @@ -341,12 +354,16 @@ ImagingResampleHorizontal_8bpc(Imaging imOut, Imaging imIn, int offset, xmin = bounds[xx * 2 + 0]; xmax = bounds[xx * 2 + 1]; k = &kk[xx * ksize]; - ss0 = ss1 = ss2 = ss3 = 1 << (PRECISION_BITS -1); + ss0 = ss1 = ss2 = ss3 = 1 << (PRECISION_BITS - 1); for (x = 0; x < xmax; x++) { - ss0 += ((UINT8) imIn->image[yy + offset][(x + xmin)*4 + 0]) * k[x]; - ss1 += ((UINT8) imIn->image[yy + offset][(x + xmin)*4 + 1]) * k[x]; - ss2 += ((UINT8) imIn->image[yy + offset][(x + xmin)*4 + 2]) * k[x]; - ss3 += ((UINT8) imIn->image[yy + offset][(x + xmin)*4 + 3]) * k[x]; + ss0 += ((UINT8)imIn->image[yy + offset][(x + xmin) * 4 + 0]) * + k[x]; + ss1 += ((UINT8)imIn->image[yy + offset][(x + xmin) * 4 + 1]) * + k[x]; + ss2 += ((UINT8)imIn->image[yy + offset][(x + xmin) * 4 + 2]) * + k[x]; + ss3 += ((UINT8)imIn->image[yy + offset][(x + xmin) * 4 + 3]) * + k[x]; } v = MAKE_UINT32(clip8(ss0), clip8(ss1), clip8(ss2), clip8(ss3)); memcpy(imOut->image[yy] + xx * sizeof(v), &v, sizeof(v)); @@ -357,18 +374,16 @@ ImagingResampleHorizontal_8bpc(Imaging imOut, Imaging imIn, int offset, ImagingSectionLeave(&cookie); } - void -ImagingResampleVertical_8bpc(Imaging imOut, Imaging imIn, int offset, - int ksize, int *bounds, double *prekk) -{ +ImagingResampleVertical_8bpc( + Imaging imOut, Imaging imIn, int offset, int ksize, int *bounds, double *prekk) { ImagingSectionCookie cookie; int ss0, ss1, ss2, ss3; int xx, yy, y, ymin, ymax; INT32 *k, *kk; // use the same buffer for normalized coefficients - kk = (INT32 *) prekk; + kk = (INT32 *)prekk; normalize_coeffs_8bpc(imOut->ysize, ksize, prekk); ImagingSectionEnter(&cookie); @@ -378,9 +393,10 @@ ImagingResampleVertical_8bpc(Imaging imOut, Imaging imIn, int offset, ymin = bounds[yy * 2 + 0]; ymax = bounds[yy * 2 + 1]; for (xx = 0; xx < imOut->xsize; xx++) { - ss0 = 1 << (PRECISION_BITS -1); - for (y = 0; y < ymax; y++) - ss0 += ((UINT8) imIn->image8[y + ymin][xx]) * k[y]; + ss0 = 1 << (PRECISION_BITS - 1); + for (y = 0; y < ymax; y++) { + ss0 += ((UINT8)imIn->image8[y + ymin][xx]) * k[y]; + } imOut->image8[yy][xx] = clip8(ss0); } } @@ -392,10 +408,10 @@ ImagingResampleVertical_8bpc(Imaging imOut, Imaging imIn, int offset, ymax = bounds[yy * 2 + 1]; for (xx = 0; xx < imOut->xsize; xx++) { UINT32 v; - ss0 = ss3 = 1 << (PRECISION_BITS -1); + ss0 = ss3 = 1 << (PRECISION_BITS - 1); for (y = 0; y < ymax; y++) { - ss0 += ((UINT8) imIn->image[y + ymin][xx*4 + 0]) * k[y]; - ss3 += ((UINT8) imIn->image[y + ymin][xx*4 + 3]) * k[y]; + ss0 += ((UINT8)imIn->image[y + ymin][xx * 4 + 0]) * k[y]; + ss3 += ((UINT8)imIn->image[y + ymin][xx * 4 + 3]) * k[y]; } v = MAKE_UINT32(clip8(ss0), 0, 0, clip8(ss3)); memcpy(imOut->image[yy] + xx * sizeof(v), &v, sizeof(v)); @@ -408,11 +424,11 @@ ImagingResampleVertical_8bpc(Imaging imOut, Imaging imIn, int offset, ymax = bounds[yy * 2 + 1]; for (xx = 0; xx < imOut->xsize; xx++) { UINT32 v; - ss0 = ss1 = ss2 = 1 << (PRECISION_BITS -1); + ss0 = ss1 = ss2 = 1 << (PRECISION_BITS - 1); for (y = 0; y < ymax; y++) { - ss0 += ((UINT8) imIn->image[y + ymin][xx*4 + 0]) * k[y]; - ss1 += ((UINT8) imIn->image[y + ymin][xx*4 + 1]) * k[y]; - ss2 += ((UINT8) imIn->image[y + ymin][xx*4 + 2]) * k[y]; + ss0 += ((UINT8)imIn->image[y + ymin][xx * 4 + 0]) * k[y]; + ss1 += ((UINT8)imIn->image[y + ymin][xx * 4 + 1]) * k[y]; + ss2 += ((UINT8)imIn->image[y + ymin][xx * 4 + 2]) * k[y]; } v = MAKE_UINT32(clip8(ss0), clip8(ss1), clip8(ss2), 0); memcpy(imOut->image[yy] + xx * sizeof(v), &v, sizeof(v)); @@ -425,12 +441,12 @@ ImagingResampleVertical_8bpc(Imaging imOut, Imaging imIn, int offset, ymax = bounds[yy * 2 + 1]; for (xx = 0; xx < imOut->xsize; xx++) { UINT32 v; - ss0 = ss1 = ss2 = ss3 = 1 << (PRECISION_BITS -1); + ss0 = ss1 = ss2 = ss3 = 1 << (PRECISION_BITS - 1); for (y = 0; y < ymax; y++) { - ss0 += ((UINT8) imIn->image[y + ymin][xx*4 + 0]) * k[y]; - ss1 += ((UINT8) imIn->image[y + ymin][xx*4 + 1]) * k[y]; - ss2 += ((UINT8) imIn->image[y + ymin][xx*4 + 2]) * k[y]; - ss3 += ((UINT8) imIn->image[y + ymin][xx*4 + 3]) * k[y]; + ss0 += ((UINT8)imIn->image[y + ymin][xx * 4 + 0]) * k[y]; + ss1 += ((UINT8)imIn->image[y + ymin][xx * 4 + 1]) * k[y]; + ss2 += ((UINT8)imIn->image[y + ymin][xx * 4 + 2]) * k[y]; + ss3 += ((UINT8)imIn->image[y + ymin][xx * 4 + 3]) * k[y]; } v = MAKE_UINT32(clip8(ss0), clip8(ss1), clip8(ss2), clip8(ss3)); memcpy(imOut->image[yy] + xx * sizeof(v), &v, sizeof(v)); @@ -441,18 +457,16 @@ ImagingResampleVertical_8bpc(Imaging imOut, Imaging imIn, int offset, ImagingSectionLeave(&cookie); } - void -ImagingResampleHorizontal_32bpc(Imaging imOut, Imaging imIn, int offset, - int ksize, int *bounds, double *kk) -{ +ImagingResampleHorizontal_32bpc( + Imaging imOut, Imaging imIn, int offset, int ksize, int *bounds, double *kk) { ImagingSectionCookie cookie; double ss; int xx, yy, x, xmin, xmax; double *k; ImagingSectionEnter(&cookie); - switch(imIn->type) { + switch (imIn->type) { case IMAGING_TYPE_INT32: for (yy = 0; yy < imOut->ysize; yy++) { for (xx = 0; xx < imOut->xsize; xx++) { @@ -460,8 +474,9 @@ ImagingResampleHorizontal_32bpc(Imaging imOut, Imaging imIn, int offset, xmax = bounds[xx * 2 + 1]; k = &kk[xx * ksize]; ss = 0.0; - for (x = 0; x < xmax; x++) + for (x = 0; x < xmax; x++) { ss += IMAGING_PIXEL_I(imIn, x + xmin, yy + offset) * k[x]; + } IMAGING_PIXEL_I(imOut, xx, yy) = ROUND_UP(ss); } } @@ -474,8 +489,9 @@ ImagingResampleHorizontal_32bpc(Imaging imOut, Imaging imIn, int offset, xmax = bounds[xx * 2 + 1]; k = &kk[xx * ksize]; ss = 0.0; - for (x = 0; x < xmax; x++) + for (x = 0; x < xmax; x++) { ss += IMAGING_PIXEL_F(imIn, x + xmin, yy + offset) * k[x]; + } IMAGING_PIXEL_F(imOut, xx, yy) = ss; } } @@ -484,18 +500,16 @@ ImagingResampleHorizontal_32bpc(Imaging imOut, Imaging imIn, int offset, ImagingSectionLeave(&cookie); } - void -ImagingResampleVertical_32bpc(Imaging imOut, Imaging imIn, int offset, - int ksize, int *bounds, double *kk) -{ +ImagingResampleVertical_32bpc( + Imaging imOut, Imaging imIn, int offset, int ksize, int *bounds, double *kk) { ImagingSectionCookie cookie; double ss; int xx, yy, y, ymin, ymax; double *k; ImagingSectionEnter(&cookie); - switch(imIn->type) { + switch (imIn->type) { case IMAGING_TYPE_INT32: for (yy = 0; yy < imOut->ysize; yy++) { ymin = bounds[yy * 2 + 0]; @@ -503,8 +517,9 @@ ImagingResampleVertical_32bpc(Imaging imOut, Imaging imIn, int offset, k = &kk[yy * ksize]; for (xx = 0; xx < imOut->xsize; xx++) { ss = 0.0; - for (y = 0; y < ymax; y++) + for (y = 0; y < ymax; y++) { ss += IMAGING_PIXEL_I(imIn, xx, y + ymin) * k[y]; + } IMAGING_PIXEL_I(imOut, xx, yy) = ROUND_UP(ss); } } @@ -517,8 +532,9 @@ ImagingResampleVertical_32bpc(Imaging imOut, Imaging imIn, int offset, k = &kk[yy * ksize]; for (xx = 0; xx < imOut->xsize; xx++) { ss = 0.0; - for (y = 0; y < ymax; y++) + for (y = 0; y < ymax; y++) { ss += IMAGING_PIXEL_F(imIn, xx, y + ymin) * k[y]; + } IMAGING_PIXEL_F(imOut, xx, yy) = ss; } } @@ -527,35 +543,36 @@ ImagingResampleVertical_32bpc(Imaging imOut, Imaging imIn, int offset, ImagingSectionLeave(&cookie); } - -typedef void (*ResampleFunction)(Imaging imOut, Imaging imIn, int offset, - int ksize, int *bounds, double *kk); - +typedef void (*ResampleFunction)( + Imaging imOut, Imaging imIn, int offset, int ksize, int *bounds, double *kk); Imaging -ImagingResampleInner(Imaging imIn, int xsize, int ysize, - struct filter *filterp, float box[4], - ResampleFunction ResampleHorizontal, - ResampleFunction ResampleVertical); - +ImagingResampleInner( + Imaging imIn, + int xsize, + int ysize, + struct filter *filterp, + float box[4], + ResampleFunction ResampleHorizontal, + ResampleFunction ResampleVertical); Imaging -ImagingResample(Imaging imIn, int xsize, int ysize, int filter, float box[4]) -{ +ImagingResample(Imaging imIn, int xsize, int ysize, int filter, float box[4]) { struct filter *filterp; ResampleFunction ResampleHorizontal; ResampleFunction ResampleVertical; - if (strcmp(imIn->mode, "P") == 0 || strcmp(imIn->mode, "1") == 0) - return (Imaging) ImagingError_ModeError(); + if (strcmp(imIn->mode, "P") == 0 || strcmp(imIn->mode, "1") == 0) { + return (Imaging)ImagingError_ModeError(); + } if (imIn->type == IMAGING_TYPE_SPECIAL) { - return (Imaging) ImagingError_ModeError(); + return (Imaging)ImagingError_ModeError(); } else if (imIn->image8) { ResampleHorizontal = ImagingResampleHorizontal_8bpc; ResampleVertical = ImagingResampleVertical_8bpc; } else { - switch(imIn->type) { + switch (imIn->type) { case IMAGING_TYPE_UINT8: ResampleHorizontal = ImagingResampleHorizontal_8bpc; ResampleVertical = ImagingResampleVertical_8bpc; @@ -566,44 +583,44 @@ ImagingResample(Imaging imIn, int xsize, int ysize, int filter, float box[4]) ResampleVertical = ImagingResampleVertical_32bpc; break; default: - return (Imaging) ImagingError_ModeError(); + return (Imaging)ImagingError_ModeError(); } } /* check filter */ switch (filter) { - case IMAGING_TRANSFORM_BOX: - filterp = &BOX; - break; - case IMAGING_TRANSFORM_BILINEAR: - filterp = &BILINEAR; - break; - case IMAGING_TRANSFORM_HAMMING: - filterp = &HAMMING; - break; - case IMAGING_TRANSFORM_BICUBIC: - filterp = &BICUBIC; - break; - case IMAGING_TRANSFORM_LANCZOS: - filterp = &LANCZOS; - break; - default: - return (Imaging) ImagingError_ValueError( - "unsupported resampling filter" - ); + case IMAGING_TRANSFORM_BOX: + filterp = &BOX; + break; + case IMAGING_TRANSFORM_BILINEAR: + filterp = &BILINEAR; + break; + case IMAGING_TRANSFORM_HAMMING: + filterp = &HAMMING; + break; + case IMAGING_TRANSFORM_BICUBIC: + filterp = &BICUBIC; + break; + case IMAGING_TRANSFORM_LANCZOS: + filterp = &LANCZOS; + break; + default: + return (Imaging)ImagingError_ValueError("unsupported resampling filter"); } - return ImagingResampleInner(imIn, xsize, ysize, filterp, box, - ResampleHorizontal, ResampleVertical); + return ImagingResampleInner( + imIn, xsize, ysize, filterp, box, ResampleHorizontal, ResampleVertical); } - Imaging -ImagingResampleInner(Imaging imIn, int xsize, int ysize, - struct filter *filterp, float box[4], - ResampleFunction ResampleHorizontal, - ResampleFunction ResampleVertical) -{ +ImagingResampleInner( + Imaging imIn, + int xsize, + int ysize, + struct filter *filterp, + float box[4], + ResampleFunction ResampleHorizontal, + ResampleFunction ResampleVertical) { Imaging imTemp = NULL; Imaging imOut = NULL; @@ -616,15 +633,15 @@ ImagingResampleInner(Imaging imIn, int xsize, int ysize, need_horizontal = xsize != imIn->xsize || box[0] || box[2] != xsize; need_vertical = ysize != imIn->ysize || box[1] || box[3] != ysize; - ksize_horiz = precompute_coeffs(imIn->xsize, box[0], box[2], xsize, - filterp, &bounds_horiz, &kk_horiz); - if ( ! ksize_horiz) { + ksize_horiz = precompute_coeffs( + imIn->xsize, box[0], box[2], xsize, filterp, &bounds_horiz, &kk_horiz); + if (!ksize_horiz) { return NULL; } - ksize_vert = precompute_coeffs(imIn->ysize, box[1], box[3], ysize, - filterp, &bounds_vert, &kk_vert); - if ( ! ksize_vert) { + ksize_vert = precompute_coeffs( + imIn->ysize, box[1], box[3], ysize, filterp, &bounds_vert, &kk_vert); + if (!ksize_vert) { free(bounds_horiz); free(kk_horiz); return NULL; @@ -633,8 +650,7 @@ ImagingResampleInner(Imaging imIn, int xsize, int ysize, // First used row in the source image ybox_first = bounds_vert[0]; // Last used row in the source image - ybox_last = bounds_vert[ysize*2 - 2] + bounds_vert[ysize*2 - 1]; - + ybox_last = bounds_vert[ysize * 2 - 2] + bounds_vert[ysize * 2 - 1]; /* two-pass resize, horizontal pass */ if (need_horizontal) { @@ -645,12 +661,12 @@ ImagingResampleInner(Imaging imIn, int xsize, int ysize, imTemp = ImagingNewDirty(imIn->mode, xsize, ybox_last - ybox_first); if (imTemp) { - ResampleHorizontal(imTemp, imIn, ybox_first, - ksize_horiz, bounds_horiz, kk_horiz); + ResampleHorizontal( + imTemp, imIn, ybox_first, ksize_horiz, bounds_horiz, kk_horiz); } free(bounds_horiz); free(kk_horiz); - if ( ! imTemp) { + if (!imTemp) { free(bounds_vert); free(kk_vert); return NULL; @@ -667,15 +683,14 @@ ImagingResampleInner(Imaging imIn, int xsize, int ysize, imOut = ImagingNewDirty(imIn->mode, imIn->xsize, ysize); if (imOut) { /* imIn can be the original image or horizontally resampled one */ - ResampleVertical(imOut, imIn, 0, - ksize_vert, bounds_vert, kk_vert); + ResampleVertical(imOut, imIn, 0, ksize_vert, bounds_vert, kk_vert); } /* it's safe to call ImagingDelete with empty value if previous step was not performed. */ ImagingDelete(imTemp); free(bounds_vert); free(kk_vert); - if ( ! imOut) { + if (!imOut) { return NULL; } } else { @@ -685,7 +700,7 @@ ImagingResampleInner(Imaging imIn, int xsize, int ysize, } /* none of the previous steps are performed, copying */ - if ( ! imOut) { + if (!imOut) { imOut = ImagingCopy(imIn); } diff --git a/src/libImaging/Sgi.h b/src/libImaging/Sgi.h index 8015d6661..39dd68825 100644 --- a/src/libImaging/Sgi.h +++ b/src/libImaging/Sgi.h @@ -1,7 +1,6 @@ /* Sgi.h */ typedef struct { - /* CONFIGURATION */ /* Number of bytes per channel per pixel */ diff --git a/src/libImaging/SgiRleDecode.c b/src/libImaging/SgiRleDecode.c index 1ba56b8c7..c19231e02 100644 --- a/src/libImaging/SgiRleDecode.c +++ b/src/libImaging/SgiRleDecode.c @@ -6,7 +6,7 @@ * * history: * 2017-07-28 mb fixed for images larger than 64KB - * 2017-07-20 mb created + * 2017-07-20 mb created * * Copyright (c) Mickael Bonfill 2017. * @@ -20,102 +20,119 @@ #define RLE_COPY_FLAG 0x80 #define RLE_MAX_RUN 0x7f -static void read4B(UINT32* dest, UINT8* buf) -{ +static void +read4B(UINT32 *dest, UINT8 *buf) { *dest = (UINT32)((buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3]); } -static int expandrow(UINT8* dest, UINT8* src, int n, int z, int xsize) -{ +static int +expandrow(UINT8 *dest, UINT8 *src, int n, int z, int xsize) { UINT8 pixel, count; + int x = 0; - for (;n > 0; n--) - { + for (; n > 0; n--) { pixel = *src++; - if (n == 1 && pixel != 0) + if (n == 1 && pixel != 0) { return n; + } count = pixel & RLE_MAX_RUN; - if (!count) + if (!count) { return count; - if (count > xsize) { + } + if (x + count > xsize) { return -1; } + x += count; if (pixel & RLE_COPY_FLAG) { - while(count--) { + while (count--) { *dest = *src++; dest += z; } - } - else { + } else { pixel = *src++; while (count--) { *dest = pixel; dest += z; } } - } return 0; } -static int expandrow2(UINT8* dest, const UINT8* src, int n, int z, int xsize) -{ +static int +expandrow2(UINT8 *dest, const UINT8 *src, int n, int z, int xsize) { UINT8 pixel, count; + int x = 0; - for (;n > 0; n--) - { + for (; n > 0; n--) { pixel = src[1]; - src+=2; - if (n == 1 && pixel != 0) + src += 2; + if (n == 1 && pixel != 0) { return n; + } count = pixel & RLE_MAX_RUN; - if (!count) + if (!count) { return count; - if (count > xsize) { + } + if (x + count > xsize) { return -1; } + x += count; if (pixel & RLE_COPY_FLAG) { - while(count--) { + while (count--) { memcpy(dest, src, 2); src += 2; dest += z * 2; } - } - else { + } else { while (count--) { memcpy(dest, src, 2); dest += z * 2; } - src+=2; + src += 2; } } return 0; } - int -ImagingSgiRleDecode(Imaging im, ImagingCodecState state, - UINT8* buf, Py_ssize_t bytes) -{ +ImagingSgiRleDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t bytes) { UINT8 *ptr; SGISTATE *c; int err = 0; int status; + /* size check */ + if (im->xsize > INT_MAX / im->bands || im->ysize > INT_MAX / im->bands) { + state->errcode = IMAGING_CODEC_MEMORY; + return -1; + } + /* Get all data from File descriptor */ - c = (SGISTATE*)state->context; + c = (SGISTATE *)state->context; _imaging_seek_pyFd(state->fd, 0L, SEEK_END); c->bufsize = _imaging_tell_pyFd(state->fd); c->bufsize -= SGI_HEADER_SIZE; + + c->tablen = im->bands * im->ysize; + /* below, we populate the starttab and lentab into the bufsize, + each with 4 bytes per element of tablen + Check here before we allocate any memory + */ + if (c->bufsize < 8 * c->tablen) { + state->errcode = IMAGING_CODEC_OVERRUN; + return -1; + } + ptr = malloc(sizeof(UINT8) * c->bufsize); if (!ptr) { - return IMAGING_CODEC_MEMORY; + state->errcode = IMAGING_CODEC_MEMORY; + return -1; } _imaging_seek_pyFd(state->fd, SGI_HEADER_SIZE, SEEK_SET); - _imaging_read_pyFd(state->fd, (char*)ptr, c->bufsize); - + _imaging_read_pyFd(state->fd, (char *)ptr, c->bufsize); /* decoder initialization */ state->count = 0; @@ -126,59 +143,62 @@ ImagingSgiRleDecode(Imaging im, ImagingCodecState state, state->ystep = 1; } - if (im->xsize > INT_MAX / im->bands || - im->ysize > INT_MAX / im->bands) { - err = IMAGING_CODEC_MEMORY; - goto sgi_finish_decode; - } - /* Allocate memory for RLE tables and rows */ free(state->buffer); state->buffer = NULL; /* malloc overflow check above */ state->buffer = calloc(im->xsize * im->bands, sizeof(UINT8) * 2); - c->tablen = im->bands * im->ysize; c->starttab = calloc(c->tablen, sizeof(UINT32)); c->lengthtab = calloc(c->tablen, sizeof(UINT32)); - if (!state->buffer || - !c->starttab || - !c->lengthtab) { + if (!state->buffer || !c->starttab || !c->lengthtab) { err = IMAGING_CODEC_MEMORY; goto sgi_finish_decode; } /* populate offsets table */ - for (c->tabindex = 0, c->bufindex = 0; c->tabindex < c->tablen; c->tabindex++, c->bufindex+=4) + for (c->tabindex = 0, c->bufindex = 0; c->tabindex < c->tablen; + c->tabindex++, c->bufindex += 4) { read4B(&c->starttab[c->tabindex], &ptr[c->bufindex]); + } /* populate lengths table */ - for (c->tabindex = 0, c->bufindex = c->tablen * sizeof(UINT32); c->tabindex < c->tablen; c->tabindex++, c->bufindex+=4) + for (c->tabindex = 0, c->bufindex = c->tablen * sizeof(UINT32); + c->tabindex < c->tablen; + c->tabindex++, c->bufindex += 4) { read4B(&c->lengthtab[c->tabindex], &ptr[c->bufindex]); + } state->count += c->tablen * sizeof(UINT32) * 2; /* read compressed rows */ - for (c->rowno = 0; c->rowno < im->ysize; c->rowno++, state->y += state->ystep) - { - for (c->channo = 0; c->channo < im->bands; c->channo++) - { + for (c->rowno = 0; c->rowno < im->ysize; c->rowno++, state->y += state->ystep) { + for (c->channo = 0; c->channo < im->bands; c->channo++) { c->rleoffset = c->starttab[c->rowno + c->channo * im->ysize]; c->rlelength = c->lengthtab[c->rowno + c->channo * im->ysize]; c->rleoffset -= SGI_HEADER_SIZE; if (c->rleoffset + c->rlelength > c->bufsize) { state->errcode = IMAGING_CODEC_OVERRUN; - return -1; + goto sgi_finish_decode; } /* row decompression */ - if (c->bpc ==1) { - status = expandrow(&state->buffer[c->channo], &ptr[c->rleoffset], c->rlelength, im->bands, im->xsize); - } - else { - status = expandrow2(&state->buffer[c->channo * 2], &ptr[c->rleoffset], c->rlelength, im->bands, im->xsize); + if (c->bpc == 1) { + status = expandrow( + &state->buffer[c->channo], + &ptr[c->rleoffset], + c->rlelength, + im->bands, + im->xsize); + } else { + status = expandrow2( + &state->buffer[c->channo * 2], + &ptr[c->rleoffset], + c->rlelength, + im->bands, + im->xsize); } if (status == -1) { state->errcode = IMAGING_CODEC_OVERRUN; - return -1; + goto sgi_finish_decode; } else if (status == 1) { goto sgi_finish_decode; } @@ -187,19 +207,19 @@ ImagingSgiRleDecode(Imaging im, ImagingCodecState state, } /* store decompressed data in image */ - state->shuffle((UINT8*)im->image[state->y], state->buffer, im->xsize); - + state->shuffle((UINT8 *)im->image[state->y], state->buffer, im->xsize); } c->bufsize++; -sgi_finish_decode: ; +sgi_finish_decode:; free(c->starttab); free(c->lengthtab); free(ptr); - if (err != 0){ - return err; + if (err != 0) { + state->errcode = err; + return -1; } return state->count - c->bufsize; } diff --git a/src/libImaging/Storage.c b/src/libImaging/Storage.c index 7d0b91a31..e8a57ab1b 100644 --- a/src/libImaging/Storage.c +++ b/src/libImaging/Storage.c @@ -34,11 +34,9 @@ * See the README file for information on usage and redistribution. */ - #include "Imaging.h" #include - int ImagingNewCount = 0; /* -------------------------------------------------------------------- @@ -46,18 +44,17 @@ int ImagingNewCount = 0; */ Imaging -ImagingNewPrologueSubtype(const char *mode, int xsize, int ysize, int size) -{ +ImagingNewPrologueSubtype(const char *mode, int xsize, int ysize, int size) { Imaging im; /* linesize overflow check, roughly the current largest space req'd */ if (xsize > (INT_MAX / 4) - 1) { - return (Imaging) ImagingError_MemoryError(); + return (Imaging)ImagingError_MemoryError(); } - im = (Imaging) calloc(1, size); + im = (Imaging)calloc(1, size); if (!im) { - return (Imaging) ImagingError_MemoryError(); + return (Imaging)ImagingError_MemoryError(); } /* Setup image descriptor */ @@ -116,8 +113,9 @@ ImagingNewPrologueSubtype(const char *mode, int xsize, int ysize, int size) im->linesize = xsize * 4; im->type = IMAGING_TYPE_INT32; - } else if (strcmp(mode, "I;16") == 0 || strcmp(mode, "I;16L") == 0 \ - || strcmp(mode, "I;16B") == 0 || strcmp(mode, "I;16N") == 0) { + } else if ( + strcmp(mode, "I;16") == 0 || strcmp(mode, "I;16L") == 0 || + strcmp(mode, "I;16B") == 0 || strcmp(mode, "I;16N") == 0) { /* EXPERIMENTAL */ /* 16-bit raw integer images */ im->bands = 1; @@ -136,7 +134,7 @@ ImagingNewPrologueSubtype(const char *mode, int xsize, int ysize, int size) /* 15-bit reversed true colour */ im->bands = 1; im->pixelsize = 2; - im->linesize = (xsize*2 + 3) & -4; + im->linesize = (xsize * 2 + 3) & -4; im->type = IMAGING_TYPE_SPECIAL; } else if (strcmp(mode, "BGR;16") == 0) { @@ -144,7 +142,7 @@ ImagingNewPrologueSubtype(const char *mode, int xsize, int ysize, int size) /* 16-bit reversed true colour */ im->bands = 1; im->pixelsize = 2; - im->linesize = (xsize*2 + 3) & -4; + im->linesize = (xsize * 2 + 3) & -4; im->type = IMAGING_TYPE_SPECIAL; } else if (strcmp(mode, "BGR;24") == 0) { @@ -152,7 +150,7 @@ ImagingNewPrologueSubtype(const char *mode, int xsize, int ysize, int size) /* 24-bit reversed true colour */ im->bands = 1; im->pixelsize = 3; - im->linesize = (xsize*3 + 3) & -4; + im->linesize = (xsize * 3 + 3) & -4; im->type = IMAGING_TYPE_SPECIAL; } else if (strcmp(mode, "BGR;32") == 0) { @@ -160,7 +158,7 @@ ImagingNewPrologueSubtype(const char *mode, int xsize, int ysize, int size) /* 32-bit reversed true colour */ im->bands = 1; im->pixelsize = 4; - im->linesize = (xsize*4 + 3) & -4; + im->linesize = (xsize * 4 + 3) & -4; im->type = IMAGING_TYPE_SPECIAL; } else if (strcmp(mode, "RGBX") == 0) { @@ -205,7 +203,7 @@ ImagingNewPrologueSubtype(const char *mode, int xsize, int ysize, int size) } else { free(im); - return (Imaging) ImagingError_ValueError("unrecognized image mode"); + return (Imaging)ImagingError_ValueError("unrecognized image mode"); } /* Setup image descriptor */ @@ -213,21 +211,23 @@ ImagingNewPrologueSubtype(const char *mode, int xsize, int ysize, int size) /* Pointer array (allocate at least one line, to avoid MemoryError exceptions on platforms where calloc(0, x) returns NULL) */ - im->image = (char **) calloc((ysize > 0) ? ysize : 1, sizeof(void *)); + im->image = (char **)calloc((ysize > 0) ? ysize : 1, sizeof(void *)); - if ( ! im->image) { + if (!im->image) { free(im); - return (Imaging) ImagingError_MemoryError(); + return (Imaging)ImagingError_MemoryError(); } /* Initialize alias pointers to pixel data. */ switch (im->pixelsize) { - case 1: case 2: case 3: - im->image8 = (UINT8 **) im->image; - break; - case 4: - im->image32 = (INT32 **) im->image; - break; + case 1: + case 2: + case 3: + im->image8 = (UINT8 **)im->image; + break; + case 4: + im->image32 = (INT32 **)im->image; + break; } ImagingDefaultArena.stats_new_count += 1; @@ -236,31 +236,32 @@ ImagingNewPrologueSubtype(const char *mode, int xsize, int ysize, int size) } Imaging -ImagingNewPrologue(const char *mode, int xsize, int ysize) -{ +ImagingNewPrologue(const char *mode, int xsize, int ysize) { return ImagingNewPrologueSubtype( mode, xsize, ysize, sizeof(struct ImagingMemoryInstance)); } void -ImagingDelete(Imaging im) -{ - if (!im) +ImagingDelete(Imaging im) { + if (!im) { return; + } - if (im->palette) + if (im->palette) { ImagingPaletteDelete(im->palette); + } - if (im->destroy) + if (im->destroy) { im->destroy(im); + } - if (im->image) + if (im->image) { free(im->image); + } free(im); } - /* Array Storage Type */ /* ------------------ */ /* Allocate image as an array of line buffers. */ @@ -268,17 +269,20 @@ ImagingDelete(Imaging im) #define IMAGING_PAGE_SIZE (4096) struct ImagingMemoryArena ImagingDefaultArena = { - 1, // alignment - 16*1024*1024, // block_size - 0, // blocks_max - 0, // blocks_cached - NULL, // blocks_pool - 0, 0, 0, 0, 0 // Stats + 1, // alignment + 16 * 1024 * 1024, // block_size + 0, // blocks_max + 0, // blocks_cached + NULL, // blocks_pool + 0, + 0, + 0, + 0, + 0 // Stats }; int -ImagingMemorySetBlocksMax(ImagingMemoryArena arena, int blocks_max) -{ +ImagingMemorySetBlocksMax(ImagingMemoryArena arena, int blocks_max) { void *p; /* Free already cached blocks */ ImagingMemoryClearCache(arena, blocks_max); @@ -288,14 +292,14 @@ ImagingMemorySetBlocksMax(ImagingMemoryArena arena, int blocks_max) arena->blocks_pool = NULL; } else if (arena->blocks_pool != NULL) { p = realloc(arena->blocks_pool, sizeof(*arena->blocks_pool) * blocks_max); - if ( ! p) { + if (!p) { // Leave previous blocks_max value return 0; } arena->blocks_pool = p; } else { arena->blocks_pool = calloc(sizeof(*arena->blocks_pool), blocks_max); - if ( ! arena->blocks_pool) { + if (!arena->blocks_pool) { return 0; } } @@ -305,8 +309,7 @@ ImagingMemorySetBlocksMax(ImagingMemoryArena arena, int blocks_max) } void -ImagingMemoryClearCache(ImagingMemoryArena arena, int new_size) -{ +ImagingMemoryClearCache(ImagingMemoryArena arena, int new_size) { while (arena->blocks_cached > new_size) { arena->blocks_cached -= 1; free(arena->blocks_pool[arena->blocks_cached].ptr); @@ -315,8 +318,7 @@ ImagingMemoryClearCache(ImagingMemoryArena arena, int new_size) } ImagingMemoryBlock -memory_get_block(ImagingMemoryArena arena, int requested_size, int dirty) -{ +memory_get_block(ImagingMemoryArena arena, int requested_size, int dirty) { ImagingMemoryBlock block = {NULL, 0}; if (arena->blocks_cached > 0) { @@ -324,16 +326,16 @@ memory_get_block(ImagingMemoryArena arena, int requested_size, int dirty) arena->blocks_cached -= 1; block = arena->blocks_pool[arena->blocks_cached]; // Reallocate if needed - if (block.size != requested_size){ + if (block.size != requested_size) { block.ptr = realloc(block.ptr, requested_size); } - if ( ! block.ptr) { + if (!block.ptr) { // Can't allocate, free previous pointer (it is still valid) free(arena->blocks_pool[arena->blocks_cached].ptr); arena->stats_freed_blocks += 1; return block; } - if ( ! dirty) { + if (!dirty) { memset(block.ptr, 0, requested_size); } arena->stats_reused_blocks += 1; @@ -353,9 +355,8 @@ memory_get_block(ImagingMemoryArena arena, int requested_size, int dirty) } void -memory_return_block(ImagingMemoryArena arena, ImagingMemoryBlock block) -{ - if (arena->blocks_cached < arena->blocks_max) { +memory_return_block(ImagingMemoryArena arena, ImagingMemoryBlock block) { + if (arena->blocks_cached < arena->blocks_max) { // Reduce block size if (block.size > arena->block_size) { block.size = arena->block_size; @@ -369,15 +370,13 @@ memory_return_block(ImagingMemoryArena arena, ImagingMemoryBlock block) } } - static void -ImagingDestroyArray(Imaging im) -{ +ImagingDestroyArray(Imaging im) { int y = 0; if (im->blocks) { while (im->blocks[y].ptr) { - memory_return_block(&ImagingDefaultArena, im->blocks[y]); + memory_return_block(&ImagingDefaultArena, im->blocks[y]); y += 1; } free(im->blocks); @@ -385,8 +384,7 @@ ImagingDestroyArray(Imaging im) } Imaging -ImagingAllocateArray(Imaging im, int dirty, int block_size) -{ +ImagingAllocateArray(Imaging im, int dirty, int block_size) { int y, line_in_block, current_block; ImagingMemoryArena arena = &ImagingDefaultArena; ImagingMemoryBlock block = {NULL, 0}; @@ -394,22 +392,23 @@ ImagingAllocateArray(Imaging im, int dirty, int block_size) char *aligned_ptr = NULL; /* 0-width or 0-height image. No need to do anything */ - if ( ! im->linesize || ! im->ysize) { + if (!im->linesize || !im->ysize) { return im; } aligned_linesize = (im->linesize + arena->alignment - 1) & -arena->alignment; lines_per_block = (block_size - (arena->alignment - 1)) / aligned_linesize; - if (lines_per_block == 0) + if (lines_per_block == 0) { lines_per_block = 1; + } blocks_count = (im->ysize + lines_per_block - 1) / lines_per_block; // printf("NEW size: %dx%d, ls: %d, lpb: %d, blocks: %d\n", // im->xsize, im->ysize, aligned_linesize, lines_per_block, blocks_count); /* One extra pointer is always NULL */ im->blocks = calloc(sizeof(*im->blocks), blocks_count + 1); - if ( ! im->blocks) { - return (Imaging) ImagingError_MemoryError(); + if (!im->blocks) { + return (Imaging)ImagingError_MemoryError(); } /* Allocate image as an array of lines */ @@ -424,9 +423,9 @@ ImagingAllocateArray(Imaging im, int dirty, int block_size) } required = lines_remaining * aligned_linesize + arena->alignment - 1; block = memory_get_block(arena, required, dirty); - if ( ! block.ptr) { + if (!block.ptr) { ImagingDestroyArray(im); - return (Imaging) ImagingError_MemoryError(); + return (Imaging)ImagingError_MemoryError(); } im->blocks[current_block] = block; /* Bulletproof code from libc _int_memalign */ @@ -450,41 +449,38 @@ ImagingAllocateArray(Imaging im, int dirty, int block_size) return im; } - /* Block Storage Type */ /* ------------------ */ /* Allocate image as a single block. */ static void -ImagingDestroyBlock(Imaging im) -{ - if (im->block) +ImagingDestroyBlock(Imaging im) { + if (im->block) { free(im->block); + } } Imaging -ImagingAllocateBlock(Imaging im) -{ +ImagingAllocateBlock(Imaging im) { Py_ssize_t y, i; /* overflow check for malloc */ - if (im->linesize && - im->ysize > INT_MAX / im->linesize) { - return (Imaging) ImagingError_MemoryError(); + if (im->linesize && im->ysize > INT_MAX / im->linesize) { + return (Imaging)ImagingError_MemoryError(); } if (im->ysize * im->linesize <= 0) { /* some platforms return NULL for malloc(0); this fix prevents MemoryError on zero-sized images on such platforms */ - im->block = (char *) malloc(1); + im->block = (char *)malloc(1); } else { /* malloc check ok, overflow check above */ - im->block = (char *) calloc(im->ysize, im->linesize); + im->block = (char *)calloc(im->ysize, im->linesize); } - if ( ! im->block) { - return (Imaging) ImagingError_MemoryError(); + if (!im->block) { + return (Imaging)ImagingError_MemoryError(); } for (y = i = 0; y < im->ysize; y++) { @@ -502,17 +498,17 @@ ImagingAllocateBlock(Imaging im) */ Imaging -ImagingNewInternal(const char* mode, int xsize, int ysize, int dirty) -{ +ImagingNewInternal(const char *mode, int xsize, int ysize, int dirty) { Imaging im; if (xsize < 0 || ysize < 0) { - return (Imaging) ImagingError_ValueError("bad image size"); + return (Imaging)ImagingError_ValueError("bad image size"); } im = ImagingNewPrologue(mode, xsize, ysize); - if ( ! im) + if (!im) { return NULL; + } if (ImagingAllocateArray(im, dirty, ImagingDefaultArena.block_size)) { return im; @@ -530,29 +526,27 @@ ImagingNewInternal(const char* mode, int xsize, int ysize, int dirty) } Imaging -ImagingNew(const char* mode, int xsize, int ysize) -{ +ImagingNew(const char *mode, int xsize, int ysize) { return ImagingNewInternal(mode, xsize, ysize, 0); } Imaging -ImagingNewDirty(const char* mode, int xsize, int ysize) -{ +ImagingNewDirty(const char *mode, int xsize, int ysize) { return ImagingNewInternal(mode, xsize, ysize, 1); } Imaging -ImagingNewBlock(const char* mode, int xsize, int ysize) -{ +ImagingNewBlock(const char *mode, int xsize, int ysize) { Imaging im; if (xsize < 0 || ysize < 0) { - return (Imaging) ImagingError_ValueError("bad image size"); + return (Imaging)ImagingError_ValueError("bad image size"); } im = ImagingNewPrologue(mode, xsize, ysize); - if ( ! im) + if (!im) { return NULL; + } if (ImagingAllocateBlock(im)) { return im; @@ -563,33 +557,32 @@ ImagingNewBlock(const char* mode, int xsize, int ysize) } Imaging -ImagingNew2Dirty(const char* mode, Imaging imOut, Imaging imIn) -{ +ImagingNew2Dirty(const char *mode, Imaging imOut, Imaging imIn) { /* allocate or validate output image */ if (imOut) { /* make sure images match */ - if (strcmp(imOut->mode, mode) != 0 - || imOut->xsize != imIn->xsize - || imOut->ysize != imIn->ysize) { + if (strcmp(imOut->mode, mode) != 0 || imOut->xsize != imIn->xsize || + imOut->ysize != imIn->ysize) { return ImagingError_Mismatch(); } } else { /* create new image */ imOut = ImagingNewDirty(mode, imIn->xsize, imIn->ysize); - if (!imOut) + if (!imOut) { return NULL; + } } return imOut; } void -ImagingCopyPalette(Imaging destination, Imaging source) -{ +ImagingCopyPalette(Imaging destination, Imaging source) { if (source->palette) { - if (destination->palette) + if (destination->palette) { ImagingPaletteDelete(destination->palette); + } destination->palette = ImagingPaletteDuplicate(source->palette); } } diff --git a/src/libImaging/SunRleDecode.c b/src/libImaging/SunRleDecode.c index e627c2c9a..9d8e1292a 100644 --- a/src/libImaging/SunRleDecode.c +++ b/src/libImaging/SunRleDecode.c @@ -15,35 +15,30 @@ * See the README file for information on usage and redistribution. */ - #include "Imaging.h" - int -ImagingSunRleDecode(Imaging im, ImagingCodecState state, UINT8* buf, Py_ssize_t bytes) -{ +ImagingSunRleDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t bytes) { int n; - UINT8* ptr; + UINT8 *ptr; UINT8 extra_data = 0; UINT8 extra_bytes = 0; ptr = buf; for (;;) { - - if (bytes < 1) + if (bytes < 1) { return ptr - buf; + } if (ptr[0] == 0x80) { - - if (bytes < 2) + if (bytes < 2) { break; + } n = ptr[1]; - if (n == 0) { - /* Literal 0x80 (2 bytes) */ n = 1; @@ -53,10 +48,10 @@ ImagingSunRleDecode(Imaging im, ImagingCodecState state, UINT8* buf, Py_ssize_t bytes -= 2; } else { - /* Run (3 bytes) */ - if (bytes < 3) + if (bytes < 3) { break; + } /* from (https://www.fileformat.info/format/sunraster/egff.htm) @@ -81,7 +76,7 @@ ImagingSunRleDecode(Imaging im, ImagingCodecState state, UINT8* buf, Py_ssize_t n += 1; if (state->x + n > state->bytes) { - extra_bytes = n; /* full value */ + extra_bytes = n; /* full value */ n = state->bytes - state->x; extra_bytes -= n; extra_data = ptr[2]; @@ -91,11 +86,9 @@ ImagingSunRleDecode(Imaging im, ImagingCodecState state, UINT8* buf, Py_ssize_t ptr += 3; bytes -= 3; - } } else { - /* Literal byte */ n = 1; @@ -103,18 +96,18 @@ ImagingSunRleDecode(Imaging im, ImagingCodecState state, UINT8* buf, Py_ssize_t ptr += 1; bytes -= 1; - } for (;;) { state->x += n; if (state->x >= state->bytes) { - /* Got a full line, unpack it */ - state->shuffle((UINT8*) im->image[state->y + state->yoff] + - state->xoff * im->pixelsize, state->buffer, - state->xsize); + state->shuffle( + (UINT8 *)im->image[state->y + state->yoff] + + state->xoff * im->pixelsize, + state->buffer, + state->xsize); state->x = 0; @@ -129,7 +122,7 @@ ImagingSunRleDecode(Imaging im, ImagingCodecState state, UINT8* buf, Py_ssize_t } if (state->x > 0) { - break; // assert + break; // assert } if (extra_bytes >= state->bytes) { diff --git a/src/libImaging/TgaRleDecode.c b/src/libImaging/TgaRleDecode.c index d1971e546..273ecdffd 100644 --- a/src/libImaging/TgaRleDecode.c +++ b/src/libImaging/TgaRleDecode.c @@ -5,8 +5,8 @@ * decoder for Targa RLE data. * * history: - * 97-01-04 fl created - * 98-09-11 fl don't one byte per pixel; take orientation into account + * 97-01-04 fl created + * 98-09-11 fl don't one byte per pixel; take orientation into account * * Copyright (c) Fredrik Lundh 1997. * Copyright (c) Secret Labs AB 1997-98. @@ -14,94 +14,90 @@ * See the README file for information on usage and redistribution. */ - #include "Imaging.h" - int -ImagingTgaRleDecode(Imaging im, ImagingCodecState state, - UINT8* buf, Py_ssize_t bytes) -{ +ImagingTgaRleDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t bytes) { int n, depth; - UINT8* ptr; + UINT8 *ptr; ptr = buf; if (state->state == 0) { + /* check image orientation */ + if (state->ystep < 0) { + state->y = state->ysize - 1; + state->ystep = -1; + } else { + state->ystep = 1; + } - /* check image orientation */ - if (state->ystep < 0) { - state->y = state->ysize-1; - state->ystep = -1; - } else - state->ystep = 1; - - state->state = 1; - + state->state = 1; } depth = state->count; for (;;) { + if (bytes < 1) { + return ptr - buf; + } - if (bytes < 1) - return ptr - buf; + if (ptr[0] & 0x80) { + /* Run (1 + pixelsize bytes) */ - if (ptr[0] & 0x80) { + if (bytes < 1 + depth) { + break; + } - /* Run (1 + pixelsize bytes) */ + n = depth * ((ptr[0] & 0x7f) + 1); - if (bytes < 1 + depth) - break; + if (state->x + n > state->bytes) { + state->errcode = IMAGING_CODEC_OVERRUN; + return -1; + } - n = depth * ((ptr[0] & 0x7f) + 1); - - if (state->x + n > state->bytes) { - state->errcode = IMAGING_CODEC_OVERRUN; - return -1; - } - - if (depth == 1) + if (depth == 1) { memset(state->buffer + state->x, ptr[1], n); - else { + } else { int i; - for (i = 0; i < n; i += depth) - memcpy(state->buffer + state->x + i, ptr+1, depth); + for (i = 0; i < n; i += depth) { + memcpy(state->buffer + state->x + i, ptr + 1, depth); + } } ptr += 1 + depth; - bytes -= 1 + depth; + bytes -= 1 + depth; - } else { + } else { + /* Literal (1+n+1 bytes block) */ + n = depth * (ptr[0] + 1); - /* Literal (1+n+1 bytes block) */ - n = depth * (ptr[0] + 1); + if (bytes < 1 + n) { + break; + } - if (bytes < 1 + n) - break; + if (state->x + n > state->bytes) { + state->errcode = IMAGING_CODEC_OVERRUN; + return -1; + } - if (state->x + n > state->bytes) { - state->errcode = IMAGING_CODEC_OVERRUN; - return -1; - } + memcpy(state->buffer + state->x, ptr + 1, n); - memcpy(state->buffer + state->x, ptr + 1, n); + ptr += 1 + n; + bytes -= 1 + n; + } - ptr += 1 + n; - bytes -= 1 + n; + state->x += n; - } + if (state->x >= state->bytes) { + /* Got a full line, unpack it */ + state->shuffle( + (UINT8 *)im->image[state->y + state->yoff] + + state->xoff * im->pixelsize, + state->buffer, + state->xsize); - state->x += n; - - if (state->x >= state->bytes) { - - /* Got a full line, unpack it */ - state->shuffle((UINT8*) im->image[state->y + state->yoff] + - state->xoff * im->pixelsize, state->buffer, - state->xsize); - - state->x = 0; + state->x = 0; state->y += state->ystep; @@ -109,9 +105,7 @@ ImagingTgaRleDecode(Imaging im, ImagingCodecState state, /* End of file (errcode = 0) */ return -1; } - - } - + } } return ptr - buf; diff --git a/src/libImaging/TgaRleEncode.c b/src/libImaging/TgaRleEncode.c index 2fb831e6b..aa7e7b96d 100644 --- a/src/libImaging/TgaRleEncode.c +++ b/src/libImaging/TgaRleEncode.c @@ -4,26 +4,24 @@ #include #include - -static int comparePixels(const UINT8* buf, int x, int bytesPerPixel) -{ +static int +comparePixels(const UINT8 *buf, int x, int bytesPerPixel) { buf += x * bytesPerPixel; return memcmp(buf, buf + bytesPerPixel, bytesPerPixel) == 0; } - int -ImagingTgaRleEncode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) -{ - UINT8* dst; +ImagingTgaRleEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) { + UINT8 *dst; int bytesPerPixel; if (state->state == 0) { if (state->ystep < 0) { state->ystep = -1; state->y = state->ysize - 1; - } else + } else { state->ystep = 1; + } state->state = 1; } @@ -39,15 +37,16 @@ ImagingTgaRleEncode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) * excluding the 1-byte descriptor. */ if (state->count == 0) { - UINT8* row; + UINT8 *row; UINT8 descriptor; int startX; assert(state->x <= state->xsize); /* Make sure we have space for the descriptor. */ - if (bytes < 1) + if (bytes < 1) { break; + } if (state->x == state->xsize) { state->x = 0; @@ -59,12 +58,13 @@ ImagingTgaRleEncode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) } } - if (state->x == 0) + if (state->x == 0) { state->shuffle( state->buffer, - (UINT8*)im->image[state->y + state->yoff] - + state->xoff * im->pixelsize, + (UINT8 *)im->image[state->y + state->yoff] + + state->xoff * im->pixelsize, state->xsize); + } row = state->buffer; @@ -87,28 +87,32 @@ ImagingTgaRleEncode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) */ maxLookup = state->x + 126; /* A packet must not span multiple rows. */ - if (maxLookup > state->xsize - 1) + if (maxLookup > state->xsize - 1) { maxLookup = state->xsize - 1; + } if (isRaw) { - while (state->x < maxLookup) - if (!comparePixels(row, state->x, bytesPerPixel)) + while (state->x < maxLookup) { + if (!comparePixels(row, state->x, bytesPerPixel)) { ++state->x; - else { + } else { /* Two identical pixels will go to RLE packet. */ --state->x; break; } + } state->count += (state->x - startX) * bytesPerPixel; } else { descriptor |= 0x80; - while (state->x < maxLookup) - if (comparePixels(row, state->x, bytesPerPixel)) + while (state->x < maxLookup) { + if (comparePixels(row, state->x, bytesPerPixel)) { ++state->x; - else + } else { break; + } + } } } @@ -132,17 +136,17 @@ ImagingTgaRleEncode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) assert(state->x > 0); assert(state->count <= state->x * bytesPerPixel); - if (bytes == 0) + if (bytes == 0) { break; + } flushCount = state->count; - if (flushCount > bytes) + if (flushCount > bytes) { flushCount = bytes; + } memcpy( - dst, - state->buffer + (state->x * bytesPerPixel - state->count), - flushCount); + dst, state->buffer + (state->x * bytesPerPixel - state->count), flushCount); dst += flushCount; bytes -= flushCount; diff --git a/src/libImaging/TiffDecode.c b/src/libImaging/TiffDecode.c index c3df1174e..7f14b5a34 100644 --- a/src/libImaging/TiffDecode.c +++ b/src/libImaging/TiffDecode.c @@ -20,16 +20,35 @@ #include "TiffDecode.h" -void dump_state(const TIFFSTATE *state){ - TRACE(("State: Location %u size %d eof %d data: %p ifd: %d\n", (uint)state->loc, - (int)state->size, (uint)state->eof, state->data, state->ifd)); +/* Convert C file descriptor to WinApi HFILE if LibTiff was compiled with tif_win32.c + * + * This cast is safe, as the top 32-bits of HFILE are guaranteed to be zero, + * see + * https://docs.microsoft.com/en-us/windows/win32/winprog64/interprocess-communication + */ +#ifndef USE_WIN32_FILEIO +#define fd_to_tiff_fd(fd) (fd) +#else +#define fd_to_tiff_fd(fd) ((int)_get_osfhandle(fd)) +#endif + +void +dump_state(const TIFFSTATE *state) { + TRACE( + ("State: Location %u size %d eof %d data: %p ifd: %d\n", + (uint)state->loc, + (int)state->size, + (uint)state->eof, + state->data, + state->ifd)); } /* procs for TIFFOpenClient */ -tsize_t _tiffReadProc(thandle_t hdata, tdata_t buf, tsize_t size) { +tsize_t +_tiffReadProc(thandle_t hdata, tdata_t buf, tsize_t size) { TIFFSTATE *state = (TIFFSTATE *)hdata; tsize_t to_read; @@ -42,11 +61,12 @@ tsize_t _tiffReadProc(thandle_t hdata, tdata_t buf, tsize_t size) { _TIFFmemcpy(buf, (UINT8 *)state->data + state->loc, to_read); state->loc += (toff_t)to_read; - TRACE( ("location: %u\n", (uint)state->loc)); + TRACE(("location: %u\n", (uint)state->loc)); return to_read; } -tsize_t _tiffWriteProc(thandle_t hdata, tdata_t buf, tsize_t size) { +tsize_t +_tiffWriteProc(thandle_t hdata, tdata_t buf, tsize_t size) { TIFFSTATE *state = (TIFFSTATE *)hdata; tsize_t to_write; @@ -54,14 +74,14 @@ tsize_t _tiffWriteProc(thandle_t hdata, tdata_t buf, tsize_t size) { dump_state(state); to_write = min(size, state->size - (tsize_t)state->loc); - if (state->flrealloc && size>to_write) { + if (state->flrealloc && size > to_write) { tdata_t new_data; - tsize_t newsize=state->size; + tsize_t newsize = state->size; while (newsize < (size + state->size)) { - if (newsize > INT_MAX - 64*1024){ + if (newsize > INT_MAX - 64 * 1024) { return 0; } - newsize += 64*1024; + newsize += 64 * 1024; // newsize*=2; // UNDONE, by 64k chunks? } TRACE(("Reallocing in write to %d bytes\n", (int)newsize)); @@ -86,27 +106,29 @@ tsize_t _tiffWriteProc(thandle_t hdata, tdata_t buf, tsize_t size) { return to_write; } -toff_t _tiffSeekProc(thandle_t hdata, toff_t off, int whence) { +toff_t +_tiffSeekProc(thandle_t hdata, toff_t off, int whence) { TIFFSTATE *state = (TIFFSTATE *)hdata; TRACE(("_tiffSeekProc: off: %u whence: %d \n", (uint)off, whence)); dump_state(state); switch (whence) { - case 0: - state->loc = off; - break; - case 1: - state->loc += off; - break; - case 2: - state->loc = state->eof + off; - break; + case 0: + state->loc = off; + break; + case 1: + state->loc += off; + break; + case 2: + state->loc = state->eof + off; + break; } dump_state(state); return state->loc; } -int _tiffCloseProc(thandle_t hdata) { +int +_tiffCloseProc(thandle_t hdata) { TIFFSTATE *state = (TIFFSTATE *)hdata; TRACE(("_tiffCloseProc \n")); @@ -115,8 +137,8 @@ int _tiffCloseProc(thandle_t hdata) { return 0; } - -toff_t _tiffSizeProc(thandle_t hdata) { +toff_t +_tiffSizeProc(thandle_t hdata) { TIFFSTATE *state = (TIFFSTATE *)hdata; TRACE(("_tiffSizeProc \n")); @@ -125,7 +147,8 @@ toff_t _tiffSizeProc(thandle_t hdata) { return (toff_t)state->size; } -int _tiffMapProc(thandle_t hdata, tdata_t* pbase, toff_t* psize) { +int +_tiffMapProc(thandle_t hdata, tdata_t *pbase, toff_t *psize) { TIFFSTATE *state = (TIFFSTATE *)hdata; TRACE(("_tiffMapProc input size: %u, data: %p\n", (uint)*psize, *pbase)); @@ -137,25 +160,41 @@ int _tiffMapProc(thandle_t hdata, tdata_t* pbase, toff_t* psize) { return (1); } -int _tiffNullMapProc(thandle_t hdata, tdata_t* pbase, toff_t* psize) { - (void) hdata; (void) pbase; (void) psize; +int +_tiffNullMapProc(thandle_t hdata, tdata_t *pbase, toff_t *psize) { + (void)hdata; + (void)pbase; + (void)psize; return (0); } -void _tiffUnmapProc(thandle_t hdata, tdata_t base, toff_t size) { +void +_tiffUnmapProc(thandle_t hdata, tdata_t base, toff_t size) { TRACE(("_tiffUnMapProc\n")); - (void) hdata; (void) base; (void) size; + (void)hdata; + (void)base; + (void)size; } -int ImagingLibTiffInit(ImagingCodecState state, int fp, uint32 offset) { +int +ImagingLibTiffInit(ImagingCodecState state, int fp, uint32 offset) { TIFFSTATE *clientstate = (TIFFSTATE *)state->context; TRACE(("initing libtiff\n")); - TRACE(("filepointer: %d \n", fp)); - TRACE(("State: count %d, state %d, x %d, y %d, ystep %d\n", state->count, state->state, - state->x, state->y, state->ystep)); - TRACE(("State: xsize %d, ysize %d, xoff %d, yoff %d \n", state->xsize, state->ysize, - state->xoff, state->yoff)); + TRACE(("filepointer: %d \n", fp)); + TRACE( + ("State: count %d, state %d, x %d, y %d, ystep %d\n", + state->count, + state->state, + state->x, + state->y, + state->ystep)); + TRACE( + ("State: xsize %d, ysize %d, xoff %d, yoff %d \n", + state->xsize, + state->ysize, + state->xoff, + state->yoff)); TRACE(("State: bits %d, bytes %d \n", state->bits, state->bytes)); TRACE(("State: context %p \n", state->context)); @@ -169,138 +208,249 @@ int ImagingLibTiffInit(ImagingCodecState state, int fp, uint32 offset) { return 1; } - -int ReadTile(TIFF* tiff, UINT32 col, UINT32 row, UINT32* buffer) { - uint16 photometric; - - TIFFGetField(tiff, TIFFTAG_PHOTOMETRIC, &photometric); - +int +_decodeStripYCbCr(Imaging im, ImagingCodecState state, TIFF *tiff) { // To avoid dealing with YCbCr subsampling, let libtiff handle it - if (photometric == PHOTOMETRIC_YCBCR) { - UINT32 tile_width, tile_height, swap_line_size, i_row; - UINT32* swap_line; + // Use a TIFFRGBAImage wrapping the tiff image, and let libtiff handle + // all of the conversion. Metadata read from the TIFFRGBAImage could + // be different from the metadata that the base tiff returns. - TIFFGetField(tiff, TIFFTAG_TILEWIDTH, &tile_width); - TIFFGetField(tiff, TIFFTAG_TILELENGTH, &tile_height); + INT32 strip_row; + UINT8 *new_data; + UINT32 rows_per_strip, row_byte_size, rows_to_read; + int ret; + TIFFRGBAImage img; + char emsg[1024] = ""; - swap_line_size = tile_width * sizeof(UINT32); - if (tile_width != swap_line_size / sizeof(UINT32)) { - return -1; - } - - /* Read the tile into an RGBA array */ - if (!TIFFReadRGBATile(tiff, col, row, buffer)) { - return -1; - } - - swap_line = (UINT32*)malloc(swap_line_size); - if (swap_line == NULL) { - return -1; - } - /* - * For some reason the TIFFReadRGBATile() function chooses the - * lower left corner as the origin. Vertically mirror scanlines. - */ - for(i_row = 0; i_row < tile_height / 2; i_row++) { - UINT32 *top_line, *bottom_line; - - top_line = buffer + tile_width * i_row; - bottom_line = buffer + tile_width * (tile_height - i_row - 1); - - memcpy(swap_line, top_line, 4*tile_width); - memcpy(top_line, bottom_line, 4*tile_width); - memcpy(bottom_line, swap_line, 4*tile_width); - } - - free(swap_line); - - return 0; + ret = TIFFGetFieldDefaulted(tiff, TIFFTAG_ROWSPERSTRIP, &rows_per_strip); + if (ret != 1) { + rows_per_strip = state->ysize; } + TRACE(("RowsPerStrip: %u \n", rows_per_strip)); - if (TIFFReadTile(tiff, (tdata_t)buffer, col, row, 0, 0) == -1) { - TRACE(("Decode Error, Tile at %dx%d\n", col, row)); + if (!(TIFFRGBAImageOK(tiff, emsg) && TIFFRGBAImageBegin(&img, tiff, 0, emsg))) { + TRACE(("Decode error, msg: %s", emsg)); + state->errcode = IMAGING_CODEC_BROKEN; + // nothing to clean up, just return return -1; } - TRACE(("Successfully read tile at %dx%d; \n\n", col, row)); + img.req_orientation = ORIENTATION_TOPLEFT; + img.col_offset = 0; + if (state->xsize != img.width || state->ysize != img.height) { + TRACE( + ("Inconsistent Image Error: %d =? %d, %d =? %d", + state->xsize, + img.width, + state->ysize, + img.height)); + state->errcode = IMAGING_CODEC_BROKEN; + goto decodeycbcr_err; + } + + /* overflow check for row byte size */ + if (INT_MAX / 4 < img.width) { + state->errcode = IMAGING_CODEC_MEMORY; + goto decodeycbcr_err; + } + + // TiffRGBAImages are 32bits/pixel. + row_byte_size = img.width * 4; + + /* overflow check for realloc */ + if (INT_MAX / row_byte_size < rows_per_strip) { + state->errcode = IMAGING_CODEC_MEMORY; + goto decodeycbcr_err; + } + + state->bytes = rows_per_strip * row_byte_size; + + TRACE(("StripSize: %d \n", state->bytes)); + + /* realloc to fit whole strip */ + /* malloc check above */ + new_data = realloc(state->buffer, state->bytes); + if (!new_data) { + state->errcode = IMAGING_CODEC_MEMORY; + goto decodeycbcr_err; + } + + state->buffer = new_data; + + for (; state->y < state->ysize; state->y += rows_per_strip) { + img.row_offset = state->y; + rows_to_read = min(rows_per_strip, img.height - state->y); + + if (TIFFRGBAImageGet(&img, (UINT32 *)state->buffer, img.width, rows_to_read) == + -1) { + TRACE(("Decode Error, y: %d\n", state->y)); + state->errcode = IMAGING_CODEC_BROKEN; + goto decodeycbcr_err; + } + + TRACE(("Decoded strip for row %d \n", state->y)); + + // iterate over each row in the strip and stuff data into image + for (strip_row = 0; + strip_row < min((INT32)rows_per_strip, state->ysize - state->y); + strip_row++) { + TRACE(("Writing data into line %d ; \n", state->y + strip_row)); + + // UINT8 * bbb = state->buffer + strip_row * (state->bytes / + // rows_per_strip); TRACE(("chars: %x %x %x %x\n", ((UINT8 *)bbb)[0], + // ((UINT8 *)bbb)[1], ((UINT8 *)bbb)[2], ((UINT8 *)bbb)[3])); + + state->shuffle( + (UINT8 *)im->image[state->y + state->yoff + strip_row] + + state->xoff * im->pixelsize, + state->buffer + strip_row * row_byte_size, + state->xsize); + } + } + +decodeycbcr_err: + TIFFRGBAImageEnd(&img); + if (state->errcode != 0) { + return -1; + } return 0; } -int ReadStrip(TIFF* tiff, UINT32 row, UINT32* buffer) { - uint16 photometric; - TIFFGetField(tiff, TIFFTAG_PHOTOMETRIC, &photometric); +int +_decodeStrip(Imaging im, ImagingCodecState state, TIFF *tiff) { + INT32 strip_row; + UINT8 *new_data; + UINT32 rows_per_strip, row_byte_size; + int ret; - // To avoid dealing with YCbCr subsampling, let libtiff handle it - if (photometric == PHOTOMETRIC_YCBCR) { - TIFFRGBAImage img; - char emsg[1024] = ""; - UINT32 rows_per_strip, rows_to_read; - int ok; - - - TIFFGetFieldDefaulted(tiff, TIFFTAG_ROWSPERSTRIP, &rows_per_strip); - if ((row % rows_per_strip) != 0) { - TRACE(("Row passed to ReadStrip() must be first in a strip.")); - return -1; - } - - if (TIFFRGBAImageOK(tiff, emsg) && TIFFRGBAImageBegin(&img, tiff, 0, emsg)) { - TRACE(("Initialized RGBAImage\n")); - - img.req_orientation = ORIENTATION_TOPLEFT; - img.row_offset = row; - img.col_offset = 0; - - rows_to_read = min(rows_per_strip, img.height - row); - - TRACE(("rows to read: %d\n", rows_to_read)); - ok = TIFFRGBAImageGet(&img, buffer, img.width, rows_to_read); - - TIFFRGBAImageEnd(&img); - } else { - ok = 0; - } - - if (ok == 0) { - TRACE(("Decode Error, row %d; msg: %s\n", row, emsg)); - return -1; - } - - return 0; + ret = TIFFGetField(tiff, TIFFTAG_ROWSPERSTRIP, &rows_per_strip); + if (ret != 1) { + rows_per_strip = state->ysize; } + TRACE(("RowsPerStrip: %u \n", rows_per_strip)); - if (TIFFReadEncodedStrip(tiff, TIFFComputeStrip(tiff, row, 0), (tdata_t)buffer, -1) == -1) { - TRACE(("Decode Error, strip %d\n", TIFFComputeStrip(tiff, row, 0))); + // We could use TIFFStripSize, but for YCbCr data it returns subsampled data size + row_byte_size = (state->xsize * state->bits + 7) / 8; + + /* overflow check for realloc */ + if (INT_MAX / row_byte_size < rows_per_strip) { + state->errcode = IMAGING_CODEC_MEMORY; return -1; } + state->bytes = rows_per_strip * row_byte_size; + + TRACE(("StripSize: %d \n", state->bytes)); + + if (TIFFStripSize(tiff) > state->bytes) { + // If the strip size as expected by LibTiff isn't what we're expecting, abort. + // man: TIFFStripSize returns the equivalent size for a strip of data as it + // would be returned in a + // call to TIFFReadEncodedStrip ... + + state->errcode = IMAGING_CODEC_MEMORY; + return -1; + } + + /* realloc to fit whole strip */ + /* malloc check above */ + new_data = realloc(state->buffer, state->bytes); + if (!new_data) { + state->errcode = IMAGING_CODEC_MEMORY; + return -1; + } + + state->buffer = new_data; + + for (; state->y < state->ysize; state->y += rows_per_strip) { + if (TIFFReadEncodedStrip( + tiff, + TIFFComputeStrip(tiff, state->y, 0), + (tdata_t)state->buffer, + -1) == -1) { + TRACE(("Decode Error, strip %d\n", TIFFComputeStrip(tiff, state->y, 0))); + state->errcode = IMAGING_CODEC_BROKEN; + return -1; + } + + TRACE(("Decoded strip for row %d \n", state->y)); + + // iterate over each row in the strip and stuff data into image + for (strip_row = 0; + strip_row < min((INT32)rows_per_strip, state->ysize - state->y); + strip_row++) { + TRACE(("Writing data into line %d ; \n", state->y + strip_row)); + + // UINT8 * bbb = state->buffer + strip_row * (state->bytes / + // rows_per_strip); TRACE(("chars: %x %x %x %x\n", ((UINT8 *)bbb)[0], + // ((UINT8 *)bbb)[1], ((UINT8 *)bbb)[2], ((UINT8 *)bbb)[3])); + + state->shuffle( + (UINT8 *)im->image[state->y + state->yoff + strip_row] + + state->xoff * im->pixelsize, + state->buffer + strip_row * row_byte_size, + state->xsize); + } + } return 0; } -int ImagingLibTiffDecode(Imaging im, ImagingCodecState state, UINT8* buffer, Py_ssize_t bytes) { +int +ImagingLibTiffDecode( + Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes) { TIFFSTATE *clientstate = (TIFFSTATE *)state->context; char *filename = "tempfile.tif"; char *mode = "r"; TIFF *tiff; + uint16 photometric = 0; // init to not PHOTOMETRIC_YCBCR + int isYCbCr = 0; /* buffer is the encoded file, bytes is the length of the encoded file */ /* it all ends up in state->buffer, which is a uint8* from Imaging.h */ TRACE(("in decoder: bytes %d\n", bytes)); - TRACE(("State: count %d, state %d, x %d, y %d, ystep %d\n", state->count, state->state, - state->x, state->y, state->ystep)); - TRACE(("State: xsize %d, ysize %d, xoff %d, yoff %d \n", state->xsize, state->ysize, - state->xoff, state->yoff)); + TRACE( + ("State: count %d, state %d, x %d, y %d, ystep %d\n", + state->count, + state->state, + state->x, + state->y, + state->ystep)); + TRACE( + ("State: xsize %d, ysize %d, xoff %d, yoff %d \n", + state->xsize, + state->ysize, + state->xoff, + state->yoff)); TRACE(("State: bits %d, bytes %d \n", state->bits, state->bytes)); - TRACE(("Buffer: %p: %c%c%c%c\n", buffer, (char)buffer[0], (char)buffer[1],(char)buffer[2], (char)buffer[3])); - TRACE(("State->Buffer: %c%c%c%c\n", (char)state->buffer[0], (char)state->buffer[1],(char)state->buffer[2], (char)state->buffer[3])); - TRACE(("Image: mode %s, type %d, bands: %d, xsize %d, ysize %d \n", - im->mode, im->type, im->bands, im->xsize, im->ysize)); - TRACE(("Image: image8 %p, image32 %p, image %p, block %p \n", - im->image8, im->image32, im->image, im->block)); - TRACE(("Image: pixelsize: %d, linesize %d \n", - im->pixelsize, im->linesize)); + TRACE( + ("Buffer: %p: %c%c%c%c\n", + buffer, + (char)buffer[0], + (char)buffer[1], + (char)buffer[2], + (char)buffer[3])); + TRACE( + ("State->Buffer: %c%c%c%c\n", + (char)state->buffer[0], + (char)state->buffer[1], + (char)state->buffer[2], + (char)state->buffer[3])); + TRACE( + ("Image: mode %s, type %d, bands: %d, xsize %d, ysize %d \n", + im->mode, + im->type, + im->bands, + im->xsize, + im->ysize)); + TRACE( + ("Image: image8 %p, image32 %p, image %p, block %p \n", + im->image8, + im->image32, + im->image, + im->block)); + TRACE(("Image: pixelsize: %d, linesize %d \n", im->pixelsize, im->linesize)); dump_state(clientstate); clientstate->size = bytes; @@ -314,62 +464,93 @@ int ImagingLibTiffDecode(Imaging im, ImagingCodecState state, UINT8* buffer, Py_ TIFFSetWarningHandlerExt(NULL); if (clientstate->fp) { - TRACE(("Opening using fd: %d\n",clientstate->fp)); - lseek(clientstate->fp,0,SEEK_SET); // Sometimes, I get it set to the end. - tiff = TIFFFdOpen(clientstate->fp, filename, mode); + TRACE(("Opening using fd: %d\n", clientstate->fp)); + lseek(clientstate->fp, 0, SEEK_SET); // Sometimes, I get it set to the end. + tiff = TIFFFdOpen(fd_to_tiff_fd(clientstate->fp), filename, mode); } else { TRACE(("Opening from string\n")); - tiff = TIFFClientOpen(filename, mode, - (thandle_t) clientstate, - _tiffReadProc, _tiffWriteProc, - _tiffSeekProc, _tiffCloseProc, _tiffSizeProc, - _tiffMapProc, _tiffUnmapProc); + tiff = TIFFClientOpen( + filename, + mode, + (thandle_t)clientstate, + _tiffReadProc, + _tiffWriteProc, + _tiffSeekProc, + _tiffCloseProc, + _tiffSizeProc, + _tiffMapProc, + _tiffUnmapProc); } - if (!tiff){ + if (!tiff) { TRACE(("Error, didn't get the tiff\n")); state->errcode = IMAGING_CODEC_BROKEN; return -1; } - if (clientstate->ifd){ + if (clientstate->ifd) { int rv; uint32 ifdoffset = clientstate->ifd; TRACE(("reading tiff ifd %u\n", ifdoffset)); rv = TIFFSetSubDirectory(tiff, ifdoffset); - if (!rv){ + if (!rv) { TRACE(("error in TIFFSetSubDirectory")); - return -1; + goto decode_err; } } + TIFFGetField(tiff, TIFFTAG_PHOTOMETRIC, &photometric); + isYCbCr = photometric == PHOTOMETRIC_YCBCR; + if (TIFFIsTiled(tiff)) { - UINT32 x, y, tile_y, row_byte_size; - UINT32 tile_width, tile_length, current_tile_width; + INT32 x, y, tile_y; + UINT32 tile_width, tile_length, current_tile_length, current_line, + current_tile_width, row_byte_size; UINT8 *new_data; TIFFGetField(tiff, TIFFTAG_TILEWIDTH, &tile_width); TIFFGetField(tiff, TIFFTAG_TILELENGTH, &tile_length); - // We could use TIFFTileSize, but for YCbCr data it returns subsampled data size - row_byte_size = (tile_width * state->bits + 7) / 8; + /* overflow check for row_byte_size calculation */ + if ((UINT32)INT_MAX / state->bits < tile_width) { + state->errcode = IMAGING_CODEC_MEMORY; + goto decode_err; + } + + if (isYCbCr) { + row_byte_size = tile_width * 4; + /* sanity check, we use this value in shuffle below */ + if (im->pixelsize != 4) { + state->errcode = IMAGING_CODEC_BROKEN; + goto decode_err; + } + } else { + // We could use TIFFTileSize, but for YCbCr data it returns subsampled data + // size + row_byte_size = (tile_width * state->bits + 7) / 8; + } /* overflow check for realloc */ if (INT_MAX / row_byte_size < tile_length) { state->errcode = IMAGING_CODEC_MEMORY; - TIFFClose(tiff); - return -1; + goto decode_err; } - + state->bytes = row_byte_size * tile_length; + if (TIFFTileSize(tiff) > state->bytes) { + // If the strip size as expected by LibTiff isn't what we're expecting, + // abort. + state->errcode = IMAGING_CODEC_MEMORY; + goto decode_err; + } + /* realloc to fit whole tile */ /* malloc check above */ - new_data = realloc (state->buffer, state->bytes); + new_data = realloc(state->buffer, state->bytes); if (!new_data) { state->errcode = IMAGING_CODEC_MEMORY; - TIFFClose(tiff); - return -1; + goto decode_err; } state->buffer = new_data; @@ -378,93 +559,65 @@ int ImagingLibTiffDecode(Imaging im, ImagingCodecState state, UINT8* buffer, Py_ for (y = state->yoff; y < state->ysize; y += tile_length) { for (x = state->xoff; x < state->xsize; x += tile_width) { - if (ReadTile(tiff, x, y, (UINT32*) state->buffer) == -1) { - TRACE(("Decode Error, Tile at %dx%d\n", x, y)); - state->errcode = IMAGING_CODEC_BROKEN; - TIFFClose(tiff); - return -1; + if (isYCbCr) { + /* To avoid dealing with YCbCr subsampling, let libtiff handle it */ + if (!TIFFReadRGBATile(tiff, x, y, (UINT32 *)state->buffer)) { + TRACE(("Decode Error, Tile at %dx%d\n", x, y)); + state->errcode = IMAGING_CODEC_BROKEN; + goto decode_err; + } + } else { + if (TIFFReadTile(tiff, (tdata_t)state->buffer, x, y, 0, 0) == -1) { + TRACE(("Decode Error, Tile at %dx%d\n", x, y)); + state->errcode = IMAGING_CODEC_BROKEN; + goto decode_err; + } } TRACE(("Read tile at %dx%d; \n\n", x, y)); - current_tile_width = min(tile_width, state->xsize - x); - + current_tile_width = min((INT32)tile_width, state->xsize - x); + current_tile_length = min((INT32)tile_length, state->ysize - y); // iterate over each line in the tile and stuff data into image - for (tile_y = 0; tile_y < min(tile_length, state->ysize - y); tile_y++) { - TRACE(("Writing tile data at %dx%d using tile_width: %d; \n", tile_y + y, x, current_tile_width)); + for (tile_y = 0; tile_y < current_tile_length; tile_y++) { + TRACE( + ("Writing tile data at %dx%d using tile_width: %d; \n", + tile_y + y, + x, + current_tile_width)); // UINT8 * bbb = state->buffer + tile_y * row_byte_size; - // TRACE(("chars: %x%x%x%x\n", ((UINT8 *)bbb)[0], ((UINT8 *)bbb)[1], ((UINT8 *)bbb)[2], ((UINT8 *)bbb)[3])); + // TRACE(("chars: %x%x%x%x\n", ((UINT8 *)bbb)[0], ((UINT8 *)bbb)[1], + // ((UINT8 *)bbb)[2], ((UINT8 *)bbb)[3])); + /* + * For some reason the TIFFReadRGBATile() function + * chooses the lower left corner as the origin. + * Vertically mirror by shuffling the scanlines + * backwards + */ - state->shuffle((UINT8*) im->image[tile_y + y] + x * im->pixelsize, - state->buffer + tile_y * row_byte_size, - current_tile_width - ); + if (isYCbCr) { + current_line = tile_length - tile_y - 1; + } else { + current_line = tile_y; + } + + state->shuffle( + (UINT8 *)im->image[tile_y + y] + x * im->pixelsize, + state->buffer + current_line * row_byte_size, + current_tile_width); } } } } else { - UINT32 strip_row, row_byte_size; - UINT8 *new_data; - UINT32 rows_per_strip; - int ret; - - ret = TIFFGetField(tiff, TIFFTAG_ROWSPERSTRIP, &rows_per_strip); - if (ret != 1) { - rows_per_strip = state->ysize; - } - TRACE(("RowsPerStrip: %u \n", rows_per_strip)); - - // We could use TIFFStripSize, but for YCbCr data it returns subsampled data size - row_byte_size = (state->xsize * state->bits + 7) / 8; - - /* overflow check for realloc */ - if (INT_MAX / row_byte_size < rows_per_strip) { - state->errcode = IMAGING_CODEC_MEMORY; - TIFFClose(tiff); - return -1; - } - - state->bytes = rows_per_strip * row_byte_size; - - TRACE(("StripSize: %d \n", state->bytes)); - - /* realloc to fit whole strip */ - /* malloc check above */ - new_data = realloc (state->buffer, state->bytes); - if (!new_data) { - state->errcode = IMAGING_CODEC_MEMORY; - TIFFClose(tiff); - return -1; - } - - state->buffer = new_data; - - for (; state->y < state->ysize; state->y += rows_per_strip) { - if (ReadStrip(tiff, state->y, (UINT32 *)state->buffer) == -1) { - TRACE(("Decode Error, strip %d\n", TIFFComputeStrip(tiff, state->y, 0))); - state->errcode = IMAGING_CODEC_BROKEN; - TIFFClose(tiff); - return -1; - } - - TRACE(("Decoded strip for row %d \n", state->y)); - - // iterate over each row in the strip and stuff data into image - for (strip_row = 0; strip_row < min(rows_per_strip, state->ysize - state->y); strip_row++) { - TRACE(("Writing data into line %d ; \n", state->y + strip_row)); - - // UINT8 * bbb = state->buffer + strip_row * (state->bytes / rows_per_strip); - // TRACE(("chars: %x %x %x %x\n", ((UINT8 *)bbb)[0], ((UINT8 *)bbb)[1], ((UINT8 *)bbb)[2], ((UINT8 *)bbb)[3])); - - state->shuffle((UINT8*) im->image[state->y + state->yoff + strip_row] + - state->xoff * im->pixelsize, - state->buffer + strip_row * row_byte_size, - state->xsize); - } + if (!isYCbCr) { + _decodeStrip(im, state, tiff); + } else { + _decodeStripYCbCr(im, state, tiff); } } +decode_err: TIFFClose(tiff); TRACE(("Done Decoding, Returning \n")); // Returning -1 here to force ImageFile.load to break, rather than @@ -472,7 +625,8 @@ int ImagingLibTiffDecode(Imaging im, ImagingCodecState state, UINT8* buffer, Py_ return -1; } -int ImagingLibTiffEncodeInit(ImagingCodecState state, char *filename, int fp) { +int +ImagingLibTiffEncodeInit(ImagingCodecState state, char *filename, int fp) { // Open the FD or the pointer as a tiff file, for writing. // We may have to do some monkeying around to make this really work. // If we have a fp, then we're good. @@ -481,21 +635,30 @@ int ImagingLibTiffEncodeInit(ImagingCodecState state, char *filename, int fp) { // Going to have to deal with the directory as well. TIFFSTATE *clientstate = (TIFFSTATE *)state->context; - int bufsize = 64*1024; + int bufsize = 64 * 1024; char *mode = "w"; TRACE(("initing libtiff\n")); - TRACE(("Filename %s, filepointer: %d \n", filename, fp)); - TRACE(("State: count %d, state %d, x %d, y %d, ystep %d\n", state->count, state->state, - state->x, state->y, state->ystep)); - TRACE(("State: xsize %d, ysize %d, xoff %d, yoff %d \n", state->xsize, state->ysize, - state->xoff, state->yoff)); + TRACE(("Filename %s, filepointer: %d \n", filename, fp)); + TRACE( + ("State: count %d, state %d, x %d, y %d, ystep %d\n", + state->count, + state->state, + state->x, + state->y, + state->ystep)); + TRACE( + ("State: xsize %d, ysize %d, xoff %d, yoff %d \n", + state->xsize, + state->ysize, + state->xoff, + state->yoff)); TRACE(("State: bits %d, bytes %d \n", state->bits, state->bytes)); TRACE(("State: context %p \n", state->context)); clientstate->loc = 0; clientstate->size = 0; - clientstate->eof =0; + clientstate->eof = 0; clientstate->data = 0; clientstate->flrealloc = 0; clientstate->fp = fp; @@ -503,27 +666,33 @@ int ImagingLibTiffEncodeInit(ImagingCodecState state, char *filename, int fp) { state->state = 0; if (fp) { - TRACE(("Opening using fd: %d for writing \n",clientstate->fp)); - clientstate->tiff = TIFFFdOpen(clientstate->fp, filename, mode); + TRACE(("Opening using fd: %d for writing \n", clientstate->fp)); + clientstate->tiff = TIFFFdOpen(fd_to_tiff_fd(clientstate->fp), filename, mode); } else { - // malloc a buffer to write the tif, we're going to need to realloc or something if we need bigger. + // malloc a buffer to write the tif, we're going to need to realloc or something + // if we need bigger. TRACE(("Opening a buffer for writing \n")); /* malloc check ok, small constant allocation */ clientstate->data = malloc(bufsize); clientstate->size = bufsize; - clientstate->flrealloc=1; + clientstate->flrealloc = 1; if (!clientstate->data) { TRACE(("Error, couldn't allocate a buffer of size %d\n", bufsize)); return 0; } - clientstate->tiff = TIFFClientOpen(filename, mode, - (thandle_t) clientstate, - _tiffReadProc, _tiffWriteProc, - _tiffSeekProc, _tiffCloseProc, _tiffSizeProc, - _tiffNullMapProc, _tiffUnmapProc); /*force no mmap*/ - + clientstate->tiff = TIFFClientOpen( + filename, + mode, + (thandle_t)clientstate, + _tiffReadProc, + _tiffWriteProc, + _tiffSeekProc, + _tiffCloseProc, + _tiffSizeProc, + _tiffNullMapProc, + _tiffUnmapProc); /*force no mmap*/ } if (!clientstate->tiff) { @@ -532,27 +701,33 @@ int ImagingLibTiffEncodeInit(ImagingCodecState state, char *filename, int fp) { } return 1; - } -int ImagingLibTiffMergeFieldInfo(ImagingCodecState state, TIFFDataType field_type, int key, int is_var_length){ +int +ImagingLibTiffMergeFieldInfo( + ImagingCodecState state, TIFFDataType field_type, int key, int is_var_length) { // Refer to libtiff docs (http://www.simplesystems.org/libtiff/addingtags.html) TIFFSTATE *clientstate = (TIFFSTATE *)state->context; - char field_name[10]; uint32 n; int status = 0; // custom fields added with ImagingLibTiffMergeFieldInfo are only used for // decoding, ignore readcount; - int readcount = 0; + int readcount = 1; // we support writing a single value, or a variable number of values int writecount = 1; // whether the first value should encode the number of values. int passcount = 0; TIFFFieldInfo info[] = { - { key, readcount, writecount, field_type, FIELD_CUSTOM, 1, passcount, field_name } - }; + {key, + readcount, + writecount, + field_type, + FIELD_CUSTOM, + 1, + passcount, + "CustomField"}}; if (is_var_length) { info[0].field_writecount = -1; @@ -565,7 +740,8 @@ int ImagingLibTiffMergeFieldInfo(ImagingCodecState state, TIFFDataType field_typ n = sizeof(info) / sizeof(info[0]); // Test for libtiff 4.0 or later, excluding libtiff 3.9.6 and 3.9.7 -#if TIFFLIB_VERSION >= 20111221 && TIFFLIB_VERSION != 20120218 && TIFFLIB_VERSION != 20120922 +#if TIFFLIB_VERSION >= 20111221 && TIFFLIB_VERSION != 20120218 && \ + TIFFLIB_VERSION != 20120922 status = TIFFMergeFieldInfo(clientstate->tiff, info, n); #else TIFFMergeFieldInfo(clientstate->tiff, info, n); @@ -573,7 +749,8 @@ int ImagingLibTiffMergeFieldInfo(ImagingCodecState state, TIFFDataType field_typ return status; } -int ImagingLibTiffSetField(ImagingCodecState state, ttag_t tag, ...){ +int +ImagingLibTiffSetField(ImagingCodecState state, ttag_t tag, ...) { // after tif_dir.c->TIFFSetField. TIFFSTATE *clientstate = (TIFFSTATE *)state->context; va_list ap; @@ -585,8 +762,8 @@ int ImagingLibTiffSetField(ImagingCodecState state, ttag_t tag, ...){ return status; } - -int ImagingLibTiffEncode(Imaging im, ImagingCodecState state, UINT8* buffer, int bytes) { +int +ImagingLibTiffEncode(Imaging im, ImagingCodecState state, UINT8 *buffer, int bytes) { /* One shot encoder. Encode everything to the tiff in the clientstate. If we're running off of a FD, then run once, we're good, everything ends up in the file, we close and we're done. @@ -600,35 +777,65 @@ int ImagingLibTiffEncode(Imaging im, ImagingCodecState state, UINT8* buffer, int TIFF *tiff = clientstate->tiff; TRACE(("in encoder: bytes %d\n", bytes)); - TRACE(("State: count %d, state %d, x %d, y %d, ystep %d\n", state->count, state->state, - state->x, state->y, state->ystep)); - TRACE(("State: xsize %d, ysize %d, xoff %d, yoff %d \n", state->xsize, state->ysize, - state->xoff, state->yoff)); + TRACE( + ("State: count %d, state %d, x %d, y %d, ystep %d\n", + state->count, + state->state, + state->x, + state->y, + state->ystep)); + TRACE( + ("State: xsize %d, ysize %d, xoff %d, yoff %d \n", + state->xsize, + state->ysize, + state->xoff, + state->yoff)); TRACE(("State: bits %d, bytes %d \n", state->bits, state->bytes)); - TRACE(("Buffer: %p: %c%c%c%c\n", buffer, (char)buffer[0], (char)buffer[1],(char)buffer[2], (char)buffer[3])); - TRACE(("State->Buffer: %c%c%c%c\n", (char)state->buffer[0], (char)state->buffer[1],(char)state->buffer[2], (char)state->buffer[3])); - TRACE(("Image: mode %s, type %d, bands: %d, xsize %d, ysize %d \n", - im->mode, im->type, im->bands, im->xsize, im->ysize)); - TRACE(("Image: image8 %p, image32 %p, image %p, block %p \n", - im->image8, im->image32, im->image, im->block)); - TRACE(("Image: pixelsize: %d, linesize %d \n", - im->pixelsize, im->linesize)); + TRACE( + ("Buffer: %p: %c%c%c%c\n", + buffer, + (char)buffer[0], + (char)buffer[1], + (char)buffer[2], + (char)buffer[3])); + TRACE( + ("State->Buffer: %c%c%c%c\n", + (char)state->buffer[0], + (char)state->buffer[1], + (char)state->buffer[2], + (char)state->buffer[3])); + TRACE( + ("Image: mode %s, type %d, bands: %d, xsize %d, ysize %d \n", + im->mode, + im->type, + im->bands, + im->xsize, + im->ysize)); + TRACE( + ("Image: image8 %p, image32 %p, image %p, block %p \n", + im->image8, + im->image32, + im->image, + im->block)); + TRACE(("Image: pixelsize: %d, linesize %d \n", im->pixelsize, im->linesize)); dump_state(clientstate); if (state->state == 0) { TRACE(("Encoding line bt line")); - while(state->y < state->ysize){ - state->shuffle(state->buffer, - (UINT8*) im->image[state->y + state->yoff] + - state->xoff * im->pixelsize, - state->xsize); + while (state->y < state->ysize) { + state->shuffle( + state->buffer, + (UINT8 *)im->image[state->y + state->yoff] + + state->xoff * im->pixelsize, + state->xsize); - if (TIFFWriteScanline(tiff, (tdata_t)(state->buffer), (uint32)state->y, 0) == -1) { + if (TIFFWriteScanline( + tiff, (tdata_t)(state->buffer), (uint32)state->y, 0) == -1) { TRACE(("Encode Error, row %d\n", state->y)); state->errcode = IMAGING_CODEC_BROKEN; TIFFClose(tiff); - if (!clientstate->fp){ + if (!clientstate->fp) { free(clientstate->data); } return -1; @@ -637,7 +844,7 @@ int ImagingLibTiffEncode(Imaging im, ImagingCodecState state, UINT8* buffer, int } if (state->y == state->ysize) { - state->state=1; + state->state = 1; TRACE(("Flushing \n")); if (!TIFFFlush(tiff)) { @@ -645,7 +852,7 @@ int ImagingLibTiffEncode(Imaging im, ImagingCodecState state, UINT8* buffer, int // likely reason is memory. state->errcode = IMAGING_CODEC_MEMORY; TIFFClose(tiff); - if (!clientstate->fp){ + if (!clientstate->fp) { free(clientstate->data); } return -1; @@ -654,13 +861,19 @@ int ImagingLibTiffEncode(Imaging im, ImagingCodecState state, UINT8* buffer, int TIFFClose(tiff); // reset the clientstate metadata to use it to read out the buffer. clientstate->loc = 0; - clientstate->size = clientstate->eof; // redundant? + clientstate->size = clientstate->eof; // redundant? } } if (state->state == 1 && !clientstate->fp) { int read = (int)_tiffReadProc(clientstate, (tdata_t)buffer, (tsize_t)bytes); - TRACE(("Buffer: %p: %c%c%c%c\n", buffer, (char)buffer[0], (char)buffer[1],(char)buffer[2], (char)buffer[3])); + TRACE( + ("Buffer: %p: %c%c%c%c\n", + buffer, + (char)buffer[0], + (char)buffer[1], + (char)buffer[2], + (char)buffer[3])); if (clientstate->loc == clientstate->eof) { TRACE(("Hit EOF, calling an end, freeing data")); state->errcode = IMAGING_CODEC_END; @@ -673,9 +886,8 @@ int ImagingLibTiffEncode(Imaging im, ImagingCodecState state, UINT8* buffer, int return 0; } -const char* -ImagingTiffVersion(void) -{ +const char * +ImagingTiffVersion(void) { return TIFFGetVersion(); } diff --git a/src/libImaging/TiffDecode.h b/src/libImaging/TiffDecode.h index 08ef35cfd..2c3d88caa 100644 --- a/src/libImaging/TiffDecode.h +++ b/src/libImaging/TiffDecode.h @@ -20,34 +20,36 @@ */ #ifndef min -#define min(x,y) (( x > y ) ? y : x ) -#define max(x,y) (( x < y ) ? y : x ) +#define min(x, y) ((x > y) ? y : x) +#define max(x, y) ((x < y) ? y : x) #endif #ifndef _PIL_LIBTIFF_ #define _PIL_LIBTIFF_ typedef struct { - tdata_t data; /* tdata_t == void* */ - toff_t loc; /* toff_t == uint32 */ - tsize_t size; /* tsize_t == int32 */ - int fp; - uint32 ifd; /* offset of the ifd, used for multipage - * Should be uint32 for libtiff 3.9.x - * uint64 for libtiff 4.0.x - */ - TIFF *tiff; /* Used in write */ - toff_t eof; - int flrealloc;/* may we realloc */ + tdata_t data; /* tdata_t == void* */ + toff_t loc; /* toff_t == uint32 */ + tsize_t size; /* tsize_t == int32 */ + int fp; + uint32 ifd; /* offset of the ifd, used for multipage + * Should be uint32 for libtiff 3.9.x + * uint64 for libtiff 4.0.x + */ + TIFF *tiff; /* Used in write */ + toff_t eof; + int flrealloc; /* may we realloc */ } TIFFSTATE; - - -extern int ImagingLibTiffInit(ImagingCodecState state, int fp, uint32 offset); -extern int ImagingLibTiffEncodeInit(ImagingCodecState state, char *filename, int fp); -extern int ImagingLibTiffMergeFieldInfo(ImagingCodecState state, TIFFDataType field_type, int key, int is_var_length); -extern int ImagingLibTiffSetField(ImagingCodecState state, ttag_t tag, ...); - +extern int +ImagingLibTiffInit(ImagingCodecState state, int fp, uint32 offset); +extern int +ImagingLibTiffEncodeInit(ImagingCodecState state, char *filename, int fp); +extern int +ImagingLibTiffMergeFieldInfo( + ImagingCodecState state, TIFFDataType field_type, int key, int is_var_length); +extern int +ImagingLibTiffSetField(ImagingCodecState state, ttag_t tag, ...); /* Trace debugging @@ -55,7 +57,7 @@ extern int ImagingLibTiffSetField(ImagingCodecState state, ttag_t tag, ...); */ /* -#define VA_ARGS(...) __VA_ARGS__ +#define VA_ARGS(...) __VA_ARGS__ #define TRACE(args) fprintf(stderr, VA_ARGS args) */ diff --git a/src/libImaging/Unpack.c b/src/libImaging/Unpack.c index adf2dd277..b4ba283b2 100644 --- a/src/libImaging/Unpack.c +++ b/src/libImaging/Unpack.c @@ -46,20 +46,28 @@ /* byte-swapping macros */ -#define C16N\ - (tmp[0]=in[0], tmp[1]=in[1]); -#define C16S\ - (tmp[1]=in[0], tmp[0]=in[1]); -#define C32N\ - (tmp[0]=in[0], tmp[1]=in[1], tmp[2]=in[2], tmp[3]=in[3]); -#define C32S\ - (tmp[3]=in[0], tmp[2]=in[1], tmp[1]=in[2], tmp[0]=in[3]); -#define C64N\ - (tmp[0]=in[0], tmp[1]=in[1], tmp[2]=in[2], tmp[3]=in[3],\ - tmp[4]=in[4], tmp[5]=in[5], tmp[6]=in[6], tmp[7]=in[7]); -#define C64S\ - (tmp[7]=in[0], tmp[6]=in[1], tmp[5]=in[2], tmp[4]=in[3],\ - tmp[3]=in[4], tmp[2]=in[5], tmp[1]=in[6], tmp[0]=in[7]); +#define C16N (tmp[0] = in[0], tmp[1] = in[1]); +#define C16S (tmp[1] = in[0], tmp[0] = in[1]); +#define C32N (tmp[0] = in[0], tmp[1] = in[1], tmp[2] = in[2], tmp[3] = in[3]); +#define C32S (tmp[3] = in[0], tmp[2] = in[1], tmp[1] = in[2], tmp[0] = in[3]); +#define C64N \ + (tmp[0] = in[0], \ + tmp[1] = in[1], \ + tmp[2] = in[2], \ + tmp[3] = in[3], \ + tmp[4] = in[4], \ + tmp[5] = in[5], \ + tmp[6] = in[6], \ + tmp[7] = in[7]); +#define C64S \ + (tmp[7] = in[0], \ + tmp[6] = in[1], \ + tmp[5] = in[2], \ + tmp[4] = in[3], \ + tmp[3] = in[4], \ + tmp[2] = in[5], \ + tmp[1] = in[6], \ + tmp[0] = in[7]); #ifdef WORDS_BIGENDIAN #define C16B C16N @@ -80,111 +88,163 @@ /* bit-swapping */ static UINT8 BITFLIP[] = { - 0, 128, 64, 192, 32, 160, 96, 224, 16, 144, 80, 208, 48, 176, 112, - 240, 8, 136, 72, 200, 40, 168, 104, 232, 24, 152, 88, 216, 56, 184, - 120, 248, 4, 132, 68, 196, 36, 164, 100, 228, 20, 148, 84, 212, 52, - 180, 116, 244, 12, 140, 76, 204, 44, 172, 108, 236, 28, 156, 92, 220, - 60, 188, 124, 252, 2, 130, 66, 194, 34, 162, 98, 226, 18, 146, 82, - 210, 50, 178, 114, 242, 10, 138, 74, 202, 42, 170, 106, 234, 26, 154, - 90, 218, 58, 186, 122, 250, 6, 134, 70, 198, 38, 166, 102, 230, 22, - 150, 86, 214, 54, 182, 118, 246, 14, 142, 78, 206, 46, 174, 110, 238, - 30, 158, 94, 222, 62, 190, 126, 254, 1, 129, 65, 193, 33, 161, 97, - 225, 17, 145, 81, 209, 49, 177, 113, 241, 9, 137, 73, 201, 41, 169, - 105, 233, 25, 153, 89, 217, 57, 185, 121, 249, 5, 133, 69, 197, 37, - 165, 101, 229, 21, 149, 85, 213, 53, 181, 117, 245, 13, 141, 77, 205, - 45, 173, 109, 237, 29, 157, 93, 221, 61, 189, 125, 253, 3, 131, 67, - 195, 35, 163, 99, 227, 19, 147, 83, 211, 51, 179, 115, 243, 11, 139, - 75, 203, 43, 171, 107, 235, 27, 155, 91, 219, 59, 187, 123, 251, 7, - 135, 71, 199, 39, 167, 103, 231, 23, 151, 87, 215, 55, 183, 119, 247, - 15, 143, 79, 207, 47, 175, 111, 239, 31, 159, 95, 223, 63, 191, 127, - 255 -}; + 0, 128, 64, 192, 32, 160, 96, 224, 16, 144, 80, 208, 48, 176, 112, 240, + 8, 136, 72, 200, 40, 168, 104, 232, 24, 152, 88, 216, 56, 184, 120, 248, + 4, 132, 68, 196, 36, 164, 100, 228, 20, 148, 84, 212, 52, 180, 116, 244, + 12, 140, 76, 204, 44, 172, 108, 236, 28, 156, 92, 220, 60, 188, 124, 252, + 2, 130, 66, 194, 34, 162, 98, 226, 18, 146, 82, 210, 50, 178, 114, 242, + 10, 138, 74, 202, 42, 170, 106, 234, 26, 154, 90, 218, 58, 186, 122, 250, + 6, 134, 70, 198, 38, 166, 102, 230, 22, 150, 86, 214, 54, 182, 118, 246, + 14, 142, 78, 206, 46, 174, 110, 238, 30, 158, 94, 222, 62, 190, 126, 254, + 1, 129, 65, 193, 33, 161, 97, 225, 17, 145, 81, 209, 49, 177, 113, 241, + 9, 137, 73, 201, 41, 169, 105, 233, 25, 153, 89, 217, 57, 185, 121, 249, + 5, 133, 69, 197, 37, 165, 101, 229, 21, 149, 85, 213, 53, 181, 117, 245, + 13, 141, 77, 205, 45, 173, 109, 237, 29, 157, 93, 221, 61, 189, 125, 253, + 3, 131, 67, 195, 35, 163, 99, 227, 19, 147, 83, 211, 51, 179, 115, 243, + 11, 139, 75, 203, 43, 171, 107, 235, 27, 155, 91, 219, 59, 187, 123, 251, + 7, 135, 71, 199, 39, 167, 103, 231, 23, 151, 87, 215, 55, 183, 119, 247, + 15, 143, 79, 207, 47, 175, 111, 239, 31, 159, 95, 223, 63, 191, 127, 255}; /* Unpack to "1" image */ static void -unpack1(UINT8* out, const UINT8* in, int pixels) -{ +unpack1(UINT8 *out, const UINT8 *in, int pixels) { /* bits (msb first, white is non-zero) */ while (pixels > 0) { UINT8 byte = *in++; switch (pixels) { - default: *out++ = (byte & 128) ? 255 : 0; byte <<= 1; - case 7: *out++ = (byte & 128) ? 255 : 0; byte <<= 1; - case 6: *out++ = (byte & 128) ? 255 : 0; byte <<= 1; - case 5: *out++ = (byte & 128) ? 255 : 0; byte <<= 1; - case 4: *out++ = (byte & 128) ? 255 : 0; byte <<= 1; - case 3: *out++ = (byte & 128) ? 255 : 0; byte <<= 1; - case 2: *out++ = (byte & 128) ? 255 : 0; byte <<= 1; - case 1: *out++ = (byte & 128) ? 255 : 0; + default: + *out++ = (byte & 128) ? 255 : 0; + byte <<= 1; + case 7: + *out++ = (byte & 128) ? 255 : 0; + byte <<= 1; + case 6: + *out++ = (byte & 128) ? 255 : 0; + byte <<= 1; + case 5: + *out++ = (byte & 128) ? 255 : 0; + byte <<= 1; + case 4: + *out++ = (byte & 128) ? 255 : 0; + byte <<= 1; + case 3: + *out++ = (byte & 128) ? 255 : 0; + byte <<= 1; + case 2: + *out++ = (byte & 128) ? 255 : 0; + byte <<= 1; + case 1: + *out++ = (byte & 128) ? 255 : 0; } pixels -= 8; } } static void -unpack1I(UINT8* out, const UINT8* in, int pixels) -{ +unpack1I(UINT8 *out, const UINT8 *in, int pixels) { /* bits (msb first, white is zero) */ while (pixels > 0) { UINT8 byte = *in++; switch (pixels) { - default: *out++ = (byte & 128) ? 0 : 255; byte <<= 1; - case 7: *out++ = (byte & 128) ? 0 : 255; byte <<= 1; - case 6: *out++ = (byte & 128) ? 0 : 255; byte <<= 1; - case 5: *out++ = (byte & 128) ? 0 : 255; byte <<= 1; - case 4: *out++ = (byte & 128) ? 0 : 255; byte <<= 1; - case 3: *out++ = (byte & 128) ? 0 : 255; byte <<= 1; - case 2: *out++ = (byte & 128) ? 0 : 255; byte <<= 1; - case 1: *out++ = (byte & 128) ? 0 : 255; + default: + *out++ = (byte & 128) ? 0 : 255; + byte <<= 1; + case 7: + *out++ = (byte & 128) ? 0 : 255; + byte <<= 1; + case 6: + *out++ = (byte & 128) ? 0 : 255; + byte <<= 1; + case 5: + *out++ = (byte & 128) ? 0 : 255; + byte <<= 1; + case 4: + *out++ = (byte & 128) ? 0 : 255; + byte <<= 1; + case 3: + *out++ = (byte & 128) ? 0 : 255; + byte <<= 1; + case 2: + *out++ = (byte & 128) ? 0 : 255; + byte <<= 1; + case 1: + *out++ = (byte & 128) ? 0 : 255; } pixels -= 8; } } static void -unpack1R(UINT8* out, const UINT8* in, int pixels) -{ +unpack1R(UINT8 *out, const UINT8 *in, int pixels) { /* bits (lsb first, white is non-zero) */ while (pixels > 0) { UINT8 byte = *in++; switch (pixels) { - default: *out++ = (byte & 1) ? 255 : 0; byte >>= 1; - case 7: *out++ = (byte & 1) ? 255 : 0; byte >>= 1; - case 6: *out++ = (byte & 1) ? 255 : 0; byte >>= 1; - case 5: *out++ = (byte & 1) ? 255 : 0; byte >>= 1; - case 4: *out++ = (byte & 1) ? 255 : 0; byte >>= 1; - case 3: *out++ = (byte & 1) ? 255 : 0; byte >>= 1; - case 2: *out++ = (byte & 1) ? 255 : 0; byte >>= 1; - case 1: *out++ = (byte & 1) ? 255 : 0; + default: + *out++ = (byte & 1) ? 255 : 0; + byte >>= 1; + case 7: + *out++ = (byte & 1) ? 255 : 0; + byte >>= 1; + case 6: + *out++ = (byte & 1) ? 255 : 0; + byte >>= 1; + case 5: + *out++ = (byte & 1) ? 255 : 0; + byte >>= 1; + case 4: + *out++ = (byte & 1) ? 255 : 0; + byte >>= 1; + case 3: + *out++ = (byte & 1) ? 255 : 0; + byte >>= 1; + case 2: + *out++ = (byte & 1) ? 255 : 0; + byte >>= 1; + case 1: + *out++ = (byte & 1) ? 255 : 0; } pixels -= 8; } } static void -unpack1IR(UINT8* out, const UINT8* in, int pixels) -{ +unpack1IR(UINT8 *out, const UINT8 *in, int pixels) { /* bits (lsb first, white is zero) */ while (pixels > 0) { UINT8 byte = *in++; switch (pixels) { - default: *out++ = (byte & 1) ? 0 : 255; byte >>= 1; - case 7: *out++ = (byte & 1) ? 0 : 255; byte >>= 1; - case 6: *out++ = (byte & 1) ? 0 : 255; byte >>= 1; - case 5: *out++ = (byte & 1) ? 0 : 255; byte >>= 1; - case 4: *out++ = (byte & 1) ? 0 : 255; byte >>= 1; - case 3: *out++ = (byte & 1) ? 0 : 255; byte >>= 1; - case 2: *out++ = (byte & 1) ? 0 : 255; byte >>= 1; - case 1: *out++ = (byte & 1) ? 0 : 255; + default: + *out++ = (byte & 1) ? 0 : 255; + byte >>= 1; + case 7: + *out++ = (byte & 1) ? 0 : 255; + byte >>= 1; + case 6: + *out++ = (byte & 1) ? 0 : 255; + byte >>= 1; + case 5: + *out++ = (byte & 1) ? 0 : 255; + byte >>= 1; + case 4: + *out++ = (byte & 1) ? 0 : 255; + byte >>= 1; + case 3: + *out++ = (byte & 1) ? 0 : 255; + byte >>= 1; + case 2: + *out++ = (byte & 1) ? 0 : 255; + byte >>= 1; + case 1: + *out++ = (byte & 1) ? 0 : 255; } pixels -= 8; } } static void -unpack18(UINT8* out, const UINT8* in, int pixels) -{ +unpack18(UINT8 *out, const UINT8 *in, int pixels) { /* Unpack a '|b1' image, which is a numpy boolean. 1 == true, 0==false, in bytes */ @@ -194,169 +254,197 @@ unpack18(UINT8* out, const UINT8* in, int pixels) } } - - /* Unpack to "L" image */ static void -unpackL2(UINT8* out, const UINT8* in, int pixels) -{ +unpackL2(UINT8 *out, const UINT8 *in, int pixels) { /* nibbles (msb first, white is non-zero) */ while (pixels > 0) { UINT8 byte = *in++; switch (pixels) { - default: *out++ = ((byte >> 6) & 0x03U) * 0x55U; byte <<= 2; - case 3: *out++ = ((byte >> 6) & 0x03U) * 0x55U; byte <<= 2; - case 2: *out++ = ((byte >> 6) & 0x03U) * 0x55U; byte <<= 2; - case 1: *out++ = ((byte >> 6) & 0x03U) * 0x55U; + default: + *out++ = ((byte >> 6) & 0x03U) * 0x55U; + byte <<= 2; + case 3: + *out++ = ((byte >> 6) & 0x03U) * 0x55U; + byte <<= 2; + case 2: + *out++ = ((byte >> 6) & 0x03U) * 0x55U; + byte <<= 2; + case 1: + *out++ = ((byte >> 6) & 0x03U) * 0x55U; } pixels -= 4; } } static void -unpackL2I(UINT8* out, const UINT8* in, int pixels) -{ +unpackL2I(UINT8 *out, const UINT8 *in, int pixels) { /* nibbles (msb first, white is zero) */ while (pixels > 0) { UINT8 byte = *in++; switch (pixels) { - default: *out++ = 0xFFU - (UINT8)(((byte >> 6) & 0x03U) * 0x55U); byte <<= 2; - case 3: *out++ = 0xFFU - (UINT8)(((byte >> 6) & 0x03U) * 0x55U); byte <<= 2; - case 2: *out++ = 0xFFU - (UINT8)(((byte >> 6) & 0x03U) * 0x55U); byte <<= 2; - case 1: *out++ = 0xFFU - (UINT8)(((byte >> 6) & 0x03U) * 0x55U); + default: + *out++ = 0xFFU - (UINT8)(((byte >> 6) & 0x03U) * 0x55U); + byte <<= 2; + case 3: + *out++ = 0xFFU - (UINT8)(((byte >> 6) & 0x03U) * 0x55U); + byte <<= 2; + case 2: + *out++ = 0xFFU - (UINT8)(((byte >> 6) & 0x03U) * 0x55U); + byte <<= 2; + case 1: + *out++ = 0xFFU - (UINT8)(((byte >> 6) & 0x03U) * 0x55U); } pixels -= 4; } } static void -unpackL2R(UINT8* out, const UINT8* in, int pixels) -{ +unpackL2R(UINT8 *out, const UINT8 *in, int pixels) { /* nibbles (bit order reversed, white is non-zero) */ while (pixels > 0) { UINT8 byte = *in++; byte = BITFLIP[byte]; switch (pixels) { - default: *out++ = ((byte >> 6) & 0x03U) * 0x55U; byte <<= 2; - case 3: *out++ = ((byte >> 6) & 0x03U) * 0x55U; byte <<= 2; - case 2: *out++ = ((byte >> 6) & 0x03U) * 0x55U; byte <<= 2; - case 1: *out++ = ((byte >> 6) & 0x03U) * 0x55U; + default: + *out++ = ((byte >> 6) & 0x03U) * 0x55U; + byte <<= 2; + case 3: + *out++ = ((byte >> 6) & 0x03U) * 0x55U; + byte <<= 2; + case 2: + *out++ = ((byte >> 6) & 0x03U) * 0x55U; + byte <<= 2; + case 1: + *out++ = ((byte >> 6) & 0x03U) * 0x55U; } pixels -= 4; } } static void -unpackL2IR(UINT8* out, const UINT8* in, int pixels) -{ +unpackL2IR(UINT8 *out, const UINT8 *in, int pixels) { /* nibbles (bit order reversed, white is zero) */ while (pixels > 0) { UINT8 byte = *in++; byte = BITFLIP[byte]; switch (pixels) { - default: *out++ = 0xFFU - (UINT8)(((byte >> 6) & 0x03U) * 0x55U); byte <<= 2; - case 3: *out++ = 0xFFU - (UINT8)(((byte >> 6) & 0x03U) * 0x55U); byte <<= 2; - case 2: *out++ = 0xFFU - (UINT8)(((byte >> 6) & 0x03U) * 0x55U); byte <<= 2; - case 1: *out++ = 0xFFU - (UINT8)(((byte >> 6) & 0x03U) * 0x55U); + default: + *out++ = 0xFFU - (UINT8)(((byte >> 6) & 0x03U) * 0x55U); + byte <<= 2; + case 3: + *out++ = 0xFFU - (UINT8)(((byte >> 6) & 0x03U) * 0x55U); + byte <<= 2; + case 2: + *out++ = 0xFFU - (UINT8)(((byte >> 6) & 0x03U) * 0x55U); + byte <<= 2; + case 1: + *out++ = 0xFFU - (UINT8)(((byte >> 6) & 0x03U) * 0x55U); } pixels -= 4; } } static void -unpackL4(UINT8* out, const UINT8* in, int pixels) -{ +unpackL4(UINT8 *out, const UINT8 *in, int pixels) { /* nibbles (msb first, white is non-zero) */ while (pixels > 0) { UINT8 byte = *in++; switch (pixels) { - default: *out++ = ((byte >> 4) & 0x0FU) * 0x11U; byte <<= 4; - case 1: *out++ = ((byte >> 4) & 0x0FU) * 0x11U; + default: + *out++ = ((byte >> 4) & 0x0FU) * 0x11U; + byte <<= 4; + case 1: + *out++ = ((byte >> 4) & 0x0FU) * 0x11U; } pixels -= 2; } } static void -unpackL4I(UINT8* out, const UINT8* in, int pixels) -{ +unpackL4I(UINT8 *out, const UINT8 *in, int pixels) { /* nibbles (msb first, white is zero) */ while (pixels > 0) { UINT8 byte = *in++; switch (pixels) { - default: *out++ = 0xFFU - (UINT8)(((byte >> 4) & 0x0FU) * 0x11U); byte <<= 4; - case 1: *out++ = 0xFFU - (UINT8)(((byte >> 4) & 0x0FU) * 0x11U); + default: + *out++ = 0xFFU - (UINT8)(((byte >> 4) & 0x0FU) * 0x11U); + byte <<= 4; + case 1: + *out++ = 0xFFU - (UINT8)(((byte >> 4) & 0x0FU) * 0x11U); } pixels -= 2; } } static void -unpackL4R(UINT8* out, const UINT8* in, int pixels) -{ +unpackL4R(UINT8 *out, const UINT8 *in, int pixels) { /* nibbles (bit order reversed, white is non-zero) */ while (pixels > 0) { UINT8 byte = *in++; byte = BITFLIP[byte]; switch (pixels) { - default: *out++ = ((byte >> 4) & 0x0FU) * 0x11U; byte <<= 4; - case 1: *out++ = ((byte >> 4) & 0x0FU) * 0x11U; + default: + *out++ = ((byte >> 4) & 0x0FU) * 0x11U; + byte <<= 4; + case 1: + *out++ = ((byte >> 4) & 0x0FU) * 0x11U; } pixels -= 2; } } static void -unpackL4IR(UINT8* out, const UINT8* in, int pixels) -{ +unpackL4IR(UINT8 *out, const UINT8 *in, int pixels) { /* nibbles (bit order reversed, white is zero) */ while (pixels > 0) { UINT8 byte = *in++; byte = BITFLIP[byte]; switch (pixels) { - default: *out++ = 0xFFU - (UINT8)(((byte >> 4) & 0x0FU) * 0x11U); byte <<= 4; - case 1: *out++ = 0xFFU - (UINT8)(((byte >> 4) & 0x0FU) * 0x11U); + default: + *out++ = 0xFFU - (UINT8)(((byte >> 4) & 0x0FU) * 0x11U); + byte <<= 4; + case 1: + *out++ = 0xFFU - (UINT8)(((byte >> 4) & 0x0FU) * 0x11U); } pixels -= 2; } } static void -unpackLA(UINT8* _out, const UINT8* in, int pixels) -{ +unpackLA(UINT8 *_out, const UINT8 *in, int pixels) { int i; /* LA, pixel interleaved */ for (i = 0; i < pixels; i++) { UINT32 iv = MAKE_UINT32(in[0], in[0], in[0], in[1]); memcpy(_out, &iv, sizeof(iv)); - in += 2; _out += 4; + in += 2; + _out += 4; } } static void -unpackLAL(UINT8* _out, const UINT8* in, int pixels) -{ +unpackLAL(UINT8 *_out, const UINT8 *in, int pixels) { int i; /* LA, line interleaved */ - for (i = 0; i < pixels; i++, _out+=4) { - UINT32 iv = MAKE_UINT32(in[i], in[i], in[i], in[i+pixels]); + for (i = 0; i < pixels; i++, _out += 4) { + UINT32 iv = MAKE_UINT32(in[i], in[i], in[i], in[i + pixels]); memcpy(_out, &iv, sizeof(iv)); } } static void -unpackLI(UINT8* out, const UINT8* in, int pixels) -{ +unpackLI(UINT8 *out, const UINT8 *in, int pixels) { /* negative */ int i; - for (i = 0; i < pixels; i++) + for (i = 0; i < pixels; i++) { out[i] = ~in[i]; + } } static void -unpackLR(UINT8* out, const UINT8* in, int pixels) -{ +unpackLR(UINT8 *out, const UINT8 *in, int pixels) { int i; /* RGB, bit reversed */ for (i = 0; i < pixels; i++) { @@ -365,8 +453,7 @@ unpackLR(UINT8* out, const UINT8* in, int pixels) } static void -unpackL16(UINT8* out, const UINT8* in, int pixels) -{ +unpackL16(UINT8 *out, const UINT8 *in, int pixels) { /* int16 (upper byte, little endian) */ int i; for (i = 0; i < pixels; i++) { @@ -376,8 +463,7 @@ unpackL16(UINT8* out, const UINT8* in, int pixels) } static void -unpackL16B(UINT8* out, const UINT8* in, int pixels) -{ +unpackL16B(UINT8 *out, const UINT8 *in, int pixels) { int i; /* int16 (upper byte, big endian) */ for (i = 0; i < pixels; i++) { @@ -386,66 +472,86 @@ unpackL16B(UINT8* out, const UINT8* in, int pixels) } } - /* Unpack to "P" image */ static void -unpackP1(UINT8* out, const UINT8* in, int pixels) -{ +unpackP1(UINT8 *out, const UINT8 *in, int pixels) { /* bits */ while (pixels > 0) { UINT8 byte = *in++; switch (pixels) { - default: *out++ = (byte >> 7) & 1; byte <<= 1; - case 7: *out++ = (byte >> 7) & 1; byte <<= 1; - case 6: *out++ = (byte >> 7) & 1; byte <<= 1; - case 5: *out++ = (byte >> 7) & 1; byte <<= 1; - case 4: *out++ = (byte >> 7) & 1; byte <<= 1; - case 3: *out++ = (byte >> 7) & 1; byte <<= 1; - case 2: *out++ = (byte >> 7) & 1; byte <<= 1; - case 1: *out++ = (byte >> 7) & 1; + default: + *out++ = (byte >> 7) & 1; + byte <<= 1; + case 7: + *out++ = (byte >> 7) & 1; + byte <<= 1; + case 6: + *out++ = (byte >> 7) & 1; + byte <<= 1; + case 5: + *out++ = (byte >> 7) & 1; + byte <<= 1; + case 4: + *out++ = (byte >> 7) & 1; + byte <<= 1; + case 3: + *out++ = (byte >> 7) & 1; + byte <<= 1; + case 2: + *out++ = (byte >> 7) & 1; + byte <<= 1; + case 1: + *out++ = (byte >> 7) & 1; } pixels -= 8; } } static void -unpackP2(UINT8* out, const UINT8* in, int pixels) -{ +unpackP2(UINT8 *out, const UINT8 *in, int pixels) { /* bit pairs */ while (pixels > 0) { UINT8 byte = *in++; switch (pixels) { - default: *out++ = (byte >> 6) & 3; byte <<= 2; - case 3: *out++ = (byte >> 6) & 3; byte <<= 2; - case 2: *out++ = (byte >> 6) & 3; byte <<= 2; - case 1: *out++ = (byte >> 6) & 3; + default: + *out++ = (byte >> 6) & 3; + byte <<= 2; + case 3: + *out++ = (byte >> 6) & 3; + byte <<= 2; + case 2: + *out++ = (byte >> 6) & 3; + byte <<= 2; + case 1: + *out++ = (byte >> 6) & 3; } pixels -= 4; } } static void -unpackP4(UINT8* out, const UINT8* in, int pixels) -{ +unpackP4(UINT8 *out, const UINT8 *in, int pixels) { /* nibbles */ while (pixels > 0) { UINT8 byte = *in++; switch (pixels) { - default: *out++ = (byte >> 4) & 15; byte <<= 4; - case 1: *out++ = (byte >> 4) & 15; + default: + *out++ = (byte >> 4) & 15; + byte <<= 4; + case 1: + *out++ = (byte >> 4) & 15; } pixels -= 2; } } static void -unpackP2L(UINT8* out, const UINT8* in, int pixels) -{ +unpackP2L(UINT8 *out, const UINT8 *in, int pixels) { int i, j, m, s; /* bit layers */ m = 128; - s = (pixels+7)/8; + s = (pixels + 7) / 8; for (i = j = 0; i < pixels; i++) { out[i] = ((in[j] & m) ? 1 : 0) + ((in[j + s] & m) ? 2 : 0); if ((m >>= 1) == 0) { @@ -456,15 +562,14 @@ unpackP2L(UINT8* out, const UINT8* in, int pixels) } static void -unpackP4L(UINT8* out, const UINT8* in, int pixels) -{ +unpackP4L(UINT8 *out, const UINT8 *in, int pixels) { int i, j, m, s; /* bit layers (trust the optimizer ;-) */ m = 128; - s = (pixels+7)/8; + s = (pixels + 7) / 8; for (i = j = 0; i < pixels; i++) { out[i] = ((in[j] & m) ? 1 : 0) + ((in[j + s] & m) ? 2 : 0) + - ((in[j + 2*s] & m) ? 4 : 0) + ((in[j + 3*s] & m) ? 8 : 0); + ((in[j + 2 * s] & m) ? 4 : 0) + ((in[j + 3 * s] & m) ? 8 : 0); if ((m >>= 1) == 0) { m = 128; j++; @@ -472,403 +577,410 @@ unpackP4L(UINT8* out, const UINT8* in, int pixels) } } - /* Unpack to "RGB" image */ void -ImagingUnpackRGB(UINT8* _out, const UINT8* in, int pixels) -{ +ImagingUnpackRGB(UINT8 *_out, const UINT8 *in, int pixels) { int i = 0; /* RGB triplets */ - for (; i < pixels-1; i++) { + for (; i < pixels - 1; i++) { UINT32 iv; memcpy(&iv, in, sizeof(iv)); iv |= MASK_UINT32_CHANNEL_3; memcpy(_out, &iv, sizeof(iv)); - in += 3; _out += 4; + in += 3; + _out += 4; } for (; i < pixels; i++) { UINT32 iv = MAKE_UINT32(in[0], in[1], in[2], 255); memcpy(_out, &iv, sizeof(iv)); - in += 3; _out += 4; + in += 3; + _out += 4; } } void -unpackRGB16L(UINT8* _out, const UINT8* in, int pixels) -{ +unpackRGB16L(UINT8 *_out, const UINT8 *in, int pixels) { int i; /* 16-bit RGB triplets, little-endian order */ for (i = 0; i < pixels; i++) { UINT32 iv = MAKE_UINT32(in[1], in[3], in[5], 255); memcpy(_out, &iv, sizeof(iv)); - in += 6; _out += 4; + in += 6; + _out += 4; } } void -unpackRGB16B(UINT8* _out, const UINT8* in, int pixels) -{ +unpackRGB16B(UINT8 *_out, const UINT8 *in, int pixels) { int i; /* 16-bit RGB triplets, big-endian order */ for (i = 0; i < pixels; i++) { UINT32 iv = MAKE_UINT32(in[0], in[2], in[4], 255); memcpy(_out, &iv, sizeof(iv)); - in += 6; _out += 4; + in += 6; + _out += 4; } } static void -unpackRGBL(UINT8* _out, const UINT8* in, int pixels) -{ +unpackRGBL(UINT8 *_out, const UINT8 *in, int pixels) { int i; /* RGB, line interleaved */ - for (i = 0; i < pixels; i++, _out+=4) { - UINT32 iv = MAKE_UINT32(in[i], in[i+pixels], in[i+pixels+pixels], 255); + for (i = 0; i < pixels; i++, _out += 4) { + UINT32 iv = MAKE_UINT32(in[i], in[i + pixels], in[i + pixels + pixels], 255); memcpy(_out, &iv, sizeof(iv)); } } static void -unpackRGBR(UINT8* _out, const UINT8* in, int pixels) -{ +unpackRGBR(UINT8 *_out, const UINT8 *in, int pixels) { int i; /* RGB, bit reversed */ for (i = 0; i < pixels; i++) { - UINT32 iv = MAKE_UINT32(BITFLIP[in[0]], BITFLIP[in[1]], - BITFLIP[in[2]], 255); + UINT32 iv = MAKE_UINT32(BITFLIP[in[0]], BITFLIP[in[1]], BITFLIP[in[2]], 255); memcpy(_out, &iv, sizeof(iv)); - in += 3; _out += 4; + in += 3; + _out += 4; } } void -ImagingUnpackBGR(UINT8* _out, const UINT8* in, int pixels) -{ +ImagingUnpackBGR(UINT8 *_out, const UINT8 *in, int pixels) { int i; /* RGB, reversed bytes */ for (i = 0; i < pixels; i++) { UINT32 iv = MAKE_UINT32(in[2], in[1], in[0], 255); memcpy(_out, &iv, sizeof(iv)); - in += 3; _out += 4; + in += 3; + _out += 4; } } void -ImagingUnpackRGB15(UINT8* out, const UINT8* in, int pixels) -{ +ImagingUnpackRGB15(UINT8 *out, const UINT8 *in, int pixels) { int i, pixel; /* RGB, 5 bits per pixel */ for (i = 0; i < pixels; i++) { pixel = in[0] + (in[1] << 8); out[R] = (pixel & 31) * 255 / 31; - out[G] = ((pixel>>5) & 31) * 255 / 31; - out[B] = ((pixel>>10) & 31) * 255 / 31; + out[G] = ((pixel >> 5) & 31) * 255 / 31; + out[B] = ((pixel >> 10) & 31) * 255 / 31; out[A] = 255; - out += 4; in += 2; + out += 4; + in += 2; } } void -ImagingUnpackRGBA15(UINT8* out, const UINT8* in, int pixels) -{ +ImagingUnpackRGBA15(UINT8 *out, const UINT8 *in, int pixels) { int i, pixel; /* RGB, 5/5/5/1 bits per pixel */ for (i = 0; i < pixels; i++) { pixel = in[0] + (in[1] << 8); out[R] = (pixel & 31) * 255 / 31; - out[G] = ((pixel>>5) & 31) * 255 / 31; - out[B] = ((pixel>>10) & 31) * 255 / 31; - out[A] = (pixel>>15) * 255; - out += 4; in += 2; + out[G] = ((pixel >> 5) & 31) * 255 / 31; + out[B] = ((pixel >> 10) & 31) * 255 / 31; + out[A] = (pixel >> 15) * 255; + out += 4; + in += 2; } } void -ImagingUnpackBGR15(UINT8* out, const UINT8* in, int pixels) -{ +ImagingUnpackBGR15(UINT8 *out, const UINT8 *in, int pixels) { int i, pixel; /* RGB, reversed bytes, 5 bits per pixel */ for (i = 0; i < pixels; i++) { pixel = in[0] + (in[1] << 8); out[B] = (pixel & 31) * 255 / 31; - out[G] = ((pixel>>5) & 31) * 255 / 31; - out[R] = ((pixel>>10) & 31) * 255 / 31; + out[G] = ((pixel >> 5) & 31) * 255 / 31; + out[R] = ((pixel >> 10) & 31) * 255 / 31; out[A] = 255; - out += 4; in += 2; + out += 4; + in += 2; } } void -ImagingUnpackBGRA15(UINT8* out, const UINT8* in, int pixels) -{ +ImagingUnpackBGRA15(UINT8 *out, const UINT8 *in, int pixels) { int i, pixel; /* RGB, reversed bytes, 5/5/5/1 bits per pixel */ for (i = 0; i < pixels; i++) { pixel = in[0] + (in[1] << 8); out[B] = (pixel & 31) * 255 / 31; - out[G] = ((pixel>>5) & 31) * 255 / 31; - out[R] = ((pixel>>10) & 31) * 255 / 31; - out[A] = (pixel>>15) * 255; - out += 4; in += 2; + out[G] = ((pixel >> 5) & 31) * 255 / 31; + out[R] = ((pixel >> 10) & 31) * 255 / 31; + out[A] = (pixel >> 15) * 255; + out += 4; + in += 2; } } void -ImagingUnpackRGB16(UINT8* out, const UINT8* in, int pixels) -{ +ImagingUnpackRGB16(UINT8 *out, const UINT8 *in, int pixels) { int i, pixel; /* RGB, 5/6/5 bits per pixel */ for (i = 0; i < pixels; i++) { pixel = in[0] + (in[1] << 8); out[R] = (pixel & 31) * 255 / 31; - out[G] = ((pixel>>5) & 63) * 255 / 63; - out[B] = ((pixel>>11) & 31) * 255 / 31; + out[G] = ((pixel >> 5) & 63) * 255 / 63; + out[B] = ((pixel >> 11) & 31) * 255 / 31; out[A] = 255; - out += 4; in += 2; + out += 4; + in += 2; } } void -ImagingUnpackBGR16(UINT8* out, const UINT8* in, int pixels) -{ +ImagingUnpackBGR16(UINT8 *out, const UINT8 *in, int pixels) { int i, pixel; /* RGB, reversed bytes, 5/6/5 bits per pixel */ for (i = 0; i < pixels; i++) { pixel = in[0] + (in[1] << 8); out[B] = (pixel & 31) * 255 / 31; - out[G] = ((pixel>>5) & 63) * 255 / 63; - out[R] = ((pixel>>11) & 31) * 255 / 31; + out[G] = ((pixel >> 5) & 63) * 255 / 63; + out[R] = ((pixel >> 11) & 31) * 255 / 31; out[A] = 255; - out += 4; in += 2; + out += 4; + in += 2; } } void -ImagingUnpackRGB4B(UINT8* out, const UINT8* in, int pixels) -{ +ImagingUnpackRGB4B(UINT8 *out, const UINT8 *in, int pixels) { int i, pixel; /* RGB, 4 bits per pixel */ for (i = 0; i < pixels; i++) { pixel = in[0] + (in[1] << 8); out[R] = (pixel & 15) * 17; - out[G] = ((pixel>>4) & 15) * 17; - out[B] = ((pixel>>8) & 15) * 17; + out[G] = ((pixel >> 4) & 15) * 17; + out[B] = ((pixel >> 8) & 15) * 17; out[A] = 255; - out += 4; in += 2; + out += 4; + in += 2; } } void -ImagingUnpackRGBA4B(UINT8* out, const UINT8* in, int pixels) -{ +ImagingUnpackRGBA4B(UINT8 *out, const UINT8 *in, int pixels) { int i, pixel; /* RGBA, 4 bits per pixel */ for (i = 0; i < pixels; i++) { pixel = in[0] + (in[1] << 8); out[R] = (pixel & 15) * 17; - out[G] = ((pixel>>4) & 15) * 17; - out[B] = ((pixel>>8) & 15) * 17; - out[A] = ((pixel>>12) & 15) * 17; - out += 4; in += 2; + out[G] = ((pixel >> 4) & 15) * 17; + out[B] = ((pixel >> 8) & 15) * 17; + out[A] = ((pixel >> 12) & 15) * 17; + out += 4; + in += 2; } } static void -ImagingUnpackBGRX(UINT8* _out, const UINT8* in, int pixels) -{ +ImagingUnpackBGRX(UINT8 *_out, const UINT8 *in, int pixels) { int i; /* RGB, reversed bytes with padding */ for (i = 0; i < pixels; i++) { UINT32 iv = MAKE_UINT32(in[2], in[1], in[0], 255); memcpy(_out, &iv, sizeof(iv)); - in += 4; _out += 4; + in += 4; + _out += 4; } } static void -ImagingUnpackXRGB(UINT8* _out, const UINT8* in, int pixels) -{ +ImagingUnpackXRGB(UINT8 *_out, const UINT8 *in, int pixels) { int i; /* RGB, leading pad */ for (i = 0; i < pixels; i++) { UINT32 iv = MAKE_UINT32(in[1], in[2], in[3], 255); memcpy(_out, &iv, sizeof(iv)); - in += 4; _out += 4; + in += 4; + _out += 4; } } static void -ImagingUnpackXBGR(UINT8* _out, const UINT8* in, int pixels) -{ +ImagingUnpackXBGR(UINT8 *_out, const UINT8 *in, int pixels) { int i; /* RGB, reversed bytes, leading pad */ for (i = 0; i < pixels; i++) { UINT32 iv = MAKE_UINT32(in[3], in[2], in[1], 255); memcpy(_out, &iv, sizeof(iv)); - in += 4; _out += 4; + in += 4; + _out += 4; } } /* Unpack to "RGBA" image */ static void -unpackRGBALA(UINT8* _out, const UINT8* in, int pixels) -{ +unpackRGBALA(UINT8 *_out, const UINT8 *in, int pixels) { int i; /* greyscale with alpha */ for (i = 0; i < pixels; i++) { UINT32 iv = MAKE_UINT32(in[0], in[0], in[0], in[1]); memcpy(_out, &iv, sizeof(iv)); - in += 2; _out += 4; + in += 2; + _out += 4; } } static void -unpackRGBALA16B(UINT8* _out, const UINT8* in, int pixels) -{ +unpackRGBALA16B(UINT8 *_out, const UINT8 *in, int pixels) { int i; /* 16-bit greyscale with alpha, big-endian */ for (i = 0; i < pixels; i++) { UINT32 iv = MAKE_UINT32(in[0], in[0], in[0], in[2]); memcpy(_out, &iv, sizeof(iv)); - in += 4; _out += 4; + in += 4; + _out += 4; } } static void -unpackRGBa16L(UINT8* _out, const UINT8* in, int pixels) -{ +unpackRGBa16L(UINT8 *_out, const UINT8 *in, int pixels) { int i; /* premultiplied 16-bit RGBA, little-endian */ for (i = 0; i < pixels; i++) { int a = in[7]; UINT32 iv; - if ( ! a) { + if (!a) { iv = 0; } else if (a == 255) { iv = MAKE_UINT32(in[1], in[3], in[5], a); } else { - iv = MAKE_UINT32(CLIP8(in[1] * 255 / a), - CLIP8(in[3] * 255 / a), - CLIP8(in[5] * 255 / a), a); + iv = MAKE_UINT32( + CLIP8(in[1] * 255 / a), + CLIP8(in[3] * 255 / a), + CLIP8(in[5] * 255 / a), + a); } memcpy(_out, &iv, sizeof(iv)); - in += 8; _out += 4; + in += 8; + _out += 4; } } static void -unpackRGBa16B(UINT8* _out, const UINT8* in, int pixels) -{ +unpackRGBa16B(UINT8 *_out, const UINT8 *in, int pixels) { int i; /* premultiplied 16-bit RGBA, big-endian */ for (i = 0; i < pixels; i++) { int a = in[6]; UINT32 iv; - if ( ! a) { + if (!a) { iv = 0; } else if (a == 255) { iv = MAKE_UINT32(in[0], in[2], in[4], a); } else { - iv = MAKE_UINT32(CLIP8(in[0] * 255 / a), - CLIP8(in[2] * 255 / a), - CLIP8(in[4] * 255 / a), a); + iv = MAKE_UINT32( + CLIP8(in[0] * 255 / a), + CLIP8(in[2] * 255 / a), + CLIP8(in[4] * 255 / a), + a); } memcpy(_out, &iv, sizeof(iv)); - in += 8; _out += 4; + in += 8; + _out += 4; } } static void -unpackRGBa(UINT8* _out, const UINT8* in, int pixels) -{ +unpackRGBa(UINT8 *_out, const UINT8 *in, int pixels) { int i; /* premultiplied RGBA */ for (i = 0; i < pixels; i++) { int a = in[3]; UINT32 iv; - if ( ! a) { + if (!a) { iv = 0; } else if (a == 255) { iv = MAKE_UINT32(in[0], in[1], in[2], a); } else { - iv = MAKE_UINT32(CLIP8(in[0] * 255 / a), - CLIP8(in[1] * 255 / a), - CLIP8(in[2] * 255 / a), a); + iv = MAKE_UINT32( + CLIP8(in[0] * 255 / a), + CLIP8(in[1] * 255 / a), + CLIP8(in[2] * 255 / a), + a); } memcpy(_out, &iv, sizeof(iv)); - in += 4; _out += 4; + in += 4; + _out += 4; } } static void -unpackRGBaskip1(UINT8* _out, const UINT8* in, int pixels) -{ +unpackRGBaskip1(UINT8 *_out, const UINT8 *in, int pixels) { int i; - UINT32* out = (UINT32*) _out; + UINT32 *out = (UINT32 *)_out; /* premultiplied RGBA */ for (i = 0; i < pixels; i++) { int a = in[3]; - if ( ! a) { + if (!a) { out[i] = 0; } else if (a == 255) { out[i] = MAKE_UINT32(in[0], in[1], in[2], a); } else { - out[i] = MAKE_UINT32(CLIP8(in[0] * 255 / a), - CLIP8(in[1] * 255 / a), - CLIP8(in[2] * 255 / a), a); + out[i] = MAKE_UINT32( + CLIP8(in[0] * 255 / a), + CLIP8(in[1] * 255 / a), + CLIP8(in[2] * 255 / a), + a); } in += 5; } } static void -unpackRGBaskip2(UINT8* _out, const UINT8* in, int pixels) -{ +unpackRGBaskip2(UINT8 *_out, const UINT8 *in, int pixels) { int i; - UINT32* out = (UINT32*) _out; + UINT32 *out = (UINT32 *)_out; /* premultiplied RGBA */ for (i = 0; i < pixels; i++) { int a = in[3]; - if ( ! a) { + if (!a) { out[i] = 0; } else if (a == 255) { out[i] = MAKE_UINT32(in[0], in[1], in[2], a); } else { - out[i] = MAKE_UINT32(CLIP8(in[0] * 255 / a), - CLIP8(in[1] * 255 / a), - CLIP8(in[2] * 255 / a), a); + out[i] = MAKE_UINT32( + CLIP8(in[0] * 255 / a), + CLIP8(in[1] * 255 / a), + CLIP8(in[2] * 255 / a), + a); } in += 6; } } static void -unpackBGRa(UINT8* _out, const UINT8* in, int pixels) -{ +unpackBGRa(UINT8 *_out, const UINT8 *in, int pixels) { int i; /* premultiplied BGRA */ for (i = 0; i < pixels; i++) { int a = in[3]; UINT32 iv; - if ( ! a) { + if (!a) { iv = 0; } else if (a == 255) { iv = MAKE_UINT32(in[2], in[1], in[0], a); } else { - iv = MAKE_UINT32(CLIP8(in[2] * 255 / a), - CLIP8(in[1] * 255 / a), - CLIP8(in[0] * 255 / a), a); + iv = MAKE_UINT32( + CLIP8(in[2] * 255 / a), + CLIP8(in[1] * 255 / a), + CLIP8(in[0] * 255 / a), + a); } memcpy(_out, &iv, sizeof(iv)); - in += 4; _out += 4; + in += 4; + _out += 4; } } static void -unpackRGBAI(UINT8* out, const UINT8* in, int pixels) -{ +unpackRGBAI(UINT8 *out, const UINT8 *in, int pixels) { int i; /* RGBA, inverted RGB bytes (FlashPix) */ for (i = 0; i < pixels; i++) { @@ -876,28 +988,30 @@ unpackRGBAI(UINT8* out, const UINT8* in, int pixels) out[G] = ~in[1]; out[B] = ~in[2]; out[A] = in[3]; - out += 4; in += 4; + out += 4; + in += 4; } } static void -unpackRGBAL(UINT8* _out, const UINT8* in, int pixels) -{ +unpackRGBAL(UINT8 *_out, const UINT8 *in, int pixels) { int i; /* RGBA, line interleaved */ - for (i = 0; i < pixels; i++, _out+=4) { - UINT32 iv = MAKE_UINT32(in[i], in[i+pixels], in[i+pixels+pixels], - in[i+pixels+pixels+pixels]); + for (i = 0; i < pixels; i++, _out += 4) { + UINT32 iv = MAKE_UINT32( + in[i], + in[i + pixels], + in[i + pixels + pixels], + in[i + pixels + pixels + pixels]); memcpy(_out, &iv, sizeof(iv)); } } void -unpackRGBA16L(UINT8* _out, const UINT8* in, int pixels) -{ +unpackRGBA16L(UINT8 *_out, const UINT8 *in, int pixels) { int i; /* 16-bit RGBA, little-endian order */ - for (i = 0; i < pixels; i++, _out+=4) { + for (i = 0; i < pixels; i++, _out += 4) { UINT32 iv = MAKE_UINT32(in[1], in[3], in[5], in[7]); memcpy(_out, &iv, sizeof(iv)); in += 8; @@ -905,11 +1019,10 @@ unpackRGBA16L(UINT8* _out, const UINT8* in, int pixels) } void -unpackRGBA16B(UINT8* _out, const UINT8* in, int pixels) -{ +unpackRGBA16B(UINT8 *_out, const UINT8 *in, int pixels) { int i; /* 16-bit RGBA, big-endian order */ - for (i = 0; i < pixels; i++, _out+=4) { + for (i = 0; i < pixels; i++, _out += 4) { UINT32 iv = MAKE_UINT32(in[0], in[2], in[4], in[6]); memcpy(_out, &iv, sizeof(iv)); in += 8; @@ -917,53 +1030,52 @@ unpackRGBA16B(UINT8* _out, const UINT8* in, int pixels) } static void -unpackARGB(UINT8* _out, const UINT8* in, int pixels) -{ +unpackARGB(UINT8 *_out, const UINT8 *in, int pixels) { int i; /* RGBA, leading pad */ for (i = 0; i < pixels; i++) { UINT32 iv = MAKE_UINT32(in[1], in[2], in[3], in[0]); memcpy(_out, &iv, sizeof(iv)); - in += 4; _out += 4; + in += 4; + _out += 4; } } static void -unpackABGR(UINT8* _out, const UINT8* in, int pixels) -{ +unpackABGR(UINT8 *_out, const UINT8 *in, int pixels) { int i; /* RGBA, reversed bytes */ for (i = 0; i < pixels; i++) { UINT32 iv = MAKE_UINT32(in[3], in[2], in[1], in[0]); memcpy(_out, &iv, sizeof(iv)); - in += 4; _out += 4; + in += 4; + _out += 4; } } static void -unpackBGRA(UINT8* _out, const UINT8* in, int pixels) -{ +unpackBGRA(UINT8 *_out, const UINT8 *in, int pixels) { int i; /* RGBA, reversed bytes */ for (i = 0; i < pixels; i++) { UINT32 iv = MAKE_UINT32(in[2], in[1], in[0], in[3]); memcpy(_out, &iv, sizeof(iv)); - in += 4; _out += 4; + in += 4; + _out += 4; } } - /* Unpack to "CMYK" image */ static void -unpackCMYKI(UINT8* _out, const UINT8* in, int pixels) -{ +unpackCMYKI(UINT8 *_out, const UINT8 *in, int pixels) { int i; /* CMYK, inverted bytes (Photoshop 2.5) */ for (i = 0; i < pixels; i++) { UINT32 iv = ~MAKE_UINT32(in[0], in[1], in[2], in[3]); memcpy(_out, &iv, sizeof(iv)); - in += 4; _out += 4; + in += 4; + _out += 4; } } @@ -979,8 +1091,7 @@ unpackCMYKI(UINT8* _out, const UINT8* in, int pixels) internally, and we'll unshift for saving and whatnot. */ void -ImagingUnpackLAB(UINT8* out, const UINT8* in, int pixels) -{ +ImagingUnpackLAB(UINT8 *out, const UINT8 *in, int pixels) { int i; /* LAB triplets */ for (i = 0; i < pixels; i++) { @@ -988,32 +1099,34 @@ ImagingUnpackLAB(UINT8* out, const UINT8* in, int pixels) out[1] = in[1] ^ 128; /* signed in outside world */ out[2] = in[2] ^ 128; out[3] = 255; - out += 4; in += 3; + out += 4; + in += 3; } } static void -unpackI16N_I16B(UINT8* out, const UINT8* in, int pixels){ +unpackI16N_I16B(UINT8 *out, const UINT8 *in, int pixels) { int i; - UINT8* tmp = (UINT8*) out; + UINT8 *tmp = (UINT8 *)out; for (i = 0; i < pixels; i++) { C16B; - in += 2; tmp += 2; + in += 2; + tmp += 2; } - } static void -unpackI16N_I16(UINT8* out, const UINT8* in, int pixels){ +unpackI16N_I16(UINT8 *out, const UINT8 *in, int pixels) { int i; - UINT8* tmp = (UINT8*) out; + UINT8 *tmp = (UINT8 *)out; for (i = 0; i < pixels; i++) { C16L; - in += 2; tmp += 2; + in += 2; + tmp += 2; } } static void -unpackI12_I16(UINT8* out, const UINT8* in, int pixels){ +unpackI12_I16(UINT8 *out, const UINT8 *in, int pixels) { /* Fillorder 1/MSB -> LittleEndian, for 12bit integer greyscale tiffs. According to the TIFF spec: @@ -1035,102 +1148,100 @@ unpackI12_I16(UINT8* out, const UINT8* in, int pixels){ int i; UINT16 pixel; #ifdef WORDS_BIGENDIAN - UINT8* tmp = (UINT8 *)&pixel; + UINT8 *tmp = (UINT8 *)&pixel; #endif - for (i = 0; i < pixels-1; i+=2) { - pixel = (((UINT16) in[0]) << 4 ) + (in[1] >>4); + for (i = 0; i < pixels - 1; i += 2) { + pixel = (((UINT16)in[0]) << 4) + (in[1] >> 4); #ifdef WORDS_BIGENDIAN - out[0] = tmp[1]; out[1] = tmp[0]; + out[0] = tmp[1]; + out[1] = tmp[0]; #else memcpy(out, &pixel, sizeof(pixel)); #endif - out+=2; - pixel = (((UINT16) (in[1] & 0x0F)) << 8) + in[2]; + out += 2; + pixel = (((UINT16)(in[1] & 0x0F)) << 8) + in[2]; #ifdef WORDS_BIGENDIAN - out[0] = tmp[1]; out[1] = tmp[0]; + out[0] = tmp[1]; + out[1] = tmp[0]; #else memcpy(out, &pixel, sizeof(pixel)); #endif - in += 3; out+=2; + in += 3; + out += 2; } - if (i == pixels-1) { - pixel = (((UINT16) in[0]) << 4 ) + (in[1] >>4); + if (i == pixels - 1) { + pixel = (((UINT16)in[0]) << 4) + (in[1] >> 4); #ifdef WORDS_BIGENDIAN - out[0] = tmp[1]; out[1] = tmp[0]; + out[0] = tmp[1]; + out[1] = tmp[0]; #else memcpy(out, &pixel, sizeof(pixel)); #endif } } - static void -copy1(UINT8* out, const UINT8* in, int pixels) -{ +copy1(UINT8 *out, const UINT8 *in, int pixels) { /* L, P */ memcpy(out, in, pixels); } static void -copy2(UINT8* out, const UINT8* in, int pixels) -{ +copy2(UINT8 *out, const UINT8 *in, int pixels) { /* I;16 */ - memcpy(out, in, pixels*2); + memcpy(out, in, pixels * 2); } static void -copy4(UINT8* out, const UINT8* in, int pixels) -{ +copy4(UINT8 *out, const UINT8 *in, int pixels) { /* RGBA, CMYK quadruples */ memcpy(out, in, 4 * pixels); } static void -copy4skip1(UINT8* _out, const UINT8* in, int pixels) -{ +copy4skip1(UINT8 *_out, const UINT8 *in, int pixels) { int i; for (i = 0; i < pixels; i++) { memcpy(_out, in, 4); - in += 5; _out += 4; + in += 5; + _out += 4; } } static void -copy4skip2(UINT8* _out, const UINT8* in, int pixels) -{ +copy4skip2(UINT8 *_out, const UINT8 *in, int pixels) { int i; for (i = 0; i < pixels; i++) { memcpy(_out, in, 4); - in += 6; _out += 4; + in += 6; + _out += 4; } } - /* Unpack to "I" and "F" images */ -#define UNPACK_RAW(NAME, GET, INTYPE, OUTTYPE)\ -static void NAME(UINT8* out_, const UINT8* in, int pixels)\ -{\ - int i;\ - OUTTYPE* out = (OUTTYPE*) out_;\ - for (i = 0; i < pixels; i++, in += sizeof(INTYPE))\ - out[i] = (OUTTYPE) ((INTYPE) GET);\ -} +#define UNPACK_RAW(NAME, GET, INTYPE, OUTTYPE) \ + static void NAME(UINT8 *out_, const UINT8 *in, int pixels) { \ + int i; \ + OUTTYPE *out = (OUTTYPE *)out_; \ + for (i = 0; i < pixels; i++, in += sizeof(INTYPE)) { \ + out[i] = (OUTTYPE)((INTYPE)GET); \ + } \ + } -#define UNPACK(NAME, COPY, INTYPE, OUTTYPE)\ -static void NAME(UINT8* out_, const UINT8* in, int pixels)\ -{\ - int i;\ - OUTTYPE* out = (OUTTYPE*) out_;\ - INTYPE tmp_;\ - UINT8* tmp = (UINT8*) &tmp_;\ - for (i = 0; i < pixels; i++, in += sizeof(INTYPE)) {\ - COPY;\ - out[i] = (OUTTYPE) tmp_;\ - }\ -} +#define UNPACK(NAME, COPY, INTYPE, OUTTYPE) \ + static void NAME(UINT8 *out_, const UINT8 *in, int pixels) { \ + int i; \ + OUTTYPE *out = (OUTTYPE *)out_; \ + INTYPE tmp_; \ + UINT8 *tmp = (UINT8 *)&tmp_; \ + for (i = 0; i < pixels; i++, in += sizeof(INTYPE)) { \ + COPY; \ + out[i] = (OUTTYPE)tmp_; \ + } \ + } UNPACK_RAW(unpackI8, in[0], UINT8, INT32) UNPACK_RAW(unpackI8S, in[0], INT8, INT32) @@ -1170,12 +1281,10 @@ UNPACK(unpackF64BF, C64B, FLOAT64, FLOAT32) UNPACK(unpackF64NF, C64N, FLOAT64, FLOAT32) #endif - /* Misc. unpackers */ static void -band0(UINT8* out, const UINT8* in, int pixels) -{ +band0(UINT8 *out, const UINT8 *in, int pixels) { int i; /* band 0 only */ for (i = 0; i < pixels; i++) { @@ -1185,8 +1294,7 @@ band0(UINT8* out, const UINT8* in, int pixels) } static void -band1(UINT8* out, const UINT8* in, int pixels) -{ +band1(UINT8 *out, const UINT8 *in, int pixels) { int i; /* band 1 only */ for (i = 0; i < pixels; i++) { @@ -1196,8 +1304,7 @@ band1(UINT8* out, const UINT8* in, int pixels) } static void -band2(UINT8* out, const UINT8* in, int pixels) -{ +band2(UINT8 *out, const UINT8 *in, int pixels) { int i; /* band 2 only */ for (i = 0; i < pixels; i++) { @@ -1207,8 +1314,7 @@ band2(UINT8* out, const UINT8* in, int pixels) } static void -band3(UINT8* out, const UINT8* in, int pixels) -{ +band3(UINT8 *out, const UINT8 *in, int pixels) { /* band 3 only */ int i; for (i = 0; i < pixels; i++) { @@ -1218,8 +1324,7 @@ band3(UINT8* out, const UINT8* in, int pixels) } static void -band0I(UINT8* out, const UINT8* in, int pixels) -{ +band0I(UINT8 *out, const UINT8 *in, int pixels) { int i; /* band 0 only */ for (i = 0; i < pixels; i++) { @@ -1229,8 +1334,7 @@ band0I(UINT8* out, const UINT8* in, int pixels) } static void -band1I(UINT8* out, const UINT8* in, int pixels) -{ +band1I(UINT8 *out, const UINT8 *in, int pixels) { int i; /* band 1 only */ for (i = 0; i < pixels; i++) { @@ -1240,8 +1344,7 @@ band1I(UINT8* out, const UINT8* in, int pixels) } static void -band2I(UINT8* out, const UINT8* in, int pixels) -{ +band2I(UINT8 *out, const UINT8 *in, int pixels) { int i; /* band 2 only */ for (i = 0; i < pixels; i++) { @@ -1251,8 +1354,7 @@ band2I(UINT8* out, const UINT8* in, int pixels) } static void -band3I(UINT8* out, const UINT8* in, int pixels) -{ +band3I(UINT8 *out, const UINT8 *in, int pixels) { /* band 3 only */ int i; for (i = 0; i < pixels; i++) { @@ -1262,8 +1364,8 @@ band3I(UINT8* out, const UINT8* in, int pixels) } static struct { - const char* mode; - const char* rawmode; + const char *mode; + const char *rawmode; int bits; ImagingShuffler unpack; } unpackers[] = { @@ -1280,254 +1382,254 @@ static struct { /* exception: rawmodes "I" and "F" are always native endian byte order */ /* bilevel */ - {"1", "1", 1, unpack1}, - {"1", "1;I", 1, unpack1I}, - {"1", "1;R", 1, unpack1R}, - {"1", "1;IR", 1, unpack1IR}, - {"1", "1;8", 8, unpack18}, + {"1", "1", 1, unpack1}, + {"1", "1;I", 1, unpack1I}, + {"1", "1;R", 1, unpack1R}, + {"1", "1;IR", 1, unpack1IR}, + {"1", "1;8", 8, unpack18}, /* greyscale */ - {"L", "L;2", 2, unpackL2}, - {"L", "L;2I", 2, unpackL2I}, - {"L", "L;2R", 2, unpackL2R}, - {"L", "L;2IR", 2, unpackL2IR}, + {"L", "L;2", 2, unpackL2}, + {"L", "L;2I", 2, unpackL2I}, + {"L", "L;2R", 2, unpackL2R}, + {"L", "L;2IR", 2, unpackL2IR}, - {"L", "L;4", 4, unpackL4}, - {"L", "L;4I", 4, unpackL4I}, - {"L", "L;4R", 4, unpackL4R}, - {"L", "L;4IR", 4, unpackL4IR}, + {"L", "L;4", 4, unpackL4}, + {"L", "L;4I", 4, unpackL4I}, + {"L", "L;4R", 4, unpackL4R}, + {"L", "L;4IR", 4, unpackL4IR}, - {"L", "L", 8, copy1}, - {"L", "L;I", 8, unpackLI}, - {"L", "L;R", 8, unpackLR}, - {"L", "L;16", 16, unpackL16}, - {"L", "L;16B", 16, unpackL16B}, + {"L", "L", 8, copy1}, + {"L", "L;I", 8, unpackLI}, + {"L", "L;R", 8, unpackLR}, + {"L", "L;16", 16, unpackL16}, + {"L", "L;16B", 16, unpackL16B}, /* greyscale w. alpha */ - {"LA", "LA", 16, unpackLA}, - {"LA", "LA;L", 16, unpackLAL}, - + {"LA", "LA", 16, unpackLA}, + {"LA", "LA;L", 16, unpackLAL}, + /* greyscale w. alpha premultiplied */ - {"La", "La", 16, unpackLA}, + {"La", "La", 16, unpackLA}, /* palette */ - {"P", "P;1", 1, unpackP1}, - {"P", "P;2", 2, unpackP2}, - {"P", "P;2L", 2, unpackP2L}, - {"P", "P;4", 4, unpackP4}, - {"P", "P;4L", 4, unpackP4L}, - {"P", "P", 8, copy1}, - {"P", "P;R", 8, unpackLR}, + {"P", "P;1", 1, unpackP1}, + {"P", "P;2", 2, unpackP2}, + {"P", "P;2L", 2, unpackP2L}, + {"P", "P;4", 4, unpackP4}, + {"P", "P;4L", 4, unpackP4L}, + {"P", "P", 8, copy1}, + {"P", "P;R", 8, unpackLR}, /* palette w. alpha */ - {"PA", "PA", 16, unpackLA}, - {"PA", "PA;L", 16, unpackLAL}, + {"PA", "PA", 16, unpackLA}, + {"PA", "PA;L", 16, unpackLAL}, /* true colour */ - {"RGB", "RGB", 24, ImagingUnpackRGB}, - {"RGB", "RGB;L", 24, unpackRGBL}, - {"RGB", "RGB;R", 24, unpackRGBR}, - {"RGB", "RGB;16L", 48, unpackRGB16L}, - {"RGB", "RGB;16B", 48, unpackRGB16B}, - {"RGB", "BGR", 24, ImagingUnpackBGR}, - {"RGB", "RGB;15", 16, ImagingUnpackRGB15}, - {"RGB", "BGR;15", 16, ImagingUnpackBGR15}, - {"RGB", "RGB;16", 16, ImagingUnpackRGB16}, - {"RGB", "BGR;16", 16, ImagingUnpackBGR16}, - {"RGB", "RGB;4B", 16, ImagingUnpackRGB4B}, - {"RGB", "BGR;5", 16, ImagingUnpackBGR15}, /* compat */ - {"RGB", "RGBX", 32, copy4}, - {"RGB", "RGBX;L", 32, unpackRGBAL}, - {"RGB", "RGBA;L", 32, unpackRGBAL}, - {"RGB", "BGRX", 32, ImagingUnpackBGRX}, - {"RGB", "XRGB", 32, ImagingUnpackXRGB}, - {"RGB", "XBGR", 32, ImagingUnpackXBGR}, - {"RGB", "YCC;P", 24, ImagingUnpackYCC}, - {"RGB", "R", 8, band0}, - {"RGB", "G", 8, band1}, - {"RGB", "B", 8, band2}, + {"RGB", "RGB", 24, ImagingUnpackRGB}, + {"RGB", "RGB;L", 24, unpackRGBL}, + {"RGB", "RGB;R", 24, unpackRGBR}, + {"RGB", "RGB;16L", 48, unpackRGB16L}, + {"RGB", "RGB;16B", 48, unpackRGB16B}, + {"RGB", "BGR", 24, ImagingUnpackBGR}, + {"RGB", "RGB;15", 16, ImagingUnpackRGB15}, + {"RGB", "BGR;15", 16, ImagingUnpackBGR15}, + {"RGB", "RGB;16", 16, ImagingUnpackRGB16}, + {"RGB", "BGR;16", 16, ImagingUnpackBGR16}, + {"RGB", "RGB;4B", 16, ImagingUnpackRGB4B}, + {"RGB", "BGR;5", 16, ImagingUnpackBGR15}, /* compat */ + {"RGB", "RGBX", 32, copy4}, + {"RGB", "RGBX;L", 32, unpackRGBAL}, + {"RGB", "RGBA;L", 32, unpackRGBAL}, + {"RGB", "BGRX", 32, ImagingUnpackBGRX}, + {"RGB", "XRGB", 32, ImagingUnpackXRGB}, + {"RGB", "XBGR", 32, ImagingUnpackXBGR}, + {"RGB", "YCC;P", 24, ImagingUnpackYCC}, + {"RGB", "R", 8, band0}, + {"RGB", "G", 8, band1}, + {"RGB", "B", 8, band2}, /* true colour w. alpha */ - {"RGBA", "LA", 16, unpackRGBALA}, - {"RGBA", "LA;16B", 32, unpackRGBALA16B}, - {"RGBA", "RGBA", 32, copy4}, - {"RGBA", "RGBAX", 40, copy4skip1}, - {"RGBA", "RGBAXX", 48, copy4skip2}, - {"RGBA", "RGBa", 32, unpackRGBa}, - {"RGBA", "RGBaX", 40, unpackRGBaskip1}, - {"RGBA", "RGBaXX", 48, unpackRGBaskip2}, - {"RGBA", "RGBa;16L", 64, unpackRGBa16L}, - {"RGBA", "RGBa;16B", 64, unpackRGBa16B}, - {"RGBA", "BGRa", 32, unpackBGRa}, - {"RGBA", "RGBA;I", 32, unpackRGBAI}, - {"RGBA", "RGBA;L", 32, unpackRGBAL}, - {"RGBA", "RGBA;15", 16, ImagingUnpackRGBA15}, - {"RGBA", "BGRA;15", 16, ImagingUnpackBGRA15}, - {"RGBA", "RGBA;4B", 16, ImagingUnpackRGBA4B}, - {"RGBA", "RGBA;16L", 64, unpackRGBA16L}, - {"RGBA", "RGBA;16B", 64, unpackRGBA16B}, - {"RGBA", "BGRA", 32, unpackBGRA}, - {"RGBA", "ARGB", 32, unpackARGB}, - {"RGBA", "ABGR", 32, unpackABGR}, - {"RGBA", "YCCA;P", 32, ImagingUnpackYCCA}, - {"RGBA", "R", 8, band0}, - {"RGBA", "G", 8, band1}, - {"RGBA", "B", 8, band2}, - {"RGBA", "A", 8, band3}, + {"RGBA", "LA", 16, unpackRGBALA}, + {"RGBA", "LA;16B", 32, unpackRGBALA16B}, + {"RGBA", "RGBA", 32, copy4}, + {"RGBA", "RGBAX", 40, copy4skip1}, + {"RGBA", "RGBAXX", 48, copy4skip2}, + {"RGBA", "RGBa", 32, unpackRGBa}, + {"RGBA", "RGBaX", 40, unpackRGBaskip1}, + {"RGBA", "RGBaXX", 48, unpackRGBaskip2}, + {"RGBA", "RGBa;16L", 64, unpackRGBa16L}, + {"RGBA", "RGBa;16B", 64, unpackRGBa16B}, + {"RGBA", "BGRa", 32, unpackBGRa}, + {"RGBA", "RGBA;I", 32, unpackRGBAI}, + {"RGBA", "RGBA;L", 32, unpackRGBAL}, + {"RGBA", "RGBA;15", 16, ImagingUnpackRGBA15}, + {"RGBA", "BGRA;15", 16, ImagingUnpackBGRA15}, + {"RGBA", "RGBA;4B", 16, ImagingUnpackRGBA4B}, + {"RGBA", "RGBA;16L", 64, unpackRGBA16L}, + {"RGBA", "RGBA;16B", 64, unpackRGBA16B}, + {"RGBA", "BGRA", 32, unpackBGRA}, + {"RGBA", "ARGB", 32, unpackARGB}, + {"RGBA", "ABGR", 32, unpackABGR}, + {"RGBA", "YCCA;P", 32, ImagingUnpackYCCA}, + {"RGBA", "R", 8, band0}, + {"RGBA", "G", 8, band1}, + {"RGBA", "B", 8, band2}, + {"RGBA", "A", 8, band3}, #ifdef WORDS_BIGENDIAN - {"RGB", "RGB;16N", 48, unpackRGB16B}, - {"RGBA", "RGBa;16N", 64, unpackRGBa16B}, - {"RGBA", "RGBA;16N", 64, unpackRGBA16B}, - {"RGBX", "RGBX;16N", 64, unpackRGBA16B}, + {"RGB", "RGB;16N", 48, unpackRGB16B}, + {"RGBA", "RGBa;16N", 64, unpackRGBa16B}, + {"RGBA", "RGBA;16N", 64, unpackRGBA16B}, + {"RGBX", "RGBX;16N", 64, unpackRGBA16B}, #else - {"RGB", "RGB;16N", 48, unpackRGB16L}, - {"RGBA", "RGBa;16N", 64, unpackRGBa16L}, - {"RGBA", "RGBA;16N", 64, unpackRGBA16L}, - {"RGBX", "RGBX;16N", 64, unpackRGBA16B}, + {"RGB", "RGB;16N", 48, unpackRGB16L}, + {"RGBA", "RGBa;16N", 64, unpackRGBa16L}, + {"RGBA", "RGBA;16N", 64, unpackRGBA16L}, + {"RGBX", "RGBX;16N", 64, unpackRGBA16B}, #endif /* true colour w. alpha premultiplied */ - {"RGBa", "RGBa", 32, copy4}, - {"RGBa", "BGRa", 32, unpackBGRA}, - {"RGBa", "aRGB", 32, unpackARGB}, - {"RGBa", "aBGR", 32, unpackABGR}, + {"RGBa", "RGBa", 32, copy4}, + {"RGBa", "BGRa", 32, unpackBGRA}, + {"RGBa", "aRGB", 32, unpackARGB}, + {"RGBa", "aBGR", 32, unpackABGR}, /* true colour w. padding */ - {"RGBX", "RGB", 24, ImagingUnpackRGB}, - {"RGBX", "RGB;L", 24, unpackRGBL}, - {"RGBX", "RGB;16B", 48, unpackRGB16B}, - {"RGBX", "BGR", 24, ImagingUnpackBGR}, - {"RGBX", "RGB;15", 16, ImagingUnpackRGB15}, - {"RGBX", "BGR;15", 16, ImagingUnpackBGR15}, - {"RGBX", "RGB;4B", 16, ImagingUnpackRGB4B}, - {"RGBX", "BGR;5", 16, ImagingUnpackBGR15}, /* compat */ - {"RGBX", "RGBX", 32, copy4}, - {"RGBX", "RGBXX", 40, copy4skip1}, - {"RGBX", "RGBXXX", 48, copy4skip2}, - {"RGBX", "RGBX;L", 32, unpackRGBAL}, - {"RGBX", "RGBX;16L", 64, unpackRGBA16L}, - {"RGBX", "RGBX;16B", 64, unpackRGBA16B}, - {"RGBX", "BGRX", 32, ImagingUnpackBGRX}, - {"RGBX", "XRGB", 32, ImagingUnpackXRGB}, - {"RGBX", "XBGR", 32, ImagingUnpackXBGR}, - {"RGBX", "YCC;P", 24, ImagingUnpackYCC}, - {"RGBX", "R", 8, band0}, - {"RGBX", "G", 8, band1}, - {"RGBX", "B", 8, band2}, - {"RGBX", "X", 8, band3}, + {"RGBX", "RGB", 24, ImagingUnpackRGB}, + {"RGBX", "RGB;L", 24, unpackRGBL}, + {"RGBX", "RGB;16B", 48, unpackRGB16B}, + {"RGBX", "BGR", 24, ImagingUnpackBGR}, + {"RGBX", "RGB;15", 16, ImagingUnpackRGB15}, + {"RGBX", "BGR;15", 16, ImagingUnpackBGR15}, + {"RGBX", "RGB;4B", 16, ImagingUnpackRGB4B}, + {"RGBX", "BGR;5", 16, ImagingUnpackBGR15}, /* compat */ + {"RGBX", "RGBX", 32, copy4}, + {"RGBX", "RGBXX", 40, copy4skip1}, + {"RGBX", "RGBXXX", 48, copy4skip2}, + {"RGBX", "RGBX;L", 32, unpackRGBAL}, + {"RGBX", "RGBX;16L", 64, unpackRGBA16L}, + {"RGBX", "RGBX;16B", 64, unpackRGBA16B}, + {"RGBX", "BGRX", 32, ImagingUnpackBGRX}, + {"RGBX", "XRGB", 32, ImagingUnpackXRGB}, + {"RGBX", "XBGR", 32, ImagingUnpackXBGR}, + {"RGBX", "YCC;P", 24, ImagingUnpackYCC}, + {"RGBX", "R", 8, band0}, + {"RGBX", "G", 8, band1}, + {"RGBX", "B", 8, band2}, + {"RGBX", "X", 8, band3}, /* colour separation */ - {"CMYK", "CMYK", 32, copy4}, - {"CMYK", "CMYKX", 40, copy4skip1}, - {"CMYK", "CMYKXX", 48, copy4skip2}, - {"CMYK", "CMYK;I", 32, unpackCMYKI}, - {"CMYK", "CMYK;L", 32, unpackRGBAL}, - {"CMYK", "CMYK;16L", 64, unpackRGBA16L}, - {"CMYK", "CMYK;16B", 64, unpackRGBA16B}, - {"CMYK", "C", 8, band0}, - {"CMYK", "M", 8, band1}, - {"CMYK", "Y", 8, band2}, - {"CMYK", "K", 8, band3}, - {"CMYK", "C;I", 8, band0I}, - {"CMYK", "M;I", 8, band1I}, - {"CMYK", "Y;I", 8, band2I}, - {"CMYK", "K;I", 8, band3I}, + {"CMYK", "CMYK", 32, copy4}, + {"CMYK", "CMYKX", 40, copy4skip1}, + {"CMYK", "CMYKXX", 48, copy4skip2}, + {"CMYK", "CMYK;I", 32, unpackCMYKI}, + {"CMYK", "CMYK;L", 32, unpackRGBAL}, + {"CMYK", "CMYK;16L", 64, unpackRGBA16L}, + {"CMYK", "CMYK;16B", 64, unpackRGBA16B}, + {"CMYK", "C", 8, band0}, + {"CMYK", "M", 8, band1}, + {"CMYK", "Y", 8, band2}, + {"CMYK", "K", 8, band3}, + {"CMYK", "C;I", 8, band0I}, + {"CMYK", "M;I", 8, band1I}, + {"CMYK", "Y;I", 8, band2I}, + {"CMYK", "K;I", 8, band3I}, #ifdef WORDS_BIGENDIAN - {"CMYK", "CMYK;16N", 64, unpackRGBA16B}, + {"CMYK", "CMYK;16N", 64, unpackRGBA16B}, #else - {"CMYK", "CMYK;16N", 64, unpackRGBA16L}, + {"CMYK", "CMYK;16N", 64, unpackRGBA16L}, #endif /* video (YCbCr) */ - {"YCbCr", "YCbCr", 24, ImagingUnpackRGB}, - {"YCbCr", "YCbCr;L", 24, unpackRGBL}, - {"YCbCr", "YCbCrX", 32, copy4}, - {"YCbCr", "YCbCrK", 32, copy4}, + {"YCbCr", "YCbCr", 24, ImagingUnpackRGB}, + {"YCbCr", "YCbCr;L", 24, unpackRGBL}, + {"YCbCr", "YCbCrX", 32, copy4}, + {"YCbCr", "YCbCrK", 32, copy4}, /* LAB Color */ - {"LAB", "LAB", 24, ImagingUnpackLAB}, - {"LAB", "L", 8, band0}, - {"LAB", "A", 8, band1}, - {"LAB", "B", 8, band2}, + {"LAB", "LAB", 24, ImagingUnpackLAB}, + {"LAB", "L", 8, band0}, + {"LAB", "A", 8, band1}, + {"LAB", "B", 8, band2}, /* HSV Color */ - {"HSV", "HSV", 24, ImagingUnpackRGB}, - {"HSV", "H", 8, band0}, - {"HSV", "S", 8, band1}, - {"HSV", "V", 8, band2}, + {"HSV", "HSV", 24, ImagingUnpackRGB}, + {"HSV", "H", 8, band0}, + {"HSV", "S", 8, band1}, + {"HSV", "V", 8, band2}, /* integer variations */ - {"I", "I", 32, copy4}, - {"I", "I;8", 8, unpackI8}, - {"I", "I;8S", 8, unpackI8S}, - {"I", "I;16", 16, unpackI16}, - {"I", "I;16S", 16, unpackI16S}, - {"I", "I;16B", 16, unpackI16B}, - {"I", "I;16BS", 16, unpackI16BS}, - {"I", "I;16N", 16, unpackI16N}, - {"I", "I;16NS", 16, unpackI16NS}, - {"I", "I;32", 32, unpackI32}, - {"I", "I;32S", 32, unpackI32S}, - {"I", "I;32B", 32, unpackI32B}, - {"I", "I;32BS", 32, unpackI32BS}, - {"I", "I;32N", 32, unpackI32N}, - {"I", "I;32NS", 32, unpackI32NS}, + {"I", "I", 32, copy4}, + {"I", "I;8", 8, unpackI8}, + {"I", "I;8S", 8, unpackI8S}, + {"I", "I;16", 16, unpackI16}, + {"I", "I;16S", 16, unpackI16S}, + {"I", "I;16B", 16, unpackI16B}, + {"I", "I;16BS", 16, unpackI16BS}, + {"I", "I;16N", 16, unpackI16N}, + {"I", "I;16NS", 16, unpackI16NS}, + {"I", "I;32", 32, unpackI32}, + {"I", "I;32S", 32, unpackI32S}, + {"I", "I;32B", 32, unpackI32B}, + {"I", "I;32BS", 32, unpackI32BS}, + {"I", "I;32N", 32, unpackI32N}, + {"I", "I;32NS", 32, unpackI32NS}, /* floating point variations */ - {"F", "F", 32, copy4}, - {"F", "F;8", 8, unpackF8}, - {"F", "F;8S", 8, unpackF8S}, - {"F", "F;16", 16, unpackF16}, - {"F", "F;16S", 16, unpackF16S}, - {"F", "F;16B", 16, unpackF16B}, - {"F", "F;16BS", 16, unpackF16BS}, - {"F", "F;16N", 16, unpackF16N}, - {"F", "F;16NS", 16, unpackF16NS}, - {"F", "F;32", 32, unpackF32}, - {"F", "F;32S", 32, unpackF32S}, - {"F", "F;32B", 32, unpackF32B}, - {"F", "F;32BS", 32, unpackF32BS}, - {"F", "F;32N", 32, unpackF32N}, - {"F", "F;32NS", 32, unpackF32NS}, - {"F", "F;32F", 32, unpackF32F}, - {"F", "F;32BF", 32, unpackF32BF}, - {"F", "F;32NF", 32, unpackF32NF}, + {"F", "F", 32, copy4}, + {"F", "F;8", 8, unpackF8}, + {"F", "F;8S", 8, unpackF8S}, + {"F", "F;16", 16, unpackF16}, + {"F", "F;16S", 16, unpackF16S}, + {"F", "F;16B", 16, unpackF16B}, + {"F", "F;16BS", 16, unpackF16BS}, + {"F", "F;16N", 16, unpackF16N}, + {"F", "F;16NS", 16, unpackF16NS}, + {"F", "F;32", 32, unpackF32}, + {"F", "F;32S", 32, unpackF32S}, + {"F", "F;32B", 32, unpackF32B}, + {"F", "F;32BS", 32, unpackF32BS}, + {"F", "F;32N", 32, unpackF32N}, + {"F", "F;32NS", 32, unpackF32NS}, + {"F", "F;32F", 32, unpackF32F}, + {"F", "F;32BF", 32, unpackF32BF}, + {"F", "F;32NF", 32, unpackF32NF}, #ifdef FLOAT64 - {"F", "F;64F", 64, unpackF64F}, - {"F", "F;64BF", 64, unpackF64BF}, - {"F", "F;64NF", 64, unpackF64NF}, + {"F", "F;64F", 64, unpackF64F}, + {"F", "F;64BF", 64, unpackF64BF}, + {"F", "F;64NF", 64, unpackF64NF}, #endif /* storage modes */ - {"I;16", "I;16", 16, copy2}, - {"I;16B", "I;16B", 16, copy2}, - {"I;16L", "I;16L", 16, copy2}, + {"I;16", "I;16", 16, copy2}, + {"I;16B", "I;16B", 16, copy2}, + {"I;16L", "I;16L", 16, copy2}, - {"I;16", "I;16N", 16, unpackI16N_I16}, // LibTiff native->image endian. - {"I;16L", "I;16N", 16, unpackI16N_I16}, // LibTiff native->image endian. - {"I;16B", "I;16N", 16, unpackI16N_I16B}, + {"I;16", "I;16N", 16, unpackI16N_I16}, // LibTiff native->image endian. + {"I;16L", "I;16N", 16, unpackI16N_I16}, // LibTiff native->image endian. + {"I;16B", "I;16N", 16, unpackI16N_I16B}, - {"I;16", "I;12", 12, unpackI12_I16}, // 12 bit Tiffs stored in 16bits. + {"I;16", "I;12", 12, unpackI12_I16}, // 12 bit Tiffs stored in 16bits. {NULL} /* sentinel */ }; - ImagingShuffler -ImagingFindUnpacker(const char* mode, const char* rawmode, int* bits_out) -{ +ImagingFindUnpacker(const char *mode, const char *rawmode, int *bits_out) { int i; /* find a suitable pixel unpacker */ - for (i = 0; unpackers[i].rawmode; i++) + for (i = 0; unpackers[i].rawmode; i++) { if (strcmp(unpackers[i].mode, mode) == 0 && strcmp(unpackers[i].rawmode, rawmode) == 0) { - if (bits_out) + if (bits_out) { *bits_out = unpackers[i].bits; + } return unpackers[i].unpack; } + } /* FIXME: configure a general unpacker based on the type codes... */ diff --git a/src/libImaging/UnpackYCC.c b/src/libImaging/UnpackYCC.c index 19da1f654..0b177bdd4 100644 --- a/src/libImaging/UnpackYCC.c +++ b/src/libImaging/UnpackYCC.c @@ -5,7 +5,7 @@ * code to convert and unpack PhotoYCC data * * history: - * 97-01-25 fl Moved from PcdDecode.c + * 97-01-25 fl Moved from PcdDecode.c * * Copyright (c) Fredrik Lundh 1996-97. * Copyright (c) Secret Labs AB 1997. @@ -13,150 +13,151 @@ * See the README file for information on usage and redistribution. */ - #include "Imaging.h" - /* Tables generated by pcdtables.py, based on transforms taken from the "Colour Space Conversions FAQ" by Roberts/Ford. */ -static INT16 L[] = { 0, 1, 3, 4, 5, 7, 8, 10, 11, 12, 14, 15, 16, 18, -19, 20, 22, 23, 24, 26, 27, 29, 30, 31, 33, 34, 35, 37, 38, 39, 41, -42, 43, 45, 46, 48, 49, 50, 52, 53, 54, 56, 57, 58, 60, 61, 62, 64, -65, 67, 68, 69, 71, 72, 73, 75, 76, 77, 79, 80, 82, 83, 84, 86, 87, -88, 90, 91, 92, 94, 95, 96, 98, 99, 101, 102, 103, 105, 106, 107, 109, -110, 111, 113, 114, 115, 117, 118, 120, 121, 122, 124, 125, 126, 128, -129, 130, 132, 133, 134, 136, 137, 139, 140, 141, 143, 144, 145, 147, -148, 149, 151, 152, 153, 155, 156, 158, 159, 160, 162, 163, 164, 166, -167, 168, 170, 171, 173, 174, 175, 177, 178, 179, 181, 182, 183, 185, -186, 187, 189, 190, 192, 193, 194, 196, 197, 198, 200, 201, 202, 204, -205, 206, 208, 209, 211, 212, 213, 215, 216, 217, 219, 220, 221, 223, -224, 225, 227, 228, 230, 231, 232, 234, 235, 236, 238, 239, 240, 242, -243, 245, 246, 247, 249, 250, 251, 253, 254, 255, 257, 258, 259, 261, -262, 264, 265, 266, 268, 269, 270, 272, 273, 274, 276, 277, 278, 280, -281, 283, 284, 285, 287, 288, 289, 291, 292, 293, 295, 296, 297, 299, -300, 302, 303, 304, 306, 307, 308, 310, 311, 312, 314, 315, 317, 318, -319, 321, 322, 323, 325, 326, 327, 329, 330, 331, 333, 334, 336, 337, -338, 340, 341, 342, 344, 345, 346 }; +static INT16 L[] = { + 0, 1, 3, 4, 5, 7, 8, 10, 11, 12, 14, 15, 16, 18, 19, 20, + 22, 23, 24, 26, 27, 29, 30, 31, 33, 34, 35, 37, 38, 39, 41, 42, + 43, 45, 46, 48, 49, 50, 52, 53, 54, 56, 57, 58, 60, 61, 62, 64, + 65, 67, 68, 69, 71, 72, 73, 75, 76, 77, 79, 80, 82, 83, 84, 86, + 87, 88, 90, 91, 92, 94, 95, 96, 98, 99, 101, 102, 103, 105, 106, 107, + 109, 110, 111, 113, 114, 115, 117, 118, 120, 121, 122, 124, 125, 126, 128, 129, + 130, 132, 133, 134, 136, 137, 139, 140, 141, 143, 144, 145, 147, 148, 149, 151, + 152, 153, 155, 156, 158, 159, 160, 162, 163, 164, 166, 167, 168, 170, 171, 173, + 174, 175, 177, 178, 179, 181, 182, 183, 185, 186, 187, 189, 190, 192, 193, 194, + 196, 197, 198, 200, 201, 202, 204, 205, 206, 208, 209, 211, 212, 213, 215, 216, + 217, 219, 220, 221, 223, 224, 225, 227, 228, 230, 231, 232, 234, 235, 236, 238, + 239, 240, 242, 243, 245, 246, 247, 249, 250, 251, 253, 254, 255, 257, 258, 259, + 261, 262, 264, 265, 266, 268, 269, 270, 272, 273, 274, 276, 277, 278, 280, 281, + 283, 284, 285, 287, 288, 289, 291, 292, 293, 295, 296, 297, 299, 300, 302, 303, + 304, 306, 307, 308, 310, 311, 312, 314, 315, 317, 318, 319, 321, 322, 323, 325, + 326, 327, 329, 330, 331, 333, 334, 336, 337, 338, 340, 341, 342, 344, 345, 346}; -static INT16 CB[] = { -345, -343, -341, -338, -336, -334, -332, -329, --327, -325, -323, -321, -318, -316, -314, -312, -310, -307, -305, --303, -301, -298, -296, -294, -292, -290, -287, -285, -283, -281, --278, -276, -274, -272, -270, -267, -265, -263, -261, -258, -256, --254, -252, -250, -247, -245, -243, -241, -239, -236, -234, -232, --230, -227, -225, -223, -221, -219, -216, -214, -212, -210, -207, --205, -203, -201, -199, -196, -194, -192, -190, -188, -185, -183, --181, -179, -176, -174, -172, -170, -168, -165, -163, -161, -159, --156, -154, -152, -150, -148, -145, -143, -141, -139, -137, -134, --132, -130, -128, -125, -123, -121, -119, -117, -114, -112, -110, --108, -105, -103, -101, -99, -97, -94, -92, -90, -88, -85, -83, -81, --79, -77, -74, -72, -70, -68, -66, -63, -61, -59, -57, -54, -52, -50, --48, -46, -43, -41, -39, -37, -34, -32, -30, -28, -26, -23, -21, -19, --17, -15, -12, -10, -8, -6, -3, -1, 0, 2, 4, 7, 9, 11, 13, 16, 18, 20, -22, 24, 27, 29, 31, 33, 35, 38, 40, 42, 44, 47, 49, 51, 53, 55, 58, -60, 62, 64, 67, 69, 71, 73, 75, 78, 80, 82, 84, 86, 89, 91, 93, 95, -98, 100, 102, 104, 106, 109, 111, 113, 115, 118, 120, 122, 124, 126, -129, 131, 133, 135, 138, 140, 142, 144, 146, 149, 151, 153, 155, 157, -160, 162, 164, 166, 169, 171, 173, 175, 177, 180, 182, 184, 186, 189, -191, 193, 195, 197, 200, 202, 204, 206, 208, 211, 213, 215, 217, 220 }; +static INT16 CB[] = { + -345, -343, -341, -338, -336, -334, -332, -329, -327, -325, -323, -321, -318, -316, + -314, -312, -310, -307, -305, -303, -301, -298, -296, -294, -292, -290, -287, -285, + -283, -281, -278, -276, -274, -272, -270, -267, -265, -263, -261, -258, -256, -254, + -252, -250, -247, -245, -243, -241, -239, -236, -234, -232, -230, -227, -225, -223, + -221, -219, -216, -214, -212, -210, -207, -205, -203, -201, -199, -196, -194, -192, + -190, -188, -185, -183, -181, -179, -176, -174, -172, -170, -168, -165, -163, -161, + -159, -156, -154, -152, -150, -148, -145, -143, -141, -139, -137, -134, -132, -130, + -128, -125, -123, -121, -119, -117, -114, -112, -110, -108, -105, -103, -101, -99, + -97, -94, -92, -90, -88, -85, -83, -81, -79, -77, -74, -72, -70, -68, + -66, -63, -61, -59, -57, -54, -52, -50, -48, -46, -43, -41, -39, -37, + -34, -32, -30, -28, -26, -23, -21, -19, -17, -15, -12, -10, -8, -6, + -3, -1, 0, 2, 4, 7, 9, 11, 13, 16, 18, 20, 22, 24, + 27, 29, 31, 33, 35, 38, 40, 42, 44, 47, 49, 51, 53, 55, + 58, 60, 62, 64, 67, 69, 71, 73, 75, 78, 80, 82, 84, 86, + 89, 91, 93, 95, 98, 100, 102, 104, 106, 109, 111, 113, 115, 118, + 120, 122, 124, 126, 129, 131, 133, 135, 138, 140, 142, 144, 146, 149, + 151, 153, 155, 157, 160, 162, 164, 166, 169, 171, 173, 175, 177, 180, + 182, 184, 186, 189, 191, 193, 195, 197, 200, 202, 204, 206, 208, 211, + 213, 215, 217, 220}; -static INT16 GB[] = { 67, 67, 66, 66, 65, 65, 65, 64, 64, 63, 63, 62, -62, 62, 61, 61, 60, 60, 59, 59, 59, 58, 58, 57, 57, 56, 56, 56, 55, -55, 54, 54, 53, 53, 52, 52, 52, 51, 51, 50, 50, 49, 49, 49, 48, 48, -47, 47, 46, 46, 46, 45, 45, 44, 44, 43, 43, 43, 42, 42, 41, 41, 40, -40, 40, 39, 39, 38, 38, 37, 37, 37, 36, 36, 35, 35, 34, 34, 34, 33, -33, 32, 32, 31, 31, 31, 30, 30, 29, 29, 28, 28, 28, 27, 27, 26, 26, -25, 25, 25, 24, 24, 23, 23, 22, 22, 22, 21, 21, 20, 20, 19, 19, 19, -18, 18, 17, 17, 16, 16, 15, 15, 15, 14, 14, 13, 13, 12, 12, 12, 11, -11, 10, 10, 9, 9, 9, 8, 8, 7, 7, 6, 6, 6, 5, 5, 4, 4, 3, 3, 3, 2, 2, -1, 1, 0, 0, 0, 0, 0, -1, -1, -2, -2, -2, -3, -3, -4, -4, -5, -5, -5, --6, -6, -7, -7, -8, -8, -8, -9, -9, -10, -10, -11, -11, -11, -12, -12, --13, -13, -14, -14, -14, -15, -15, -16, -16, -17, -17, -18, -18, -18, --19, -19, -20, -20, -21, -21, -21, -22, -22, -23, -23, -24, -24, -24, --25, -25, -26, -26, -27, -27, -27, -28, -28, -29, -29, -30, -30, -30, --31, -31, -32, -32, -33, -33, -33, -34, -34, -35, -35, -36, -36, -36, --37, -37, -38, -38, -39, -39, -39, -40, -40, -41, -41, -42 }; +static INT16 GB[] = { + 67, 67, 66, 66, 65, 65, 65, 64, 64, 63, 63, 62, 62, 62, 61, 61, + 60, 60, 59, 59, 59, 58, 58, 57, 57, 56, 56, 56, 55, 55, 54, 54, + 53, 53, 52, 52, 52, 51, 51, 50, 50, 49, 49, 49, 48, 48, 47, 47, + 46, 46, 46, 45, 45, 44, 44, 43, 43, 43, 42, 42, 41, 41, 40, 40, + 40, 39, 39, 38, 38, 37, 37, 37, 36, 36, 35, 35, 34, 34, 34, 33, + 33, 32, 32, 31, 31, 31, 30, 30, 29, 29, 28, 28, 28, 27, 27, 26, + 26, 25, 25, 25, 24, 24, 23, 23, 22, 22, 22, 21, 21, 20, 20, 19, + 19, 19, 18, 18, 17, 17, 16, 16, 15, 15, 15, 14, 14, 13, 13, 12, + 12, 12, 11, 11, 10, 10, 9, 9, 9, 8, 8, 7, 7, 6, 6, 6, + 5, 5, 4, 4, 3, 3, 3, 2, 2, 1, 1, 0, 0, 0, 0, 0, + -1, -1, -2, -2, -2, -3, -3, -4, -4, -5, -5, -5, -6, -6, -7, -7, + -8, -8, -8, -9, -9, -10, -10, -11, -11, -11, -12, -12, -13, -13, -14, -14, + -14, -15, -15, -16, -16, -17, -17, -18, -18, -18, -19, -19, -20, -20, -21, -21, + -21, -22, -22, -23, -23, -24, -24, -24, -25, -25, -26, -26, -27, -27, -27, -28, + -28, -29, -29, -30, -30, -30, -31, -31, -32, -32, -33, -33, -33, -34, -34, -35, + -35, -36, -36, -36, -37, -37, -38, -38, -39, -39, -39, -40, -40, -41, -41, -42}; -static INT16 CR[] = { -249, -247, -245, -243, -241, -239, -238, -236, --234, -232, -230, -229, -227, -225, -223, -221, -219, -218, -216, --214, -212, -210, -208, -207, -205, -203, -201, -199, -198, -196, --194, -192, -190, -188, -187, -185, -183, -181, -179, -178, -176, --174, -172, -170, -168, -167, -165, -163, -161, -159, -157, -156, --154, -152, -150, -148, -147, -145, -143, -141, -139, -137, -136, --134, -132, -130, -128, -127, -125, -123, -121, -119, -117, -116, --114, -112, -110, -108, -106, -105, -103, -101, -99, -97, -96, -94, --92, -90, -88, -86, -85, -83, -81, -79, -77, -76, -74, -72, -70, -68, --66, -65, -63, -61, -59, -57, -55, -54, -52, -50, -48, -46, -45, -43, --41, -39, -37, -35, -34, -32, -30, -28, -26, -25, -23, -21, -19, -17, --15, -14, -12, -10, -8, -6, -4, -3, -1, 0, 2, 4, 5, 7, 9, 11, 13, 15, -16, 18, 20, 22, 24, 26, 27, 29, 31, 33, 35, 36, 38, 40, 42, 44, 46, -47, 49, 51, 53, 55, 56, 58, 60, 62, 64, 66, 67, 69, 71, 73, 75, 77, -78, 80, 82, 84, 86, 87, 89, 91, 93, 95, 97, 98, 100, 102, 104, 106, -107, 109, 111, 113, 115, 117, 118, 120, 122, 124, 126, 128, 129, 131, -133, 135, 137, 138, 140, 142, 144, 146, 148, 149, 151, 153, 155, 157, -158, 160, 162, 164, 166, 168, 169, 171, 173, 175, 177, 179, 180, 182, -184, 186, 188, 189, 191, 193, 195, 197, 199, 200, 202, 204, 206, 208, -209, 211, 213, 215 }; +static INT16 CR[] = { + -249, -247, -245, -243, -241, -239, -238, -236, -234, -232, -230, -229, -227, -225, + -223, -221, -219, -218, -216, -214, -212, -210, -208, -207, -205, -203, -201, -199, + -198, -196, -194, -192, -190, -188, -187, -185, -183, -181, -179, -178, -176, -174, + -172, -170, -168, -167, -165, -163, -161, -159, -157, -156, -154, -152, -150, -148, + -147, -145, -143, -141, -139, -137, -136, -134, -132, -130, -128, -127, -125, -123, + -121, -119, -117, -116, -114, -112, -110, -108, -106, -105, -103, -101, -99, -97, + -96, -94, -92, -90, -88, -86, -85, -83, -81, -79, -77, -76, -74, -72, + -70, -68, -66, -65, -63, -61, -59, -57, -55, -54, -52, -50, -48, -46, + -45, -43, -41, -39, -37, -35, -34, -32, -30, -28, -26, -25, -23, -21, + -19, -17, -15, -14, -12, -10, -8, -6, -4, -3, -1, 0, 2, 4, + 5, 7, 9, 11, 13, 15, 16, 18, 20, 22, 24, 26, 27, 29, + 31, 33, 35, 36, 38, 40, 42, 44, 46, 47, 49, 51, 53, 55, + 56, 58, 60, 62, 64, 66, 67, 69, 71, 73, 75, 77, 78, 80, + 82, 84, 86, 87, 89, 91, 93, 95, 97, 98, 100, 102, 104, 106, + 107, 109, 111, 113, 115, 117, 118, 120, 122, 124, 126, 128, 129, 131, + 133, 135, 137, 138, 140, 142, 144, 146, 148, 149, 151, 153, 155, 157, + 158, 160, 162, 164, 166, 168, 169, 171, 173, 175, 177, 179, 180, 182, + 184, 186, 188, 189, 191, 193, 195, 197, 199, 200, 202, 204, 206, 208, + 209, 211, 213, 215}; -static INT16 GR[] = { 127, 126, 125, 124, 123, 122, 121, 121, 120, 119, -118, 117, 116, 115, 114, 113, 112, 111, 110, 109, 108, 108, 107, 106, -105, 104, 103, 102, 101, 100, 99, 98, 97, 96, 95, 95, 94, 93, 92, 91, -90, 89, 88, 87, 86, 85, 84, 83, 83, 82, 81, 80, 79, 78, 77, 76, 75, -74, 73, 72, 71, 70, 70, 69, 68, 67, 66, 65, 64, 63, 62, 61, 60, 59, -58, 57, 57, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 45, 44, -43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 32, 32, 31, 30, 29, 28, -27, 26, 25, 24, 23, 22, 21, 20, 19, 19, 18, 17, 16, 15, 14, 13, 12, -11, 10, 9, 8, 7, 6, 6, 5, 4, 3, 2, 1, 0, 0, -1, -2, -3, -4, -5, -5, --6, -7, -8, -9, -10, -11, -12, -13, -14, -15, -16, -17, -18, -18, -19, --20, -21, -22, -23, -24, -25, -26, -27, -28, -29, -30, -31, -31, -32, --33, -34, -35, -36, -37, -38, -39, -40, -41, -42, -43, -44, -44, -45, --46, -47, -48, -49, -50, -51, -52, -53, -54, -55, -56, -56, -57, -58, --59, -60, -61, -62, -63, -64, -65, -66, -67, -68, -69, -69, -70, -71, --72, -73, -74, -75, -76, -77, -78, -79, -80, -81, -82, -82, -83, -84, --85, -86, -87, -88, -89, -90, -91, -92, -93, -94, -94, -95, -96, -97, --98, -99, -100, -101, -102, -103, -104, -105, -106, -107, -107, -108 }; +static INT16 GR[] = { + 127, 126, 125, 124, 123, 122, 121, 121, 120, 119, 118, 117, 116, 115, 114, + 113, 112, 111, 110, 109, 108, 108, 107, 106, 105, 104, 103, 102, 101, 100, + 99, 98, 97, 96, 95, 95, 94, 93, 92, 91, 90, 89, 88, 87, 86, + 85, 84, 83, 83, 82, 81, 80, 79, 78, 77, 76, 75, 74, 73, 72, + 71, 70, 70, 69, 68, 67, 66, 65, 64, 63, 62, 61, 60, 59, 58, + 57, 57, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 45, + 44, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 32, 32, 31, + 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 19, 18, 17, + 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 6, 5, 4, 3, + 2, 1, 0, 0, -1, -2, -3, -4, -5, -5, -6, -7, -8, -9, -10, + -11, -12, -13, -14, -15, -16, -17, -18, -18, -19, -20, -21, -22, -23, -24, + -25, -26, -27, -28, -29, -30, -31, -31, -32, -33, -34, -35, -36, -37, -38, + -39, -40, -41, -42, -43, -44, -44, -45, -46, -47, -48, -49, -50, -51, -52, + -53, -54, -55, -56, -56, -57, -58, -59, -60, -61, -62, -63, -64, -65, -66, + -67, -68, -69, -69, -70, -71, -72, -73, -74, -75, -76, -77, -78, -79, -80, + -81, -82, -82, -83, -84, -85, -86, -87, -88, -89, -90, -91, -92, -93, -94, + -94, -95, -96, -97, -98, -99, -100, -101, -102, -103, -104, -105, -106, -107, -107, + -108}; -#define R 0 -#define G 1 -#define B 2 -#define A 3 +#define R 0 +#define G 1 +#define B 2 +#define A 3 -#define YCC2RGB(rgb, y, cb, cr) {\ - int l = L[y];\ - int r = l + CR[cr];\ - int g = l + GR[cr] + GB[cb];\ - int b = l + CB[cb];\ - rgb[0] = (r <= 0) ? 0 : (r >= 255) ? 255 : r;\ - rgb[1] = (g <= 0) ? 0 : (g >= 255) ? 255 : g;\ - rgb[2] = (b <= 0) ? 0 : (b >= 255) ? 255 : b;\ -} +#define YCC2RGB(rgb, y, cb, cr) \ + { \ + int l = L[y]; \ + int r = l + CR[cr]; \ + int g = l + GR[cr] + GB[cb]; \ + int b = l + CB[cb]; \ + rgb[0] = (r <= 0) ? 0 : (r >= 255) ? 255 : r; \ + rgb[1] = (g <= 0) ? 0 : (g >= 255) ? 255 : g; \ + rgb[2] = (b <= 0) ? 0 : (b >= 255) ? 255 : b; \ + } void -ImagingUnpackYCC(UINT8* out, const UINT8* in, int pixels) -{ +ImagingUnpackYCC(UINT8 *out, const UINT8 *in, int pixels) { int i; /* PhotoYCC triplets */ for (i = 0; i < pixels; i++) { - YCC2RGB(out, in[0], in[1], in[2]); - out[A] = 255; - out += 4; in += 3; + YCC2RGB(out, in[0], in[1], in[2]); + out[A] = 255; + out += 4; + in += 3; } } void -ImagingUnpackYCCA(UINT8* out, const UINT8* in, int pixels) -{ +ImagingUnpackYCCA(UINT8 *out, const UINT8 *in, int pixels) { int i; /* PhotoYCC triplets plus premultiplied alpha */ for (i = 0; i < pixels; i++) { - /* Divide by alpha */ - UINT8 rgb[3]; - rgb[0] = (in[3] == 0) ? 0 : (((int) in[0] * 255) / in[3]); - rgb[1] = (in[3] == 0) ? 0 : (((int) in[1] * 255) / in[3]); - rgb[2] = (in[3] == 0) ? 0 : (((int) in[2] * 255) / in[3]); - /* Convert non-multiplied data to RGB */ - YCC2RGB(out, rgb[0], rgb[1], rgb[2]); - out[A] = in[3]; - out += 4; in += 4; + /* Divide by alpha */ + UINT8 rgb[3]; + rgb[0] = (in[3] == 0) ? 0 : (((int)in[0] * 255) / in[3]); + rgb[1] = (in[3] == 0) ? 0 : (((int)in[1] * 255) / in[3]); + rgb[2] = (in[3] == 0) ? 0 : (((int)in[2] * 255) / in[3]); + /* Convert non-multiplied data to RGB */ + YCC2RGB(out, rgb[0], rgb[1], rgb[2]); + out[A] = in[3]; + out += 4; + in += 4; } } diff --git a/src/libImaging/UnsharpMask.c b/src/libImaging/UnsharpMask.c index a034bebf2..643ced49f 100644 --- a/src/libImaging/UnsharpMask.c +++ b/src/libImaging/UnsharpMask.c @@ -8,24 +8,22 @@ #include "Imaging.h" - typedef UINT8 pixel[4]; - -static inline UINT8 clip8(int in) -{ - if (in >= 255) - return 255; - if (in <= 0) +static inline UINT8 +clip8(int in) { + if (in >= 255) { + return 255; + } + if (in <= 0) { return 0; - return (UINT8) in; + } + return (UINT8)in; } - Imaging -ImagingUnsharpMask(Imaging imOut, Imaging imIn, float radius, int percent, - int threshold) -{ +ImagingUnsharpMask( + Imaging imOut, Imaging imIn, float radius, int percent, int threshold) { ImagingSectionCookie cookie; Imaging result; @@ -39,8 +37,9 @@ ImagingUnsharpMask(Imaging imOut, Imaging imIn, float radius, int percent, /* First, do a gaussian blur on the image, putting results in imOut temporarily. All format checks are in gaussian blur. */ result = ImagingGaussianBlur(imOut, imIn, radius, 3); - if (!result) + if (!result) { return NULL; + } /* Now, go through each pixel, compare "normal" pixel to blurred pixel. If the difference is more than threshold values, apply @@ -50,8 +49,7 @@ ImagingUnsharpMask(Imaging imOut, Imaging imIn, float radius, int percent, ImagingSectionEnter(&cookie); for (y = 0; y < imIn->ysize; y++) { - if (imIn->image8) - { + if (imIn->image8) { lineIn8 = imIn->image8[y]; lineOut8 = imOut->image8[y]; for (x = 0; x < imIn->xsize; x++) { @@ -71,20 +69,24 @@ ImagingUnsharpMask(Imaging imOut, Imaging imIn, float radius, int percent, for (x = 0; x < imIn->xsize; x++) { /* compare in/out pixels, apply sharpening */ diff = lineIn[x][0] - lineOut[x][0]; - lineOut[x][0] = abs(diff) > threshold ? - clip8(lineIn[x][0] + diff * percent / 100) : lineIn[x][0]; + lineOut[x][0] = abs(diff) > threshold + ? clip8(lineIn[x][0] + diff * percent / 100) + : lineIn[x][0]; diff = lineIn[x][1] - lineOut[x][1]; - lineOut[x][1] = abs(diff) > threshold ? - clip8(lineIn[x][1] + diff * percent / 100) : lineIn[x][1]; + lineOut[x][1] = abs(diff) > threshold + ? clip8(lineIn[x][1] + diff * percent / 100) + : lineIn[x][1]; diff = lineIn[x][2] - lineOut[x][2]; - lineOut[x][2] = abs(diff) > threshold ? - clip8(lineIn[x][2] + diff * percent / 100) : lineIn[x][2]; + lineOut[x][2] = abs(diff) > threshold + ? clip8(lineIn[x][2] + diff * percent / 100) + : lineIn[x][2]; diff = lineIn[x][3] - lineOut[x][3]; - lineOut[x][3] = abs(diff) > threshold ? - clip8(lineIn[x][3] + diff * percent / 100) : lineIn[x][3]; + lineOut[x][3] = abs(diff) > threshold + ? clip8(lineIn[x][3] + diff * percent / 100) + : lineIn[x][3]; } } } diff --git a/src/libImaging/XbmDecode.c b/src/libImaging/XbmDecode.c index 75b4961ab..d6690de3d 100644 --- a/src/libImaging/XbmDecode.c +++ b/src/libImaging/XbmDecode.c @@ -5,7 +5,7 @@ * decoder for XBM hex image data * * history: - * 96-04-13 fl Created + * 96-04-13 fl Created * * Copyright (c) Fredrik Lundh 1996. * Copyright (c) Secret Labs AB 1997. @@ -13,69 +13,66 @@ * See the README file for information on usage and redistribution. */ - #include "Imaging.h" -#define HEX(v) ((v >= '0' && v <= '9') ? v - '0' :\ - (v >= 'a' && v <= 'f') ? v - 'a' + 10 :\ - (v >= 'A' && v <= 'F') ? v - 'A' + 10 : 0) +#define HEX(v) \ + ((v >= '0' && v <= '9') ? v - '0' \ + : (v >= 'a' && v <= 'f') ? v - 'a' + 10 \ + : (v >= 'A' && v <= 'F') ? v - 'A' + 10 \ + : 0) int -ImagingXbmDecode(Imaging im, ImagingCodecState state, UINT8* buf, Py_ssize_t bytes) -{ +ImagingXbmDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t bytes) { enum { BYTE = 1, SKIP }; - UINT8* ptr; + UINT8 *ptr; - if (!state->state) - state->state = SKIP; + if (!state->state) { + state->state = SKIP; + } ptr = buf; for (;;) { + if (state->state == SKIP) { + /* Skip forward until next 'x' */ - if (state->state == SKIP) { + while (bytes > 0) { + if (*ptr == 'x') { + break; + } + ptr++; + bytes--; + } - /* Skip forward until next 'x' */ + if (bytes == 0) { + return ptr - buf; + } - while (bytes > 0) { - if (*ptr == 'x') - break; - ptr++; - bytes--; - } + state->state = BYTE; + } - if (bytes == 0) - return ptr - buf; + if (bytes < 3) { + return ptr - buf; + } - state->state = BYTE; + state->buffer[state->x] = (HEX(ptr[1]) << 4) + HEX(ptr[2]); - } + if (++state->x >= state->bytes) { + /* Got a full line, unpack it */ + state->shuffle((UINT8 *)im->image[state->y], state->buffer, state->xsize); - if (bytes < 3) - return ptr - buf; + state->x = 0; - state->buffer[state->x] = (HEX(ptr[1])<<4) + HEX(ptr[2]); + if (++state->y >= state->ysize) { + /* End of file (errcode = 0) */ + return -1; + } + } - if (++state->x >= state->bytes) { - - /* Got a full line, unpack it */ - state->shuffle((UINT8*) im->image[state->y], state->buffer, - state->xsize); - - state->x = 0; - - if (++state->y >= state->ysize) { - /* End of file (errcode = 0) */ - return -1; - } - } - - ptr += 3; - bytes -= 3; - - state->state = SKIP; + ptr += 3; + bytes -= 3; + state->state = SKIP; } - } diff --git a/src/libImaging/XbmEncode.c b/src/libImaging/XbmEncode.c index e066fd6b5..eec4c0d84 100644 --- a/src/libImaging/XbmEncode.c +++ b/src/libImaging/XbmEncode.c @@ -5,7 +5,7 @@ * encoder for Xbm data * * history: - * 96-11-01 fl created + * 96-11-01 fl created * * Copyright (c) Fredrik Lundh 1996. * Copyright (c) Secret Labs AB 1997. @@ -13,93 +13,83 @@ * See the README file for information on usage and redistribution. */ - #include "Imaging.h" - int -ImagingXbmEncode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) -{ +ImagingXbmEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) { const char *hex = "0123456789abcdef"; - UINT8* ptr = buf; + UINT8 *ptr = buf; int i, n; if (!state->state) { + /* 8 pixels are stored in no more than 6 bytes */ + state->bytes = 6 * (state->xsize + 7) / 8; - /* 8 pixels are stored in no more than 6 bytes */ - state->bytes = 6*(state->xsize+7)/8; - - state->state = 1; - + state->state = 1; } if (bytes < state->bytes) { - state->errcode = IMAGING_CODEC_MEMORY; - return 0; + state->errcode = IMAGING_CODEC_MEMORY; + return 0; } ptr = buf; while (bytes >= state->bytes) { + state->shuffle( + state->buffer, + (UINT8 *)im->image[state->y + state->yoff] + state->xoff * im->pixelsize, + state->xsize); - state->shuffle(state->buffer, - (UINT8*) im->image[state->y + state->yoff] + - state->xoff * im->pixelsize, state->xsize); + if (state->y < state->ysize - 1) { + /* any line but the last */ + for (n = 0; n < state->xsize; n += 8) { + i = state->buffer[n / 8]; - if (state->y < state->ysize-1) { + *ptr++ = '0'; + *ptr++ = 'x'; + *ptr++ = hex[(i >> 4) & 15]; + *ptr++ = hex[i & 15]; + *ptr++ = ','; + bytes -= 5; - /* any line but the last */ - for (n = 0; n < state->xsize; n += 8) { + if (++state->count >= 79 / 5) { + *ptr++ = '\n'; + bytes--; + state->count = 0; + } + } - i = state->buffer[n/8]; + state->y++; - *ptr++ = '0'; - *ptr++ = 'x'; - *ptr++ = hex[(i>>4)&15]; - *ptr++ = hex[i&15]; - *ptr++ = ','; - bytes -= 5; + } else { + /* last line */ + for (n = 0; n < state->xsize; n += 8) { + i = state->buffer[n / 8]; - if (++state->count >= 79/5) { - *ptr++ = '\n'; - bytes--; - state->count = 0; - } + *ptr++ = '0'; + *ptr++ = 'x'; + *ptr++ = hex[(i >> 4) & 15]; + *ptr++ = hex[i & 15]; - } + if (n < state->xsize - 8) { + *ptr++ = ','; + if (++state->count >= 79 / 5) { + *ptr++ = '\n'; + bytes--; + state->count = 0; + } + } else { + *ptr++ = '\n'; + } - state->y++; + bytes -= 5; + } - } else { - - /* last line */ - for (n = 0; n < state->xsize; n += 8) { - - i = state->buffer[n/8]; - - *ptr++ = '0'; - *ptr++ = 'x'; - *ptr++ = hex[(i>>4)&15]; - *ptr++ = hex[i&15]; - - if (n < state->xsize-8) { - *ptr++ = ','; - if (++state->count >= 79/5) { - *ptr++ = '\n'; - bytes--; - state->count = 0; - } - } else - *ptr++ = '\n'; - - bytes -= 5; - - } - - state->errcode = IMAGING_CODEC_END; - break; - } + state->errcode = IMAGING_CODEC_END; + break; + } } return ptr - buf; diff --git a/src/libImaging/Zip.h b/src/libImaging/Zip.h deleted file mode 100644 index 21a336f90..000000000 --- a/src/libImaging/Zip.h +++ /dev/null @@ -1,62 +0,0 @@ -/* - * The Python Imaging Library. - * $Id$ - * - * declarations for the ZIP codecs - * - * Copyright (c) Fredrik Lundh 1996. - */ - - -#include "zlib.h" - - -/* modes */ -#define ZIP_PNG 0 /* continuous, filtered image data */ -#define ZIP_PNG_PALETTE 1 /* non-continuous data, disable filtering */ -#define ZIP_TIFF_PREDICTOR 2 /* TIFF, with predictor */ -#define ZIP_TIFF 3 /* TIFF, without predictor */ - - -typedef struct { - - /* CONFIGURATION */ - - /* Codec mode */ - int mode; - - /* Optimize (max compression) SLOW!!! */ - int optimize; - - /* 0 no compression, 9 best compression, -1 default compression */ - int compress_level; - /* compression strategy Z_XXX */ - int compress_type; - - /* Predefined dictionary (experimental) */ - char* dictionary; - int dictionary_size; - - /* PRIVATE CONTEXT (set by decoder/encoder) */ - - z_stream z_stream; /* (de)compression stream */ - - UINT8* previous; /* previous line (allocated) */ - - int last_output; /* # bytes last output by inflate */ - - /* Compressor specific stuff */ - UINT8* prior; /* filter storage (allocated) */ - UINT8* up; - UINT8* average; - UINT8* paeth; - - UINT8* output; /* output data */ - - int prefix; /* size of filter prefix (0 for TIFF data) */ - - int interlaced; /* is the image interlaced? (PNG) */ - - int pass; /* current pass of the interlaced image (PNG) */ - -} ZIPSTATE; diff --git a/src/libImaging/ZipCodecs.h b/src/libImaging/ZipCodecs.h new file mode 100644 index 000000000..50218b6c6 --- /dev/null +++ b/src/libImaging/ZipCodecs.h @@ -0,0 +1,58 @@ +/* + * The Python Imaging Library. + * $Id$ + * + * declarations for the ZIP codecs + * + * Copyright (c) Fredrik Lundh 1996. + */ + +#include "zlib.h" + +/* modes */ +#define ZIP_PNG 0 /* continuous, filtered image data */ +#define ZIP_PNG_PALETTE 1 /* non-continuous data, disable filtering */ +#define ZIP_TIFF_PREDICTOR 2 /* TIFF, with predictor */ +#define ZIP_TIFF 3 /* TIFF, without predictor */ + +typedef struct { + /* CONFIGURATION */ + + /* Codec mode */ + int mode; + + /* Optimize (max compression) SLOW!!! */ + int optimize; + + /* 0 no compression, 9 best compression, -1 default compression */ + int compress_level; + /* compression strategy Z_XXX */ + int compress_type; + + /* Predefined dictionary (experimental) */ + char *dictionary; + int dictionary_size; + + /* PRIVATE CONTEXT (set by decoder/encoder) */ + + z_stream z_stream; /* (de)compression stream */ + + UINT8 *previous; /* previous line (allocated) */ + + int last_output; /* # bytes last output by inflate */ + + /* Compressor specific stuff */ + UINT8 *prior; /* filter storage (allocated) */ + UINT8 *up; + UINT8 *average; + UINT8 *paeth; + + UINT8 *output; /* output data */ + + int prefix; /* size of filter prefix (0 for TIFF data) */ + + int interlaced; /* is the image interlaced? (PNG) */ + + int pass; /* current pass of the interlaced image (PNG) */ + +} ZIPSTATE; diff --git a/src/libImaging/ZipDecode.c b/src/libImaging/ZipDecode.c index 43601c38e..874967834 100644 --- a/src/libImaging/ZipDecode.c +++ b/src/libImaging/ZipDecode.c @@ -15,23 +15,22 @@ * See the README file for information on usage and redistribution. */ - #include "Imaging.h" -#ifdef HAVE_LIBZ +#ifdef HAVE_LIBZ -#include "Zip.h" +#include "ZipCodecs.h" -static const int OFFSET[] = { 7, 3, 3, 1, 1, 0, 0 }; -static const int STARTING_COL[] = { 0, 4, 0, 2, 0, 1, 0 }; -static const int STARTING_ROW[] = { 0, 0, 4, 0, 2, 0, 1 }; -static const int COL_INCREMENT[] = { 8, 8, 4, 4, 2, 2, 1 }; -static const int ROW_INCREMENT[] = { 8, 8, 8, 4, 4, 2, 2 }; +static const int OFFSET[] = {7, 3, 3, 1, 1, 0, 0}; +static const int STARTING_COL[] = {0, 4, 0, 2, 0, 1, 0}; +static const int STARTING_ROW[] = {0, 0, 4, 0, 2, 0, 1}; +static const int COL_INCREMENT[] = {8, 8, 4, 4, 2, 2, 1}; +static const int ROW_INCREMENT[] = {8, 8, 8, 4, 4, 2, 2}; /* Get the length in bytes of a scanline in the pass specified, * for interlaced images */ -static int get_row_len(ImagingCodecState state, int pass) -{ +static int +get_row_len(ImagingCodecState state, int pass) { int row_len = (state->xsize + OFFSET[pass]) / COL_INCREMENT[pass]; return ((row_len * state->bits) + 7) / 8; } @@ -41,20 +40,19 @@ static int get_row_len(ImagingCodecState state, int pass) /* -------------------------------------------------------------------- */ int -ImagingZipDecode(Imaging im, ImagingCodecState state, UINT8* buf, Py_ssize_t bytes) -{ - ZIPSTATE* context = (ZIPSTATE*) state->context; +ImagingZipDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t bytes) { + ZIPSTATE *context = (ZIPSTATE *)state->context; int err; int n; - UINT8* ptr; + UINT8 *ptr; int i, bpp; int row_len; if (!state->state) { - /* Initialization */ - if (context->mode == ZIP_PNG || context->mode == ZIP_PNG_PALETTE) + if (context->mode == ZIP_PNG || context->mode == ZIP_PNG_PALETTE) { context->prefix = 1; /* PNG */ + } /* overflow check for malloc */ if (state->bytes > INT_MAX - 1) { @@ -65,8 +63,8 @@ ImagingZipDecode(Imaging im, ImagingCodecState state, UINT8* buf, Py_ssize_t byt prefix, and allocate a buffer to hold the previous line */ free(state->buffer); /* malloc check ok, overflow checked above */ - state->buffer = (UINT8*) malloc(state->bytes+1); - context->previous = (UINT8*) malloc(state->bytes+1); + state->buffer = (UINT8 *)malloc(state->bytes + 1); + context->previous = (UINT8 *)malloc(state->bytes + 1); if (!state->buffer || !context->previous) { state->errcode = IMAGING_CODEC_MEMORY; return -1; @@ -75,12 +73,12 @@ ImagingZipDecode(Imaging im, ImagingCodecState state, UINT8* buf, Py_ssize_t byt context->last_output = 0; /* Initialize to black */ - memset(context->previous, 0, state->bytes+1); + memset(context->previous, 0, state->bytes + 1); /* Setup decompression context */ - context->z_stream.zalloc = (alloc_func) NULL; - context->z_stream.zfree = (free_func) NULL; - context->z_stream.opaque = (voidpf) NULL; + context->z_stream.zalloc = (alloc_func)NULL; + context->z_stream.zfree = (free_func)NULL; + context->z_stream.opaque = (voidpf)NULL; err = inflateInit(&context->z_stream); if (err < 0) { @@ -97,7 +95,6 @@ ImagingZipDecode(Imaging im, ImagingCodecState state, UINT8* buf, Py_ssize_t byt /* Ready to decode */ state->state = 1; - } if (context->interlaced) { @@ -112,21 +109,20 @@ ImagingZipDecode(Imaging im, ImagingCodecState state, UINT8* buf, Py_ssize_t byt /* Decompress what we've got this far */ while (context->z_stream.avail_in > 0) { - context->z_stream.next_out = state->buffer + context->last_output; - context->z_stream.avail_out = - row_len + context->prefix - context->last_output; + context->z_stream.avail_out = row_len + context->prefix - context->last_output; err = inflate(&context->z_stream, Z_NO_FLUSH); if (err < 0) { /* Something went wrong inside the compression library */ - if (err == Z_DATA_ERROR) + if (err == Z_DATA_ERROR) { state->errcode = IMAGING_CODEC_BROKEN; - else if (err == Z_MEM_ERROR) + } else if (err == Z_MEM_ERROR) { state->errcode = IMAGING_CODEC_MEMORY; - else + } else { state->errcode = IMAGING_CODEC_CONFIG; + } free(context->previous); context->previous = NULL; inflateEnd(&context->z_stream); @@ -142,68 +138,74 @@ ImagingZipDecode(Imaging im, ImagingCodecState state, UINT8* buf, Py_ssize_t byt /* Apply predictor */ switch (context->mode) { - case ZIP_PNG: - switch (state->buffer[0]) { - case 0: - break; - case 1: - /* prior */ - bpp = (state->bits + 7) / 8; - for (i = bpp+1; i <= row_len; i++) - state->buffer[i] += state->buffer[i-bpp]; - break; - case 2: - /* up */ - for (i = 1; i <= row_len; i++) - state->buffer[i] += context->previous[i]; - break; - case 3: - /* average */ - bpp = (state->bits + 7) / 8; - for (i = 1; i <= bpp; i++) - state->buffer[i] += context->previous[i]/2; - for (; i <= row_len; i++) - state->buffer[i] += - (state->buffer[i-bpp] + context->previous[i])/2; - break; - case 4: - /* paeth filtering */ - bpp = (state->bits + 7) / 8; - for (i = 1; i <= bpp; i++) - state->buffer[i] += context->previous[i]; - for (; i <= row_len; i++) { - int a, b, c; - int pa, pb, pc; + case ZIP_PNG: + switch (state->buffer[0]) { + case 0: + break; + case 1: + /* prior */ + bpp = (state->bits + 7) / 8; + for (i = bpp + 1; i <= row_len; i++) { + state->buffer[i] += state->buffer[i - bpp]; + } + break; + case 2: + /* up */ + for (i = 1; i <= row_len; i++) { + state->buffer[i] += context->previous[i]; + } + break; + case 3: + /* average */ + bpp = (state->bits + 7) / 8; + for (i = 1; i <= bpp; i++) { + state->buffer[i] += context->previous[i] / 2; + } + for (; i <= row_len; i++) { + state->buffer[i] += + (state->buffer[i - bpp] + context->previous[i]) / 2; + } + break; + case 4: + /* paeth filtering */ + bpp = (state->bits + 7) / 8; + for (i = 1; i <= bpp; i++) { + state->buffer[i] += context->previous[i]; + } + for (; i <= row_len; i++) { + int a, b, c; + int pa, pb, pc; - /* fetch pixels */ - a = state->buffer[i-bpp]; - b = context->previous[i]; - c = context->previous[i-bpp]; + /* fetch pixels */ + a = state->buffer[i - bpp]; + b = context->previous[i]; + c = context->previous[i - bpp]; - /* distances to surrounding pixels */ - pa = abs(b - c); - pb = abs(a - c); - pc = abs(a + b - 2*c); - - /* pick predictor with the shortest distance */ - state->buffer[i] += - (pa <= pb && pa <= pc) ? a : (pb <= pc) ? b : c; + /* distances to surrounding pixels */ + pa = abs(b - c); + pb = abs(a - c); + pc = abs(a + b - 2 * c); + /* pick predictor with the shortest distance */ + state->buffer[i] += (pa <= pb && pa <= pc) ? a + : (pb <= pc) ? b + : c; + } + break; + default: + state->errcode = IMAGING_CODEC_UNKNOWN; + free(context->previous); + context->previous = NULL; + inflateEnd(&context->z_stream); + return -1; + } + break; + case ZIP_TIFF_PREDICTOR: + bpp = (state->bits + 7) / 8; + for (i = bpp + 1; i <= row_len; i++) { + state->buffer[i] += state->buffer[i - bpp]; } break; - default: - state->errcode = IMAGING_CODEC_UNKNOWN; - free(context->previous); - context->previous = NULL; - inflateEnd(&context->z_stream); - return -1; - } - break; - case ZIP_TIFF_PREDICTOR: - bpp = (state->bits + 7) / 8; - for (i = bpp+1; i <= row_len; i++) - state->buffer[i] += state->buffer[i-bpp]; - break; } /* Stuff data into the image */ @@ -212,20 +214,22 @@ ImagingZipDecode(Imaging im, ImagingCodecState state, UINT8* buf, Py_ssize_t byt if (state->bits >= 8) { /* Stuff pixels in their correct location, one by one */ for (i = 0; i < row_len; i += ((state->bits + 7) / 8)) { - state->shuffle((UINT8*) im->image[state->y] + - col * im->pixelsize, - state->buffer + context->prefix + i, 1); + state->shuffle( + (UINT8 *)im->image[state->y] + col * im->pixelsize, + state->buffer + context->prefix + i, + 1); col += COL_INCREMENT[context->pass]; } } else { /* Handle case with more than a pixel in each byte */ - int row_bits = ((state->xsize + OFFSET[context->pass]) - / COL_INCREMENT[context->pass]) * state->bits; + int row_bits = ((state->xsize + OFFSET[context->pass]) / + COL_INCREMENT[context->pass]) * + state->bits; for (i = 0; i < row_bits; i += state->bits) { UINT8 byte = *(state->buffer + context->prefix + (i / 8)); byte <<= (i % 8); - state->shuffle((UINT8*) im->image[state->y] + - col * im->pixelsize, &byte, 1); + state->shuffle( + (UINT8 *)im->image[state->y] + col * im->pixelsize, &byte, 1); col += COL_INCREMENT[context->pass]; } } @@ -242,13 +246,14 @@ ImagingZipDecode(Imaging im, ImagingCodecState state, UINT8* buf, Py_ssize_t byt row_len = get_row_len(state, context->pass); /* Since we're moving to the "first" line, the previous line * should be black to make filters work correctly */ - memset(state->buffer, 0, state->bytes+1); + memset(state->buffer, 0, state->bytes + 1); } } else { - state->shuffle((UINT8*) im->image[state->y + state->yoff] + - state->xoff * im->pixelsize, - state->buffer + context->prefix, - state->xsize); + state->shuffle( + (UINT8 *)im->image[state->y + state->yoff] + + state->xoff * im->pixelsize, + state->buffer + context->prefix, + state->xsize); state->y++; } @@ -256,7 +261,6 @@ ImagingZipDecode(Imaging im, ImagingCodecState state, UINT8* buf, Py_ssize_t byt context->last_output = 0; if (state->y >= state->ysize || err == Z_STREAM_END) { - /* The image and the data should end simultaneously */ /* if (state->y < state->ysize || err != Z_STREAM_END) state->errcode = IMAGING_CODEC_BROKEN; */ @@ -265,26 +269,23 @@ ImagingZipDecode(Imaging im, ImagingCodecState state, UINT8* buf, Py_ssize_t byt context->previous = NULL; inflateEnd(&context->z_stream); return -1; /* end of file (errcode=0) */ - } /* Swap buffer pointers */ ptr = state->buffer; state->buffer = context->previous; context->previous = ptr; - } return bytes; /* consumed all of it */ - } - -int ImagingZipDecodeCleanup(ImagingCodecState state){ +int +ImagingZipDecodeCleanup(ImagingCodecState state) { /* called to free the decompression engine when the decode terminates due to a corrupt or truncated image */ - ZIPSTATE* context = (ZIPSTATE*) state->context; + ZIPSTATE *context = (ZIPSTATE *)state->context; /* Clean up */ if (context->previous) { diff --git a/src/libImaging/ZipEncode.c b/src/libImaging/ZipEncode.c index fa1c4e728..edbce3682 100644 --- a/src/libImaging/ZipEncode.c +++ b/src/libImaging/ZipEncode.c @@ -14,25 +14,22 @@ * See the README file for information on usage and redistribution. */ - #include "Imaging.h" -#ifdef HAVE_LIBZ +#ifdef HAVE_LIBZ -#include "Zip.h" +#include "ZipCodecs.h" int -ImagingZipEncode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) -{ - ZIPSTATE* context = (ZIPSTATE*) state->context; +ImagingZipEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) { + ZIPSTATE *context = (ZIPSTATE *)state->context; int err; int compress_level, compress_type; - UINT8* ptr; + UINT8 *ptr; int i, bpp, s, sum; ImagingSectionCookie cookie; if (!state->state) { - /* Initialization */ /* Valid modes are ZIP_PNG, ZIP_PNG_PALETTE, and ZIP_TIFF */ @@ -47,14 +44,14 @@ ImagingZipEncode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) and allocate filter buffers */ free(state->buffer); /* malloc check ok, overflow checked above */ - state->buffer = (UINT8*) malloc(state->bytes+1); - context->previous = (UINT8*) malloc(state->bytes+1); - context->prior = (UINT8*) malloc(state->bytes+1); - context->up = (UINT8*) malloc(state->bytes+1); - context->average = (UINT8*) malloc(state->bytes+1); - context->paeth = (UINT8*) malloc(state->bytes+1); - if (!state->buffer || !context->previous || !context->prior || - !context->up || !context->average || !context->paeth) { + state->buffer = (UINT8 *)malloc(state->bytes + 1); + context->previous = (UINT8 *)malloc(state->bytes + 1); + context->prior = (UINT8 *)malloc(state->bytes + 1); + context->up = (UINT8 *)malloc(state->bytes + 1); + context->average = (UINT8 *)malloc(state->bytes + 1); + context->paeth = (UINT8 *)malloc(state->bytes + 1); + if (!state->buffer || !context->previous || !context->prior || !context->up || + !context->average || !context->paeth) { free(context->paeth); free(context->average); free(context->up); @@ -72,7 +69,7 @@ ImagingZipEncode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) context->paeth[0] = 4; /* Initialise previous buffer to black */ - memset(context->previous, 0, state->bytes+1); + memset(context->previous, 0, state->bytes + 1); /* Setup compression context */ context->z_stream.zalloc = (alloc_func)0; @@ -81,33 +78,37 @@ ImagingZipEncode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) context->z_stream.next_in = 0; context->z_stream.avail_in = 0; - compress_level = (context->optimize) ? Z_BEST_COMPRESSION - : context->compress_level; + compress_level = + (context->optimize) ? Z_BEST_COMPRESSION : context->compress_level; if (context->compress_type == -1) { - compress_type = (context->mode == ZIP_PNG) ? Z_FILTERED - : Z_DEFAULT_STRATEGY; + compress_type = + (context->mode == ZIP_PNG) ? Z_FILTERED : Z_DEFAULT_STRATEGY; } else { compress_type = context->compress_type; } - err = deflateInit2(&context->z_stream, - /* compression level */ - compress_level, - /* compression method */ - Z_DEFLATED, - /* compression memory resources */ - 15, 9, - /* compression strategy (image data are filtered)*/ - compress_type); + err = deflateInit2( + &context->z_stream, + /* compression level */ + compress_level, + /* compression method */ + Z_DEFLATED, + /* compression memory resources */ + 15, + 9, + /* compression strategy (image data are filtered)*/ + compress_type); if (err < 0) { state->errcode = IMAGING_CODEC_CONFIG; return -1; } if (context->dictionary && context->dictionary_size > 0) { - err = deflateSetDictionary(&context->z_stream, (unsigned char *)context->dictionary, - context->dictionary_size); + err = deflateSetDictionary( + &context->z_stream, + (unsigned char *)context->dictionary, + context->dictionary_size); if (err < 0) { state->errcode = IMAGING_CODEC_CONFIG; return -1; @@ -116,7 +117,6 @@ ImagingZipEncode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) /* Ready to decode */ state->state = 1; - } /* Setup the destination buffer */ @@ -128,12 +128,13 @@ ImagingZipEncode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) if (err < 0) { /* Something went wrong inside the compression library */ - if (err == Z_DATA_ERROR) + if (err == Z_DATA_ERROR) { state->errcode = IMAGING_CODEC_BROKEN; - else if (err == Z_MEM_ERROR) + } else if (err == Z_MEM_ERROR) { state->errcode = IMAGING_CODEC_MEMORY; - else + } else { state->errcode = IMAGING_CODEC_CONFIG; + } free(context->paeth); free(context->average); free(context->up); @@ -146,200 +147,194 @@ ImagingZipEncode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) ImagingSectionEnter(&cookie); for (;;) { - switch (state->state) { + case 1: - case 1: - - /* Compress image data */ - while (context->z_stream.avail_out > 0) { - - if (state->y >= state->ysize) { - /* End of image; now flush compressor buffers */ - state->state = 2; - break; - - } - - /* Stuff image data into the compressor */ - state->shuffle(state->buffer+1, - (UINT8*) im->image[state->y + state->yoff] + - state->xoff * im->pixelsize, - state->xsize); - - state->y++; - - context->output = state->buffer; - - if (context->mode == ZIP_PNG) { - - /* Filter the image data. For each line, select - the filter that gives the least total distance - from zero for the filtered data (taken from - LIBPNG) */ - - bpp = (state->bits + 7) / 8; - - /* 0. No filter */ - for (i = 1, sum = 0; i <= state->bytes; i++) { - UINT8 v = state->buffer[i]; - sum += (v < 128) ? v : 256 - v; + /* Compress image data */ + while (context->z_stream.avail_out > 0) { + if (state->y >= state->ysize) { + /* End of image; now flush compressor buffers */ + state->state = 2; + break; } - /* 2. Up. We'll test this first to save time when - an image line is identical to the one above. */ - if (sum > 0) { - for (i = 1, s = 0; i <= state->bytes; i++) { - UINT8 v = state->buffer[i] - context->previous[i]; - context->up[i] = v; - s += (v < 128) ? v : 256 - v; - } - if (s < sum) { - context->output = context->up; - sum = s; /* 0 if line was duplicated */ - } - } + /* Stuff image data into the compressor */ + state->shuffle( + state->buffer + 1, + (UINT8 *)im->image[state->y + state->yoff] + + state->xoff * im->pixelsize, + state->xsize); - /* 1. Prior */ - if (sum > 0) { - for (i = 1, s = 0; i <= bpp; i++) { + state->y++; + + context->output = state->buffer; + + if (context->mode == ZIP_PNG) { + /* Filter the image data. For each line, select + the filter that gives the least total distance + from zero for the filtered data (taken from + LIBPNG) */ + + bpp = (state->bits + 7) / 8; + + /* 0. No filter */ + for (i = 1, sum = 0; i <= state->bytes; i++) { UINT8 v = state->buffer[i]; - context->prior[i] = v; - s += (v < 128) ? v : 256 - v; + sum += (v < 128) ? v : 256 - v; } - for (; i <= state->bytes; i++) { - UINT8 v = state->buffer[i] - state->buffer[i-bpp]; - context->prior[i] = v; - s += (v < 128) ? v : 256 - v; + + /* 2. Up. We'll test this first to save time when + an image line is identical to the one above. */ + if (sum > 0) { + for (i = 1, s = 0; i <= state->bytes; i++) { + UINT8 v = state->buffer[i] - context->previous[i]; + context->up[i] = v; + s += (v < 128) ? v : 256 - v; + } + if (s < sum) { + context->output = context->up; + sum = s; /* 0 if line was duplicated */ + } } - if (s < sum) { - context->output = context->prior; - sum = s; /* 0 if line is solid */ + + /* 1. Prior */ + if (sum > 0) { + for (i = 1, s = 0; i <= bpp; i++) { + UINT8 v = state->buffer[i]; + context->prior[i] = v; + s += (v < 128) ? v : 256 - v; + } + for (; i <= state->bytes; i++) { + UINT8 v = state->buffer[i] - state->buffer[i - bpp]; + context->prior[i] = v; + s += (v < 128) ? v : 256 - v; + } + if (s < sum) { + context->output = context->prior; + sum = s; /* 0 if line is solid */ + } + } + + /* 3. Average (not very common in real-life images, + so its only used with the optimize option) */ + if (context->optimize && sum > 0) { + for (i = 1, s = 0; i <= bpp; i++) { + UINT8 v = state->buffer[i] - context->previous[i] / 2; + context->average[i] = v; + s += (v < 128) ? v : 256 - v; + } + for (; i <= state->bytes; i++) { + UINT8 v = + state->buffer[i] - + (state->buffer[i - bpp] + context->previous[i]) / 2; + context->average[i] = v; + s += (v < 128) ? v : 256 - v; + } + if (s < sum) { + context->output = context->average; + sum = s; + } + } + + /* 4. Paeth */ + if (sum > 0) { + for (i = 1, s = 0; i <= bpp; i++) { + UINT8 v = state->buffer[i] - context->previous[i]; + context->paeth[i] = v; + s += (v < 128) ? v : 256 - v; + } + for (; i <= state->bytes; i++) { + UINT8 v; + int a, b, c; + int pa, pb, pc; + + /* fetch pixels */ + a = state->buffer[i - bpp]; + b = context->previous[i]; + c = context->previous[i - bpp]; + + /* distances to surrounding pixels */ + pa = abs(b - c); + pb = abs(a - c); + pc = abs(a + b - 2 * c); + + /* pick predictor with the shortest distance */ + v = state->buffer[i] - ((pa <= pb && pa <= pc) ? a + : (pb <= pc) ? b + : c); + context->paeth[i] = v; + s += (v < 128) ? v : 256 - v; + } + if (s < sum) { + context->output = context->paeth; + sum = s; + } } } - /* 3. Average (not very common in real-life images, - so its only used with the optimize option) */ - if (context->optimize && sum > 0) { - for (i = 1, s = 0; i <= bpp; i++) { - UINT8 v = state->buffer[i] - context->previous[i]/2; - context->average[i] = v; - s += (v < 128) ? v : 256 - v; - } - for (; i <= state->bytes; i++) { - UINT8 v = state->buffer[i] - - (state->buffer[i-bpp] + context->previous[i])/2; - context->average[i] = v; - s += (v < 128) ? v : 256 - v; - } - if (s < sum) { - context->output = context->average; - sum = s; + /* Compress this line */ + context->z_stream.next_in = context->output; + context->z_stream.avail_in = state->bytes + 1; + + err = deflate(&context->z_stream, Z_NO_FLUSH); + + if (err < 0) { + /* Something went wrong inside the compression library */ + if (err == Z_DATA_ERROR) { + state->errcode = IMAGING_CODEC_BROKEN; + } else if (err == Z_MEM_ERROR) { + state->errcode = IMAGING_CODEC_MEMORY; + } else { + state->errcode = IMAGING_CODEC_CONFIG; } + free(context->paeth); + free(context->average); + free(context->up); + free(context->prior); + free(context->previous); + deflateEnd(&context->z_stream); + ImagingSectionLeave(&cookie); + return -1; } - /* 4. Paeth */ - if (sum > 0) { - for (i = 1, s = 0; i <= bpp; i++) { - UINT8 v = state->buffer[i] - context->previous[i]; - context->paeth[i] = v; - s += (v < 128) ? v : 256 - v; - } - for (; i <= state->bytes; i++) { - UINT8 v; - int a, b, c; - int pa, pb, pc; - - /* fetch pixels */ - a = state->buffer[i-bpp]; - b = context->previous[i]; - c = context->previous[i-bpp]; - - /* distances to surrounding pixels */ - pa = abs(b - c); - pb = abs(a - c); - pc = abs(a + b - 2*c); - - /* pick predictor with the shortest distance */ - v = state->buffer[i] - - ((pa <= pb && pa <= pc) ? a : - (pb <= pc) ? b : c); - context->paeth[i] = v; - s += (v < 128) ? v : 256 - v; - } - if (s < sum) { - context->output = context->paeth; - sum = s; - } - } + /* Swap buffer pointers */ + ptr = state->buffer; + state->buffer = context->previous; + context->previous = ptr; } - /* Compress this line */ - context->z_stream.next_in = context->output; - context->z_stream.avail_in = state->bytes+1; - - err = deflate(&context->z_stream, Z_NO_FLUSH); - - if (err < 0) { - /* Something went wrong inside the compression library */ - if (err == Z_DATA_ERROR) - state->errcode = IMAGING_CODEC_BROKEN; - else if (err == Z_MEM_ERROR) - state->errcode = IMAGING_CODEC_MEMORY; - else - state->errcode = IMAGING_CODEC_CONFIG; - free(context->paeth); - free(context->average); - free(context->up); - free(context->prior); - free(context->previous); - deflateEnd(&context->z_stream); - ImagingSectionLeave(&cookie); - return -1; - } - - /* Swap buffer pointers */ - ptr = state->buffer; - state->buffer = context->previous; - context->previous = ptr; - - } - - if (context->z_stream.avail_out == 0) - break; /* Buffer full */ - - case 2: - - /* End of image data; flush compressor buffers */ - - while (context->z_stream.avail_out > 0) { - - err = deflate(&context->z_stream, Z_FINISH); - - if (err == Z_STREAM_END) { - - free(context->paeth); - free(context->average); - free(context->up); - free(context->prior); - free(context->previous); - - deflateEnd(&context->z_stream); - - state->errcode = IMAGING_CODEC_END; - - break; - } - - if (context->z_stream.avail_out == 0) + if (context->z_stream.avail_out == 0) { break; /* Buffer full */ + } - } + case 2: + /* End of image data; flush compressor buffers */ + + while (context->z_stream.avail_out > 0) { + err = deflate(&context->z_stream, Z_FINISH); + + if (err == Z_STREAM_END) { + free(context->paeth); + free(context->average); + free(context->up); + free(context->prior); + free(context->previous); + + deflateEnd(&context->z_stream); + + state->errcode = IMAGING_CODEC_END; + + break; + } + + if (context->z_stream.avail_out == 0) { + break; /* Buffer full */ + } + } } ImagingSectionLeave(&cookie); return bytes - context->z_stream.avail_out; - } /* Should never ever arrive here... */ @@ -354,22 +349,19 @@ ImagingZipEncode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) int ImagingZipEncodeCleanup(ImagingCodecState state) { - ZIPSTATE* context = (ZIPSTATE*) state->context; + ZIPSTATE *context = (ZIPSTATE *)state->context; if (context->dictionary) { - free (context->dictionary); + free(context->dictionary); context->dictionary = NULL; } return -1; } - - -const char* -ImagingZipVersion(void) -{ - return ZLIB_VERSION; +const char * +ImagingZipVersion(void) { + return zlibVersion(); } #endif diff --git a/src/libImaging/codec_fd.c b/src/libImaging/codec_fd.c index 5cde31cdc..526168110 100644 --- a/src/libImaging/codec_fd.c +++ b/src/libImaging/codec_fd.c @@ -1,10 +1,8 @@ #include "Python.h" #include "Imaging.h" - Py_ssize_t -_imaging_read_pyFd(PyObject *fd, char* dest, Py_ssize_t bytes) -{ +_imaging_read_pyFd(PyObject *fd, char *dest, Py_ssize_t bytes) { /* dest should be a buffer bytes long, returns length of read -1 on error */ @@ -29,16 +27,13 @@ _imaging_read_pyFd(PyObject *fd, char* dest, Py_ssize_t bytes) Py_DECREF(result); return length; - err: +err: Py_DECREF(result); return -1; - } Py_ssize_t -_imaging_write_pyFd(PyObject *fd, char* src, Py_ssize_t bytes) -{ - +_imaging_write_pyFd(PyObject *fd, char *src, Py_ssize_t bytes) { PyObject *result; PyObject *byteObj; @@ -49,24 +44,20 @@ _imaging_write_pyFd(PyObject *fd, char* src, Py_ssize_t bytes) Py_DECREF(result); return bytes; - } int -_imaging_seek_pyFd(PyObject *fd, Py_ssize_t offset, int whence) -{ +_imaging_seek_pyFd(PyObject *fd, Py_ssize_t offset, int whence) { PyObject *result; result = PyObject_CallMethod(fd, "seek", "ni", offset, whence); Py_DECREF(result); return 0; - } Py_ssize_t -_imaging_tell_pyFd(PyObject *fd) -{ +_imaging_tell_pyFd(PyObject *fd) { PyObject *result; Py_ssize_t location; diff --git a/src/libImaging/raqm.h b/src/libImaging/raqm.h index eae1f43b7..5f865853a 100644 --- a/src/libImaging/raqm.h +++ b/src/libImaging/raqm.h @@ -63,8 +63,7 @@ typedef struct _raqm raqm_t; * * Since: 0.1 */ -typedef enum -{ +typedef enum { RAQM_DIRECTION_DEFAULT, RAQM_DIRECTION_RTL, RAQM_DIRECTION_LTR, @@ -106,73 +105,50 @@ typedef struct raqm_glyph_t_01 { uint32_t cluster; } raqm_glyph_t_01; +raqm_t * +raqm_create(void); raqm_t * -raqm_create (void); - -raqm_t * -raqm_reference (raqm_t *rq); +raqm_reference(raqm_t *rq); void -raqm_destroy (raqm_t *rq); +raqm_destroy(raqm_t *rq); bool -raqm_set_text (raqm_t *rq, - const uint32_t *text, - size_t len); +raqm_set_text(raqm_t *rq, const uint32_t *text, size_t len); bool -raqm_set_text_utf8 (raqm_t *rq, - const char *text, - size_t len); +raqm_set_text_utf8(raqm_t *rq, const char *text, size_t len); bool -raqm_set_par_direction (raqm_t *rq, - raqm_direction_t dir); +raqm_set_par_direction(raqm_t *rq, raqm_direction_t dir); bool -raqm_set_language (raqm_t *rq, - const char *lang, - size_t start, - size_t len); +raqm_set_language(raqm_t *rq, const char *lang, size_t start, size_t len); bool -raqm_add_font_feature (raqm_t *rq, - const char *feature, - int len); +raqm_add_font_feature(raqm_t *rq, const char *feature, int len); bool -raqm_set_freetype_face (raqm_t *rq, - FT_Face face); +raqm_set_freetype_face(raqm_t *rq, FT_Face face); bool -raqm_set_freetype_face_range (raqm_t *rq, - FT_Face face, - size_t start, - size_t len); +raqm_set_freetype_face_range(raqm_t *rq, FT_Face face, size_t start, size_t len); bool -raqm_set_freetype_load_flags (raqm_t *rq, - int flags); +raqm_set_freetype_load_flags(raqm_t *rq, int flags); bool -raqm_layout (raqm_t *rq); +raqm_layout(raqm_t *rq); raqm_glyph_t * -raqm_get_glyphs (raqm_t *rq, - size_t *length); +raqm_get_glyphs(raqm_t *rq, size_t *length); bool -raqm_index_to_position (raqm_t *rq, - size_t *index, - int *x, - int *y); +raqm_index_to_position(raqm_t *rq, size_t *index, int *x, int *y); bool -raqm_position_to_index (raqm_t *rq, - int x, - int y, - size_t *index); +raqm_position_to_index(raqm_t *rq, int x, int y, size_t *index); #ifdef __cplusplus } diff --git a/src/map.c b/src/map.c index 27bf83ed5..5c047d130 100644 --- a/src/map.c +++ b/src/map.c @@ -20,20 +20,21 @@ #include "Python.h" -#include "Imaging.h" +#include "libImaging/Imaging.h" /* compatibility wrappers (defined in _imaging.c) */ -extern int PyImaging_CheckBuffer(PyObject* buffer); -extern int PyImaging_GetBuffer(PyObject* buffer, Py_buffer *view); +extern int +PyImaging_CheckBuffer(PyObject *buffer); +extern int +PyImaging_GetBuffer(PyObject *buffer, Py_buffer *view); /* -------------------------------------------------------------------- */ /* Standard mapper */ typedef struct { - PyObject_HEAD - char* base; - int size; - int offset; + PyObject_HEAD char *base; + int size; + int offset; #ifdef _WIN32 HANDLE hFile; HANDLE hMap; @@ -42,54 +43,50 @@ typedef struct { static PyTypeObject ImagingMapperType; -ImagingMapperObject* -PyImaging_MapperNew(const char* filename, int readonly) -{ +ImagingMapperObject * +PyImaging_MapperNew(const char *filename, int readonly) { ImagingMapperObject *mapper; - if (PyType_Ready(&ImagingMapperType) < 0) + if (PyType_Ready(&ImagingMapperType) < 0) { return NULL; + } mapper = PyObject_New(ImagingMapperObject, &ImagingMapperType); - if (mapper == NULL) + if (mapper == NULL) { return NULL; + } mapper->base = NULL; mapper->size = mapper->offset = 0; #ifdef _WIN32 mapper->hFile = (HANDLE)-1; - mapper->hMap = (HANDLE)-1; + mapper->hMap = (HANDLE)-1; /* FIXME: currently supports readonly mappings only */ mapper->hFile = CreateFile( filename, GENERIC_READ, FILE_SHARE_READ, - NULL, OPEN_EXISTING, + NULL, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (mapper->hFile == (HANDLE)-1) { - PyErr_SetString(PyExc_IOError, "cannot open file"); + PyErr_SetString(PyExc_OSError, "cannot open file"); Py_DECREF(mapper); return NULL; } - mapper->hMap = CreateFileMapping( - mapper->hFile, NULL, - PAGE_READONLY, - 0, 0, NULL); + mapper->hMap = CreateFileMapping(mapper->hFile, NULL, PAGE_READONLY, 0, 0, NULL); if (mapper->hMap == (HANDLE)-1) { CloseHandle(mapper->hFile); - PyErr_SetString(PyExc_IOError, "cannot map file"); + PyErr_SetString(PyExc_OSError, "cannot map file"); Py_DECREF(mapper); return NULL; } - mapper->base = (char*) MapViewOfFile( - mapper->hMap, - FILE_MAP_READ, - 0, 0, 0); + mapper->base = (char *)MapViewOfFile(mapper->hMap, FILE_MAP_READ, 0, 0, 0); mapper->size = GetFileSize(mapper->hFile, 0); #endif @@ -98,15 +95,17 @@ PyImaging_MapperNew(const char* filename, int readonly) } static void -mapping_dealloc(ImagingMapperObject* mapper) -{ +mapping_dealloc(ImagingMapperObject *mapper) { #ifdef _WIN32 - if (mapper->base != 0) + if (mapper->base != 0) { UnmapViewOfFile(mapper->base); - if (mapper->hMap != (HANDLE)-1) + } + if (mapper->hMap != (HANDLE)-1) { CloseHandle(mapper->hMap); - if (mapper->hFile != (HANDLE)-1) + } + if (mapper->hFile != (HANDLE)-1) { CloseHandle(mapper->hFile); + } mapper->base = 0; mapper->hMap = mapper->hFile = (HANDLE)-1; #endif @@ -116,24 +115,27 @@ mapping_dealloc(ImagingMapperObject* mapper) /* -------------------------------------------------------------------- */ /* standard file operations */ -static PyObject* -mapping_read(ImagingMapperObject* mapper, PyObject* args) -{ - PyObject* buf; +static PyObject * +mapping_read(ImagingMapperObject *mapper, PyObject *args) { + PyObject *buf; int size = -1; - if (!PyArg_ParseTuple(args, "|i", &size)) + if (!PyArg_ParseTuple(args, "|i", &size)) { return NULL; + } /* check size */ - if (size < 0 || mapper->offset + size > mapper->size) + if (size < 0 || mapper->offset + size > mapper->size) { size = mapper->size - mapper->offset; - if (size < 0) + } + if (size < 0) { size = 0; + } buf = PyBytes_FromStringAndSize(NULL, size); - if (!buf) + if (!buf) { return NULL; + } if (size > 0) { memcpy(PyBytes_AsString(buf), mapper->base + mapper->offset, size); @@ -143,13 +145,13 @@ mapping_read(ImagingMapperObject* mapper, PyObject* args) return buf; } -static PyObject* -mapping_seek(ImagingMapperObject* mapper, PyObject* args) -{ +static PyObject * +mapping_seek(ImagingMapperObject *mapper, PyObject *args) { int offset; int whence = 0; - if (!PyArg_ParseTuple(args, "i|i", &offset, &whence)) + if (!PyArg_ParseTuple(args, "i|i", &offset, &whence)) { return NULL; + } switch (whence) { case 0: /* SEEK_SET */ @@ -173,58 +175,63 @@ mapping_seek(ImagingMapperObject* mapper, PyObject* args) /* -------------------------------------------------------------------- */ /* map entire image */ -extern PyObject*PyImagingNew(Imaging im); +extern PyObject * +PyImagingNew(Imaging im); static void -ImagingDestroyMap(Imaging im) -{ +ImagingDestroyMap(Imaging im) { return; /* nothing to do! */ } -static PyObject* -mapping_readimage(ImagingMapperObject* mapper, PyObject* args) -{ +static PyObject * +mapping_readimage(ImagingMapperObject *mapper, PyObject *args) { int y, size; Imaging im; - char* mode; + char *mode; int xsize; int ysize; int stride; int orientation; - if (!PyArg_ParseTuple(args, "s(ii)ii", &mode, &xsize, &ysize, - &stride, &orientation)) + if (!PyArg_ParseTuple( + args, "s(ii)ii", &mode, &xsize, &ysize, &stride, &orientation)) { return NULL; + } if (stride <= 0) { /* FIXME: maybe we should call ImagingNewPrologue instead */ - if (!strcmp(mode, "L") || !strcmp(mode, "P")) + if (!strcmp(mode, "L") || !strcmp(mode, "P")) { stride = xsize; - else if (!strcmp(mode, "I;16") || !strcmp(mode, "I;16B")) + } else if (!strcmp(mode, "I;16") || !strcmp(mode, "I;16B")) { stride = xsize * 2; - else + } else { stride = xsize * 4; + } } size = ysize * stride; if (mapper->offset + size > mapper->size) { - PyErr_SetString(PyExc_IOError, "image file truncated"); + PyErr_SetString(PyExc_OSError, "image file truncated"); return NULL; } im = ImagingNewPrologue(mode, xsize, ysize); - if (!im) + if (!im) { return NULL; + } im->readonly = 1; /* setup file pointers */ - if (orientation > 0) - for (y = 0; y < ysize; y++) + if (orientation > 0) { + for (y = 0; y < ysize; y++) { im->image[y] = mapper->base + mapper->offset + y * stride; - else - for (y = 0; y < ysize; y++) - im->image[ysize-y-1] = mapper->base + mapper->offset + y * stride; + } + } else { + for (y = 0; y < ysize; y++) { + im->image[ysize - y - 1] = mapper->base + mapper->offset + y * stride; + } + } im->destroy = ImagingDestroyMap; @@ -243,47 +250,46 @@ static struct PyMethodDef methods[] = { }; static PyTypeObject ImagingMapperType = { - PyVarObject_HEAD_INIT(NULL, 0) - "ImagingMapper", /*tp_name*/ - sizeof(ImagingMapperObject), /*tp_size*/ - 0, /*tp_itemsize*/ - /* methods */ - (destructor)mapping_dealloc, /*tp_dealloc*/ - 0, /*tp_print*/ - 0, /*tp_getattr*/ - 0, /*tp_setattr*/ - 0, /*tp_compare*/ - 0, /*tp_repr*/ - 0, /*tp_as_number */ - 0, /*tp_as_sequence */ - 0, /*tp_as_mapping */ - 0, /*tp_hash*/ - 0, /*tp_call*/ - 0, /*tp_str*/ - 0, /*tp_getattro*/ - 0, /*tp_setattro*/ - 0, /*tp_as_buffer*/ - Py_TPFLAGS_DEFAULT, /*tp_flags*/ - 0, /*tp_doc*/ - 0, /*tp_traverse*/ - 0, /*tp_clear*/ - 0, /*tp_richcompare*/ - 0, /*tp_weaklistoffset*/ - 0, /*tp_iter*/ - 0, /*tp_iternext*/ - methods, /*tp_methods*/ - 0, /*tp_members*/ - 0, /*tp_getset*/ + PyVarObject_HEAD_INIT(NULL, 0) "ImagingMapper", /*tp_name*/ + sizeof(ImagingMapperObject), /*tp_size*/ + 0, /*tp_itemsize*/ + /* methods */ + (destructor)mapping_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number */ + 0, /*tp_as_sequence */ + 0, /*tp_as_mapping */ + 0, /*tp_hash*/ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT, /*tp_flags*/ + 0, /*tp_doc*/ + 0, /*tp_traverse*/ + 0, /*tp_clear*/ + 0, /*tp_richcompare*/ + 0, /*tp_weaklistoffset*/ + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + methods, /*tp_methods*/ + 0, /*tp_members*/ + 0, /*tp_getset*/ }; -PyObject* -PyImaging_Mapper(PyObject* self, PyObject* args) -{ - char* filename; - if (!PyArg_ParseTuple(args, "s", &filename)) +PyObject * +PyImaging_Mapper(PyObject *self, PyObject *args) { + char *filename; + if (!PyArg_ParseTuple(args, "s", &filename)) { return NULL; + } - return (PyObject*) PyImaging_MapperNew(filename, 1); + return (PyObject *)PyImaging_MapperNew(filename, 1); } /* -------------------------------------------------------------------- */ @@ -291,37 +297,45 @@ PyImaging_Mapper(PyObject* self, PyObject* args) typedef struct ImagingBufferInstance { struct ImagingMemoryInstance im; - PyObject* target; + PyObject *target; Py_buffer view; } ImagingBufferInstance; static void -mapping_destroy_buffer(Imaging im) -{ - ImagingBufferInstance* buffer = (ImagingBufferInstance*) im; +mapping_destroy_buffer(Imaging im) { + ImagingBufferInstance *buffer = (ImagingBufferInstance *)im; PyBuffer_Release(&buffer->view); Py_XDECREF(buffer->target); } -PyObject* -PyImaging_MapBuffer(PyObject* self, PyObject* args) -{ +PyObject * +PyImaging_MapBuffer(PyObject *self, PyObject *args) { Py_ssize_t y, size; Imaging im; - PyObject* target; + PyObject *target; Py_buffer view; - char* mode; - char* codec; + char *mode; + char *codec; Py_ssize_t offset; int xsize, ysize; int stride; int ystep; - if (!PyArg_ParseTuple(args, "O(ii)sn(sii)", &target, &xsize, &ysize, - &codec, &offset, &mode, &stride, &ystep)) + if (!PyArg_ParseTuple( + args, + "O(ii)sn(sii)", + &target, + &xsize, + &ysize, + &codec, + &offset, + &mode, + &stride, + &ystep)) { return NULL; + } if (!PyImaging_CheckBuffer(target)) { PyErr_SetString(PyExc_TypeError, "expected string or buffer"); @@ -329,12 +343,13 @@ PyImaging_MapBuffer(PyObject* self, PyObject* args) } if (stride <= 0) { - if (!strcmp(mode, "L") || !strcmp(mode, "P")) + if (!strcmp(mode, "L") || !strcmp(mode, "P")) { stride = xsize; - else if (!strncmp(mode, "I;16", 4)) + } else if (!strncmp(mode, "I;16", 4)) { stride = xsize * 2; - else + } else { stride = xsize * 4; + } } if (stride > 0 && ysize > PY_SSIZE_T_MAX / stride) { @@ -342,7 +357,7 @@ PyImaging_MapBuffer(PyObject* self, PyObject* args) return NULL; } - size = (Py_ssize_t) ysize * stride; + size = (Py_ssize_t)ysize * stride; if (offset > PY_SSIZE_T_MAX - size) { PyErr_SetString(PyExc_MemoryError, "Integer overflow in offset"); @@ -350,38 +365,44 @@ PyImaging_MapBuffer(PyObject* self, PyObject* args) } /* check buffer size */ - if (PyImaging_GetBuffer(target, &view) < 0) + if (PyImaging_GetBuffer(target, &view) < 0) { return NULL; + } if (view.len < 0) { PyErr_SetString(PyExc_ValueError, "buffer has negative size"); + PyBuffer_Release(&view); return NULL; } if (offset + size > view.len) { PyErr_SetString(PyExc_ValueError, "buffer is not large enough"); + PyBuffer_Release(&view); return NULL; } - im = ImagingNewPrologueSubtype( - mode, xsize, ysize, sizeof(ImagingBufferInstance)); - if (!im) + im = ImagingNewPrologueSubtype(mode, xsize, ysize, sizeof(ImagingBufferInstance)); + if (!im) { + PyBuffer_Release(&view); return NULL; + } im->readonly = view.readonly; /* setup file pointers */ - if (ystep > 0) - for (y = 0; y < ysize; y++) - im->image[y] = (char*)view.buf + offset + y * stride; - else - for (y = 0; y < ysize; y++) - im->image[ysize-y-1] = (char*)view.buf + offset + y * stride; + if (ystep > 0) { + for (y = 0; y < ysize; y++) { + im->image[y] = (char *)view.buf + offset + y * stride; + } + } else { + for (y = 0; y < ysize; y++) { + im->image[ysize - y - 1] = (char *)view.buf + offset + y * stride; + } + } im->destroy = mapping_destroy_buffer; Py_INCREF(target); - ((ImagingBufferInstance*) im)->target = target; - ((ImagingBufferInstance*) im)->view = view; + ((ImagingBufferInstance *)im)->target = target; + ((ImagingBufferInstance *)im)->view = view; return PyImagingNew(im); } - diff --git a/src/outline.c b/src/outline.c index 25e63aeaf..ba3e056cc 100644 --- a/src/outline.c +++ b/src/outline.c @@ -19,32 +19,31 @@ #include "Python.h" -#include "Imaging.h" - +#include "libImaging/Imaging.h" /* -------------------------------------------------------------------- */ -/* Class */ +/* Class */ typedef struct { - PyObject_HEAD - ImagingOutline outline; + PyObject_HEAD ImagingOutline outline; } OutlineObject; static PyTypeObject OutlineType; #define PyOutline_Check(op) (Py_TYPE(op) == &OutlineType) -static OutlineObject* -_outline_new(void) -{ +static OutlineObject * +_outline_new(void) { OutlineObject *self; - if (PyType_Ready(&OutlineType) < 0) + if (PyType_Ready(&OutlineType) < 0) { return NULL; + } self = PyObject_New(OutlineObject, &OutlineType); - if (self == NULL) - return NULL; + if (self == NULL) { + return NULL; + } self->outline = ImagingOutlineNew(); @@ -52,44 +51,41 @@ _outline_new(void) } static void -_outline_dealloc(OutlineObject* self) -{ +_outline_dealloc(OutlineObject *self) { ImagingOutlineDelete(self->outline); PyObject_Del(self); } ImagingOutline -PyOutline_AsOutline(PyObject* outline) -{ - if (PyOutline_Check(outline)) - return ((OutlineObject*) outline)->outline; +PyOutline_AsOutline(PyObject *outline) { + if (PyOutline_Check(outline)) { + return ((OutlineObject *)outline)->outline; + } return NULL; } - /* -------------------------------------------------------------------- */ -/* Factories */ +/* Factories */ -PyObject* -PyOutline_Create(PyObject* self, PyObject* args) -{ - if (!PyArg_ParseTuple(args, ":outline")) +PyObject * +PyOutline_Create(PyObject *self, PyObject *args) { + if (!PyArg_ParseTuple(args, ":outline")) { return NULL; + } - return (PyObject*) _outline_new(); + return (PyObject *)_outline_new(); } - /* -------------------------------------------------------------------- */ -/* Methods */ +/* Methods */ -static PyObject* -_outline_move(OutlineObject* self, PyObject* args) -{ +static PyObject * +_outline_move(OutlineObject *self, PyObject *args) { float x0, y0; - if (!PyArg_ParseTuple(args, "ff", &x0, &y0)) - return NULL; + if (!PyArg_ParseTuple(args, "ff", &x0, &y0)) { + return NULL; + } ImagingOutlineMove(self->outline, x0, y0); @@ -97,12 +93,12 @@ _outline_move(OutlineObject* self, PyObject* args) return Py_None; } -static PyObject* -_outline_line(OutlineObject* self, PyObject* args) -{ +static PyObject * +_outline_line(OutlineObject *self, PyObject *args) { float x1, y1; - if (!PyArg_ParseTuple(args, "ff", &x1, &y1)) - return NULL; + if (!PyArg_ParseTuple(args, "ff", &x1, &y1)) { + return NULL; + } ImagingOutlineLine(self->outline, x1, y1); @@ -110,12 +106,12 @@ _outline_line(OutlineObject* self, PyObject* args) return Py_None; } -static PyObject* -_outline_curve(OutlineObject* self, PyObject* args) -{ +static PyObject * +_outline_curve(OutlineObject *self, PyObject *args) { float x1, y1, x2, y2, x3, y3; - if (!PyArg_ParseTuple(args, "ffffff", &x1, &y1, &x2, &y2, &x3, &y3)) - return NULL; + if (!PyArg_ParseTuple(args, "ffffff", &x1, &y1, &x2, &y2, &x3, &y3)) { + return NULL; + } ImagingOutlineCurve(self->outline, x1, y1, x2, y2, x3, y3); @@ -123,11 +119,11 @@ _outline_curve(OutlineObject* self, PyObject* args) return Py_None; } -static PyObject* -_outline_close(OutlineObject* self, PyObject* args) -{ - if (!PyArg_ParseTuple(args, ":close")) +static PyObject * +_outline_close(OutlineObject *self, PyObject *args) { + if (!PyArg_ParseTuple(args, ":close")) { return NULL; + } ImagingOutlineClose(self->outline); @@ -135,12 +131,12 @@ _outline_close(OutlineObject* self, PyObject* args) return Py_None; } -static PyObject* -_outline_transform(OutlineObject* self, PyObject* args) -{ +static PyObject * +_outline_transform(OutlineObject *self, PyObject *args) { double a[6]; - if (!PyArg_ParseTuple(args, "(dddddd)", a+0, a+1, a+2, a+3, a+4, a+5)) + if (!PyArg_ParseTuple(args, "(dddddd)", a + 0, a + 1, a + 2, a + 3, a + 4, a + 5)) { return NULL; + } ImagingOutlineTransform(self->outline, a); @@ -158,35 +154,34 @@ static struct PyMethodDef _outline_methods[] = { }; static PyTypeObject OutlineType = { - PyVarObject_HEAD_INIT(NULL, 0) - "Outline", /*tp_name*/ - sizeof(OutlineObject), /*tp_size*/ - 0, /*tp_itemsize*/ - /* methods */ - (destructor)_outline_dealloc, /*tp_dealloc*/ - 0, /*tp_print*/ - 0, /*tp_getattr*/ - 0, /*tp_setattr*/ - 0, /*tp_compare*/ - 0, /*tp_repr*/ - 0, /*tp_as_number */ - 0, /*tp_as_sequence */ - 0, /*tp_as_mapping */ - 0, /*tp_hash*/ - 0, /*tp_call*/ - 0, /*tp_str*/ - 0, /*tp_getattro*/ - 0, /*tp_setattro*/ - 0, /*tp_as_buffer*/ - Py_TPFLAGS_DEFAULT, /*tp_flags*/ - 0, /*tp_doc*/ - 0, /*tp_traverse*/ - 0, /*tp_clear*/ - 0, /*tp_richcompare*/ - 0, /*tp_weaklistoffset*/ - 0, /*tp_iter*/ - 0, /*tp_iternext*/ - _outline_methods, /*tp_methods*/ - 0, /*tp_members*/ - 0, /*tp_getset*/ + PyVarObject_HEAD_INIT(NULL, 0) "Outline", /*tp_name*/ + sizeof(OutlineObject), /*tp_size*/ + 0, /*tp_itemsize*/ + /* methods */ + (destructor)_outline_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number */ + 0, /*tp_as_sequence */ + 0, /*tp_as_mapping */ + 0, /*tp_hash*/ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT, /*tp_flags*/ + 0, /*tp_doc*/ + 0, /*tp_traverse*/ + 0, /*tp_clear*/ + 0, /*tp_richcompare*/ + 0, /*tp_weaklistoffset*/ + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + _outline_methods, /*tp_methods*/ + 0, /*tp_members*/ + 0, /*tp_getset*/ }; diff --git a/src/path.c b/src/path.c index f69755d16..8d1f68e84 100644 --- a/src/path.c +++ b/src/path.c @@ -25,57 +25,55 @@ * See the README file for information on usage and redistribution. */ - #include "Python.h" -#include "Imaging.h" +#include "libImaging/Imaging.h" #include /* compatibility wrappers (defined in _imaging.c) */ -extern int PyImaging_CheckBuffer(PyObject* buffer); -extern int PyImaging_GetBuffer(PyObject* buffer, Py_buffer *view); +extern int +PyImaging_CheckBuffer(PyObject *buffer); +extern int +PyImaging_GetBuffer(PyObject *buffer, Py_buffer *view); /* -------------------------------------------------------------------- */ /* Class */ /* -------------------------------------------------------------------- */ typedef struct { - PyObject_HEAD - Py_ssize_t count; + PyObject_HEAD Py_ssize_t count; double *xy; int index; /* temporary use, e.g. in decimate */ } PyPathObject; static PyTypeObject PyPathType; -static double* -alloc_array(Py_ssize_t count) -{ - double* xy; +static double * +alloc_array(Py_ssize_t count) { + double *xy; if (count < 0) { - PyErr_NoMemory(); - return NULL; + return ImagingError_MemoryError(); } - if (count > (SIZE_MAX / (2 * sizeof(double))) - 1 ) { - PyErr_NoMemory(); - return NULL; + if ((unsigned long long)count > (SIZE_MAX / (2 * sizeof(double))) - 1) { + return ImagingError_MemoryError(); } xy = malloc(2 * count * sizeof(double) + 1); - if (!xy) - PyErr_NoMemory(); + if (!xy) { + ImagingError_MemoryError(); + } return xy; } -static PyPathObject* -path_new(Py_ssize_t count, double* xy, int duplicate) -{ +static PyPathObject * +path_new(Py_ssize_t count, double *xy, int duplicate) { PyPathObject *path; if (duplicate) { /* duplicate path */ - double* p = alloc_array(count); - if (!p) + double *p = alloc_array(count); + if (!p) { return NULL; + } memcpy(p, xy, count * 2 * sizeof(double)); xy = p; } @@ -98,8 +96,7 @@ path_new(Py_ssize_t count, double* xy, int duplicate) } static void -path_dealloc(PyPathObject* path) -{ +path_dealloc(PyPathObject *path) { free(path->xy); PyObject_Del(path); } @@ -111,17 +108,17 @@ path_dealloc(PyPathObject* path) #define PyPath_Check(op) (Py_TYPE(op) == &PyPathType) Py_ssize_t -PyPath_Flatten(PyObject* data, double **pxy) -{ +PyPath_Flatten(PyObject *data, double **pxy) { Py_ssize_t i, j, n; double *xy; if (PyPath_Check(data)) { /* This was another path object. */ - PyPathObject *path = (PyPathObject*) data; + PyPathObject *path = (PyPathObject *)data; xy = alloc_array(path->count); - if (!xy) + if (!xy) { return -1; + } memcpy(xy, path->xy, 2 * path->count * sizeof(double)); *pxy = xy; return path->count; @@ -131,13 +128,15 @@ PyPath_Flatten(PyObject* data, double **pxy) /* Assume the buffer contains floats */ Py_buffer buffer; if (PyImaging_GetBuffer(data, &buffer) == 0) { - float *ptr = (float*) buffer.buf; + float *ptr = (float *)buffer.buf; n = buffer.len / (2 * sizeof(float)); xy = alloc_array(n); - if (!xy) + if (!xy) { return -1; - for (i = 0; i < n+n; i++) + } + for (i = 0; i < n + n; i++) { xy[i] = ptr[i]; + } *pxy = xy; PyBuffer_Release(&buffer); return n; @@ -153,26 +152,28 @@ PyPath_Flatten(PyObject* data, double **pxy) j = 0; n = PyObject_Length(data); /* Just in case __len__ breaks (or doesn't exist) */ - if (PyErr_Occurred()) + if (PyErr_Occurred()) { return -1; + } /* Allocate for worst case */ xy = alloc_array(n); - if (!xy) + if (!xy) { return -1; + } /* Copy table to path array */ if (PyList_Check(data)) { for (i = 0; i < n; i++) { double x, y; PyObject *op = PyList_GET_ITEM(data, i); - if (PyFloat_Check(op)) + if (PyFloat_Check(op)) { xy[j++] = PyFloat_AS_DOUBLE(op); - else if (PyLong_Check(op)) - xy[j++] = (float) PyLong_AS_LONG(op); - else if (PyNumber_Check(op)) + } else if (PyLong_Check(op)) { + xy[j++] = (float)PyLong_AS_LONG(op); + } else if (PyNumber_Check(op)) { xy[j++] = PyFloat_AsDouble(op); - else if (PyArg_ParseTuple(op, "dd", &x, &y)) { + } else if (PyArg_ParseTuple(op, "dd", &x, &y)) { xy[j++] = x; xy[j++] = y; } else { @@ -184,13 +185,13 @@ PyPath_Flatten(PyObject* data, double **pxy) for (i = 0; i < n; i++) { double x, y; PyObject *op = PyTuple_GET_ITEM(data, i); - if (PyFloat_Check(op)) + if (PyFloat_Check(op)) { xy[j++] = PyFloat_AS_DOUBLE(op); - else if (PyLong_Check(op)) - xy[j++] = (float) PyLong_AS_LONG(op); - else if (PyNumber_Check(op)) + } else if (PyLong_Check(op)) { + xy[j++] = (float)PyLong_AS_LONG(op); + } else if (PyNumber_Check(op)) { xy[j++] = PyFloat_AsDouble(op); - else if (PyArg_ParseTuple(op, "dd", &x, &y)) { + } else if (PyArg_ParseTuple(op, "dd", &x, &y)) { xy[j++] = x; xy[j++] = y; } else { @@ -204,8 +205,7 @@ PyPath_Flatten(PyObject* data, double **pxy) PyObject *op = PySequence_GetItem(data, i); if (!op) { /* treat IndexError as end of sequence */ - if (PyErr_Occurred() && - PyErr_ExceptionMatches(PyExc_IndexError)) { + if (PyErr_Occurred() && PyErr_ExceptionMatches(PyExc_IndexError)) { PyErr_Clear(); break; } else { @@ -213,13 +213,13 @@ PyPath_Flatten(PyObject* data, double **pxy) return -1; } } - if (PyFloat_Check(op)) + if (PyFloat_Check(op)) { xy[j++] = PyFloat_AS_DOUBLE(op); - else if (PyLong_Check(op)) - xy[j++] = (float) PyLong_AS_LONG(op); - else if (PyNumber_Check(op)) + } else if (PyLong_Check(op)) { + xy[j++] = (float)PyLong_AS_LONG(op); + } else if (PyNumber_Check(op)) { xy[j++] = PyFloat_AsDouble(op); - else if (PyArg_ParseTuple(op, "dd", &x, &y)) { + } else if (PyArg_ParseTuple(op, "dd", &x, &y)) { xy[j++] = x; xy[j++] = y; } else { @@ -238,51 +238,48 @@ PyPath_Flatten(PyObject* data, double **pxy) } *pxy = xy; - return j/2; + return j / 2; } - /* -------------------------------------------------------------------- */ /* Factories */ /* -------------------------------------------------------------------- */ -PyObject* -PyPath_Create(PyObject* self, PyObject* args) -{ - PyObject* data; +PyObject * +PyPath_Create(PyObject *self, PyObject *args) { + PyObject *data; Py_ssize_t count; double *xy; if (PyArg_ParseTuple(args, "n:Path", &count)) { - /* number of vertices */ xy = alloc_array(count); - if (!xy) + if (!xy) { return NULL; + } } else { - /* sequence or other path */ PyErr_Clear(); - if (!PyArg_ParseTuple(args, "O", &data)) + if (!PyArg_ParseTuple(args, "O", &data)) { return NULL; + } count = PyPath_Flatten(data, &xy); - if (count < 0) + if (count < 0) { return NULL; + } } - return (PyObject*) path_new(count, xy, 0); + return (PyObject *)path_new(count, xy, 0); } - /* -------------------------------------------------------------------- */ /* Methods */ /* -------------------------------------------------------------------- */ -static PyObject* -path_compact(PyPathObject* self, PyObject* args) -{ +static PyObject * +path_compact(PyPathObject *self, PyObject *args) { /* Simple-minded method to shorten path. A point is removed if the city block distance to the previous point is less than the given distance */ @@ -291,16 +288,18 @@ path_compact(PyPathObject* self, PyObject* args) double cityblock = 2.0; - if (!PyArg_ParseTuple(args, "|d:compact", &cityblock)) + if (!PyArg_ParseTuple(args, "|d:compact", &cityblock)) { return NULL; + } xy = self->xy; /* remove bogus vertices */ for (i = j = 1; i < self->count; i++) { - if (fabs(xy[j+j-2]-xy[i+i]) + fabs(xy[j+j-1]-xy[i+i+1]) >= cityblock) { - xy[j+j] = xy[i+i]; - xy[j+j+1] = xy[i+i+1]; + if (fabs(xy[j + j - 2] - xy[i + i]) + fabs(xy[j + j - 1] - xy[i + i + 1]) >= + cityblock) { + xy[j + j] = xy[i + i]; + xy[j + j + 1] = xy[i + i + 1]; j++; } } @@ -315,16 +314,16 @@ path_compact(PyPathObject* self, PyObject* args) return Py_BuildValue("i", i); /* number of removed vertices */ } -static PyObject* -path_getbbox(PyPathObject* self, PyObject* args) -{ +static PyObject * +path_getbbox(PyPathObject *self, PyObject *args) { /* Find bounding box */ Py_ssize_t i; double *xy; double x0, y0, x1, y1; - if (!PyArg_ParseTuple(args, ":getbbox")) + if (!PyArg_ParseTuple(args, ":getbbox")) { return NULL; + } xy = self->xy; @@ -332,80 +331,85 @@ path_getbbox(PyPathObject* self, PyObject* args) y0 = y1 = xy[1]; for (i = 1; i < self->count; i++) { - if (xy[i+i] < x0) - x0 = xy[i+i]; - if (xy[i+i] > x1) - x1 = xy[i+i]; - if (xy[i+i+1] < y0) - y0 = xy[i+i+1]; - if (xy[i+i+1] > y1) - y1 = xy[i+i+1]; + if (xy[i + i] < x0) { + x0 = xy[i + i]; + } + if (xy[i + i] > x1) { + x1 = xy[i + i]; + } + if (xy[i + i + 1] < y0) { + y0 = xy[i + i + 1]; + } + if (xy[i + i + 1] > y1) { + y1 = xy[i + i + 1]; + } } return Py_BuildValue("dddd", x0, y0, x1, y1); } -static PyObject* -path_getitem(PyPathObject* self, Py_ssize_t i) -{ - if (i < 0) +static PyObject * +path_getitem(PyPathObject *self, Py_ssize_t i) { + if (i < 0) { i = self->count + i; + } if (i < 0 || i >= self->count) { PyErr_SetString(PyExc_IndexError, "path index out of range"); return NULL; } - return Py_BuildValue("dd", self->xy[i+i], self->xy[i+i+1]); + return Py_BuildValue("dd", self->xy[i + i], self->xy[i + i + 1]); } -static PyObject* -path_getslice(PyPathObject* self, Py_ssize_t ilow, Py_ssize_t ihigh) -{ +static PyObject * +path_getslice(PyPathObject *self, Py_ssize_t ilow, Py_ssize_t ihigh) { /* adjust arguments */ - if (ilow < 0) + if (ilow < 0) { ilow = 0; - else if (ilow >= self->count) + } else if (ilow >= self->count) { ilow = self->count; - if (ihigh < 0) + } + if (ihigh < 0) { ihigh = 0; - if (ihigh < ilow) + } + if (ihigh < ilow) { ihigh = ilow; - else if (ihigh > self->count) + } else if (ihigh > self->count) { ihigh = self->count; + } - return (PyObject*) path_new(ihigh - ilow, self->xy + ilow * 2, 1); + return (PyObject *)path_new(ihigh - ilow, self->xy + ilow * 2, 1); } static Py_ssize_t -path_len(PyPathObject* self) -{ +path_len(PyPathObject *self) { return self->count; } -static PyObject* -path_map(PyPathObject* self, PyObject* args) -{ +static PyObject * +path_map(PyPathObject *self, PyObject *args) { /* Map coordinate set through function */ Py_ssize_t i; double *xy; - PyObject* function; + PyObject *function; - if (!PyArg_ParseTuple(args, "O:map", &function)) + if (!PyArg_ParseTuple(args, "O:map", &function)) { return NULL; + } xy = self->xy; /* apply function to coordinate set */ for (i = 0; i < self->count; i++) { - double x = xy[i+i]; - double y = xy[i+i+1]; - PyObject* item = PyObject_CallFunction(function, "dd", x, y); + double x = xy[i + i]; + double y = xy[i + i + 1]; + PyObject *item = PyObject_CallFunction(function, "dd", x, y); if (!item || !PyArg_ParseTuple(item, "dd", &x, &y)) { Py_XDECREF(item); return NULL; } - xy[i+i] = x; - xy[i+i+1] = y; + xy[i + i] = x; + xy[i + i + 1] = y; Py_DECREF(item); } @@ -414,56 +418,56 @@ path_map(PyPathObject* self, PyObject* args) } static int -path_setitem(PyPathObject* self, Py_ssize_t i, PyObject* op) -{ - double* xy; +path_setitem(PyPathObject *self, Py_ssize_t i, PyObject *op) { + double *xy; if (i < 0 || i >= self->count) { - PyErr_SetString(PyExc_IndexError, - "path assignment index out of range"); + PyErr_SetString(PyExc_IndexError, "path assignment index out of range"); return -1; } if (op == NULL) { - PyErr_SetString(PyExc_TypeError, - "cannot delete from path"); + PyErr_SetString(PyExc_TypeError, "cannot delete from path"); return -1; } - xy = &self->xy[i+i]; + xy = &self->xy[i + i]; - if (!PyArg_ParseTuple(op, "dd", &xy[0], &xy[1])) + if (!PyArg_ParseTuple(op, "dd", &xy[0], &xy[1])) { return -1; + } return 0; } -static PyObject* -path_tolist(PyPathObject* self, PyObject* args) -{ +static PyObject * +path_tolist(PyPathObject *self, PyObject *args) { PyObject *list; Py_ssize_t i; int flat = 0; - if (!PyArg_ParseTuple(args, "|i:tolist", &flat)) + if (!PyArg_ParseTuple(args, "|i:tolist", &flat)) { return NULL; + } if (flat) { - list = PyList_New(self->count*2); - for (i = 0; i < self->count*2; i++) { - PyObject* item; + list = PyList_New(self->count * 2); + for (i = 0; i < self->count * 2; i++) { + PyObject *item; item = PyFloat_FromDouble(self->xy[i]); - if (!item) + if (!item) { goto error; + } PyList_SetItem(list, i, item); } } else { list = PyList_New(self->count); for (i = 0; i < self->count; i++) { - PyObject* item; - item = Py_BuildValue("dd", self->xy[i+i], self->xy[i+i+1]); - if (!item) + PyObject *item; + item = Py_BuildValue("dd", self->xy[i + i], self->xy[i + i + 1]); + if (!item) { goto error; + } PyList_SetItem(list, i, item); } } @@ -475,9 +479,8 @@ error: return NULL; } -static PyObject* -path_transform(PyPathObject* self, PyObject* args) -{ +static PyObject * +path_transform(PyPathObject *self, PyObject *args) { /* Apply affine transform to coordinate set */ Py_ssize_t i; double *xy; @@ -485,33 +488,36 @@ path_transform(PyPathObject* self, PyObject* args) double wrap = 0.0; - if (!PyArg_ParseTuple(args, "(dddddd)|d:transform", - &a, &b, &c, &d, &e, &f, - &wrap)) + if (!PyArg_ParseTuple( + args, "(dddddd)|d:transform", &a, &b, &c, &d, &e, &f, &wrap)) { return NULL; + } xy = self->xy; /* transform the coordinate set */ - if (b == 0.0 && d == 0.0) + if (b == 0.0 && d == 0.0) { /* scaling */ for (i = 0; i < self->count; i++) { - xy[i+i] = a*xy[i+i]+c; - xy[i+i+1] = e*xy[i+i+1]+f; + xy[i + i] = a * xy[i + i] + c; + xy[i + i + 1] = e * xy[i + i + 1] + f; } - else + } else { /* affine transform */ for (i = 0; i < self->count; i++) { - double x = xy[i+i]; - double y = xy[i+i+1]; - xy[i+i] = a*x+b*y+c; - xy[i+i+1] = d*x+e*y+f; + double x = xy[i + i]; + double y = xy[i + i + 1]; + xy[i + i] = a * x + b * y + c; + xy[i + i + 1] = d * x + e * y + f; } + } /* special treatment of geographical map data */ - if (wrap != 0.0) - for (i = 0; i < self->count; i++) - xy[i+i] = fmod(xy[i+i], wrap); + if (wrap != 0.0) { + for (i = 0; i < self->count; i++) { + xy[i + i] = fmod(xy[i + i], wrap); + } + } Py_INCREF(Py_None); return Py_None; @@ -526,100 +532,91 @@ static struct PyMethodDef methods[] = { {NULL, NULL} /* sentinel */ }; -static PyObject* -path_getattr_id(PyPathObject* self, void* closure) -{ - return Py_BuildValue("n", (Py_ssize_t) self->xy); +static PyObject * +path_getattr_id(PyPathObject *self, void *closure) { + return Py_BuildValue("n", (Py_ssize_t)self->xy); } -static struct PyGetSetDef getsetters[] = { - { "id", (getter) path_getattr_id }, - { NULL } -}; +static struct PyGetSetDef getsetters[] = {{"id", (getter)path_getattr_id}, {NULL}}; -static PyObject* -path_subscript(PyPathObject* self, PyObject* item) { +static PyObject * +path_subscript(PyPathObject *self, PyObject *item) { if (PyIndex_Check(item)) { Py_ssize_t i; i = PyNumber_AsSsize_t(item, PyExc_IndexError); - if (i == -1 && PyErr_Occurred()) + if (i == -1 && PyErr_Occurred()) { return NULL; + } return path_getitem(self, i); } if (PySlice_Check(item)) { int len = 4; Py_ssize_t start, stop, step, slicelength; - if (PySlice_GetIndicesEx(item, len, &start, &stop, &step, &slicelength) < 0) + if (PySlice_GetIndicesEx(item, len, &start, &stop, &step, &slicelength) < 0) { return NULL; + } if (slicelength <= 0) { double *xy = alloc_array(0); - return (PyObject*) path_new(0, xy, 0); - } - else if (step == 1) { + return (PyObject *)path_new(0, xy, 0); + } else if (step == 1) { return path_getslice(self, start, stop); - } - else { + } else { PyErr_SetString(PyExc_TypeError, "slice steps not supported"); return NULL; } - } - else { - PyErr_Format(PyExc_TypeError, - "Path indices must be integers, not %.200s", - Py_TYPE(item)->tp_name); + } else { + PyErr_Format( + PyExc_TypeError, + "Path indices must be integers, not %.200s", + Py_TYPE(item)->tp_name); return NULL; } } static PySequenceMethods path_as_sequence = { - (lenfunc)path_len, /*sq_length*/ - (binaryfunc)0, /*sq_concat*/ - (ssizeargfunc)0, /*sq_repeat*/ - (ssizeargfunc)path_getitem, /*sq_item*/ - (ssizessizeargfunc)path_getslice, /*sq_slice*/ - (ssizeobjargproc)path_setitem, /*sq_ass_item*/ - (ssizessizeobjargproc)0, /*sq_ass_slice*/ + (lenfunc)path_len, /*sq_length*/ + (binaryfunc)0, /*sq_concat*/ + (ssizeargfunc)0, /*sq_repeat*/ + (ssizeargfunc)path_getitem, /*sq_item*/ + (ssizessizeargfunc)path_getslice, /*sq_slice*/ + (ssizeobjargproc)path_setitem, /*sq_ass_item*/ + (ssizessizeobjargproc)0, /*sq_ass_slice*/ }; static PyMappingMethods path_as_mapping = { - (lenfunc)path_len, - (binaryfunc)path_subscript, - NULL -}; + (lenfunc)path_len, (binaryfunc)path_subscript, NULL}; static PyTypeObject PyPathType = { - PyVarObject_HEAD_INIT(NULL, 0) - "Path", /*tp_name*/ - sizeof(PyPathObject), /*tp_size*/ - 0, /*tp_itemsize*/ + PyVarObject_HEAD_INIT(NULL, 0) "Path", /*tp_name*/ + sizeof(PyPathObject), /*tp_size*/ + 0, /*tp_itemsize*/ /* methods */ - (destructor)path_dealloc, /*tp_dealloc*/ - 0, /*tp_print*/ - 0, /*tp_getattr*/ - 0, /*tp_setattr*/ - 0, /*tp_compare*/ - 0, /*tp_repr*/ - 0, /*tp_as_number */ - &path_as_sequence, /*tp_as_sequence */ - &path_as_mapping, /*tp_as_mapping */ - 0, /*tp_hash*/ - 0, /*tp_call*/ - 0, /*tp_str*/ - 0, /*tp_getattro*/ - 0, /*tp_setattro*/ - 0, /*tp_as_buffer*/ - Py_TPFLAGS_DEFAULT, /*tp_flags*/ - 0, /*tp_doc*/ - 0, /*tp_traverse*/ - 0, /*tp_clear*/ - 0, /*tp_richcompare*/ - 0, /*tp_weaklistoffset*/ - 0, /*tp_iter*/ - 0, /*tp_iternext*/ - methods, /*tp_methods*/ - 0, /*tp_members*/ - getsetters, /*tp_getset*/ + (destructor)path_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number */ + &path_as_sequence, /*tp_as_sequence */ + &path_as_mapping, /*tp_as_mapping */ + 0, /*tp_hash*/ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT, /*tp_flags*/ + 0, /*tp_doc*/ + 0, /*tp_traverse*/ + 0, /*tp_clear*/ + 0, /*tp_richcompare*/ + 0, /*tp_weaklistoffset*/ + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + methods, /*tp_methods*/ + 0, /*tp_members*/ + getsetters, /*tp_getset*/ }; - diff --git a/tox.ini b/tox.ini index cdfd05dde..2557d5067 100644 --- a/tox.ini +++ b/tox.ini @@ -1,12 +1,12 @@ # Tox (https://tox.readthedocs.io/en/latest/) is a tool for running tests # in multiple virtualenvs. This configuration file will run the -# test suite on all supported python versions. To use it, "pip install tox" -# and then run "tox" from this directory. +# test suite on all supported python versions. To use it, +# "python3 -m pip install tox" and then run "tox" from this directory. [tox] envlist = lint - py{35,36,37,38,py3} + py{36,37,38,39,py3} minversion = 1.9 [testenv] @@ -24,13 +24,10 @@ deps = [testenv:lint] commands = - black --target-version py35 --check --diff . - flake8 --statistics --count - isort --check-only --diff + pre-commit run --all-files --show-diff-on-failure check-manifest deps = - black + pre-commit check-manifest - flake8 - isort skip_install = true +passenv = PRE_COMMIT_COLOR diff --git a/winbuild/README.md b/winbuild/README.md index 471b61a57..611d1ed1a 100644 --- a/winbuild/README.md +++ b/winbuild/README.md @@ -1,18 +1,29 @@ -Quick README ------------- - -For more extensive info, see the [Windows build instructions](build.rst). - -* See https://github.com/python-pillow/Pillow/issues/553#issuecomment-37877416 and https://github.com/matplotlib/matplotlib/issues/1717#issuecomment-13343859 - -* Works best with Python 3.4, due to virtualenv and pip batteries included. Python3+ required for fetch command. -* Check config.py for virtual env paths, suffix for 64-bit releases. Defaults to `x64`, set `X64_EXT` to change. -* When running in CI with one Python per invocation, set the `PYTHON` env variable to the Python folder. (e.g. `PYTHON`=`c:\Python27\`) This overrides the matrix in config.py and will just build and test for the specific Python. -* `python get_pythons.py` downloads all the Python releases, and their signatures. (Manually) Install in `c:\PythonXX[x64]\`. -* `python build_dep.py` downloads and creates a build script for all the dependencies, in 32 and 64-bit versions, and with both compiler versions. -* (in powershell) `build_deps.cmd` invokes the dependency build. -* `python build.py --clean` makes Pillow for the matrix of Pythons. -* `python test.py` runs the tests on Pillow in all the virtual envs. -* Currently working with zlib, libjpeg, freetype, and libtiff on Python 2.7, and 3.4, both 32 and 64-bit, on a local win7 pro machine and appveyor.com -* WebP is built, not detected. -* LCMS, OpenJPEG and libimagequant are not building. +Quick README +------------ + +For more extensive info, see the [Windows build instructions](build.rst). + +* See [Current Windows Build/Testing process (Pillow#553)](https://github.com/python-pillow/Pillow/issues/553#issuecomment-37877416), + [Definitive docs for how to compile on Windows (matplotlib#1717)](https://github.com/matplotlib/matplotlib/issues/1717#issuecomment-13343859), + [Test Windows with GitHub Actions (Pillow#4084)](https://github.com/python-pillow/Pillow/pull/4084). + + +* Requires Microsoft Visual Studio 2017 or newer with C++ component. +* Requires NASM for libjpeg-turbo, a required dependency when using this script. +* Requires CMake 3.12 or newer (available as Visual Studio component). +* Tested on Windows Server 2016 with Visual Studio 2017 Community (AppVeyor). +* Tested on Windows Server 2019 with Visual Studio 2019 Enterprise (GitHub Actions). + +The following is a simplified version of the script used on AppVeyor: +``` +set PYTHON=C:\Python38\bin +cd /D C:\Pillow\winbuild +C:\Python37\bin\python.exe build_prepare.py -v --depends=C:\pillow-depends +build\build_dep_all.cmd +build\build_pillow.cmd install +cd .. +path C:\Pillow\winbuild\build\bin;%PATH% +%PYTHON%\python.exe selftest.py +%PYTHON%\python.exe -m pytest -vx --cov PIL --cov Tests --cov-report term --cov-report xml Tests +build\build_pillow.cmd bdist_wheel +``` diff --git a/winbuild/appveyor_build_msys2.sh b/winbuild/appveyor_build_msys2.sh deleted file mode 100644 index 489f9411e..000000000 --- a/winbuild/appveyor_build_msys2.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -cd /c/pillow && /mingw32/$EXECUTABLE setup.py install diff --git a/winbuild/appveyor_install_msys2_deps.sh b/winbuild/appveyor_install_msys2_deps.sh deleted file mode 100644 index 4cc01082d..000000000 --- a/winbuild/appveyor_install_msys2_deps.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/sh - -mkdir /var/cache/pacman/pkg -pacman -S --noconfirm mingw32/mingw-w64-i686-python3-pip \ - mingw32/mingw-w64-i686-python3-setuptools \ - mingw32/mingw-w64-i686-python3-pytest \ - mingw32/mingw-w64-i686-python3-pytest-cov \ - mingw-w64-i686-libjpeg-turbo \ - mingw-w64-i686-libimagequant - -C:/msys64/mingw32/bin/python3 -m pip install --upgrade pip - -/mingw32/bin/pip install olefile -/mingw32/bin/pip3 install olefile diff --git a/winbuild/appveyor_install_pypy3.cmd b/winbuild/appveyor_install_pypy3.cmd deleted file mode 100644 index 75a22ca59..000000000 --- a/winbuild/appveyor_install_pypy3.cmd +++ /dev/null @@ -1,3 +0,0 @@ -curl -fsSL -o pypy3.zip https://bitbucket.org/pypy/pypy/downloads/pypy3.6-v7.3.0-win32.zip -7z x pypy3.zip -oc:\ -c:\Python37\Scripts\virtualenv.exe -p c:\pypy3.6-v7.3.0-win32\pypy3.exe c:\vp\pypy3 diff --git a/winbuild/build.py b/winbuild/build.py deleted file mode 100755 index e565226bd..000000000 --- a/winbuild/build.py +++ /dev/null @@ -1,205 +0,0 @@ -#!/usr/bin/env python3 - -import getopt -import os -import shutil -import subprocess -import sys - -from config import ( - VIRT_BASE, - X64_EXT, - bit_from_env, - compiler_from_env, - compilers, - pythons, - pyversion_from_env, -) - - -def setup_vms(): - ret = [] - for py in pythons: - for arch in ("", X64_EXT): - ret.append( - "virtualenv -p c:/Python%s%s/python.exe --clear %s%s%s" - % (py, arch, VIRT_BASE, py, arch) - ) - ret.append( - r"%s%s%s\Scripts\pip.exe install pytest pytest-cov" - % (VIRT_BASE, py, arch) - ) - return "\n".join(ret) - - -def run_script(params): - (version, script) = params - try: - print("Running %s" % version) - filename = "build_pillow_%s.cmd" % version - with open(filename, "w") as f: - f.write(script) - - command = ["powershell", "./%s" % filename] - proc = subprocess.Popen( - command, - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - ) - (trace, stderr) = proc.communicate() - status = proc.returncode - print("-- stderr --") - print(stderr.decode()) - print("-- stdout --") - print(trace.decode()) - print("Done with {}: {}".format(version, status)) - return (version, status, trace, stderr) - except Exception as msg: - print("Error with {}: {}".format(version, str(msg))) - return (version, -1, "", str(msg)) - - -def header(op): - return r""" -setlocal -set MPLSRC=%%~dp0\.. -set INCLIB=%%~dp0\depends -set BLDOPT=%s -cd /D %%MPLSRC%% -""" % ( - op - ) - - -def footer(): - return """endlocal -exit -""" - - -def vc_setup(compiler, bit): - script = "" - if compiler["vc_version"] == "2015": - arch = "x86" if bit == 32 else "x86_amd64" - script = ( - r""" -call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" %s -echo on""" - % arch - ) - return script - - -def build_one(py_ver, compiler, bit): - # UNDONE virtual envs if we're not running on AppVeyor - args = {} - args.update(compiler) - if "PYTHON" in os.environ: - args["python_path"] = "%PYTHON%" - else: - args["python_path"] = "{}{}\\Scripts".format(VIRT_BASE, py_ver) - - args["executable"] = "python.exe" - if "EXECUTABLE" in os.environ: - args["executable"] = "%EXECUTABLE%" - - args["py_ver"] = py_ver - args["tcl_ver"] = "86" - - if compiler["vc_version"] == "2015": - args["imaging_libs"] = " build_ext --add-imaging-libs=msvcrt" - else: - args["imaging_libs"] = "" - - args["vc_setup"] = vc_setup(compiler, bit) - - script = r""" -setlocal EnableDelayedExpansion -call "%%ProgramFiles%%\Microsoft SDKs\Windows\%(env_version)s\Bin\SetEnv.Cmd" /Release %(env_flags)s -set DISTUTILS_USE_SDK=1 -set LIB=%%LIB%%;%%INCLIB%%\%(inc_dir)s -set INCLUDE=%%INCLUDE%%;%%INCLIB%%\%(inc_dir)s;%%INCLIB%%\tcl%(tcl_ver)s\include - -setlocal -set LIB=%%LIB%%;C:\Python%(py_ver)s\tcl%(vc_setup)s -call %(python_path)s\%(executable)s setup.py %(imaging_libs)s %%BLDOPT%% -call %(python_path)s\%(executable)s -c "from PIL import _webp;import os, shutil;shutil.copy(r'%%INCLIB%%\freetype.dll', os.path.dirname(_webp.__file__));" -endlocal - -endlocal -""" # noqa: E501 - return script % args - - -def clean(): - try: - shutil.rmtree("../build") - except Exception: - # could already be removed - pass - run_script(("virtualenvs", setup_vms())) - - -def main(op): - scripts = [] - - for py_version, py_info in pythons.items(): - py_compilers = compilers[py_info["compiler"]][py_info["vc"]] - scripts.append( - ( - py_version, - "\n".join( - [header(op), build_one(py_version, py_compilers[32], 32), footer()] - ), - ) - ) - - scripts.append( - ( - "{}{}".format(py_version, X64_EXT), - "\n".join( - [ - header(op), - build_one("%sx64" % py_version, py_compilers[64], 64), - footer(), - ] - ), - ) - ) - - results = map(run_script, scripts) - - for (version, status, trace, err) in results: - print("Compiled {}: {}".format(version, status and "ERR" or "OK")) - - -def run_one(op): - - compiler = compiler_from_env() - py_version = pyversion_from_env() - bit = bit_from_env() - - run_script( - ( - py_version, - "\n".join([header(op), build_one(py_version, compiler, bit), footer()]), - ) - ) - - -if __name__ == "__main__": - opts, args = getopt.getopt(sys.argv[1:], "", ["clean", "wheel"]) - opts = dict(opts) - - if "--clean" in opts: - clean() - - op = "install" - if "--wheel" in opts: - op = "bdist_wheel" - - if "PYTHON" in os.environ: - run_one(op) - else: - main(op) diff --git a/winbuild/build.rst b/winbuild/build.rst index 1d2084044..cd4a45e87 100644 --- a/winbuild/build.rst +++ b/winbuild/build.rst @@ -5,89 +5,109 @@ Building Pillow on Windows <../docs/installation.rst#windows-installation>`_ should be sufficient. -This page will describe a build setup to build Pillow against the -supported Python versions in 32 and 64-bit modes, using freely -available Microsoft compilers. This has been developed and tested -against 64-bit Windows 7 Professional and Windows Server 2012 -64-bit version on Amazon EC2. +This page describes the steps necessary to build Pillow using the same +scripts used on GitHub Actions and AppVeyor CIs. Prerequisites ------------- -Extra Build Helpers -^^^^^^^^^^^^^^^^^^^ - -* Powershell (available by default on Windows Server) -* GitHub client (provides git+bash shell) - -Optional: -* GPG (for checking signatures) (UNDONE -- Python signature checking) - - -Pythons -^^^^^^^ - -The build routines expect Python to be installed at C:\PythonXX for -32-bit versions or C:\PythonXXx64 for the 64-bit versions. - -Download Python 3.4, install it, and add it to the path. This is the -Python that we will use to bootstrap the build process. (The download -routines are using 3 features, and installing 3.4 gives us pip and -virtualenv as well, reducing the number of packages that we need to -install.) - -Download the rest of the Pythons by opening a command window, changing -to the ``winbuild`` directory, and running ``python -get_pythons.py``. - -UNDONE -- gpg verify the signatures (note that we can download from -https) - -Run each installer and set the proper path to the installation. Don't -set any of them as the default Python, or add them to the path. - Compilers ^^^^^^^^^ Download and install: -* `Microsoft Windows SDK for Windows 7 and .NET Framework - 4 `_ +* `Microsoft Visual Studio 2017 or newer or Build Tools for Visual Studio 2017 or newer + `_ + (MSVC C++ build tools, and any Windows SDK version required) -* `CMake-2.8.10.2-win32-x86.exe - `_ +* `CMake 3.12 or newer `_ + (also available as Visual Studio component C++ CMake tools for Windows) -The samples and the .NET SDK portions aren't required, just the -compilers and other tools. UNDONE -- check exact wording. +* `NASM `_ + +Any version of Visual Studio 2017 or newer should be supported, +including Visual Studio 2017 Community, or Build Tools for Visual Studio 2019. + +Paths to CMake (if standalone) and NASM must be added to the ``PATH`` environment variable. +Visual Studio is found automatically with ``vswhere.exe``. + +Build configuration +------------------- + +The following environment variables, if set, will override the default +behaviour of ``build_prepare.py``: + +* ``PYTHON`` + ``EXECUTABLE`` point to the target version of Python. + If ``PYTHON`` is unset, the version of Python used to run + ``build_prepare.py`` will be used. If only ``PYTHON`` is set, + ``EXECUTABLE`` defaults to ``python.exe``. +* ``ARCHITECTURE`` is used to select a ``x86`` or ``x64`` build. By default, + uses same architecture as the version of Python used to run ``build_prepare.py``. + is used. +* ``PILLOW_BUILD`` can be used to override the ``winbuild\build`` directory + path, used to store generated build scripts and compiled libraries. + **Warning:** This directory is wiped when ``build_prepare.py`` is run. +* ``PILLOW_DEPS`` points to the directory used to store downloaded + dependencies. By default ``winbuild\depends`` is used. + +``build_prepare.py`` also supports the following command line parameters: + +* ``-v`` will print generated scripts. +* ``--no-imagequant`` will skip GPL-licensed ``libimagequant`` optional dependency +* ``--no-raqm`` will skip optional dependency Raqm (which itself depends on + LGPL-licensed ``fribidi``). +* ``--python=`` and ``--executable=`` override ``PYTHON`` and ``EXECUTABLE``. +* ``--architecture=`` overrides ``ARCHITECTURE``. +* ``--dir=`` and ``--depends=`` override ``PILLOW_BUILD`` + and ``PILLOW_DEPS``. Dependencies ------------ -The script 'build_dep.py' downloads and builds the dependencies. Open -a command window, change directory into ``winbuild`` and run ``python -build_dep.py``. +Dependencies will be automatically downloaded by ``build_prepare.py``. +By default, downloaded dependencies are stored in ``winbuild\depends``; +set the ``PILLOW_DEPS`` environment variable to override this location. -This will download libjpeg, libtiff, libz, and freetype. It will then -compile 32 and 64-bit versions of the libraries, with both versions of -the compilers. - -UNDONE -- lcms fails. -UNDONE -- webp, jpeg2k not recognized +To build all dependencies, run ``winbuild\build\build_dep_all.cmd``, +or run the individual scripts to build each dependency separately. Building Pillow --------------- -Once the dependencies are built, run ``python build.py --clean`` to -build and install Pillow in virtualenvs for each Python -build. ``build.py --wheel`` will build wheels instead of -installing into virtualenvs. +Once the dependencies are built, run +``winbuild\build\build_pillow.cmd install`` to build and install +Pillow for the selected version of Python. +``winbuild\build\build_pillow.cmd bdist_wheel`` will build wheels +instead of installing Pillow. -UNDONE -- suppressed output, what about failures. +You can also use ``winbuild\build\build_pillow.cmd --inplace develop`` to build +and install Pillow in develop mode (instead of ``python3 -m pip install --editable``). Testing Pillow -------------- -Build and install Pillow, then run ``python test.py`` from the -``winbuild`` directory. +Some binary dependencies (e.g. ``libraqm.dll``) will be stored in the +``winbuild\build\bin`` directory; this directory should be added to ``PATH`` +before running tests. +Build and install Pillow, then run ``python -m pytest Tests`` +from the root Pillow directory. + +Example +------- + +The following is a simplified version of the script used on AppVeyor: + +.. code-block:: + + set PYTHON=C:\Python38\bin + cd /D C:\Pillow\winbuild + C:\Python37\bin\python.exe build_prepare.py -v --depends=C:\pillow-depends + build\build_dep_all.cmd + build\build_pillow.cmd install + cd .. + path C:\Pillow\winbuild\build\bin;%PATH% + %PYTHON%\python.exe selftest.py + %PYTHON%\python.exe -m pytest -vx --cov PIL --cov Tests --cov-report term --cov-report xml Tests + build\build_pillow.cmd bdist_wheel diff --git a/winbuild/build_dep.py b/winbuild/build_dep.py deleted file mode 100644 index 778570139..000000000 --- a/winbuild/build_dep.py +++ /dev/null @@ -1,328 +0,0 @@ -import os - -from build import vc_setup -from config import all_compilers, bit_from_env, compiler_from_env, compilers, libs -from fetch import fetch -from untar import untar -from unzip import unzip - - -def _relpath(*args): - return os.path.join(os.getcwd(), *args) - - -build_dir = _relpath("build") -inc_dir = _relpath("depends") - - -def check_sig(filename, signame): - # UNDONE -- need gpg - return filename - - -def mkdirs(): - try: - os.mkdir(build_dir) - except OSError: - pass - try: - os.mkdir(inc_dir) - except OSError: - pass - for compiler in all_compilers(): - try: - os.mkdir(os.path.join(inc_dir, compiler["inc_dir"])) - except OSError: - pass - - -def extract(src, dest): - if ".zip" in src: - return unzip(src, dest) - if ".tar.gz" in src or ".tgz" in src: - return untar(src, dest) - - -def extract_libs(): - for name, lib in libs.items(): - filename = fetch(lib["url"]) - if name == "openjpeg": - for compiler in all_compilers(): - if not os.path.exists( - os.path.join(build_dir, lib["dir"] + compiler["inc_dir"]) - ): - extract(filename, build_dir) - os.rename( - os.path.join(build_dir, lib["dir"]), - os.path.join(build_dir, lib["dir"] + compiler["inc_dir"]), - ) - else: - extract(filename, build_dir) - - -def extract_openjpeg(compiler): - return ( - r""" -rem build openjpeg -setlocal -cd %%BUILD%% -mkdir %%INCLIB%%\openjpeg-2.0 -copy /Y /B openjpeg-2.0.0-win32-x86\include\openjpeg-2.0 %%INCLIB%%\openjpeg-2.0 -copy /Y /B openjpeg-2.0.0-win32-x86\bin\ %%INCLIB%% -copy /Y /B openjpeg-2.0.0-win32-x86\lib\ %%INCLIB%% -endlocal -""" - % compiler - ) - - -def cp_tk(ver_85, ver_86): - versions = {"ver_85": ver_85, "ver_86": ver_86} - return ( - r""" -mkdir %%INCLIB%%\tcl85\include\X11 -copy /Y /B %%BUILD%%\tcl%(ver_85)s\generic\*.h %%INCLIB%%\tcl85\include\ -copy /Y /B %%BUILD%%\tk%(ver_85)s\generic\*.h %%INCLIB%%\tcl85\include\ -copy /Y /B %%BUILD%%\tk%(ver_85)s\xlib\X11\* %%INCLIB%%\tcl85\include\X11\ - -mkdir %%INCLIB%%\tcl86\include\X11 -copy /Y /B %%BUILD%%\tcl%(ver_86)s\generic\*.h %%INCLIB%%\tcl86\include\ -copy /Y /B %%BUILD%%\tk%(ver_86)s\generic\*.h %%INCLIB%%\tcl86\include\ -copy /Y /B %%BUILD%%\tk%(ver_86)s\xlib\X11\* %%INCLIB%%\tcl86\include\X11\ -""" - % versions - ) - - -def header(): - return r"""setlocal -set MSBUILD=C:\Windows\Microsoft.NET\Framework64\v4.0.30319\MSBuild.exe -set CMAKE="cmake.exe" -set INCLIB=%~dp0\depends -set BUILD=%~dp0\build -""" + "\n".join( - r"set {}=%BUILD%\{}".format(k.upper(), v["dir"]) - for (k, v) in libs.items() - if v["dir"] - ) - - -def setup_compiler(compiler): - return ( - r"""setlocal EnableDelayedExpansion -call "%%ProgramFiles%%\Microsoft SDKs\Windows\%(env_version)s\Bin\SetEnv.Cmd" /Release %(env_flags)s -echo on -set INCLIB=%%INCLIB%%\%(inc_dir)s -""" # noqa: E501 - % compiler - ) - - -def end_compiler(): - return """ -endlocal -""" - - -def nmake_openjpeg(compiler, bit): - if compiler["env_version"] == "v7.0": - return "" - - atts = {"op_ver": "2.3.1"} - atts.update(compiler) - return ( - r""" -rem build openjpeg -setlocal -""" - + vc_setup(compiler, bit) - + r""" -cd /D %%OPENJPEG%%%(inc_dir)s - -%%CMAKE%% -DBUILD_THIRDPARTY:BOOL=OFF -DBUILD_SHARED_LIBS:BOOL=OFF -DCMAKE_BUILD_TYPE=Release -G "NMake Makefiles" . -nmake -nologo -f Makefile clean -nmake -nologo -f Makefile -copy /Y /B bin\* %%INCLIB%% -mkdir %%INCLIB%%\openjpeg-%(op_ver)s -copy /Y /B src\lib\openjp2\*.h %%INCLIB%%\openjpeg-%(op_ver)s -endlocal -""" # noqa: E501 - % atts - ) - - -def nmake_libs(compiler, bit): - # undone -- pre, makes, headers, libs - script = ( - r""" -rem Build libjpeg -setlocal -""" - + vc_setup(compiler, bit) - + r""" -cd /D %%JPEG%% -nmake -nologo -f makefile.vc setup-vc6 -nmake -nologo -f makefile.vc clean -nmake -nologo -f makefile.vc nodebug=1 libjpeg.lib -copy /Y /B *.dll %%INCLIB%% -copy /Y /B *.lib %%INCLIB%% -copy /Y /B j*.h %%INCLIB%% -endlocal - -rem Build zlib -setlocal -cd /D %%ZLIB%% -nmake -nologo -f win32\Makefile.msc clean -nmake -nologo -f win32\Makefile.msc zlib.lib -copy /Y /B *.dll %%INCLIB%% -copy /Y /B *.lib %%INCLIB%% -copy /Y /B zlib.lib %%INCLIB%%\z.lib -copy /Y /B zlib.h %%INCLIB%% -copy /Y /B zconf.h %%INCLIB%% -endlocal - -rem Build webp -setlocal -""" - + vc_setup(compiler, bit) - + r""" -cd /D %%WEBP%% -rd /S /Q %%WEBP%%\output\release-static -nmake -nologo -f Makefile.vc CFG=release-static RTLIBCFG=static OBJDIR=output all -copy /Y /B output\release-static\%(webp_platform)s\lib\* %%INCLIB%% -mkdir %%INCLIB%%\webp -copy /Y /B src\webp\*.h %%INCLIB%%\\webp -endlocal - -rem Build libtiff -setlocal -""" - + vc_setup(compiler, bit) - + r""" -rem do after building jpeg and zlib -copy %%~dp0\tiff.opt %%TIFF%%\nmake.opt - -cd /D %%TIFF%% -nmake -nologo -f makefile.vc clean -nmake -nologo -f makefile.vc lib -copy /Y /B libtiff\*.dll %%INCLIB%% -copy /Y /B libtiff\*.lib %%INCLIB%% -copy /Y /B libtiff\tiff*.h %%INCLIB%% -endlocal -""" - ) - return script % compiler - - -def msbuild_freetype(compiler, bit): - script = r""" -rem Build freetype -setlocal -rd /S /Q %%FREETYPE%%\objs -set DefaultPlatformToolset=v100 -""" - properties = r"""/p:Configuration="Release" /p:Platform=%(platform)s""" - if bit == 64: - script += ( - r"copy /Y /B " - r'"C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Lib\x64\*.Lib" ' - r"%%FREETYPE%%\builds\windows\vc2010" - ) - properties += r" /p:_IsNativeEnvironment=false" - script += ( - r""" -%%MSBUILD%% %%FREETYPE%%\builds\windows\vc2010\freetype.sln /t:Clean;Build """ - + properties - + r""" /m -xcopy /Y /E /Q %%FREETYPE%%\include %%INCLIB%% -""" - ) - freetypeReleaseDir = r"%%FREETYPE%%\objs\%(platform)s\Release" - script += ( - r""" -copy /Y /B """ - + freetypeReleaseDir - + r"""\freetype.lib %%INCLIB%%\freetype.lib -copy /Y /B """ - + freetypeReleaseDir - + r"""\freetype.dll %%INCLIB%%\..\freetype.dll -endlocal -""" - ) - return script % compiler - - -def build_lcms2(compiler): - if compiler["env_version"] == "v7.1": - return build_lcms_71(compiler) - return build_lcms_70(compiler) - - -def build_lcms_70(compiler): - """Link error here on x64""" - if compiler["platform"] == "x64": - return "" - - """Build LCMS on VC2008. This version is only 32bit/Win32""" - return ( - r""" -rem Build lcms2 -setlocal -set LCMS=%%LCMS-2.7%% -rd /S /Q %%LCMS%%\Lib -rd /S /Q %%LCMS%%\Projects\VC%(vc_version)s\Release -%%MSBUILD%% %%LCMS%%\Projects\VC%(vc_version)s\lcms2.sln /t:Clean /p:Configuration="Release" /p:Platform=Win32 /m -%%MSBUILD%% %%LCMS%%\Projects\VC%(vc_version)s\lcms2.sln /t:lcms2_static /p:Configuration="Release" /p:Platform=Win32 /p:PlatformToolset=v90 /m -xcopy /Y /E /Q %%LCMS%%\include %%INCLIB%% -copy /Y /B %%LCMS%%\Lib\MS\*.lib %%INCLIB%% -endlocal -""" # noqa: E501 - % compiler - ) - - -def build_lcms_71(compiler): - return ( - r""" -rem Build lcms2 -setlocal -set LCMS=%%LCMS-2.8%% -rd /S /Q %%LCMS%%\Lib -rd /S /Q %%LCMS%%\Projects\VC%(vc_version)s\Release -powershell -Command "(gc Projects\VC2015\lcms2_static\lcms2_static.vcxproj) -replace 'MultiThreadedDLL', 'MultiThreaded' | Out-File -encoding ASCII Projects\VC2015\lcms2_static\lcms2_static.vcxproj" -%%MSBUILD%% %%LCMS%%\Projects\VC%(vc_version)s\lcms2.sln /t:Clean /p:Configuration="Release" /p:Platform=%(platform)s /m -%%MSBUILD%% %%LCMS%%\Projects\VC%(vc_version)s\lcms2.sln /t:lcms2_static /p:Configuration="Release" /p:Platform=%(platform)s /m -xcopy /Y /E /Q %%LCMS%%\include %%INCLIB%% -copy /Y /B %%LCMS%%\Lib\MS\*.lib %%INCLIB%% -endlocal -""" # noqa: E501 - % compiler - ) - - -def add_compiler(compiler, bit): - script.append(setup_compiler(compiler)) - script.append(nmake_libs(compiler, bit)) - - # script.append(extract_openjpeg(compiler)) - - script.append(msbuild_freetype(compiler, bit)) - script.append(build_lcms2(compiler)) - script.append(nmake_openjpeg(compiler, bit)) - script.append(end_compiler()) - - -mkdirs() -extract_libs() -script = [header(), cp_tk(libs["tk-8.5"]["version"], libs["tk-8.6"]["version"])] - - -if "PYTHON" in os.environ: - add_compiler(compiler_from_env(), bit_from_env()) -else: - # for compiler in all_compilers(): - # add_compiler(compiler) - add_compiler(compilers[7.0][2010][32], 32) - -with open("build_deps.cmd", "w") as f: - f.write("\n".join(script)) diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py new file mode 100644 index 000000000..2531d5504 --- /dev/null +++ b/winbuild/build_prepare.py @@ -0,0 +1,596 @@ +import os +import shutil +import struct +import subprocess +import sys + + +def cmd_cd(path): + return f"cd /D {path}" + + +def cmd_set(name, value): + return f"set {name}={value}" + + +def cmd_append(name, value): + op = "path " if name == "PATH" else f"set {name}=" + return op + f"%{name}%;{value}" + + +def cmd_copy(src, tgt): + return f'copy /Y /B "{src}" "{tgt}"' + + +def cmd_xcopy(src, tgt): + return f'xcopy /Y /E "{src}" "{tgt}"' + + +def cmd_mkdir(path): + return f'mkdir "{path}"' + + +def cmd_rmdir(path): + return f'rmdir /S /Q "{path}"' + + +def cmd_nmake(makefile=None, target="", params=None): + if params is None: + params = "" + elif isinstance(params, list) or isinstance(params, tuple): + params = " ".join(params) + else: + params = str(params) + + return " ".join( + [ + "{nmake}", + "-nologo", + f'-f "{makefile}"' if makefile is not None else "", + f"{params}", + f'"{target}"', + ] + ) + + +def cmd_cmake(params=None, file="."): + if params is None: + params = "" + elif isinstance(params, list) or isinstance(params, tuple): + params = " ".join(params) + else: + params = str(params) + return " ".join( + [ + "{cmake}", + "-DCMAKE_VERBOSE_MAKEFILE=ON", + "-DCMAKE_RULE_MESSAGES:BOOL=OFF", + "-DCMAKE_BUILD_TYPE=Release", + f"{params}", + '-G "NMake Makefiles"', + f'"{file}"', + ] + ) + + +def cmd_msbuild( + file, configuration="Release", target="Build", platform="{msbuild_arch}" +): + return " ".join( + [ + "{msbuild}", + f"{file}", + f'/t:"{target}"', + f'/p:Configuration="{configuration}"', + f"/p:Platform={platform}", + "/m", + ] + ) + + +SF_MIRROR = "http://iweb.dl.sourceforge.net" + +architectures = { + "x86": {"vcvars_arch": "x86", "msbuild_arch": "Win32"}, + "x64": {"vcvars_arch": "x86_amd64", "msbuild_arch": "x64"}, +} + +header = [ + cmd_set("INCLUDE", "{inc_dir}"), + cmd_set("INCLIB", "{lib_dir}"), + cmd_set("LIB", "{lib_dir}"), + cmd_append("PATH", "{bin_dir}"), +] + +# dependencies, listed in order of compilation +deps = { + "libjpeg": { + "url": SF_MIRROR + "/project/libjpeg-turbo/2.0.6/libjpeg-turbo-2.0.6.tar.gz", + "filename": "libjpeg-turbo-2.0.6.tar.gz", + "dir": "libjpeg-turbo-2.0.6", + "build": [ + cmd_cmake( + [ + "-DENABLE_SHARED:BOOL=FALSE", + "-DWITH_JPEG8:BOOL=TRUE", + "-DWITH_CRT_DLL:BOOL=TRUE", + ] + ), + cmd_nmake(target="clean"), + cmd_nmake(target="jpeg-static"), + cmd_copy("jpeg-static.lib", "libjpeg.lib"), + cmd_nmake(target="cjpeg-static"), + cmd_copy("cjpeg-static.exe", "cjpeg.exe"), + cmd_nmake(target="djpeg-static"), + cmd_copy("djpeg-static.exe", "djpeg.exe"), + ], + "headers": ["j*.h"], + "libs": ["libjpeg.lib"], + "bins": ["cjpeg.exe", "djpeg.exe"], + }, + "zlib": { + "url": "http://zlib.net/zlib1211.zip", + "filename": "zlib1211.zip", + "dir": "zlib-1.2.11", + "build": [ + cmd_nmake(r"win32\Makefile.msc", "clean"), + cmd_nmake(r"win32\Makefile.msc", "zlib.lib"), + cmd_copy("zlib.lib", "z.lib"), + ], + "headers": [r"z*.h"], + "libs": [r"*.lib"], + }, + "libtiff": { + "url": "https://download.osgeo.org/libtiff/tiff-4.2.0.tar.gz", + "filename": "tiff-4.2.0.tar.gz", + "dir": "tiff-4.2.0", + "build": [ + cmd_copy(r"{winbuild_dir}\tiff.opt", "nmake.opt"), + cmd_nmake("makefile.vc", "clean"), + cmd_nmake("makefile.vc", "lib"), + ], + "headers": [r"libtiff\tiff*.h"], + "libs": [r"libtiff\*.lib"], + # "bins": [r"libtiff\*.dll"], + }, + "libwebp": { + "url": "http://downloads.webmproject.org/releases/webp/libwebp-1.1.0.tar.gz", + "filename": "libwebp-1.1.0.tar.gz", + "dir": "libwebp-1.1.0", + "build": [ + cmd_rmdir(r"output\release-static"), # clean + cmd_nmake( + "Makefile.vc", + "all", + ["CFG=release-static", "OBJDIR=output", "ARCH={architecture}"], + ), + cmd_mkdir(r"{inc_dir}\webp"), + cmd_copy(r"src\webp\*.h", r"{inc_dir}\webp"), + ], + "libs": [r"output\release-static\{architecture}\lib\*.lib"], + }, + "libpng": { + "url": SF_MIRROR + "/project/libpng/libpng16/1.6.37/lpng1637.zip", + "filename": "lpng1637.zip", + "dir": "lpng1637", + "build": [ + # lint: do not inline + cmd_cmake(("-DPNG_SHARED:BOOL=OFF", "-DPNG_TESTS:BOOL=OFF")), + cmd_nmake(target="clean"), + cmd_nmake(), + cmd_copy("libpng16_static.lib", "libpng16.lib"), + ], + "headers": [r"png*.h"], + "libs": [r"libpng16.lib"], + }, + "freetype": { + "url": "https://download.savannah.gnu.org/releases/freetype/freetype-2.10.4.tar.gz", # noqa: E501 + "filename": "freetype-2.10.4.tar.gz", + "dir": "freetype-2.10.4", + "patch": { + r"builds\windows\vc2010\freetype.vcxproj": { + # freetype setting is /MD for .dll and /MT for .lib, we need /MD + "MultiThreaded": "MultiThreadedDLL", # noqa: E501 + # freetype doesn't specify SDK version, MSBuild may guess incorrectly + '': '\n $(WindowsSDKVersion)', # noqa: E501 + }, + r"builds\windows\vc2010\freetype.user.props": { + "": "FT_CONFIG_OPTION_SYSTEM_ZLIB;FT_CONFIG_OPTION_USE_PNG;FT_CONFIG_OPTION_USE_HARFBUZZ", # noqa: E501 + "": r"{dir_harfbuzz}\src;{inc_dir}", # noqa: E501 + "": "{lib_dir}", # noqa: E501 + "": "zlib.lib;libpng16.lib", # noqa: E501 + }, + r"src/autofit/afshaper.c": { + # link against harfbuzz.lib once it becomes available + "#ifdef FT_CONFIG_OPTION_USE_HARFBUZZ": '#ifdef FT_CONFIG_OPTION_USE_HARFBUZZ\n#pragma comment(lib, "harfbuzz.lib")', # noqa: E501 + }, + }, + "build": [ + cmd_rmdir("objs"), + cmd_msbuild( + r"builds\windows\vc2010\freetype.sln", "Release Static", "Clean" + ), + cmd_msbuild( + r"builds\windows\vc2010\freetype.sln", "Release Static", "Build" + ), + cmd_xcopy("include", "{inc_dir}"), + ], + "libs": [r"objs\{msbuild_arch}\Release Static\freetype.lib"], + # "bins": [r"objs\{msbuild_arch}\Release\freetype.dll"], + }, + "lcms2": { + "url": SF_MIRROR + "/project/lcms/lcms/2.11/lcms2-2.11.tar.gz", + "filename": "lcms2-2.11.tar.gz", + "dir": "lcms2-2.11", + "patch": { + r"Projects\VC2017\lcms2_static\lcms2_static.vcxproj": { + # default is /MD for x86 and /MT for x64, we need /MD always + "MultiThreaded": "MultiThreadedDLL", # noqa: E501 + # retarget to default toolset (selected by vcvarsall.bat) + "v141": "$(DefaultPlatformToolset)", # noqa: E501 + # retarget to latest (selected by vcvarsall.bat) + "10.0.17134.0": "$(WindowsSDKVersion)", # noqa: E501 + } + }, + "build": [ + cmd_rmdir("Lib"), + cmd_rmdir(r"Projects\VC2017\Release"), + cmd_msbuild(r"Projects\VC2017\lcms2.sln", "Release", "Clean"), + cmd_msbuild(r"Projects\VC2017\lcms2.sln", "Release", "lcms2_static"), + cmd_xcopy("include", "{inc_dir}"), + ], + "libs": [r"Lib\MS\*.lib"], + }, + "openjpeg": { + "url": "https://github.com/uclouvain/openjpeg/archive/v2.4.0.tar.gz", + "filename": "openjpeg-2.4.0.tar.gz", + "dir": "openjpeg-2.4.0", + "build": [ + cmd_cmake(("-DBUILD_THIRDPARTY:BOOL=OFF", "-DBUILD_SHARED_LIBS:BOOL=OFF")), + cmd_nmake(target="clean"), + cmd_nmake(target="openjp2"), + cmd_mkdir(r"{inc_dir}\openjpeg-2.4.0"), + cmd_copy(r"src\lib\openjp2\*.h", r"{inc_dir}\openjpeg-2.4.0"), + ], + "libs": [r"bin\*.lib"], + }, + "libimagequant": { + # e5d454b: Merge tag '2.12.6' into msvc + "url": "https://github.com/ImageOptim/libimagequant/archive/e5d454bc7f5eb63ee50c84a83a7fa5ac94f68ec4.zip", # noqa: E501 + "filename": "libimagequant-e5d454bc7f5eb63ee50c84a83a7fa5ac94f68ec4.zip", + "dir": "libimagequant-e5d454bc7f5eb63ee50c84a83a7fa5ac94f68ec4", + "patch": { + "CMakeLists.txt": { + "add_library": "add_compile_options(-openmp-)\r\nadd_library", + " SHARED": " STATIC", + } + }, + "build": [ + # lint: do not inline + cmd_cmake(), + cmd_nmake(target="clean"), + cmd_nmake(), + ], + "headers": [r"*.h"], + "libs": [r"*.lib"], + }, + "harfbuzz": { + "url": "https://github.com/harfbuzz/harfbuzz/archive/2.7.4.zip", + "filename": "harfbuzz-2.7.4.zip", + "dir": "harfbuzz-2.7.4", + "build": [ + cmd_cmake("-DHB_HAVE_FREETYPE:BOOL=TRUE"), + cmd_nmake(target="clean"), + cmd_nmake(target="harfbuzz"), + ], + "headers": [r"src\*.h"], + "libs": [r"*.lib"], + }, + "fribidi": { + "url": "https://github.com/fribidi/fribidi/archive/v1.0.10.zip", + "filename": "fribidi-1.0.10.zip", + "dir": "fribidi-1.0.10", + "build": [ + cmd_copy(r"{winbuild_dir}\fribidi.cmake", r"CMakeLists.txt"), + cmd_cmake(), + cmd_nmake(target="clean"), + cmd_nmake(target="fribidi"), + ], + "headers": [r"lib\*.h"], + "libs": [r"*.lib"], + }, + "libraqm": { + "url": "https://github.com/HOST-Oman/libraqm/archive/v0.7.1.zip", + "filename": "libraqm-0.7.1.zip", + "dir": "libraqm-0.7.1", + "build": [ + cmd_copy(r"{winbuild_dir}\raqm.cmake", r"CMakeLists.txt"), + cmd_cmake(), + cmd_nmake(target="clean"), + cmd_nmake(target="libraqm"), + ], + "headers": [r"src\*.h"], + "bins": [r"libraqm.dll"], + }, +} + + +# based on distutils._msvccompiler from CPython 3.7.4 +def find_msvs(): + root = os.environ.get("ProgramFiles(x86)") or os.environ.get("ProgramFiles") + if not root: + print("Program Files not found") + return None + + try: + vspath = ( + subprocess.check_output( + [ + os.path.join( + root, "Microsoft Visual Studio", "Installer", "vswhere.exe" + ), + "-latest", + "-prerelease", + "-requires", + "Microsoft.VisualStudio.Component.VC.Tools.x86.x64", + "-property", + "installationPath", + "-products", + "*", + ] + ) + .decode(encoding="mbcs") + .strip() + ) + except (subprocess.CalledProcessError, OSError, UnicodeDecodeError): + print("vswhere not found") + return None + + if not os.path.isdir(os.path.join(vspath, "VC", "Auxiliary", "Build")): + print("Visual Studio seems to be missing C compiler") + return None + + vs = { + "header": [], + # nmake selected by vcvarsall + "nmake": "nmake.exe", + "vs_dir": vspath, + } + + # vs2017 + msbuild = os.path.join(vspath, "MSBuild", "15.0", "Bin", "MSBuild.exe") + if os.path.isfile(msbuild): + vs["msbuild"] = f'"{msbuild}"' + else: + # vs2019 + msbuild = os.path.join(vspath, "MSBuild", "Current", "Bin", "MSBuild.exe") + if os.path.isfile(msbuild): + vs["msbuild"] = f'"{msbuild}"' + else: + print("Visual Studio MSBuild not found") + return None + + vcvarsall = os.path.join(vspath, "VC", "Auxiliary", "Build", "vcvarsall.bat") + if not os.path.isfile(vcvarsall): + print("Visual Studio vcvarsall not found") + return None + vs["header"].append(f'call "{vcvarsall}" {{vcvars_arch}}') + + return vs + + +def extract_dep(url, filename): + import tarfile + import urllib.request + import zipfile + + file = os.path.join(depends_dir, filename) + if not os.path.exists(file): + ex = None + for i in range(3): + try: + print("Fetching %s (attempt %d)..." % (url, i + 1)) + content = urllib.request.urlopen(url).read() + with open(file, "wb") as f: + f.write(content) + break + except urllib.error.URLError as e: + ex = e + else: + raise RuntimeError(ex) + + print("Extracting " + filename) + if filename.endswith(".zip"): + with zipfile.ZipFile(file) as zf: + zf.extractall(sources_dir) + elif filename.endswith(".tar.gz") or filename.endswith(".tgz"): + with tarfile.open(file, "r:gz") as tgz: + tgz.extractall(sources_dir) + else: + raise RuntimeError("Unknown archive type: " + filename) + + +def write_script(name, lines): + name = os.path.join(build_dir, name) + lines = [line.format(**prefs) for line in lines] + print("Writing " + name) + with open(name, "w") as f: + f.write("\n\r".join(lines)) + if verbose: + for line in lines: + print(" " + line) + + +def get_footer(dep): + lines = [] + for out in dep.get("headers", []): + lines.append(cmd_copy(out, "{inc_dir}")) + for out in dep.get("libs", []): + lines.append(cmd_copy(out, "{lib_dir}")) + for out in dep.get("bins", []): + lines.append(cmd_copy(out, "{bin_dir}")) + return lines + + +def build_dep(name): + dep = deps[name] + dir = dep["dir"] + file = f"build_dep_{name}.cmd" + + extract_dep(dep["url"], dep["filename"]) + + for patch_file, patch_list in dep.get("patch", {}).items(): + patch_file = os.path.join(sources_dir, dir, patch_file.format(**prefs)) + with open(patch_file) as f: + text = f.read() + for patch_from, patch_to in patch_list.items(): + patch_from = patch_from.format(**prefs) + patch_to = patch_to.format(**prefs) + assert patch_from in text + text = text.replace(patch_from, patch_to) + with open(patch_file, "w") as f: + f.write(text) + + banner = f"Building {name} ({dir})" + lines = [ + "@echo " + ("=" * 70), + f"@echo ==== {banner:<60} ====", + "@echo " + ("=" * 70), + "cd /D %s" % os.path.join(sources_dir, dir), + *prefs["header"], + *dep.get("build", []), + *get_footer(dep), + ] + + write_script(file, lines) + return file + + +def build_dep_all(): + lines = ["@echo on"] + for dep_name in deps: + if dep_name in disabled: + continue + script = build_dep(dep_name) + lines.append(fr'cmd.exe /c "{{build_dir}}\{script}"') + lines.append("if errorlevel 1 echo Build failed! && exit /B 1") + lines.append("@echo All Pillow dependencies built successfully!") + write_script("build_dep_all.cmd", lines) + + +def build_pillow(): + lines = [ + "@echo ---- Building Pillow (build_ext %*) ----", + cmd_cd("{pillow_dir}"), + *prefs["header"], + cmd_set("DISTUTILS_USE_SDK", "1"), # use same compiler to build Pillow + cmd_set("MSSdk", "1"), # for PyPy3.6 + cmd_set("py_vcruntime_redist", "true"), # use /MD, not /MT + r'"{python_dir}\{python_exe}" setup.py build_ext %*', + ] + + write_script("build_pillow.cmd", lines) + + +if __name__ == "__main__": + # winbuild directory + winbuild_dir = os.path.dirname(os.path.realpath(__file__)) + + verbose = False + disabled = [] + depends_dir = os.environ.get("PILLOW_DEPS", os.path.join(winbuild_dir, "depends")) + python_dir = os.environ.get("PYTHON") + python_exe = os.environ.get("EXECUTABLE", "python.exe") + architecture = os.environ.get( + "ARCHITECTURE", "x86" if struct.calcsize("P") == 4 else "x64" + ) + build_dir = os.environ.get("PILLOW_BUILD", os.path.join(winbuild_dir, "build")) + sources_dir = "" + for arg in sys.argv[1:]: + if arg == "-v": + verbose = True + elif arg == "--no-imagequant": + disabled += ["libimagequant"] + elif arg == "--no-raqm": + disabled += ["fribidi", "libraqm"] + elif arg.startswith("--depends="): + depends_dir = arg[10:] + elif arg.startswith("--python="): + python_dir = arg[9:] + elif arg.startswith("--executable="): + python_exe = arg[13:] + elif arg.startswith("--architecture="): + architecture = arg[15:] + elif arg.startswith("--dir="): + build_dir = arg[6:] + elif arg == "--srcdir": + sources_dir = os.path.sep + "src" + else: + raise ValueError("Unknown parameter: " + arg) + + # dependency cache directory + os.makedirs(depends_dir, exist_ok=True) + print("Caching dependencies in:", depends_dir) + + if python_dir is None: + python_dir = os.path.dirname(os.path.realpath(sys.executable)) + python_exe = os.path.basename(sys.executable) + print("Target Python:", os.path.join(python_dir, python_exe)) + + arch_prefs = architectures[architecture] + print("Target Architecture:", architecture) + + msvs = find_msvs() + if msvs is None: + raise RuntimeError( + "Visual Studio not found. Please install Visual Studio 2017 or newer." + ) + print("Found Visual Studio at:", msvs["vs_dir"]) + + print("Using output directory:", build_dir) + + # build directory for *.h files + inc_dir = os.path.join(build_dir, "inc") + # build directory for *.lib files + lib_dir = os.path.join(build_dir, "lib") + # build directory for *.bin files + bin_dir = os.path.join(build_dir, "bin") + # directory for storing project files + sources_dir = build_dir + sources_dir + + shutil.rmtree(build_dir, ignore_errors=True) + os.makedirs(build_dir, exist_ok=False) + for path in [inc_dir, lib_dir, bin_dir, sources_dir]: + os.makedirs(path, exist_ok=True) + + prefs = { + # Python paths / preferences + "python_dir": python_dir, + "python_exe": python_exe, + "architecture": architecture, + **arch_prefs, + # Pillow paths + "pillow_dir": os.path.realpath(os.path.join(winbuild_dir, "..")), + "winbuild_dir": winbuild_dir, + # Build paths + "build_dir": build_dir, + "inc_dir": inc_dir, + "lib_dir": lib_dir, + "bin_dir": bin_dir, + "src_dir": sources_dir, + # Compilers / Tools + **msvs, + "cmake": "cmake.exe", # TODO find CMAKE automatically + # TODO find NASM automatically + # script header + "header": sum([header, msvs["header"], ["@echo on"]], []), + } + + for k, v in deps.items(): + prefs[f"dir_{k}"] = os.path.join(sources_dir, v["dir"]) + + print() + + write_script(".gitignore", ["*"]) + build_dep_all() + build_pillow() diff --git a/winbuild/config.py b/winbuild/config.py deleted file mode 100644 index f9b23b4ff..000000000 --- a/winbuild/config.py +++ /dev/null @@ -1,199 +0,0 @@ -import os - -SF_MIRROR = "https://iweb.dl.sourceforge.net" - -pythons = { - "pypy3": {"compiler": 7.1, "vc": 2015}, - # for AppVeyor - "35": {"compiler": 7.1, "vc": 2015}, - "36": {"compiler": 7.1, "vc": 2015}, - "37": {"compiler": 7.1, "vc": 2015}, - "38": {"compiler": 7.1, "vc": 2015}, - # for GitHub Actions - "3.5": {"compiler": 7.1, "vc": 2015}, - "3.6": {"compiler": 7.1, "vc": 2015}, - "3.7": {"compiler": 7.1, "vc": 2015}, - "3.8": {"compiler": 7.1, "vc": 2015}, -} - -VIRT_BASE = "c:/vp/" -X64_EXT = os.environ.get("X64_EXT", "x64") - -libs = { - # 'openjpeg': { - # 'filename': 'openjpeg-2.0.0-win32-x86.zip', - # 'version': '2.0' - # }, - "zlib": { - "url": "http://zlib.net/zlib1211.zip", - "filename": "zlib1211.zip", - "dir": "zlib-1.2.11", - }, - "jpeg": { - "url": "http://www.ijg.org/files/jpegsr9d.zip", - "filename": "jpegsr9d.zip", - "dir": "jpeg-9d", - }, - "tiff": { - "url": "ftp://download.osgeo.org/libtiff/tiff-4.1.0.tar.gz", - "filename": "tiff-4.1.0.tar.gz", - "dir": "tiff-4.1.0", - }, - "freetype": { - "url": "https://download.savannah.gnu.org/releases/freetype/freetype-2.10.1.tar.gz", # noqa: E501 - "filename": "freetype-2.10.1.tar.gz", - "dir": "freetype-2.10.1", - }, - "lcms-2.7": { - "url": SF_MIRROR + "/project/lcms/lcms/2.7/lcms2-2.7.zip", - "filename": "lcms2-2.7.zip", - "dir": "lcms2-2.7", - }, - "lcms-2.8": { - "url": SF_MIRROR + "/project/lcms/lcms/2.8/lcms2-2.8.zip", - "filename": "lcms2-2.8.zip", - "dir": "lcms2-2.8", - }, - "tcl-8.5": { - "url": SF_MIRROR + "/project/tcl/Tcl/8.5.19/tcl8519-src.zip", - "filename": "tcl8519-src.zip", - "dir": "", - }, - "tk-8.5": { - "url": SF_MIRROR + "/project/tcl/Tcl/8.5.19/tk8519-src.zip", - "filename": "tk8519-src.zip", - "dir": "", - "version": "8.5.19", - }, - "tcl-8.6": { - "url": SF_MIRROR + "/project/tcl/Tcl/8.6.10/tcl8610-src.zip", - "filename": "tcl8610-src.zip", - "dir": "", - }, - "tk-8.6": { - "url": SF_MIRROR + "/project/tcl/Tcl/8.6.10/tk8610-src.zip", - "filename": "tk8610-src.zip", - "dir": "", - "version": "8.6.10", - }, - "webp": { - "url": "http://downloads.webmproject.org/releases/webp/libwebp-1.1.0.tar.gz", - "filename": "libwebp-1.1.0.tar.gz", - "dir": "libwebp-1.1.0", - }, - "openjpeg": { - "url": "https://github.com/uclouvain/openjpeg/archive/v2.3.1.tar.gz", - "filename": "openjpeg-2.3.1.tar.gz", - "dir": "openjpeg-2.3.1", - }, - "jpeg-turbo": { - "url": SF_MIRROR + "/project/libjpeg-turbo/2.0.3/libjpeg-turbo-2.0.3.tar.gz", - "filename": "libjpeg-turbo-2.0.3.tar.gz", - "dir": "libjpeg-turbo-2.0.3", - }, - # e5d454b: Merge tag '2.12.6' into msvc - "imagequant": { - "url": "https://github.com/ImageOptim/libimagequant/archive/e5d454bc7f5eb63ee50c84a83a7fa5ac94f68ec4.zip", # noqa: E501 - "filename": "libimagequant-e5d454bc7f5eb63ee50c84a83a7fa5ac94f68ec4.zip", - "dir": "libimagequant-e5d454bc7f5eb63ee50c84a83a7fa5ac94f68ec4", - }, - "harfbuzz": { - "url": "https://github.com/harfbuzz/harfbuzz/archive/2.6.1.zip", - "filename": "harfbuzz-2.6.1.zip", - "dir": "harfbuzz-2.6.1", - }, - "fribidi": { - "url": "https://github.com/fribidi/fribidi/archive/v1.0.7.zip", - "filename": "fribidi-1.0.7.zip", - "dir": "fribidi-1.0.7", - }, - "libraqm": { - "url": "https://github.com/HOST-Oman/libraqm/archive/v0.7.0.zip", - "filename": "libraqm-0.7.0.zip", - "dir": "libraqm-0.7.0", - }, -} - -compilers = { - 7: { - 2010: { - 64: { - "env_version": "v7.0", - "vc_version": "2010", - "env_flags": "/x64 /xp", - "inc_dir": "msvcr90-x64", - "platform": "x64", - "webp_platform": "x64", - }, - 32: { - "env_version": "v7.0", - "vc_version": "2010", - "env_flags": "/x86 /xp", - "inc_dir": "msvcr90-x32", - "platform": "Win32", - "webp_platform": "x86", - }, - } - }, - 7.1: { - 2015: { - 64: { - "env_version": "v7.1", - "vc_version": "2015", - "env_flags": "/x64 /vista", - "inc_dir": "msvcr10-x64", - "platform": "x64", - "webp_platform": "x64", - }, - 32: { - "env_version": "v7.1", - "vc_version": "2015", - "env_flags": "/x86 /vista", - "inc_dir": "msvcr10-x32", - "platform": "Win32", - "webp_platform": "x86", - }, - } - }, -} - - -def pyversion_from_env(): - py = os.environ["PYTHON"] - - py_version = "35" - for k in pythons: - if k in py: - py_version = k - break - - if "64" in py: - py_version = "{}{}".format(py_version, X64_EXT) - - return py_version - - -def compiler_from_env(): - py = os.environ["PYTHON"] - - for k, v in pythons.items(): - if k in py: - py_info = v - break - - bit = bit_from_env() - return compilers[py_info["compiler"]][py_info["vc"]][bit] - - -def bit_from_env(): - py = os.environ["PYTHON"] - - return 64 if "64" in py else 32 - - -def all_compilers(): - all = [] - for vc_compilers in compilers.values(): - for bit_compilers in vc_compilers.values(): - all += bit_compilers.values() - return all diff --git a/winbuild/fetch.py b/winbuild/fetch.py deleted file mode 100644 index adc45429a..000000000 --- a/winbuild/fetch.py +++ /dev/null @@ -1,44 +0,0 @@ -import os -import sys -import urllib.parse -import urllib.request - -from config import libs - - -def fetch(url): - depends_filename = None - for lib in libs.values(): - if lib["url"] == url: - depends_filename = lib["filename"] - break - if depends_filename and os.path.exists(depends_filename): - return depends_filename - name = urllib.parse.urlsplit(url)[2].split("/")[-1] - - if not os.path.exists(name): - - def retrieve(request_url): - print("Fetching", request_url) - try: - return urllib.request.urlopen(request_url) - except urllib.error.URLError: - return urllib.request.urlopen(request_url) - - try: - r = retrieve(url) - except urllib.error.HTTPError: - if depends_filename: - r = retrieve( - "https://github.com/python-pillow/pillow-depends/raw/master/" - + depends_filename - ) - name = depends_filename - content = r.read() - with open(name, "wb") as fd: - fd.write(content) - return name - - -if __name__ == "__main__": - fetch(sys.argv[1]) diff --git a/winbuild/fribidi.cmake b/winbuild/fribidi.cmake index 247e79e4c..47ab2c329 100644 --- a/winbuild/fribidi.cmake +++ b/winbuild/fribidi.cmake @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.13) +cmake_minimum_required(VERSION 3.12) project(fribidi) @@ -99,4 +99,4 @@ add_library(fribidi STATIC ${FRIBIDI_SOURCES_GENERATED}) fribidi_definitions(fribidi) target_compile_definitions(fribidi - PUBLIC -DFRIBIDI_ENTRY=extern) + PUBLIC -DFRIBIDI_LIB_STATIC) diff --git a/winbuild/get_pythons.py b/winbuild/get_pythons.py deleted file mode 100644 index a853fc6f7..000000000 --- a/winbuild/get_pythons.py +++ /dev/null @@ -1,15 +0,0 @@ -import os - -from fetch import fetch - -if __name__ == "__main__": - for version in ["3.4.4"]: - for platform in ["", ".amd64"]: - for extension in ["", ".asc"]: - fetch( - "https://www.python.org/ftp/python/%s/python-%s%s.msi%s" - % (version, version, platform, extension) - ) - - # find pip, if it's not in the path! - os.system("pip install virtualenv") diff --git a/winbuild/lcms2_patch.ps1 b/winbuild/lcms2_patch.ps1 deleted file mode 100644 index 7fc48c034..000000000 --- a/winbuild/lcms2_patch.ps1 +++ /dev/null @@ -1,9 +0,0 @@ - -Get-ChildItem .\Projects\VC2015\ *.vcxproj -recurse | - Foreach-Object { - $c = ($_ | Get-Content) - $c = $c -replace 'MultiThreaded<','MultiThreadedDLL<' - $c = $c -replace '8.1','10' - $c = $c -replace 'v140','v142' - [IO.File]::WriteAllText($_.FullName, ($c -join "`r`n")) - } diff --git a/winbuild/raqm.cmake b/winbuild/raqm.cmake index 88eb7f284..82c9cdc70 100644 --- a/winbuild/raqm.cmake +++ b/winbuild/raqm.cmake @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.13) +cmake_minimum_required(VERSION 3.12) project(libraqm) @@ -7,7 +7,7 @@ find_library(fribidi NAMES fribidi) find_library(harfbuzz NAMES harfbuzz) find_library(freetype NAMES freetype) -add_definitions(-DFRIBIDI_ENTRY=extern) +add_definitions(-DFRIBIDI_LIB_STATIC) function(raqm_conf) diff --git a/winbuild/test.py b/winbuild/test.py deleted file mode 100755 index a05a20b18..000000000 --- a/winbuild/test.py +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env python3 - -import glob -import os -import subprocess -import sys - -from config import VIRT_BASE, X64_EXT, pythons - - -def test_one(params): - python, architecture = params - try: - print("Running: %s, %s" % params) - command = [ - r"{}\{}{}\Scripts\python.exe".format(VIRT_BASE, python, architecture), - "test-installed.py", - "--processes=-0", - "--process-timeout=30", - ] - command.extend(glob.glob("Tests/test*.py")) - proc = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE) - (trace, stderr) = proc.communicate() - status = proc.returncode - print("Done with {}, {} -- {}".format(python, architecture, status)) - return (python, architecture, status, trace) - except Exception as msg: - print("Error with {}, {}: {}".format(python, architecture, msg)) - return (python, architecture, -1, str(msg)) - - -if __name__ == "__main__": - - os.chdir("..") - matrix = [ - (python, architecture) for python in pythons for architecture in ("", X64_EXT) - ] - - results = map(test_one, matrix) - - for (python, architecture, status, trace) in results: - print("{}{}: {}".format(python, architecture, status and "ERR" or "PASS")) - - res = all(status for (python, architecture, status, trace) in results) - sys.exit(res) diff --git a/winbuild/tiff.opt b/winbuild/tiff.opt index 16acabc26..d82c51678 100644 --- a/winbuild/tiff.opt +++ b/winbuild/tiff.opt @@ -66,6 +66,10 @@ ZIP_SUPPORT = 1 ZLIB_INCLUDE = -I$(INCLIB) ZLIB_LIB = $(INCLIB)/zlib.lib +# Indicate if the compiler provides strtoll/strtoull (default 1) +# Users of MSVC++ 14.0 ("Visual Studio 2015") and later should set this to 1 +HAVE_STRTOLL = 1 + # # Uncomment and edit following lines to enable ISO JBIG support # diff --git a/winbuild/untar.py b/winbuild/untar.py deleted file mode 100644 index f2713b2f2..000000000 --- a/winbuild/untar.py +++ /dev/null @@ -1,11 +0,0 @@ -import sys -import tarfile - - -def untar(src, dest): - with tarfile.open(src, "r:gz") as tgz: - tgz.extractall(dest) - - -if __name__ == "__main__": - untar(sys.argv[1], sys.argv[2]) diff --git a/winbuild/unzip.py b/winbuild/unzip.py deleted file mode 100644 index eb17a2e63..000000000 --- a/winbuild/unzip.py +++ /dev/null @@ -1,11 +0,0 @@ -import sys -import zipfile - - -def unzip(src, dest): - with zipfile.ZipFile(src) as zf: - zf.extractall(dest) - - -if __name__ == "__main__": - unzip(sys.argv[1], sys.argv[2])