diff --git a/.appveyor.yml b/.appveyor.yml index f6ac309c49a..d525e4cfc07 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -6,81 +6,51 @@ 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:/Python310 + ARCHITECTURE: x86 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 + - PYTHON: C:/Python37-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 +- '%PYTHON%\%EXECUTABLE% --version' +- curl -fsSL -o pillow-depends.zip https://github.com/python-pillow/pillow-depends/archive/main.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 +- mv c:\pillow-depends-main c:\pillow-depends +- xcopy /S /Y c:\pillow-depends\test_images\* c:\pillow\tests\images +- 7z x ..\pillow-depends\nasm-2.15.05-win64.zip -oc:\ +- ..\pillow-depends\gs9550w32.exe /S +- path c:\nasm-2.15.05;C:\Program Files (x86)\gs\gs9.55.0\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 gs952.exe https://github.com/ArtifexSoftware/ghostpdl-downloads/releases/download/gs952/gs952w32.exe -- gs952.exe /S -- path %path%;C:\Program Files (x86)\gs\gs9.52\bin +- path C:\pillow\winbuild\build\bin;%PATH% 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" - $host.SetShouldExit(0) - } - else - { - & $env:PYTHON/$env:EXECUTABLE c:\pillow\winbuild\build.py + c:\pillow\winbuild\build\build_pillow.cmd install $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% -c "from PIL import Image"' - '%PYTHON%\%EXECUTABLE% -m pytest -vx --cov PIL --cov Tests --cov-report term --cov-report xml Tests' #- '%PYTHON%\%EXECUTABLE% test-installed.py -v -s %TEST_OPTIONS%' TODO TEST_OPTIONS with pytest? after_test: -- pip install codecov +- python -m pip install codecov - codecov --file coverage.xml --name %PYTHON% --flags AppVeyor matrix: @@ -97,9 +67,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 } @@ -114,7 +84,7 @@ deploy: artifact: /.*egg|wheel/ on: APPVEYOR_REPO_NAME: python-pillow/Pillow - branch: master + branch: main deploy: YES diff --git a/.ci/after_success.sh b/.ci/after_success.sh index dcf276daa56..ff91b481eca 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 8e819631aa5..c48acf9eece 100755 --- a/.ci/install.sh +++ b/.ci/install.sh @@ -1,32 +1,47 @@ #!/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 +python3 -m pip install --upgrade wheel +PYTHONOPTIMIZE=0 python3 -m pip install cffi +python3 -m pip install coverage +python3 -m pip install defusedxml +python3 -m pip install olefile +python3 -m pip install -U pytest +python3 -m pip install -U pytest-cov +python3 -m pip install -U pytest-timeout +python3 -m pip install pyroma +python3 -m pip install test-image-results +python3 -m pip install numpy + +# PyQt5 doesn't support PyPy3 +if [[ $GHA_PYTHON_VERSION == 3.* ]]; 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 516581ff008..8ff7c5f6483 100755 --- a/.ci/test.sh +++ b/.ci/test.sh @@ -2,9 +2,6 @@ set -e -python -m pytest -v -x -W always --cov PIL --cov Tests --cov-report term Tests +python3 -c "from PIL import Image" -# Docs -if [ "$TRAVIS_PYTHON_VERSION" == "3.8" ] && [ "$TRAVIS_CPU_ARCH" == "amd64" ]; then - make doccheck -fi +python3 -bb -m pytest -v -x -W always --cov PIL --cov Tests --cov-report term Tests $REVERSE diff --git a/.clang-format b/.clang-format new file mode 100644 index 00000000000..be32e6d1a8a --- /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 3d27b5d88c6..bc958774497 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -4,20 +4,21 @@ Bug fixes, feature additions, tests, documentation and more can be contributed v ## Bug fixes, feature additions, etc. -Please send a pull request to the master branch. Please include [documentation](https://pillow.readthedocs.io) and [tests](../Tests/README.rst) for new features. Tests or documentation without bug fixes or feature additions are welcome too. Feel free to ask questions [via issues](https://github.com/python-pillow/Pillow/issues/new), [Gitter](https://gitter.im/python-pillow/Pillow) or irc://irc.freenode.net#pil +Please send a pull request to the `main` branch. Please include [documentation](https://pillow.readthedocs.io) and [tests](../Tests/README.rst) for new features. Tests or documentation without bug fixes or feature additions are welcome too. Feel free to ask questions [via issues](https://github.com/python-pillow/Pillow/issues/new), [Gitter](https://gitter.im/python-pillow/Pillow) or irc://irc.freenode.net#pil - Fork the Pillow repository. -- Create a branch from master. +- Create a branch from `main`. - 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. -- Create a pull request to pull the changes from your branch to the Pillow master. +- 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 `main`. ### Guidelines - 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. +- Include [release notes](https://github.com/python-pillow/Pillow/tree/main/docs/releasenotes) as needed or appropriate with your bug fixes, feature additions and tests. ## Reporting Issues @@ -34,4 +35,4 @@ The best reproductions are self-contained scripts with minimal dependencies. If ## Security vulnerabilities -Please see our [security policy](https://github.com/python-pillow/Pillow/blob/master/.github/SECURITY.md). +Please see our [security policy](https://github.com/python-pillow/Pillow/blob/main/.github/SECURITY.md). diff --git a/.github/mergify.yml b/.github/mergify.yml new file mode 100644 index 00000000000..8b289bda671 --- /dev/null +++ b/.github/mergify.yml @@ -0,0 +1,14 @@ +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=MinGW 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 00000000000..4d855469a12 --- /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/cifuzz.yml b/.github/workflows/cifuzz.yml new file mode 100644 index 00000000000..7e2fbf28f88 --- /dev/null +++ b/.github/workflows/cifuzz.yml @@ -0,0 +1,49 @@ +name: CIFuzz + +on: + push: + paths: + - "**.c" + - "**.h" + pull_request: + paths: + - "**.c" + - "**.h" + workflow_dispatch: + +jobs: + Fuzzing: + runs-on: ubuntu-latest + steps: + - name: Build Fuzzers + id: build + uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master + with: + oss-fuzz-project-name: 'pillow' + language: python + dry-run: false + - name: Run Fuzzers + id: run + uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master + with: + oss-fuzz-project-name: 'pillow' + fuzz-seconds: 600 + language: python + dry-run: false + - name: Upload New Crash + uses: actions/upload-artifact@v2 + if: failure() && steps.build.outcome == 'success' + with: + name: artifacts + path: ./out/artifacts + - name: Upload Legacy Crash + uses: actions/upload-artifact@v2 + if: steps.run.outcome == 'success' + with: + name: crash + path: ./out/crash* + - name: Fail on legacy crash + if: success() + run: | + [ ! -e out/crash-* ] + echo No legacy crash detected diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 3cad8d4170b..533ce8cbd53 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,49 +1,41 @@ name: Lint -on: [push, pull_request] +on: [push, pull_request, workflow_dispatch] jobs: build: runs-on: ubuntu-latest - strategy: - matrix: - python-version: ["3.8"] - name: Python ${{ matrix.python-version }} + name: Lint steps: - - uses: actions/checkout@v1 - - - name: pip cache - uses: actions/cache@v1 - with: - path: ~/.cache/pip - key: lint-pip-${{ hashFiles('**/setup.py') }} - restore-keys: | - lint-pip- + - uses: actions/checkout@v2 - name: pre-commit cache - uses: actions/cache@v1 + 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 ${{ matrix.python-version }} - uses: actions/setup-python@v1 + - name: Set up Python + uses: actions/setup-python@v2 with: - python-version: ${{ matrix.python-version }} + python-version: "3.10" + cache: pip + cache-dependency-path: "setup.py" - name: Build system information - run: python .github/workflows/system-info.py + run: python3 .github/workflows/system-info.py - name: Install dependencies run: | - python -m pip install --upgrade pip - python -m pip install --upgrade tox + python3 -m pip install -U pip + python3 -m pip install -U tox - name: Lint run: tox -e lint - + env: + PRE_COMMIT_COLOR: always diff --git a/.github/workflows/macos-install.sh b/.github/workflows/macos-install.sh index 6cd9dadf3d1..8260cf8d8d3 100755 --- a/.github/workflows/macos-install.sh +++ b/.github/workflows/macos-install.sh @@ -2,16 +2,20 @@ 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 defusedxml +python3 -m pip install olefile +python3 -m pip install -U pytest +python3 -m pip install -U pytest-cov +python3 -m pip install -U pytest-timeout +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 +python3 -m pip install numpy # 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 00000000000..ad66117b187 --- /dev/null +++ b/.github/workflows/release-drafter.yml @@ -0,0 +1,18 @@ +name: Release drafter + +on: + push: + # branches to consider in the event; optional, defaults to all + branches: + - main + workflow_dispatch: + +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 "main" + - 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 c10f9af3e74..57396fddcce 100644 --- a/.github/workflows/test-docker.yml +++ b/.github/workflows/test-docker.yml @@ -1,6 +1,6 @@ name: Test Docker -on: [push, pull_request] +on: [push, pull_request, workflow_dispatch] jobs: build: @@ -10,29 +10,44 @@ jobs: 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, + centos-stream-8-amd64, + debian-10-buster-x86, + fedora-34-amd64, + fedora-35-amd64, + ubuntu-18.04-bionic-amd64, + ubuntu-20.04-focal-amd64, ] - dockerTag: [master] + dockerTag: [main] + 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 + run: python3 .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: | @@ -46,7 +61,6 @@ jobs: sudo chown -R runner $GITHUB_WORKSPACE - name: After success - if: success() run: | PATH="$PATH:~/.local/bin" docker start pillow_container @@ -59,8 +73,15 @@ jobs: MATRIX_DOCKER: ${{ matrix.docker }} - name: Upload coverage - if: success() uses: codecov/codecov-action@v1 with: 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-mingw.yml b/.github/workflows/test-mingw.yml new file mode 100644 index 00000000000..d94c7d53751 --- /dev/null +++ b/.github/workflows/test-mingw.yml @@ -0,0 +1,84 @@ +name: Test MinGW + +on: [push, pull_request, workflow_dispatch] + +jobs: + build: + 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: + - name: Checkout Pillow + 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 }}-python-pyqt6 \ + ${{ 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 pytest pytest-cov pytest-timeout + + pushd depends && ./install_extra_test_images.sh && popd + + - name: Build Pillow + run: CFLAGS="-coverage" python3 -m pip install --global-option="build_ext" . + + - name: Test Pillow + run: | + python3 selftest.py --installed + python3 -c "from PIL import Image" + python3 -m pytest -vx --cov PIL --cov Tests --cov-report term --cov-report xml Tests + + - name: Upload coverage + run: | + python3 -m pip install codecov + bash <(curl -s https://codecov.io/bash) -F GHA_Windows + env: + CODECOV_NAME: ${{ matrix.name }} + + success: + needs: build + runs-on: ubuntu-latest + name: MinGW Test Successful + steps: + - name: Success + run: echo MinGW Test Successful diff --git a/.github/workflows/test-valgrind.yml b/.github/workflows/test-valgrind.yml new file mode 100644 index 00000000000..4a8966ca8b3 --- /dev/null +++ b/.github/workflows/test-valgrind.yml @@ -0,0 +1,45 @@ +name: Test Valgrind + +# like the docker tests, but running valgrind only on *.c/*.h changes. + +on: + push: + paths: + - "**.c" + - "**.h" + pull_request: + paths: + - "**.c" + - "**.h" + workflow_dispatch: + +jobs: + build: + + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + docker: [ + ubuntu-20.04-focal-amd64-valgrind, + ] + dockerTag: [main] + + name: ${{ matrix.docker }} + + steps: + - uses: actions/checkout@v2 + + - name: Build system information + run: python3 .github/workflows/system-info.py + + - name: Docker pull + run: | + docker pull pythonpillow/${{ matrix.docker }}:${{ matrix.dockerTag }} + + - name: Build and Run Valgrind + run: | + # The Pillow user in the docker container is UID 1000 + sudo chown -R 1000 $GITHUB_WORKSPACE + docker run --name pillow_container -v $GITHUB_WORKSPACE:/Pillow pythonpillow/${{ matrix.docker }}:${{ matrix.dockerTag }} + sudo chown -R runner $GITHUB_WORKSPACE diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index 705c61e50a0..c768838eb06 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -1,389 +1,196 @@ name: Test Windows -on: [push, pull_request] +on: [push, pull_request, workflow_dispatch] jobs: build: - runs-on: windows-2019 strategy: fail-fast: false matrix: - python-version: ["3.5", "3.6", "3.7", "3.8", "pypy3"] + python-version: ["3.7", "3.8", "3.9", "3.10"] architecture: ["x86", "x64"] include: - - architecture: "x86" - platform-vcvars: "x86" - platform-msbuild: "Win32" - - architecture: "x64" - platform-vcvars: "x86_amd64" - platform-msbuild: "x64" - exclude: - # PyPy does not support 64-bit on Windows - - python-version: "pypy3" + # PyPy 7.3.4+ only ships 64-bit binaries for Windows + - python-version: "pypy-3.7" + architecture: "x64" + - python-version: "pypy-3.8" 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 - - - name: Cache - uses: actions/cache@v1 - with: - path: ~\AppData\Local\pip\Cache - key: - ${{ runner.os }}-${{ matrix.python-version }}-${{ matrix.architecture }}-${{ hashFiles('**/.github/workflows/test-windows.yml') }} - restore-keys: | - ${{ runner.os }}-${{ matrix.python-version }}-${{ matrix.architecture }}- - ${{ runner.os }}-${{ matrix.python-version }}- + path: winbuild\depends # 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 }} + cache: pip + cache-dependency-path: ".github/workflows/test-windows.yml" - - name: Build system information + - name: Print build system information run: python .github/workflows/system-info.py - - name: pip install wheel pytest pytest-cov - run: | - "%pythonLocation%\python.exe" -m pip install wheel pytest pytest-cov - shell: cmd + - name: python -m pip install wheel pytest pytest-cov pytest-timeout defusedxml + run: python -m pip install wheel pytest pytest-cov pytest-timeout defusedxml - - name: Fetch dependencies + - name: Install dependencies + id: install 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" - - ..\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" - - $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 + 7z x winbuild\depends\nasm-2.15.05-win64.zip "-o$env:RUNNER_WORKSPACE\" + echo "$env:RUNNER_WORKSPACE\nasm-2.15.05" >> $env:GITHUB_PATH + + winbuild\depends\gs9550w32.exe /S + echo "C:\Program Files (x86)\gs\gs9.55.0\bin" >> $env:GITHUB_PATH + + xcopy /S /Y winbuild\depends\test_images\* Tests\images\ + + # make cache key depend on VS version + & "C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe" | find """catalog_buildVersion""" | ForEach-Object { $a = $_.split(" ")[1]; echo "::set-output name=vs::$a" } shell: pwsh - - name: Build dependencies / libjpeg - if: false + - 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 }} + + - name: Prepare build + if: steps.build-cache.outputs.cache-hit != 'true' run: | - REM FIXME uses /MT not /MD, see makefile.vc and win32.mak for more info - - 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 + & 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/SBIX 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.4 - 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 + # Raqm dependencies - 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.9 - 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% + # 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 }}' -ne 'pull_request') { $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 if: failure() run: | mkdir -p Tests/errors - shell: pwsh + shell: bash - 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: Upload coverage - if: success() uses: codecov/codecov-action@v1 with: - 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 != 'pull_request'" 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 != 'pull_request'" with: name: ${{ steps.wheel.outputs.dist }} - path: dist + path: dist\*.whl + + success: + needs: build + runs-on: ubuntu-latest + name: Windows Test Successful + steps: + - name: Success + run: echo Windows Test Successful diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f1643edd615..414c7e94edd 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,6 +1,6 @@ name: Test -on: [push, pull_request] +on: [push, pull_request, workflow_dispatch] jobs: build: @@ -9,25 +9,27 @@ jobs: fail-fast: false matrix: os: [ + "macos-latest", "ubuntu-latest", - "macOS-latest", ] python-version: [ - "pypy3", + "pypy-3.8", + "pypy-3.7", + "3.10", + "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 + - python-version: "3.7" + PYTHONOPTIMIZE: 1 + REVERSE: "--reverse" + - python-version: "3.8" + PYTHONOPTIMIZE: 2 # Include new variables for Codecov - os: ubuntu-latest codecov-flag: GHA_Ubuntu - - os: macOS-latest + - os: macos-latest codecov-flag: GHA_macOS runs-on: ${{ matrix.os }} @@ -36,38 +38,22 @@ jobs: steps: - uses: actions/checkout@v2 - - 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 }}- - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v1 + uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} + cache: pip + cache-dependency-path: ".ci/*.sh" - name: Build system information - run: python .github/workflows/system-info.py + run: python3 .github/workflows/system-info.py - name: Install Linux dependencies if: startsWith(matrix.os, 'ubuntu') run: | .ci/install.sh + env: + GHA_PYTHON_VERSION: ${{ matrix.python-version }} - name: Install macOS dependencies if: startsWith(matrix.os, 'macOS') @@ -80,28 +66,49 @@ jobs: - name: Test run: | - .ci/test.sh + if [ $REVERSE ]; then + python3 -m pip install pytest-reverse + fi + if [ "${{ matrix.os }}" = "ubuntu-latest" ]; then + xvfb-run -s '-screen 0 1024x768x24' .ci/test.sh + else + .ci/test.sh + fi + env: + PYTHONOPTIMIZE: ${{ matrix.PYTHONOPTIMIZE }} + REVERSE: ${{ matrix.REVERSE }} - name: Prepare to upload errors if: failure() run: | mkdir -p Tests/errors - 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-copybutton sphinx-issues sphinx-removed-in sphinx-rtd-theme sphinxext-opengraph + make doccheck + - name: After success - if: success() run: | .ci/after_success.sh - name: Upload coverage - if: success() 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/.github/workflows/tidelift.yml b/.github/workflows/tidelift.yml new file mode 100644 index 00000000000..c2b8b3bdaa9 --- /dev/null +++ b/.github/workflows/tidelift.yml @@ -0,0 +1,26 @@ +name: Tidelift Align +on: + schedule: + - cron: "30 2 * * *" # daily at 02:30 UTC + push: + paths: + - ".github/workflows/tidelift.yml" + pull_request: + paths: + - ".github/workflows/tidelift.yml" + workflow_dispatch: + +jobs: + build: + if: github.repository_owner == 'python-pillow' + name: Run Tidelift to ensure approved open source packages are in use + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Scan + uses: tidelift/alignment-action@main + env: + TIDELIFT_API_KEY: ${{ secrets.TIDELIFT_API_KEY }} + TIDELIFT_ORGANIZATION: team/aclark4life + TIDELIFT_PROJECT: pillow diff --git a/.gitignore b/.gitignore index ef7520c0db2..790404535f7 100644 --- a/.gitignore +++ b/.gitignore @@ -81,6 +81,14 @@ 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/crash-81154a65438ba5aaeca73fd502fa4850fbde60f8.tif +Tests/images/string_dimension.tiff +Tests/images/jpeg2000 Tests/images/msp Tests/images/picins Tests/images/sunraster + +# pyinstaller +*.spec diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e30453c3ed2..5f1d16709c1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,32 +1,46 @@ repos: - repo: https://github.com/psf/black - rev: 19.10b0 + rev: 911470a610e47d9da5ea938b0887c3df62819b85 # frozen: 21.9b0 hooks: - id: black - args: ["--target-version", "py35"] + args: ["--target-version", "py37"] # Only .py files, until https://github.com/psf/black/issues/402 resolved files: \.py$ types: [] - - repo: https://gitlab.com/pycqa/flake8 - rev: 3.7.9 + - repo: https://github.com/PyCQA/isort + rev: fd5ba70665a37ec301a1f714ed09336048b3be63 # frozen: 5.9.3 hooks: - - id: flake8 - additional_dependencies: [flake8-2020, flake8-implicit-str-concat] + - id: isort - - repo: https://github.com/timothycrosley/isort - rev: 4.3.21 + - repo: https://github.com/asottile/yesqa + rev: 644ede78511c02fc6f8e03e014cc1ddcfbf1e1f5 # frozen: v1.2.3 hooks: - - id: isort + - id: yesqa + + - repo: https://github.com/Lucas-C/pre-commit-hooks + rev: 3592548bbd98528887eeed63486cf6c9bae00b98 # frozen: v1.1.10 + hooks: + - id: remove-tabs + exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.opt$) + + - repo: https://github.com/PyCQA/flake8 + rev: dcd740bc0ebaf2b3d43e59a0060d157c97de13f3 # frozen: 3.9.2 + hooks: + - id: flake8 + additional_dependencies: [flake8-2020, flake8-implicit-str-concat] - repo: https://github.com/pre-commit/pygrep-hooks - rev: v1.5.1 + rev: 6f51a66bba59954917140ec2eeeaa4d5e630e6ce # frozen: v1.9.0 hooks: - id: python-check-blanket-noqa - id: rst-backticks - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v2.5.0 + rev: 38b88246ccc552bffaaf54259d064beeee434539 # frozen: v4.0.1 hooks: - id: check-merge-conflict - id: check-yaml + +ci: + autoupdate_schedule: quarterly diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index e77102e44a2..00000000000 --- a/.travis.yml +++ /dev/null @@ -1,67 +0,0 @@ -dist: xenial -language: python -cache: - pip: true - directories: - - $HOME/.cache/pre-commit - -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 eec3c2fcf71..fa9fb52cf06 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -2,6 +2,813 @@ Changelog (Pillow) ================== +9.0.1 (2022-02-03) +------------------ + +- In show_file, use os.remove to remove temporary images. CVE-2022-24303 #6010 + [radarhere, hugovk] + +- Restrict builtins within lambdas for ImageMath.eval. CVE-2022-22817 #6009 + [radarhere] + +9.0.0 (2022-01-02) +------------------ + +- Restrict builtins for ImageMath.eval(). CVE-2022-22817 #5923 + [radarhere] + +- Ensure JpegImagePlugin stops at the end of a truncated file #5921 + [radarhere] + +- Fixed ImagePath.Path array handling. CVE-2022-22815, CVE-2022-22816 #5920 + [radarhere] + +- Remove consecutive duplicate tiles that only differ by their offset #5919 + [radarhere] + +- Improved I;16 operations on big endian #5901 + [radarhere] + +- Limit quantized palette to number of colors #5879 + [radarhere] + +- Fixed palette index for zeroed color in FASTOCTREE quantize #5869 + [radarhere] + +- When saving RGBA to GIF, make use of first transparent palette entry #5859 + [radarhere] + +- Pass SAMPLEFORMAT to libtiff #5848 + [radarhere] + +- Added rounding when converting P and PA #5824 + [radarhere] + +- Improved putdata() documentation and data handling #5910 + [radarhere] + +- Exclude carriage return in PDF regex to help prevent ReDoS #5912 + [hugovk] + +- Fixed freeing pointer in ImageDraw.Outline.transform #5909 + [radarhere] + +- Added ImageShow support for xdg-open #5897 + [m-shinder, radarhere] + +- Support 16-bit grayscale ImageQt conversion #5856 + [cmbruns, radarhere] + +- Convert subsequent GIF frames to RGB or RGBA #5857 + [radarhere] + +- Do not prematurely return in ImageFile when saving to stdout #5665 + [infmagic2047, radarhere] + +- Added support for top right and bottom right TGA orientations #5829 + [radarhere] + +- Corrected ICNS file length in header #5845 + [radarhere] + +- Block tile TIFF tags when saving #5839 + [radarhere] + +- Added line width argument to polygon #5694 + [radarhere] + +- Do not redeclare class each time when converting to NumPy #5844 + [radarhere] + +- Only prevent repeated polygon pixels when drawing with transparency #5835 + [radarhere] + +- Add support for pickling TrueType fonts #5826 + [hugovk, radarhere] + +- Only prefer command line tools SDK on macOS over default MacOSX SDK #5828 + [radarhere] + +- Drop support for soon-EOL Python 3.6 #5768 + [hugovk, nulano, radarhere] + +- Fix compilation on 64-bit Termux #5793 + [landfillbaby] + +- Use title for display in ImageShow #5788 + [radarhere] + +- Remove support for FreeType 2.7 and older #5777 + [hugovk, radarhere] + +- Fix for PyQt6 #5775 + [hugovk, radarhere] + +- Removed deprecated PILLOW_VERSION, Image.show command parameter, Image._showxv and ImageFile.raise_ioerror #5776 + [radarhere] + +8.4.0 (2021-10-15) +------------------ + +- Prefer global transparency in GIF when replacing with background color #5756 + [radarhere] + +- Added "exif" keyword argument to TIFF saving #5575 + [radarhere] + +- Copy Python palette to new image in quantize() #5696 + [radarhere] + +- Read ICO AND mask from end #5667 + [radarhere] + +- Actually check the framesize in FliDecode.c #5659 + [wiredfool] + +- Determine JPEG2000 mode purely from ihdr header box #5654 + [radarhere] + +- Fixed using info dictionary when writing multiple APNG frames #5611 + [radarhere] + +- Allow saving 1 and L mode TIFF with PhotometricInterpretation 0 #5655 + [radarhere] + +- For GIF save_all with palette, do not include palette with each frame #5603 + [radarhere] + +- Keep transparency when converting from P to LA or PA #5606 + [radarhere] + +- Copy palette to new image in transform() #5647 + [radarhere] + +- Added "transparency" argument to EpsImagePlugin load() #5620 + [radarhere] + +- Corrected pathlib.Path detection when saving #5633 + [radarhere] + +- Added WalImageFile class #5618 + [radarhere] + +- Consider I;16 pixel size when drawing text #5598 + [radarhere] + +- If default conversion from P is RGB with transparency, convert to RGBA #5594 + [radarhere] + +- Speed up rotating square images by 90 or 270 degrees #5646 + [radarhere] + +- Add support for reading DPI information from JPEG2000 images + [rogermb, radarhere] + +- Catch TypeError from corrupted DPI value in EXIF #5639 + [homm, radarhere] + +- Do not close file pointer when saving SGI images #5645 + [farizrahman4u, radarhere] + +- Deprecate ImagePalette size parameter #5641 + [radarhere, hugovk] + +- Prefer command line tools SDK on macOS #5624 + [radarhere] + +- Added tags when saving YCbCr TIFF #5597 + [radarhere] + +- PSD layer count may be negative #5613 + [radarhere] + +- Fixed ImageOps expand with tuple border on P image #5615 + [radarhere] + +- Fixed error saving APNG with duplicate frames and different duration times #5609 + [thak1411, radarhere] + +8.3.2 (2021-09-02) +------------------ + +- CVE-2021-23437 Raise ValueError if color specifier is too long + [hugovk, radarhere] + +- Fix 6-byte OOB read in FliDecode + [wiredfool] + +- Add support for Python 3.10 #5569, #5570 + [hugovk, radarhere] + +- Ensure TIFF ``RowsPerStrip`` is multiple of 8 for JPEG compression #5588 + [kmilos, radarhere] + +- Updates for ``ImagePalette`` channel order #5599 + [radarhere] + +- Hide FriBiDi shim symbols to avoid conflict with real FriBiDi library #5651 + [nulano] + +8.3.1 (2021-07-06) +------------------ + +- Catch OSError when checking if fp is sys.stdout #5585 + [radarhere] + +- Handle removing orientation from alternate types of EXIF data #5584 + [radarhere] + +- Make Image.__array__ take optional dtype argument #5572 + [t-vi, radarhere] + +8.3.0 (2021-07-01) +------------------ + +- Use snprintf instead of sprintf. CVE-2021-34552 #5567 + [radarhere] + +- Limit TIFF strip size when saving with LibTIFF #5514 + [kmilos] + +- Allow ICNS save on all operating systems #4526 + [baletu, radarhere, newpanjing, hugovk] + +- De-zigzag JPEG's DQT when loading; deprecate convert_dict_qtables #4989 + [gofr, radarhere] + +- Replaced xml.etree.ElementTree #5565 + [radarhere] + +- Moved CVE image to pillow-depends #5561 + [radarhere] + +- Added tag data for IFD groups #5554 + [radarhere] + +- Improved ImagePalette #5552 + [radarhere] + +- Add DDS saving #5402 + [radarhere] + +- Improved getxmp() #5455 + [radarhere] + +- Convert to float for comparison with float in IFDRational __eq__ #5412 + [radarhere] + +- Allow getexif() to access TIFF tag_v2 data #5416 + [radarhere] + +- Read FITS image mode and size #5405 + [radarhere] + +- Merge parallel horizontal edges in ImagingDrawPolygon #5347 + [radarhere, hrdrq] + +- Use transparency behind first GIF frame and when disposing to background #5557 + [radarhere, zewt] + +- Avoid unstable nature of qsort in Quant.c #5367 + [radarhere] + +- Copy palette to new images in ImageOps expand #5551 + [radarhere] + +- Ensure palette string matches RGB mode #5549 + [radarhere] + +- Do not modify EXIF of original image instance in exif_transpose() #5547 + [radarhere] + +- Fixed default numresolution for small JPEG2000 images #5540 + [radarhere] + +- Added DDS BC5 reading #5501 + [radarhere] + +- Raise an error if ImageDraw.textbbox is used without a TrueType font #5510 + [radarhere] + +- Added ICO saving in BMP format #5513 + [radarhere] + +- Ensure PNG seeks to end of previous chunk at start of load_end #5493 + [radarhere] + +- Do not allow TIFF to seek to a past frame #5473 + [radarhere] + +- Avoid race condition when displaying images with eog #5507 + [mconst] + +- Added specific error messages when ink has incorrect number of bands #5504 + [radarhere] + +- Allow converting an image to a numpy array to raise errors #5379 + [radarhere] + +- Removed DPI rounding from BMP, JPEG, PNG and WMF loading #5476, #5470 + [radarhere] + +- Remove spikes when drawing thin pieslices #5460 + [xtsm] + +- Updated default value for SAMPLESPERPIXEL TIFF tag #5452 + [radarhere] + +- Removed TIFF DPI rounding #5446 + [radarhere, hugovk] + +- Include code in WebP error #5471 + [radarhere] + +- Do not alter pixels outside mask when drawing text on an image with transparency #5434 + [radarhere] + +- Reset handle when seeking backwards in TIFF #5443 + [radarhere] + +- Replace sys.stdout with sys.stdout.buffer when saving #5437 + [radarhere] + +- Fixed UNDEFINED TIFF tag of length 0 being changed in roundtrip #5426 + [radarhere] + +- Fixed bug when checking FreeType2 version if it is not installed #5445 + [radarhere] + +- Do not round dimensions when saving PDF #5459 + [radarhere] + +- Added ImageOps contain() #5417 + [radarhere, hugovk] + +- Changed WebP default "method" value to 4 #5450 + [radarhere] + +- Switched to saving 1-bit PDFs with DCTDecode #5430 + [radarhere] + +- Use bpp from ICO header #5429 + [radarhere] + +- Corrected JPEG APP14 transform value #5408 + [radarhere] + +- Changed TIFF tag 33723 length to 1 #5425 + [radarhere] + +- Changed ImageMorph incorrect mode errors to ValueError #5414 + [radarhere] + +- Add EXIF tags specified in EXIF 2.32 #5419 + [gladiusglad] + +- Treat previous contents of first GIF frame as transparent #5391 + [radarhere] + +- For special image modes, revert default resize resampling to NEAREST #5411 + [radarhere] + +- JPEG2000: Support decoding subsampled RGB and YCbCr images #4996 + [nulano, radarhere] + +- Stop decoding BC1 punchthrough alpha in BC2&3 #4144 + [jansol] + +- Use zero if GIF background color index is missing #5390 + [radarhere] + +- Fixed ensuring that GIF previous frame was loaded #5386 + [radarhere] + +- Valgrind fixes #5397 + [wiredfool] + +- Round down the radius in rounded_rectangle #5382 + [radarhere] + +- Fixed reading uncompressed RGB data from DDS #5383 + [radarhere] + +8.2.0 (2021-04-01) +------------------ + +- Added getxmp() method #5144 + [UrielMaD, radarhere] + +- Add ImageShow support for GraphicsMagick #5349 + [latosha-maltba, radarhere] + +- Do not load transparent pixels from subsequent GIF frames #5333 + [zewt, radarhere] + +- Use LZW encoding when saving GIF images #5291 + [raygard] + +- Set all transparent colors to be equal in quantize() #5282 + [radarhere] + +- Allow PixelAccess to use Python __int__ when parsing x and y #5206 + [radarhere] + +- Removed Image._MODEINFO #5316 + [radarhere] + +- Add preserve_tone option to autocontrast #5350 + [elejke, radarhere] + +- Fixed linear_gradient and radial_gradient I and F modes #5274 + [radarhere] + +- Add support for reading TIFFs with PlanarConfiguration=2 #5364 + [kkopachev, wiredfool, nulano] + +- Deprecated categories #5351 + [radarhere] + +- Do not premultiply alpha when resizing with Image.NEAREST resampling #5304 + [nulano] + +- Dynamically link FriBiDi instead of Raqm #5062 + [nulano] + +- Allow fewer PNG palette entries than the bit depth maximum when saving #5330 + [radarhere] + +- Use duration from info dictionary when saving WebP #5338 + [radarhere] + +- Stop flattening EXIF IFD into getexif() #4947 + [radarhere, kkopachev] + +- Replaced tiff_deflate with tiff_adobe_deflate compression when saving TIFF images #5343 + [radarhere] + +- Save ICC profile from TIFF encoderinfo #5321 + [radarhere] + +- Moved RGB fix inside ImageQt class #5268 + [radarhere] + +- Allow alpha_composite destination to be negative #5313 + [radarhere] + +- Ensure file is closed if it is opened by ImageQt.ImageQt #5260 + [radarhere] + +- Added ImageDraw rounded_rectangle method #5208 + [radarhere] + +- Added IPythonViewer #5289 + [radarhere, Kipkurui-mutai] + +- Only draw each rectangle outline pixel once #5183 + [radarhere] + +- Use mmap instead of built-in Win32 mapper #5224 + [radarhere, cgohlke] + +- Handle PCX images with an odd stride #5214 + [radarhere] + +- Only read different sizes for "Large Thumbnail" MPO frames #5168 + [radarhere] + +- Added PyQt6 support #5258 + [radarhere] + +- Changed Image.open formats parameter to be case-insensitive #5250 + [Piolie, radarhere] + +- Deprecate Tk/Tcl 8.4, to be removed in Pillow 10 (2023-07-01) #5216 + [radarhere] + +- Added tk version to pilinfo #5226 + [radarhere, nulano] + +- Support for ignoring tests when running valgrind #5150 + [wiredfool, radarhere, hugovk] + +- OSS-Fuzz support #5189 + [wiredfool, radarhere] + +8.1.2 (2021-03-06) +------------------ + +- Fix Memory DOS in BLP (CVE-2021-27921), ICNS (CVE-2021-27922) and ICO (CVE-2021-27923) Image Plugins + [wiredfool] + +8.1.1 (2021-03-01) +------------------ + +- Use more specific regex chars to prevent ReDoS. CVE-2021-25292 + [hugovk] + +- Fix OOB Read in TiffDecode.c, and check the tile validity before reading. CVE-2021-25291 + [wiredfool] + +- Fix negative size read in TiffDecode.c. CVE-2021-25290 + [wiredfool] + +- Fix OOB read in SgiRleDecode.c. CVE-2021-25293 + [wiredfool] + +- Incorrect error code checking in TiffDecode.c. CVE-2021-25289 + [wiredfool] + +- PyModule_AddObject fix for Python 3.10 #5194 + [radarhere] + +8.1.0 (2021-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) ------------------ @@ -3808,8 +4615,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 @@ -3855,7 +4662,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) @@ -5289,23 +6096,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. @@ -5326,31 +6133,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, @@ -5488,7 +6295,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 @@ -5571,7 +6378,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 @@ -5585,5 +6392,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 4aac532f486..40aabc3239f 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-2022 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 a5f726b040a..26f9401f2d9 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,6 +1,7 @@ include *.c include *.h include *.in +include *.lock include *.md include *.py include *.rst @@ -9,6 +10,7 @@ include *.txt include *.yaml include LICENSE include Makefile +include Pipfile include tox.ini graft Tests graft src @@ -18,13 +20,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 6e55e4c7a98..0dac63d3961 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,64 +40,87 @@ 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 + python3 -m pip install -e --global-option="build_ext" --global-option="--inplace" . +.PHONY: install install: - python3 setup.py install + python3 -m pip install . python3 selftest.py +.PHONY: install-coverage install-coverage: - CFLAGS="-coverage" python3 setup.py build_ext install + CFLAGS="-coverage -Werror=implicit-function-declaration" python3 -m pip install --global-option="build_ext" . 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 # see any build failures. make clean > /dev/null - CFLAGS='-g -O0' python3 setup.py build_ext install > /dev/null + CFLAGS='-g -O0' python3 -m pip install --global-option="build_ext" . > /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 -m pip install -e . python3 selftest.py python3 -m pytest Tests - python3 setup.py install + python3 -m pip 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 + python3 -m build --help > /dev/null 2>&1 || python3 -m pip install build + python3 -m build --sdist +.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: valgrind +valgrind: + python3 -c "import pytest_valgrind" || pip3 install pytest-valgrind + PYTHONMALLOC=malloc valgrind --suppressions=Tests/oss-fuzz/python.supp --leak-check=no \ + --log-file=/tmp/valgrind-output \ + python3 -m pytest --no-memcheck -vv --valgrind --valgrind-log=/tmp/valgrind-output +.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 py37 . + isort . diff --git a/Pipfile b/Pipfile new file mode 100644 index 00000000000..1e611a63ce7 --- /dev/null +++ b/Pipfile @@ -0,0 +1,22 @@ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +black = "*" +check-manifest = "*" +coverage = "*" +defusedxml = "*" +packaging = "*" +markdown2 = "*" +olefile = "*" +pyroma = "*" +pytest = "*" +pytest-cov = "*" +pytest-timeout = "*" + +[dev-packages] + +[requires] +python_version = "3.9" diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 00000000000..600b19050f5 --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,324 @@ +{ + "_meta": { + "hash": { + "sha256": "e5cad23bf4187647d53b613a64dc4792b7064bf86b08dfb5737580e32943f54d" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.9" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "attrs": { + "hashes": [ + "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1", + "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==21.2.0" + }, + "black": { + "hashes": [ + "sha256:77b80f693a569e2e527958459634f18df9b0ba2625ba4e0c2d5da5be42e6f2b3", + "sha256:a615e69ae185e08fdd73e4715e260e2479c861b5740057fde6e8b4e3b7dd589f" + ], + "index": "pypi", + "version": "==21.12b0" + }, + "build": { + "hashes": [ + "sha256:1aaadcd69338252ade4f7ec1265e1a19184bf916d84c9b7df095f423948cb89f", + "sha256:21b7ebbd1b22499c4dac536abc7606696ea4d909fd755e00f09f3c0f2c05e3c8" + ], + "markers": "python_version >= '3.6'", + "version": "==0.7.0" + }, + "certifi": { + "hashes": [ + "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872", + "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569" + ], + "version": "==2021.10.8" + }, + "charset-normalizer": { + "hashes": [ + "sha256:1eecaa09422db5be9e29d7fc65664e6c33bd06f9ced7838578ba40d58bdf3721", + "sha256:b0b883e8e874edfdece9c28f314e3dd5badf067342e42fb162203335ae61aa2c" + ], + "markers": "python_version >= '3'", + "version": "==2.0.9" + }, + "check-manifest": { + "hashes": [ + "sha256:365c94d65de4c927d9d8b505371d08ee19f9f369c86b9ac3db97c2754c827c95", + "sha256:56dadd260a9c7d550b159796d2894b6d0bcc176a94cbc426d9bb93e5e48d12ce" + ], + "index": "pypi", + "version": "==0.47" + }, + "click": { + "hashes": [ + "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3", + "sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b" + ], + "markers": "python_version >= '3.6'", + "version": "==8.0.3" + }, + "coverage": { + "hashes": [ + "sha256:01774a2c2c729619760320270e42cd9e797427ecfddd32c2a7b639cdc481f3c0", + "sha256:03b20e52b7d31be571c9c06b74746746d4eb82fc260e594dc662ed48145e9efd", + "sha256:0a7726f74ff63f41e95ed3a89fef002916c828bb5fcae83b505b49d81a066884", + "sha256:1219d760ccfafc03c0822ae2e06e3b1248a8e6d1a70928966bafc6838d3c9e48", + "sha256:13362889b2d46e8d9f97c421539c97c963e34031ab0cb89e8ca83a10cc71ac76", + "sha256:174cf9b4bef0db2e8244f82059a5a72bd47e1d40e71c68ab055425172b16b7d0", + "sha256:17e6c11038d4ed6e8af1407d9e89a2904d573be29d51515f14262d7f10ef0a64", + "sha256:215f8afcc02a24c2d9a10d3790b21054b58d71f4b3c6f055d4bb1b15cecce685", + "sha256:22e60a3ca5acba37d1d4a2ee66e051f5b0e1b9ac950b5b0cf4aa5366eda41d47", + "sha256:2641f803ee9f95b1f387f3e8f3bf28d83d9b69a39e9911e5bfee832bea75240d", + "sha256:276651978c94a8c5672ea60a2656e95a3cce2a3f31e9fb2d5ebd4c215d095840", + "sha256:3f7c17209eef285c86f819ff04a6d4cbee9b33ef05cbcaae4c0b4e8e06b3ec8f", + "sha256:3feac4084291642165c3a0d9eaebedf19ffa505016c4d3db15bfe235718d4971", + "sha256:49dbff64961bc9bdd2289a2bda6a3a5a331964ba5497f694e2cbd540d656dc1c", + "sha256:4e547122ca2d244f7c090fe3f4b5a5861255ff66b7ab6d98f44a0222aaf8671a", + "sha256:5829192582c0ec8ca4a2532407bc14c2f338d9878a10442f5d03804a95fac9de", + "sha256:5d6b09c972ce9200264c35a1d53d43ca55ef61836d9ec60f0d44273a31aa9f17", + "sha256:600617008aa82032ddeace2535626d1bc212dfff32b43989539deda63b3f36e4", + "sha256:619346d57c7126ae49ac95b11b0dc8e36c1dd49d148477461bb66c8cf13bb521", + "sha256:63c424e6f5b4ab1cf1e23a43b12f542b0ec2e54f99ec9f11b75382152981df57", + "sha256:6dbc1536e105adda7a6312c778f15aaabe583b0e9a0b0a324990334fd458c94b", + "sha256:6e1394d24d5938e561fbeaa0cd3d356207579c28bd1792f25a068743f2d5b282", + "sha256:86f2e78b1eff847609b1ca8050c9e1fa3bd44ce755b2ec30e70f2d3ba3844644", + "sha256:8bdfe9ff3a4ea37d17f172ac0dff1e1c383aec17a636b9b35906babc9f0f5475", + "sha256:8e2c35a4c1f269704e90888e56f794e2d9c0262fb0c1b1c8c4ee44d9b9e77b5d", + "sha256:92b8c845527eae547a2a6617d336adc56394050c3ed8a6918683646328fbb6da", + "sha256:9365ed5cce5d0cf2c10afc6add145c5037d3148585b8ae0e77cc1efdd6aa2953", + "sha256:9a29311bd6429be317c1f3fe4bc06c4c5ee45e2fa61b2a19d4d1d6111cb94af2", + "sha256:9a2b5b52be0a8626fcbffd7e689781bf8c2ac01613e77feda93d96184949a98e", + "sha256:a4bdeb0a52d1d04123b41d90a4390b096f3ef38eee35e11f0b22c2d031222c6c", + "sha256:a9c8c4283e17690ff1a7427123ffb428ad6a52ed720d550e299e8291e33184dc", + "sha256:b637c57fdb8be84e91fac60d9325a66a5981f8086c954ea2772efe28425eaf64", + "sha256:bf154ba7ee2fd613eb541c2bc03d3d9ac667080a737449d1a3fb342740eb1a74", + "sha256:c254b03032d5a06de049ce8bca8338a5185f07fb76600afff3c161e053d88617", + "sha256:c332d8f8d448ded473b97fefe4a0983265af21917d8b0cdcb8bb06b2afe632c3", + "sha256:c7912d1526299cb04c88288e148c6c87c0df600eca76efd99d84396cfe00ef1d", + "sha256:cfd9386c1d6f13b37e05a91a8583e802f8059bebfccde61a418c5808dea6bbfa", + "sha256:d5d2033d5db1d58ae2d62f095e1aefb6988af65b4b12cb8987af409587cc0739", + "sha256:dca38a21e4423f3edb821292e97cec7ad38086f84313462098568baedf4331f8", + "sha256:e2cad8093172b7d1595b4ad66f24270808658e11acf43a8f95b41276162eb5b8", + "sha256:e3db840a4dee542e37e09f30859f1612da90e1c5239a6a2498c473183a50e781", + "sha256:edcada2e24ed68f019175c2b2af2a8b481d3d084798b8c20d15d34f5c733fa58", + "sha256:f467bbb837691ab5a8ca359199d3429a11a01e6dfb3d9dcc676dc035ca93c0a9", + "sha256:f506af4f27def639ba45789fa6fde45f9a217da0be05f8910458e4557eed020c", + "sha256:f614fc9956d76d8a88a88bb41ddc12709caa755666f580af3a688899721efecd", + "sha256:f9afb5b746781fc2abce26193d1c817b7eb0e11459510fba65d2bd77fe161d9e", + "sha256:fb8b8ee99b3fffe4fd86f4c81b35a6bf7e4462cba019997af2fe679365db0c49" + ], + "index": "pypi", + "version": "==6.2" + }, + "defusedxml": { + "hashes": [ + "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", + "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61" + ], + "index": "pypi", + "version": "==0.7.1" + }, + "docutils": { + "hashes": [ + "sha256:23010f129180089fbcd3bc08cfefccb3b890b0050e1ca00c867036e9d161b98c", + "sha256:679987caf361a7539d76e584cbeddc311e3aee937877c87346f31debc63e9d06" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==0.18.1" + }, + "idna": { + "hashes": [ + "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff", + "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d" + ], + "markers": "python_version >= '3'", + "version": "==3.3" + }, + "iniconfig": { + "hashes": [ + "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3", + "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32" + ], + "version": "==1.1.1" + }, + "markdown2": { + "hashes": [ + "sha256:8f4ac8d9a124ab408c67361090ed512deda746c04362c36c2ec16190c720c2b0", + "sha256:91113caf23aa662570fe21984f08fe74f814695c0a0ea8e863a8b4c4f63f9f6e" + ], + "index": "pypi", + "version": "==2.4.2" + }, + "mypy-extensions": { + "hashes": [ + "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d", + "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8" + ], + "version": "==0.4.3" + }, + "olefile": { + "hashes": [ + "sha256:133b031eaf8fd2c9399b78b8bc5b8fcbe4c31e85295749bb17a87cba8f3c3964" + ], + "index": "pypi", + "version": "==0.46" + }, + "packaging": { + "hashes": [ + "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb", + "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522" + ], + "index": "pypi", + "version": "==21.3" + }, + "pathspec": { + "hashes": [ + "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a", + "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1" + ], + "version": "==0.9.0" + }, + "pep517": { + "hashes": [ + "sha256:931378d93d11b298cf511dd634cf5ea4cb249a28ef84160b3247ee9afb4e8ab0", + "sha256:dd884c326898e2c6e11f9e0b64940606a93eb10ea022a2e067959f3a110cf161" + ], + "version": "==0.12.0" + }, + "platformdirs": { + "hashes": [ + "sha256:367a5e80b3d04d2428ffa76d33f124cf11e8fff2acdaa9b43d545f5c7d661ef2", + "sha256:8868bbe3c3c80d42f20156f22e7131d2fb321f5bc86a2a345375c6481a67021d" + ], + "markers": "python_version >= '3.6'", + "version": "==2.4.0" + }, + "pluggy": { + "hashes": [ + "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159", + "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3" + ], + "markers": "python_version >= '3.6'", + "version": "==1.0.0" + }, + "py": { + "hashes": [ + "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719", + "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==1.11.0" + }, + "pygments": { + "hashes": [ + "sha256:b8e67fe6af78f492b3c4b3e2970c0624cbf08beb1e493b2c99b9fa1b67a20380", + "sha256:f398865f7eb6874156579fdf36bc840a03cab64d1cde9e93d68f46a425ec52c6" + ], + "markers": "python_version >= '3.5'", + "version": "==2.10.0" + }, + "pyparsing": { + "hashes": [ + "sha256:04ff808a5b90911829c55c4e26f75fa5ca8a2f5f36aa3a51f68e27033341d3e4", + "sha256:d9bdec0013ef1eb5a84ab39a3b3868911598afa494f5faa038647101504e2b81" + ], + "markers": "python_version >= '3.6'", + "version": "==3.0.6" + }, + "pyroma": { + "hashes": [ + "sha256:0fba67322913026091590e68e0d9e0d4fbd6420fcf34d315b2ad6985ab104d65", + "sha256:f8c181e0d5d292f11791afc18f7d0218a83c85cf64d6f8fb1571ce9d29a24e4a" + ], + "index": "pypi", + "version": "==3.2" + }, + "pytest": { + "hashes": [ + "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89", + "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134" + ], + "index": "pypi", + "version": "==6.2.5" + }, + "pytest-cov": { + "hashes": [ + "sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6", + "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470" + ], + "index": "pypi", + "version": "==3.0.0" + }, + "pytest-timeout": { + "hashes": [ + "sha256:e6f98b54dafde8d70e4088467ff621260b641eb64895c4195b6e5c8f45638112", + "sha256:fe9c3d5006c053bb9e062d60f641e6a76d6707aedb645350af9593e376fcc717" + ], + "index": "pypi", + "version": "==2.0.2" + }, + "requests": { + "hashes": [ + "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24", + "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", + "version": "==2.26.0" + }, + "setuptools": { + "hashes": [ + "sha256:5ec2bbb534ed160b261acbbdd1b463eb3cf52a8d223d96a8ab9981f63798e85c", + "sha256:75fd345a47ce3d79595b27bf57e6f49c2ca7904f3c7ce75f8a87012046c86b0b" + ], + "markers": "python_version >= '3.7'", + "version": "==60.0.0" + }, + "toml": { + "hashes": [ + "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", + "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" + ], + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'", + "version": "==0.10.2" + }, + "tomli": { + "hashes": [ + "sha256:05b6166bff487dc068d322585c7ea4ef78deed501cc124060e0f238e89a9231f", + "sha256:e3069e4be3ead9668e21cb9b074cd948f7b3113fd9c8bba083f48247aab8b11c" + ], + "markers": "python_version >= '3.6'", + "version": "==1.2.3" + }, + "typing-extensions": { + "hashes": [ + "sha256:4ca091dea149f945ec56afb48dae714f21e8692ef22a395223bcd328961b6a0e", + "sha256:7f001e5ac290a0c0401508864c7ec868be4e701886d5b573a9528ed3973d9d3b" + ], + "markers": "python_version >= '3.6'", + "version": "==4.0.1" + }, + "urllib3": { + "hashes": [ + "sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece", + "sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", + "version": "==1.26.7" + } + }, + "develop": {} +} diff --git a/README.md b/README.md new file mode 100644 index 00000000000..782b81f3370 --- /dev/null +++ b/README.md @@ -0,0 +1,108 @@ +

+ 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) + GitHub Actions wheels build status (Wheels) + Travis CI wheels build status (aarch64) + Code coverage + Tidelift Align +
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/main/.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/main/CHANGES.rst) + - [Pre-fork](https://github.com/python-pillow/Pillow/blob/main/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 c1d5be57912..00000000000 --- a/README.rst +++ /dev/null @@ -1,103 +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 - :target: https://github.com/python-pillow/Pillow/actions?query=workflow%3ALint - :alt: GitHub Actions build status (Lint) - -.. |gha_docker| 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) - -.. |gha| 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) - -.. |gha_windows| 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) - -.. |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=badge - -.. |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 3a285662cb3..cbedd449c0c 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 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. +* [ ] Develop and prepare release in `main` branch. +* [ ] Check [GitHub Actions](https://github.com/python-pillow/Pillow/actions) and [AppVeyor](https://ci.appveyor.com/project/python-pillow/Pillow) to confirm passing tests in `main` branch. +* [ ] Check 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,48 +21,62 @@ Released quarterly on January 2nd, April 1st, July 1st and October 15th. 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/main/RELEASING.md#binary-distributions) +* [ ] Check and upload all binaries and source distributions e.g.: + ```bash + twine check dist/* + twine upload dist/Pillow-5.2.0* ``` -* [ ] 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) +* [ ] 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 Released as needed for security, installation or critical bug fixes. -* [ ] Make necessary changes in `master` branch. +* [ ] Make necessary changes in `main` branch. * [ ] Update `CHANGES.rst`. * [ ] Check out release branch e.g.: ```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 `main` 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/main/RELEASING.md#binary-distributions) +* [ ] Check and upload all binaries and source distributions e.g.: + ```bash + twine check dist/* + twine upload dist/Pillow-5.2.1* ``` -* [ ] 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) +* [ ] Publish the [release on GitHub](https://github.com/python-pillow/Pillow/releases) ## Embargoed Release Released as needed privately to individual vendors for critical security-related bug fixes. * [ ] Prepare patch for all versions that will get a fix. Test against local installations. -* [ ] Commit against master, cherry pick to affected release branches. +* [ ] Commit against `main`, cherry pick to affected release branches. * [ ] Run local test matrix on each release & Python version. * [ ] Privately send to distros. * [ ] Run pre-release check via `make release-test` @@ -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) +* [ ] Create [binary distributions](https://github.com/python-pillow/Pillow/blob/main/RELEASING.md#binary-distributions) +* [ ] 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/32bit_segfault_check.py b/Tests/32bit_segfault_check.py index 26a91d5cd72..e19cdf7a918 100755 --- a/Tests/32bit_segfault_check.py +++ b/Tests/32bit_segfault_check.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 import sys diff --git a/Tests/bench_cffi_access.py b/Tests/bench_cffi_access.py index f196757dcc6..87cad699d3b 100644 --- a/Tests/bench_cffi_access.py +++ b/Tests/bench_cffi_access.py @@ -4,7 +4,7 @@ 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): @@ -28,15 +28,17 @@ 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) + ) ) diff --git a/Tests/check_fli_oob.py b/Tests/check_fli_oob.py index 739ad224e7e..7b3d4d7ee9d 100644 --- a/Tests/check_fli_oob.py +++ b/Tests/check_fli_oob.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 from PIL import Image @@ -61,8 +61,8 @@ 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) + with Image.open(path) as im: + try: + im.load() + except Exception as msg: + print(msg) diff --git a/Tests/check_icns_dos.py b/Tests/check_icns_dos.py index 3f4fb6518e2..a34bee45c51 100644 --- a/Tests/check_icns_dos.py +++ b/Tests/check_icns_dos.py @@ -5,4 +5,5 @@ from PIL import Image -Image.open(BytesIO(b"icns\x00\x00\x00\x10hang\x00\x00\x00\x00")) +with Image.open(BytesIO(b"icns\x00\x00\x00\x10hang\x00\x00\x00\x00")): + pass diff --git a/Tests/check_imaging_leaks.py b/Tests/check_imaging_leaks.py index db12d00e3f5..d07082aba9f 100755 --- a/Tests/check_imaging_leaks.py +++ b/Tests/check_imaging_leaks.py @@ -1,5 +1,6 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 import pytest + from PIL import Image from .helper import is_win32 @@ -11,7 +12,7 @@ def _get_mem_usage(): - from resource import getpagesize, getrusage, RUSAGE_SELF + from resource import RUSAGE_SELF, getpagesize, getrusage mem = getrusage(RUSAGE_SELF).ru_maxrss return mem * getpagesize() / 1024 / 1024 @@ -25,7 +26,7 @@ def _test_leak(min_iterations, max_iterations, fn, *args, **kwargs): if i < min_iterations: mem_limit = mem + 1 continue - msg = "memory usage limit exceeded after %d iterations" % (i + 1) + msg = f"memory usage limit exceeded after {i + 1} iterations" assert mem <= mem_limit, msg diff --git a/Tests/check_j2k_dos.py b/Tests/check_j2k_dos.py index 273c18585e8..71dcea4f39f 100644 --- a/Tests/check_j2k_dos.py +++ b/Tests/check_j2k_dos.py @@ -5,4 +5,7 @@ from PIL import Image -Image.open(BytesIO(b"\x00\x00\x00\x0cjP\x20\x20\x0d\x0a\x87\x0a\x00\x00\x00\x00hang")) +with Image.open( + BytesIO(b"\x00\x00\x00\x0cjP\x20\x20\x0d\x0a\x87\x0a\x00\x00\x00\x00hang") +): + pass diff --git a/Tests/check_j2k_leaks.py b/Tests/check_j2k_leaks.py index 5cef4b544ee..afe5836f3fd 100755 --- a/Tests/check_j2k_leaks.py +++ b/Tests/check_j2k_leaks.py @@ -1,6 +1,7 @@ from io import BytesIO import pytest + from PIL import Image from .helper import is_win32, skip_unless_feature @@ -18,7 +19,7 @@ def test_leak_load(): - from resource import setrlimit, RLIMIT_AS, RLIMIT_STACK + from resource import RLIMIT_AS, RLIMIT_STACK, setrlimit setrlimit(RLIMIT_STACK, (stack_size, stack_size)) setrlimit(RLIMIT_AS, (mem_limit, mem_limit)) @@ -28,7 +29,7 @@ def test_leak_load(): def test_leak_save(): - from resource import setrlimit, RLIMIT_AS, RLIMIT_STACK + from resource import RLIMIT_AS, RLIMIT_STACK, setrlimit setrlimit(RLIMIT_STACK, (stack_size, stack_size)) setrlimit(RLIMIT_AS, (mem_limit, mem_limit)) diff --git a/Tests/check_j2k_overflow.py b/Tests/check_j2k_overflow.py index f20ad674819..b16412898f0 100644 --- a/Tests/check_j2k_overflow.py +++ b/Tests/check_j2k_overflow.py @@ -1,9 +1,10 @@ import pytest + from PIL import Image def test_j2k_overflow(tmp_path): im = Image.new("RGBA", (1024, 131584)) target = str(tmp_path / "temp.jpc") - with pytest.raises(IOError): + with pytest.raises(OSError): im.save(target) diff --git a/Tests/check_jp2_overflow.py b/Tests/check_jp2_overflow.py index a7a343c98ec..0210505f5fe 100755 --- a/Tests/check_jp2_overflow.py +++ b/Tests/check_jp2_overflow.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # Reproductions/tests for OOB read errors in FliDecode.c @@ -19,8 +19,8 @@ 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) + with Image.open(path) as im: + try: + im.load() + except Exception as msg: + print(msg) diff --git a/Tests/check_jpeg_leaks.py b/Tests/check_jpeg_leaks.py index b63fa2a1e59..ab8d7771992 100644 --- a/Tests/check_jpeg_leaks.py +++ b/Tests/check_jpeg_leaks.py @@ -119,60 +119,59 @@ def test_qtables_leak(): def test_exif_leak(): """ -pre patch: - - MB -177.1^ # - | @@@# - | :@@@@@@# - | ::::@@@@@@# - | ::::::::@@@@@@# - | @@::::: ::::@@@@@@# - | @@@@ ::::: ::::@@@@@@# - | @@@@@@@ ::::: ::::@@@@@@# - | @@::@@@@@@@ ::::: ::::@@@@@@# - | @@@@ : @@@@@@@ ::::: ::::@@@@@@# - | @@@@@@ @@ : @@@@@@@ ::::: ::::@@@@@@# - | @@@@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# - | @::@@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# - | ::::@: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# - | :@@: : @: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# - | ::@@::@@: : @: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# - | @@::: @ ::@@: : @: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# - | @::@ : : @ ::@@: : @: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# - | :::@: @ : : @ ::@@: : @: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# - | @@@:: @: @ : : @ ::@@: : @: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# - 0 +----------------------------------------------------------------------->Gi - 0 11.37 - - -post patch: - - MB -21.06^ ::::::::::::::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - | ##::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - | # ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - | # ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - | # ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - | @# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - | @# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - | @# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - | @# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - | @@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - | @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - | @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - | @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - | @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - | @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - | @@@@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - | @ @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - | @ @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - | @ @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - | @ @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - 0 +----------------------------------------------------------------------->Gi - 0 11.33 - -""" + pre patch: + + MB + 177.1^ # + | @@@# + | :@@@@@@# + | ::::@@@@@@# + | ::::::::@@@@@@# + | @@::::: ::::@@@@@@# + | @@@@ ::::: ::::@@@@@@# + | @@@@@@@ ::::: ::::@@@@@@# + | @@::@@@@@@@ ::::: ::::@@@@@@# + | @@@@ : @@@@@@@ ::::: ::::@@@@@@# + | @@@@@@ @@ : @@@@@@@ ::::: ::::@@@@@@# + | @@@@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# + | @::@@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# + | ::::@: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# + | :@@: : @: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# + | ::@@::@@: : @: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# + | @@::: @ ::@@: : @: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# + | @::@ : : @ ::@@: : @: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# + | :::@: @ : : @ ::@@: : @: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# + | @@@:: @: @ : : @ ::@@: : @: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# + 0 +----------------------------------------------------------------------->Gi + 0 11.37 + + + post patch: + + MB + 21.06^ ::::::::::::::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + | ##::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + | # ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + | # ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + | # ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + | @# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + | @# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + | @# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + | @# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + | @@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + | @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + | @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + | @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + | @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + | @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + | @@@@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + | @ @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + | @ @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + | @ @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + | @ @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + 0 +----------------------------------------------------------------------->Gi + 0 11.33 + """ im = hopper("RGB") exif = b"12345678" * 4096 @@ -183,31 +182,30 @@ def test_exif_leak(): def test_base_save(): """ -base case: - MB -20.99^ ::::: :::::::::::::::::::::::::::::::::::::::::::@::: - | ##: : ::::::@::::::: :::: :::: : : : : : : :::::::::::: :::@::: - | # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: - | # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: - | # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: - | @@# : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: - | @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: - | @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: - | @@@ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: - | @ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: - | @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: - | @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: - | @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: - | @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: - | @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: - | :@@@@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: - | :@ @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: - | :@ @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: - | :@ @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: - | :@ @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: - 0 +----------------------------------------------------------------------->Gi - 0 7.882 -""" + base case: + MB + 20.99^ ::::: :::::::::::::::::::::::::::::::::::::::::::@::: + | ##: : ::::::@::::::: :::: :::: : : : : : : :::::::::::: :::@::: + | # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: + | # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: + | # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: + | @@# : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: + | @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: + | @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: + | @@@ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: + | @ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: + | @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: + | @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: + | @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: + | @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: + | @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: + | :@@@@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: + | :@ @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: + | :@ @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: + | :@ @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: + | :@ @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: + 0 +----------------------------------------------------------------------->Gi + 0 7.882""" im = hopper("RGB") for _ in range(iterations): diff --git a/Tests/check_large_memory.py b/Tests/check_large_memory.py index f44a5a5bb37..c191ffc1eb8 100644 --- a/Tests/check_large_memory.py +++ b/Tests/check_large_memory.py @@ -1,6 +1,7 @@ import sys import pytest + from PIL import Image # This test is not run automatically. @@ -32,7 +33,7 @@ def _write_png(tmp_path, xdim, ydim): def test_large(tmp_path): - """ succeeded prepatch""" + """succeeded prepatch""" _write_png(tmp_path, XDIM, YDIM) diff --git a/Tests/check_large_memory_numpy.py b/Tests/check_large_memory_numpy.py index de6f4571cb8..70ae6d230b8 100644 --- a/Tests/check_large_memory_numpy.py +++ b/Tests/check_large_memory_numpy.py @@ -1,6 +1,7 @@ import sys import pytest + from PIL import Image # This test is not run automatically. @@ -30,7 +31,7 @@ def _write_png(tmp_path, xdim, ydim): def test_large(tmp_path): - """ succeeded prepatch""" + """succeeded prepatch""" _write_png(tmp_path, XDIM, YDIM) diff --git a/Tests/check_libtiff_segfault.py b/Tests/check_libtiff_segfault.py index 5187385d621..bd7f407e4ab 100644 --- a/Tests/check_libtiff_segfault.py +++ b/Tests/check_libtiff_segfault.py @@ -1,14 +1,15 @@ import pytest + from PIL import Image TEST_FILE = "Tests/images/libtiff_segfault.tif" def test_libtiff_segfault(): - """ This test should not segfault. It will on Pillow <= 3.1.0 and - libtiff >= 4.0.0 - """ + """This test should not segfault. It will on Pillow <= 3.1.0 and + libtiff >= 4.0.0 + """ - with pytest.raises(IOError): + 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 86eb937e9ba..d8d645189e6 100644 --- a/Tests/check_png_dos.py +++ b/Tests/check_png_dos.py @@ -42,8 +42,8 @@ def test_dos_total_memory(): 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) + 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) diff --git a/Tests/check_tiff_crashes.py b/Tests/check_tiff_crashes.py deleted file mode 100644 index f4eb0437514..00000000000 --- a/Tests/check_tiff_crashes.py +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env python - -# 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 python check_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. - - -from PIL import Image - -repro_read_strip = ( - "images/crash_1.tif", - "images/crash_2.tif", -) - -for path in repro_read_strip: - with Image.open(path) as im: - try: - im.load() - except Exception as msg: - print(msg) diff --git a/Tests/conftest.py b/Tests/conftest.py index 624eab73c25..66da7593c2e 100644 --- a/Tests/conftest.py +++ b/Tests/conftest.py @@ -9,4 +9,23 @@ 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" % e + return f"pytest_report_header failed: {e}" + + +def pytest_configure(config): + config.addinivalue_line( + "markers", + "pil_noop_mark: A conditional mark where nothing special happens", + ) + + # We're marking some tests to ignore valgrind errors and XFAIL them. + # Ensure that the mark is defined + # even in cases where pytest-valgrind isn't installed + try: + config.addinivalue_line( + "markers", + "valgrind_known_error: Tests that have known issues with valgrind", + ) + except Exception: + # valgrind is already installed + pass diff --git a/Tests/createfontdatachunk.py b/Tests/createfontdatachunk.py index c7055995e37..e318eb73217 100755 --- a/Tests/createfontdatachunk.py +++ b/Tests/createfontdatachunk.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 import base64 import os @@ -6,7 +6,7 @@ # 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 00000000000..d8eabb3b6a3 Binary files /dev/null and b/Tests/fonts/BungeeColor-Regular_colr_Windows.ttf differ diff --git a/Tests/fonts/DejaVuSans-bitmap.ttf b/Tests/fonts/DejaVuSans-bitmap.ttf deleted file mode 100644 index 702cce37de2..00000000000 Binary files a/Tests/fonts/DejaVuSans-bitmap.ttf and /dev/null differ diff --git a/Tests/fonts/DejaVuSans/DejaVuSans-24-1-stripped.ttf b/Tests/fonts/DejaVuSans/DejaVuSans-24-1-stripped.ttf new file mode 100644 index 00000000000..8eaf1ee0811 Binary files /dev/null and b/Tests/fonts/DejaVuSans/DejaVuSans-24-1-stripped.ttf differ diff --git a/Tests/fonts/DejaVuSans/DejaVuSans-24-2-stripped.ttf b/Tests/fonts/DejaVuSans/DejaVuSans-24-2-stripped.ttf new file mode 100644 index 00000000000..23366725106 Binary files /dev/null and b/Tests/fonts/DejaVuSans/DejaVuSans-24-2-stripped.ttf differ diff --git a/Tests/fonts/DejaVuSans/DejaVuSans-24-4-stripped.ttf b/Tests/fonts/DejaVuSans/DejaVuSans-24-4-stripped.ttf new file mode 100644 index 00000000000..9accc9ebcaf Binary files /dev/null and b/Tests/fonts/DejaVuSans/DejaVuSans-24-4-stripped.ttf differ diff --git a/Tests/fonts/DejaVuSans/DejaVuSans-24-8-stripped.ttf b/Tests/fonts/DejaVuSans/DejaVuSans-24-8-stripped.ttf new file mode 100644 index 00000000000..0f93442678d Binary files /dev/null and b/Tests/fonts/DejaVuSans/DejaVuSans-24-8-stripped.ttf differ diff --git a/Tests/fonts/DejaVuSans.ttf b/Tests/fonts/DejaVuSans/DejaVuSans.ttf similarity index 100% rename from Tests/fonts/DejaVuSans.ttf rename to Tests/fonts/DejaVuSans/DejaVuSans.ttf diff --git a/Tests/fonts/DejaVuSans/LICENSE.txt b/Tests/fonts/DejaVuSans/LICENSE.txt new file mode 100644 index 00000000000..30516578fb2 --- /dev/null +++ b/Tests/fonts/DejaVuSans/LICENSE.txt @@ -0,0 +1,40 @@ +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. + +DejaVu Fonts — License +Fonts are © Bitstream (see below). DejaVu changes are in public domain. Explanation of copyright is on Gnome page on Bitstream Vera fonts. Glyphs imported from Arev fonts are © Tavmjung Bah (see below) + +Bitstream Vera Fonts Copyright +Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is a trademark of Bitstream, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy of the fonts accompanying this license ("Fonts") and associated documentation files (the "Font Software"), to reproduce and distribute the Font Software, including without limitation the rights to use, copy, merge, publish, distribute, and/or sell copies of the Font Software, and to permit persons to whom the Font Software is furnished to do so, subject to the following conditions: + +The above copyright and trademark notices and this permission notice shall be included in all copies of one or more of the Font Software typefaces. + +The Font Software may be modified, altered, or added to, and in particular the designs of glyphs or characters in the Fonts may be modified and additional glyphs or characters may be added to the Fonts, only if the fonts are renamed to names not containing either the words "Bitstream" or the word "Vera". + +This License becomes null and void to the extent applicable to Fonts or Font Software that has been modified and is distributed under the "Bitstream Vera" names. + +The Font Software may be sold as part of a larger software package but no copy of one or more of the Font Software typefaces may be sold by itself. + +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. + +Except as contained in this notice, the names of Gnome, the Gnome Foundation, and Bitstream Inc., shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Font Software without prior written authorization from the Gnome Foundation or Bitstream Inc., respectively. For further information, contact: fonts at gnome dot org. + +Arev Fonts Copyright +Original text + +Copyright (c) 2006 by Tavmjong Bah. All Rights Reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of the fonts accompanying this license ("Fonts") and associated documentation files (the "Font Software"), to reproduce and distribute the modifications to the Bitstream Vera Font Software, including without limitation the rights to use, copy, merge, publish, distribute, and/or sell copies of the Font Software, and to permit persons to whom the Font Software is furnished to do so, subject to the following conditions: + +The above copyright and trademark notices and this permission notice shall be included in all copies of one or more of the Font Software typefaces. + +The Font Software may be modified, altered, or added to, and in particular the designs of glyphs or characters in the Fonts may be modified and additional glyphs or characters may be added to the Fonts, only if the fonts are renamed to names not containing either the words "Tavmjong Bah" or the word "Arev". + +This License becomes null and void to the extent applicable to Fonts or Font Software that has been modified and is distributed under the "Tavmjong Bah Arev" names. + +The Font Software may be sold as part of a larger software package but no copy of one or more of the Font Software typefaces may be sold by itself. + +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL TAVMJONG BAH BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. + +Except as contained in this notice, the name of Tavmjong Bah shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Font Software without prior written authorization from Tavmjong Bah. For further information, contact: tavmjong @ free . fr. \ No newline at end of file diff --git a/Tests/fonts/LICENSE.txt b/Tests/fonts/LICENSE.txt index b9488cb94e1..104ff677cad 100644 --- a/Tests/fonts/LICENSE.txt +++ b/Tests/fonts/LICENSE.txt @@ -1,13 +1,25 @@ 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) + +chromacheck-sbix.woff, from https://github.com/RoelN/ChromaCheck, under The MIT License (MIT), Copyright (c) 2018 Roel Nieskens, https://pixelambacht.nl Copyright (c) 2018 Google LLC + +KhmerOSBattambang-Regular.ttf is licensed under LGPL-2.1 or later. + +FreeMono.ttf is licensed under GPLv3. 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 00000000000..ef7b725758c 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 00000000000..a1b8994edea 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 00000000000..b4ee4951f33 Binary files /dev/null and b/Tests/fonts/OpenSansCondensed-LightItalic.ttf differ diff --git a/Tests/fonts/chromacheck-sbix.woff b/Tests/fonts/chromacheck-sbix.woff new file mode 100644 index 00000000000..518d4b7ea6c Binary files /dev/null and b/Tests/fonts/chromacheck-sbix.woff differ diff --git a/Tests/fonts/oom-e8e927ba6c0d38274a37c1567560eb33baf74627.ttf b/Tests/fonts/oom-e8e927ba6c0d38274a37c1567560eb33baf74627.ttf new file mode 100644 index 00000000000..79013251524 Binary files /dev/null and b/Tests/fonts/oom-e8e927ba6c0d38274a37c1567560eb33baf74627.ttf differ diff --git a/Tests/helper.py b/Tests/helper.py index 15a51ccd1e9..8504993fb5d 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -6,10 +6,13 @@ import os import shutil import sys +import sysconfig import tempfile from io import BytesIO import pytest +from packaging.version import parse as parse_version + from PIL import Image, ImageMath, features logger = logging.getLogger(__name__) @@ -66,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 @@ -111,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) @@ -128,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 @@ -164,17 +158,36 @@ 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 = "%s not available" % feature + reason = f"{feature} not available" return pytest.mark.skipif(not features.check(feature), reason=reason) +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) + + +def mark_if_feature_version(mark, feature, version_blacklist, reason=None): + if not features.check(feature): + return pytest.mark.pil_noop_mark() + if reason is None: + reason = f"{feature} is {version_blacklist}" + version_required = parse_version(version_blacklist) + version_available = parse_version(features.version(feature)) + if ( + version_available.major == version_required.major + and version_available.minor == version_required.minor + ): + return mark(reason=reason) + return pytest.mark.pil_noop_mark() + + @pytest.mark.skipif(sys.platform.startswith("win32"), reason="Requires Unix or macOS") class PillowLeakTestCase: # requires unix/macOS @@ -189,7 +202,7 @@ def _get_mem_usage(self): :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": @@ -209,7 +222,7 @@ def _test_leak(self, core): for cycle in range(self.iterations): core() mem = self._get_mem_usage() - start_mem - msg = "memory usage limit exceeded in iteration %d" % cycle + msg = f"memory usage limit exceeded in iteration {cycle}" assert mem < self.mem_limit, msg @@ -259,8 +272,23 @@ def netpbm_available(): return bool(shutil.which("ppmquant") and shutil.which("ppmtogif")) -def imagemagick_available(): - return bool(IMCONVERT and shutil.which(IMCONVERT)) +def magick_command(): + if sys.platform == "win32": + magickhome = os.environ.get("MAGICK_HOME", "") + if magickhome: + imagemagick = [os.path.join(magickhome, "convert.exe")] + graphicsmagick = [os.path.join(magickhome, "gm.exe"), "convert"] + else: + imagemagick = None + graphicsmagick = None + else: + imagemagick = ["convert"] + graphicsmagick = ["gm", "convert"] + + if imagemagick and shutil.which(imagemagick[0]): + return imagemagick + elif graphicsmagick and shutil.which(graphicsmagick[0]): + return graphicsmagick def on_appveyor(): @@ -272,18 +300,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") @@ -292,12 +322,8 @@ def is_pypy(): return hasattr(sys, "pypy_translation_info") -if sys.platform == "win32": - IMCONVERT = os.environ.get("MAGICK_HOME", "") - if IMCONVERT: - IMCONVERT = os.path.join(IMCONVERT, "convert.exe") -else: - IMCONVERT = "convert" +def is_mingw(): + return sysconfig.get_platform() == "mingw" class cached_property: diff --git a/Tests/images/200x32_p_bl_raw_origin.tga b/Tests/images/200x32_p_bl_raw_origin.tga new file mode 100644 index 00000000000..329f0ca4d9e Binary files /dev/null and b/Tests/images/200x32_p_bl_raw_origin.tga 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 00000000000..1da9293de9b 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 00000000000..57177fe2bb8 Binary files /dev/null and b/Tests/images/DXGI_FORMAT_R8G8B8A8_UNORM_SRGB.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 00000000000..e5fb4784d26 Binary files /dev/null and b/Tests/images/apng/dispose_op_background_p_mode.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 00000000000..14168da8992 Binary files /dev/null and b/Tests/images/apng/dispose_op_previous_frame.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 00000000000..d1d1998b1b3 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 00000000000..3570ccf355e Binary files /dev/null and b/Tests/images/argb-32bpp_MipMaps-1.png differ diff --git a/Tests/images/balloon_eciRGBv2_aware.jp2 b/Tests/images/balloon_eciRGBv2_aware.jp2 new file mode 100644 index 00000000000..18fd1e1723d Binary files /dev/null and b/Tests/images/balloon_eciRGBv2_aware.jp2 differ diff --git a/Tests/images/bc5_snorm.dds b/Tests/images/bc5_snorm.dds new file mode 100644 index 00000000000..7458c67c6ad Binary files /dev/null and b/Tests/images/bc5_snorm.dds differ diff --git a/Tests/images/bc5_typeless.dds b/Tests/images/bc5_typeless.dds new file mode 100644 index 00000000000..b5bae52bb95 Binary files /dev/null and b/Tests/images/bc5_typeless.dds differ diff --git a/Tests/images/bc5_unorm.dds b/Tests/images/bc5_unorm.dds new file mode 100644 index 00000000000..a04a026eb1f Binary files /dev/null and b/Tests/images/bc5_unorm.dds differ diff --git a/Tests/images/bc5_unorm.png b/Tests/images/bc5_unorm.png new file mode 100644 index 00000000000..05279ddfbe6 Binary files /dev/null and b/Tests/images/bc5_unorm.png differ diff --git a/Tests/images/bc5s.dds b/Tests/images/bc5s.dds new file mode 100644 index 00000000000..0b999eed320 Binary files /dev/null and b/Tests/images/bc5s.dds differ diff --git a/Tests/images/bc5s.png b/Tests/images/bc5s.png new file mode 100644 index 00000000000..39d7811bf2e Binary files /dev/null and b/Tests/images/bc5s.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 00000000000..01a05606c0a 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 00000000000..560efb68598 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 00000000000..44d137dd67c 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 00000000000..7a40bd6c239 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 00000000000..e79d86aa886 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 00000000000..d98a3bc3ee1 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 00000000000..15a7c980914 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 00000000000..1ad088c9362 Binary files /dev/null and b/Tests/images/bitmap_font_8_raqm.png differ diff --git a/Tests/images/bitmap_font_stroke_basic.png b/Tests/images/bitmap_font_stroke_basic.png new file mode 100644 index 00000000000..86b2d09f66e Binary files /dev/null and b/Tests/images/bitmap_font_stroke_basic.png differ diff --git a/Tests/images/bitmap_font_stroke_raqm.png b/Tests/images/bitmap_font_stroke_raqm.png new file mode 100644 index 00000000000..08029ce34be Binary files /dev/null and b/Tests/images/bitmap_font_stroke_raqm.png differ diff --git a/Tests/images/black_and_white.ico b/Tests/images/black_and_white.ico new file mode 100644 index 00000000000..f98d7ac8e8d Binary files /dev/null and b/Tests/images/black_and_white.ico differ diff --git a/Tests/images/broken_exif_dpi.jpg b/Tests/images/broken_exif_dpi.jpg new file mode 100644 index 00000000000..2c88b94630b Binary files /dev/null and b/Tests/images/broken_exif_dpi.jpg differ diff --git a/Tests/images/cbdt_notocoloremoji.png b/Tests/images/cbdt_notocoloremoji.png new file mode 100644 index 00000000000..1da12fba115 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 00000000000..6d036a0b6ba Binary files /dev/null and b/Tests/images/cbdt_notocoloremoji_mask.png differ diff --git a/Tests/images/chromacheck-sbix.png b/Tests/images/chromacheck-sbix.png new file mode 100644 index 00000000000..b906ef133a4 Binary files /dev/null and b/Tests/images/chromacheck-sbix.png differ diff --git a/Tests/images/chromacheck-sbix_mask.png b/Tests/images/chromacheck-sbix_mask.png new file mode 100644 index 00000000000..4b68ff91bec Binary files /dev/null and b/Tests/images/chromacheck-sbix_mask.png differ diff --git a/Tests/images/colr_bungee.png b/Tests/images/colr_bungee.png new file mode 100644 index 00000000000..b10a60be057 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 00000000000..f13e1767749 Binary files /dev/null and b/Tests/images/colr_bungee_mask.png differ diff --git a/Tests/images/crash-0c7e0e8e11ce787078f00b5b0ca409a167f070e0.tif b/Tests/images/crash-0c7e0e8e11ce787078f00b5b0ca409a167f070e0.tif new file mode 100644 index 00000000000..5275075e917 Binary files /dev/null and b/Tests/images/crash-0c7e0e8e11ce787078f00b5b0ca409a167f070e0.tif differ diff --git a/Tests/images/crash-0da013a13571cc8eb457a39fee8db18f8a3c7127.tif b/Tests/images/crash-0da013a13571cc8eb457a39fee8db18f8a3c7127.tif new file mode 100644 index 00000000000..6e4e9b9caa5 Binary files /dev/null and b/Tests/images/crash-0da013a13571cc8eb457a39fee8db18f8a3c7127.tif differ diff --git a/Tests/images/crash-0e16d3bfb83be87356d026d66919deaefca44dac.tif b/Tests/images/crash-0e16d3bfb83be87356d026d66919deaefca44dac.tif new file mode 100644 index 00000000000..f59aab21afe Binary files /dev/null and b/Tests/images/crash-0e16d3bfb83be87356d026d66919deaefca44dac.tif differ diff --git a/Tests/images/crash-1152ec2d1a1a71395b6f2ce6721c38924d025bf3.tif b/Tests/images/crash-1152ec2d1a1a71395b6f2ce6721c38924d025bf3.tif new file mode 100644 index 00000000000..c8d6e2aada3 Binary files /dev/null and b/Tests/images/crash-1152ec2d1a1a71395b6f2ce6721c38924d025bf3.tif differ diff --git a/Tests/images/crash-1185209cf7655b5aed8ae5e77784dfdd18ab59e9.tif b/Tests/images/crash-1185209cf7655b5aed8ae5e77784dfdd18ab59e9.tif new file mode 100644 index 00000000000..ecf7db38f2e Binary files /dev/null and b/Tests/images/crash-1185209cf7655b5aed8ae5e77784dfdd18ab59e9.tif differ diff --git a/Tests/images/crash-2020-10-test.tif b/Tests/images/crash-2020-10-test.tif new file mode 100644 index 00000000000..958cdde2209 Binary files /dev/null and b/Tests/images/crash-2020-10-test.tif differ diff --git a/Tests/images/crash-338516dbd2f0e83caddb8ce256c22db3bd6dc40f.tif b/Tests/images/crash-338516dbd2f0e83caddb8ce256c22db3bd6dc40f.tif new file mode 100644 index 00000000000..344d62b277a Binary files /dev/null and b/Tests/images/crash-338516dbd2f0e83caddb8ce256c22db3bd6dc40f.tif differ diff --git a/Tests/images/crash-465703f71a0f0094873a3e0e82c9f798161171b8.sgi b/Tests/images/crash-465703f71a0f0094873a3e0e82c9f798161171b8.sgi new file mode 100644 index 00000000000..81ae1182391 Binary files /dev/null and b/Tests/images/crash-465703f71a0f0094873a3e0e82c9f798161171b8.sgi differ diff --git a/Tests/images/crash-4f085cc12ece8cde18758d42608bed6a2a2cfb1c.tif b/Tests/images/crash-4f085cc12ece8cde18758d42608bed6a2a2cfb1c.tif new file mode 100644 index 00000000000..18197c15f1a Binary files /dev/null and b/Tests/images/crash-4f085cc12ece8cde18758d42608bed6a2a2cfb1c.tif differ diff --git a/Tests/images/crash-4fb027452e6988530aa5dabee76eecacb3b79f8a.j2k b/Tests/images/crash-4fb027452e6988530aa5dabee76eecacb3b79f8a.j2k new file mode 100644 index 00000000000..c9bd7fc0a8d Binary files /dev/null and b/Tests/images/crash-4fb027452e6988530aa5dabee76eecacb3b79f8a.j2k differ diff --git a/Tests/images/crash-5762152299364352.fli b/Tests/images/crash-5762152299364352.fli new file mode 100644 index 00000000000..944fe0b56c7 Binary files /dev/null and b/Tests/images/crash-5762152299364352.fli differ diff --git a/Tests/images/crash-63b1dffefc8c075ddc606c0a2f5fdc15ece78863.tif b/Tests/images/crash-63b1dffefc8c075ddc606c0a2f5fdc15ece78863.tif new file mode 100644 index 00000000000..b89203f75c4 Binary files /dev/null and b/Tests/images/crash-63b1dffefc8c075ddc606c0a2f5fdc15ece78863.tif differ diff --git a/Tests/images/crash-64834657ee604b8797bf99eac6a194c124a9a8ba.sgi b/Tests/images/crash-64834657ee604b8797bf99eac6a194c124a9a8ba.sgi new file mode 100644 index 00000000000..f31d810e4c3 Binary files /dev/null and b/Tests/images/crash-64834657ee604b8797bf99eac6a194c124a9a8ba.sgi differ diff --git a/Tests/images/crash-6b7f2244da6d0ae297ee0754a424213444e92778.sgi b/Tests/images/crash-6b7f2244da6d0ae297ee0754a424213444e92778.sgi new file mode 100644 index 00000000000..74396935b9a Binary files /dev/null and b/Tests/images/crash-6b7f2244da6d0ae297ee0754a424213444e92778.sgi differ diff --git a/Tests/images/crash-74d2a78403a5a59db1fb0a2b8735ac068a75f6e3.tif b/Tests/images/crash-74d2a78403a5a59db1fb0a2b8735ac068a75f6e3.tif new file mode 100644 index 00000000000..053e4e4e952 Binary files /dev/null and b/Tests/images/crash-74d2a78403a5a59db1fb0a2b8735ac068a75f6e3.tif differ diff --git a/Tests/images/crash-754d9c7ec485ffb76a90eeaab191ef69a2a3a3cd.sgi b/Tests/images/crash-754d9c7ec485ffb76a90eeaab191ef69a2a3a3cd.sgi new file mode 100644 index 00000000000..8e093bdfd72 Binary files /dev/null and b/Tests/images/crash-754d9c7ec485ffb76a90eeaab191ef69a2a3a3cd.sgi differ diff --git a/Tests/images/crash-7d4c83eb92150fb8f1653a697703ae06ae7c4998.j2k b/Tests/images/crash-7d4c83eb92150fb8f1653a697703ae06ae7c4998.j2k new file mode 100644 index 00000000000..fd2f4dd3677 Binary files /dev/null and b/Tests/images/crash-7d4c83eb92150fb8f1653a697703ae06ae7c4998.j2k differ diff --git a/Tests/images/crash-86214e58da443d2b80820cff9677a38a33dcbbca.tif b/Tests/images/crash-86214e58da443d2b80820cff9677a38a33dcbbca.tif new file mode 100644 index 00000000000..34e4f6014af Binary files /dev/null and b/Tests/images/crash-86214e58da443d2b80820cff9677a38a33dcbbca.tif differ diff --git a/Tests/images/crash-abcf1c97b8fe42a6c68f1fb0b978530c98d57ced.sgi b/Tests/images/crash-abcf1c97b8fe42a6c68f1fb0b978530c98d57ced.sgi new file mode 100644 index 00000000000..790cb37449e Binary files /dev/null and b/Tests/images/crash-abcf1c97b8fe42a6c68f1fb0b978530c98d57ced.sgi differ diff --git a/Tests/images/crash-b82e64d4f3f76d7465b6af535283029eda211259.sgi b/Tests/images/crash-b82e64d4f3f76d7465b6af535283029eda211259.sgi new file mode 100644 index 00000000000..8b7d8776591 Binary files /dev/null and b/Tests/images/crash-b82e64d4f3f76d7465b6af535283029eda211259.sgi differ diff --git a/Tests/images/crash-c1b2595b8b0b92cc5f38b6635e98e3a119ade807.sgi b/Tests/images/crash-c1b2595b8b0b92cc5f38b6635e98e3a119ade807.sgi new file mode 100644 index 00000000000..e9d2ca1a6f2 Binary files /dev/null and b/Tests/images/crash-c1b2595b8b0b92cc5f38b6635e98e3a119ade807.sgi differ diff --git a/Tests/images/crash-ccca68ff40171fdae983d924e127a721cab2bd50.j2k b/Tests/images/crash-ccca68ff40171fdae983d924e127a721cab2bd50.j2k new file mode 100644 index 00000000000..c3ad0d6330a Binary files /dev/null and b/Tests/images/crash-ccca68ff40171fdae983d924e127a721cab2bd50.j2k differ diff --git a/Tests/images/crash-d2c93af851d3ab9a19e34503626368b2ecde9c03.j2k b/Tests/images/crash-d2c93af851d3ab9a19e34503626368b2ecde9c03.j2k new file mode 100644 index 00000000000..3aadfc37727 Binary files /dev/null and b/Tests/images/crash-d2c93af851d3ab9a19e34503626368b2ecde9c03.j2k differ diff --git a/Tests/images/crash-db8bfa78b19721225425530c5946217720d7df4e.sgi b/Tests/images/crash-db8bfa78b19721225425530c5946217720d7df4e.sgi new file mode 100644 index 00000000000..b02aacea9c3 Binary files /dev/null and b/Tests/images/crash-db8bfa78b19721225425530c5946217720d7df4e.sgi differ diff --git a/Tests/images/crash-f46f5b2f43c370fe65706c11449f567ecc345e74.tif b/Tests/images/crash-f46f5b2f43c370fe65706c11449f567ecc345e74.tif new file mode 100644 index 00000000000..c6774d4591a Binary files /dev/null and b/Tests/images/crash-f46f5b2f43c370fe65706c11449f567ecc345e74.tif differ diff --git a/Tests/images/crash_1.tif b/Tests/images/crash_1.tif deleted file mode 100644 index 230d4439aad..00000000000 Binary files a/Tests/images/crash_1.tif and /dev/null differ diff --git a/Tests/images/crash_2.tif b/Tests/images/crash_2.tif deleted file mode 100644 index 26c00d0ff1a..00000000000 Binary files a/Tests/images/crash_2.tif and /dev/null differ diff --git a/Tests/images/different_transparency.gif b/Tests/images/different_transparency.gif new file mode 100644 index 00000000000..2d36bef9e36 Binary files /dev/null and b/Tests/images/different_transparency.gif differ diff --git a/Tests/images/different_transparency_merged.png b/Tests/images/different_transparency_merged.png new file mode 100644 index 00000000000..3438f62a6f4 Binary files /dev/null and b/Tests/images/different_transparency_merged.png differ diff --git a/Tests/images/dispose_bgnd_rgba.gif b/Tests/images/dispose_bgnd_rgba.gif new file mode 100644 index 00000000000..c18a0ba71f1 Binary files /dev/null and b/Tests/images/dispose_bgnd_rgba.gif differ diff --git a/Tests/images/dispose_bgnd_transparency.gif b/Tests/images/dispose_bgnd_transparency.gif new file mode 100644 index 00000000000..7c626fe72c0 Binary files /dev/null and b/Tests/images/dispose_bgnd_transparency.gif differ diff --git a/Tests/images/dispose_none_load_end.gif b/Tests/images/dispose_none_load_end.gif new file mode 100644 index 00000000000..3c94eb1b6e0 Binary files /dev/null and b/Tests/images/dispose_none_load_end.gif differ diff --git a/Tests/images/dispose_none_load_end_second.png b/Tests/images/dispose_none_load_end_second.png new file mode 100644 index 00000000000..dc01ccbdd0c Binary files /dev/null and b/Tests/images/dispose_none_load_end_second.png differ diff --git a/Tests/images/dispose_prev_first_frame.gif b/Tests/images/dispose_prev_first_frame.gif new file mode 100644 index 00000000000..4c19dd1ed43 Binary files /dev/null and b/Tests/images/dispose_prev_first_frame.gif differ diff --git a/Tests/images/dispose_prev_first_frame_seeked.png b/Tests/images/dispose_prev_first_frame_seeked.png new file mode 100644 index 00000000000..85a3753e16d Binary files /dev/null and b/Tests/images/dispose_prev_first_frame_seeked.png differ diff --git a/Tests/images/drawing_roundDown.emf b/Tests/images/drawing_roundDown.emf deleted file mode 100644 index 6c3e20248c8..00000000000 Binary files a/Tests/images/drawing_roundDown.emf and /dev/null differ diff --git a/Tests/images/dxt5-colorblock-alpha-issue-4142.dds b/Tests/images/dxt5-colorblock-alpha-issue-4142.dds new file mode 100644 index 00000000000..905527eada4 Binary files /dev/null and b/Tests/images/dxt5-colorblock-alpha-issue-4142.dds differ diff --git a/Tests/images/empty_gps_ifd.jpg b/Tests/images/empty_gps_ifd.jpg new file mode 100644 index 00000000000..28f180b8743 Binary files /dev/null and b/Tests/images/empty_gps_ifd.jpg differ diff --git a/Tests/images/exif_imagemagick_orientation.png b/Tests/images/exif_imagemagick_orientation.png new file mode 100644 index 00000000000..819a0703f83 Binary files /dev/null and b/Tests/images/exif_imagemagick_orientation.png differ diff --git a/Tests/images/exif_text.png b/Tests/images/exif_text.png new file mode 100644 index 00000000000..e2d8dc0fffa Binary files /dev/null and b/Tests/images/exif_text.png differ diff --git a/Tests/images/expected_to_read.jp2 b/Tests/images/expected_to_read.jp2 new file mode 100644 index 00000000000..d8029a0d3a0 Binary files /dev/null and b/Tests/images/expected_to_read.jp2 differ diff --git a/Tests/images/first_frame_transparency.gif b/Tests/images/first_frame_transparency.gif new file mode 100644 index 00000000000..86dc0de64a9 Binary files /dev/null and b/Tests/images/first_frame_transparency.gif differ diff --git a/Tests/images/hopper.dds b/Tests/images/hopper.dds new file mode 100644 index 00000000000..8b9af9ed9a4 Binary files /dev/null and b/Tests/images/hopper.dds differ diff --git a/Tests/images/hopper_16bit_qtables.jpg b/Tests/images/hopper_16bit_qtables.jpg new file mode 100644 index 00000000000..88abef943b9 Binary files /dev/null and b/Tests/images/hopper_16bit_qtables.jpg differ diff --git a/Tests/images/hopper_roundUp_2.tif b/Tests/images/hopper_float_dpi_2.tif similarity index 100% rename from Tests/images/hopper_roundUp_2.tif rename to Tests/images/hopper_float_dpi_2.tif diff --git a/Tests/images/hopper_roundUp_3.tif b/Tests/images/hopper_float_dpi_3.tif similarity index 100% rename from Tests/images/hopper_roundUp_3.tif rename to Tests/images/hopper_float_dpi_3.tif diff --git a/Tests/images/hopper_roundUp_None.tif b/Tests/images/hopper_float_dpi_None.tif similarity index 100% rename from Tests/images/hopper_roundUp_None.tif rename to Tests/images/hopper_float_dpi_None.tif diff --git a/Tests/images/hopper_mask.ico b/Tests/images/hopper_mask.ico new file mode 100644 index 00000000000..e8d66c689fd Binary files /dev/null and b/Tests/images/hopper_mask.ico differ diff --git a/Tests/images/hopper_mask.png b/Tests/images/hopper_mask.png new file mode 100644 index 00000000000..c7bd2f70842 Binary files /dev/null and b/Tests/images/hopper_mask.png differ diff --git a/Tests/images/hopper_naxis_zero.fits b/Tests/images/hopper_naxis_zero.fits new file mode 100644 index 00000000000..580cf3a2c00 Binary files /dev/null and b/Tests/images/hopper_naxis_zero.fits differ diff --git a/Tests/images/hopper_resized.gif b/Tests/images/hopper_resized.gif new file mode 100644 index 00000000000..f7be6c26298 Binary files /dev/null and b/Tests/images/hopper_resized.gif differ diff --git a/Tests/images/hopper_roundDown.bmp b/Tests/images/hopper_roundDown.bmp deleted file mode 100644 index 62aada05067..00000000000 Binary files a/Tests/images/hopper_roundDown.bmp and /dev/null differ diff --git a/Tests/images/hopper_roundDown_2.tif b/Tests/images/hopper_roundDown_2.tif deleted file mode 100644 index ac8cd057d61..00000000000 Binary files a/Tests/images/hopper_roundDown_2.tif and /dev/null differ diff --git a/Tests/images/hopper_roundDown_3.tif b/Tests/images/hopper_roundDown_3.tif deleted file mode 100644 index 0542fab9aa8..00000000000 Binary files a/Tests/images/hopper_roundDown_3.tif and /dev/null differ diff --git a/Tests/images/hopper_roundDown_None.tif b/Tests/images/hopper_roundDown_None.tif deleted file mode 100644 index 21c40e8fe6f..00000000000 Binary files a/Tests/images/hopper_roundDown_None.tif and /dev/null differ diff --git a/Tests/images/hopper_wal.png b/Tests/images/hopper_wal.png new file mode 100644 index 00000000000..b6067c219c4 Binary files /dev/null and b/Tests/images/hopper_wal.png differ diff --git a/Tests/images/icc-after-SOF.jpg b/Tests/images/icc-after-SOF.jpg new file mode 100644 index 00000000000..a284a2298dc 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 00000000000..316d2089e35 Binary files /dev/null and b/Tests/images/ifd_tag_type.tiff differ diff --git a/Tests/images/ignore_frame_size.mpo b/Tests/images/ignore_frame_size.mpo new file mode 100644 index 00000000000..c4d60707a47 Binary files /dev/null and b/Tests/images/ignore_frame_size.mpo differ diff --git a/Tests/images/imagedraw/continuous_horizontal_edges_polygon.png b/Tests/images/imagedraw/continuous_horizontal_edges_polygon.png new file mode 100644 index 00000000000..beffed5b918 Binary files /dev/null and b/Tests/images/imagedraw/continuous_horizontal_edges_polygon.png differ diff --git a/Tests/images/imagedraw/triangle_right_width.png b/Tests/images/imagedraw/triangle_right_width.png new file mode 100644 index 00000000000..57b73553a6d Binary files /dev/null and b/Tests/images/imagedraw/triangle_right_width.png differ diff --git a/Tests/images/imagedraw/triangle_right_width_no_fill.png b/Tests/images/imagedraw/triangle_right_width_no_fill.png new file mode 100644 index 00000000000..dd65be6be7b Binary files /dev/null and b/Tests/images/imagedraw/triangle_right_width_no_fill.png differ diff --git a/Tests/images/imagedraw_arc.png b/Tests/images/imagedraw_arc.png index b097743890c..967e214d9bd 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 aee48e1c6bb..191cc0b3a67 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 00000000000..e3fb66cd0c0 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 e45ad57a5c3..03bbd4b4336 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 ff3f1f0b21c..70dae7d5ff5 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 9572a60590b..6c135ab7679 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 1fb9a3c8695..f54eb1c2932 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 950d95dd6b7..e1aa95e88e5 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 a5a0078d042..6c89da9eaf8 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 af6fc766007..d4592cda109 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 00000000000..2021202fe79 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 33a59b487c4..04d3dadf8d1 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 809c3ea1cdf..77475d266c6 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 index c1c0058d766..c8ebe39175c 100644 Binary files a/Tests/images/imagedraw_chord_zero_width.png 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 e47e6e441c1..d5959cc0820 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 b52b128023c..1d310ce115a 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 25a95a6018a..a2235115af3 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_various_sizes.png b/Tests/images/imagedraw_ellipse_various_sizes.png new file mode 100644 index 00000000000..11a1be6faeb 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 00000000000..d71e175b8fd 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 ec0ca6731f6..54cfc291ca2 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 9b7be602916..4a1edc3797f 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 9d3c3326b71..e9518601980 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 index f14a279f2fe..6661b7d999e 100644 Binary files a/Tests/images/imagedraw_ellipse_zero_width.png 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 9e9cb5af006..3c71312c75d 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 2f8c091915f..41c786e7712 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 00000000000..44687478836 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 3bd69222cf3..422d92f3bba 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 c5a34e0f35e..bee6aac3b99 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 index 4ca05158327..d6ceb0b5a04 100644 Binary files a/Tests/images/imagedraw_pieslice_zero_width.png 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 00000000000..e06508a0af0 Binary files /dev/null and b/Tests/images/imagedraw_polygon_1px_high.png differ diff --git a/Tests/images/imagedraw_polygon_translucent.png b/Tests/images/imagedraw_polygon_translucent.png new file mode 100644 index 00000000000..da8d790a36f Binary files /dev/null and b/Tests/images/imagedraw_polygon_translucent.png differ diff --git a/Tests/images/imagedraw_rectangle_translucent_outline.png b/Tests/images/imagedraw_rectangle_translucent_outline.png new file mode 100644 index 00000000000..845648762cc Binary files /dev/null and b/Tests/images/imagedraw_rectangle_translucent_outline.png differ diff --git a/Tests/images/imagedraw_regular_octagon.png b/Tests/images/imagedraw_regular_octagon.png new file mode 100644 index 00000000000..7f215dc08bf Binary files /dev/null and b/Tests/images/imagedraw_regular_octagon.png differ diff --git a/Tests/images/imagedraw_rounded_rectangle.png b/Tests/images/imagedraw_rounded_rectangle.png new file mode 100644 index 00000000000..2e815f4ada2 Binary files /dev/null and b/Tests/images/imagedraw_rounded_rectangle.png differ diff --git a/Tests/images/imagedraw_rounded_rectangle_both.png b/Tests/images/imagedraw_rounded_rectangle_both.png new file mode 100644 index 00000000000..24f600e3913 Binary files /dev/null and b/Tests/images/imagedraw_rounded_rectangle_both.png differ diff --git a/Tests/images/imagedraw_rounded_rectangle_non_integer_radius_given.png b/Tests/images/imagedraw_rounded_rectangle_non_integer_radius_given.png new file mode 100644 index 00000000000..59e55b2a1e9 Binary files /dev/null and b/Tests/images/imagedraw_rounded_rectangle_non_integer_radius_given.png differ diff --git a/Tests/images/imagedraw_rounded_rectangle_non_integer_radius_height.png b/Tests/images/imagedraw_rounded_rectangle_non_integer_radius_height.png new file mode 100644 index 00000000000..c4e54896ba0 Binary files /dev/null and b/Tests/images/imagedraw_rounded_rectangle_non_integer_radius_height.png differ diff --git a/Tests/images/imagedraw_rounded_rectangle_non_integer_radius_width.png b/Tests/images/imagedraw_rounded_rectangle_non_integer_radius_width.png new file mode 100644 index 00000000000..6b0f11fa627 Binary files /dev/null and b/Tests/images/imagedraw_rounded_rectangle_non_integer_radius_width.png differ diff --git a/Tests/images/imagedraw_rounded_rectangle_x.png b/Tests/images/imagedraw_rounded_rectangle_x.png new file mode 100644 index 00000000000..4bf5211a334 Binary files /dev/null and b/Tests/images/imagedraw_rounded_rectangle_x.png differ diff --git a/Tests/images/imagedraw_rounded_rectangle_y.png b/Tests/images/imagedraw_rounded_rectangle_y.png new file mode 100644 index 00000000000..9b391b95e28 Binary files /dev/null and b/Tests/images/imagedraw_rounded_rectangle_y.png differ diff --git a/Tests/images/imagedraw_square.png b/Tests/images/imagedraw_square.png new file mode 100644 index 00000000000..fd75f2f3b0c 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 00000000000..8ab0e3c1839 Binary files /dev/null and b/Tests/images/imagedraw_square_rotate_45.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 00000000000..68073ce4827 Binary files /dev/null and b/Tests/images/imagedraw_wide_line_larger_than_int.png differ diff --git a/Tests/images/invalid_header_length.jp2 b/Tests/images/invalid_header_length.jp2 new file mode 100644 index 00000000000..c0c14f42160 Binary files /dev/null and b/Tests/images/invalid_header_length.jp2 differ diff --git a/Tests/images/iptc_roundDown.jpg b/Tests/images/iptc_roundDown.jpg deleted file mode 100644 index f98206f1826..00000000000 Binary files a/Tests/images/iptc_roundDown.jpg and /dev/null differ diff --git a/Tests/images/missing_background.gif b/Tests/images/missing_background.gif new file mode 100644 index 00000000000..550d68d8101 Binary files /dev/null and b/Tests/images/missing_background.gif differ diff --git a/Tests/images/missing_background_first_frame.png b/Tests/images/missing_background_first_frame.png new file mode 100644 index 00000000000..25237ba5d20 Binary files /dev/null and b/Tests/images/missing_background_first_frame.png differ diff --git a/Tests/images/multiline_text.png b/Tests/images/multiline_text.png index ff1308c5ef2..e39c6586ca5 100644 Binary files a/Tests/images/multiline_text.png and b/Tests/images/multiline_text.png differ diff --git a/Tests/images/multiline_text_center.png b/Tests/images/multiline_text_center.png index f44d0783a09..837c6382a97 100644 Binary files a/Tests/images/multiline_text_center.png and b/Tests/images/multiline_text_center.png differ diff --git a/Tests/images/multiline_text_right.png b/Tests/images/multiline_text_right.png index 1b32d916754..58b3bdddd87 100644 Binary files a/Tests/images/multiline_text_right.png and b/Tests/images/multiline_text_right.png differ diff --git a/Tests/images/multiline_text_spacing.png b/Tests/images/multiline_text_spacing.png index 3c3bc0f267d..3b367c7ddee 100644 Binary files a/Tests/images/multiline_text_spacing.png and b/Tests/images/multiline_text_spacing.png differ diff --git a/Tests/images/multipage_multiple_frame_loop.tiff b/Tests/images/multipage_multiple_frame_loop.tiff new file mode 100644 index 00000000000..b6759b08023 Binary files /dev/null and b/Tests/images/multipage_multiple_frame_loop.tiff differ diff --git a/Tests/images/multipage_out_of_order.tiff b/Tests/images/multipage_out_of_order.tiff new file mode 100644 index 00000000000..1576a549b58 Binary files /dev/null and b/Tests/images/multipage_out_of_order.tiff differ diff --git a/Tests/images/multipage_single_frame_loop.tiff b/Tests/images/multipage_single_frame_loop.tiff new file mode 100644 index 00000000000..26f27c421cd Binary files /dev/null and b/Tests/images/multipage_single_frame_loop.tiff differ diff --git a/Tests/images/negative_layer_count.psd b/Tests/images/negative_layer_count.psd new file mode 100644 index 00000000000..b111c2d5675 Binary files /dev/null and b/Tests/images/negative_layer_count.psd differ diff --git a/Tests/images/not_enough_data.jp2 b/Tests/images/not_enough_data.jp2 new file mode 100644 index 00000000000..2d28bb5e96b Binary files /dev/null and b/Tests/images/not_enough_data.jp2 differ diff --git a/Tests/images/odd_stride.pcx b/Tests/images/odd_stride.pcx new file mode 100644 index 00000000000..ee0c2eecaeb Binary files /dev/null and b/Tests/images/odd_stride.pcx differ diff --git a/Tests/images/old-style-jpeg-compression-no-samplesperpixel.tif b/Tests/images/old-style-jpeg-compression-no-samplesperpixel.tif new file mode 100644 index 00000000000..d43ba919220 Binary files /dev/null and b/Tests/images/old-style-jpeg-compression-no-samplesperpixel.tif differ diff --git a/Tests/images/oom-8ed3316a4109213ca96fb8a256a0bfefdece1461.icns b/Tests/images/oom-8ed3316a4109213ca96fb8a256a0bfefdece1461.icns new file mode 100644 index 00000000000..0521f5cf176 Binary files /dev/null and b/Tests/images/oom-8ed3316a4109213ca96fb8a256a0bfefdece1461.icns differ diff --git a/Tests/images/ossfuzz-4836216264589312.pcx b/Tests/images/ossfuzz-4836216264589312.pcx new file mode 100644 index 00000000000..fdde9716a0c 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 00000000000..a92c1ed019b Binary files /dev/null and b/Tests/images/ossfuzz-5730089102868480.sgi differ diff --git a/Tests/images/p_16.png b/Tests/images/p_16.png new file mode 100644 index 00000000000..e3588641277 Binary files /dev/null and b/Tests/images/p_16.png differ diff --git a/Tests/images/p_16.tga b/Tests/images/p_16.tga new file mode 100644 index 00000000000..2b2ca4c703c Binary files /dev/null and b/Tests/images/p_16.tga differ diff --git a/Tests/images/padded_idat.png b/Tests/images/padded_idat.png new file mode 100644 index 00000000000..18c5a4990cd Binary files /dev/null and b/Tests/images/padded_idat.png differ diff --git a/Tests/images/pal8_offset.bmp b/Tests/images/pal8_offset.bmp new file mode 100644 index 00000000000..24be65f22c3 Binary files /dev/null and b/Tests/images/pal8_offset.bmp differ diff --git a/Tests/images/palette_negative.png b/Tests/images/palette_negative.png new file mode 100644 index 00000000000..938a7285fd7 Binary files /dev/null and b/Tests/images/palette_negative.png differ diff --git a/Tests/images/palette_sepia.png b/Tests/images/palette_sepia.png new file mode 100644 index 00000000000..f3fc932531f Binary files /dev/null and b/Tests/images/palette_sepia.png differ diff --git a/Tests/images/palette_wedge.png b/Tests/images/palette_wedge.png new file mode 100644 index 00000000000..23fb7940d6d Binary files /dev/null and b/Tests/images/palette_wedge.png differ diff --git a/Tests/images/reqd_showpage_transparency.png b/Tests/images/reqd_showpage_transparency.png new file mode 100644 index 00000000000..3ce159d0fc0 Binary files /dev/null and b/Tests/images/reqd_showpage_transparency.png differ diff --git a/Tests/images/rgb32rle_bottom_right.tga b/Tests/images/rgb32rle_bottom_right.tga new file mode 100644 index 00000000000..bd4609e9c1c Binary files /dev/null and b/Tests/images/rgb32rle_bottom_right.tga differ diff --git a/Tests/images/rgb32rle_top_right.tga b/Tests/images/rgb32rle_top_right.tga new file mode 100644 index 00000000000..78f9dc5dfb0 Binary files /dev/null and b/Tests/images/rgb32rle_top_right.tga differ diff --git a/Tests/images/standard_embedded.png b/Tests/images/standard_embedded.png new file mode 100644 index 00000000000..8905325317f 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 d0b55830128..00000000000 Binary files a/Tests/images/string_dimension.tiff and /dev/null differ diff --git a/Tests/images/sugarshack_frame_size.mpo b/Tests/images/sugarshack_frame_size.mpo index 81d58e64b82..009280a79a6 100644 Binary files a/Tests/images/sugarshack_frame_size.mpo and b/Tests/images/sugarshack_frame_size.mpo 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 00000000000..6fff287e47c 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 00000000000..b76a81b8127 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 00000000000..c12a8d63e9b 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 00000000000..4f35d781f62 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 00000000000..8290d045cfa 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 00000000000..773cf2a4a06 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 00000000000..87d56636a13 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 00000000000..cf002b12cd0 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 00000000000..98073144bc2 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 00000000000..838fd7858a4 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 00000000000..290f5841794 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 00000000000..524c417c394 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 00000000000..cfff27f7dda 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 00000000000..ff11f478e7d 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 00000000000..5cbccb170bd 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 00000000000..500294c3b8c 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 00000000000..b1012463eb7 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 00000000000..19423e51afe 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 00000000000..20a5e6c6e73 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 00000000000..5f70a65c425 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 00000000000..e7be557d2a5 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 00000000000..b78e2f954fa 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 00000000000..f6dc7c70f0e 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 9bfa5a931cf..a03845acef3 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 00000000000..1097f4be59e 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 00000000000..6e7d88a92c7 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 00000000000..f59e722b2da 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 00000000000..540ab7d4264 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 00000000000..1109b4ee670 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 00000000000..5c7576de01b 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 00000000000..bacd6a141f1 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 00000000000..1097f4be59e 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 00000000000..1a721873cad 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 00000000000..91e50d45f1f 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 00000000000..a94be2f0af6 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 00000000000..a94be2f0af6 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 00000000000..30252107faa 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 00000000000..aea09538f7e 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 00000000000..febd3ab670c 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 00000000000..8e42c0d16da 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 00000000000..9a755e8f8d8 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 00000000000..b954a541522 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 00000000000..b6b08145e8f 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 00000000000..7b1e9c4e42f 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 00000000000..a26996c2dbe 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 00000000000..7caf5cb742a 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 00000000000..a859e9570c8 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 00000000000..aadb5191f0e 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 00000000000..8238d4ec8ca 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 00000000000..7568dd63a33 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 00000000000..b8c3b5b143d 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 00000000000..14c478a72d0 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 00000000000..dc5e8636163 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 00000000000..dc5e8636163 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 00000000000..cbb2d472dc7 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 00000000000..f74538d9c3a 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 00000000000..e915543d66c 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 00000000000..186d6ee843b 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 00000000000..e915543d66c 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 f1a6f7ec61d..61174d75f68 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 543b174c0a7..0526233c0f5 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 42239334d12..b30fcd5d819 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 966b67d6b64..282eed88393 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 825f3213ec8..52dbf572340 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 3fa844e9a89..4b689c38ec7 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_kerning_features.png b/Tests/images/test_kerning_features.png index ca895735c4d..78bcd951bba 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 8daf007b02c..c7721531892 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 664e9929d05..89ea648bfc1 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 c156399cd1b..5888e52e53c 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 f8bec3e95e5..21401813f46 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 2d57890cb5f..bda2490df11 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 00000000000..b350c10e64a Binary files /dev/null and b/Tests/images/text_mono.gif differ diff --git a/Tests/images/tiff_strip_planar_16bit_RGB.tiff b/Tests/images/tiff_strip_planar_16bit_RGB.tiff new file mode 100644 index 00000000000..360b4c16533 Binary files /dev/null and b/Tests/images/tiff_strip_planar_16bit_RGB.tiff differ diff --git a/Tests/images/tiff_strip_planar_16bit_RGBa.tiff b/Tests/images/tiff_strip_planar_16bit_RGBa.tiff new file mode 100644 index 00000000000..b8c3dcf6438 Binary files /dev/null and b/Tests/images/tiff_strip_planar_16bit_RGBa.tiff differ diff --git a/Tests/images/tiff_strip_planar_lzw.tiff b/Tests/images/tiff_strip_planar_lzw.tiff new file mode 100644 index 00000000000..8145703f430 Binary files /dev/null and b/Tests/images/tiff_strip_planar_lzw.tiff differ diff --git a/Tests/images/tiff_tiled_planar_16bit_RGB.tiff b/Tests/images/tiff_tiled_planar_16bit_RGB.tiff new file mode 100644 index 00000000000..0376e90a7be Binary files /dev/null and b/Tests/images/tiff_tiled_planar_16bit_RGB.tiff differ diff --git a/Tests/images/tiff_tiled_planar_16bit_RGBa.tiff b/Tests/images/tiff_tiled_planar_16bit_RGBa.tiff new file mode 100644 index 00000000000..ae777386704 Binary files /dev/null and b/Tests/images/tiff_tiled_planar_16bit_RGBa.tiff differ diff --git a/Tests/images/tiff_tiled_planar_lzw.tiff b/Tests/images/tiff_tiled_planar_lzw.tiff new file mode 100644 index 00000000000..57cd6094a28 Binary files /dev/null and b/Tests/images/tiff_tiled_planar_lzw.tiff differ diff --git a/Tests/images/timeout-060745d3f534ad6e4128c51d336ea5489182c69d.blp b/Tests/images/timeout-060745d3f534ad6e4128c51d336ea5489182c69d.blp new file mode 100644 index 00000000000..97def320f3a Binary files /dev/null and b/Tests/images/timeout-060745d3f534ad6e4128c51d336ea5489182c69d.blp differ diff --git a/Tests/images/timeout-1ee28a249896e05b83840ae8140622de8e648ba9.psd b/Tests/images/timeout-1ee28a249896e05b83840ae8140622de8e648ba9.psd new file mode 100644 index 00000000000..63319e545a2 Binary files /dev/null and b/Tests/images/timeout-1ee28a249896e05b83840ae8140622de8e648ba9.psd differ diff --git a/Tests/images/timeout-31c8f86233ea728339c6e586be7af661a09b5b98.blp b/Tests/images/timeout-31c8f86233ea728339c6e586be7af661a09b5b98.blp new file mode 100644 index 00000000000..73022abfc4e Binary files /dev/null and b/Tests/images/timeout-31c8f86233ea728339c6e586be7af661a09b5b98.blp differ diff --git a/Tests/images/timeout-598843abc37fc080ec36a2699ebbd44f795d3a6f.psd b/Tests/images/timeout-598843abc37fc080ec36a2699ebbd44f795d3a6f.psd new file mode 100644 index 00000000000..c259a15e7f8 Binary files /dev/null and b/Tests/images/timeout-598843abc37fc080ec36a2699ebbd44f795d3a6f.psd differ diff --git a/Tests/images/timeout-60d8b7c8469d59fc9ffff6b3a3dc0faeae6ea8ee.blp b/Tests/images/timeout-60d8b7c8469d59fc9ffff6b3a3dc0faeae6ea8ee.blp new file mode 100644 index 00000000000..79e97dce357 Binary files /dev/null and b/Tests/images/timeout-60d8b7c8469d59fc9ffff6b3a3dc0faeae6ea8ee.blp differ diff --git a/Tests/images/timeout-6646305047838720 b/Tests/images/timeout-6646305047838720 new file mode 100644 index 00000000000..eae1f333a03 Binary files /dev/null and b/Tests/images/timeout-6646305047838720 differ diff --git a/Tests/images/timeout-8073b430977660cdd48d96f6406ddfd4114e69c7.blp b/Tests/images/timeout-8073b430977660cdd48d96f6406ddfd4114e69c7.blp new file mode 100644 index 00000000000..9b9ecbcb077 Binary files /dev/null and b/Tests/images/timeout-8073b430977660cdd48d96f6406ddfd4114e69c7.blp differ diff --git a/Tests/images/timeout-9139147ce93e20eb14088fe238e541443ffd64b3.fli b/Tests/images/timeout-9139147ce93e20eb14088fe238e541443ffd64b3.fli new file mode 100644 index 00000000000..ce4607d2dd0 Binary files /dev/null and b/Tests/images/timeout-9139147ce93e20eb14088fe238e541443ffd64b3.fli differ diff --git a/Tests/images/timeout-bba4f2e026b5786529370e5dfe9a11b1bf991f07.blp b/Tests/images/timeout-bba4f2e026b5786529370e5dfe9a11b1bf991f07.blp new file mode 100644 index 00000000000..cb9a4e8b37f Binary files /dev/null and b/Tests/images/timeout-bba4f2e026b5786529370e5dfe9a11b1bf991f07.blp differ diff --git a/Tests/images/timeout-bff0a9dc7243a8e6ede2408d2ffa6a9964698b87.fli b/Tests/images/timeout-bff0a9dc7243a8e6ede2408d2ffa6a9964698b87.fli new file mode 100644 index 00000000000..77a94b87a3a Binary files /dev/null and b/Tests/images/timeout-bff0a9dc7243a8e6ede2408d2ffa6a9964698b87.fli differ diff --git a/Tests/images/timeout-c8efc3fded6426986ba867a399791bae544f59bc.psd b/Tests/images/timeout-c8efc3fded6426986ba867a399791bae544f59bc.psd new file mode 100644 index 00000000000..955fc332522 Binary files /dev/null and b/Tests/images/timeout-c8efc3fded6426986ba867a399791bae544f59bc.psd differ diff --git a/Tests/images/timeout-d675703545fee17acab56e5fec644c19979175de.eps b/Tests/images/timeout-d675703545fee17acab56e5fec644c19979175de.eps new file mode 100644 index 00000000000..5000ca9aa55 Binary files /dev/null and b/Tests/images/timeout-d675703545fee17acab56e5fec644c19979175de.eps differ diff --git a/Tests/images/timeout-d6ec061c4afdef39d3edf6da8927240bb07fe9b7.blp b/Tests/images/timeout-d6ec061c4afdef39d3edf6da8927240bb07fe9b7.blp new file mode 100644 index 00000000000..5044fbde107 Binary files /dev/null and b/Tests/images/timeout-d6ec061c4afdef39d3edf6da8927240bb07fe9b7.blp differ diff --git a/Tests/images/timeout-dedc7a4ebd856d79b4359bbcc79e8ef231ce38f6.psd b/Tests/images/timeout-dedc7a4ebd856d79b4359bbcc79e8ef231ce38f6.psd new file mode 100644 index 00000000000..c658ea45c4b Binary files /dev/null and b/Tests/images/timeout-dedc7a4ebd856d79b4359bbcc79e8ef231ce38f6.psd differ diff --git a/Tests/images/timeout-ef9112a065e7183fa7faa2e18929b03e44ee16bf.blp b/Tests/images/timeout-ef9112a065e7183fa7faa2e18929b03e44ee16bf.blp new file mode 100644 index 00000000000..7ef78eeec77 Binary files /dev/null and b/Tests/images/timeout-ef9112a065e7183fa7faa2e18929b03e44ee16bf.blp differ diff --git a/Tests/images/transparent_background_text.png b/Tests/images/transparent_background_text.png new file mode 100644 index 00000000000..8ddd65cc68b Binary files /dev/null and b/Tests/images/transparent_background_text.png differ diff --git a/Tests/images/transparent_background_text_L.png b/Tests/images/transparent_background_text_L.png new file mode 100644 index 00000000000..d37de20a734 Binary files /dev/null and b/Tests/images/transparent_background_text_L.png differ diff --git a/Tests/images/transparent_dispose.gif b/Tests/images/transparent_dispose.gif new file mode 100644 index 00000000000..92b615543de Binary files /dev/null and b/Tests/images/transparent_dispose.gif differ diff --git a/Tests/images/truncated_app14.jpg b/Tests/images/truncated_app14.jpg new file mode 100644 index 00000000000..232a4c35f8c Binary files /dev/null and b/Tests/images/truncated_app14.jpg differ diff --git a/Tests/images/uncompressed_rgb.png b/Tests/images/uncompressed_rgb.png index 50bca09eec8..f02b50f6f6f 100644 Binary files a/Tests/images/uncompressed_rgb.png and b/Tests/images/uncompressed_rgb.png differ diff --git a/Tests/images/variation_adobe.png b/Tests/images/variation_adobe.png index 71b879bc5a2..e9cfafb48b5 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 9376c1d7b8f..ad3a3a96088 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 9e5fe70e539..11ceaf6e65b 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 00000000000..5abc907caab 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 00000000000..b39d460f977 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 00000000000..2adb517a759 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 d06ac7a60e7..8cb6d1f62a4 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 a0c6ffe3f1f..69f1550dbfc 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 00000000000..c1be1665fa7 Binary files /dev/null and b/Tests/images/xmp_tags_orientation.png differ diff --git a/Tests/images/xmp_test.jpg b/Tests/images/xmp_test.jpg new file mode 100644 index 00000000000..4b9354f3a17 Binary files /dev/null and b/Tests/images/xmp_test.jpg differ diff --git a/Tests/images/zero_dpi.jp2 b/Tests/images/zero_dpi.jp2 new file mode 100644 index 00000000000..079271fc6d8 Binary files /dev/null and b/Tests/images/zero_dpi.jp2 differ diff --git a/Tests/oss-fuzz/build.sh b/Tests/oss-fuzz/build.sh new file mode 100755 index 00000000000..09cc7bc1696 --- /dev/null +++ b/Tests/oss-fuzz/build.sh @@ -0,0 +1,48 @@ +#!/bin/bash -eu +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +################################################################################ + +python3 setup.py build --build-base=/tmp/build install + +# Build fuzzers in $OUT. +for fuzzer in $(find $SRC -name 'fuzz_*.py'); do + fuzzer_basename=$(basename -s .py $fuzzer) + fuzzer_package=${fuzzer_basename}.pkg + pyinstaller \ + --add-binary /usr/local/lib/libjpeg.so.62.3.0:. \ + --add-binary /usr/local/lib/libfreetype.so.6:. \ + --add-binary /usr/local/lib/liblcms2.so.2:. \ + --add-binary /usr/local/lib/libopenjp2.so.7:. \ + --add-binary /usr/local/lib/libpng16.so.16:. \ + --add-binary /usr/local/lib/libtiff.so.5:. \ + --add-binary /usr/local/lib/libwebp.so.7:. \ + --add-binary /usr/local/lib/libwebpdemux.so.2:. \ + --add-binary /usr/local/lib/libwebpmux.so.3:. \ + --add-binary /usr/local/lib/libxcb.so.1:. \ + --distpath $OUT --onefile --name $fuzzer_package $fuzzer + + # Create execution wrapper. + echo "#!/bin/sh +# LLVMFuzzerTestOneInput for fuzzer detection. +this_dir=\$(dirname \"\$0\") +LD_PRELOAD=\$this_dir/sanitizer_with_fuzzer.so \ +ASAN_OPTIONS=\$ASAN_OPTIONS:symbolize=1:external_symbolizer_path=\$this_dir/llvm-symbolizer:detect_leaks=0 \ +\$this_dir/$fuzzer_package \$@" > $OUT/$fuzzer_basename + chmod u+x $OUT/$fuzzer_basename +done + +find Tests/images Tests/icc -print | zip -q $OUT/fuzz_pillow_seed_corpus.zip -@ +find Tests/fonts -print | zip -q $OUT/fuzz_font_seed_corpus.zip -@ diff --git a/Tests/oss-fuzz/build_dictionaries.sh b/Tests/oss-fuzz/build_dictionaries.sh new file mode 100755 index 00000000000..9aae56ca8d1 --- /dev/null +++ b/Tests/oss-fuzz/build_dictionaries.sh @@ -0,0 +1,33 @@ +#!/bin/bash -eu +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +################################################################################ + +# Generate image dictionaries here for each of the fuzzers and put them in the +# $OUT directory, named for the fuzzer + +git clone --depth 1 https://github.com/google/fuzzing +cat fuzzing/dictionaries/bmp.dict \ + fuzzing/dictionaries/dds.dict \ + fuzzing/dictionaries/gif.dict \ + fuzzing/dictionaries/icns.dict \ + fuzzing/dictionaries/jpeg.dict \ + fuzzing/dictionaries/jpeg2000.dict \ + fuzzing/dictionaries/pbm.dict \ + fuzzing/dictionaries/png.dict \ + fuzzing/dictionaries/psd.dict \ + fuzzing/dictionaries/tiff.dict \ + fuzzing/dictionaries/webp.dict \ + > $OUT/fuzz_pillow.dict diff --git a/Tests/oss-fuzz/fuzz_font.py b/Tests/oss-fuzz/fuzz_font.py new file mode 100755 index 00000000000..bc2ba9a7e27 --- /dev/null +++ b/Tests/oss-fuzz/fuzz_font.py @@ -0,0 +1,43 @@ +#!/usr/bin/python3 + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import atheris + +with atheris.instrument_imports(): + import sys + + import fuzzers + + +def TestOneInput(data): + try: + fuzzers.fuzz_font(data) + except Exception: + # We're catching all exceptions because Pillow's exceptions are + # directly inheriting from Exception. + pass + + +def main(): + fuzzers.enable_decompressionbomb_error() + atheris.Setup(sys.argv, TestOneInput) + atheris.Fuzz() + fuzzers.disable_decompressionbomb_error() + + +if __name__ == "__main__": + main() diff --git a/Tests/oss-fuzz/fuzz_pillow.py b/Tests/oss-fuzz/fuzz_pillow.py new file mode 100644 index 00000000000..545daccb680 --- /dev/null +++ b/Tests/oss-fuzz/fuzz_pillow.py @@ -0,0 +1,43 @@ +#!/usr/bin/python3 + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import atheris + +with atheris.instrument_imports(): + import sys + + import fuzzers + + +def TestOneInput(data): + try: + fuzzers.fuzz_image(data) + except Exception: + # We're catching all exceptions because Pillow's exceptions are + # directly inheriting from Exception. + pass + + +def main(): + fuzzers.enable_decompressionbomb_error() + atheris.Setup(sys.argv, TestOneInput) + atheris.Fuzz() + fuzzers.disable_decompressionbomb_error() + + +if __name__ == "__main__": + main() diff --git a/Tests/oss-fuzz/fuzzers.py b/Tests/oss-fuzz/fuzzers.py new file mode 100644 index 00000000000..5786764a64d --- /dev/null +++ b/Tests/oss-fuzz/fuzzers.py @@ -0,0 +1,41 @@ +import io +import warnings + +from PIL import Image, ImageDraw, ImageFile, ImageFilter, ImageFont + + +def enable_decompressionbomb_error(): + ImageFile.LOAD_TRUNCATED_IMAGES = True + warnings.filterwarnings("ignore") + warnings.simplefilter("error", Image.DecompressionBombWarning) + + +def disable_decompressionbomb_error(): + ImageFile.LOAD_TRUNCATED_IMAGES = False + warnings.resetwarnings() + + +def fuzz_image(data): + # This will fail on some images in the corpus, as we have many + # invalid images in the test suite. + with Image.open(io.BytesIO(data)) as im: + im.rotate(45) + im.filter(ImageFilter.DETAIL) + im.save(io.BytesIO(), "BMP") + + +def fuzz_font(data): + wrapper = io.BytesIO(data) + try: + font = ImageFont.truetype(wrapper) + except OSError: + # Catch pcf/pilfonts/random garbage here. They return + # different font objects. + return + + font.getsize_multiline("ABC\nAaaa") + font.getmask("test text") + with Image.new(mode="RGBA", size=(200, 200)) as im: + draw = ImageDraw.Draw(im) + draw.multiline_textsize("ABC\nAaaa", font, stroke_width=2) + draw.text((10, 10), "Test Text", font=font, fill="#000") diff --git a/Tests/oss-fuzz/python.supp b/Tests/oss-fuzz/python.supp new file mode 100644 index 00000000000..94cc87db97d --- /dev/null +++ b/Tests/oss-fuzz/python.supp @@ -0,0 +1,16 @@ +{ + + Memcheck:Cond + ... + fun:encode_current_locale +} + + +{ + + Memcheck:Cond + fun:inflate + fun:ZIPDecode + fun:_TIFFReadEncodedTileAndAllocBuffer + ... +} diff --git a/Tests/oss-fuzz/test_fuzzers.py b/Tests/oss-fuzz/test_fuzzers.py new file mode 100644 index 00000000000..629e9ac00d4 --- /dev/null +++ b/Tests/oss-fuzz/test_fuzzers.py @@ -0,0 +1,62 @@ +import subprocess +import sys + +import fuzzers +import packaging +import pytest + +from PIL import Image, features + +if sys.platform.startswith("win32"): + pytest.skip("Fuzzer is linux only", allow_module_level=True) +if features.check("libjpeg_turbo"): + version = packaging.version.parse(features.version("libjpeg_turbo")) + if version.major == 2 and version.minor == 0: + pytestmark = pytest.mark.valgrind_known_error( + reason="Known failing with libjpeg_turbo 2.0" + ) + + +@pytest.mark.parametrize( + "path", + subprocess.check_output("find Tests/images -type f", shell=True).split(b"\n"), +) +def test_fuzz_images(path): + fuzzers.enable_decompressionbomb_error() + try: + with open(path, "rb") as f: + fuzzers.fuzz_image(f.read()) + assert True + except ( + OSError, + SyntaxError, + MemoryError, + ValueError, + NotImplementedError, + OverflowError, + ): + # Known exceptions that are through from Pillow + assert True + except ( + Image.DecompressionBombError, + Image.DecompressionBombWarning, + Image.UnidentifiedImageError, + ): + # Known Image.* exceptions + assert True + finally: + fuzzers.disable_decompressionbomb_error() + + +@pytest.mark.parametrize( + "path", subprocess.check_output("find Tests/fonts -type f", shell=True).split(b"\n") +) +def test_fuzz_fonts(path): + if not path: + return + with open(path, "rb") as f: + try: + fuzzers.fuzz_font(f.read()) + except (Image.DecompressionBombError, Image.DecompressionBombWarning): + pass + assert True diff --git a/Tests/test_bmp_reference.py b/Tests/test_bmp_reference.py index ade2901b710..99e16391adc 100644 --- a/Tests/test_bmp_reference.py +++ b/Tests/test_bmp_reference.py @@ -1,6 +1,7 @@ import os import pytest + from PIL import Image from .helper import assert_image_similar @@ -15,11 +16,11 @@ def get_files(d, ext=".bmp"): def test_bad(): - """ These shouldn't crash/dos, but they shouldn't return anything - either """ + """These shouldn't crash/dos, but they shouldn't return anything + either""" for f in get_files("b"): - def open(f): + with pytest.warns(None) as record: try: with Image.open(f) as im: im.load() @@ -27,12 +28,12 @@ def open(f): pass # Assert that there is no unclosed file warning - pytest.warns(None, open, f) + assert not record def test_questionable(): - """ These shouldn't crash/dos, but it's not well defined that these - are in spec """ + """These shouldn't crash/dos, but it's not well defined that these + are in spec""" supported = [ "pal8os2v2.bmp", "rgb24prof.bmp", @@ -49,15 +50,15 @@ def test_questionable(): 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) + print(f"Please add {f} to the partially supported bmp specs.") except Exception: # as msg: if os.path.basename(f) in supported: raise def test_good(): - """ These should all work. There's a set of target files in the - html directory that we can compare against. """ + """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 = { @@ -84,7 +85,7 @@ def get_compare(f): 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) + return os.path.join(base, "html", f"{name}.png") for f in get_files("g"): try: @@ -107,4 +108,4 @@ def get_compare(f): os.path.join(base, "g", "pal8rle.bmp"), os.path.join(base, "g", "pal4rle.bmp"), ) - assert f in unsupported, "Unsupported Image {}: {}".format(f, msg) + assert f in unsupported, f"Unsupported Image {f}: {msg}" diff --git a/Tests/test_box_blur.py b/Tests/test_box_blur.py index 44910b9ed5a..94f504e0b55 100644 --- a/Tests/test_box_blur.py +++ b/Tests/test_box_blur.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image, ImageFilter sample = Image.new("L", (7, 5)) diff --git a/Tests/test_color_lut.py b/Tests/test_color_lut.py index b34dbadb6bf..99776ce58cf 100644 --- a/Tests/test_color_lut.py +++ b/Tests/test_color_lut.py @@ -1,6 +1,7 @@ from array import array import pytest + from PIL import Image, ImageFilter from .helper import assert_image_equal diff --git a/Tests/test_core_resources.py b/Tests/test_core_resources.py index a8fe8bfebeb..6c52d25a4aa 100644 --- a/Tests/test_core_resources.py +++ b/Tests/test_core_resources.py @@ -1,6 +1,7 @@ import sys import pytest + from PIL import Image from .helper import is_pypy diff --git a/Tests/test_decompression_bomb.py b/Tests/test_decompression_bomb.py index 1704400b489..d918ef9410c 100644 --- a/Tests/test_decompression_bomb.py +++ b/Tests/test_decompression_bomb.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image from .helper import hopper @@ -9,8 +10,7 @@ class TestDecompressionBomb: - @classmethod - def teardown_class(self): + def teardown_method(self, method): Image.MAX_IMAGE_PIXELS = ORIGINAL_LIMIT def test_no_warning_small_file(self): @@ -51,13 +51,21 @@ def test_exception(self): with Image.open(TEST_FILE): pass + @pytest.mark.xfail(reason="different exception") def test_exception_ico(self): with pytest.raises(Image.DecompressionBombError): - Image.open("Tests/images/decompression_bomb.ico") + with Image.open("Tests/images/decompression_bomb.ico"): + pass def test_exception_gif(self): with pytest.raises(Image.DecompressionBombError): - Image.open("Tests/images/decompression_bomb.gif") + with Image.open("Tests/images/decompression_bomb.gif"): + pass + + def test_exception_bmp(self): + with pytest.raises(Image.DecompressionBombError): + with Image.open("Tests/images/bmp/b/reallybig.bmp"): + pass class TestDecompressionCrop: diff --git a/Tests/test_features.py b/Tests/test_features.py index 7cfa08071ba..284f72205f3 100644 --- a/Tests/test_features.py +++ b/Tests/test_features.py @@ -1,6 +1,8 @@ import io +import re import pytest + from PIL import features from .helper import skip_unless_feature @@ -21,6 +23,27 @@ def test_check(): assert features.check_feature(feature) == features.check(feature) +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() @@ -37,9 +60,22 @@ 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] @@ -64,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(): @@ -72,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 index deb043fdd4c..d48e5ce07f3 100644 --- a/Tests/test_file_apng.py +++ b/Tests/test_file_apng.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image, ImageSequence, PngImagePlugin @@ -104,6 +105,38 @@ def test_apng_dispose_region(): 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) @@ -216,8 +249,8 @@ def test_apng_mode(): 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) + assert im.getpixel((0, 0)) == (255, 0, 0, 0) + assert im.getpixel((64, 32)) == (255, 0, 0, 0) with Image.open("Tests/images/apng/mode_palette_1bit_alpha.png") as im: assert im.mode == "P" @@ -279,7 +312,7 @@ def open_frames_zero_default(): exception = e assert exception is None - with pytest.raises(SyntaxError): + with pytest.raises(OSError): with Image.open("Tests/images/apng/syntax_num_frames_high.png") as im: im.seek(im.n_frames - 1) im.load() @@ -304,7 +337,7 @@ def test_apng_sequence_errors(): ] for f in test_files: with pytest.raises(SyntaxError): - with Image.open("Tests/images/apng/{0}".format(f)) as im: + with Image.open(f"Tests/images/apng/{f}") as im: im.seek(im.n_frames - 1) im.load() @@ -351,7 +384,10 @@ def test_apng_save_split_fdat(tmp_path): 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, + test_file, + save_all=True, + default_image=True, + append_images=frames, ) with Image.open(test_file) as im: exception = None @@ -397,12 +433,20 @@ def test_apng_save_duration_loop(tmp_path): # 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]) + frame.save( + test_file, save_all=True, append_images=[frame, frame], duration=[500, 100, 150] + ) with Image.open(test_file) as im: im.load() assert im.n_frames == 1 assert im.info.get("duration") == 750 + # test info duration + frame.info["duration"] = 750 + frame.save(test_file, save_all=True) + with Image.open(test_file) as im: + assert im.info.get("duration") == 750 + def test_apng_save_disposal(tmp_path): test_file = str(tmp_path / "temp.png") @@ -493,6 +537,37 @@ def test_apng_save_disposal(tmp_path): assert im.getpixel((0, 0)) == (0, 255, 0, 255) assert im.getpixel((64, 32)) == (0, 255, 0, 255) + # test info disposal + red.info["disposal"] = PngImagePlugin.APNG_DISPOSE_OP_BACKGROUND + red.save( + test_file, + save_all=True, + append_images=[Image.new("RGBA", (10, 10), (0, 255, 0, 255))], + ) + with Image.open(test_file) as im: + im.seek(1) + assert im.getpixel((64, 32)) == (0, 0, 0, 0) + + +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") @@ -553,3 +628,10 @@ def test_apng_save_blend(tmp_path): im.seek(2) assert im.getpixel((0, 0)) == (0, 255, 0, 255) assert im.getpixel((64, 32)) == (0, 255, 0, 255) + + # test info blend + red.info["blend"] = PngImagePlugin.APNG_BLEND_OP_OVER + red.save(test_file, save_all=True, append_images=[green, transparent]) + with Image.open(test_file) as im: + im.seek(2) + assert im.getpixel((0, 0)) == (0, 255, 0, 255) diff --git a/Tests/test_file_blp.py b/Tests/test_file_blp.py index 94c469c7f23..15bd7e4f8ef 100644 --- a/Tests/test_file_blp.py +++ b/Tests/test_file_blp.py @@ -1,21 +1,39 @@ +import pytest + from PIL import Image -from .helper import assert_image_equal +from .helper import assert_image_equal_tofile 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) + assert_image_equal_tofile(im, "Tests/images/blp/blp2_raw.png") 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) + assert_image_equal_tofile(im, "Tests/images/blp/blp2_dxt1.png") 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) + assert_image_equal_tofile(im, "Tests/images/blp/blp2_dxt1a.png") + + +@pytest.mark.parametrize( + "test_file", + [ + "Tests/images/timeout-060745d3f534ad6e4128c51d336ea5489182c69d.blp", + "Tests/images/timeout-31c8f86233ea728339c6e586be7af661a09b5b98.blp", + "Tests/images/timeout-60d8b7c8469d59fc9ffff6b3a3dc0faeae6ea8ee.blp", + "Tests/images/timeout-8073b430977660cdd48d96f6406ddfd4114e69c7.blp", + "Tests/images/timeout-bba4f2e026b5786529370e5dfe9a11b1bf991f07.blp", + "Tests/images/timeout-d6ec061c4afdef39d3edf6da8927240bb07fe9b7.blp", + "Tests/images/timeout-ef9112a065e7183fa7faa2e18929b03e44ee16bf.blp", + ], +) +def test_crashes(test_file): + with open(test_file, "rb") as f: + with Image.open(f) as im: + with pytest.raises(OSError): + im.load() diff --git a/Tests/test_file_bmp.py b/Tests/test_file_bmp.py index 8bb58794c00..47fc97df055 100644 --- a/Tests/test_file_bmp.py +++ b/Tests/test_file_bmp.py @@ -1,9 +1,10 @@ import io import pytest + from PIL import BmpImagePlugin, Image -from .helper import assert_image_equal, hopper +from .helper import assert_image_equal, assert_image_equal_tofile, hopper def test_sanity(tmp_path): @@ -62,7 +63,7 @@ def test_dpi(): output.seek(0) with Image.open(output) as reloaded: - assert reloaded.info["dpi"] == dpi + assert reloaded.info["dpi"] == (72.008961115161, 72.008961115161) def test_save_bmp_with_dpi(tmp_path): @@ -70,6 +71,7 @@ def test_save_bmp_with_dpi(tmp_path): # Arrange outfile = str(tmp_path / "temp.jpg") with Image.open("Tests/images/hopper.bmp") as im: + assert im.info["dpi"] == (95.98654816726399, 95.98654816726399) # Act im.save(outfile, "JPEG", dpi=im.info["dpi"]) @@ -77,31 +79,17 @@ def test_save_bmp_with_dpi(tmp_path): # Assert with Image.open(outfile) as reloaded: reloaded.load() - assert im.info["dpi"] == reloaded.info["dpi"] - assert im.size == reloaded.size + assert reloaded.info["dpi"] == (96, 96) + assert reloaded.size == im.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): +def test_save_float_dpi(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)) + im.save(outfile, dpi=(72.21216100543306, 72.21216100543306)) 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) + assert reloaded.info["dpi"] == (72.21216100543306, 72.21216100543306) def test_load_dib(): @@ -110,8 +98,7 @@ def test_load_dib(): 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) + assert_image_equal_tofile(im, "Tests/images/clipboard_target.png") def test_save_dib(tmp_path): @@ -135,5 +122,11 @@ def test_rgba_bitfields(): 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) + assert_image_equal_tofile(im, "Tests/images/bmp/q/rgb32bf-xbgr.bmp") + + +def test_offset(): + # This image has been hexedited + # to exclude the palette size from the pixel data offset + with Image.open("Tests/images/pal8_offset.bmp") as im: + assert_image_equal_tofile(im, "Tests/images/bmp/g/pal8.bmp") diff --git a/Tests/test_file_bufrstub.py b/Tests/test_file_bufrstub.py index ee6f3f2a4a2..11acc1c88c0 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_cur.py b/Tests/test_file_cur.py index 3200fd8f63a..f04a20a220a 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 bc76b4591d0..58d5cbf1a61 100644 --- a/Tests/test_file_dcx.py +++ b/Tests/test_file_dcx.py @@ -1,4 +1,5 @@ import pytest + from PIL import DcxImagePlugin, Image from .helper import assert_image_equal, hopper, is_pypy @@ -30,20 +31,20 @@ def open(): def test_closed_file(): - def open(): + with pytest.warns(None) as record: im = Image.open(TEST_FILE) im.load() im.close() - pytest.warns(None, open) + assert not record def test_context_manager(): - def open(): + with pytest.warns(None) as record: with Image.open(TEST_FILE) as im: im.load() - pytest.warns(None, open) + assert not record def test_invalid_file(): diff --git a/Tests/test_file_dds.py b/Tests/test_file_dds.py index d157e15fd71..2f46ed77e0c 100644 --- a/Tests/test_file_dds.py +++ b/Tests/test_file_dds.py @@ -2,16 +2,24 @@ from io import BytesIO import pytest + from PIL import DdsImagePlugin, Image -from .helper import assert_image_equal +from .helper import assert_image_equal, assert_image_equal_tofile, hopper 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_BC5_TYPELESS = "Tests/images/bc5_typeless.dds" +TEST_FILE_DX10_BC5_UNORM = "Tests/images/bc5_unorm.dds" +TEST_FILE_DX10_BC5_SNORM = "Tests/images/bc5_snorm.dds" +TEST_FILE_BC5S = "Tests/images/bc5s.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_UNCOMPRESSED_RGB = "Tests/images/uncompressed_rgb.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/hopper.dds" +TEST_FILE_UNCOMPRESSED_RGB_WITH_ALPHA = "Tests/images/uncompressed_rgb.dds" def test_sanity_dxt1(): @@ -28,6 +36,19 @@ def test_sanity_dxt1(): assert_image_equal(im, target) +def test_sanity_dxt3(): + """Check DXT3 images can be opened""" + + with Image.open(TEST_FILE_DXT3) as im: + im.load() + + assert im.format == "DDS" + assert im.mode == "RGBA" + assert im.size == (256, 256) + + assert_image_equal_tofile(im, TEST_FILE_DXT3.replace(".dds", ".png")) + + def test_sanity_dxt5(): """Check DXT5 images can be opened""" @@ -38,22 +59,31 @@ def test_sanity_dxt5(): 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) + assert_image_equal_tofile(im, TEST_FILE_DXT5.replace(".dds", ".png")) -def test_sanity_dxt3(): - """Check DXT3 images can be opened""" +@pytest.mark.parametrize( + ("image_path", "expected_path"), + ( + # hexeditted to be typeless + (TEST_FILE_DX10_BC5_TYPELESS, TEST_FILE_DX10_BC5_UNORM), + (TEST_FILE_DX10_BC5_UNORM, TEST_FILE_DX10_BC5_UNORM), + # hexeditted to use DX10 FourCC + (TEST_FILE_DX10_BC5_SNORM, TEST_FILE_BC5S), + (TEST_FILE_BC5S, TEST_FILE_BC5S), + ), +) +def test_dx10_bc5(image_path, expected_path): + """Check DX10 BC5 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() + with Image.open(image_path) as im: + im.load() - assert im.format == "DDS" - assert im.mode == "RGBA" - assert im.size == (256, 256) + assert im.format == "DDS" + assert im.mode == "RGB" + assert im.size == (256, 256) - assert_image_equal(target, im) + assert_image_equal_tofile(im, expected_path.replace(".dds", ".png")) def test_dx10_bc7(): @@ -66,8 +96,7 @@ def test_dx10_bc7(): assert im.mode == "RGBA" assert im.size == (256, 256) - with Image.open(TEST_FILE_DX10_BC7.replace(".dds", ".png")) as target: - assert_image_equal(target, im) + assert_image_equal_tofile(im, TEST_FILE_DX10_BC7.replace(".dds", ".png")) def test_dx10_bc7_unorm_srgb(): @@ -81,69 +110,107 @@ def test_dx10_bc7_unorm_srgb(): 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) + assert_image_equal_tofile( + im, TEST_FILE_DX10_BC7_UNORM_SRGB.replace(".dds", ".png") + ) + + +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) + + assert_image_equal_tofile(im, TEST_FILE_DX10_R8G8B8A8.replace(".dds", ".png")) + + +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 + + assert_image_equal_tofile( + im, TEST_FILE_DX10_R8G8B8A8_UNORM_SRGB.replace(".dds", ".png") + ) def test_unimplemented_dxgi_format(): with pytest.raises(NotImplementedError): - Image.open("Tests/images/unimplemented_dxgi_format.dds") + with Image.open("Tests/images/unimplemented_dxgi_format.dds"): + pass def test_uncompressed_rgb(): """Check uncompressed RGB images can be opened""" + # convert -format dds -define dds:compression=none hopper.jpg hopper.dds with Image.open(TEST_FILE_UNCOMPRESSED_RGB) as im: - im.load() + assert im.format == "DDS" + assert im.mode == "RGB" + assert im.size == (128, 128) + assert_image_equal_tofile(im, "Tests/images/hopper.png") + + # Test image with alpha + with Image.open(TEST_FILE_UNCOMPRESSED_RGB_WITH_ALPHA) as im: 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) + assert_image_equal_tofile( + im, TEST_FILE_UNCOMPRESSED_RGB_WITH_ALPHA.replace(".dds", ".png") + ) -def test__validate_true(): +def test__accept_true(): """Check valid prefix""" # Arrange prefix = b"DDS etc" # Act - output = DdsImagePlugin._validate(prefix) + output = DdsImagePlugin._accept(prefix) # Assert assert output -def test__validate_false(): +def test__accept_false(): """Check invalid prefix""" # Arrange prefix = b"something invalid" # Act - output = DdsImagePlugin._validate(prefix) + output = DdsImagePlugin._accept(prefix) # Assert assert not output def test_short_header(): - """ Check a 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 Image.open(BytesIO(img_file[:119])): + pass # pragma: no cover - with pytest.raises(IOError): + with pytest.raises(OSError): short_header() def test_short_file(): - """ Check that the appropriate error is thrown for a 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() @@ -152,10 +219,50 @@ def short_file(): with Image.open(BytesIO(img_file[:-100])) as im: im.load() - with pytest.raises(IOError): + with pytest.raises(OSError): short_file() +def test_dxt5_colorblock_alpha_issue_4142(): + """Check that colorblocks are decoded correctly in DXT5""" + + with Image.open("Tests/images/dxt5-colorblock-alpha-issue-4142.dds") as im: + px = im.getpixel((0, 0)) + assert px[0] != 0 + assert px[1] != 0 + assert px[2] != 0 + + px = im.getpixel((1, 0)) + assert px[0] != 0 + assert px[1] != 0 + assert px[2] != 0 + + def test_unimplemented_pixel_format(): with pytest.raises(NotImplementedError): - Image.open("Tests/images/unimplemented_pixel_format.dds") + with Image.open("Tests/images/unimplemented_pixel_format.dds"): + pass + + +def test_save_unsupported_mode(tmp_path): + out = str(tmp_path / "temp.dds") + im = hopper("HSV") + with pytest.raises(OSError): + im.save(out) + + +@pytest.mark.parametrize( + ("mode", "test_file"), + [ + ("RGB", "Tests/images/hopper.png"), + ("RGBA", "Tests/images/pil123rgba.png"), + ], +) +def test_save(mode, test_file, tmp_path): + out = str(tmp_path / "temp.dds") + with Image.open(test_file) as im: + assert im.mode == mode + im.save(out) + + with Image.open(out) as reloaded: + assert_image_equal(im, reloaded) diff --git a/Tests/test_file_eps.py b/Tests/test_file_eps.py index 504c09db1ab..4c0b96f7376 100644 --- a/Tests/test_file_eps.py +++ b/Tests/test_file_eps.py @@ -1,9 +1,16 @@ import io import pytest + from PIL import EpsImagePlugin, Image, features -from .helper import assert_image_similar, hopper, skip_unless_feature +from .helper import ( + assert_image_similar, + assert_image_similar_tofile, + hopper, + mark_if_feature_version, + skip_unless_feature, +) HAS_GHOSTSCRIPT = EpsImagePlugin.has_ghostscript() @@ -58,6 +65,9 @@ def test_invalid_file(): EpsImagePlugin.EpsImageFile(invalid_file) +@mark_if_feature_version( + pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" +) @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: @@ -70,8 +80,9 @@ def test_cmyk(): assert cmyk_image.mode == "RGB" if features.check("jpg"): - with Image.open("Tests/images/pil_sample_rgb.jpg") as target: - assert_image_similar(cmyk_image, target, 10) + assert_image_similar_tofile( + cmyk_image, "Tests/images/pil_sample_rgb.jpg", 10 + ) @pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available") @@ -85,6 +96,17 @@ def test_showpage(): assert_image_similar(plot_image, target, 6) +@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available") +def test_transparency(): + with Image.open("Tests/images/reqd_showpage.eps") as plot_image: + plot_image.load(transparency=True) + assert plot_image.mode == "RGBA" + + with Image.open("Tests/images/reqd_showpage_transparency.png") as target: + # fonts could be slightly different + assert_image_similar(plot_image, target, 6) + + @pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available") def test_file_object(tmp_path): # issue 479 @@ -256,3 +278,15 @@ def test_emptyline(): assert image.mode == "RGB" assert image.size == (460, 352) assert image.format == "EPS" + + +@pytest.mark.timeout(timeout=5) +@pytest.mark.parametrize( + "test_file", + ["Tests/images/timeout-d675703545fee17acab56e5fec644c19979175de.eps"], +) +def test_timeout(test_file): + with open(test_file, "rb") as f: + with pytest.raises(Image.UnidentifiedImageError): + with Image.open(f): + pass diff --git a/Tests/test_file_fitsstub.py b/Tests/test_file_fitsstub.py index f9f6c4ba9b4..c77457947ef 100644 --- a/Tests/test_file_fitsstub.py +++ b/Tests/test_file_fitsstub.py @@ -1,4 +1,7 @@ +from io import BytesIO + import pytest + from PIL import FitsStubImagePlugin, Image TEST_FILE = "Tests/images/hopper.fits" @@ -10,10 +13,8 @@ def test_open(): # Assert assert im.format == "FITS" - - # Dummy data from the stub - assert im.mode == "F" - assert im.size == (1, 1) + assert im.size == (128, 128) + assert im.mode == "L" def test_invalid_file(): @@ -30,10 +31,25 @@ 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() +def test_truncated_fits(): + # No END to headers + image_data = b"SIMPLE = T" + b" " * 50 + b"TRUNCATE" + with pytest.raises(OSError): + FitsStubImagePlugin.FITSStubImageFile(BytesIO(image_data)) + + +def test_naxis_zero(): + # This test image has been manually hexedited + # to set the number of data axes to zero + with pytest.raises(ValueError): + with Image.open("Tests/images/hopper_naxis_zero.fits"): + pass + + def test_save(): # Arrange with Image.open(TEST_FILE) as im: @@ -41,7 +57,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 726e16c1a16..675e06bf83c 100644 --- a/Tests/test_file_fli.py +++ b/Tests/test_file_fli.py @@ -1,7 +1,8 @@ import pytest + from PIL import FliImagePlugin, Image -from .helper import assert_image_equal, is_pypy +from .helper import assert_image_equal_tofile, is_pypy # created as an export of a palette image from Gimp2.6 # save as...-> hopper.fli, default options. @@ -37,20 +38,20 @@ def open(): def test_closed_file(): - def open(): + with pytest.warns(None) as record: im = Image.open(static_test_file) im.load() im.close() - pytest.warns(None, open) + assert not record def test_context_manager(): - def open(): + with pytest.warns(None) as record: with Image.open(static_test_file) as im: im.load() - pytest.warns(None, open) + assert not record def test_tell(): @@ -121,5 +122,32 @@ 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) + assert_image_equal_tofile(im, "Tests/images/a_fli.png") + + +@pytest.mark.parametrize( + "test_file", + [ + "Tests/images/timeout-9139147ce93e20eb14088fe238e541443ffd64b3.fli", + "Tests/images/timeout-bff0a9dc7243a8e6ede2408d2ffa6a9964698b87.fli", + ], +) +@pytest.mark.timeout(timeout=3) +def test_timeouts(test_file): + with open(test_file, "rb") as f: + with Image.open(f) as im: + with pytest.raises(OSError): + im.load() + + +@pytest.mark.parametrize( + "test_file", + [ + "Tests/images/crash-5762152299364352.fli", + ], +) +def test_crash(test_file): + with open(test_file, "rb") as f: + with Image.open(f) as im: + with pytest.raises(OSError): + im.load() diff --git a/Tests/test_file_fpx.py b/Tests/test_file_fpx.py index ef8cdb5770b..818565f88b3 100644 --- a/Tests/test_file_fpx.py +++ b/Tests/test_file_fpx.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image FpxImagePlugin = pytest.importorskip( @@ -19,5 +20,6 @@ def test_invalid_file(): def test_fpx_invalid_number_of_bands(): - with pytest.raises(IOError, match="Invalid number of bands"): - Image.open("Tests/images/input_bw_five_bands.fpx") + with pytest.raises(OSError, match="Invalid number of bands"): + with Image.open("Tests/images/input_bw_five_bands.fpx"): + pass diff --git a/Tests/test_file_ftex.py b/Tests/test_file_ftex.py index 9b4375cd430..f76fd895a58 100644 --- a/Tests/test_file_ftex.py +++ b/Tests/test_file_ftex.py @@ -1,12 +1,11 @@ from PIL import Image -from .helper import assert_image_equal, assert_image_similar +from .helper import assert_image_equal_tofile, assert_image_similar 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) + assert_image_equal_tofile(im, "Tests/images/ftex_uncompressed.png") def test_load_dxt1(): diff --git a/Tests/test_file_gbr.py b/Tests/test_file_gbr.py index f183390bccf..8d7fcf14779 100644 --- a/Tests/test_file_gbr.py +++ b/Tests/test_file_gbr.py @@ -1,7 +1,8 @@ import pytest + from PIL import GbrImagePlugin, Image -from .helper import assert_image_equal +from .helper import assert_image_equal_tofile def test_invalid_file(): @@ -13,5 +14,11 @@ def test_invalid_file(): 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) + assert_image_equal_tofile(im, "Tests/images/gbr.png") + + +def test_multiple_load_operations(): + with Image.open("Tests/images/gbr.gbr") as im: + im.load() + im.load() + assert_image_equal_tofile(im, "Tests/images/gbr.png") diff --git a/Tests/test_file_gd.py b/Tests/test_file_gd.py index b6f8594bec7..5594e5bbb9e 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 455e30f71da..00bf582fafb 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -1,10 +1,12 @@ from io import BytesIO import pytest + from PIL import GifImagePlugin, Image, ImageDraw, ImagePalette, features from .helper import ( assert_image_equal, + assert_image_equal_tofile, assert_image_similar, hopper, is_pypy, @@ -37,20 +39,20 @@ def open(): def test_closed_file(): - def open(): + with pytest.warns(None) as record: im = Image.open(TEST_GIF) im.load() im.close() - pytest.warns(None, open) + assert not record def test_context_manager(): - def open(): + with pytest.warns(None) as record: with Image.open(TEST_GIF) as im: im.load() - pytest.warns(None, open) + assert not record def test_invalid_file(): @@ -73,10 +75,10 @@ def test_bilevel(optimize): im.save(test_file, "GIF", optimize=optimize) return len(test_file.getvalue()) - assert test_grayscale(0) == 800 - assert test_grayscale(1) == 44 - assert test_bilevel(0) == 800 - assert test_bilevel(1) == 800 + assert test_grayscale(0) == 799 + assert test_grayscale(1) == 43 + assert test_bilevel(0) == 799 + assert test_bilevel(1) == 799 def test_optimize_correctness(): @@ -161,6 +163,32 @@ def test_roundtrip_save_all(tmp_path): assert reread.n_frames == 5 +@pytest.mark.parametrize( + "path, mode", + ( + ("Tests/images/dispose_bgnd.gif", "RGB"), + # Hexeditted copy of dispose_bgnd to add transparency + ("Tests/images/dispose_bgnd_rgba.gif", "RGBA"), + ), +) +def test_loading_multiple_palettes(path, mode): + with Image.open(path) as im: + assert im.mode == "P" + first_frame_colors = im.palette.colors.keys() + original_color = im.convert("RGB").load()[0, 0] + + im.seek(1) + assert im.mode == mode + if mode == "RGBA": + im = im.convert("RGB") + + # Check a color only from the old palette + assert im.load()[0, 0] == original_color + + # Check a color from the new palette + assert im.load()[24, 24] not in first_frame_colors + + def test_headers_saving_for_animated_gifs(tmp_path): important_headers = ["background", "version", "duration", "loop"] # Multiframe image @@ -296,6 +324,12 @@ def test_eoferror(): im.seek(n_frames - 1) +def test_first_frame_transparency(): + with Image.open("Tests/images/first_frame_transparency.gif") as im: + px = im.load() + assert px[0, 0] == im.info["transparency"] + + def test_dispose_none(): with Image.open("Tests/images/dispose_none.gif") as img: try: @@ -306,6 +340,19 @@ def test_dispose_none(): 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) + + assert_image_equal_tofile(img, "Tests/images/dispose_none_load_end_second.png") + + def test_dispose_background(): with Image.open("Tests/images/dispose_bgnd.gif") as img: try: @@ -316,6 +363,27 @@ def test_dispose_background(): pass +def test_dispose_background_transparency(): + with Image.open("Tests/images/dispose_bgnd_transparency.gif") as img: + img.seek(2) + px = img.load() + assert px[35, 30][3] == 0 + + +def test_transparent_dispose(): + expected_colors = [ + (2, 1, 2), + ((0, 255, 24, 255), (0, 0, 255, 255), (0, 255, 24, 255)), + ((0, 0, 0, 0), (0, 0, 255, 255), (0, 0, 0, 0)), + ] + with Image.open("Tests/images/transparent_dispose.gif") as img: + for frame in range(3): + img.seek(frame) + for x in range(3): + color = img.getpixel((x, 0)) + assert color == expected_colors[frame][x] + + def test_dispose_previous(): with Image.open("Tests/images/dispose_prev.gif") as img: try: @@ -326,6 +394,25 @@ def test_dispose_previous(): pass +def test_dispose_previous_first_frame(): + with Image.open("Tests/images/dispose_prev_first_frame.gif") as im: + im.seek(1) + assert_image_equal_tofile( + im, "Tests/images/dispose_prev_first_frame_seeked.png" + ) + + +def test_previous_frame_loaded(): + with Image.open("Tests/images/dispose_none.gif") as img: + img.load() + img.seek(1) + img.load() + img.seek(2) + with Image.open("Tests/images/dispose_none.gif") as img_skipped: + img_skipped.seek(2) + assert_image_equal(img_skipped, img) + + def test_save_dispose(tmp_path): out = str(tmp_path / "temp.gif") im_list = [ @@ -358,14 +445,15 @@ def test_save_dispose(tmp_path): def test_dispose2_palette(tmp_path): out = str(tmp_path / "temp.gif") - # 4 backgrounds: White, Grey, Black, Red + # Four colors: white, grey, black, red circles = [(255, 255, 255), (153, 153, 153), (0, 0, 0), (255, 0, 0)] im_list = [] for circle in circles: + # Red background img = Image.new("RGB", (100, 100), (255, 0, 0)) - # Red circle in center of each frame + # Circle in center of each frame d = ImageDraw.Draw(img) d.ellipse([(40, 40), (60, 60)], fill=circle) @@ -450,15 +538,28 @@ def test_dispose2_background(tmp_path): with Image.open(out) as im: im.seek(1) - assert im.getpixel((0, 0)) == 0 + assert im.getpixel((0, 0)) == (255, 0, 0) + +def test_transparency_in_second_frame(): + with Image.open("Tests/images/different_transparency.gif") as im: + assert im.info["transparency"] == 0 -def test_iss634(): + # Seek to the second frame + im.seek(im.tell() + 1) + assert "transparency" not in im.info + + assert_image_equal_tofile(im, "Tests/images/different_transparency_merged.png") + + +def test_no_transparency_in_second_frame(): with Image.open("Tests/images/iss634.gif") as img: # Seek to the second frame img.seek(img.tell() + 1) + assert "transparency" not in img.info + # All transparent pixels should be replaced with the color from the first frame - assert img.histogram()[img.info["transparency"]] == 0 + assert img.histogram()[255] == 0 def test_duration(tmp_path): @@ -614,8 +715,7 @@ def test_comment_over_255(tmp_path): 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) + assert_image_equal_tofile(im, TEST_GIF) def test_version(tmp_path): @@ -703,10 +803,10 @@ def test_rgb_transparency(tmp_path): # Single frame im = Image.new("RGB", (1, 1)) im.info["transparency"] = (255, 0, 0) - pytest.warns(UserWarning, im.save, out) + im.save(out) with Image.open(out) as reloaded: - assert "transparency" not in reloaded.info + assert "transparency" in reloaded.info # Multiple frames im = Image.new("RGB", (1, 1)) @@ -748,7 +848,7 @@ def test_palette_save_P(tmp_path): # Forcing a non-straight grayscale palette. im = hopper("P") - palette = bytes([255 - i // 3 for i in range(768)]) + palette = bytes(255 - i // 3 for i in range(768)) out = str(tmp_path / "temp.gif") im.save(out, palette=palette) @@ -758,6 +858,29 @@ def test_palette_save_P(tmp_path): assert_image_equal(reloaded, im) +def test_palette_save_all_P(tmp_path): + frames = [] + colors = ((255, 0, 0), (0, 255, 0)) + for color in colors: + frame = Image.new("P", (100, 100)) + frame.putpalette(color) + frames.append(frame) + + out = str(tmp_path / "temp.gif") + frames[0].save( + out, save_all=True, palette=[255, 0, 0, 0, 255, 0], append_images=frames[1:] + ) + + with Image.open(out) as im: + # Assert that the frames are correct, and each frame has the same palette + assert_image_equal(im.convert("RGB"), frames[0].convert("RGB")) + assert im.palette.palette == im.global_palette.palette + + im.seek(1) + assert_image_equal(im.convert("RGB"), frames[1].convert("RGB")) + assert im.palette.palette == im.global_palette.palette + + def test_palette_save_ImagePalette(tmp_path): # Pass in a different palette, as an ImagePalette.ImagePalette # effectively the same as test_palette_save_P @@ -770,7 +893,7 @@ def test_palette_save_ImagePalette(tmp_path): with Image.open(out) as reloaded: im.putpalette(palette) - assert_image_equal(reloaded, im) + assert_image_equal(reloaded.convert("RGB"), im.convert("RGB")) def test_save_I(tmp_path): @@ -792,7 +915,7 @@ def test_getdata(): im.putpalette(ImagePalette.ImagePalette("RGB")) im.info = {"background": 0} - passed_palette = bytes([255 - i // 3 for i in range(768)]) + passed_palette = bytes(255 - i // 3 for i in range(768)) GifImagePlugin._FORCE_OPTIMIZE = True try: @@ -826,3 +949,21 @@ def test_extents(): assert im.size == (100, 100) im.seek(1) assert im.size == (150, 150) + + +def test_missing_background(): + # The Global Color Table Flag isn't set, so there is no background color index, + # but the disposal method is "Restore to background color" + with Image.open("Tests/images/missing_background.gif") as im: + im.seek(1) + assert_image_equal_tofile(im, "Tests/images/missing_background_first_frame.png") + + +def test_saving_rgba(tmp_path): + out = str(tmp_path / "temp.gif") + with Image.open("Tests/images/transparent.png") as im: + im.save(out) + + with Image.open(out) as reloaded: + reloaded_rgba = reloaded.convert("RGBA") + assert reloaded_rgba.load()[0, 0][3] == 0 diff --git a/Tests/test_file_gimppalette.py b/Tests/test_file_gimppalette.py index a38c6320c8d..caec9cf2115 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 1cc1f47ac9e..e4930d8dc2e 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 526fd7c99ce..ff339705522 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 aeb146f7ec2..3afbbeaac05 100644 --- a/Tests/test_file_icns.py +++ b/Tests/test_file_icns.py @@ -1,15 +1,16 @@ import io -import sys +import os import pytest -from PIL import IcnsImagePlugin, Image -from .helper import assert_image_equal, assert_image_similar +from PIL import IcnsImagePlugin, Image, _binary, features + +from .helper import assert_image_equal, assert_image_similar_tofile # sample icon file TEST_FILE = "Tests/images/pillow.icns" -ENABLE_JPEG2K = hasattr(Image.core, "jp2klib_version") +ENABLE_JPEG2K = features.check_codec("jpg_2000") def test_sanity(): @@ -18,14 +19,15 @@ def test_sanity(): with Image.open(TEST_FILE) as im: # Assert that there is no unclosed file warning - pytest.warns(None, im.load) + with pytest.warns(None) as record: + im.load() + assert not record assert im.mode == "RGBA" assert im.size == (1024, 1024) assert im.format == "ICNS" -@pytest.mark.skipif(sys.platform != "darwin", reason="Requires macOS") def test_save(tmp_path): temp_file = str(tmp_path / "temp.icns") @@ -37,8 +39,12 @@ def test_save(tmp_path): assert reread.size == (1024, 1024) assert reread.format == "ICNS" + file_length = os.path.getsize(temp_file) + with open(temp_file, "rb") as fp: + fp.seek(4) + assert _binary.i32be(fp.read(4)) == file_length + -@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)) @@ -46,8 +52,7 @@ def test_save_append_images(tmp_path): 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) + assert_image_similar_tofile(im, temp_file, 1) with Image.open(temp_file) as reread: reread.size = (16, 16, 2) @@ -55,6 +60,18 @@ def test_save_append_images(tmp_path): assert_image_equal(reread, provided_im) +def test_save_fp(): + fp = io.BytesIO() + + with Image.open(TEST_FILE) as im: + im.save(fp, format="ICNS") + + with Image.open(fp) as reread: + assert reread.mode == "RGBA" + assert reread.size == (1024, 1024) + assert reread.format == "ICNS" + + def test_sizes(): # Check that we can load all of the sizes, and that the final pixel # dimensions are as expected @@ -125,3 +142,11 @@ def test_not_an_icns_file(): with io.BytesIO(b"invalid\n") as fp: with pytest.raises(SyntaxError): IcnsImagePlugin.IcnsFile(fp) + + +def test_icns_decompression_bomb(): + with Image.open( + "Tests/images/oom-8ed3316a4109213ca96fb8a256a0bfefdece1461.icns" + ) as im: + with pytest.raises(Image.DecompressionBombError): + im.load() diff --git a/Tests/test_file_ico.py b/Tests/test_file_ico.py index 9ed1ffcb70e..317264db646 100644 --- a/Tests/test_file_ico.py +++ b/Tests/test_file_ico.py @@ -1,9 +1,10 @@ import io import pytest + from PIL import IcoImagePlugin, Image, ImageDraw -from .helper import assert_image_equal, hopper +from .helper import assert_image_equal, assert_image_equal_tofile, hopper TEST_ICO_FILE = "Tests/images/hopper.ico" @@ -17,6 +18,17 @@ def test_sanity(): assert im.get_format_mimetype() == "image/x-icon" +def test_mask(): + with Image.open("Tests/images/hopper_mask.ico") as im: + assert_image_equal_tofile(im, "Tests/images/hopper_mask.png") + + +def test_black_and_white(): + with Image.open("Tests/images/black_and_white.ico") as im: + assert im.mode == "RGBA" + assert im.size == (16, 16) + + def test_invalid_file(): with open("Tests/images/flower.jpg", "rb") as fp: with pytest.raises(SyntaxError): @@ -49,6 +61,35 @@ def test_save_to_bytes(): assert_image_equal(reloaded, hopper().resize((32, 32), Image.LANCZOS)) +@pytest.mark.parametrize("mode", ("1", "L", "P", "RGB", "RGBA")) +def test_save_to_bytes_bmp(mode): + output = io.BytesIO() + im = hopper(mode) + im.save(output, "ico", bitmap_format="bmp", sizes=[(32, 32), (64, 64)]) + + # The default image + output.seek(0) + with Image.open(output) as reloaded: + assert reloaded.info["sizes"] == {(32, 32), (64, 64)} + + assert "RGBA" == reloaded.mode + assert (64, 64) == reloaded.size + assert reloaded.format == "ICO" + im = hopper(mode).resize((64, 64), Image.LANCZOS).convert("RGBA") + assert_image_equal(reloaded, im) + + # The other one + output.seek(0) + with Image.open(output) as reloaded: + reloaded.size = (32, 32) + + assert "RGBA" == reloaded.mode + assert (32, 32) == reloaded.size + assert reloaded.format == "ICO" + im = hopper(mode).resize((32, 32), Image.LANCZOS).convert("RGBA") + assert_image_equal(reloaded, im) + + def test_incorrect_size(): with Image.open(TEST_ICO_FILE) as im: with pytest.raises(ValueError): @@ -85,6 +126,20 @@ def test_only_save_relevant_sizes(tmp_path): 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 @@ -104,6 +159,4 @@ def test_draw_reloaded(tmp_path): 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) + assert_image_equal_tofile(im, "Tests/images/hopper_draw.ico") diff --git a/Tests/test_file_im.py b/Tests/test_file_im.py index 30a9fd52a07..9d25a4d1a8f 100644 --- a/Tests/test_file_im.py +++ b/Tests/test_file_im.py @@ -1,9 +1,10 @@ import filecmp import pytest + from PIL import Image, ImImagePlugin -from .helper import assert_image_equal, hopper, is_pypy +from .helper import assert_image_equal_tofile, hopper, is_pypy # sample im TEST_IM = "Tests/images/hopper.im" @@ -34,20 +35,20 @@ def open(): def test_closed_file(): - def open(): + with pytest.warns(None) as record: im = Image.open(TEST_IM) im.load() im.close() - pytest.warns(None, open) + assert not record def test_context_manager(): - def open(): + with pytest.warns(None) as record: with Image.open(TEST_IM) as im: im.load() - pytest.warns(None, open) + assert not record def test_tell(): @@ -85,8 +86,7 @@ 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) + assert_image_equal_tofile(im, out) for mode in ["RGB", "P", "PA"]: roundtrip(mode) diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index 33045122891..4b2ffe70d0c 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -3,19 +3,36 @@ from io import BytesIO import pytest -from PIL import ExifTags, Image, ImageFile, JpegImagePlugin + +from PIL import ( + ExifTags, + Image, + ImageFile, + ImageOps, + JpegImagePlugin, + UnidentifiedImageError, + features, +) from .helper import ( assert_image, assert_image_equal, + assert_image_equal_tofile, assert_image_similar, + assert_image_similar_tofile, cjpeg_available, djpeg_available, hopper, is_win32, + mark_if_feature_version, skip_unless_feature, ) +try: + import defusedxml.ElementTree as ElementTree +except ImportError: + ElementTree = None + TEST_FILE = "Tests/images/hopper.jpg" @@ -31,7 +48,7 @@ def roundtrip(self, im, **options): 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 @@ -41,7 +58,7 @@ def gen_random_image(self, size, mode="RGB"): def test_sanity(self): # internal version number - assert re.search(r"\d+\.\d+$", Image.core.jpeglib_version) + assert re.search(r"\d+\.\d+$", features.version_codec("jpg")) with Image.open(TEST_FILE) as im: im.load() @@ -68,31 +85,35 @@ def test_cmyk(self): f = "Tests/images/pil_sample_cmyk.jpg" 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))] + c, m, y, k = (x / 255.0 for x in im.getpixel((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 = [ + c, m, y, k = ( x / 255.0 for x in im.getpixel((im.size[0] - 1, im.size[1] - 1)) - ] + ) 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))] + c, m, y, k = (x / 255.0 for x in im.getpixel((0, 0))) assert c == 0.0 assert m > 0.8 assert y > 0.8 assert k == 0.0 - c, m, y, k = [ + c, m, y, k = ( x / 255.0 for x in im.getpixel((im.size[0] - 1, im.size[1] - 1)) - ] + ) 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") @@ -101,6 +122,9 @@ def test(xdpi, ydpi=None): assert test(100, 200) == (100, 200) assert test(0) is None # square pixels + @mark_if_feature_version( + pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" + ) def test_icc(self, tmp_path): # Test ICC support with Image.open("Tests/images/rgb.jpg") as im1: @@ -140,6 +164,9 @@ def test(n): test(ImageFile.MAXBLOCK + 1) # full buffer block plus one byte test(ImageFile.MAXBLOCK * 4 + 3) # large block + @mark_if_feature_version( + pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" + ) 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 @@ -147,7 +174,7 @@ def test_large_icc_meta(self, tmp_path): with Image.open("Tests/images/icc_profile_big.jpg") as im: 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", @@ -213,23 +240,58 @@ def test_exif_typeerror(self): # Should not raise a TypeError im._getexif() - def test_exif_gps(self): - # Arrange - 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", - } + 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 - # Act + # Reading + with Image.open("Tests/images/exif_gps.jpg") as im: exif = im._getexif() + assert exif[gps_index] == expected_exif_gps - # Assert - assert 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.get_ifd(0x8825) == {} + + transposed = ImageOps.exif_transpose(im) + exif = transposed.getexif() + assert exif.get_ifd(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. @@ -241,7 +303,7 @@ def test_exif_rollback(self): 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", @@ -253,11 +315,11 @@ def test_exif_rollback(self): 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: @@ -371,6 +433,9 @@ def test_ff00_jpeg_header(self): with Image.open(filename): pass + @mark_if_feature_version( + pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" + ) def test_truncated_jpeg_should_read_all_the_data(self): filename = "Tests/images/truncated_jpeg.jpg" ImageFile.LOAD_TRUNCATED_IMAGES = True @@ -379,16 +444,19 @@ def test_truncated_jpeg_should_read_all_the_data(self): ImageFile.LOAD_TRUNCATED_IMAGES = False 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 pytest.raises(IOError): + with pytest.raises(OSError): im.load() # Test that the error is raised if loaded a second time - with pytest.raises(IOError): + with pytest.raises(OSError): im.load() + @mark_if_feature_version( + pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" + ) def test_qtables(self, tmp_path): def _n_qtables_helper(n, test_file): with Image.open(test_file) as im: @@ -398,6 +466,7 @@ def _n_qtables_helper(n, test_file): assert len(im.quantization) == n reloaded = self.roundtrip(im, qtables="keep") assert im.quantization == reloaded.quantization + assert max(reloaded.quantization[0]) <= 255 with Image.open("Tests/images/hopper.jpg") as im: qtables = im.quantization @@ -409,7 +478,8 @@ def _n_qtables_helper(n, test_file): # valid bounds for baseline qtable bounds_qtable = [int(s) for s in ("255 1 " * 32).split(None)] - self.roundtrip(im, qtables=[bounds_qtable]) + im2 = self.roundtrip(im, qtables=[bounds_qtable]) + assert im2.quantization == {0: bounds_qtable} # values from wizard.txt in jpeg9-a src package. standard_l_qtable = [ @@ -496,11 +566,41 @@ def _n_qtables_helper(n, test_file): with pytest.raises(ValueError): self.roundtrip(im, qtables=[[1, 2, 3, 4]]) + 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 + + def test_convert_dict_qtables_deprecation(self): + with pytest.warns(DeprecationWarning): + qtable = {0: [1, 2, 3, 4]} + qtable2 = JpegImagePlugin.convert_dict_qtables(qtable) + assert qtable == qtable2 + @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_tofile(img, TEST_FILE, 5) @pytest.mark.skipif(not cjpeg_available(), reason="cjpeg not available") def test_save_cjpeg(self, tmp_path): @@ -508,7 +608,7 @@ def test_save_cjpeg(self, tmp_path): 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) + assert_image_similar_tofile(img, tempfile, 17) def test_no_duplicate_0x1001_tag(self): # Arrange @@ -530,7 +630,7 @@ def test_MAXBLOCK_scaling(self, tmp_path): reloaded.save(f, quality="keep", optimize=True) def test_bad_mpo_header(self): - """ Treat unknown MPO as JPEG """ + """Treat unknown MPO as JPEG""" # Arrange # Act @@ -552,7 +652,7 @@ def test_save_wrong_modes(self): out = BytesIO() for mode in ["LA", "La", "RGBA", "RGBa", "P"]: img = Image.new(mode, (20, 20)) - with pytest.raises(IOError): + with pytest.raises(OSError): img.save(out, "JPEG") def test_save_tiff_with_dpi(self, tmp_path): @@ -568,15 +668,6 @@ def test_save_tiff_with_dpi(self, tmp_path): reloaded.load() 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: - assert im.info["dpi"] == (44, 44) - - # Round down - with Image.open("Tests/images/iptc_roundDown.jpg") as im: - assert im.info["dpi"] == (2, 2) - def test_save_dpi_rounding(self, tmp_path): outfile = str(tmp_path / "temp.jpg") with Image.open("Tests/images/hopper.jpg") as im: @@ -627,6 +718,15 @@ def test_dpi_exif_zero_division(self): # This should return the default, and not raise a ZeroDivisionError assert im.info.get("dpi") == (72, 72) + def test_dpi_exif_string(self): + # Arrange + # 0x011A tag in this exif contains string '300300\x02' + with Image.open("Tests/images/broken_exif_dpi.jpg") as im: + + # Act / Assert + # This should return the default + assert im.info.get("dpi") == (72, 72) + def test_no_dpi_in_exif(self): # Arrange # This is photoshop-200dpi.jpg with resolution removed from EXIF: @@ -647,6 +747,22 @@ def test_invalid_exif(self): # OSError for unidentified image. assert im.info.get("dpi") == (72, 72) + @mark_if_feature_version( + pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" + ) + 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 not record + + 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 with Image.open("Tests/images/invalid-exif-without-x-resolution.jpg") as im: @@ -664,6 +780,9 @@ def test_ifd_offset_exif(self): # Act / Assert assert im._getexif()[306] == "2017:03:13 23:03:09" + @mark_if_feature_version( + pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" + ) def test_photoshop(self): with Image.open("Tests/images/photoshop-200dpi.jpg") as im: assert im.info["photoshop"][0x03ED] == { @@ -675,8 +794,7 @@ def test_photoshop(self): # Test that the image can still load, even with broken Photoshop data # This image had the APP13 length hexedited to be smaller - with Image.open("Tests/images/photoshop-200dpi-broken.jpg") as im_broken: - assert_image_equal(im_broken, im) + assert_image_equal_tofile(im, "Tests/images/photoshop-200dpi-broken.jpg") # This image does not contain a Photoshop header string with Image.open("Tests/images/app13.jpg") as im: @@ -689,6 +807,93 @@ def test_photoshop_malformed_and_multiple(self): apps_13_lengths = [len(v) for k, v in im.applist if k == "APP13"] assert [65504, 24] == apps_13_lengths + def test_adobe_transform(self): + with Image.open("Tests/images/pil_sample_rgb.jpg") as im: + assert im.info["adobe_transform"] == 1 + + with Image.open("Tests/images/pil_sample_cmyk.jpg") as im: + assert im.info["adobe_transform"] == 2 + + # This image has been manually hexedited + # so that the APP14 reports its length to be 11, + # leaving no room for "adobe_transform" + with Image.open("Tests/images/truncated_app14.jpg") as im: + assert "adobe" in im.info + assert "adobe_transform" not in im.info + + 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): + with Image.open(buffer): + pass + + # Assert the entire file has not been read + assert 0 < buffer.max_pos < size + + def test_getxmp(self): + with Image.open("Tests/images/xmp_test.jpg") as im: + if ElementTree is None: + with pytest.warns(UserWarning): + assert im.getxmp() == {} + else: + xmp = im.getxmp() + + description = xmp["xmpmeta"]["RDF"]["Description"] + assert description["DerivedFrom"] == { + "documentID": "8367D410E636EA95B7DE7EBA1C43A412", + "originalDocumentID": "8367D410E636EA95B7DE7EBA1C43A412", + } + assert description["Look"]["Description"]["Group"]["Alt"]["li"] == { + "lang": "x-default", + "text": "Profiles", + } + assert description["ToneCurve"]["Seq"]["li"] == ["0, 0", "255, 255"] + + # Attribute + assert description["Version"] == "10.4" + + if ElementTree is not None: + with Image.open("Tests/images/hopper.jpg") as im: + assert im.getxmp() == {} + + @pytest.mark.timeout(timeout=1) + def test_eof(self): + # Even though this decoder never says that it is finished + # the image should still end when there is no new data + class InfiniteMockPyDecoder(ImageFile.PyDecoder): + def decode(self, buffer): + return 0, 0 + + decoder = InfiniteMockPyDecoder(None) + + def closure(mode, *args): + decoder.__init__(mode, *args) + return decoder + + Image.register_decoder("INFINITE", closure) + + with Image.open(TEST_FILE) as im: + im.tile = [ + ("INFINITE", (0, 0, 128, 128), 0, ("RGB", 0, 1)), + ] + ImageFile.LOAD_TRUNCATED_IMAGES = True + im.load() + ImageFile.LOAD_TRUNCATED_IMAGES = False + @pytest.mark.skipif(not is_win32(), reason="Windows only") @skip_unless_feature("jpg") @@ -702,7 +907,7 @@ def test_fd_leak(self, tmp_path): im = Image.open(tmpfile) fp = im.fp assert not fp.closed - with pytest.raises(WindowsError): + with pytest.raises(OSError): os.remove(tmpfile) im.load() assert fp.closed diff --git a/Tests/test_file_jpeg2k.py b/Tests/test_file_jpeg2k.py index 72bc7df672f..ca410162a1c 100644 --- a/Tests/test_file_jpeg2k.py +++ b/Tests/test_file_jpeg2k.py @@ -1,17 +1,20 @@ +import os import re from io import BytesIO import pytest -from PIL import Image, ImageFile, Jpeg2KImagePlugin + +from PIL import Image, ImageFile, Jpeg2KImagePlugin, UnidentifiedImageError, features from .helper import ( assert_image_equal, assert_image_similar, - is_big_endian, - on_ci, + assert_image_similar_tofile, skip_unless_feature, ) +EXTRA_DIR = "Tests/images/jpeg2000" + pytestmark = skip_unless_feature("jpg_2000") test_card = Image.open("Tests/images/test-card.png") @@ -27,15 +30,15 @@ def roundtrip(im, **options): 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() + with Image.open(out) as im: + im.bytes = test_bytes # for testing only + im.load() return im def test_sanity(): # Internal version number - assert re.search(r"\d+\.\d+\.\d+$", Image.core.jp2klib_version) + 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() @@ -62,9 +65,7 @@ def test_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() - assert_image_similar(im, test_card, 1.0e-3) + assert_image_similar_tofile(test_card, data, 1.0e-3) # These two test pre-written JPEG 2000 files that were not written with @@ -80,9 +81,9 @@ def test_lossless(tmp_path): 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) + assert_image_similar_tofile( + test_card, "Tests/images/test-card-lossy-tiled.jp2", 2.0 + ) def test_lossless_rt(): @@ -125,6 +126,16 @@ def test_prog_res_rt(): assert_image_equal(im, test_card) +def test_default_num_resolutions(): + for num_resolutions in range(2, 6): + d = 1 << (num_resolutions - 1) + im = test_card.resize((d - 1, d - 1)) + with pytest.raises(OSError): + roundtrip(im, num_resolutions=num_resolutions) + reloaded = roundtrip(im) + assert_image_equal(im, reloaded) + + def test_reduce(): with Image.open("Tests/images/test-card-lossless.jp2") as im: assert callable(im.reduce) @@ -139,6 +150,38 @@ def test_reduce(): assert im.size == (40, 30) +def test_load_dpi(): + with Image.open("Tests/images/test-card-lossless.jp2") as im: + assert im.info["dpi"] == (71.9836, 71.9836) + + with Image.open("Tests/images/zero_dpi.jp2") as im: + assert "dpi" not in im.info + + +def test_restricted_icc_profile(): + ImageFile.LOAD_TRUNCATED_IMAGES = True + try: + # JPEG2000 image with a restricted ICC profile and a known colorspace + with Image.open("Tests/images/balloon_eciRGBv2_aware.jp2") as im: + assert im.mode == "RGB" + finally: + ImageFile.LOAD_TRUNCATED_IMAGES = False + + +def test_header_errors(): + for path in ( + "Tests/images/invalid_header_length.jp2", + "Tests/images/not_enough_data.jp2", + ): + with pytest.raises(UnidentifiedImageError): + with Image.open(path): + pass + + with pytest.raises(OSError): + with Image.open("Tests/images/expected_to_read.jp2"): + pass + + def test_layers_type(tmp_path): outfile = str(tmp_path / "temp_layers.jp2") for quality_layers in [[100, 50, 10], (100, 50, 10), None]: @@ -190,18 +233,14 @@ def test_16bit_monochrome_has_correct_mode(): assert 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(): 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_image_similar_tofile(tiff_16bit, "Tests/images/16bit.cropped.jp2", 1e-3) -@pytest.mark.xfail(is_big_endian() and on_ci(), 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) + assert_image_similar_tofile(tiff_16bit, "Tests/images/16bit.cropped.j2k", 1e-3) def test_16bit_j2k_roundtrips(): @@ -218,8 +257,9 @@ def test_16bit_jp2_roundtrips(): def test_unbound_local(): # prepatch, a malformed jp2 file could cause an UnboundLocalError exception. - with pytest.raises(IOError): - Image.open("Tests/images/unbound_variable.jp2") + with pytest.raises(OSError): + with Image.open("Tests/images/unbound_variable.jp2"): + pass def test_parser_feed(): @@ -233,3 +273,42 @@ def test_parser_feed(): # Assert assert p.image.size == (640, 480) + + +@pytest.mark.skipif( + not os.path.exists(EXTRA_DIR), reason="Extra image files not installed" +) +@pytest.mark.parametrize("name", ("subsampling_1", "subsampling_2", "zoo1", "zoo2")) +def test_subsampling_decode(name): + test = f"{EXTRA_DIR}/{name}.jp2" + reference = f"{EXTRA_DIR}/{name}.ppm" + + with Image.open(test) as im: + epsilon = 3 # for YCbCr images + with Image.open(reference) as im2: + width, height = im2.size + if name[-1] == "2": + # RGB reference images are downscaled + epsilon = 3e-3 + width, height = width * 2, height * 2 + expected = im2.resize((width, height), Image.NEAREST) + assert_image_similar(im, expected, epsilon) + + +@pytest.mark.parametrize( + "test_file", + [ + "Tests/images/crash-4fb027452e6988530aa5dabee76eecacb3b79f8a.j2k", + "Tests/images/crash-7d4c83eb92150fb8f1653a697703ae06ae7c4998.j2k", + "Tests/images/crash-ccca68ff40171fdae983d924e127a721cab2bd50.j2k", + "Tests/images/crash-d2c93af851d3ab9a19e34503626368b2ecde9c03.j2k", + ], +) +def test_crashes(test_file): + with open(test_file, "rb") as f: + with Image.open(f) as im: + # Valgrind should not complain here + try: + im.load() + except OSError: + pass diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index 923bd610714..e40a19394bf 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -1,13 +1,15 @@ import base64 import io import itertools -import logging import os +import re from collections import namedtuple from ctypes import c_float import pytest -from PIL import Image, ImageFilter, TiffImagePlugin, TiffTags + +from PIL import Image, ImageFilter, TiffImagePlugin, TiffTags, features +from PIL.TiffImagePlugin import SAMPLEFORMAT, STRIPOFFSETS, SUBIFD from .helper import ( assert_image_equal, @@ -15,11 +17,10 @@ assert_image_similar, assert_image_similar_tofile, hopper, + mark_if_feature_version, skip_unless_feature, ) -logger = logging.getLogger(__name__) - @skip_unless_feature("libtiff") class LibTiffTestCase: @@ -47,6 +48,9 @@ def _assert_noerr(self, tmp_path, im): class TestFileLibTiff(LibTiffTestCase): + def test_version(self): + assert re.search(r"\d+\.\d+\.\d+$", features.version_codec("libtiff")) + def test_g4_tiff(self, tmp_path): """Test the ordinary file path load path""" @@ -93,17 +97,15 @@ def test_g4_non_disk_file_object(self, tmp_path): self._assert_noerr(tmp_path, im) def test_g4_eq_png(self): - """ Checking that we're actually getting the data that we expect""" + """Checking that we're actually getting the data that we expect""" with Image.open("Tests/images/hopper_bw_500.png") as png: - with Image.open("Tests/images/hopper_g4_500.tif") as g4: - assert_image_equal(g4, png) + assert_image_equal_tofile(png, "Tests/images/hopper_g4_500.tif") # see https://github.com/python-pillow/Pillow/issues/279 def test_g4_fillorder_eq_png(self): - """ Checking that we're actually getting the data that we expect""" - with Image.open("Tests/images/g4-fillorder-test.png") as png: - with Image.open("Tests/images/g4-fillorder-test.tif") as g4: - assert_image_equal(g4, png) + """Checking that we're actually getting the data that we expect""" + with Image.open("Tests/images/g4-fillorder-test.tif") as g4: + assert_image_equal_tofile(g4, "Tests/images/g4-fillorder-test.png") def test_g4_write(self, tmp_path): """Checking to see that the saved image is the same as what we wrote""" @@ -135,7 +137,7 @@ def test_adobe_deflate_tiff(self): assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png") def test_write_metadata(self, tmp_path): - """ Test metadata writing through libtiff """ + """Test metadata writing through libtiff""" for legacy_api in [False, True]: f = str(tmp_path / "temp.tiff") with Image.open("Tests/images/hopper_g4.tif") as img: @@ -169,19 +171,20 @@ def test_write_metadata(self, tmp_path): assert ( c_float(val[0][0] / val[0][1]).value == c_float(value[0][0] / value[0][1]).value - ), ("%s didn't roundtrip" % tag) + ), f"{tag} didn't roundtrip" else: - assert c_float(val).value == c_float(value).value, ( - "%s didn't roundtrip" % tag - ) + assert ( + c_float(val).value == c_float(value).value + ), f"{tag} didn't roundtrip" else: - assert val == value, "%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: - assert field in reloaded, "%s not in metadata" % field + assert field in reloaded, f"{field} not in metadata" + @pytest.mark.valgrind_known_error(reason="Known invalid metadata") def test_additional_metadata(self, tmp_path): # these should not crash. Seriously dummy data, most of it doesn't make # any sense, so we're running up against limits where we're asking @@ -203,6 +206,7 @@ def test_additional_metadata(self, tmp_path): del core_items[tag] except KeyError: pass + del core_items[320] # colormap is special, tested below # Type codes: # 2: "ascii", @@ -299,9 +303,6 @@ def check_tags(tiffinfo): ) continue - if libtiff and isinstance(value, bytes): - value = value.decode() - assert reloaded_value == value # Test with types @@ -322,6 +323,25 @@ def check_tags(tiffinfo): ) TiffImagePlugin.WRITE_LIBTIFF = False + def test_subifd(self, tmp_path): + outfile = str(tmp_path / "temp.tif") + with Image.open("Tests/images/g4_orientation_6.tif") as im: + im.tag_v2[SUBIFD] = 10000 + + # Should not segfault + im.save(outfile) + + def test_xmlpacket_tag(self, tmp_path): + TiffImagePlugin.WRITE_LIBTIFF = True + + out = str(tmp_path / "temp.tif") + hopper().save(out, tiffinfo={700: b"xmlpacket tag"}) + TiffImagePlugin.WRITE_LIBTIFF = False + + with Image.open(out) as reloaded: + if 700 in reloaded.tag_v2: + assert reloaded.tag_v2[700] == b"xmlpacket tag" + def test_int_dpi(self, tmp_path): # issue #1765 im = hopper("RGB") @@ -391,8 +411,8 @@ def test_g4_string_info(self, tmp_path): 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() @@ -415,10 +435,7 @@ def test_blur(self, tmp_path): im = im.filter(ImageFilter.GaussianBlur(4)) im.save(out, compression="tiff_adobe_deflate") - with Image.open(out) as im2: - im2.load() - - assert_image_equal(im, im2) + assert_image_equal_tofile(im, out) def test_compressions(self, tmp_path): # Test various tiff compressions and assert similar image content but reduced @@ -431,8 +448,7 @@ def test_compressions(self, tmp_path): for compression in ("packbits", "tiff_lzw"): im.save(out, compression=compression) size_compressed = os.path.getsize(out) - with Image.open(out) as im2: - assert_image_equal(im, im2) + assert_image_equal_tofile(im, out) im.save(out, compression="jpeg") size_jpeg = os.path.getsize(out) @@ -441,13 +457,28 @@ def test_compressions(self, tmp_path): im.save(out, compression="jpeg", quality=30) size_jpeg_30 = os.path.getsize(out) - with Image.open(out) as im3: - assert_image_similar(im2, im3, 30) + assert_image_similar_tofile(im2, out, 30) assert size_raw > size_compressed assert size_compressed > size_jpeg assert size_jpeg > size_jpeg_30 + def test_tiff_jpeg_compression(self, tmp_path): + im = hopper("RGB") + out = str(tmp_path / "temp.tif") + im.save(out, compression="tiff_jpeg") + + with Image.open(out) as reloaded: + assert reloaded.info["compression"] == "jpeg" + + def test_tiff_deflate_compression(self, tmp_path): + im = hopper("RGB") + out = str(tmp_path / "temp.tif") + im.save(out, compression="tiff_deflate") + + with Image.open(out) as reloaded: + assert reloaded.info["compression"] == "tiff_adobe_deflate" + def test_quality(self, tmp_path): im = hopper("RGB") out = str(tmp_path / "temp.tif") @@ -468,22 +499,33 @@ def test_cmyk_save(self, tmp_path): out = str(tmp_path / "temp.tif") im.save(out, compression="tiff_adobe_deflate") - with Image.open(out) as im2: - assert_image_equal(im, im2) + assert_image_equal_tofile(im, out) + + 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""" + """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 = str(tmp_path / "temp.tif") - with pytest.raises(IOError): + with pytest.raises(OSError): im.save(out, compression="tiff_ccitt") - with pytest.raises(IOError): + with pytest.raises(OSError): im.save(out, compression="group3") - with pytest.raises(IOError): + with pytest.raises(OSError): im.save(out, compression="group4") def test_fp_leak(self): @@ -536,6 +578,17 @@ def test_multipage_nframes(self): TiffImagePlugin.READ_LIBTIFF = False + def test_multipage_seek_backwards(self): + TiffImagePlugin.READ_LIBTIFF = True + with Image.open("Tests/images/multipage.tiff") as im: + im.seek(1) + im.load() + + im.seek(0) + assert im.convert("RGB").getpixel((0, 0)) == (0, 128, 0) + + TiffImagePlugin.READ_LIBTIFF = False + def test__next(self): TiffImagePlugin.READ_LIBTIFF = True with Image.open("Tests/images/hopper.tif") as im: @@ -607,8 +660,7 @@ def save_bytesio(compression=None): pilim.save(buffer_io, format="tiff", compression=compression) buffer_io.seek(0) - with Image.open(buffer_io) as pilim_load: - assert_image_similar(pilim, pilim_load, 0) + assert_image_similar_tofile(pilim, buffer_io, 0) save_bytesio() save_bytesio("raw") @@ -618,6 +670,15 @@ def save_bytesio(compression=None): TiffImagePlugin.WRITE_LIBTIFF = False TiffImagePlugin.READ_LIBTIFF = False + def test_save_ycbcr(self, tmp_path): + im = hopper("YCbCr") + outfile = str(tmp_path / "temp.tif") + im.save(outfile, compression="jpeg") + + with Image.open(outfile) as reloaded: + assert reloaded.tag_v2[530] == (1, 1) + assert reloaded.tag_v2[532] == (0, 255, 128, 255, 128, 255) + def test_crashing_metadata(self, tmp_path): # issue 1597 with Image.open("Tests/images/rdf.tif") as im: @@ -667,6 +728,26 @@ def test_read_icc(self): TiffImagePlugin.READ_LIBTIFF = False assert icc == icc_libtiff + def test_write_icc(self, tmp_path): + def check_write(libtiff): + TiffImagePlugin.WRITE_LIBTIFF = libtiff + + with Image.open("Tests/images/hopper.iccprofile.tif") as img: + icc_profile = img.info["icc_profile"] + + out = str(tmp_path / "temp.tif") + img.save(out, icc_profile=icc_profile) + with Image.open(out) as reloaded: + assert icc_profile == reloaded.info["icc_profile"] + + libtiffs = [] + if Image.core.libtiff_support_custom_tags: + libtiffs.append(True) + libtiffs.append(False) + + for libtiff in libtiffs: + check_write(libtiff) + def test_multipage_compression(self): with Image.open("Tests/images/compression.tif") as im: @@ -717,7 +798,7 @@ def test_16bit_RGBa_tiff(self): assert im.mode == "RGBA" assert im.size == (100, 40) assert im.tile, [ - ("libtiff", (0, 0, 100, 40), 0, ("RGBa;16N", "tiff_lzw", False, 38236),) + ("libtiff", (0, 0, 100, 40), 0, ("RGBa;16N", "tiff_lzw", False, 38236)) ] im.load() @@ -744,6 +825,17 @@ def test_sampleformat(self): assert_image_equal_tofile(im, "Tests/images/copyleft.png", mode="RGB") + def test_sampleformat_write(self, tmp_path): + im = Image.new("F", (1, 1)) + out = str(tmp_path / "temp.tif") + TiffImagePlugin.WRITE_LIBTIFF = True + im.save(out) + TiffImagePlugin.WRITE_LIBTIFF = False + + with Image.open(out) as reloaded: + assert reloaded.mode == "F" + assert reloaded.getexif()[SAMPLEFORMAT] == 3 + def test_lzw(self): with Image.open("Tests/images/hopper_lzw.tif") as im: assert im.mode == "RGB" @@ -762,11 +854,17 @@ def test_strip_cmyk_16l_jpeg(self): with Image.open(infile) as im: assert_image_similar_tofile(im, "Tests/images/pil_sample_cmyk.jpg", 0.5) + @mark_if_feature_version( + pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" + ) def test_strip_ycbcr_jpeg_2x2_sampling(self): infile = "Tests/images/tiff_strip_ycbcr_jpeg_2x2_sampling.tif" with Image.open(infile) as im: assert_image_similar_tofile(im, "Tests/images/flower.jpg", 0.5) + @mark_if_feature_version( + pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" + ) def test_strip_ycbcr_jpeg_1x1_sampling(self): infile = "Tests/images/tiff_strip_ycbcr_jpeg_1x1_sampling.tif" with Image.open(infile) as im: @@ -777,19 +875,87 @@ def test_tiled_cmyk_jpeg(self): with Image.open(infile) as im: assert_image_similar_tofile(im, "Tests/images/pil_sample_cmyk.jpg", 0.5) + @mark_if_feature_version( + pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" + ) def test_tiled_ycbcr_jpeg_1x1_sampling(self): infile = "Tests/images/tiff_tiled_ycbcr_jpeg_1x1_sampling.tif" with Image.open(infile) as im: assert_image_equal_tofile(im, "Tests/images/flower2.jpg") + @mark_if_feature_version( + pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" + ) def test_tiled_ycbcr_jpeg_2x2_sampling(self): infile = "Tests/images/tiff_tiled_ycbcr_jpeg_2x2_sampling.tif" with Image.open(infile) as im: assert_image_similar_tofile(im, "Tests/images/flower.jpg", 0.5) - def test_old_style_jpeg(self): - infile = "Tests/images/old-style-jpeg-compression.tif" + def test_strip_planar_rgb(self): + # gdal_translate -co TILED=no -co INTERLEAVE=BAND -co COMPRESS=LZW \ + # tiff_strip_raw.tif tiff_strip_planar_lzw.tiff + infile = "Tests/images/tiff_strip_planar_lzw.tiff" + with Image.open(infile) as im: + assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png") + + def test_tiled_planar_rgb(self): + # gdal_translate -co TILED=yes -co INTERLEAVE=BAND -co COMPRESS=LZW \ + # tiff_tiled_raw.tif tiff_tiled_planar_lzw.tiff + infile = "Tests/images/tiff_tiled_planar_lzw.tiff" with Image.open(infile) as im: + assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png") + + def test_tiled_planar_16bit_RGB(self): + # gdal_translate -co TILED=yes -co INTERLEAVE=BAND -co COMPRESS=LZW \ + # tiff_16bit_RGB.tiff tiff_tiled_planar_16bit_RGB.tiff + with Image.open("Tests/images/tiff_tiled_planar_16bit_RGB.tiff") as im: + assert_image_equal_tofile(im, "Tests/images/tiff_16bit_RGB_target.png") + + def test_strip_planar_16bit_RGB(self): + # gdal_translate -co TILED=no -co INTERLEAVE=BAND -co COMPRESS=LZW \ + # tiff_16bit_RGB.tiff tiff_strip_planar_16bit_RGB.tiff + with Image.open("Tests/images/tiff_strip_planar_16bit_RGB.tiff") as im: + assert_image_equal_tofile(im, "Tests/images/tiff_16bit_RGB_target.png") + + def test_tiled_planar_16bit_RGBa(self): + # gdal_translate -co TILED=yes \ + # -co INTERLEAVE=BAND -co COMPRESS=LZW -co ALPHA=PREMULTIPLIED \ + # tiff_16bit_RGBa.tiff tiff_tiled_planar_16bit_RGBa.tiff + with Image.open("Tests/images/tiff_tiled_planar_16bit_RGBa.tiff") as im: + assert_image_equal_tofile(im, "Tests/images/tiff_16bit_RGBa_target.png") + + def test_strip_planar_16bit_RGBa(self): + # gdal_translate -co TILED=no \ + # -co INTERLEAVE=BAND -co COMPRESS=LZW -co ALPHA=PREMULTIPLIED \ + # tiff_16bit_RGBa.tiff tiff_strip_planar_16bit_RGBa.tiff + with Image.open("Tests/images/tiff_strip_planar_16bit_RGBa.tiff") as im: + assert_image_equal_tofile(im, "Tests/images/tiff_16bit_RGBa_target.png") + + @pytest.mark.parametrize("compression", (None, "jpeg")) + def test_block_tile_tags(self, compression, tmp_path): + im = hopper() + out = str(tmp_path / "temp.tif") + + tags = { + TiffImagePlugin.TILEWIDTH: 256, + TiffImagePlugin.TILELENGTH: 256, + TiffImagePlugin.TILEOFFSETS: 256, + TiffImagePlugin.TILEBYTECOUNTS: 256, + } + im.save(out, exif=tags, compression=compression) + + with Image.open(out) as reloaded: + for tag in tags.keys(): + assert tag not in reloaded.getexif() + + def test_old_style_jpeg(self): + with Image.open("Tests/images/old-style-jpeg-compression.tif") as im: + assert_image_equal_tofile(im, "Tests/images/old-style-jpeg-compression.png") + + def test_open_missing_samplesperpixel(self): + with Image.open( + "Tests/images/old-style-jpeg-compression-no-samplesperpixel.tif" + ) as im: assert_image_equal_tofile(im, "Tests/images/old-style-jpeg-compression.png") def test_no_rows_per_strip(self): @@ -807,6 +973,7 @@ def test_orientation(self): assert_image_similar(base_im, im, 0.7) + @pytest.mark.valgrind_known_error(reason="Backtrace in Python Core") def test_sampleformat_not_corrupted(self): # Assert that a TIFF image with SampleFormat=UINT tag is not corrupted # when saving to a new file. @@ -831,9 +998,40 @@ def test_sampleformat_not_corrupted(self): def test_realloc_overflow(self): TiffImagePlugin.READ_LIBTIFF = True with Image.open("Tests/images/tiff_overflow_rows_per_strip.tif") as im: - with pytest.raises(IOError) as e: + with pytest.raises(OSError) as e: im.load() # Assert that the error code is IMAGING_CODEC_MEMORY assert str(e.value) == "-9" TiffImagePlugin.READ_LIBTIFF = False + + @pytest.mark.parametrize("compression", ("tiff_adobe_deflate", "jpeg")) + def test_save_multistrip(self, compression, tmp_path): + im = hopper("RGB").resize((256, 256)) + out = str(tmp_path / "temp.tif") + im.save(out, compression=compression) + + with Image.open(out) as im: + # Assert that there are multiple strips + assert len(im.tag_v2[STRIPOFFSETS]) > 1 + + def test_save_single_strip(self, tmp_path): + im = hopper("RGB").resize((256, 256)) + out = str(tmp_path / "temp.tif") + + TiffImagePlugin.STRIP_SIZE = 2 ** 18 + try: + + im.save(out, compression="tiff_adobe_deflate") + + with Image.open(out) as im: + assert len(im.tag_v2[STRIPOFFSETS]) == 1 + finally: + TiffImagePlugin.STRIP_SIZE = 65536 + + @pytest.mark.parametrize("compression", ("tiff_adobe_deflate", None)) + def test_save_zero(self, compression, tmp_path): + im = Image.new("RGB", (0, 0)) + out = str(tmp_path / "temp.tif") + with pytest.raises(SystemError): + im.save(out, compression=compression) diff --git a/Tests/test_file_libtiff_small.py b/Tests/test_file_libtiff_small.py index 593a8eda83f..03137c8b603 100644 --- a/Tests/test_file_libtiff_small.py +++ b/Tests/test_file_libtiff_small.py @@ -7,13 +7,13 @@ 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, tmp_path): """Testing the open file load path""" diff --git a/Tests/test_file_mcidas.py b/Tests/test_file_mcidas.py index 516dbb20872..41f22cf0c7d 100644 --- a/Tests/test_file_mcidas.py +++ b/Tests/test_file_mcidas.py @@ -1,7 +1,8 @@ import pytest + from PIL import Image, McIdasImagePlugin -from .helper import assert_image_equal +from .helper import assert_image_equal_tofile def test_invalid_file(): @@ -26,5 +27,4 @@ def test_valid_file(): 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) + assert_image_equal_tofile(im, saved_file) diff --git a/Tests/test_file_mic.py b/Tests/test_file_mic.py index 5003090c7d8..464d138e2af 100644 --- a/Tests/test_file_mic.py +++ b/Tests/test_file_mic.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image, ImagePalette from .helper import assert_image_similar, hopper, skip_unless_feature diff --git a/Tests/test_file_mpo.py b/Tests/test_file_mpo.py index 893f9075d03..9de09645856 100644 --- a/Tests/test_file_mpo.py +++ b/Tests/test_file_mpo.py @@ -1,6 +1,7 @@ from io import BytesIO import pytest + from PIL import Image from .helper import assert_image_similar, is_pypy, skip_unless_feature @@ -40,20 +41,20 @@ def open(): def test_closed_file(): - def open(): + with pytest.warns(None) as record: im = Image.open(test_files[0]) im.load() im.close() - pytest.warns(None, open) + assert not record def test_context_manager(): - def open(): + with pytest.warns(None) as record: with Image.open(test_files[0]) as im: im.load() - pytest.warns(None, open) + assert not record def test_app(): @@ -88,6 +89,20 @@ def test_frame_size(): assert im.size == (680, 480) +def test_ignore_frame_size(): + # Ignore the different size of the second frame + # since this is not a "Large Thumbnail" image + with Image.open("Tests/images/ignore_frame_size.mpo") as im: + assert im.size == (64, 64) + + im.seek(1) + assert ( + im.mpinfo[0xB002][1]["Attribute"]["MPType"] + == "Multi-Frame Image: (Disparity)" + ) + assert im.size == (64, 64) + + def test_parallax(): # Nintendo with Image.open("Tests/images/sugarshack.mpo") as im: @@ -131,7 +146,7 @@ def test_mp_attribute(): with Image.open(test_file) as im: mpinfo = im._getmp() frameNumber = 0 - for mpentry in mpinfo[45058]: + for mpentry in mpinfo[0xB002]: mpattr = mpentry["Attribute"] if frameNumber: assert not mpattr["RepresentativeImageFlag"] diff --git a/Tests/test_file_msp.py b/Tests/test_file_msp.py index 8f261388ee7..50d7c590b7a 100644 --- a/Tests/test_file_msp.py +++ b/Tests/test_file_msp.py @@ -1,9 +1,10 @@ import os import pytest + from PIL import Image, MspImagePlugin -from .helper import assert_image_equal, hopper +from .helper import assert_image_equal, assert_image_equal_tofile, hopper TEST_FILE = "Tests/images/hopper.msp" EXTRA_DIR = "Tests/images/picins" @@ -51,8 +52,7 @@ def test_open_windows_v1(): 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) + assert_image_equal_tofile(im, target_path) @pytest.mark.skipif( @@ -86,5 +86,5 @@ def test_cannot_save_wrong_mode(tmp_path): filename = str(tmp_path / "temp.msp") # Act/Assert - with pytest.raises(IOError): + with pytest.raises(OSError): im.save(filename) diff --git a/Tests/test_file_palm.py b/Tests/test_file_palm.py index 886332dea82..e1c1c361b1e 100644 --- a/Tests/test_file_palm.py +++ b/Tests/test_file_palm.py @@ -2,17 +2,10 @@ import subprocess import pytest -from PIL import Image -from .helper import ( - IMCONVERT, - assert_image_equal, - hopper, - imagemagick_available, - skip_known_bad_test, -) +from PIL import Image -_roundtrip = imagemagick_available() +from .helper import assert_image_equal, hopper, magick_command def helper_save_as_palm(tmp_path, mode): @@ -28,13 +21,10 @@ def helper_save_as_palm(tmp_path, mode): assert os.path.getsize(outfile) > 0 -def open_with_imagemagick(tmp_path, f): - if not imagemagick_available(): - raise OSError() - +def open_with_magick(magick, tmp_path, f): outfile = str(tmp_path / "temp.png") rc = subprocess.call( - [IMCONVERT, f, outfile], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT + magick + [f, outfile], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT ) if rc: raise OSError @@ -42,14 +32,15 @@ def open_with_imagemagick(tmp_path, f): def roundtrip(tmp_path, mode): - if not _roundtrip: + magick = magick_command() + if not magick: return im = hopper(mode) outfile = str(tmp_path / "temp.palm") im.save(outfile) - converted = open_with_imagemagick(tmp_path, outfile) + converted = open_with_magick(magick, tmp_path, outfile) assert_image_equal(converted, im) @@ -62,29 +53,29 @@ def test_monochrome(tmp_path): roundtrip(tmp_path, 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) - skip_known_bad_test("Palm P image is wrong") roundtrip(tmp_path, mode) -def test_l_ioerror(tmp_path): +def test_l_oserror(tmp_path): # Arrange mode = "L" # Act / Assert - with pytest.raises(IOError): + with pytest.raises(OSError): helper_save_as_palm(tmp_path, mode) -def test_rgb_ioerror(tmp_path): +def test_rgb_oserror(tmp_path): # Arrange mode = "RGB" # Act / Assert - with pytest.raises(IOError): + with pytest.raises(OSError): helper_save_as_palm(tmp_path, mode) diff --git a/Tests/test_file_pcx.py b/Tests/test_file_pcx.py index 5af7469c7fd..61e33a57bb4 100644 --- a/Tests/test_file_pcx.py +++ b/Tests/test_file_pcx.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image, ImageFile, PcxImagePlugin from .helper import assert_image_equal, hopper @@ -43,6 +44,14 @@ def test_odd(tmp_path): _roundtrip(tmp_path, hopper(mode).resize((511, 511))) +def test_odd_read(): + # Reading an image with an odd stride, making it malformed + with Image.open("Tests/images/odd_stride.pcx") as im: + im.load() + + assert im.size == (371, 150) + + def test_pil184(): # Check reading of files where xmin/xmax is not zero. diff --git a/Tests/test_file_pdf.py b/Tests/test_file_pdf.py index ea3b6c1d9f3..10daa414b5a 100644 --- a/Tests/test_file_pdf.py +++ b/Tests/test_file_pdf.py @@ -5,9 +5,10 @@ import time import pytest + from PIL import Image, PdfParser -from .helper import hopper +from .helper import hopper, mark_if_feature_version def helper_save_as_pdf(tmp_path, mode, **kwargs): @@ -29,7 +30,7 @@ def helper_save_as_pdf(tmp_path, mode, **kwargs): 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() + float(d) for d in contents.split(b"/MediaBox [ 0 0 ")[1].split(b"]")[0].split() ) assert im.size == size @@ -41,7 +42,8 @@ def test_monochrome(tmp_path): mode = "1" # Act / Assert - helper_save_as_pdf(tmp_path, mode) + outfile = helper_save_as_pdf(tmp_path, mode) + assert os.path.getsize(outfile) < 15000 def test_greyscale(tmp_path): @@ -84,6 +86,30 @@ def test_unsupported_mode(tmp_path): im.save(outfile) +def test_resolution(tmp_path): + im = hopper() + + outfile = str(tmp_path / "temp.pdf") + im.save(outfile, resolution=150) + + with open(outfile, "rb") as fp: + contents = fp.read() + + size = tuple( + float(d) + for d in contents.split(b"stream\nq ")[1].split(b" 0 0 cm")[0].split(b" 0 0 ") + ) + assert size == (61.44, 61.44) + + size = tuple( + float(d) for d in contents.split(b"/MediaBox [ 0 0 ")[1].split(b"]")[0].split() + ) + assert size == (61.44, 61.44) + + +@mark_if_feature_version( + pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" +) def test_save_all(tmp_path): # Single frame image helper_save_as_pdf(tmp_path, "RGB", save_all=True) @@ -172,7 +198,7 @@ def test_pdf_open(tmp_path): def test_pdf_append_fails_on_nonexistent_file(): im = hopper("RGB") with tempfile.TemporaryDirectory() as temp_dir: - with pytest.raises(IOError): + with pytest.raises(OSError): im.save(os.path.join(temp_dir, "nonexistent.pdf"), append=True) @@ -284,3 +310,14 @@ def test_pdf_append_to_bytesio(): f = io.BytesIO(f.getvalue()) im.save(f, format="PDF", append=True) assert len(f.getvalue()) > initial_size + + +@pytest.mark.timeout(1) +@pytest.mark.parametrize("newline", (b"\r", b"\n")) +def test_redos(newline): + malicious = b" trailer<<>>" + newline * 3456 + + # This particular exception isn't relevant here. + # The important thing is it doesn't timeout, cause a ReDoS (CVE-2021-25292). + with pytest.raises(PdfParser.PdfFormatError): + PdfParser.PdfParser(buf=malicious) diff --git a/Tests/test_file_pixar.py b/Tests/test_file_pixar.py index 5e83c610498..315ea4676e1 100644 --- a/Tests/test_file_pixar.py +++ b/Tests/test_file_pixar.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image, PixarImagePlugin from .helper import assert_image_similar, hopper diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index b6da365309c..0869cc58bc5 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -1,21 +1,28 @@ import re +import sys 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, assert_image, assert_image_equal, + assert_image_equal_tofile, hopper, - is_big_endian, is_win32, - on_ci, + mark_if_feature_version, skip_unless_feature, ) +try: + import defusedxml.ElementTree as ElementTree +except ImportError: + ElementTree = None + # sample png stream TEST_PNG_FILE = "Tests/images/hopper.png" @@ -69,11 +76,10 @@ def get_chunks(self, filename): png.crc(cid, s) return chunks - @pytest.mark.xfail(is_big_endian() and on_ci(), reason="Fails on big-endian") def test_sanity(self, tmp_path): # internal version number - assert re.search(r"\d+\.\d+\.\d+(\.\d+)?$", Image.core.zlib_version) + assert re.search(r"\d+\.\d+\.\d+(\.\d+)?$", features.version_codec("zlib")) test_file = str(tmp_path / "temp.png") @@ -105,8 +111,9 @@ def test_broken(self): # file was checked into Subversion as a text file. test_file = "Tests/images/broken.png" - with pytest.raises(IOError): - Image.open(test_file) + with pytest.raises(OSError): + with Image.open(test_file): + pass def test_bad_text(self): # Make sure PIL can read malformed tEXt chunks (@PIL152) @@ -324,7 +331,9 @@ def test_load_verify(self): with Image.open(TEST_PNG_FILE) as im: # Assert that there is no unclosed file warning - pytest.warns(None, im.verify) + with pytest.warns(None) as record: + im.verify() + assert not record with Image.open(TEST_PNG_FILE) as im: im.load() @@ -334,7 +343,7 @@ def test_load_verify(self): 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 @@ -344,7 +353,7 @@ def test_verify_struct_error(self): with Image.open(BytesIO(test_file)) as im: assert im.fp is not None - with pytest.raises((IOError, SyntaxError)): + with pytest.raises((OSError, SyntaxError)): im.verify() def test_verify_ignores_crc_error(self): @@ -380,25 +389,12 @@ def test_roundtrip_dpi(self): # Check dpi roundtripping with Image.open(TEST_PNG_FILE) as im: - im = roundtrip(im, 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: - assert im.info["dpi"] == (96, 96) - - # Round down - with Image.open("Tests/images/icc_profile_none.png") as im: - assert im.info["dpi"] == (72, 72) + im = roundtrip(im, dpi=(100.33, 100.33)) + assert im.info["dpi"] == (100.33, 100.33) - def test_save_dpi_rounding(self): + def test_load_float_dpi(self): with Image.open(TEST_PNG_FILE) as im: - im = roundtrip(im, dpi=(72.2, 72.2)) - assert im.info["dpi"] == (72, 72) - - im = roundtrip(im, dpi=(72.8, 72.8)) - assert im.info["dpi"] == (73, 73) + assert im.info["dpi"] == (95.9866, 95.9866) def test_roundtrip_text(self): # Check text roundtripping @@ -463,8 +459,9 @@ def test_scary(self): data = b"\x89" + fd.read() pngfile = BytesIO(data) - with pytest.raises(IOError): - Image.open(pngfile) + with pytest.raises(OSError): + with Image.open(pngfile): + pass def test_trns_rgb(self): # Check writing and reading of tRNS chunks for RGB images. @@ -513,6 +510,8 @@ def test_save_icc_profile(self): def test_discard_icc_profile(self): with Image.open("Tests/images/icc_profile.png") as im: + assert "icc_profile" in im.info + im = roundtrip(im, icc_profile=None) assert "icc_profile" not in im.info @@ -537,6 +536,12 @@ def test_repr_png(self): assert repr_png.format == "PNG" assert_image_equal(im, repr_png) + 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 = str(tmp_path / "temp.png") @@ -564,6 +569,28 @@ def test_getchunks(self): chunks = PngImagePlugin.getchunks(im) assert len(chunks) == 3 + def test_read_private_chunks(self): + with Image.open("Tests/images/exif.png") as im: + 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: assert "comment" in im.text.keys() @@ -575,13 +602,13 @@ def test_textual_chunks_after_idat(self): # Raises a SyntaxError in load_end with Image.open("Tests/images/broken_data_stream.png") as im: - with pytest.raises(IOError): + 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 - with pytest.raises(IOError): + with pytest.raises(OSError): im.text() ImageFile.LOAD_TRUNCATED_IMAGES = True assert isinstance(im.text, dict) @@ -591,18 +618,80 @@ def test_textual_chunks_after_idat(self): with Image.open("Tests/images/hopper_idat_after_image_end.png") as im: assert im.text == {"TXT": "VALUE", "ZIP": "VALUE"} - @pytest.mark.parametrize( - "test_file", - [ - "Tests/images/exif.png", # With an EXIF chunk - "Tests/images/exif_imagemagick.png", # With an ImageMagick zTXt chunk - ], - ) - def test_exif(self, test_file): - with Image.open(test_file) as im: + def test_padded_idat(self): + # This image has been manually hexedited + # so that the IDAT chunk has padding at the end + # Set MAXBLOCK to the length of the actual data + # so that the decoder finishes reading before the chunk ends + MAXBLOCK = ImageFile.MAXBLOCK + ImageFile.MAXBLOCK = 45 + ImageFile.LOAD_TRUNCATED_IMAGES = True + + with Image.open("Tests/images/padded_idat.png") as im: + im.load() + + ImageFile.MAXBLOCK = MAXBLOCK + ImageFile.LOAD_TRUNCATED_IMAGES = False + + assert_image_equal_tofile(im, "Tests/images/bw_gradient.png") + + def test_specify_bits(self, tmp_path): + im = hopper("P") + + out = str(tmp_path / "temp.png") + im.save(out, bits=4) + + with Image.open(out) as reloaded: + assert len(reloaded.png.im_palette[1]) == 48 + + def test_plte_length(self, tmp_path): + im = Image.new("P", (1, 1)) + im.putpalette((1, 1, 1)) + + out = str(tmp_path / "temp.png") + im.save(str(tmp_path / "temp.png")) + + with Image.open(out) as reloaded: + assert len(reloaded.png.im_palette[1]) == 3 + + def test_getxmp(self): + with Image.open("Tests/images/color_snakes.png") as im: + if ElementTree is None: + with pytest.warns(UserWarning): + assert im.getxmp() == {} + else: + xmp = im.getxmp() + + description = xmp["xmpmeta"]["RDF"]["Description"] + assert description["PixelXDimension"] == "10" + assert description["subject"]["Seq"] is None + + def test_exif(self): + # With an EXIF chunk + with Image.open("Tests/images/exif.png") as im: exif = im._getexif() assert exif[274] == 1 + # 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 = str(tmp_path / "temp.png") @@ -612,6 +701,9 @@ def test_exif_save(self, tmp_path): exif = reloaded._getexif() assert exif[274] == 1 + @mark_if_feature_version( + pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" + ) def test_exif_from_jpg(self, tmp_path): with Image.open("Tests/images/pil_sample_rgb.jpg") as im: test_file = str(tmp_path / "temp.png") @@ -629,6 +721,43 @@ def test_exif_argument(self, tmp_path): with Image.open(test_file) as reloaded: assert reloaded.info["exif"] == b"Exif\x00\x00exifstring" + def test_tell(self): + with Image.open(TEST_PNG_FILE) as im: + assert im.tell() == 0 + + def test_seek(self): + with Image.open(TEST_PNG_FILE) as im: + im.seek(0) + + with pytest.raises(EOFError): + im.seek(1) + + @pytest.mark.parametrize("buffer", (True, False)) + def test_save_stdout(self, buffer): + old_stdout = sys.stdout + + if buffer: + + class MyStdOut: + buffer = BytesIO() + + mystdout = MyStdOut() + else: + mystdout = BytesIO() + + sys.stdout = mystdout + + with Image.open(TEST_PNG_FILE) as im: + im.save(sys.stdout, "PNG") + + # Reset stdout + sys.stdout = old_stdout + + if buffer: + mystdout = mystdout.buffer + with Image.open(mystdout) as reloaded: + assert_image_equal_tofile(reloaded, TEST_PNG_FILE) + @pytest.mark.skipif(is_win32(), reason="Requires Unix or macOS") @skip_unless_feature("zlib") diff --git a/Tests/test_file_ppm.py b/Tests/test_file_ppm.py index 6b91ba28adb..ad36319db27 100644 --- a/Tests/test_file_ppm.py +++ b/Tests/test_file_ppm.py @@ -1,7 +1,11 @@ +import sys +from io import BytesIO + import pytest + from PIL import Image -from .helper import assert_image_equal, assert_image_similar, hopper +from .helper import assert_image_equal_tofile, assert_image_similar, hopper # sample ppm stream TEST_FILE = "Tests/images/hopper.ppm" @@ -23,8 +27,7 @@ def test_16bit_pgm(): assert im.size == (20, 100) assert 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) + assert_image_equal_tofile(im, "Tests/images/16_bit_binary_pgm.png") def test_16bit_pgm_write(tmp_path): @@ -34,8 +37,7 @@ def test_16bit_pgm_write(tmp_path): f = str(tmp_path / "temp.pgm") im.save(f, "PPM") - with Image.open(f) as reloaded: - assert_image_equal(im, reloaded) + assert_image_equal_tofile(im, f) def test_pnm(tmp_path): @@ -45,8 +47,7 @@ def test_pnm(tmp_path): f = str(tmp_path / "temp.pnm") im.save(f) - with Image.open(f) as reloaded: - assert_image_equal(im, reloaded) + assert_image_equal_tofile(im, f) def test_truncated_file(tmp_path): @@ -55,7 +56,8 @@ def test_truncated_file(tmp_path): f.write("P6") with pytest.raises(ValueError): - Image.open(path) + with Image.open(path): + pass def test_neg_ppm(): @@ -64,8 +66,9 @@ def test_neg_ppm(): # has been removed. The default opener doesn't accept negative # sizes. - with pytest.raises(IOError): - Image.open("Tests/images/negative_size.ppm") + with pytest.raises(OSError): + with Image.open("Tests/images/negative_size.ppm"): + pass def test_mimetypes(tmp_path): @@ -80,3 +83,30 @@ def test_mimetypes(tmp_path): f.write("PyCMYK\n128 128\n255") with Image.open(path) as im: assert im.get_format_mimetype() == "image/x-portable-anymap" + + +@pytest.mark.parametrize("buffer", (True, False)) +def test_save_stdout(buffer): + old_stdout = sys.stdout + + if buffer: + + class MyStdOut: + buffer = BytesIO() + + mystdout = MyStdOut() + else: + mystdout = BytesIO() + + sys.stdout = mystdout + + with Image.open(TEST_FILE) as im: + im.save(sys.stdout, "PPM") + + # Reset stdout + sys.stdout = old_stdout + + if buffer: + mystdout = mystdout.buffer + with Image.open(mystdout) as reloaded: + assert_image_equal_tofile(reloaded, TEST_FILE) diff --git a/Tests/test_file_psd.py b/Tests/test_file_psd.py index 4d8c6eba466..f50fe133ffc 100644 --- a/Tests/test_file_psd.py +++ b/Tests/test_file_psd.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image, PsdImagePlugin from .helper import assert_image_similar, hopper, is_pypy @@ -12,6 +13,7 @@ def test_sanity(): 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) @@ -27,20 +29,20 @@ def open(): def test_closed_file(): - def open(): + with pytest.warns(None) as record: im = Image.open(test_file) im.load() im.close() - pytest.warns(None, open) + assert not record def test_context_manager(): - def open(): + with pytest.warns(None) as record: with Image.open(test_file) as im: im.load() - pytest.warns(None, open) + assert not record def test_invalid_file(): @@ -55,9 +57,10 @@ def test_n_frames(): assert im.n_frames == 1 assert not im.is_animated - with Image.open(test_file) as im: - assert im.n_frames == 2 - assert im.is_animated + for path in [test_file, "Tests/images/negative_layer_count.psd"]: + with Image.open(path) as im: + assert im.n_frames == 2 + assert im.is_animated def test_eoferror(): @@ -125,5 +128,28 @@ def test_combined_larger_than_size(): # 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(IOError): - Image.open("Tests/images/combined_larger_than_size.psd") + with pytest.raises(OSError): + with Image.open("Tests/images/combined_larger_than_size.psd"): + pass + + +@pytest.mark.parametrize( + "test_file,raises", + [ + ( + "Tests/images/timeout-1ee28a249896e05b83840ae8140622de8e648ba9.psd", + Image.UnidentifiedImageError, + ), + ( + "Tests/images/timeout-598843abc37fc080ec36a2699ebbd44f795d3a6f.psd", + Image.UnidentifiedImageError, + ), + ("Tests/images/timeout-c8efc3fded6426986ba867a399791bae544f59bc.psd", OSError), + ("Tests/images/timeout-dedc7a4ebd856d79b4359bbcc79e8ef231ce38f6.psd", OSError), + ], +) +def test_crashes(test_file, raises): + with open(test_file, "rb") as f: + with pytest.raises(raises): + with Image.open(f): + pass diff --git a/Tests/test_file_sgi.py b/Tests/test_file_sgi.py index cb16276ce97..6a5d8887d33 100644 --- a/Tests/test_file_sgi.py +++ b/Tests/test_file_sgi.py @@ -1,7 +1,13 @@ import pytest + from PIL import Image, SgiImagePlugin -from .helper import assert_image_equal, assert_image_similar, hopper +from .helper import ( + assert_image_equal, + assert_image_equal_tofile, + assert_image_similar, + hopper, +) def test_rgb(): @@ -15,10 +21,7 @@ def test_rgb(): def test_rgb16(): - test_file = "Tests/images/hopper16.rgb" - - with Image.open(test_file) as im: - assert_image_equal(im, hopper()) + assert_image_equal_tofile(hopper(), "Tests/images/hopper16.rgb") def test_l(): @@ -37,8 +40,7 @@ def test_rgba(): test_file = "Tests/images/transparent.sgi" with Image.open(test_file) as im: - with Image.open("Tests/images/transparent.png") as target: - assert_image_equal(im, target) + assert_image_equal_tofile(im, "Tests/images/transparent.png") assert im.get_format_mimetype() == "image/sgi" @@ -48,16 +50,14 @@ def test_rle(): 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) + assert_image_equal_tofile(im, "Tests/images/hopper.rgb") def test_rle16(): test_file = "Tests/images/tv16.sgi" with Image.open(test_file) as im: - with Image.open("Tests/images/tv.rgb") as target: - assert_image_equal(im, target) + assert_image_equal_tofile(im, "Tests/images/tv.rgb") def test_invalid_file(): @@ -71,8 +71,14 @@ 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) + assert_image_equal_tofile(img, out) + + out = str(tmp_path / "fp.sgi") + with open(out, "wb") as fp: + img.save(fp) + assert_image_equal_tofile(img, out) + + assert not fp.closed for mode in ("L", "RGB", "RGBA"): roundtrip(hopper(mode)) @@ -88,8 +94,7 @@ def test_write16(tmp_path): out = str(tmp_path / "temp.sgi") im.save(out, format="sgi", bpc=2) - with Image.open(out) as reloaded: - assert_image_equal(im, reloaded) + assert_image_equal_tofile(im, out) def test_unsupported_mode(tmp_path): diff --git a/Tests/test_file_spider.py b/Tests/test_file_spider.py index c7446e161c9..3c93160f11d 100644 --- a/Tests/test_file_spider.py +++ b/Tests/test_file_spider.py @@ -2,9 +2,10 @@ from io import BytesIO import pytest + from PIL import Image, ImageSequence, SpiderImagePlugin -from .helper import assert_image_equal, hopper, is_pypy +from .helper import assert_image_equal_tofile, hopper, is_pypy TEST_FILE = "Tests/images/hopper.spider" @@ -27,20 +28,20 @@ def open(): def test_closed_file(): - def open(): + with pytest.warns(None) as record: im = Image.open(TEST_FILE) im.load() im.close() - pytest.warns(None, open) + assert not record def test_context_manager(): - def open(): + with pytest.warns(None) as record: with Image.open(TEST_FILE) as im: im.load() - pytest.warns(None, open) + assert not record def test_save(tmp_path): @@ -134,8 +135,9 @@ def test_is_int_not_a_number(): def test_invalid_file(): invalid_file = "Tests/images/invalid.spider" - with pytest.raises(IOError): - Image.open(invalid_file) + with pytest.raises(OSError): + with Image.open(invalid_file): + pass def test_nonstack_file(): @@ -158,5 +160,4 @@ def test_odd_size(): im.save(data, format="SPIDER") data.seek(0) - with Image.open(data) as im2: - assert_image_equal(im, im2) + assert_image_equal_tofile(im, data) diff --git a/Tests/test_file_sun.py b/Tests/test_file_sun.py index 03e26ef8b04..05c78c3161b 100644 --- a/Tests/test_file_sun.py +++ b/Tests/test_file_sun.py @@ -1,9 +1,10 @@ import os import pytest + from PIL import Image, SunImagePlugin -from .helper import assert_image_equal, assert_image_similar, hopper +from .helper import assert_image_equal_tofile, assert_image_similar, hopper EXTRA_DIR = "Tests/images/sunraster" @@ -28,8 +29,7 @@ def test_sanity(): 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) + assert_image_equal_tofile(im, "Tests/images/sunraster.im1.png") @pytest.mark.skipif( @@ -45,7 +45,4 @@ def test_others(): with Image.open(path) as im: im.load() assert isinstance(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) + assert_image_equal_tofile(im, f"{os.path.splitext(path)[0]}.png") diff --git a/Tests/test_file_tar.py b/Tests/test_file_tar.py index 3fe0cd04e1a..b38727fb9b2 100644 --- a/Tests/test_file_tar.py +++ b/Tests/test_file_tar.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image, TarIO, features from .helper import is_pypy @@ -30,16 +31,16 @@ def open(): def test_close(): - def open(): + with pytest.warns(None) as record: tar = TarIO.TarIO(TEST_TAR_FILE, "hopper.jpg") tar.close() - pytest.warns(None, open) + assert not record def test_contextmanager(): - def open(): + with pytest.warns(None) as record: with TarIO.TarIO(TEST_TAR_FILE, "hopper.jpg"): pass - pytest.warns(None, open) + assert not record diff --git a/Tests/test_file_tga.py b/Tests/test_file_tga.py index 4919ad76627..e2351d72362 100644 --- a/Tests/test_file_tga.py +++ b/Tests/test_file_tga.py @@ -3,9 +3,10 @@ from itertools import product import pytest + from PIL import Image -from .helper import assert_image_equal, hopper +from .helper import assert_image_equal, assert_image_equal_tofile, hopper _TGA_DIR = os.path.join("Tests", "images", "tga") _TGA_DIR_COMMON = os.path.join(_TGA_DIR, "common") @@ -35,9 +36,7 @@ def roundtrip(original_im): assert_image_equal(saved_im, original_im) - png_paths = glob( - os.path.join(_TGA_DIR_COMMON, "*x*_{}.png".format(mode.lower())) - ) + 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: @@ -66,6 +65,16 @@ def roundtrip(original_im): roundtrip(original_im) +def test_palette_depth_16(tmp_path): + with Image.open("Tests/images/p_16.tga") as im: + assert_image_equal_tofile(im.convert("RGB"), "Tests/images/p_16.png") + + out = str(tmp_path / "temp.png") + im.save(out) + with Image.open(out) as reloaded: + assert_image_equal_tofile(reloaded.convert("RGB"), "Tests/images/p_16.png") + + def test_id_field(): # tga file with id field test_file = "Tests/images/tga_id_field.tga" @@ -113,6 +122,14 @@ def test_save_wrong_mode(tmp_path): im.save(out) +def test_save_mapdepth(): + # This image has been manually hexedited from 200x32_p_bl_raw.tga + # to include an origin + test_file = "Tests/images/200x32_p_bl_raw_origin.tga" + with Image.open(test_file) as im: + assert_image_equal_tofile(im, "Tests/images/tga/common/200x32_p.png") + + def test_save_id_section(tmp_path): test_file = "Tests/images/rgb32rle.tga" with Image.open(test_file) as im: @@ -154,6 +171,15 @@ def test_save_orientation(tmp_path): assert test_im.info["orientation"] == 1 +def test_horizontal_orientations(): + # These images have been manually hexedited to have the relevant orientations + with Image.open("Tests/images/rgb32rle_top_right.tga") as im: + assert im.load()[90, 90][:3] == (0, 0, 0) + + with Image.open("Tests/images/rgb32rle_bottom_right.tga") as im: + assert im.load()[90, 90][:3] == (0, 255, 0) + + def test_save_rle(tmp_path): test_file = "Tests/images/rgb32rle.tga" with Image.open(test_file) as im: diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index a996f0b0e7f..5801e176636 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -1,9 +1,9 @@ -import logging import os from io import BytesIO import pytest -from PIL import Image, TiffImagePlugin + +from PIL import Image, ImageFile, TiffImagePlugin from PIL.TiffImagePlugin import RESOLUTION_UNIT, X_RESOLUTION, Y_RESOLUTION from .helper import ( @@ -16,7 +16,10 @@ is_win32, ) -logger = logging.getLogger(__name__) +try: + import defusedxml.ElementTree as ElementTree +except ImportError: + ElementTree = None class TestFileTiff: @@ -61,19 +64,19 @@ def open(): pytest.warns(ResourceWarning, open) def test_closed_file(self): - def open(): + with pytest.warns(None) as record: im = Image.open("Tests/images/multipage.tiff") im.load() im.close() - pytest.warns(None, open) + assert not record def test_context_manager(self): - def open(): + with pytest.warns(None) as record: with Image.open("Tests/images/multipage.tiff") as im: im.load() - pytest.warns(None, open) + assert not record def test_mac_tiff(self): # Read RGBa images from macOS [@PIL136] @@ -139,38 +142,33 @@ def test_int_resolution(self): im._setup() 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: - 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: - assert im.tag_v2.get(RESOLUTION_UNIT) == resolutionUnit - assert im.info["dpi"] == (dpi[1], dpi[1]) - - def test_save_dpi_rounding(self, tmp_path): + @pytest.mark.parametrize( + "resolutionUnit, dpi", + [(None, 72.8), (2, 72.8), (3, 184.912)], + ) + def test_load_float_dpi(self, resolutionUnit, dpi): + with Image.open( + "Tests/images/hopper_float_dpi_" + str(resolutionUnit) + ".tif" + ) as im: + assert im.tag_v2.get(RESOLUTION_UNIT) == resolutionUnit + assert im.info["dpi"] == (dpi, dpi) + + def test_save_float_dpi(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)) + dpi = (72.2, 72.2) + im.save(outfile, dpi=dpi) - with Image.open(outfile) as reloaded: - reloaded.load() - assert (round(dpi), round(dpi)) == reloaded.info["dpi"] + with Image.open(outfile) as reloaded: + assert reloaded.info["dpi"] == dpi def test_save_setting_missing_resolution(self): b = BytesIO() - Image.open("Tests/images/10ct_32bit_128.tiff").save( - b, format="tiff", resolution=123.45 - ) + with Image.open("Tests/images/10ct_32bit_128.tiff") as im: + im.save(b, format="tiff", resolution=123.45) with Image.open(b) as im: - assert float(im.tag_v2[X_RESOLUTION]) == 123.45 - assert float(im.tag_v2[Y_RESOLUTION]) == 123.45 + assert im.tag_v2[X_RESOLUTION] == 123.45 + assert im.tag_v2[Y_RESOLUTION] == 123.45 def test_invalid_file(self): invalid_file = "Tests/images/flower.jpg" @@ -196,7 +194,7 @@ def test_save_rgba(self, tmp_path): def test_save_unsupported_mode(self, tmp_path): im = hopper("HSV") outfile = str(tmp_path / "temp.tif") - with pytest.raises(IOError): + with pytest.raises(OSError): im.save(outfile) def test_little_endian(self): @@ -227,8 +225,8 @@ def test_16bit_s(self): 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 -- @@ -249,8 +247,9 @@ def test_32bit_float(self): assert im.getextrema() == (-3.140936851501465, 3.140684127807617) def test_unknown_pixel_mode(self): - with pytest.raises(IOError): - Image.open("Tests/images/hopper_unknown_pixel_mode.tif") + with pytest.raises(OSError): + with Image.open("Tests/images/hopper_unknown_pixel_mode.tif"): + pass def test_n_frames(self): for path, n_frames in [ @@ -303,6 +302,19 @@ def test_multipage_last_frame(self): assert im.size == (20, 20) assert im.convert("RGB").getpixel((0, 0)) == (0, 0, 255) + def test_frame_order(self): + # A frame can't progress to itself after reading + with Image.open("Tests/images/multipage_single_frame_loop.tiff") as im: + assert im.n_frames == 1 + + # A frame can't progress to a frame that has already been read + with Image.open("Tests/images/multipage_multiple_frame_loop.tiff") as im: + assert im.n_frames == 2 + + # Frames don't have to be in sequence + with Image.open("Tests/images/multipage_out_of_order.tiff") as im: + assert im.n_frames == 3 + def test___str__(self): filename = "Tests/images/pil136.tiff" with Image.open(filename) as im: @@ -387,6 +399,79 @@ def test_load_double(self): ret = ifd.load_double(data, False) 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_exif(self, tmp_path): + def check_exif(exif): + assert sorted(exif.keys()) == [ + 256, + 257, + 258, + 259, + 262, + 271, + 272, + 273, + 277, + 278, + 279, + 282, + 283, + 284, + 296, + 297, + 305, + 339, + 700, + 34665, + 34853, + 50735, + ] + assert exif[256] == 640 + assert exif[271] == "FLIR" + + gps = exif.get_ifd(0x8825) + assert list(gps.keys()) == [0, 1, 2, 3, 4, 5, 6, 18] + assert gps[0] == b"\x03\x02\x00\x00" + assert gps[18] == "WGS-84" + + outfile = str(tmp_path / "temp.tif") + with Image.open("Tests/images/ifd_tag_type.tiff") as im: + exif = im.getexif() + check_exif(exif) + + im.save(outfile, exif=exif) + + outfile2 = str(tmp_path / "temp2.tif") + with Image.open(outfile) as im: + exif = im.getexif() + check_exif(exif) + + im.save(outfile2, exif=exif.tobytes()) + + with Image.open(outfile2) as im: + exif = im.getexif() + check_exif(exif) + + def test_exif_frames(self): + # Test that EXIF data can change across frames + with Image.open("Tests/images/g4-multi.tiff") as im: + assert im.getexif()[273] == (328, 815) + + im.seek(1) + assert im.getexif()[273] == (1408, 1907) + + @pytest.mark.parametrize("mode", ("1", "L")) + def test_photometric(self, mode, tmp_path): + filename = str(tmp_path / "temp.tif") + im = hopper(mode) + im.save(filename, tiffinfo={262: 0}) + with Image.open(filename) as reloaded: + assert reloaded.tag_v2[262] == 0 + assert_image_equal(im, reloaded) + def test_seek(self): filename = "Tests/images/pil136.tiff" with Image.open(filename) as im: @@ -481,8 +566,7 @@ def test_roundtrip_tiff_uint16(self, tmp_path): tmpfile = str(tmp_path / "temp.tif") im.save(tmpfile) - with Image.open(tmpfile) as reloaded: - assert_image_equal(im, reloaded) + assert_image_equal_tofile(im, tmpfile) def test_strip_raw(self): infile = "Tests/images/tiff_strip_raw.tif" @@ -567,6 +651,40 @@ def test_saving_icc_profile(self, tmp_path): with Image.open(tmpfile) as reloaded: assert b"Dummy value" == reloaded.info["icc_profile"] + def test_save_icc_profile(self, tmp_path): + im = hopper() + assert "icc_profile" not in im.info + + outfile = str(tmp_path / "temp.tif") + icc_profile = b"Dummy value" + im.save(outfile, icc_profile=icc_profile) + + with Image.open(outfile) as reloaded: + assert reloaded.info["icc_profile"] == icc_profile + + def test_discard_icc_profile(self, tmp_path): + outfile = str(tmp_path / "temp.tif") + + with Image.open("Tests/images/icc_profile.png") as im: + assert "icc_profile" in im.info + + im.save(outfile, icc_profile=None) + + with Image.open(outfile) as reloaded: + assert "icc_profile" not in reloaded.info + + def test_getxmp(self): + with Image.open("Tests/images/lab.tif") as im: + if ElementTree is None: + with pytest.warns(UserWarning): + assert im.getxmp() == {} + else: + xmp = im.getxmp() + + description = xmp["xmpmeta"]["RDF"]["Description"] + assert description[0]["format"] == "image/tiff" + assert description[3]["BitsPerSample"]["Seq"]["li"] == ["8", "8", "8"] + def test_close_on_load_exclusive(self, tmp_path): # similar to test_fd_leak, but runs on unixlike os tmpfile = str(tmp_path / "temp.tif") @@ -596,10 +714,25 @@ def test_close_on_load_nonexclusive(self, tmp_path): # 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") + # Ignore this UserWarning: + @pytest.mark.filterwarnings("ignore:Truncated File Read") + @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 pytest.raises(ValueError): - Image.open("Tests/images/string_dimension.tiff") + with Image.open("Tests/images/string_dimension.tiff") as im: + with pytest.raises(OSError): + im.load() + + @pytest.mark.timeout(6) + @pytest.mark.filterwarnings("ignore:Truncated File Read") + def test_timeout(self): + with Image.open("Tests/images/timeout-6646305047838720") as im: + ImageFile.LOAD_TRUNCATED_IMAGES = True + im.load() + ImageFile.LOAD_TRUNCATED_IMAGES = False @pytest.mark.skipif(not is_win32(), reason="Windows only") @@ -614,7 +747,7 @@ def test_fd_leak(self, tmp_path): im = Image.open(tmpfile) fp = im.fp assert not fp.closed - with pytest.raises(WindowsError): + with pytest.raises(OSError): os.remove(tmpfile) im.load() assert fp.closed diff --git a/Tests/test_file_tiff_metadata.py b/Tests/test_file_tiff_metadata.py index 9fe601bd65b..2213af5aadf 100644 --- a/Tests/test_file_tiff_metadata.py +++ b/Tests/test_file_tiff_metadata.py @@ -2,6 +2,7 @@ import struct import pytest + from PIL import Image, TiffImagePlugin, TiffTags from PIL.TiffImagePlugin import IFDRational @@ -11,10 +12,10 @@ 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 - """ + """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() @@ -121,7 +122,7 @@ def test_read_metadata(): def test_write_metadata(tmp_path): - """ Test metadata writing through the python code """ + """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) @@ -144,16 +145,33 @@ def test_write_metadata(tmp_path): assert_deep_equal( original[tag], value, - "{} didn't roundtrip, {}, {}".format(tag, original[tag], value), + f"{tag} didn't roundtrip, {original[tag]}, {value}", ) else: - assert original[tag] == value, "{} didn't roundtrip, {}, {}".format( - tag, original[tag], value - ) + assert ( + original[tag] == value + ), f"{tag} didn't roundtrip, {original[tag]}, {value}" for tag, value in original.items(): if tag not in ignored: - assert value == reloaded[tag], "%s didn't roundtrip" % tag + assert value == reloaded[tag], f"{tag} didn't roundtrip" + + +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 + + # Resize the image so that STRIPBYTECOUNTS will be larger than a SHORT + im = im.resize((500, 500)) + + # STRIPBYTECOUNTS can be a SHORT or a LONG + info.tagtype[TiffImagePlugin.STRIPBYTECOUNTS] = TiffTags.SHORT + + im.save(out, tiffinfo=info) + + with Image.open(out) as reloaded: + assert reloaded.tag_v2.tagtype[TiffImagePlugin.STRIPBYTECOUNTS] == TiffTags.LONG def test_no_duplicate_50741_tag(): @@ -161,6 +179,27 @@ def test_no_duplicate_50741_tag(): assert TAG_IDS["BestQualityScale"] == 50780 +def test_iptc(tmp_path): + out = str(tmp_path / "temp.tiff") + with Image.open("Tests/images/hopper.Lab.tif") as im: + im.save(out) + + +def test_undefined_zero(tmp_path): + # Check that the tag has not been changed since this test was created + tag = TiffTags.TAGS_V2[45059] + assert tag.type == TiffTags.UNDEFINED + assert tag.length == 0 + + info = TiffImagePlugin.ImageFileDirectory(b"II*\x00\x08\x00\x00\x00") + info[45059] = b"test" + + # Assert that the tag value does not change by setting it to itself + original = info[45059] + info[45059] = info[45059] + assert info[45059] == original + + def test_empty_metadata(): f = io.BytesIO(b"II*\x00\x08\x00\x00\x00") head = f.read(8) @@ -319,13 +358,13 @@ def test_empty_values(): def test_PhotoshopInfo(tmp_path): with Image.open("Tests/images/issue_2278.tif") as im: - assert len(im.tag_v2[34377]) == 1 - assert isinstance(im.tag_v2[34377][0], bytes) + 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]) == 1 - assert isinstance(reloaded.tag_v2[34377][0], bytes) + assert len(reloaded.tag_v2[34377]) == 70 + assert isinstance(reloaded.tag_v2[34377], bytes) def test_too_many_entries(): @@ -337,3 +376,30 @@ def test_too_many_entries(): # Should not raise ValueError. pytest.warns(UserWarning, lambda: ifd[277]) + + +def test_tag_group_data(): + base_ifd = TiffImagePlugin.ImageFileDirectory_v2() + interop_ifd = TiffImagePlugin.ImageFileDirectory_v2(group=40965) + for ifd in (base_ifd, interop_ifd): + ifd[2] = "test" + ifd[256] = 10 + + assert base_ifd.tagtype[256] == 4 + assert interop_ifd.tagtype[256] != base_ifd.tagtype[256] + + assert interop_ifd.tagtype[2] == 7 + assert base_ifd.tagtype[2] != interop_ifd.tagtype[256] + + +def test_empty_subifd(tmp_path): + out = str(tmp_path / "temp.jpg") + + im = hopper() + exif = im.getexif() + exif[TiffImagePlugin.EXIFIFD] = {} + im.save(out, exif=exif) + + with Image.open(out) as reloaded: + exif = reloaded.getexif() + assert exif.get_ifd(TiffImagePlugin.EXIFIFD) == {} diff --git a/Tests/test_file_wal.py b/Tests/test_file_wal.py index 60be1d5bcd5..f25b42fe0c4 100644 --- a/Tests/test_file_wal.py +++ b/Tests/test_file_wal.py @@ -1,15 +1,21 @@ from PIL import WalImageFile +from .helper import assert_image_equal_tofile + def test_open(): # Arrange TEST_FILE = "Tests/images/hopper.wal" # Act - im = WalImageFile.open(TEST_FILE) + with WalImageFile.open(TEST_FILE) as im: + + # Assert + assert im.format == "WAL" + assert im.format_description == "Quake2 Texture" + assert im.mode == "P" + assert im.size == (128, 128) + + assert isinstance(im, WalImageFile.WalImageFile) - # Assert - assert im.format == "WAL" - assert im.format_description == "Quake2 Texture" - assert im.mode == "P" - assert im.size == (128, 128) + assert_image_equal_tofile(im, "Tests/images/hopper_wal.png") diff --git a/Tests/test_file_webp.py b/Tests/test_file_webp.py index 22957f06d9b..e72b4993c63 100644 --- a/Tests/test_file_webp.py +++ b/Tests/test_file_webp.py @@ -1,5 +1,10 @@ +import io +import re +import sys + import pytest -from PIL import Image, WebPImagePlugin + +from PIL import Image, WebPImagePlugin, features from .helper import ( assert_image_similar, @@ -22,7 +27,7 @@ def test_unsupported(self): WebPImagePlugin.SUPPORTED = False file_path = "Tests/images/hopper.webp" - pytest.warns(UserWarning, lambda: pytest.raises(IOError, Image.open, file_path)) + pytest.warns(UserWarning, lambda: pytest.raises(OSError, Image.open, file_path)) if HAVE_WEBP: WebPImagePlugin.SUPPORTED = True @@ -36,6 +41,7 @@ def setup_method(self): def test_version(self): _webp.WebPDecoderVersion() _webp.WebPDecoderBuggyAlpha() + assert re.search(r"\d+\.\d+\.\d+$", features.version_module("webp")) def test_read_rgb(self): """ @@ -54,15 +60,10 @@ def test_read_rgb(self): # 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, tmp_path): - """ - 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") - hopper(self.rgb_mode).save(temp_file) + hopper(mode).save(temp_file, **args) with Image.open(temp_file) as image: assert image.mode == self.rgb_mode assert image.size == (128, 128) @@ -70,18 +71,45 @@ def test_write_rgb(self, tmp_path): 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_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_icc_profile(self, tmp_path): + self._roundtrip(tmp_path, self.rgb_mode, 12.5, {"icc_profile": None}) + if _webp.HAVE_WEBPANIM: + self._roundtrip( + tmp_path, self.rgb_mode, 12.5, {"icc_profile": None, "save_all": True} + ) def test_write_unsupported_mode_L(self, tmp_path): """ @@ -89,18 +117,7 @@ def test_write_unsupported_mode_L(self, tmp_path): similar to the original file. """ - temp_file = str(tmp_path / "temp.webp") - hopper("L").save(temp_file) - with Image.open(temp_file) as image: - assert image.mode == self.rgb_mode - assert image.size == (128, 128) - assert image.format == "WEBP" - - image.load() - image.getdata() - target = hopper("L").convert(self.rgb_mode) - - assert_image_similar(image, target, 10.0) + self._roundtrip(tmp_path, "L", 10.0) def test_write_unsupported_mode_P(self, tmp_path): """ @@ -108,18 +125,15 @@ def test_write_unsupported_mode_P(self, tmp_path): similar to the original file. """ - temp_file = str(tmp_path / "temp.webp") - hopper("P").save(temp_file) - with Image.open(temp_file) as image: - assert image.mode == self.rgb_mode - assert image.size == (128, 128) - assert image.format == "WEBP" - - image.load() - image.getdata() - target = hopper("P").convert(self.rgb_mode) + self._roundtrip(tmp_path, "P", 50.0) - assert_image_similar(image, target, 50.0) + @pytest.mark.skipif(sys.maxsize <= 2 ** 32, reason="Requires 64-bit system") + def test_write_encoding_error_message(self, tmp_path): + temp_file = str(tmp_path / "temp.webp") + im = Image.new("RGB", (15000, 15000)) + with pytest.raises(ValueError) as e: + im.save(temp_file, method=0) + assert str(e.value) == "encoding error 6" def test_WebPEncode_with_invalid_args(self): """ @@ -147,7 +161,9 @@ def test_no_resource_warning(self, tmp_path): file_path = "Tests/images/hopper.webp" with Image.open(file_path) as image: temp_file = str(tmp_path / "temp.webp") - pytest.warns(None, image.save, temp_file) + with pytest.warns(None) as record: + image.save(temp_file) + assert not record def test_file_pointer_could_be_reused(self): file_path = "Tests/images/hopper.webp" @@ -167,11 +183,23 @@ def test_background_from_gif(self, tmp_path): # Save as GIF out_gif = str(tmp_path / "temp.gif") - Image.open(out_webp).save(out_gif) + with Image.open(out_webp) as im: + im.save(out_gif) with Image.open(out_gif) as reread: reread_value = reread.convert("RGB").getpixel((1, 1)) - difference = sum( - [abs(original_value[i] - reread_value[i]) for i in range(0, 3)] - ) + difference = sum(abs(original_value[i] - reread_value[i]) for i in range(0, 3)) assert difference < 5 + + @skip_unless_feature("webp") + @skip_unless_feature("webp_anim") + def test_duration(self, tmp_path): + with Image.open("Tests/images/dispose_bgnd.gif") as im: + assert im.info["duration"] == 1000 + + out_webp = str(tmp_path / "temp.webp") + im.save(out_webp, save_all=True) + + with Image.open(out_webp) as reloaded: + reloaded.load() + assert reloaded.info["duration"] == 1000 diff --git a/Tests/test_file_webp_alpha.py b/Tests/test_file_webp_alpha.py index c624156df13..dc82fb742b2 100644 --- a/Tests/test_file_webp_alpha.py +++ b/Tests/test_file_webp_alpha.py @@ -1,7 +1,13 @@ import pytest + from PIL import Image -from .helper import assert_image_equal, assert_image_similar, hopper +from .helper import ( + assert_image_equal, + assert_image_similar, + assert_image_similar_tofile, + hopper, +) _webp = pytest.importorskip("PIL._webp", reason="WebP support not installed") @@ -28,8 +34,7 @@ def test_read_rgba(): image.tobytes() - with Image.open("Tests/images/transparent.png") as target: - assert_image_similar(image, target, 20.0) + assert_image_similar_tofile(image, "Tests/images/transparent.png", 20.0) def test_write_lossless_rgb(tmp_path): diff --git a/Tests/test_file_webp_animated.py b/Tests/test_file_webp_animated.py index a846a6db4fd..25ebffe0248 100644 --- a/Tests/test_file_webp_animated.py +++ b/Tests/test_file_webp_animated.py @@ -1,11 +1,11 @@ import pytest + from PIL import Image from .helper import ( assert_image_equal, assert_image_similar, is_big_endian, - on_ci, skip_unless_feature, ) @@ -27,7 +27,7 @@ def test_n_frames(): assert im.is_animated -@pytest.mark.xfail(is_big_endian() and on_ci(), reason="Fails on big-endian") +@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 @@ -45,15 +45,15 @@ def test_write_animation_L(tmp_path): # Compare first and last frames to the original animated GIF orig.load() im.load() - assert_image_similar(im, orig.convert("RGBA"), 25.0) + assert_image_similar(im, orig.convert("RGBA"), 32.9) 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) + assert_image_similar(im, orig.convert("RGBA"), 32.9) -@pytest.mark.xfail(is_big_endian() and on_ci(), reason="Fails on big-endian") +@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 diff --git a/Tests/test_file_webp_lossless.py b/Tests/test_file_webp_lossless.py index 4d06f53b1af..2da443628d7 100644 --- a/Tests/test_file_webp_lossless.py +++ b/Tests/test_file_webp_lossless.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image from .helper import assert_image_equal, hopper diff --git a/Tests/test_file_webp_metadata.py b/Tests/test_file_webp_metadata.py index 9fa20e403bf..e6d6fc63fc4 100644 --- a/Tests/test_file_webp_metadata.py +++ b/Tests/test_file_webp_metadata.py @@ -1,8 +1,10 @@ from io import BytesIO +import pytest + from PIL import Image -from .helper import skip_unless_feature +from .helper import mark_if_feature_version, skip_unless_feature pytestmark = [ skip_unless_feature("webp"), @@ -30,6 +32,18 @@ def test_read_exif_metadata(): assert 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" + + exif = im.getexif() + assert exif[305] == "Adobe Photoshop CS6 (Macintosh)" + + +@mark_if_feature_version( + pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" +) def test_write_exif_metadata(): file_path = "Tests/images/flower.jpg" test_buffer = BytesIO() @@ -62,6 +76,9 @@ def test_read_icc_profile(): assert icc == expected_icc +@mark_if_feature_version( + pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" +) def test_write_icc_metadata(): file_path = "Tests/images/flower2.jpg" test_buffer = BytesIO() @@ -79,6 +96,9 @@ def test_write_icc_metadata(): assert webp_icc_profile == expected_icc_profile, "Webp ICC didn't match" +@mark_if_feature_version( + pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" +) def test_read_no_exif(): file_path = "Tests/images/flower.jpg" test_buffer = BytesIO() diff --git a/Tests/test_file_wmf.py b/Tests/test_file_wmf.py index 03444eb9d3f..3f8bc96ccdd 100644 --- a/Tests/test_file_wmf.py +++ b/Tests/test_file_wmf.py @@ -1,7 +1,8 @@ import pytest + from PIL import Image, WmfImagePlugin -from .helper import assert_image_similar, hopper +from .helper import assert_image_similar_tofile, hopper def test_load_raw(): @@ -12,9 +13,7 @@ def test_load_raw(): # 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) + assert_image_similar_tofile(im, "Tests/images/drawing_emf_ref.png", 0) # Test basic WMF open and rendering with Image.open("Tests/images/drawing.wmf") as im: @@ -22,9 +21,7 @@ def test_load_raw(): # 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) + assert_image_similar_tofile(im, "Tests/images/drawing_wmf_ref.png", 2.0) def test_register_handler(tmp_path): @@ -47,14 +44,9 @@ def save(self, im, fp, filename): WmfImagePlugin.register_handler(original_handler) -def test_load_dpi_rounding(): - # Round up +def test_load_float_dpi(): with Image.open("Tests/images/drawing.emf") as im: - assert im.info["dpi"] == 1424 - - # Round down - with Image.open("Tests/images/drawing_roundDown.emf") as im: - assert im.info["dpi"] == 1426 + assert im.info["dpi"] == 1423.7668161434979 def test_load_set_dpi(): @@ -65,8 +57,7 @@ def test_load_set_dpi(): 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.0) + assert_image_similar_tofile(im, "Tests/images/drawing_wmf_ref_144.png", 2.1) def test_save(tmp_path): @@ -74,5 +65,5 @@ def test_save(tmp_path): for ext in [".wmf", ".emf"]: tmpfile = str(tmp_path / ("temp" + ext)) - with pytest.raises(IOError): + with pytest.raises(OSError): im.save(tmpfile) diff --git a/Tests/test_file_xbm.py b/Tests/test_file_xbm.py index 23a54056969..487920a9282 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 187440d4e2a..8595b07eb91 100644 --- a/Tests/test_file_xpm.py +++ b/Tests/test_file_xpm.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image, XpmImagePlugin from .helper import assert_image_similar, hopper diff --git a/Tests/test_file_xvthumb.py b/Tests/test_file_xvthumb.py index 7c8c451139f..ae53d2b6357 100644 --- a/Tests/test_file_xvthumb.py +++ b/Tests/test_file_xvthumb.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image, XVThumbImagePlugin from .helper import assert_image_similar, hopper diff --git a/Tests/test_font_bdf.py b/Tests/test_font_bdf.py index 4be39c383de..1e7caee3297 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 015210b4d4c..38f7ddac5de 100644 --- a/Tests/test_font_leaks.py +++ b/Tests/test_font_leaks.py @@ -4,7 +4,7 @@ class TestTTypeFontLeak(PillowLeakTestCase): - # fails at iteration 3 in master + # fails at iteration 3 in main iterations = 10 mem_limit = 4096 # k @@ -24,7 +24,7 @@ def test_leak(self): class TestDefaultFontLeak(TestTTypeFontLeak): - # fails at iteration 37 in master + # fails at iteration 37 in main iterations = 100 mem_limit = 1024 # k diff --git a/Tests/test_font_pcf.py b/Tests/test_font_pcf.py index afd0c38b2e6..288848f2619 100644 --- a/Tests/test_font_pcf.py +++ b/Tests/test_font_pcf.py @@ -1,9 +1,14 @@ import os import pytest + from PIL import FontFile, Image, ImageDraw, ImageFont, PcfFontFile -from .helper import assert_image_equal, assert_image_similar, skip_unless_feature +from .helper import ( + assert_image_equal_tofile, + assert_image_similar_tofile, + skip_unless_feature, +) fontname = "Tests/fonts/10x20-ISO8859-1.pcf" @@ -32,8 +37,7 @@ def delete_tempfile(): font.save(tempname) with Image.open(tempname.replace(".pil", ".pbm")) as loaded: - with Image.open("Tests/fonts/10x20.pbm") as target: - assert_image_equal(loaded, target) + assert_image_equal_tofile(loaded, "Tests/fonts/10x20.pbm") with open(tempname, "rb") as f_loaded: with open("Tests/fonts/10x20.pil", "rb") as f_target: @@ -57,8 +61,7 @@ def test_draw(request, tmp_path): 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) + assert_image_similar_tofile(im, "Tests/images/test_draw_pbm_target.png", 0) def test_textsize(request, tmp_path): @@ -68,8 +71,8 @@ def test_textsize(request, tmp_path): (dx, dy) = font.getsize(chr(i)) assert dy == 20 assert dx in (0, 10) - for l in range(len(message)): - msg = message[: l + 1] + for i in range(len(message)): + msg = message[: i + 1] assert font.getsize(msg) == (len(msg) * 10, 20) @@ -79,8 +82,7 @@ def _test_high_characters(request, tmp_path, message): 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) + assert_image_similar_tofile(im, "Tests/images/high_ascii_chars.png", 0) def test_high_characters(request, tmp_path): diff --git a/Tests/test_font_pcf_charsets.py b/Tests/test_font_pcf_charsets.py index 8621f18aee4..a1036fd28e6 100644 --- a/Tests/test_font_pcf_charsets.py +++ b/Tests/test_font_pcf_charsets.py @@ -2,7 +2,11 @@ from PIL import FontFile, Image, ImageDraw, ImageFont, PcfFontFile -from .helper import assert_image_equal, assert_image_similar, skip_unless_feature +from .helper import ( + assert_image_equal_tofile, + assert_image_similar_tofile, + skip_unless_feature, +) fontname = "Tests/fonts/ter-x20b.pcf" @@ -47,11 +51,10 @@ def delete_tempfile(): font.save(tempname) with Image.open(tempname.replace(".pil", ".pbm")) as loaded: - with Image.open("Tests/fonts/ter-x20b-%s.pbm" % encoding) as target: - assert_image_equal(loaded, target) + assert_image_equal_tofile(loaded, f"Tests/fonts/ter-x20b-{encoding}.pbm") with open(tempname, "rb") as f_loaded: - with open("Tests/fonts/ter-x20b-%s.pil" % encoding, "rb") as f_target: + with open(f"Tests/fonts/ter-x20b-{encoding}.pil", "rb") as f_target: assert f_loaded.read() == f_target.read() return tempname @@ -79,8 +82,7 @@ def _test_draw(request, tmp_path, encoding): 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) + assert_image_similar_tofile(im, charsets[encoding]["image1"], 0) def test_draw_iso8859_1(request, tmp_path): @@ -103,8 +105,8 @@ def _test_textsize(request, tmp_path, encoding): assert dy == 20 assert dx in (0, 10) message = charsets[encoding]["message"].encode(encoding) - for l in range(len(message)): - msg = message[: l + 1] + for i in range(len(message)): + msg = message[: i + 1] assert font.getsize(msg) == (len(msg) * 10, 20) diff --git a/Tests/test_format_hsv.py b/Tests/test_format_hsv.py index d10b1acfd66..3b9c8b07146 100644 --- a/Tests/test_format_hsv.py +++ b/Tests/test_format_hsv.py @@ -85,7 +85,10 @@ def test_wedge(): 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", + 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" @@ -113,7 +116,10 @@ def test_convert(): 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", + 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" @@ -126,11 +132,20 @@ def test_hsv_to_rgb(): comparable = to_rgb_colorsys(comparable) assert_image_similar( - converted.getchannel(0), comparable.getchannel(0), 3, "R conversion is wrong", + 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", + 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", + converted.getchannel(2), + comparable.getchannel(2), + 3, + "B conversion is wrong", ) diff --git a/Tests/test_image.py b/Tests/test_image.py index 3a0b7bd62d9..4dde66f11a1 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -1,18 +1,22 @@ import io import os import shutil +import sys import tempfile -import PIL import pytest + from PIL import Image, ImageDraw, ImagePalette, UnidentifiedImageError from .helper import ( assert_image_equal, - assert_image_similar, + assert_image_equal_tofile, + assert_image_similar_tofile, assert_not_all_same, hopper, is_win32, + mark_if_feature_version, + skip_unless_feature, ) @@ -57,7 +61,7 @@ def test_image_modes_fail(self): assert str(e.value) == "unrecognized image mode" def test_exception_inheritance(self): - assert issubclass(UnidentifiedImageError, IOError) + assert issubclass(UnidentifiedImageError, OSError) def test_sanity(self): @@ -85,6 +89,28 @@ def test_sanity(self): # with pytest.raises(MemoryError): # Image.new("L", (1000000, 1000000)) + def test_open_formats(self): + PNGFILE = "Tests/images/hopper.png" + JPGFILE = "Tests/images/hopper.jpg" + + with pytest.raises(TypeError): + with Image.open(PNGFILE, formats=123): + pass + + for formats in [["JPEG"], ("JPEG",), ["jpeg"], ["Jpeg"], ["jPeG"], ["JpEg"]]: + with pytest.raises(UnidentifiedImageError): + with Image.open(PNGFILE, formats=formats): + pass + + with Image.open(JPGFILE, formats=formats) as im: + assert im.mode == "RGB" + assert im.size == (128, 128) + + for file in [PNGFILE, JPGFILE]: + with Image.open(file, formats=None) as im: + assert im.mode == "RGB" + assert im.size == (128, 128) + def test_width_height(self): im = Image.new("RGB", (1, 2)) assert im.width == 1 @@ -98,15 +124,18 @@ def test_invalid_image(self): im = io.BytesIO(b"") with pytest.raises(UnidentifiedImageError): - Image.open(im) + with Image.open(im): + pass def test_bad_mode(self): with pytest.raises(ValueError): - Image.open("filename", "bad mode") + with Image.open("filename", "bad mode"): + pass def test_stringio(self): with pytest.raises(ValueError): - Image.open(io.StringIO()) + with Image.open(io.StringIO()): + pass def test_pathlib(self, tmp_path): from PIL.Image import Path @@ -119,10 +148,11 @@ def test_pathlib(self, tmp_path): assert im.mode == "RGB" assert im.size == (128, 128) - temp_file = str(tmp_path / "temp.jpg") - if os.path.exists(temp_file): - os.remove(temp_file) - im.save(Path(temp_file)) + for ext in (".jpg", ".jp2"): + temp_file = str(tmp_path / ("temp." + ext)) + if os.path.exists(temp_file): + os.remove(temp_file) + im.save(Path(temp_file)) def test_fp_name(self, tmp_path): temp_file = str(tmp_path / "temp.jpg") @@ -144,8 +174,7 @@ def test_tempfile(self): with tempfile.TemporaryFile() as fp: im.save(fp, "JPEG") fp.seek(0) - with Image.open(fp) as reloaded: - assert_image_similar(im, reloaded, 20) + assert_image_similar_tofile(im, fp, 20) def test_unknown_extension(self, tmp_path): im = hopper() @@ -164,6 +193,10 @@ def test_internals(self): assert not im.readonly @pytest.mark.skipif(is_win32(), reason="Test requires opening tempfile twice") + @pytest.mark.skipif( + sys.platform == "cygwin", + reason="Test requires opening an mmaped file for writing", + ) def test_readonly_save(self, tmp_path): temp_file = str(tmp_path / "temp.bmp") shutil.copy("Tests/images/rgb32bf-rgba.bmp", temp_file) @@ -317,6 +350,12 @@ def test_alpha_inplace(self): assert_image_equal(offset.crop((64, 64, 127, 127)), target.crop((0, 0, 63, 63))) assert offset.size == (128, 128) + # with negative offset + offset = src.copy() + offset.alpha_composite(over, (-64, -64)) + assert_image_equal(offset.crop((0, 0, 63, 63)), target.crop((64, 64, 127, 127))) + assert offset.size == (128, 128) + # offset and crop box = src.copy() box.alpha_composite(over, (64, 64), (0, 0, 32, 32)) @@ -340,8 +379,6 @@ def test_alpha_inplace(self): source.alpha_composite(over, 0) with pytest.raises(ValueError): source.alpha_composite(over, (0, 0), 0) - with pytest.raises(ValueError): - source.alpha_composite(over, (0, -1)) with pytest.raises(ValueError): source.alpha_composite(over, (0, 0), (0, -1)) @@ -386,8 +423,7 @@ def test_effect_mandelbrot(self): # Assert assert im.size == (512, 512) - with Image.open("Tests/images/effect_mandelbrot.png") as im2: - assert_image_equal(im, im2) + assert_image_equal_tofile(im, "Tests/images/effect_mandelbrot.png") def test_effect_mandelbrot_bad_arguments(self): # Arrange @@ -429,8 +465,18 @@ def test_effect_spread(self): # Assert assert im.size == (128, 128) - with Image.open("Tests/images/effect_spread.png") as im3: - assert_image_similar(im2, im3, 110) + assert_image_similar_tofile(im2, "Tests/images/effect_spread.png", 110) + + def test_effect_spread_zero(self): + # Arrange + im = hopper() + distance = 0 + + # Act + im2 = im.effect_spread(distance) + + # Assert + assert_image_equal(im, im2) def test_check_size(self): # Checking that the _check_size function throws value errors when we want it to @@ -465,17 +511,11 @@ def test_storage_neg(self): with pytest.raises(ValueError): Image.core.fill("RGB", (2, -2), (0, 0, 0)) - def test_offset_not_implemented(self): - # Arrange - with hopper() as im: - - # Act / Assert - with pytest.raises(NotImplementedError): - im.offset(None) - - def test_fromstring(self): - with pytest.raises(NotImplementedError): - Image.fromstring() + def test_one_item_tuple(self): + for mode in ("I", "F", "L"): + im = Image.new(mode, (100, 100), (5,)) + px = im.load() + assert px[0, 0] == 5 def test_linear_gradient_wrong_mode(self): # Arrange @@ -489,7 +529,7 @@ def test_linear_gradient(self): # Arrange target_file = "Tests/images/linear_gradient.png" - for mode in ["L", "P"]: + for mode in ["L", "P", "I", "F"]: # Act im = Image.linear_gradient(mode) @@ -515,7 +555,7 @@ def test_radial_gradient(self): # Arrange target_file = "Tests/images/radial_gradient.png" - for mode in ["L", "P"]: + for mode in ["L", "P", "I", "F"]: # Act im = Image.radial_gradient(mode) @@ -546,6 +586,10 @@ def test_register_extensions(self): assert ext_individual == ext_multiple def test_remap_palette(self): + # Test identity transform + with Image.open("Tests/images/hopper.gif") as im: + assert_image_equal(im, im.remap_palette(list(range(256)))) + # Test illegal image mode with hopper() as im: with pytest.raises(ValueError): @@ -570,7 +614,7 @@ def _make_new(base_image, im, palette_result=None): else: assert new_im.palette is None - _make_new(im, im_p, im_p.palette) + _make_new(im, im_p, ImagePalette.ImagePalette(list(range(256)) * 3)) _make_new(im_p, im, None) _make_new(im, blank_p, ImagePalette.ImagePalette()) _make_new(im, blank_pa, ImagePalette.ImagePalette()) @@ -593,7 +637,9 @@ def test_no_resource_warning_on_save(self, tmp_path): # Act/Assert with Image.open(test_file) as im: - pytest.warns(None, im.save, temp_file) + with pytest.warns(None) as record: + im.save(temp_file) + assert not record def test_load_on_nonexclusive_multiframe(self): with open("Tests/images/frozenpond.mpo", "rb") as fp: @@ -609,54 +655,176 @@ def act(fp): assert not fp.closed - @pytest.mark.parametrize( - "test_module", [PIL, Image], + @mark_if_feature_version( + pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" ) - def test_pillow_version(self, test_module): + def test_exif_jpeg(self, tmp_path): + with Image.open("Tests/images/exif-72dpi-int.jpg") as im: # Little endian + exif = im.getexif() + assert 258 not in exif + assert 274 in exif + assert 282 in exif + assert exif[296] == 2 + assert exif[11] == "gThumb 3.0.1" + + out = str(tmp_path / "temp.jpg") + exif[258] = 8 + del exif[274] + del exif[282] + exif[296] = 455 + exif[11] = "Pillow test" + im.save(out, exif=exif) + with Image.open(out) as reloaded: + reloaded_exif = reloaded.getexif() + assert reloaded_exif[258] == 8 + assert 274 not in reloaded_exif + assert 282 not in reloaded_exif + assert reloaded_exif[296] == 455 + assert reloaded_exif[11] == "Pillow test" + + with Image.open("Tests/images/no-dpi-in-exif.jpg") as im: # Big endian + exif = im.getexif() + assert 258 not in exif + assert 306 in exif + assert exif[274] == 1 + assert exif[305] == "Adobe Photoshop CC 2017 (Macintosh)" + + out = str(tmp_path / "temp.jpg") + exif[258] = 8 + del exif[306] + exif[274] = 455 + exif[305] = "Pillow test" + im.save(out, exif=exif) + with Image.open(out) as reloaded: + reloaded_exif = reloaded.getexif() + assert reloaded_exif[258] == 8 + assert 306 not in reloaded_exif + assert reloaded_exif[274] == 455 + assert reloaded_exif[305] == "Pillow test" + + @skip_unless_feature("webp") + @skip_unless_feature("webp_anim") + def test_exif_webp(self, tmp_path): + with Image.open("Tests/images/hopper.webp") as im: + exif = im.getexif() + assert exif == {} + + out = str(tmp_path / "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() + assert reloaded_exif[258] == 8 + assert reloaded_exif[40963] == 455 + assert reloaded_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, tmp_path): + with Image.open("Tests/images/exif.png") as im: + exif = im.getexif() + assert exif == {274: 1} + + out = str(tmp_path / "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() + assert 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() + assert exif.get_ifd(0xA005) == { + 1: "R98", + 2: b"0100", + 4097: 2272, + 4098: 1704, + } + + reloaded_exif = Image.Exif() + reloaded_exif.load(exif.tobytes()) + assert reloaded_exif.get_ifd(0xA005) == exif.get_ifd(0xA005) + + def test_exif_ifd(self): + with Image.open("Tests/images/flower.jpg") as im: + exif = im.getexif() + del exif.get_ifd(0x8769)[0xA005] + + reloaded_exif = Image.Exif() + reloaded_exif.load(exif.tobytes()) + assert reloaded_exif.get_ifd(0x8769) == exif.get_ifd(0x8769) + + def test_exif_load_from_fp(self): + with Image.open("Tests/images/flower.jpg") as im: + data = im.info["exif"] + if data.startswith(b"Exif\x00\x00"): + data = data[6:] + fp = io.BytesIO(data) + + exif = Image.Exif() + exif.load_from_fp(fp) + assert exif == { + 271: "Canon", + 272: "Canon PowerShot S40", + 274: 1, + 282: 180.0, + 283: 180.0, + 296: 2, + 306: "2003:12:14 12:01:44", + 531: 1, + 34665: 196, + } + + def test_categories_deprecation(self): with pytest.warns(DeprecationWarning): - assert test_module.PILLOW_VERSION == PIL.__version__ + assert hopper().category == 0 with pytest.warns(DeprecationWarning): - str(test_module.PILLOW_VERSION) - + assert Image.NORMAL == 0 with pytest.warns(DeprecationWarning): - assert int(test_module.PILLOW_VERSION[0]) >= 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" - + assert Image.SEQUENCE == 1 with pytest.warns(DeprecationWarning): - assert test_module.PILLOW_VERSION > "7.0.0" + assert Image.CONTAINER == 2 - def test_overrun(self): - """ For overrun completeness, test as: - valgrind pytest -qq Tests/test_image.py::TestImage::test_overrun | grep decode.c - """ - for file in [ + @pytest.mark.parametrize( + "path", + [ "fli_overrun.bin", "sgi_overrun.bin", "sgi_overrun_expandrow.bin", "sgi_overrun_expandrow2.bin", "pcx_overrun.bin", "pcx_overrun2.bin", + "ossfuzz-4836216264589312.pcx", "01r_00.pcx", - ]: - with Image.open(os.path.join("Tests/images", file)) as im: - try: - im.load() - assert False - except OSError as e: - assert str(e) == "buffer overrun when reading image file" + ], + ) + 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) @@ -687,5 +855,5 @@ def test_encode_registry(self): assert enc.args == ("RGB", "args", "extra") def test_encode_registry_fail(self): - with pytest.raises(IOError): + 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 25cc9fef4e3..7b30369793c 100644 --- a/Tests/test_image_access.py +++ b/Tests/test_image_access.py @@ -2,9 +2,11 @@ import os import subprocess import sys -from distutils import ccompiler, sysconfig +import sysconfig import pytest +from setuptools.command.build_ext import new_compiler + from PIL import Image from .helper import assert_image_equal, hopper, is_win32, on_ci @@ -15,11 +17,17 @@ cffi = None else: try: - from PIL import PyAccess import cffi + + from PIL import PyAccess except ImportError: cffi = None +try: + import numpy +except ImportError: + numpy = None + class AccessTest: # initial value @@ -63,6 +71,10 @@ def test_sanity(self): pix1 = im1.load() pix2 = im2.load() + for x, y in ((0, "0"), ("0", 0)): + with pytest.raises(TypeError): + pix1[x, y] + for y in range(im1.size[1]): for x in range(im1.size[0]): pix2[x, y] = pix1[x, y] @@ -106,6 +118,13 @@ def test_sanity_negative_index(self): assert_image_equal(im1, im2) + @pytest.mark.skipif(numpy is None, reason="NumPy not installed") + def test_numpy(self): + im = hopper() + pix = im.load() + + assert pix[numpy.int32(1), numpy.int32(2)] == (18, 20, 59) + class TestImageGetPixel(AccessTest): @staticmethod @@ -125,14 +144,13 @@ def check(self, mode, c=None): im.putpixel((0, 0), c) assert ( im.getpixel((0, 0)) == c - ), "put/getpixel roundtrip failed for mode {}, color {}".format(mode, c) + ), f"put/getpixel roundtrip failed for mode {mode}, color {c}" # check putpixel negative index im.putpixel((-1, -1), c) - assert 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) @@ -150,11 +168,11 @@ def check(self, mode, c=None): im = Image.new(mode, (1, 1), c) assert ( im.getpixel((0, 0)) == c - ), "initial color failed for mode {}, color {} ".format(mode, c) + ), f"initial color failed for mode {mode}, color {c} " # check initial color negative index assert ( im.getpixel((-1, -1)) == c - ), "initial color failed with negative index for mode %s, color %s " % (mode, c) + ), f"initial color failed with negative index for mode {mode}, color {c} " # Check 0 im = Image.new(mode, (0, 0), c) @@ -325,6 +343,57 @@ def test_p_putpixel_rgb_rgba(self): assert im.convert("RGB").getpixel((0, 0)) == (255, 0, 0) +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", "band_numbers", "match"), + ( + ("L", (0, 2), "color must be int or single-element tuple"), + ("LA", (0, 3), "color must be int, or tuple of one or two elements"), + ( + "RGB", + (0, 2, 5), + "color must be int, or tuple of one, three or four elements", + ), + ), + ) + def test_putpixel_invalid_number_of_bands(self, mode, band_numbers, match): + im = hopper(mode) + for band_number in band_numbers: + with pytest.raises(TypeError, match=match): + im.putpixel((0, 0), (0,) * band_number) + + @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(), @@ -359,13 +428,12 @@ def test_embeddable(self): % 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") diff --git a/Tests/test_image_array.py b/Tests/test_image_array.py index bf6d88a97e5..5c9cdd7e0e0 100644 --- a/Tests/test_image_array.py +++ b/Tests/test_image_array.py @@ -1,33 +1,47 @@ import pytest + from PIL import Image from .helper import hopper +numpy = pytest.importorskip("numpy", reason="NumPy not installed") + im = hopper().resize((128, 100)) def test_toarray(): def test(mode): - ai = im.convert(mode).__array_interface__ - return ai["version"], ai["shape"], ai["typestr"], len(ai["data"]) + ai = numpy.array(im.convert(mode)) + return ai.shape, ai.dtype.str, ai.nbytes + + def test_with_dtype(dtype): + ai = numpy.array(im, dtype=dtype) + assert ai.dtype == dtype - # assert test("1") == (3, (100, 128), '|b1', 1600)) - assert test("L") == (3, (100, 128), "|u1", 12800) + # assert test("1") == ((100, 128), '|b1', 1600)) + assert test("L") == ((100, 128), "|u1", 12800) # FIXME: wrong? - assert test("I") == (3, (100, 128), Image._ENDIAN + "i4", 51200) + assert test("I") == ((100, 128), Image._ENDIAN + "i4", 51200) # FIXME: wrong? - assert test("F") == (3, (100, 128), Image._ENDIAN + "f4", 51200) + assert test("F") == ((100, 128), Image._ENDIAN + "f4", 51200) + + assert test("LA") == ((100, 128, 2), "|u1", 25600) + assert test("RGB") == ((100, 128, 3), "|u1", 38400) + assert test("RGBA") == ((100, 128, 4), "|u1", 51200) + assert test("RGBX") == ((100, 128, 4), "|u1", 51200) + + test_with_dtype(numpy.float64) + test_with_dtype(numpy.uint8) - assert test("LA") == (3, (100, 128, 2), "|u1", 25600) - assert test("RGB") == (3, (100, 128, 3), "|u1", 38400) - assert test("RGBA") == (3, (100, 128, 4), "|u1", 51200) - assert test("RGBX") == (3, (100, 128, 4), "|u1", 51200) + with Image.open("Tests/images/truncated_jpeg.jpg") as im_truncated: + with pytest.raises(OSError): + numpy.array(im_truncated) def test_fromarray(): class Wrapper: - """ Class with API matching Image.fromarray """ + """Class with API matching Image.fromarray""" def __init__(self, img, arr_params): self.img = img @@ -38,10 +52,18 @@ def tobytes(self): def test(mode): i = im.convert(mode) - a = i.__array_interface__ - a["strides"] = 1 # pretend it's non-contiguous + a = numpy.array(i) # Make wrapper instance for image, new array interface - wrapped = Wrapper(i, a) + wrapped = Wrapper( + i, + { + "shape": a.shape, + "typestr": a.dtype.str, + "version": 3, + "data": a.data, + "strides": 1, # pretend it's non-contiguous + }, + ) out = Image.fromarray(wrapped) return out.mode, out.size, list(i.getdata()) == list(out.getdata()) diff --git a/Tests/test_image_convert.py b/Tests/test_image_convert.py index cf83922b6f7..a5a95e96255 100644 --- a/Tests/test_image_convert.py +++ b/Tests/test_image_convert.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image from .helper import assert_image, assert_image_equal, assert_image_similar, hopper @@ -40,11 +41,15 @@ def convert(im, mode): def test_default(): 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) + assert im.mode == "P" + converted_im = im.convert() + assert_image(converted_im, "RGB", im.size) + converted_im = im.convert() + assert_image(converted_im, "RGB", im.size) + + im.info["transparency"] = 0 + converted_im = im.convert() + assert_image(converted_im, "RGBA", im.size) # ref https://github.com/python-pillow/Pillow/issues/274 @@ -88,29 +93,33 @@ def test_trns_p(tmp_path): f = str(tmp_path / "temp.png") im_l = im.convert("L") - assert im_l.info["transparency"] == 0 # undone + assert im_l.info["transparency"] == 1 # undone im_l.save(f) im_rgb = im.convert("RGB") - assert im_rgb.info["transparency"] == (0, 0, 0) # undone + assert im_rgb.info["transparency"] == (0, 1, 2) # undone im_rgb.save(f) # ref https://github.com/python-pillow/Pillow/issues/664 -def test_trns_p_rgba(): +@pytest.mark.parametrize("mode", ("LA", "PA", "RGBA")) +def test_trns_p_transparency(mode): # Arrange im = hopper("P") im.info["transparency"] = 128 # Act - im_rgba = im.convert("RGBA") + converted_im = im.convert(mode) # Assert - assert "transparency" not in im_rgba.info - # https://github.com/python-pillow/Pillow/issues/2702 - assert im_rgba.palette is None + assert "transparency" not in converted_im.info + if mode == "PA": + assert converted_im.palette is not None + else: + # https://github.com/python-pillow/Pillow/issues/2702 + assert converted_im.palette is None def test_trns_l(tmp_path): @@ -127,8 +136,8 @@ def test_trns_l(tmp_path): 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 = im.convert("P", palette=Image.ADAPTIVE) + assert "transparency" in im_p.info im_p.save(f) @@ -154,13 +163,33 @@ def test_trns_RGB(tmp_path): assert "transparency" not in im_p.info im_p.save(f) + im = Image.new("RGB", (1, 1)) + im.info["transparency"] = im.getpixel((0, 0)) + im_p = im.convert("P", palette=Image.ADAPTIVE) + assert im_p.info["transparency"] == im_p.getpixel((0, 0)) + im_p.save(f) + + +@pytest.mark.parametrize("convert_mode", ("L", "LA", "I")) +def test_l_macro_rounding(convert_mode): + for mode in ("P", "PA"): + im = Image.new(mode, (1, 1)) + im.palette.getcolor((0, 1, 2)) + + converted_im = im.convert(convert_mode) + px = converted_im.load() + converted_color = px[0, 0] + if convert_mode == "LA": + converted_color = converted_color[0] + assert converted_color == 1 + 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" + assert im.palette.mode == "RGB" im_p = im.convert("P") # Should not raise ValueError: unrecognized raw mode diff --git a/Tests/test_image_crop.py b/Tests/test_image_crop.py index 3a2ce150d9e..e2228758c09 100644 --- a/Tests/test_image_crop.py +++ b/Tests/test_image_crop.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image from .helper import assert_image_equal, hopper diff --git a/Tests/test_image_filter.py b/Tests/test_image_filter.py index 42f4f448d01..df8c353f391 100644 --- a/Tests/test_image_filter.py +++ b/Tests/test_image_filter.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image, ImageFilter from .helper import assert_image_equal, hopper @@ -112,12 +113,12 @@ def test_kernel_not_enough_coefficients(): 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( # noqa: E127 + kernel = ImageFilter.Kernel( (3, 3), # fmt: off (-1, -1, 0, -1, 0, 1, - 0, 1, 1), + 0, 1, 1), # fmt: on 0.3, ) @@ -134,14 +135,14 @@ def test_consistency_3x3(): 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( # noqa: E127 + 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), + 0, 1, 1, 1, 1), # fmt: on 0.3, ) diff --git a/Tests/test_image_frombytes.py b/Tests/test_image_frombytes.py index faf94ac7794..7fb05cda7b2 100644 --- a/Tests/test_image_frombytes.py +++ b/Tests/test_image_frombytes.py @@ -1,4 +1,3 @@ -import pytest from PIL import Image from .helper import assert_image_equal, hopper @@ -9,8 +8,3 @@ def test_sanity(): im2 = Image.frombytes(im1.mode, im1.size, im1.tobytes()) assert_image_equal(im1, im2) - - -def test_not_implemented(): - with pytest.raises(NotImplementedError): - Image.fromstring() diff --git a/Tests/test_image_fromqimage.py b/Tests/test_image_fromqimage.py index 170d49ae1b1..5ad5b5c3c0a 100644 --- a/Tests/test_image_fromqimage.py +++ b/Tests/test_image_fromqimage.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image, ImageQt from .helper import assert_image_equal, hopper diff --git a/Tests/test_image_load.py b/Tests/test_image_load.py index efb9a1452d8..f7fe99bb4c2 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_point.py b/Tests/test_image_point.py index fe868b7c2fe..366f458544f 100644 --- a/Tests/test_image_point.py +++ b/Tests/test_image_point.py @@ -24,15 +24,15 @@ def test_sanity(): 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 - """ + """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(): - """ Tests for floating point lut of 8bit gray image """ + """Tests for floating point lut of 8bit gray image""" im = hopper("L") lut = [0.5 * float(x) for x in range(256)] diff --git a/Tests/test_image_putdata.py b/Tests/test_image_putdata.py index 54712fd6c9d..7e4bbaaec61 100644 --- a/Tests/test_image_putdata.py +++ b/Tests/test_image_putdata.py @@ -1,6 +1,8 @@ import sys from array import array +import pytest + from PIL import Image from .helper import assert_image_equal, hopper @@ -47,6 +49,12 @@ def test_pypy_performance(): im.putdata(list(range(256)) * 256) +def test_mode_with_L_with_float(): + im = Image.new("L", (1, 1), 0) + im.putdata([2.0]) + assert im.getpixel((0, 0)) == 2 + + def test_mode_i(): src = hopper("L") data = list(src.getdata()) @@ -87,3 +95,18 @@ def test_array_F(): im.putdata(arr) assert len(im.getdata()) == len(arr) + + +def test_not_flattened(): + im = Image.new("L", (1, 1)) + with pytest.raises(TypeError): + im.putdata([[0]]) + with pytest.raises(TypeError): + im.putdata([[0]], 2) + + with pytest.raises(TypeError): + im = Image.new("I", (1, 1)) + im.putdata([[0]]) + with pytest.raises(TypeError): + im = Image.new("F", (1, 1)) + im.putdata([[0]]) diff --git a/Tests/test_image_putpalette.py b/Tests/test_image_putpalette.py index 7b05e88b6b8..012a57a0999 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, assert_image_equal_tofile, hopper def test_putpalette(): @@ -35,6 +36,29 @@ def palette(mode): def test_imagepalette(): im = hopper("P") im.putpalette(ImagePalette.negative()) + assert_image_equal_tofile(im.convert("RGB"), "Tests/images/palette_negative.png") + im.putpalette(ImagePalette.random()) + im.putpalette(ImagePalette.sepia()) + assert_image_equal_tofile(im.convert("RGB"), "Tests/images/palette_sepia.png") + im.putpalette(ImagePalette.wedge()) + assert_image_equal_tofile(im.convert("RGB"), "Tests/images/palette_wedge.png") + + +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 96fa143a9e1..53b6c900793 100644 --- a/Tests/test_image_quantize.py +++ b/Tests/test_image_quantize.py @@ -1,31 +1,33 @@ import pytest + from PIL import Image -from .helper import assert_image, assert_image_similar, hopper +from .helper import assert_image_similar, hopper, is_ppc64le def test_sanity(): image = hopper() converted = image.quantize() - assert_image(converted, "P", converted.size) + assert converted.mode == "P" assert_image_similar(converted.convert("RGB"), image, 10) image = hopper() converted = image.quantize(palette=hopper("P")) - assert_image(converted, "P", converted.size) + assert converted.mode == "P" assert_image_similar(converted.convert("RGB"), image, 60) +@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: + 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 converted.mode == "P" assert_image_similar(converted.convert("RGB"), image, 15) assert len(converted.getcolors()) == 100 @@ -33,7 +35,7 @@ def test_libimagequant_quantize(): def test_octree_quantize(): image = hopper() converted = image.quantize(100, Image.FASTOCTREE) - assert_image(converted, "P", converted.size) + assert converted.mode == "P" assert_image_similar(converted.convert("RGB"), image, 20) assert len(converted.getcolors()) == 100 @@ -50,7 +52,7 @@ 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 converted.mode == "P" assert_image_similar(converted.convert("RGB"), image, 1) @@ -60,7 +62,8 @@ def test_quantize_no_dither(): palette = palette.convert("P") converted = image.quantize(dither=0, palette=palette) - assert_image(converted, "P", converted.size) + assert converted.mode == "P" + assert converted.palette.palette == palette.palette.palette def test_quantize_dither_diff(): @@ -72,3 +75,37 @@ def test_quantize_dither_diff(): nodither = image.quantize(dither=0, palette=palette) assert dither.tobytes() != nodither.tobytes() + + +def test_colors(): + im = hopper() + colors = 2 + converted = im.quantize(colors) + assert len(converted.palette.palette) == colors * len("RGB") + + +def test_transparent_colors_equal(): + im = Image.new("RGBA", (1, 2), (0, 0, 0, 0)) + px = im.load() + px[0, 1] = (255, 255, 255, 0) + + converted = im.quantize() + converted_px = converted.load() + assert converted_px[0, 0] == converted_px[0, 1] + + +@pytest.mark.parametrize( + "method, color", + ( + (Image.MEDIANCUT, (0, 0, 0)), + (Image.MAXCOVERAGE, (0, 0, 0)), + (Image.FASTOCTREE, (0, 0, 0)), + (Image.FASTOCTREE, (0, 0, 0, 0)), + ), +) +def test_palette(method, color): + im = Image.new("RGBA" if len(color) == 4 else "RGB", (1, 1), color) + + converted = im.quantize(method=method) + converted_px = converted.load() + assert converted_px[0, 0] == converted.palette.colors[color] diff --git a/Tests/test_image_reduce.py b/Tests/test_image_reduce.py index 729645a0b2b..b4eebc14218 100644 --- a/Tests/test_image_reduce.py +++ b/Tests/test_image_reduce.py @@ -1,7 +1,8 @@ import pytest + from PIL import Image, ImageMath, ImageMode -from .helper import convert_to_comparable +from .helper import convert_to_comparable, skip_unless_feature codecs = dir(Image.core) @@ -161,8 +162,8 @@ def compare_reduce_with_reference(im, factor, average_diff=0.4, max_diff=1): def assert_compare_images(a, b, max_average_diff, max_diff=255): - assert a.mode == b.mode, "got mode %r, expected %r" % (a.mode, b.mode) - assert a.size == b.size, "got size %r, expected %r" % (a.size, b.size) + 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)}" a, b = convert_to_comparable(a, b) @@ -174,15 +175,16 @@ def assert_compare_images(a, b, max_average_diff, max_diff=255): average_diff = sum(i * num for i, num in enumerate(ch_hist)) / ( a.size[0] * a.size[1] ) - msg = "average pixel value difference {:.4f} > expected {:.4f} " - "for '{}' band".format(average_diff, max_average_diff, band) + 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 last_diff = [i for i, num in enumerate(ch_hist) if num > 0][-1] - assert ( - max_diff >= last_diff - ), "max pixel value difference {} > expected {} for '{}' band".format( - last_diff, max_diff, band + assert max_diff >= last_diff, ( + f"max pixel value difference {last_diff} > expected {max_diff} " + f"for '{band}' band" ) @@ -252,9 +254,7 @@ def test_mode_F(): compare_reduce_with_box(im, factor) -@pytest.mark.skipif( - "jpeg2k_decoder" not in codecs, reason="JPEG 2000 support not available" -) +@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 764a3ca4907..8bf2ce916dd 100644 --- a/Tests/test_image_resample.py +++ b/Tests/test_image_resample.py @@ -1,9 +1,15 @@ from contextlib import contextmanager import pytest + from PIL import Image, ImageDraw -from .helper import assert_image_equal, assert_image_similar, hopper +from .helper import ( + assert_image_equal, + assert_image_similar, + hopper, + mark_if_feature_version, +) class TestImagingResampleVulnerability: @@ -81,15 +87,16 @@ def check_case(self, case, sample): 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)}" ) 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 +225,7 @@ def test_box_filter_correct_range(self): assert_image_equal(im, ref) -class CoreResampleConsistencyTest: +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,7 +236,7 @@ def run_case(self, case): 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)) + message = f"{px[x, y]} != {color} for pixel {(x, y)}" assert px[x, y] == color, message def test_8u(self): @@ -253,7 +260,7 @@ def test_32f(self): self.run_case(self.make_case("F", 1.192093e-07)) -class CoreResampleAlphaCorrectTest: +class TestCoreResampleAlphaCorrect: def make_levels_case(self, mode): i = Image.new(mode, (256, 16)) px = i.load() @@ -268,13 +275,12 @@ def run_levels_case(self, i): px = i.load() for y in range(i.size[1]): used_colors = {px[x, y][0] for x in range(i.size[0])} - assert 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." ) - @pytest.mark.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)) @@ -283,7 +289,7 @@ def test_levels_rgba(self): self.run_levels_case(case.resize((512, 32), Image.BICUBIC)) self.run_levels_case(case.resize((512, 32), Image.LANCZOS)) - @pytest.mark.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)) @@ -307,8 +313,9 @@ def run_dirty_case(self, i, clean_pixel): 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}" ) assert px[x, y][:3] == clean_pixel, message @@ -329,7 +336,7 @@ def test_dirty_pixels_la(self): self.run_dirty_case(case.resize((20, 20), Image.LANCZOS), (255,)) -class CoreResamplePassesTest: +class TestCoreResamplePasses: @contextmanager def count(self, diff): count = Image.core.get_stats()["new_count"] @@ -372,7 +379,7 @@ def test_box_vertical(self): assert_image_similar(with_box, cropped, 0.1) -class CoreResampleCoefficientsTest: +class TestCoreResampleCoefficients: def test_reduce(self): test_color = 254 @@ -401,7 +408,7 @@ def test_nonzero_coefficients(self): assert histogram[0x100 * 3 + 0xFF] == 0x10000 -class CoreResampleBoxTest: +class TestCoreResampleBox: def test_wrong_arguments(self): im = hopper() for resample in ( @@ -453,6 +460,9 @@ def split_range(size, tiles): tiled.paste(tile, (x0, y0)) return tiled + @mark_if_feature_version( + pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" + ) def test_tiles(self): with Image.open("Tests/images/flower.jpg") as im: assert im.size == (480, 360) @@ -463,6 +473,9 @@ def test_tiles(self): tiled = self.resize_tiled(im, dst_size, *tiles) assert_image_similar(reference, tiled, 0.01) + @mark_if_feature_version( + pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" + ) def test_subsample(self): # This test shows advantages of the subpixel resizing # after supersampling (e.g. during JPEG decoding). @@ -503,7 +516,7 @@ def test_passthrough(self): ]: res = im.resize(size, Image.LANCZOS, box) assert res.size == size - assert_image_equal(res, im.crop(box), ">>> {} {}".format(size, box)) + assert_image_equal(res, im.crop(box), f">>> {size} {box}") def test_no_passthrough(self): # When resize is required @@ -519,9 +532,7 @@ def test_no_passthrough(self): 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 @@ -541,7 +552,7 @@ def test_skip_horizontal(self): res, im.crop(box).resize(size, flt), 0.4, - ">>> {} {} {}".format(size, box, flt), + f">>> {size} {box} {flt}", ) def test_skip_vertical(self): @@ -562,5 +573,5 @@ def test_skip_vertical(self): 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 ad4be135a00..1fe278052fa 100644 --- a/Tests/test_image_resize.py +++ b/Tests/test_image_resize.py @@ -4,9 +4,15 @@ from itertools import permutations import pytest + from PIL import Image -from .helper import assert_image_equal, assert_image_similar, hopper +from .helper import ( + assert_image_equal, + assert_image_equal_tofile, + assert_image_similar, + hopper, +) class TestImagingCoreResize: @@ -134,11 +140,22 @@ def test_unknown_filter(self): with pytest.raises(ValueError): self.resize(hopper(), (10, 10), 9) + def test_cross_platform(self, tmp_path): + # This test is intended for only check for consistent behaviour across + # platforms. So if a future Pillow change requires that the test file + # be updated, that is okay. + im = hopper().resize((64, 64)) + temp_file = str(tmp_path / "temp.gif") + im.save(temp_file) + + with Image.open(temp_file) as reloaded: + assert_image_equal_tofile(reloaded, "Tests/images/hopper_resized.gif") + @pytest.fixture def gradients_image(): - im = Image.open("Tests/images/radial_gradients.png") - im.load() + with Image.open("Tests/images/radial_gradients.png") as im: + im.load() try: yield im finally: @@ -249,3 +266,7 @@ def test_default_filter(self): for mode in "1", "P": im = hopper(mode) assert im.resize((20, 20), Image.NEAREST) == im.resize((20, 20)) + + for mode in "I;16", "I;16L", "I;16B", "BGR;15", "BGR;16": + im = hopper(mode) + 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 a41d850bb01..2d72ffa684c 100644 --- a/Tests/test_image_rotate.py +++ b/Tests/test_image_rotate.py @@ -1,6 +1,11 @@ from PIL import Image -from .helper import assert_image_equal, assert_image_similar, hopper +from .helper import ( + assert_image_equal, + assert_image_equal_tofile, + assert_image_similar, + hopper, +) def rotate(im, mode, angle, center=None, translate=None): @@ -28,6 +33,9 @@ def test_angle(): with Image.open("Tests/images/test-card.png") as im: rotate(im, im.mode, angle) + im = hopper() + assert_image_equal(im.rotate(angle), im.rotate(angle, expand=1)) + def test_zero(): for angle in (0, 45, 90, 180, 270): @@ -113,15 +121,13 @@ def test_center(): 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) + assert_image_equal_tofile(im, "Tests/images/rotate_45_no_fill.png") 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) + assert_image_equal_tofile(im, "Tests/images/rotate_45_with_fill.png") def test_alpha_rotate_no_fill(): diff --git a/Tests/test_image_thumbnail.py b/Tests/test_image_thumbnail.py index f4ed8e746ff..dd140955dee 100644 --- a/Tests/test_image_thumbnail.py +++ b/Tests/test_image_thumbnail.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image from .helper import ( @@ -63,6 +64,12 @@ def test_aspect(): assert im.size == (75, 23) # ratio is 3.260869565217 +def test_division_by_zero(): + im = Image.new("L", (200, 2)) + im.thumbnail((75, 75)) + assert im.size == (75, 1) + + def test_float(): im = Image.new("L", (128, 128)) im.thumbnail((99.9, 99.9)) @@ -81,6 +88,8 @@ def test_no_resize(): assert im.size == (64, 64) +# valgrind test is failing with memory allocated in libjpeg +@pytest.mark.valgrind_known_error(reason="Known Failing") 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") diff --git a/Tests/test_image_transform.py b/Tests/test_image_transform.py index 3409d86f08d..ea208362b2a 100644 --- a/Tests/test_image_transform.py +++ b/Tests/test_image_transform.py @@ -1,6 +1,7 @@ import math import pytest + from PIL import Image, ImageTransform from .helper import assert_image_equal, assert_image_similar, hopper @@ -31,6 +32,11 @@ def test_info(self): new_im = im.transform((100, 100), transform) assert new_im.info["comment"] == comment + def test_palette(self): + with Image.open("Tests/images/hopper.gif") as im: + transformed = im.transform(im.size, Image.AFFINE, [1, 0, 0, 0, 1, 0]) + assert im.palette.palette == transformed.palette.palette + def test_extent(self): im = hopper("RGB") (w, h) = im.size @@ -142,6 +148,41 @@ def op(im, sz): self._test_alpha_premult(op) + def _test_nearest(self, op, mode): + # create white image with half transparent, + # do op, + # the image should remain white with half transparent + transparent, opaque = { + "RGBA": ((255, 255, 255, 0), (255, 255, 255, 255)), + "LA": ((255, 0), (255, 255)), + }[mode] + im = Image.new(mode, (10, 10), transparent) + im2 = Image.new(mode, (5, 10), opaque) + im.paste(im2, (0, 0)) + + im = op(im, (40, 10)) + + colors = im.getcolors() + assert colors == [ + (20 * 10, opaque), + (20 * 10, transparent), + ] + + @pytest.mark.parametrize("mode", ("RGBA", "LA")) + def test_nearest_resize(self, mode): + def op(im, sz): + return im.resize(sz, Image.NEAREST) + + self._test_nearest(op, mode) + + @pytest.mark.parametrize("mode", ("RGBA", "LA")) + def test_nearest_transform(self, mode): + def op(im, sz): + (w, h) = im.size + return im.transform(sz, Image.EXTENT, (0, 0, w, h), Image.NEAREST) + + self._test_nearest(op, mode) + def test_blank_fill(self): # attempting to hit # https://github.com/python-pillow/Pillow/issues/254 reported diff --git a/Tests/test_imagechops.py b/Tests/test_imagechops.py index 7d042cb9feb..b839a7b140a 100644 --- a/Tests/test_imagechops.py +++ b/Tests/test_imagechops.py @@ -311,8 +311,8 @@ def test_subtract(): # Assert assert new.getbbox() == (25, 50, 76, 76) - assert new.getpixel((50, 50)) == GREEN - assert new.getpixel((50, 51)) == BLACK + assert new.getpixel((50, 51)) == GREEN + assert new.getpixel((50, 52)) == BLACK def test_subtract_scale_offset(): @@ -350,8 +350,8 @@ def test_subtract_modulo(): # Assert assert new.getbbox() == (25, 50, 76, 76) - assert new.getpixel((50, 50)) == GREEN - assert new.getpixel((50, 51)) == BLACK + assert new.getpixel((50, 51)) == GREEN + assert new.getpixel((50, 52)) == BLACK def test_subtract_modulo_no_clip(): @@ -368,11 +368,11 @@ def test_subtract_modulo_no_clip(): def test_soft_light(): # Arrange - im1 = Image.open("Tests/images/hopper.png") - im2 = Image.open("Tests/images/hopper-XYZ.png") + with Image.open("Tests/images/hopper.png") as im1: + with Image.open("Tests/images/hopper-XYZ.png") as im2: - # Act - new = ImageChops.soft_light(im1, im2) + # Act + new = ImageChops.soft_light(im1, im2) # Assert assert new.getpixel((64, 64)) == (163, 54, 32) @@ -381,11 +381,11 @@ def test_soft_light(): def test_hard_light(): # Arrange - im1 = Image.open("Tests/images/hopper.png") - im2 = Image.open("Tests/images/hopper-XYZ.png") + with Image.open("Tests/images/hopper.png") as im1: + with Image.open("Tests/images/hopper-XYZ.png") as im2: - # Act - new = ImageChops.hard_light(im1, im2) + # Act + new = ImageChops.hard_light(im1, im2) # Assert assert new.getpixel((64, 64)) == (144, 50, 27) @@ -394,11 +394,11 @@ def test_hard_light(): def test_overlay(): # Arrange - im1 = Image.open("Tests/images/hopper.png") - im2 = Image.open("Tests/images/hopper-XYZ.png") + with Image.open("Tests/images/hopper.png") as im1: + with Image.open("Tests/images/hopper-XYZ.png") as im2: - # Act - new = ImageChops.overlay(im1, im2) + # Act + new = ImageChops.overlay(im1, im2) # Assert assert new.getpixel((64, 64)) == (159, 50, 27) diff --git a/Tests/test_imagecms.py b/Tests/test_imagecms.py index ac34a81064e..99f3b4e0329 100644 --- a/Tests/test_imagecms.py +++ b/Tests/test_imagecms.py @@ -1,12 +1,20 @@ import datetime import os import re +import shutil from io import BytesIO import pytest -from PIL import Image, ImageMode -from .helper import 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, + assert_image_similar_tofile, + hopper, +) try: from PIL import ImageCms @@ -46,7 +54,7 @@ def test_sanity(): assert list(map(type, v)) == [str, str, str, str] # internal version number - assert re.search(r"\d+\.\d+$", ImageCms.core.littlecms_version) + assert re.search(r"\d+\.\d+(\.\d+)?$", features.version_module("littlecms2")) skip_missing() i = ImageCms.profileToProfile(hopper(), SRGB, SRGB) @@ -238,8 +246,7 @@ def test_lab_color(): # 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) + assert_image_similar_tofile(i, "Tests/images/hopper.Lab.tif", 3.5) def test_lab_srgb(): @@ -435,41 +442,21 @@ def truncate_tuple(tuple_or_float): assert p.xcolor_space == "RGB " -def test_deprecations(): +def test_non_ascii_path(tmp_path): skip_missing() - o = ImageCms.getOpenProfile(SRGB) - p = o.profile - - def helper_deprecated(attr, expected): - result = pytest.warns(DeprecationWarning, getattr, p, attr) - assert result == expected - - # p.color_space - helper_deprecated("color_space", "RGB") - - # p.pcs - helper_deprecated("pcs", "XYZ") - - # p.product_copyright - helper_deprecated( - "product_copyright", "Copyright International Color Consortium, 2009" - ) - - # p.product_desc - helper_deprecated("product_desc", "sRGB IEC61966-2-1 black scaled") - - # p.product_description - helper_deprecated("product_description", "sRGB IEC61966-2-1 black scaled") - - # p.product_manufacturer - helper_deprecated("product_manufacturer", "") + 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.product_model - helper_deprecated("product_model", "IEC 61966-2-1 Default RGB Colour Space - sRGB") + o = ImageCms.getOpenProfile(tempfile) + p = o.profile + assert p.model == "IEC 61966-2-1 Default RGB Colour Space - sRGB" def test_profile_typesafety(): - """ Profile init type safety + """Profile init type safety prepatch, these would segfault, postpatch they should emit a typeerror """ @@ -484,10 +471,10 @@ 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 = [ # noqa: E131 + nine_grid_deltas = [ (-1, -1), (-1, 0), (-1, 1), - (0, -1), (0, 0), (0, 1), - (1, -1), (1, 0), (1, 1), + (0, -1), (0, 0), (0, 1), + (1, -1), (1, 0), (1, 1), ] # fmt: on chans = [] diff --git a/Tests/test_imagecolor.py b/Tests/test_imagecolor.py index d2fd07c8132..dcc44e6e342 100644 --- a/Tests/test_imagecolor.py +++ b/Tests/test_imagecolor.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image, ImageColor @@ -190,3 +191,12 @@ def test_rounding_errors(): assert (255, 255) == ImageColor.getcolor("white", "LA") assert (163, 33) == ImageColor.getcolor("rgba(0, 255, 115, 33)", "LA") Image.new("LA", (1, 1), "white") + + +def test_color_too_long(): + # Arrange + color_too_long = "hsl(" + "1" * 40 + "," + "1" * 40 + "%," + "1" * 40 + "%)" + + # Act / Assert + with pytest.raises(ValueError): + ImageColor.getrgb(color_too_long) diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index f6eabb21a66..b661494c733 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -1,11 +1,13 @@ import os.path import pytest + from PIL import Image, ImageColor, ImageDraw, ImageFont from .helper import ( assert_image_equal, - assert_image_similar, + assert_image_equal_tofile, + assert_image_similar_tofile, hopper, skip_unless_feature, ) @@ -71,7 +73,7 @@ def helper_arc(bbox, start, end): draw.arc(bbox, start, end) # Assert - assert_image_similar(im, Image.open("Tests/images/imagedraw_arc.png"), 1) + assert_image_similar_tofile(im, "Tests/images/imagedraw_arc.png", 1) def test_arc1(): @@ -95,7 +97,7 @@ def test_arc_end_le_start(): draw.arc(BBOX1, start=start, end=end) # Assert - assert_image_equal(im, Image.open("Tests/images/imagedraw_arc_end_le_start.png")) + assert_image_equal_tofile(im, "Tests/images/imagedraw_arc_end_le_start.png") def test_arc_no_loops(): @@ -110,20 +112,19 @@ def test_arc_no_loops(): draw.arc(BBOX1, start=start, end=end) # Assert - assert_image_similar(im, Image.open("Tests/images/imagedraw_arc_no_loops.png"), 1) + 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) - expected = "Tests/images/imagedraw_arc_width.png" # Act draw.arc(BBOX1, 10, 260, width=5) # Assert - assert_image_similar(im, Image.open(expected), 1) + assert_image_similar_tofile(im, "Tests/images/imagedraw_arc_width.png", 1) def test_arc_width_pieslice_large(): @@ -131,26 +132,24 @@ def test_arc_width_pieslice_large(): # 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) + 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) - 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) + assert_image_similar_tofile(im, "Tests/images/imagedraw_arc_width_fill.png", 1) def test_arc_width_non_whole_angle(): @@ -163,7 +162,20 @@ def test_arc_width_non_whole_angle(): draw.arc(BBOX1, 10, 259.5, width=5) # Assert - assert_image_similar(im, Image.open(expected), 1) + 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_tofile(im, "Tests/images/imagedraw_arc_high.png") def test_bitmap(): @@ -177,58 +189,54 @@ def test_bitmap(): draw.bitmap((10, 10), small) # Assert - assert_image_equal(im, Image.open("Tests/images/imagedraw_bitmap.png")) + assert_image_equal_tofile(im, "Tests/images/imagedraw_bitmap.png") def helper_chord(mode, bbox, start, end): # Arrange im = Image.new(mode, (W, H)) draw = ImageDraw.Draw(im) - expected = "Tests/images/imagedraw_chord_{}.png".format(mode) + expected = f"Tests/images/imagedraw_chord_{mode}.png" # Act draw.chord(bbox, start, end, fill="red", outline="yellow") # Assert - assert_image_similar(im, Image.open(expected), 1) + assert_image_similar_tofile(im, expected, 1) def test_chord1(): for mode in ["RGB", "L"]: helper_chord(mode, BBOX1, 0, 180) - helper_chord(mode, BBOX1, 0.5, 180.4) def test_chord2(): for mode in ["RGB", "L"]: helper_chord(mode, BBOX2, 0, 180) - helper_chord(mode, BBOX2, 0.5, 180.4) def test_chord_width(): # 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) + 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) - 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) + assert_image_similar_tofile(im, "Tests/images/imagedraw_chord_width_fill.png", 1) def test_chord_zero_width(): @@ -240,21 +248,32 @@ def test_chord_zero_width(): 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) + assert_image_equal_tofile(im, "Tests/images/imagedraw_chord_zero_width.png") + + +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_tofile(im, "Tests/images/imagedraw_chord_too_fat.png") def helper_ellipse(mode, bbox): # Arrange im = Image.new(mode, (W, H)) draw = ImageDraw.Draw(im) - expected = "Tests/images/imagedraw_ellipse_{}.png".format(mode) + expected = f"Tests/images/imagedraw_ellipse_{mode}.png" # Act draw.ellipse(bbox, fill="green", outline="blue") # Assert - assert_image_similar(im, Image.open(expected), 1) + assert_image_similar_tofile(im, expected, 1) def test_ellipse1(): @@ -276,8 +295,8 @@ def test_ellipse_translucent(): draw.ellipse(BBOX1, fill=(0, 255, 0, 127)) # Assert - expected = Image.open("Tests/images/imagedraw_ellipse_translucent.png") - assert_image_similar(im, expected, 1) + expected = "Tests/images/imagedraw_ellipse_translucent.png" + assert_image_similar_tofile(im, expected, 1) def test_ellipse_edge(): @@ -286,15 +305,18 @@ def test_ellipse_edge(): draw = ImageDraw.Draw(im) # Act - draw.ellipse(((0, 0), (W - 1, H)), fill="white") + draw.ellipse(((0, 0), (W - 1, H - 1)), fill="white") # Assert - assert_image_similar(im, Image.open("Tests/images/imagedraw_ellipse_edge.png"), 1) + assert_image_similar_tofile(im, "Tests/images/imagedraw_ellipse_edge.png", 1) def test_ellipse_symmetric(): - for bbox in [(25, 25, 76, 76), (25, 25, 75, 75)]: - im = Image.new("RGB", (101, 101)) + for width, bbox in ( + (100, (24, 24, 75, 75)), + (101, (25, 25, 75, 75)), + ): + im = Image.new("RGB", (width, 100)) draw = ImageDraw.Draw(im) draw.ellipse(bbox, fill="green", outline="blue") assert_image_equal(im, im.transpose(Image.FLIP_LEFT_RIGHT)) @@ -304,39 +326,36 @@ def test_ellipse_width(): # 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) + 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) - 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) + 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) - 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) + assert_image_similar_tofile(im, "Tests/images/imagedraw_ellipse_width_fill.png", 1) def test_ellipse_zero_width(): @@ -348,8 +367,42 @@ def test_ellipse_zero_width(): 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) + assert_image_equal_tofile(im, "Tests/images/imagedraw_ellipse_zero_width.png") + + +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) + + assert_image_equal_tofile(im, "Tests/images/imagedraw_ellipse_various_sizes.png") + + +def test_ellipse_various_sizes_filled(): + im = ellipse_various_sizes_helper(True) + + assert_image_equal_tofile( + im, "Tests/images/imagedraw_ellipse_various_sizes_filled.png" + ) def helper_line(points): @@ -361,7 +414,7 @@ def helper_line(points): draw.line(points, fill="yellow", width=2) # Assert - assert_image_equal(im, Image.open("Tests/images/imagedraw_line.png")) + assert_image_equal_tofile(im, "Tests/images/imagedraw_line.png") def test_line1(): @@ -390,7 +443,7 @@ def test_shape1(): draw.shape(s, fill=1) # Assert - assert_image_equal(im, Image.open("Tests/images/imagedraw_shape1.png")) + assert_image_equal_tofile(im, "Tests/images/imagedraw_shape1.png") def test_shape2(): @@ -411,7 +464,24 @@ def test_shape2(): draw.shape(s, outline="blue") # Assert - assert_image_equal(im, Image.open("Tests/images/imagedraw_shape2.png")) + assert_image_equal_tofile(im, "Tests/images/imagedraw_shape2.png") + + +def test_transform(): + # Arrange + im = Image.new("RGB", (100, 100), "white") + expected = im.copy() + draw = ImageDraw.Draw(im) + + # Act + s = ImageDraw.Outline() + s.line(0, 0) + s.transform((0, 0, 0, 0, 0, 0)) + + draw.shape(s, fill=1) + + # Assert + assert_image_equal(im, expected) def helper_pieslice(bbox, start, end): @@ -423,30 +493,29 @@ def helper_pieslice(bbox, start, end): draw.pieslice(bbox, start, end, fill="white", outline="blue") # Assert - assert_image_similar(im, Image.open("Tests/images/imagedraw_pieslice.png"), 1) + assert_image_similar_tofile(im, "Tests/images/imagedraw_pieslice.png", 1) def test_pieslice1(): - helper_pieslice(BBOX1, -90, 45) - helper_pieslice(BBOX1, -90.5, 45.4) + helper_pieslice(BBOX1, -92, 46) + helper_pieslice(BBOX1, -92.2, 46.2) def test_pieslice2(): - helper_pieslice(BBOX2, -90, 45) - helper_pieslice(BBOX2, -90.5, 45.4) + 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) - 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) + assert_image_similar_tofile(im, "Tests/images/imagedraw_pieslice_width.png", 1) def test_pieslice_width_fill(): @@ -459,7 +528,7 @@ def test_pieslice_width_fill(): draw.pieslice(BBOX1, 10, 260, fill="white", outline="blue", width=5) # Assert - assert_image_similar(im, Image.open(expected), 1) + assert_image_similar_tofile(im, expected, 1) def test_pieslice_zero_width(): @@ -471,8 +540,49 @@ def test_pieslice_zero_width(): 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) + assert_image_equal_tofile(im, "Tests/images/imagedraw_pieslice_zero_width.png") + + +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_tofile(im, "Tests/images/imagedraw_pieslice_wide.png") + + +def test_pieslice_no_spikes(): + im = Image.new("RGB", (161, 161), "white") + draw = ImageDraw.Draw(im) + cxs = ( + [140] * 3 + + list(range(140, 19, -20)) + + [20] * 5 + + list(range(20, 141, 20)) + + [140] * 2 + ) + cys = ( + list(range(80, 141, 20)) + + [140] * 5 + + list(range(140, 19, -20)) + + [20] * 5 + + list(range(20, 80, 20)) + ) + + for cx, cy, angle in zip(cxs, cys, range(0, 360, 15)): + draw.pieslice( + [cx - 100, cy - 100, cx + 100, cy + 100], angle, angle + 1, fill="black" + ) + draw.point([cx, cy], fill="red") + + im_pre_erase = im.copy() + draw.rectangle([21, 21, 139, 139], fill="white") + + assert_image_equal(im, im_pre_erase) def helper_point(points): @@ -484,7 +594,7 @@ def helper_point(points): draw.point(points, fill="yellow") # Assert - assert_image_equal(im, Image.open("Tests/images/imagedraw_point.png")) + assert_image_equal_tofile(im, "Tests/images/imagedraw_point.png") def test_point1(): @@ -504,7 +614,7 @@ def helper_polygon(points): draw.polygon(points, fill="red", outline="blue") # Assert - assert_image_equal(im, Image.open("Tests/images/imagedraw_polygon.png")) + assert_image_equal_tofile(im, "Tests/images/imagedraw_polygon.png") def test_polygon1(): @@ -522,13 +632,40 @@ def test_polygon_kite(): # Arrange im = Image.new(mode, (W, H)) draw = ImageDraw.Draw(im) - expected = "Tests/images/imagedraw_polygon_kite_{}.png".format(mode) + expected = f"Tests/images/imagedraw_polygon_kite_{mode}.png" # Act draw.polygon(KITE_POINTS, fill="blue", outline="yellow") # Assert - assert_image_equal(im, Image.open(expected)) + assert_image_equal_tofile(im, expected) + + +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_tofile(im, expected) + + +def test_polygon_translucent(): + # Arrange + im = Image.new("RGB", (W, H)) + draw = ImageDraw.Draw(im, "RGBA") + + # Act + draw.polygon([(20, 80), (80, 80), (80, 20)], fill=(0, 255, 0, 127)) + + # Assert + expected = "Tests/images/imagedraw_polygon_translucent.png" + assert_image_equal_tofile(im, expected) def helper_rectangle(bbox): @@ -540,7 +677,7 @@ def helper_rectangle(bbox): draw.rectangle(bbox, fill="black", outline="green") # Assert - assert_image_equal(im, Image.open("Tests/images/imagedraw_rectangle.png")) + assert_image_equal_tofile(im, "Tests/images/imagedraw_rectangle.png") def test_rectangle1(): @@ -557,13 +694,12 @@ def test_big_rectangle(): 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) + assert_image_similar_tofile(im, "Tests/images/imagedraw_big_rectangle.png", 1) def test_rectangle_width(): @@ -576,7 +712,7 @@ def test_rectangle_width(): draw.rectangle(BBOX1, outline="green", width=5) # Assert - assert_image_equal(im, Image.open(expected)) + assert_image_equal_tofile(im, expected) def test_rectangle_width_fill(): @@ -589,7 +725,7 @@ def test_rectangle_width_fill(): draw.rectangle(BBOX1, fill="blue", outline="green", width=5) # Assert - assert_image_equal(im, Image.open(expected)) + assert_image_equal_tofile(im, expected) def test_rectangle_zero_width(): @@ -601,8 +737,7 @@ def test_rectangle_zero_width(): 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) + assert_image_equal_tofile(im, "Tests/images/imagedraw_rectangle_zero_width.png") def test_rectangle_I16(): @@ -614,8 +749,95 @@ def test_rectangle_I16(): draw.rectangle(BBOX1, fill="black", outline="green") # Assert - assert_image_equal( - im.convert("I"), Image.open("Tests/images/imagedraw_rectangle_I.png") + assert_image_equal_tofile(im.convert("I"), "Tests/images/imagedraw_rectangle_I.png") + + +def test_rectangle_translucent_outline(): + # Arrange + im = Image.new("RGB", (W, H)) + draw = ImageDraw.Draw(im, "RGBA") + + # Act + draw.rectangle(BBOX1, fill="black", outline=(0, 255, 0, 127), width=5) + + # Assert + assert_image_equal_tofile( + im, "Tests/images/imagedraw_rectangle_translucent_outline.png" + ) + + +@pytest.mark.parametrize( + "xy", + [(10, 20, 190, 180), ([10, 20], [190, 180]), ((10, 20), (190, 180))], +) +def test_rounded_rectangle(xy): + # Arrange + im = Image.new("RGB", (200, 200)) + draw = ImageDraw.Draw(im) + + # Act + draw.rounded_rectangle(xy, 30, fill="red", outline="green", width=5) + + # Assert + assert_image_equal_tofile(im, "Tests/images/imagedraw_rounded_rectangle.png") + + +@pytest.mark.parametrize( + "xy, radius, type", + [ + ((10, 20, 190, 180), 30.5, "given"), + ((10, 10, 181, 190), 90, "width"), + ((10, 20, 190, 181), 85, "height"), + ], +) +def test_rounded_rectangle_non_integer_radius(xy, radius, type): + # Arrange + im = Image.new("RGB", (200, 200)) + draw = ImageDraw.Draw(im) + + # Act + draw.rounded_rectangle(xy, radius, fill="red", outline="green", width=5) + + # Assert + assert_image_equal_tofile( + im, + "Tests/images/imagedraw_rounded_rectangle_non_integer_radius_" + type + ".png", + ) + + +def test_rounded_rectangle_zero_radius(): + # Arrange + im = Image.new("RGB", (W, H)) + draw = ImageDraw.Draw(im) + + # Act + draw.rounded_rectangle(BBOX1, 0, fill="blue", outline="green", width=5) + + # Assert + assert_image_equal_tofile(im, "Tests/images/imagedraw_rectangle_width_fill.png") + + +@pytest.mark.parametrize( + "xy, suffix", + [ + ((20, 10, 80, 90), "x"), + ((10, 20, 90, 80), "y"), + ((20, 20, 80, 80), "both"), + ], +) +def test_rounded_rectangle_translucent(xy, suffix): + # Arrange + im = Image.new("RGB", (W, H)) + draw = ImageDraw.Draw(im, "RGBA") + + # Act + draw.rounded_rectangle( + xy, 30, fill=(255, 0, 0, 127), outline=(0, 255, 0, 127), width=5 + ) + + # Assert + assert_image_equal_tofile( + im, "Tests/images/imagedraw_rounded_rectangle_" + suffix + ".png" ) @@ -662,11 +884,14 @@ def test_floodfill_border(): # Act ImageDraw.floodfill( - im, centre_point, ImageColor.getrgb("red"), border=ImageColor.getrgb("black"), + im, + centre_point, + ImageColor.getrgb("red"), + border=ImageColor.getrgb("black"), ) # Assert - assert_image_equal(im, Image.open("Tests/images/imagedraw_floodfill2.png")) + assert_image_equal_tofile(im, "Tests/images/imagedraw_floodfill2.png") def test_floodfill_thresh(): @@ -682,7 +907,7 @@ def test_floodfill_thresh(): ImageDraw.floodfill(im, centre_point, ImageColor.getrgb("red"), thresh=30) # Assert - assert_image_equal(im, Image.open("Tests/images/imagedraw_floodfill2.png")) + assert_image_equal_tofile(im, "Tests/images/imagedraw_floodfill2.png") def test_floodfill_not_negative(): @@ -699,9 +924,7 @@ def test_floodfill_not_negative(): 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") - ) + assert_image_equal_tofile(im, "Tests/images/imagedraw_floodfill_not_negative.png") def create_base_image_draw( @@ -716,147 +939,156 @@ def create_base_image_draw( 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") + expected = os.path.join(IMAGES_PATH, "square.png") + img, draw = create_base_image_draw((10, 10)) + draw.polygon([(2, 2), (2, 7), (7, 7), (7, 2)], BLACK) + assert_image_equal_tofile(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_tofile(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_tofile(img, expected, "square as normal rectangle failed") + img, draw = create_base_image_draw((10, 10)) + draw.rectangle((7, 7, 2, 2), BLACK) + assert_image_equal_tofile(img, expected, "square as inverted rectangle failed") def test_triangle_right(): - 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") + img, draw = create_base_image_draw((20, 20)) + draw.polygon([(3, 5), (17, 5), (10, 12)], BLACK) + assert_image_equal_tofile( + img, os.path.join(IMAGES_PATH, "triangle_right.png"), "triangle right failed" + ) + + +@pytest.mark.parametrize( + "fill, suffix", + ((BLACK, "width"), (None, "width_no_fill")), +) +def test_triangle_right_width(fill, suffix): + img, draw = create_base_image_draw((100, 100)) + draw.polygon([(15, 25), (85, 25), (50, 60)], fill, WHITE, width=5) + assert_image_equal_tofile( + img, os.path.join(IMAGES_PATH, "triangle_right_" + suffix + ".png") + ) 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( - 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") + img, draw = create_base_image_draw((20, 20)) + draw.line((5, 5, 14, 5), BLACK, 2) + assert_image_equal_tofile( + img, + os.path.join(IMAGES_PATH, "line_horizontal_w2px_normal.png"), + "line straight horizontal normal 2px wide failed", + ) + + img, draw = create_base_image_draw((20, 20)) + draw.line((14, 5, 5, 5), BLACK, 2) + assert_image_equal_tofile( + img, + os.path.join(IMAGES_PATH, "line_horizontal_w2px_inverted.png"), + "line straight horizontal inverted 2px wide failed", + ) + + expected = os.path.join(IMAGES_PATH, "line_horizontal_w3px.png") + img, draw = create_base_image_draw((20, 20)) + draw.line((5, 5, 14, 5), BLACK, 3) + assert_image_equal_tofile( + 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_tofile( + img, expected, "line straight horizontal inverted 3px wide failed" + ) + + img, draw = create_base_image_draw((200, 110)) + draw.line((5, 55, 195, 55), BLACK, 101) + assert_image_equal_tofile( + img, + os.path.join(IMAGES_PATH, "line_horizontal_w101px.png"), + "line straight horizontal 101px wide 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") + img, draw = create_base_image_draw((20, 20)) + draw.line((5, 5, 14, 6), BLACK, 2) + assert_image_equal_tofile( + img, + os.path.join(IMAGES_PATH, "line_horizontal_slope1px_w2px.png"), + "line horizontal 1px slope 2px 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") + img, draw = create_base_image_draw((20, 20)) + draw.line((5, 5, 5, 14), BLACK, 2) + assert_image_equal_tofile( + img, + os.path.join(IMAGES_PATH, "line_vertical_w2px_normal.png"), + "line straight vertical normal 2px wide failed", + ) + + img, draw = create_base_image_draw((20, 20)) + draw.line((5, 14, 5, 5), BLACK, 2) + assert_image_equal_tofile( + img, + os.path.join(IMAGES_PATH, "line_vertical_w2px_inverted.png"), + "line straight vertical inverted 2px wide failed", + ) + + expected = os.path.join(IMAGES_PATH, "line_vertical_w3px.png") + img, draw = create_base_image_draw((20, 20)) + draw.line((5, 5, 5, 14), BLACK, 3) + assert_image_equal_tofile( + 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_tofile( + img, expected, "line straight vertical inverted 3px wide failed" + ) + + img, draw = create_base_image_draw((110, 200)) + draw.line((55, 5, 55, 195), BLACK, 101) + assert_image_equal_tofile( + img, + os.path.join(IMAGES_PATH, "line_vertical_w101px.png"), + "line straight vertical 101px wide failed", + ) + + img, draw = create_base_image_draw((20, 20)) + draw.line((5, 5, 6, 14), BLACK, 2) + assert_image_equal_tofile( + img, + os.path.join(IMAGES_PATH, "line_vertical_slope1px_w2px.png"), + "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") + expected = os.path.join(IMAGES_PATH, "line_oblique_45_w3px_a.png") + img, draw = create_base_image_draw((20, 20)) + draw.line((5, 5, 14, 14), BLACK, 3) + assert_image_equal_tofile(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_tofile( + img, expected, "line oblique 45 inverted 3px wide A failed" + ) + + expected = os.path.join(IMAGES_PATH, "line_oblique_45_w3px_b.png") + img, draw = create_base_image_draw((20, 20)) + draw.line((14, 5, 5, 14), BLACK, 3) + assert_image_equal_tofile(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_tofile( + img, expected, "line oblique 45 inverted 3px wide B failed" + ) def test_wide_line_dot(): @@ -864,41 +1096,117 @@ def test_wide_line_dot(): # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) - expected = "Tests/images/imagedraw_wide_line_dot.png" # Act draw.line([(50, 50), (50, 50)], width=3) # Assert - assert_image_similar(im, Image.open(expected), 1) + assert_image_similar_tofile(im, "Tests/images/imagedraw_wide_line_dot.png", 1) + + +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) -def test_line_joint(): + # Assert + assert_image_similar_tofile(im, expected, 1) + + +@pytest.mark.parametrize( + "xy", + [ + [ + (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, + ), + [ + 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) - expected = "Tests/images/imagedraw_line_joint_curve.png" # Act - xy = [ - (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), - ] draw.line(xy, GRAY, 50, "curve") # Assert - assert_image_similar(im, Image.open(expected), 3) + assert_image_similar_tofile(im, "Tests/images/imagedraw_line_joint_curve.png", 3) def test_textsize_empty_string(): @@ -936,11 +1244,11 @@ def test_stroke(): font = ImageFont.truetype("Tests/fonts/FreeMono.ttf", 120) # Act - draw.text((10, 10), "A", "#f00", font, stroke_width=2, stroke_fill=stroke_fill) + 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_" + suffix + ".png"), 3.1 + assert_image_similar_tofile( + im, "Tests/images/imagedraw_stroke_" + suffix + ".png", 3.1 ) @@ -952,12 +1260,10 @@ def test_stroke_descender(): font = ImageFont.truetype("Tests/fonts/FreeMono.ttf", 120) # Act - draw.text((10, 0), "y", "#f00", font, stroke_width=2, stroke_fill="#0f0") + draw.text((12, 2), "y", "#f00", font, stroke_width=2, stroke_fill="#0f0") # Assert - assert_image_similar( - im, Image.open("Tests/images/imagedraw_stroke_descender.png"), 6.76 - ) + assert_image_similar_tofile(im, "Tests/images/imagedraw_stroke_descender.png", 6.76) @skip_unless_feature("freetype2") @@ -969,13 +1275,11 @@ def test_stroke_multiline(): # Act draw.multiline_text( - (10, 10), "A\nB", "#f00", font, stroke_width=2, stroke_fill="#0f0" + (12, 12), "A\nB", "#f00", font, stroke_width=2, stroke_fill="#0f0" ) # Assert - assert_image_similar( - im, Image.open("Tests/images/imagedraw_stroke_multiline.png"), 3.3 - ) + assert_image_similar_tofile(im, "Tests/images/imagedraw_stroke_multiline.png", 3.3) def test_same_color_outline(): @@ -1011,7 +1315,128 @@ def test_same_color_outline(): draw_method(*args) # Assert - expected = "Tests/images/imagedraw_outline_{}_{}.png".format( - operation, mode - ) - assert_image_similar(im, Image.open(expected), 1) + 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_tofile(im, 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 + + +def test_continuous_horizontal_edges_polygon(): + xy = [ + (2, 6), + (6, 6), + (12, 6), + (12, 12), + (8, 12), + (8, 8), + (4, 8), + (2, 8), + ] + img, draw = create_base_image_draw((16, 16)) + draw.polygon(xy, BLACK) + expected = os.path.join(IMAGES_PATH, "continuous_horizontal_edges_polygon.png") + assert_image_equal_tofile( + img, expected, "continuous horizontal edges polygon failed" + ) diff --git a/Tests/test_imagedraw2.py b/Tests/test_imagedraw2.py index 72cbb79b8e5..3a70176cee5 100644 --- a/Tests/test_imagedraw2.py +++ b/Tests/test_imagedraw2.py @@ -4,7 +4,8 @@ from .helper import ( assert_image_equal, - assert_image_similar, + assert_image_equal_tofile, + assert_image_similar_tofile, hopper, skip_unless_feature, ) @@ -55,13 +56,13 @@ def helper_ellipse(mode, bbox): draw = ImageDraw2.Draw(im) pen = ImageDraw2.Pen("blue", width=2) brush = ImageDraw2.Brush("green") - expected = "Tests/images/imagedraw_ellipse_{}.png".format(mode) + expected = f"Tests/images/imagedraw_ellipse_{mode}.png" # Act draw.ellipse(bbox, pen, brush) # Assert - assert_image_similar(im, Image.open(expected), 1) + assert_image_similar_tofile(im, expected, 1) def test_ellipse1(): @@ -79,10 +80,10 @@ def test_ellipse_edge(): brush = ImageDraw2.Brush("white") # Act - draw.ellipse(((0, 0), (W - 1, H)), brush) + draw.ellipse(((0, 0), (W - 1, H - 1)), brush) # Assert - assert_image_similar(im, Image.open("Tests/images/imagedraw_ellipse_edge.png"), 1) + assert_image_similar_tofile(im, "Tests/images/imagedraw_ellipse_edge.png", 1) def helper_line(points): @@ -95,7 +96,7 @@ def helper_line(points): draw.line(points, pen) # Assert - assert_image_equal(im, Image.open("Tests/images/imagedraw_line.png")) + assert_image_equal_tofile(im, "Tests/images/imagedraw_line.png") def test_line1_pen(): @@ -118,7 +119,7 @@ def test_line_pen_as_brush(): draw.line(POINTS1, pen, brush) # Assert - assert_image_equal(im, Image.open("Tests/images/imagedraw_line.png")) + assert_image_equal_tofile(im, "Tests/images/imagedraw_line.png") def helper_polygon(points): @@ -132,7 +133,7 @@ def helper_polygon(points): draw.polygon(points, pen, brush) # Assert - assert_image_equal(im, Image.open("Tests/images/imagedraw_polygon.png")) + assert_image_equal_tofile(im, "Tests/images/imagedraw_polygon.png") def test_polygon1(): @@ -154,7 +155,7 @@ def helper_rectangle(bbox): draw.rectangle(bbox, pen, brush) # Assert - assert_image_equal(im, Image.open("Tests/images/imagedraw_rectangle.png")) + assert_image_equal_tofile(im, "Tests/images/imagedraw_rectangle.png") def test_rectangle1(): @@ -178,7 +179,7 @@ def test_big_rectangle(): draw.rectangle(bbox, brush) # Assert - assert_image_similar(im, Image.open(expected), 1) + assert_image_similar_tofile(im, expected, 1) @skip_unless_feature("freetype2") @@ -193,7 +194,7 @@ def test_text(): draw.text((5, 5), "ImageDraw2", font) # Assert - assert_image_similar(im, Image.open(expected), 13) + assert_image_similar_tofile(im, expected, 13) @skip_unless_feature("freetype2") diff --git a/Tests/test_imageenhance.py b/Tests/test_imageenhance.py index 32ab05f1772..8bc94401e80 100644 --- a/Tests/test_imageenhance.py +++ b/Tests/test_imageenhance.py @@ -35,7 +35,7 @@ def _check_alpha(im, original, op, amount): assert_image_equal( im.getchannel("A"), original.getchannel("A"), - "Diff on {}: {}".format(op, amount), + f"Diff on {op}: {amount}", ) diff --git a/Tests/test_imagefile.py b/Tests/test_imagefile.py index 883e3f5668e..a5c76700d65 100644 --- a/Tests/test_imagefile.py +++ b/Tests/test_imagefile.py @@ -1,7 +1,8 @@ from io import BytesIO import pytest -from PIL import EpsImagePlugin, Image, ImageFile, features + +from PIL import BmpImagePlugin, EpsImagePlugin, Image, ImageFile, _binary, features from .helper import ( assert_image, @@ -71,7 +72,7 @@ def roundtrip(format): im1, im2 = roundtrip("JPEG") # lossy compression assert_image(im1, im2.mode, im2.size) - with pytest.raises(IOError): + with pytest.raises(OSError): roundtrip("PDF") def test_ico(self): @@ -81,6 +82,19 @@ def test_ico(self): p.feed(data) assert (48, 48) == p.image.size + @skip_unless_feature("webp") + @skip_unless_feature("webp_anim") + def test_incremental_webp(self): + with ImageFile.Parser() as p: + with open("Tests/images/hopper.webp", "rb") as f: + p.feed(f.read(1024)) + + # Check that insufficient data was given in the first feed + assert not p.image + + p.feed(f.read()) + assert (128, 128) == p.image.size + @skip_unless_feature("zlib") def test_safeblock(self): im1 = hopper() @@ -93,9 +107,9 @@ def test_safeblock(self): assert_image_equal(im1, im2) - def test_raise_ioerror(self): - with pytest.raises(IOError): - ImageFile.raise_ioerror(1) + def test_raise_oserror(self): + with pytest.raises(OSError): + ImageFile.raise_oserror(1) def test_raise_typeerror(self): with pytest.raises(TypeError): @@ -107,17 +121,31 @@ def test_negative_stride(self): input = f.read() p = ImageFile.Parser() p.feed(input) - with pytest.raises(IOError): + with pytest.raises(OSError): p.close() + def test_truncated(self): + b = BytesIO( + b"BM000000000000" # head_data + + _binary.o32le( + ImageFile.SAFEBLOCK + 1 + 4 + ) # header_size, so BmpImagePlugin will try to read SAFEBLOCK + 1 bytes + + ( + b"0" * ImageFile.SAFEBLOCK + ) # only SAFEBLOCK bytes, so that the header is truncated + ) + with pytest.raises(OSError) as e: + BmpImagePlugin.BmpImageFile(b) + assert str(e.value) == "Truncated File Read" + @skip_unless_feature("zlib") def test_truncated_with_errors(self): with Image.open("Tests/images/truncated_image.png") as im: - with pytest.raises(IOError): + with pytest.raises(OSError): im.load() # Test that the error is raised if loaded a second time - with pytest.raises(IOError): + with pytest.raises(OSError): im.load() @skip_unless_feature("zlib") @@ -132,7 +160,7 @@ def test_truncated_without_errors(self): @skip_unless_feature("zlib") def test_broken_datastream_with_errors(self): with Image.open("Tests/images/broken_data_stream.png") as im: - with pytest.raises(IOError): + with pytest.raises(OSError): im.load() @skip_unless_feature("zlib") @@ -238,93 +266,7 @@ def test_no_format(self): assert im.format is None assert im.get_format_mimetype() is None - def test_exif_jpeg(self, tmp_path): - with Image.open("Tests/images/exif-72dpi-int.jpg") as im: # Little endian - exif = im.getexif() - assert 258 not in exif - assert 40960 in exif - assert exif[40963] == 450 - assert exif[11] == "gThumb 3.0.1" - - out = str(tmp_path / "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() - assert reloaded_exif[258] == 8 - assert 40960 not in exif - assert reloaded_exif[40963] == 455 - assert exif[11] == "Pillow test" - - with Image.open("Tests/images/no-dpi-in-exif.jpg") as im: # Big endian - exif = im.getexif() - assert 258 not in exif - assert 40962 in exif - assert exif[40963] == 200 - assert exif[305] == "Adobe Photoshop CC 2017 (Macintosh)" - - out = str(tmp_path / "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() - assert reloaded_exif[258] == 8 - assert 40960 not in exif - assert reloaded_exif[40963] == 455 - assert exif[305] == "Pillow test" - - @skip_unless_feature("webp") - @skip_unless_feature("webp_anim") - def test_exif_webp(self, tmp_path): - with Image.open("Tests/images/hopper.webp") as im: - exif = im.getexif() - assert exif == {} - - out = str(tmp_path / "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() - assert reloaded_exif[258] == 8 - assert reloaded_exif[40963] == 455 - assert 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, tmp_path): - with Image.open("Tests/images/exif.png") as im: - exif = im.getexif() - assert exif == {274: 1} - - out = str(tmp_path / "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() - assert 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() - assert 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", num_resolutions=2) diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index e93aff4b22d..0d423aab7be 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -1,5 +1,4 @@ import copy -import distutils.version import os import re import shutil @@ -7,15 +6,17 @@ from io import BytesIO import pytest -from PIL import Image, ImageDraw, ImageFont +from packaging.version import parse as parse_version + +from PIL import Image, ImageDraw, ImageFont, features from .helper import ( assert_image_equal, - assert_image_similar, + assert_image_equal_tofile, assert_image_similar_tofile, - is_pypy, is_win32, skip_unless_feature, + skip_unless_feature_version, ) FONT_PATH = "Tests/fonts/FreeMono.ttf" @@ -30,44 +31,13 @@ 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)}, - } - - @classmethod - def setup_class(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): - assert re.search(r"\d+\.\d+\.\d+$", ImageFont.core.freetype2_version) + assert re.search(r"\d+\.\d+\.\d+$", features.version_module("freetype2")) def test_font_properties(self): ttf = self.get_font() @@ -81,7 +51,7 @@ def test_font_properties(self): ttf_copy = ttf.font_variant(size=FONT_SIZE + 1) assert ttf_copy.size == FONT_SIZE + 1 - second_font_path = "Tests/fonts/DejaVuSans.ttf" + second_font_path = "Tests/fonts/DejaVuSans/DejaVuSans.ttf" ttf_copy = ttf.font_variant(font=second_font_path) assert ttf_copy.path == second_font_path @@ -109,12 +79,12 @@ def test_font_with_open_file(self): with open(FONT_PATH, "rb") as f: self._render(f) - def test_non_unicode_path(self, tmp_path): + def test_non_ascii_path(self, tmp_path): tempfile = str(tmp_path / ("temp_" + chr(128) + ".ttf")) try: shutil.copy(FONT_PATH, tempfile) except UnicodeEncodeError: - pytest.skip("Unicode path could not be created") + pytest.skip("Non-ASCII path could not be created") ImageFont.truetype(tempfile, FONT_SIZE) @@ -150,6 +120,31 @@ def test_render_equal(self): 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" + assert_image_similar_tofile(im, target, 4.09) + + target = "Tests/images/transparent_background_text_L.png" + assert_image_similar_tofile(im.convert("L"), target, 0.01) + + def test_I16(self): + im = Image.new(mode="I;16", 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_L.png" + assert_image_similar_tofile(im.convert("L"), target, 0.01) + def test_textsize_equal(self): im = Image.new(mode="RGB", size=(300, 100)) draw = ImageDraw.Draw(im) @@ -160,11 +155,40 @@ def test_textsize_equal(self): draw.text((10, 10), txt, font=ttf) draw.rectangle((10, 10, 10 + size[0], 10 + size[1])) - target = "Tests/images/rectangle_surrounding_text.png" - with Image.open(target) as target_img: + assert_image_similar_tofile( + im, "Tests/images/rectangle_surrounding_text.png", 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/DejaVuSans.ttf", 18, 21, 22.21875), + ("rrr", "1", "DejaVuSans/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) - # Epsilon ~.5 fails with FreeType 2.7 - assert_image_similar(im, target_img, self.metrics["textsize"]) + 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)) @@ -177,13 +201,10 @@ def test_render_multiline(self): draw.text((0, y), line, font=ttf) y += line_spacing - target = "Tests/images/multiline_text.png" - with Image.open(target) as target_img: - - # 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"]) + # 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_tofile(im, "Tests/images/multiline_text.png", 6.2) def test_render_multiline_text(self): ttf = self.get_font() @@ -194,11 +215,7 @@ def test_render_multiline_text(self): draw = ImageDraw.Draw(im) draw.text((0, 0), TEST_TEXT, font=ttf) - target = "Tests/images/multiline_text.png" - 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_tofile(im, "Tests/images/multiline_text.png", 0.01) # Test that text() can pass on additional arguments # to multiline_text() @@ -213,11 +230,9 @@ def test_render_multiline_text(self): draw = ImageDraw.Draw(im) draw.multiline_text((0, 0), TEST_TEXT, font=ttf, align=align) - target = "Tests/images/multiline_text" + ext + ".png" - 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_tofile( + im, "Tests/images/multiline_text" + ext + ".png", 0.01 + ) def test_unknown_align(self): im = Image.new(mode="RGB", size=(300, 100)) @@ -271,11 +286,7 @@ def test_multiline_spacing(self): draw = ImageDraw.Draw(im) draw.multiline_text((0, 0), TEST_TEXT, font=ttf, spacing=10) - target = "Tests/images/multiline_text_spacing.png" - 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_tofile(im, "Tests/images/multiline_text_spacing.png", 2.5) def test_rotated_transposed_font(self): img_grey = Image.new("L", (100, 100)) @@ -393,14 +404,14 @@ def test_load_path_not_found(self): filename = "somefilenamethatdoesntexist.ttf" # Act/Assert - with pytest.raises(IOError): + with pytest.raises(OSError): ImageFont.load_path(filename) - with pytest.raises(IOError): + with pytest.raises(OSError): ImageFont.truetype(filename) def test_load_non_font_bytes(self): with open("Tests/images/hopper.jpg", "rb") as f: - with pytest.raises(IOError): + with pytest.raises(OSError): ImageFont.truetype(f) def test_default_font(self): @@ -409,15 +420,12 @@ def test_default_font(self): im = Image.new(mode="RGB", size=(300, 100)) draw = ImageDraw.Draw(im) - target = "Tests/images/default_font.png" - with Image.open(target) as target_img: - - # Act - default_font = ImageFont.load_default() - draw.text((10, 10), txt, font=default_font) + # Act + default_font = ImageFont.load_default() + draw.text((10, 10), txt, font=default_font) - # Assert - assert_image_equal(im, target_img) + # Assert + assert_image_equal_tofile(im, "Tests/images/default_font.png") def test_getsize_empty(self): # issue #2614 @@ -442,7 +450,6 @@ def test_unicode_pilfont(self): with pytest.raises(UnicodeEncodeError): font.getsize("’") - @pytest.mark.skipif(is_pypy(), reason="failing on PyPy") def test_unicode_extended(self): # issue #3777 text = "A\u278A\U0001F12B" @@ -457,7 +464,8 @@ def test_unicode_extended(self): 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, monkeypatch, path_to_fake, fontname): # Make a copy of FreeTypeFont so we can patch the original @@ -567,7 +575,7 @@ def test_imagefont_getters(self): assert t.font.glyphs == 4177 assert t.getsize("A") == (12, 16) assert t.getsize("AB") == (24, 16) - assert t.getsize("M") == self.metrics["getters"] + 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) @@ -607,17 +615,17 @@ def test_complex_font_settings(self): def test_variation_get(self): font = self.get_font() - freetype = distutils.version.StrictVersion(ImageFont.core.freetype2_version) - if freetype < "2.9.1": + 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 - with pytest.raises(IOError): + with pytest.raises(OSError): font.get_variation_names() - with pytest.raises(IOError): + with pytest.raises(OSError): font.get_variation_axes() font = ImageFont.truetype("Tests/fonts/AdobeVFPrototype.ttf") @@ -660,67 +668,357 @@ def test_variation_get(self): {"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: + assert_image_similar_tofile(im, path, epsilon) + except AssertionError: + if "_adobe" in path: + path = path.replace("_adobe", "_adobe_older_harfbuzz") + assert_image_similar_tofile(im, path, 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": + 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 - with pytest.raises(IOError): + with pytest.raises(OSError): 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) - 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": + 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 - with pytest.raises(IOError): + with pytest.raises(OSError): 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) - 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) + + def test_textbbox_non_freetypefont(self): + im = Image.new("RGB", (200, 200)) + d = ImageDraw.Draw(im) + default_font = ImageFont.load_default() + with pytest.raises(ValueError): + d.textbbox((0, 0), "test", font=default_font) + + @pytest.mark.parametrize( + "anchor, left, top", + ( + # test horizontal anchors + ("ls", 0, -36), + ("ms", -64, -36), + ("rs", -128, -36), + # test vertical anchors + ("ma", -64, 16), + ("mt", -64, 0), + ("mm", -64, -17), + ("mb", -64, -44), + ("md", -64, -51), + ), + ids=("ls", "ms", "rs", "ma", "mt", "mm", "mb", "md"), + ) + def test_anchor(self, anchor, left, top): + name, text = "quick", "Quick" + path = f"Tests/images/test_anchor_{name}_{anchor}.png" + + if 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 + + assert_image_similar_tofile(im, path, 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 + ) + + assert_image_similar_tofile(im, target, 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/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_bitmap_font_stroke(self): + text = "Bitmap Font" + layout_name = ["basic", "raqm"][self.LAYOUT_ENGINE] + target = f"Tests/images/bitmap_font_stroke_{layout_name}.png" + font = ImageFont.truetype( + "Tests/fonts/DejaVuSans/DejaVuSans-24-8-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, stroke_width=2, stroke_fill="red") + + assert_image_similar_tofile(im, target, 0.03) + + 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) + + assert_image_similar_tofile(im, "Tests/images/standard_embedded.png", 6.2) + + 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", font=font, embedded_color=True) + + assert_image_similar_tofile(im, "Tests/images/cbdt_notocoloremoji.png", 6.2) + except OSError as e: # pragma: no cover + assert str(e) in ("unimplemented feature", "unknown file format") + pytest.skip("freetype compiled without libpng or CBDT support") + + 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) + + assert_image_similar_tofile( + im, "Tests/images/cbdt_notocoloremoji_mask.png", 6.2 + ) + except OSError as e: # pragma: no cover + assert str(e) in ("unimplemented feature", "unknown file format") + pytest.skip("freetype compiled without libpng or CBDT support") + + def test_sbix(self): + try: + font = ImageFont.truetype( + "Tests/fonts/chromacheck-sbix.woff", + size=300, + layout_engine=self.LAYOUT_ENGINE, + ) + + im = Image.new("RGB", (400, 400), "white") + d = ImageDraw.Draw(im) + + d.text((50, 50), "\uE901", font=font, embedded_color=True) + + assert_image_similar_tofile(im, "Tests/images/chromacheck-sbix.png", 1) + except OSError as e: # pragma: no cover + assert str(e) in ("unimplemented feature", "unknown file format") + pytest.skip("freetype compiled without libpng or SBIX support") + + def test_sbix_mask(self): + try: + font = ImageFont.truetype( + "Tests/fonts/chromacheck-sbix.woff", + size=300, + layout_engine=self.LAYOUT_ENGINE, + ) + + im = Image.new("RGB", (400, 400), "white") + d = ImageDraw.Draw(im) + + d.text((50, 50), "\uE901", (100, 0, 0), font=font) + + assert_image_similar_tofile(im, "Tests/images/chromacheck-sbix_mask.png", 1) + except OSError as e: # pragma: no cover + assert str(e) in ("unimplemented feature", "unknown file format") + pytest.skip("freetype compiled without libpng or SBIX support") + + @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", font=font, embedded_color=True) + + assert_image_similar_tofile(im, "Tests/images/colr_bungee.png", 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) + + assert_image_similar_tofile(im, "Tests/images/colr_bungee_mask.png", 22) @skip_unless_feature("raqm") class TestImageFont_RaqmLayout(TestImageFont): LAYOUT_ENGINE = ImageFont.LAYOUT_RAQM + + +def test_render_mono_size(): + # issue 4177 + + im = Image.new("P", (100, 30), "white") + draw = ImageDraw.Draw(im) + ttf = ImageFont.truetype( + "Tests/fonts/DejaVuSans/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") + + +@pytest.mark.parametrize( + "test_file", + [ + "Tests/fonts/oom-e8e927ba6c0d38274a37c1567560eb33baf74627.ttf", + ], +) +def test_oom(test_file): + with open(test_file, "rb") as f: + font = ImageFont.truetype(BytesIO(f.read())) + with pytest.raises(Image.DecompressionBombError): + font.getmask("Test Text") diff --git a/Tests/test_imagefont_bitmap.py b/Tests/test_imagefont_bitmap.py deleted file mode 100644 index c4032d55dfc..00000000000 --- a/Tests/test_imagefont_bitmap.py +++ /dev/null @@ -1,38 +0,0 @@ -import pytest -from PIL import Image, ImageDraw, ImageFont - -from .helper import assert_image_similar - -image_font_installed = True -try: - ImageFont.core.getfont -except ImportError: - image_font_installed = False - - -@pytest.mark.skipif(not image_font_installed, reason="Image font not installed") -def test_similar(): - 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 6d7a9c2f485..ffb70cf1799 100644 --- a/Tests/test_imagefontctl.py +++ b/Tests/test_imagefontctl.py @@ -1,10 +1,11 @@ import pytest + from PIL import Image, ImageDraw, ImageFont -from .helper import assert_image_similar, skip_unless_feature +from .helper import assert_image_similar_tofile, skip_unless_feature FONT_SIZE = 20 -FONT_PATH = "Tests/fonts/DejaVuSans.ttf" +FONT_PATH = "Tests/fonts/DejaVuSans/DejaVuSans.ttf" pytestmark = skip_unless_feature("raqm") @@ -25,8 +26,7 @@ def test_complex_text(): 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) + assert_image_similar_tofile(im, target, 0.5) def test_y_offset(): @@ -37,8 +37,7 @@ def test_y_offset(): draw.text((0, 0), "العالم العربي", font=ttf, fill=500) target = "Tests/images/test_y_offset.png" - with Image.open(target) as target_img: - assert_image_similar(im, target_img, 1.7) + assert_image_similar_tofile(im, target, 1.7) def test_complex_unicode_text(): @@ -49,8 +48,7 @@ def test_complex_unicode_text(): 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) + assert_image_similar_tofile(im, target, 0.5) ttf = ImageFont.truetype("Tests/fonts/KhmerOSBattambang-Regular.ttf", FONT_SIZE) @@ -59,8 +57,7 @@ def test_complex_unicode_text(): draw.text((0, 0), "លោកុប្បត្តិ", font=ttf, fill=500) target = "Tests/images/test_complex_unicode_text2.png" - with Image.open(target) as target_img: - assert_image_similar(im, target_img, 2.3) + assert_image_similar_tofile(im, target, 2.33) def test_text_direction_rtl(): @@ -71,8 +68,7 @@ def test_text_direction_rtl(): draw.text((0, 0), "English عربي", 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) + assert_image_similar_tofile(im, target, 0.5) def test_text_direction_ltr(): @@ -83,8 +79,7 @@ def test_text_direction_ltr(): 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) + assert_image_similar_tofile(im, target, 0.5) def test_text_direction_rtl2(): @@ -95,8 +90,7 @@ def test_text_direction_rtl2(): draw.text((0, 0), "Oman سلطنة عمان", font=ttf, fill=500, direction="rtl") target = "Tests/images/test_direction_ltr.png" - with Image.open(target) as target_img: - assert_image_similar(im, target_img, 0.5) + assert_image_similar_tofile(im, target, 0.5) def test_text_direction_ttb(): @@ -111,8 +105,7 @@ def test_text_direction_ttb(): pytest.skip("libraqm 0.7 or greater not available") target = "Tests/images/test_direction_ttb.png" - with Image.open(target) as target_img: - assert_image_similar(im, target_img, 1.15) + assert_image_similar_tofile(im, target, 2.8) def test_text_direction_ttb_stroke(): @@ -122,7 +115,7 @@ def test_text_direction_ttb_stroke(): draw = ImageDraw.Draw(im) try: draw.text( - (25, 25), + (27, 27), "あい", font=ttf, fill=500, @@ -135,8 +128,7 @@ def test_text_direction_ttb_stroke(): pytest.skip("libraqm 0.7 or greater not available") target = "Tests/images/test_direction_ttb_stroke.png" - with Image.open(target) as target_img: - assert_image_similar(im, target_img, 12.4) + assert_image_similar_tofile(im, target, 19.4) def test_ligature_features(): @@ -146,8 +138,7 @@ def test_ligature_features(): 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) + assert_image_similar_tofile(im, target, 0.5) liga_size = ttf.getsize("fi", features=["-liga"]) assert liga_size == (13, 19) @@ -161,8 +152,7 @@ def test_kerning_features(): 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) + assert_image_similar_tofile(im, target, 0.5) def test_arabictext_features(): @@ -179,8 +169,7 @@ def test_arabictext_features(): ) target = "Tests/images/test_arabictext_features.png" - with Image.open(target) as target_img: - assert_image_similar(im, target_img, 0.5) + assert_image_similar_tofile(im, target, 0.5) def test_x_max_and_y_offset(): @@ -191,8 +180,7 @@ def test_x_max_and_y_offset(): 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) + assert_image_similar_tofile(im, target, 0.5) def test_language(): @@ -203,5 +191,206 @@ def test_language(): 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) + assert_image_similar_tofile(im, target, 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") + + +@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") + + assert_image_similar_tofile(im, path, 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): + 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") + + assert_image_similar_tofile(im, path, 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) + + assert_image_similar_tofile(im, path, 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 7908477344c..fa2291582d4 100644 --- a/Tests/test_imagegrab.py +++ b/Tests/test_imagegrab.py @@ -1,10 +1,12 @@ +import os import subprocess import sys import pytest + from PIL import Image, ImageGrab -from .helper import assert_image +from .helper import assert_image_equal_tofile, skip_unless_feature class TestImageGrab: @@ -12,42 +14,37 @@ class TestImageGrab: 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) + ImageGrab.grab() + ImageGrab.grab(include_layered_windows=True) + ImageGrab.grab(all_screens=True) im = ImageGrab.grab(bbox=(10, 20, 50, 80)) - assert_image(im, im.mode, (40, 60)) + assert im.size == (40, 60) - @pytest.mark.skipif(not Image.core.HAVE_XCB, reason="requires XCB") + @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) + ImageGrab.grab() - im2 = ImageGrab.grab(xdisplay="") - assert_image(im2, im2.mode, im2.size) - except IOError as e: + ImageGrab.grab(xdisplay="") + except OSError as e: pytest.skip(str(e)) @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(IOError) as e: + with pytest.raises(OSError) as e: ImageGrab.grab() assert str(e.value).startswith("Pillow was built without XCB support") - with pytest.raises(IOError) as e: + with pytest.raises(OSError) as e: ImageGrab.grab(xdisplay="") assert str(e.value).startswith("Pillow was built without XCB support") - @pytest.mark.skipif(not Image.core.HAVE_XCB, reason="requires XCB") + @skip_unless_feature("xcb") def test_grab_invalid_xdisplay(self): - with pytest.raises(IOError) as e: + with pytest.raises(OSError) as e: ImageGrab.grab(xdisplay="error.test:0.0") assert str(e.value).startswith("X connection failed") @@ -69,5 +66,28 @@ def test_grabclipboard(self): assert str(e.value) == "ImageGrab.grabclipboard() is macOS and Windows only" return + ImageGrab.grabclipboard() + + @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(im, im.mode, im.size) + assert_image_equal_tofile(im, "Tests/images/hopper.png") diff --git a/Tests/test_imagemath.py b/Tests/test_imagemath.py index bc4f1af28ba..39d91eadea6 100644 --- a/Tests/test_imagemath.py +++ b/Tests/test_imagemath.py @@ -1,9 +1,11 @@ +import pytest + from PIL import Image, ImageMath def pixel(im): if hasattr(im, "im"): - return "{} {!r}".format(im.mode, im.getpixel((0, 0))) + return f"{im.mode} {repr(im.getpixel((0, 0)))}" else: if isinstance(im, int): return int(im) # hack to deal with booleans @@ -50,6 +52,19 @@ def test_ops(): assert pixel(ImageMath.eval("float(B)**33", images)) == "F 8589934592.0" +@pytest.mark.parametrize( + "expression", + ( + "exec('pass')", + "(lambda: exec('pass'))()", + "(lambda: (lambda: exec('pass'))())()", + ), +) +def test_prevent_exec(expression): + with pytest.raises(ValueError): + ImageMath.eval(expression) + + def test_logical(): assert pixel(ImageMath.eval("not A", images)) == 0 assert pixel(ImageMath.eval("A and B", images)) == "L 2" diff --git a/Tests/test_imagemorph.py b/Tests/test_imagemorph.py index 62119e4b3bc..368c2bba140 100644 --- a/Tests/test_imagemorph.py +++ b/Tests/test_imagemorph.py @@ -1,8 +1,9 @@ # Test the ImageMorphology functionality import pytest + from PIL import Image, ImageMorph, _imagingmorph -from .helper import assert_image_equal, hopper +from .helper import assert_image_equal_tofile, hopper def string_to_img(image_string): @@ -56,15 +57,14 @@ def assert_img_equal_img_string(A, Bstring): def test_str_to_img(): - with Image.open("Tests/images/morph_a.png") as im: - assert_image_equal(A, im) + assert_image_equal_tofile(A, "Tests/images/morph_a.png") def create_lut(): 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: + with open(f"Tests/images/{op}.lut", "wb") as f: f.write(lut) @@ -75,7 +75,7 @@ def test_lut(): assert lb.get_lut() is None lut = lb.build_lut() - with open("Tests/images/%s.lut" % op, "rb") as f: + with open(f"Tests/images/{op}.lut", "rb") as f: assert lut == bytearray(f.read()) @@ -235,19 +235,19 @@ def test_negate(): ) -def test_non_binary_images(): +def test_incorrect_mode(): im = hopper("RGB") mop = ImageMorph.MorphOp(op_name="erosion8") - with pytest.raises(Exception) as e: + with pytest.raises(ValueError) 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: + assert str(e.value) == "Image mode must be L" + with pytest.raises(ValueError) 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: + assert str(e.value) == "Image mode must be L" + with pytest.raises(ValueError) as e: mop.get_on_pixels(im) - assert str(e.value) == "Image must be binary, meaning it must use mode L" + assert str(e.value) == "Image mode must be L" def test_add_patterns(): diff --git a/Tests/test_imageops.py b/Tests/test_imageops.py index 3d0afba9c58..6aa1cf35edf 100644 --- a/Tests/test_imageops.py +++ b/Tests/test_imageops.py @@ -1,9 +1,11 @@ import pytest -from PIL import Image, ImageOps, features + +from PIL import Image, ImageDraw, ImageOps, ImageStat, features from .helper import ( assert_image_equal, assert_image_similar, + assert_image_similar_tofile, assert_tuple_approx_equal, hopper, ) @@ -24,7 +26,10 @@ def test_sanity(): ImageOps.autocontrast(hopper("RGB")) 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.autocontrast(hopper("L"), preserve_tone=True) ImageOps.colorize(hopper("L"), (0, 0, 0), (255, 255, 255)) ImageOps.colorize(hopper("L"), "black", "white") @@ -32,6 +37,9 @@ def test_sanity(): ImageOps.pad(hopper("L"), (128, 128)) ImageOps.pad(hopper("RGB"), (128, 128)) + ImageOps.contain(hopper("L"), (128, 128)) + ImageOps.contain(hopper("RGB"), (128, 128)) + ImageOps.crop(hopper("L"), 1) ImageOps.crop(hopper("RGB"), 1) @@ -94,6 +102,13 @@ def test_fit_same_ratio(): assert new_im.size == (1000, 755) +@pytest.mark.parametrize("new_size", ((256, 256), (512, 256), (256, 512))) +def test_contain(new_size): + im = hopper() + new_im = ImageOps.contain(im, new_size) + assert new_im.size == (256, 256) + + def test_pad(): # Same ratio im = hopper() @@ -109,10 +124,9 @@ def test_pad(): 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) + assert_image_similar_tofile( + new_im, "Tests/images/imageops_pad_" + label + "_" + str(i) + ".jpg", 6 + ) def test_pil163(): @@ -142,6 +156,33 @@ def test_scale(): assert newimg.size == (25, 25) +@pytest.mark.parametrize("border", (10, (1, 2, 3, 4))) +def test_expand_palette(border): + with Image.open("Tests/images/p_16.tga") as im: + im_expanded = ImageOps.expand(im, border, (255, 0, 0)) + + if isinstance(border, int): + left = top = right = bottom = border + else: + left, top, right, bottom = border + px = im_expanded.convert("RGB").load() + for x in range(im_expanded.width): + for b in range(top): + assert px[x, b] == (255, 0, 0) + for b in range(bottom): + assert px[x, im_expanded.height - 1 - b] == (255, 0, 0) + for y in range(im_expanded.height): + for b in range(left): + assert px[b, y] == (255, 0, 0) + for b in range(right): + assert px[im_expanded.width - 1 - b, y] == (255, 0, 0) + + im_cropped = im_expanded.crop( + (left, top, im_expanded.width - right, im_expanded.height - bottom) + ) + assert_image_equal(im_cropped, im) + + def test_colorize_2color(): # Test the colorizing function with 2-color functionality @@ -288,6 +329,7 @@ def check(orientation_im): else: assert transposed_im.info["exif"] != original_exif + assert 0x0112 in im.getexif() assert 0x0112 not in transposed_im.getexif() # Repeat the operation to test that it does not keep transposing @@ -300,3 +342,133 @@ def check(orientation_im): "Tests/images/hopper_orientation_" + str(i) + ext ) as orientation_im: check(orientation_im) + + # Orientation from "XML:com.adobe.xmp" info key + with Image.open("Tests/images/xmp_tags_orientation.png") as im: + assert im.getexif()[0x0112] == 3 + + transposed_im = ImageOps.exif_transpose(im) + assert 0x0112 not in transposed_im.getexif() + + # Orientation from "Raw profile type exif" info key + # This test image has been manually hexedited from exif_imagemagick.png + # to have a different orientation + with Image.open("Tests/images/exif_imagemagick_orientation.png") as im: + assert im.getexif()[0x0112] == 3 + + transposed_im = ImageOps.exif_transpose(im) + assert 0x0112 not in transposed_im.getexif() + + # Orientation set directly on Image.Exif + im = hopper() + im.getexif()[0x0112] = 3 + transposed_im = ImageOps.exif_transpose(im) + assert 0x0112 not in transposed_im.getexif() + + +def test_autocontrast_cutoff(): + # Test the cutoff argument of autocontrast + with Image.open("Tests/images/bw_gradient.png") as img: + + def autocontrast(cutoff): + return ImageOps.autocontrast(img, cutoff).histogram() + + assert autocontrast(10) == autocontrast((10, 10)) + assert autocontrast(10) != autocontrast((1, 10)) + + +def test_autocontrast_mask_toy_input(): + # Test the mask argument of autocontrast + with Image.open("Tests/images/bw_gradient.png") as img: + + 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) + + result = ImageOps.autocontrast(img, mask=rect_mask) + result_nomask = ImageOps.autocontrast(img) + + assert result != result_nomask + assert ImageStat.Stat(result, mask=rect_mask).median == [127] + assert ImageStat.Stat(result_nomask).median == [128] + + +def test_autocontrast_mask_real_input(): + # Test the autocontrast with a rectangular mask + with Image.open("Tests/images/iptc.jpg") as img: + + 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( + ImageStat.Stat(result, mask=rect_mask).median, + [195, 202, 184], + threshold=2, + msg="autocontrast with mask pixel incorrect", + ) + assert_tuple_approx_equal( + ImageStat.Stat(result_nomask).median, + [119, 106, 79], + threshold=2, + msg="autocontrast without mask pixel incorrect", + ) + + +def test_autocontrast_preserve_tone(): + def autocontrast(mode, preserve_tone): + im = hopper(mode) + return ImageOps.autocontrast(im, preserve_tone=preserve_tone).histogram() + + assert autocontrast("RGB", True) != autocontrast("RGB", False) + assert autocontrast("L", True) == autocontrast("L", False) + + +def test_autocontrast_preserve_gradient(): + gradient = Image.linear_gradient("L") + + # test with a grayscale gradient that extends to 0,255. + # Should be a noop. + out = ImageOps.autocontrast(gradient, cutoff=0, preserve_tone=True) + + assert_image_equal(gradient, out) + + # cutoff the top and bottom + # autocontrast should make the first and last histogram entries equal + # and, with rounding, should be 10% of the image pixels + out = ImageOps.autocontrast(gradient, cutoff=10, preserve_tone=True) + hist = out.histogram() + assert hist[0] == hist[-1] + assert hist[-1] == 256 * round(256 * 0.10) + + # in rgb + img = gradient.convert("RGB") + out = ImageOps.autocontrast(img, cutoff=0, preserve_tone=True) + assert_image_equal(img, out) + + +@pytest.mark.parametrize( + "color", ((255, 255, 255), (127, 255, 0), (127, 127, 127), (0, 0, 0)) +) +def test_autocontrast_preserve_one_color(color): + img = Image.new("RGB", (10, 10), color) + + # single color images shouldn't change + out = ImageOps.autocontrast(img, cutoff=0, preserve_tone=True) + assert_image_equal(img, out) # single color, no cutoff + + # even if there is a cutoff + out = ImageOps.autocontrast( + img, cutoff=10, preserve_tone=True + ) # single color 10 cutoff + assert_image_equal(img, out) diff --git a/Tests/test_imageops_usm.py b/Tests/test_imageops_usm.py index 61f8dc2baee..8837ed2a262 100644 --- a/Tests/test_imageops_usm.py +++ b/Tests/test_imageops_usm.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image, ImageFilter diff --git a/Tests/test_imagepalette.py b/Tests/test_imagepalette.py index 29771cf0301..475d249ed09 100644 --- a/Tests/test_imagepalette.py +++ b/Tests/test_imagepalette.py @@ -1,33 +1,78 @@ import pytest + from PIL import Image, ImagePalette -from .helper import assert_image_equal +from .helper import assert_image_equal, assert_image_equal_tofile def test_sanity(): - ImagePalette.ImagePalette("RGB", list(range(256)) * 3) - with pytest.raises(ValueError): - ImagePalette.ImagePalette("RGB", list(range(256)) * 2) + palette = ImagePalette.ImagePalette("RGB", list(range(256)) * 3) + assert len(palette.colors) == 256 + + with pytest.warns(DeprecationWarning): + with pytest.raises(ValueError): + ImagePalette.ImagePalette("RGB", list(range(256)) * 3, 10) + + +def test_reload(): + with Image.open("Tests/images/hopper.gif") as im: + original = im.copy() + im.palette.dirty = 1 + assert_image_equal(im.convert("RGB"), original.convert("RGB")) def test_getcolor(): palette = ImagePalette.ImagePalette() + assert len(palette.palette) == 0 + assert len(palette.colors) == 0 test_map = {} for i in range(256): test_map[palette.getcolor((i, i, i))] = i - assert len(test_map) == 256 + + # Colors can be converted between RGB and RGBA + rgba_palette = ImagePalette.ImagePalette("RGBA") + assert rgba_palette.getcolor((0, 0, 0)) == rgba_palette.getcolor((0, 0, 0, 255)) + + assert palette.getcolor((0, 0, 0)) == palette.getcolor((0, 0, 0, 255)) + + # An error is raised when the palette is full with pytest.raises(ValueError): palette.getcolor((1, 2, 3)) + # But not if the image is not using one of the palette entries + palette.getcolor((1, 2, 3), image=Image.new("P", (1, 1))) # Test unknown color specifier with pytest.raises(ValueError): palette.getcolor("unknown") +@pytest.mark.parametrize( + "index, palette", + [ + # Test when the palette is not full + (0, ImagePalette.ImagePalette()), + # Test when the palette is full + (255, ImagePalette.ImagePalette("RGB", list(range(256)) * 3)), + ], +) +def test_getcolor_not_special(index, palette): + im = Image.new("P", (1, 1)) + + # Do not use transparency index as a new color + im.info["transparency"] = index + index1 = palette.getcolor((0, 0, 0), im) + assert index1 != index + + # Do not use background index as a new color + im.info["background"] = index1 + index2 = palette.getcolor((0, 0, 1), im) + assert index2 not in (index, index1) + + def test_file(tmp_path): palette = ImagePalette.ImagePalette("RGB", list(range(256)) * 3) @@ -115,7 +160,7 @@ def test_getdata(): mode, data_out = palette.getdata() # Assert - assert mode == "RGB;L" + assert mode == "RGB" def test_rawmode_getdata(): @@ -140,10 +185,9 @@ def test_2bit_palette(tmp_path): 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) + assert_image_equal_tofile(img, outfile) def test_invalid_palette(): - with pytest.raises(IOError): + with pytest.raises(OSError): ImagePalette.load("Tests/images/hopper.jpg") diff --git a/Tests/test_imagepath.py b/Tests/test_imagepath.py index 52af164551f..b18271cc5a1 100644 --- a/Tests/test_imagepath.py +++ b/Tests/test_imagepath.py @@ -1,79 +1,183 @@ import array +import math import struct import pytest + from PIL import Image, ImagePath -class TestImagePath: - def test_path(self): - - 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)] - - 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 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 +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)] + + +def test_invalid_coords(): + # Arrange + coords = ["a", "b"] + + # Act / Assert + with pytest.raises(SystemError): + ImagePath.Path(coords) + + +def test_path_odd_number_of_coordinates(): + # Arrange + coords = [0] + + # Act / Assert + with pytest.raises(ValueError) as e: + ImagePath.Path(coords) + + assert str(e.value) == "wrong number of coordinates" + + +@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)), + (0, (0.0, 0.0, 0.0, 0.0)), + (1, (0.0, 0.0, 0.0, 0.0)), + ], +) +def test_getbbox(coords, expected): + # Arrange + p = ImagePath.Path(coords) + + # Act / Assert + assert p.getbbox() == expected + + +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 d723690ef24..589cb5a210a 100644 --- a/Tests/test_imageqt.py +++ b/Tests/test_imageqt.py @@ -1,40 +1,27 @@ import pytest + from PIL import ImageQt -from .helper import hopper +from .helper import assert_image_similar, 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 qRgba -@pytest.mark.skipif(not ImageQt.qt_is_installed, reason="Qt bindings are not installed") -class PillowQPixmapTestCase: - @classmethod - def setup_class(self): - try: - if ImageQt.qt_version == "5": - from PyQt5.QtGui import QGuiApplication - elif ImageQt.qt_version == "side2": - from PySide2.QtGui import QGuiApplication - except ImportError: - pytest.skip("QGuiApplication not installed") - return - - self.app = QGuiApplication([]) - - @classmethod - def teardown_class(self): - self.app.quit() - self.app = None - - -@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 == "5": + if ImageQt.qt_version == "6": + from PyQt6.QtGui import qRgb + elif 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 @@ -54,7 +41,22 @@ def checkrgb(r, g, b): checkrgb(0, 0, 255) -@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)) + modes = ["1", "RGB", "RGBA", "L", "P"] + qt_format = ImageQt.QImage.Format if ImageQt.qt_version == "6" else ImageQt.QImage + if hasattr(qt_format, "Format_Grayscale16"): # Qt 5.13+ + modes.append("I;16") + + for mode in modes: + im = hopper(mode) + roundtripped_im = ImageQt.fromqimage(ImageQt.ImageQt(im)) + if mode not in ("RGB", "RGBA"): + im = im.convert("RGB") + assert_image_similar(roundtripped_im, im, 1) + + +def test_closed_file(): + with pytest.warns(None) as record: + ImageQt.ImageQt("Tests/images/hopper.gif") + + assert not record diff --git a/Tests/test_imagesequence.py b/Tests/test_imagesequence.py index b3fe9df97e1..7cf237b4654 100644 --- a/Tests/test_imagesequence.py +++ b/Tests/test_imagesequence.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image, ImageSequence, TiffImagePlugin from .helper import assert_image_equal, hopper, skip_unless_feature diff --git a/Tests/test_imageshow.py b/Tests/test_imageshow.py index 0d513a47dc4..5981e22c012 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 show_image(self, image, **options): @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"): @@ -58,4 +62,20 @@ def test_viewer(): def test_viewers(): for viewer in ImageShow._viewers: - viewer.get_command("test.jpg") + try: + viewer.get_command("test.jpg") + except NotImplementedError: + pass + + +def test_ipythonviewer(): + pytest.importorskip("IPython", reason="IPython not installed") + for viewer in ImageShow._viewers: + if isinstance(viewer, ImageShow.IPythonViewer): + test_viewer = viewer + break + else: + assert False + + im = hopper() + assert test_viewer.show(im) == 1 diff --git a/Tests/test_imagestat.py b/Tests/test_imagestat.py index 6c70193ce67..9474ff6f9ba 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 d13920c16d4..928b8cbd188 100644 --- a/Tests/test_imagetk.py +++ b/Tests/test_imagetk.py @@ -1,13 +1,14 @@ import pytest + from PIL import Image 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): @@ -26,7 +27,7 @@ def setup_module(): tk.Frame() # root = tk.Tk() except tk.TclError as v: - pytest.skip("TCL Error: %s" % v) + pytest.skip(f"TCL Error: {v}") def test_kw(): diff --git a/Tests/test_imagewin.py b/Tests/test_imagewin.py index b1ddc75e95a..9d64d17a30f 100644 --- a/Tests/test_imagewin.py +++ b/Tests/test_imagewin.py @@ -1,4 +1,5 @@ import pytest + from PIL import ImageWin from .helper import hopper, is_win32 diff --git a/Tests/test_imagewin_pointers.py b/Tests/test_imagewin_pointers.py index a5cac96e42c..c51a66089c8 100644 --- a/Tests/test_imagewin_pointers.py +++ b/Tests/test_imagewin_pointers.py @@ -110,4 +110,5 @@ def test_pointer(tmp_path): DeleteObject(dib) DeleteDC(hdc) - Image.open(BytesIO(bitmap)).save(opath) + with Image.open(BytesIO(bitmap)) as im: + im.save(opath) diff --git a/Tests/test_lib_image.py b/Tests/test_lib_image.py index 7115e62add6..37ed3659d0a 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 8e3c1fda925..af7eae935f6 100644 --- a/Tests/test_lib_pack.py +++ b/Tests/test_lib_pack.py @@ -1,6 +1,7 @@ import sys import pytest + from PIL import Image X = 255 @@ -319,6 +320,23 @@ def test_RGB(self): self.assert_unpack("RGB", "G", 1, (0, 1, 0), (0, 2, 0), (0, 3, 0)) self.assert_unpack("RGB", "B", 1, (0, 0, 1), (0, 0, 2), (0, 0, 3)) + self.assert_unpack("RGB", "R;16B", 2, (1, 0, 0), (3, 0, 0), (5, 0, 0)) + self.assert_unpack("RGB", "G;16B", 2, (0, 1, 0), (0, 3, 0), (0, 5, 0)) + self.assert_unpack("RGB", "B;16B", 2, (0, 0, 1), (0, 0, 3), (0, 0, 5)) + + self.assert_unpack("RGB", "R;16L", 2, (2, 0, 0), (4, 0, 0), (6, 0, 0)) + self.assert_unpack("RGB", "G;16L", 2, (0, 2, 0), (0, 4, 0), (0, 6, 0)) + self.assert_unpack("RGB", "B;16L", 2, (0, 0, 2), (0, 0, 4), (0, 0, 6)) + + if sys.byteorder == "little": + self.assert_unpack("RGB", "R;16N", 2, (2, 0, 0), (4, 0, 0), (6, 0, 0)) + self.assert_unpack("RGB", "G;16N", 2, (0, 2, 0), (0, 4, 0), (0, 6, 0)) + self.assert_unpack("RGB", "B;16N", 2, (0, 0, 2), (0, 0, 4), (0, 0, 6)) + else: + self.assert_unpack("RGB", "R;16N", 2, (1, 0, 0), (3, 0, 0), (5, 0, 0)) + self.assert_unpack("RGB", "G;16N", 2, (0, 1, 0), (0, 3, 0), (0, 5, 0)) + self.assert_unpack("RGB", "B;16N", 2, (0, 0, 1), (0, 0, 3), (0, 0, 5)) + def test_RGBA(self): self.assert_unpack("RGBA", "LA", 2, (1, 1, 1, 2), (3, 3, 3, 4), (5, 5, 5, 6)) self.assert_unpack( @@ -449,6 +467,43 @@ def test_RGBA(self): self.assert_unpack("RGBA", "B", 1, (0, 0, 1, 0), (0, 0, 2, 0), (0, 0, 3, 0)) self.assert_unpack("RGBA", "A", 1, (0, 0, 0, 1), (0, 0, 0, 2), (0, 0, 0, 3)) + self.assert_unpack("RGBA", "R;16B", 2, (1, 0, 0, 0), (3, 0, 0, 0), (5, 0, 0, 0)) + self.assert_unpack("RGBA", "G;16B", 2, (0, 1, 0, 0), (0, 3, 0, 0), (0, 5, 0, 0)) + self.assert_unpack("RGBA", "B;16B", 2, (0, 0, 1, 0), (0, 0, 3, 0), (0, 0, 5, 0)) + self.assert_unpack("RGBA", "A;16B", 2, (0, 0, 0, 1), (0, 0, 0, 3), (0, 0, 0, 5)) + + self.assert_unpack("RGBA", "R;16L", 2, (2, 0, 0, 0), (4, 0, 0, 0), (6, 0, 0, 0)) + self.assert_unpack("RGBA", "G;16L", 2, (0, 2, 0, 0), (0, 4, 0, 0), (0, 6, 0, 0)) + self.assert_unpack("RGBA", "B;16L", 2, (0, 0, 2, 0), (0, 0, 4, 0), (0, 0, 6, 0)) + self.assert_unpack("RGBA", "A;16L", 2, (0, 0, 0, 2), (0, 0, 0, 4), (0, 0, 0, 6)) + + if sys.byteorder == "little": + self.assert_unpack( + "RGBA", "R;16N", 2, (2, 0, 0, 0), (4, 0, 0, 0), (6, 0, 0, 0) + ) + self.assert_unpack( + "RGBA", "G;16N", 2, (0, 2, 0, 0), (0, 4, 0, 0), (0, 6, 0, 0) + ) + self.assert_unpack( + "RGBA", "B;16N", 2, (0, 0, 2, 0), (0, 0, 4, 0), (0, 0, 6, 0) + ) + self.assert_unpack( + "RGBA", "A;16N", 2, (0, 0, 0, 2), (0, 0, 0, 4), (0, 0, 0, 6) + ) + else: + self.assert_unpack( + "RGBA", "R;16N", 2, (1, 0, 0, 0), (3, 0, 0, 0), (5, 0, 0, 0) + ) + self.assert_unpack( + "RGBA", "G;16N", 2, (0, 1, 0, 0), (0, 3, 0, 0), (0, 5, 0, 0) + ) + self.assert_unpack( + "RGBA", "B;16N", 2, (0, 0, 1, 0), (0, 0, 3, 0), (0, 0, 5, 0) + ) + self.assert_unpack( + "RGBA", "A;16N", 2, (0, 0, 0, 1), (0, 0, 0, 3), (0, 0, 0, 5) + ) + def test_RGBa(self): self.assert_unpack( "RGBa", "RGBa", 4, (1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12) diff --git a/Tests/test_locale.py b/Tests/test_locale.py index c5e54883d90..7a07fbbe0a4 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 b2f3ff2271b..42f3447ebfd 100644 --- a/Tests/test_map.py +++ b/Tests/test_map.py @@ -1,11 +1,8 @@ import sys import pytest -from PIL import Image - -from .helper import is_win32 -pytestmark = pytest.mark.skipif(is_win32(), reason="Win32 does not call map_buffer") +from PIL import Image def test_overflow(): @@ -20,12 +17,25 @@ def test_overflow(): # This image hits the offset test. with Image.open("Tests/images/l2rgb_read.bmp") as im: - with pytest.raises((ValueError, MemoryError, IOError)): + with pytest.raises((ValueError, MemoryError, OSError)): im.load() Image.MAX_IMAGE_PIXELS = max_pixels +def test_tobytes(): + # Note that this image triggers the decompression bomb warning: + max_pixels = Image.MAX_IMAGE_PIXELS + Image.MAX_IMAGE_PIXELS = None + + # Previously raised an access violation on Windows + with Image.open("Tests/images/l2rgb_read.bmp") as im: + with pytest.raises((ValueError, MemoryError, OSError)): + im.tobytes() + + Image.MAX_IMAGE_PIXELS = max_pixels + + @pytest.mark.skipif(sys.maxsize <= 2 ** 32, reason="Requires 64-bit system") def test_ysize(): numpy = pytest.importorskip("numpy", reason="NumPy not installed") diff --git a/Tests/test_mode_i16.py b/Tests/test_mode_i16.py index 19e16f2c44b..0571aabf495 100644 --- a/Tests/test_mode_i16.py +++ b/Tests/test_mode_i16.py @@ -15,9 +15,9 @@ def verify(im1): xy = x, y p1 = pix1[xy] p2 = pix2[xy] - assert p1 == p2, "got {!r} from mode {} at {}, expected {!r}".format( - p1, im1.mode, xy, p2 - ) + assert ( + p1 == p2 + ), f"got {repr(p1)} from mode {im1.mode} at {xy}, expected {repr(p2)}" def test_basic(tmp_path): diff --git a/Tests/test_numpy.py b/Tests/test_numpy.py index 30ab5132a31..def7adf3f02 100644 --- a/Tests/test_numpy.py +++ b/Tests/test_numpy.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image from .helper import assert_deep_equal, assert_image, hopper @@ -30,7 +31,7 @@ def to_image(dtype, bands=1, boolean=0): return i # Check supported 1-bit integer formats - assert_image(to_image(numpy.bool, 1, 1), "1", TEST_IMAGE_SIZE) + assert_image(to_image(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 @@ -64,7 +65,7 @@ def to_image(dtype, bands=1, boolean=0): to_image(numpy.int64) # Check floating-point formats - assert_image(to_image(numpy.float), "F", TEST_IMAGE_SIZE) + assert_image(to_image(float), "F", TEST_IMAGE_SIZE) with pytest.raises(TypeError): to_image(numpy.float16) assert_image(to_image(numpy.float32), "F", TEST_IMAGE_SIZE) @@ -89,6 +90,11 @@ def test_3d_array(): assert_image(Image.fromarray(a[:, :, 1]), "L", TEST_IMAGE_SIZE) +def test_1d_array(): + a = numpy.ones(5, dtype=numpy.uint8) + assert_image(Image.fromarray(a), "L", (1, 5)) + + def _test_img_equals_nparray(img, np): assert len(np.shape) >= 2 np_size = np.shape[1], np.shape[0] @@ -185,7 +191,7 @@ def test_putdata(): def test_roundtrip_eye(): for dtype in ( - numpy.bool, + bool, numpy.bool8, numpy.int8, numpy.int16, @@ -193,7 +199,7 @@ def test_roundtrip_eye(): numpy.uint8, numpy.uint16, numpy.uint32, - numpy.float, + float, numpy.float32, numpy.float64, ): @@ -212,7 +218,7 @@ def test_zero_size(): def test_bool(): # https://github.com/python-pillow/Pillow/issues/2044 - a = numpy.zeros((10, 2), dtype=numpy.bool) + a = numpy.zeros((10, 2), dtype=bool) a[0][0] = True im2 = Image.fromarray(a) @@ -228,4 +234,6 @@ def test_no_resource_warning_for_numpy_array(): with Image.open(test_file) as im: # Act/Assert - pytest.warns(None, lambda: array(im)) + with pytest.warns(None) as record: + array(im) + assert not record diff --git a/Tests/test_pdfparser.py b/Tests/test_pdfparser.py index f5cd403d530..2d428e95fce 100644 --- a/Tests/test_pdfparser.py +++ b/Tests/test_pdfparser.py @@ -1,6 +1,7 @@ import time import pytest + from PIL.PdfParser import ( IndirectObjectDef, IndirectReference, diff --git a/Tests/test_pickle.py b/Tests/test_pickle.py index ba48689883c..5fd04585563 100644 --- a/Tests/test_pickle.py +++ b/Tests/test_pickle.py @@ -1,11 +1,18 @@ import pickle -from PIL import Image +import pytest +from PIL import Image, ImageDraw, ImageFont -def helper_pickle_file(tmp_path, pickle, protocol=0, mode=None): +from .helper import assert_image_equal, skip_unless_feature + +FONT_SIZE = 20 +FONT_PATH = "Tests/fonts/DejaVuSans/DejaVuSans.ttf" + + +def helper_pickle_file(tmp_path, pickle, protocol, test_file, mode): # Arrange - with Image.open("Tests/images/hopper.jpg") as im: + with Image.open(test_file) as im: filename = str(tmp_path / "temp.pkl") if mode: im = im.convert(mode) @@ -20,9 +27,7 @@ def helper_pickle_file(tmp_path, pickle, protocol=0, mode=None): assert im == loaded_im -def helper_pickle_string( - pickle, protocol=0, test_file="Tests/images/hopper.jpg", mode=None -): +def helper_pickle_string(pickle, protocol, test_file, mode): with Image.open(test_file) as im: if mode: im = im.convert(mode) @@ -35,41 +40,31 @@ def helper_pickle_string( assert im == loaded_im -def test_pickle_image(tmp_path): - # Act / Assert - for protocol in range(0, pickle.HIGHEST_PROTOCOL + 1): - helper_pickle_string(pickle, protocol) - helper_pickle_file(tmp_path, pickle, protocol) - - -def test_pickle_p_mode(): - # 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): - helper_pickle_string(pickle, protocol=protocol, test_file=test_file) - - -def test_pickle_pa_mode(tmp_path): - # Act / Assert - for protocol in range(0, pickle.HIGHEST_PROTOCOL + 1): - helper_pickle_string(pickle, protocol, mode="PA") - helper_pickle_file(tmp_path, pickle, protocol, mode="PA") - - -def test_pickle_l_mode(tmp_path): +@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, mode="L") - helper_pickle_file(tmp_path, pickle, protocol, mode="L") + helper_pickle_string(pickle, protocol, test_file, test_mode) + helper_pickle_file(tmp_path, pickle, protocol, test_file, test_mode) def test_pickle_la_mode_with_palette(tmp_path): @@ -88,3 +83,60 @@ def test_pickle_la_mode_with_palette(tmp_path): im.mode = "PA" assert im == loaded_im + + +@skip_unless_feature("webp") +def test_pickle_tell(): + # Arrange + with Image.open("Tests/images/hopper.webp") as image: + + # Act: roundtrip + unpickled_image = pickle.loads(pickle.dumps(image)) + + # Assert + assert unpickled_image.tell() == 0 + + +def helper_assert_pickled_font_images(font1, font2): + # Arrange + im1 = Image.new(mode="RGBA", size=(300, 100)) + im2 = Image.new(mode="RGBA", size=(300, 100)) + draw1 = ImageDraw.Draw(im1) + draw2 = ImageDraw.Draw(im2) + txt = "Hello World!" + + # Act + draw1.text((10, 10), txt, font=font1) + draw2.text((10, 10), txt, font=font2) + + # Assert + assert_image_equal(im1, im2) + + +@pytest.mark.parametrize("protocol", list(range(0, pickle.HIGHEST_PROTOCOL + 1))) +def test_pickle_font_string(protocol): + # Arrange + font = ImageFont.truetype(FONT_PATH, FONT_SIZE) + + # Act: roundtrip + pickled_font = pickle.dumps(font, protocol) + unpickled_font = pickle.loads(pickled_font) + + # Assert + helper_assert_pickled_font_images(font, unpickled_font) + + +@pytest.mark.parametrize("protocol", list(range(0, pickle.HIGHEST_PROTOCOL + 1))) +def test_pickle_font_file(tmp_path, protocol): + # Arrange + font = ImageFont.truetype(FONT_PATH, FONT_SIZE) + filename = str(tmp_path / "temp.pkl") + + # Act: roundtrip + with open(filename, "wb") as f: + pickle.dump(font, f, protocol) + with open(filename, "rb") as f: + unpickled_font = pickle.load(f) + + # Assert + helper_assert_pickled_font_images(font, unpickled_font) diff --git a/Tests/test_psdraw.py b/Tests/test_psdraw.py index 31f0f493b25..e74d798282a 100644 --- a/Tests/test_psdraw.py +++ b/Tests/test_psdraw.py @@ -1,6 +1,8 @@ import os import sys -from io import StringIO +from io import BytesIO + +import pytest from PIL import Image, PSDraw @@ -44,10 +46,21 @@ def test_draw_postscript(tmp_path): assert os.path.getsize(tempfile) > 0 -def test_stdout(): +@pytest.mark.parametrize("buffer", (True, False)) +def test_stdout(buffer): # Temporarily redirect stdout old_stdout = sys.stdout - sys.stdout = mystdout = StringIO() + + if buffer: + + class MyStdOut: + buffer = BytesIO() + + mystdout = MyStdOut() + else: + mystdout = BytesIO() + + sys.stdout = mystdout ps = PSDraw.PSDraw() _create_document(ps) @@ -55,4 +68,6 @@ def test_stdout(): # Reset stdout sys.stdout = old_stdout - assert mystdout.getvalue() != "" + if buffer: + mystdout = mystdout.buffer + assert mystdout.getvalue() != b"" diff --git a/Tests/test_pyroma.py b/Tests/test_pyroma.py index f4302350d70..aa05c2cfdd8 100644 --- a/Tests/test_pyroma.py +++ b/Tests/test_pyroma.py @@ -1,4 +1,5 @@ import pytest + from PIL import __version__ pyroma = pytest.importorskip("pyroma", reason="Pyroma not installed") diff --git a/Tests/test_qt_image_fromqpixmap.py b/Tests/test_qt_image_fromqpixmap.py deleted file mode 100644 index cb1b385ec8b..00000000000 --- a/Tests/test_qt_image_fromqpixmap.py +++ /dev/null @@ -1,15 +0,0 @@ -from PIL import ImageQt - -from .helper import assert_image_equal, hopper -from .test_imageqt import PillowQPixmapTestCase - - -class TestFromQPixmap(PillowQPixmapTestCase): - 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 00000000000..dec790c5069 --- /dev/null +++ b/Tests/test_qt_image_qapplication.py @@ -0,0 +1,88 @@ +import pytest + +from PIL import ImageQt + +from .helper import assert_image_equal, assert_image_equal_tofile, hopper + +if ImageQt.qt_is_installed: + from PIL.ImageQt import QPixmap + + if ImageQt.qt_version == "6": + from PyQt6.QtCore import QPoint + from PyQt6.QtGui import QImage, QPainter, QRegion + from PyQt6.QtWidgets import QApplication, QHBoxLayout, QLabel, QWidget + elif ImageQt.qt_version == "side6": + from PySide6.QtCore import QPoint + from PySide6.QtGui import QImage, QPainter, QRegion + from PySide6.QtWidgets import QApplication, QHBoxLayout, QLabel, QWidget + elif ImageQt.qt_version == "5": + from PyQt5.QtCore import QPoint + from PyQt5.QtGui import QImage, QPainter, QRegion + from PyQt5.QtWidgets import QApplication, QHBoxLayout, QLabel, QWidget + elif ImageQt.qt_version == "side2": + from PySide2.QtCore import QPoint + from PySide2.QtGui import QImage, QPainter, QRegion + 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 + im = hopper(mode) + data = ImageQt.toqpixmap(im) + + assert isinstance(data, QPixmap) + assert not data.isNull() + + # Test saving the file + tempfile = str(tmp_path / f"temp_{mode}.png") + data.save(tempfile) + + # Render the image + qimage = ImageQt.ImageQt(im) + data = QPixmap.fromImage(qimage) + qt_format = QImage.Format if ImageQt.qt_version == "6" else QImage + qimage = QImage(128, 128, qt_format.Format_ARGB32) + painter = QPainter(qimage) + image_label = QLabel() + image_label.setPixmap(data) + image_label.render(painter, QPoint(0, 0), QRegion(0, 0, 128, 128)) + painter.end() + rendered_tempfile = str(tmp_path / f"temp_rendered_{mode}.png") + qimage.save(rendered_tempfile) + assert_image_equal_tofile(im.convert("RGBA"), rendered_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 4c98bf0b498..2a6b29abebc 100644 --- a/Tests/test_qt_image_toqimage.py +++ b/Tests/test_qt_image_toqimage.py @@ -1,7 +1,8 @@ import pytest -from PIL import Image, ImageQt -from .helper import assert_image_equal, hopper +from PIL import ImageQt + +from .helper import assert_image_equal, assert_image_equal_tofile, hopper pytestmark = pytest.mark.skipif( not ImageQt.qt_is_installed, reason="Qt bindings are not installed" @@ -10,13 +11,6 @@ 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"): @@ -42,35 +36,8 @@ def test_sanity(tmp_path): continue # Test saving the file - tempfile = str(tmp_path / "temp_{}.png".format(mode)) + tempfile = str(tmp_path / f"temp_{mode}.png") data.save(tempfile) # Check that it actually worked. - with Image.open(tempfile) as reloaded: - assert_image_equal(reloaded, src) - - -def test_segfault(): - 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()) + assert_image_equal_tofile(src, tempfile) diff --git a/Tests/test_qt_image_toqpixmap.py b/Tests/test_qt_image_toqpixmap.py deleted file mode 100644 index af281da697d..00000000000 --- a/Tests/test_qt_image_toqpixmap.py +++ /dev/null @@ -1,20 +0,0 @@ -from PIL import ImageQt - -from .helper import hopper -from .test_imageqt import PillowQPixmapTestCase - -if ImageQt.qt_is_installed: - from PIL.ImageQt import QPixmap - - -class TestToQPixmap(PillowQPixmapTestCase): - def test_sanity(self, tmp_path): - for mode in ("1", "RGB", "RGBA", "L", "P"): - data = ImageQt.toqpixmap(hopper(mode)) - - assert isinstance(data, QPixmap) - assert not data.isNull() - - # Test saving the file - tempfile = str(tmp_path / "temp_{}.png".format(mode)) - data.save(tempfile) diff --git a/Tests/test_sgi_crash.py b/Tests/test_sgi_crash.py index 6f3fc6f5d13..b5f9d442490 100644 --- a/Tests/test_sgi_crash.py +++ b/Tests/test_sgi_crash.py @@ -1,14 +1,26 @@ -#!/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/sgi_overrun_expandrowF04.bin", + "Tests/images/sgi_crash.bin", + "Tests/images/crash-6b7f2244da6d0ae297ee0754a424213444e92778.sgi", + "Tests/images/ossfuzz-5730089102868480.sgi", + "Tests/images/crash-754d9c7ec485ffb76a90eeaab191ef69a2a3a3cd.sgi", + "Tests/images/crash-465703f71a0f0094873a3e0e82c9f798161171b8.sgi", + "Tests/images/crash-64834657ee604b8797bf99eac6a194c124a9a8ba.sgi", + "Tests/images/crash-abcf1c97b8fe42a6c68f1fb0b978530c98d57ced.sgi", + "Tests/images/crash-b82e64d4f3f76d7465b6af535283029eda211259.sgi", + "Tests/images/crash-c1b2595b8b0b92cc5f38b6635e98e3a119ade807.sgi", + "Tests/images/crash-db8bfa78b19721225425530c5946217720d7df4e.sgi", + ], ) def test_crashes(test_file): with open(test_file, "rb") as f: - im = Image.open(f) - with pytest.raises(IOError): - im.load() + with Image.open(f) as im: + with pytest.raises(OSError): + im.load() diff --git a/Tests/test_shell_injection.py b/Tests/test_shell_injection.py index 45c60fa107d..d25d42dfca3 100644 --- a/Tests/test_shell_injection.py +++ b/Tests/test_shell_injection.py @@ -1,6 +1,7 @@ import shutil import pytest + from PIL import GifImagePlugin, Image, JpegImagePlugin from .helper import cjpeg_available, djpeg_available, is_win32, netpbm_available diff --git a/Tests/test_tiff_crashes.py b/Tests/test_tiff_crashes.py new file mode 100644 index 00000000000..143765b8eec --- /dev/null +++ b/Tests/test_tiff_crashes.py @@ -0,0 +1,54 @@ +# 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", + "Tests/images/crash-0c7e0e8e11ce787078f00b5b0ca409a167f070e0.tif", + "Tests/images/crash-0e16d3bfb83be87356d026d66919deaefca44dac.tif", + "Tests/images/crash-1152ec2d1a1a71395b6f2ce6721c38924d025bf3.tif", + "Tests/images/crash-1185209cf7655b5aed8ae5e77784dfdd18ab59e9.tif", + "Tests/images/crash-338516dbd2f0e83caddb8ce256c22db3bd6dc40f.tif", + "Tests/images/crash-4f085cc12ece8cde18758d42608bed6a2a2cfb1c.tif", + "Tests/images/crash-86214e58da443d2b80820cff9677a38a33dcbbca.tif", + "Tests/images/crash-f46f5b2f43c370fe65706c11449f567ecc345e74.tif", + "Tests/images/crash-63b1dffefc8c075ddc606c0a2f5fdc15ece78863.tif", + "Tests/images/crash-74d2a78403a5a59db1fb0a2b8735ac068a75f6e3.tif", + "Tests/images/crash-81154a65438ba5aaeca73fd502fa4850fbde60f8.tif", + "Tests/images/crash-0da013a13571cc8eb457a39fee8db18f8a3c7127.tif", + ], +) +@pytest.mark.filterwarnings("ignore:Possibly corrupt EXIF data") +@pytest.mark.filterwarnings("ignore:Metadata warning") +@pytest.mark.filterwarnings("ignore:Truncated File Read") +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 707284d7b4f..12f475df036 100644 --- a/Tests/test_tiff_ifdrational.py +++ b/Tests/test_tiff_ifdrational.py @@ -28,6 +28,14 @@ def test_sanity(): _test_equal(1, 2, Fraction(1, 2)) _test_equal(1, 2, IFDRational(1, 2)) + _test_equal(7, 5, 1.4) + + +def test_ranges(): + for num in range(1, 10): + for denom in range(1, 10): + assert IFDRational(num, denom) == IFDRational(num, denom) + def test_nonetype(): # Fails if the _delegate function doesn't return a valid function diff --git a/Tests/test_util.py b/Tests/test_util.py index 3d88b947216..b5bfca0126f 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/depends/README.rst b/depends/README.rst index ce88fa47ba2..b69c9dcbf8f 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 a0b462b56d7..00000000000 --- 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 b007494e922..00000000000 --- 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 36af34b54c3..02da12d61a4 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 1f2b677fde9..774f2676750 100755 --- a/depends/install_imagequant.sh +++ b/depends/install_imagequant.sh @@ -1,9 +1,9 @@ #!/bin/bash # install libimagequant -archive=libimagequant-2.12.6 +archive=libimagequant-2.17.0 -./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/master/$archive.tar.gz +./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz pushd $archive diff --git a/depends/install_openjpeg.sh b/depends/install_openjpeg.sh index a9349828288..914e71e5396 100755 --- a/depends/install_openjpeg.sh +++ b/depends/install_openjpeg.sh @@ -1,9 +1,9 @@ #!/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 +./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz pushd $archive diff --git a/depends/install_raqm.sh b/depends/install_raqm.sh index 38a5bfd5264..3105465ec40 100755 --- a/depends/install_raqm.sh +++ b/depends/install_raqm.sh @@ -2,9 +2,9 @@ # 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 +./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz pushd $archive diff --git a/depends/install_raqm_cmake.sh b/depends/install_raqm_cmake.sh index c0dcd93b735..7d2c399df5d 100755 --- a/depends/install_raqm_cmake.sh +++ b/depends/install_raqm_cmake.sh @@ -4,7 +4,7 @@ archive=raqm-cmake-99300ff3 -./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/master/$archive.tar.gz +./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz pushd $archive diff --git a/depends/install_webp.sh b/depends/install_webp.sh index 9b1882c43d5..8a9c968045c 100755 --- a/depends/install_webp.sh +++ b/depends/install_webp.sh @@ -1,9 +1,9 @@ #!/bin/bash # install webp -archive=libwebp-1.1.0 +archive=libwebp-1.2.1 -./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/master/$archive.tar.gz +./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz pushd $archive diff --git a/docs/COPYING b/docs/COPYING index ec2a5d8cbf3..25f03b34312 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-2022 by Alex Clark and contributors Like PIL, Pillow is licensed under the open source PIL Software License: diff --git a/docs/Guardfile b/docs/Guardfile index 16f7316114e..b689b079aea 100755 --- a/docs/Guardfile +++ b/docs/Guardfile @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 from livereload.compiler import shell from livereload.task import Task diff --git a/docs/Makefile b/docs/Makefile index d7c20bca4c1..686f0119e37 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 fe69fed620a..fa036b9ccfe 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 ce6537e148a..96885d08db0 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 d3431810020..7bbe8c4c96f 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -16,18 +16,29 @@ # 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_copybutton", + "sphinx_issues", + "sphinx_removed_in", + "sphinx.ext.autodoc", + "sphinx.ext.intersphinx", + "sphinx.ext.viewcode", + "sphinxext.opengraph", +] + +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 +53,7 @@ # 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-2022 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 +80,7 @@ # 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 +109,17 @@ # 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 +146,7 @@ # 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 +311,18 @@ def setup(app): - app.add_javascript("js/script.js") + app.add_js_file("js/script.js") + app.add_css_file("css/styles.css") + app.add_css_file("css/dark.css") + app.add_css_file("css/light.css") + + +# GitHub repo for sphinx-issues +issues_github_path = "python-pillow/Pillow" + +# sphinxext.opengraph +ogp_image = ( + "https://raw.githubusercontent.com/python-pillow/pillow-logo/main/" + "pillow-logo-dark-text-1280x640.png" +) +ogp_image_alt = "Pillow" diff --git a/docs/deprecations.rst b/docs/deprecations.rst index 227a5bc823e..ce30fdf3b92 100644 --- a/docs/deprecations.rst +++ b/docs/deprecations.rst @@ -12,36 +12,46 @@ Deprecated features Below are features which are considered deprecated. Where appropriate, a ``DeprecationWarning`` is issued. -PILLOW_VERSION constant -~~~~~~~~~~~~~~~~~~~~~~~ +Tk/Tcl 8.4 +~~~~~~~~~~ -.. deprecated:: 5.2.0 +.. deprecated:: 8.2.0 -``PILLOW_VERSION`` has been deprecated and will be removed in a future release. Use -``__version__`` instead. +Support for Tk/Tcl 8.4 is deprecated and will be removed in Pillow 10.0.0 (2023-07-01), +when Tk/Tcl 8.5 will be the minimum supported. -It was initially removed in Pillow 7.0.0, but brought back in 7.1.0 to give projects -more time to upgrade. +Categories +~~~~~~~~~~ -ImageCms.CmsProfile attributes -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.. deprecated:: 8.2.0 -.. deprecated:: 3.2.0 +``im.category`` is deprecated and will be removed in Pillow 10.0.0 (2023-07-01), +along with the related ``Image.NORMAL``, ``Image.SEQUENCE`` and +``Image.CONTAINER`` attributes. + +To determine if an image has multiple frames or not, +``getattr(im, "is_animated", False)`` can be used instead. + +JpegImagePlugin.convert_dict_qtables +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. deprecated:: 8.3.0 + +JPEG ``quantization`` is now automatically converted, but still returned as a +dictionary. The :py:attr:`~PIL.JpegImagePlugin.convert_dict_qtables` method no longer +performs any operations on the data given to it, has been deprecated and will be +removed in Pillow 10.0.0 (2023-07-01). + +ImagePalette size parameter +~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Some attributes in ``ImageCms.CmsProfile`` are deprecated. From 6.0.0, they issue a -``DeprecationWarning``: - -======================== =============================== -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`` -======================== =============================== +.. deprecated:: 8.4.0 + +The ``size`` parameter will be removed in Pillow 10.0.0 (2023-07-01). + +Before Pillow 8.3.0, ``ImagePalette`` required palette data of particular lengths by +default, and the size parameter could be used to override that. Pillow 8.3.0 removed +the default required length, also removing the need for the size parameter. Removed features ---------------- @@ -49,10 +59,111 @@ Removed features Deprecated features are only removed in major releases after an appropriate period of deprecation has passed. +PILLOW_VERSION constant +~~~~~~~~~~~~~~~~~~~~~~~ + +.. deprecated:: 5.2.0 +.. versionremoved:: 9.0.0 + +Use ``__version__`` instead. + +It was initially removed in Pillow 7.0.0, but temporarily brought back in 7.1.0 +to give projects more time to upgrade. + +Image.show command parameter +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. deprecated:: 7.2.0 +.. versionremoved:: 9.0.0 + +The ``command`` parameter has been removed. Use a subclass of +:py:class:`.ImageShow.Viewer` instead. + +Image._showxv +~~~~~~~~~~~~~ + +.. deprecated:: 7.2.0 +.. versionremoved:: 9.0.0 + +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 +.. versionremoved:: 9.0.0 + +``IOError`` was merged into ``OSError`` in Python 3.3. +So, ``ImageFile.raise_ioerror`` has been removed. +Use ``ImageFile.raise_oserror`` instead. + +FreeType 2.7 +~~~~~~~~~~~~ + +.. deprecated:: 8.1.0 +.. versionremoved:: 9.0.0 + +Support for FreeType 2.7 has been removed. + +We recommend upgrading to at least `FreeType`_ 2.10.4, which fixed a severe +vulnerability introduced in FreeType 2.6 (:cve:`CVE-2020-15999`). + +.. _FreeType: https://www.freetype.org + +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. @@ -60,7 +171,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 @@ -83,7 +195,8 @@ Use 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. @@ -108,7 +221,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. @@ -119,7 +233,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. @@ -127,7 +242,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. @@ -135,7 +251,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: @@ -153,9 +270,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 +PIL.OleFileIO was removed as a vendored file 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. ``python3 -m pip install olefile``). diff --git a/docs/example/DdsImagePlugin.py b/docs/example/DdsImagePlugin.py index 45f63f1647d..272409416cc 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: @@ -611,6 +659,8 @@ where applicable: 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 ~~~~~~ @@ -697,7 +747,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`` @@ -708,10 +758,10 @@ 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() + im = Image.open("image001.spi").convert2byte() Writing files in SPIDER format ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -750,7 +800,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** @@ -760,8 +810,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 @@ -772,9 +822,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. @@ -782,8 +832,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)``. @@ -795,7 +845,7 @@ Reading Multi-frame TIFF Images The TIFF loader supports the :py:meth:`~PIL.Image.Image.seek` and :py:meth:`~PIL.Image.Image.tell` methods, taking and returning frame numbers within the image file. You can combine these methods to seek to the next frame -(``im.seek(im.tell() + 1)``). Frames are numbered from 0 to ``im.num_frames - 1``, +(``im.seek(im.tell() + 1)``). Frames are numbered from 0 to ``im.n_frames - 1``, and can be accessed in any order. ``im.seek()`` raises an :py:exc:`EOFError` if you try to seek after the @@ -827,7 +877,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 @@ -844,7 +894,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 @@ -854,13 +904,18 @@ The :py:meth:`~PIL.Image.Image.save` method can take the following keyword argum require a matching type in :py:attr:`~PIL.TiffImagePlugin.ImageFileDirectory_v2.tagtype` tagtype. +**exif** + Alternate keyword to "tiffinfo", for consistency with other formats. + + .. versionadded:: 8.4.0 + **compression** A string containing the desired compression method for the file. (valid only with libtiff installed) Valid compression - methods are: ``None``, ``"tiff_ccitt"``, ``"group3"``, - ``"group4"``, ``"tiff_jpeg"``, ``"tiff_adobe_deflate"``, - ``"tiff_thunderscan"``, ``"tiff_deflate"``, ``"tiff_sgilog"``, - ``"tiff_sgilog24"``, ``"tiff_raw_16"`` + methods are: :data:`None`, ``"group3"``, ``"group4"``, ``"jpeg"``, ``"lzma"``, + ``"packbits"``, ``"tiff_adobe_deflate"``, ``"tiff_ccitt"``, ``"tiff_lzw"``, + ``"tiff_raw_16"``, ``"tiff_sgilog"``, ``"tiff_sgilog24"``, ``"tiff_thunderscan"``, + ``"webp"`, ``"zstd"`` **quality** The image quality for JPEG compression, on a scale from 0 (worst) to 100 @@ -882,6 +937,9 @@ using the general tags available through tiffinfo. **copyright** Strings +**icc_profile** + The ICC Profile to include in the saved file. + **resolution_unit** An integer. 1 for no unit, 2 for inches and 3 for centimeters. @@ -918,7 +976,7 @@ The :py:meth:`~PIL.Image.Image.save` method supports the following options: files compared to the slowest, but best, 100. **method** - Quality/speed trade-off (0=fast, 6=slower-better). Defaults to 0. + Quality/speed trade-off (0=fast, 6=slower-better). Defaults to 4. **icc_profile** The ICC Profile to include in the saved file. Only supported if @@ -937,9 +995,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 @@ -1005,23 +1064,12 @@ is commonly used in fax applications. The DCX decoder can read files containing When the file is opened, only the first image is read. You can use :py:meth:`~PIL.Image.Image.seek` or :py:mod:`~PIL.ImageSequence` to read other images. - -DDS -^^^ - -DDS is a popular container texture format used in video games and natively -supported by DirectX. -Currently, uncompressed RGB data and DXT1, DXT3, and DXT5 pixel formats are -supported, and only in ``RGBA`` mode. - -.. versionadded:: 3.4.0 DXT3 - 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** @@ -1054,7 +1102,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** @@ -1069,7 +1117,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** @@ -1154,6 +1202,7 @@ dpi. To load it at another resolution: .. code-block:: python from PIL import Image + with Image.open("drawing.wmf") as im: im.load(dpi=144) @@ -1165,15 +1214,19 @@ To add other read or write support, use from PIL import Image from PIL import WmfImagePlugin + class WmfHandler: def open(self, im): ... + def load(self, im): ... return image + def save(self, im, fp, filename): ... + wmf_handler = WmfHandler() WmfImagePlugin.register_handler(wmf_handler) @@ -1185,7 +1238,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** @@ -1219,14 +1272,16 @@ The :py:meth:`~PIL.Image.Image.save` method can take the following keyword argum .. versionadded:: 3.0.0 **append_images** - A list of images to append as additional pages. Each of the - images in the list can be single or multiframe images. + A list of :py:class:`PIL.Image.Image` objects to append as additional pages. Each + of the images in the list can be single or multiframe images. The ``save_all`` + parameter must be present and set to ``True`` in conjunction with + ``append_images``. .. versionadded:: 4.2.0 **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 00000000000..0aecd348366 --- /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 86840261578..aa9efe19261 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 @@ -76,7 +76,7 @@ Convert files to JPEG try: with Image.open(infile) as im: im.save(outfile) - except IOError: + except OSError: print("cannot convert", infile) A second argument can be supplied to the :py:meth:`~PIL.Image.Image.save` @@ -100,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 @@ -124,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 @@ -176,12 +176,13 @@ Rolling an image xsize, ysize = image.size delta = delta % xsize - if delta == 0: return image + if delta == 0: + return image part1 = image.crop((0, 0, delta, ysize)) part2 = image.crop((delta, 0, xsize, ysize)) - image.paste(part1, (xsize-delta, 0, xsize, ysize)) - image.paste(part2, (0, 0, xsize-delta, ysize)) + image.paste(part1, (xsize - delta, 0, xsize, ysize)) + image.paste(part2, (0, 0, xsize - delta, ysize)) return image @@ -264,6 +265,7 @@ Converting between modes :: from PIL import Image + with Image.open("hopper.ppm") as im: im = im.convert("L") @@ -382,14 +384,14 @@ Reading sequences from PIL import Image with Image.open("animation.gif") as im: - im.seek(1) # skip to the second frame + im.seek(1) # skip to the second frame try: while 1: - im.seek(im.tell()+1) + im.seek(im.tell() + 1) # do something to im except EOFError: - pass # end of sequence + pass # end of sequence As seen in this example, you’ll get an :py:exc:`EOFError` exception when the sequence ends. @@ -406,13 +408,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 ^^^^^^^^^^^^^^^^^^ :: @@ -422,9 +424,9 @@ Drawing Postscript with Image.open("hopper.ppm") as im: title = "hopper" - box = (1*72, 2*72, 7*72, 10*72) # in points + box = (1 * 72, 2 * 72, 7 * 72, 10 * 72) # in points - ps = PSDraw.PSDraw() # default is sys.stdout + ps = PSDraw.PSDraw() # default is sys.stdout or sys.stdout.buffer ps.begin_document(title) # draw the image (75 dpi) @@ -433,7 +435,7 @@ Drawing Postscript # draw title ps.setfont("HelveticaNarrow-Bold", 36) - ps.text((3*72, 4*72), title) + ps.text((3 * 72, 4 * 72), title) ps.end_document() @@ -450,11 +452,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 ^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -462,6 +464,7 @@ Reading from an open file :: from PIL import Image + with open("hopper.ppm", "rb") as fp: im = Image.open(fp) @@ -475,6 +478,7 @@ Reading from binary data from PIL import Image import io + im = Image.open(io.BytesIO(buffer)) Note that the library rewinds the file (using ``seek(0)``) before reading the @@ -493,6 +497,43 @@ Reading from a tar archive fp = TarIO.TarIO("Tests/images/hopper.tar", "hopper.jpg") im = Image.open(fp) + +Batch processing +^^^^^^^^^^^^^^^^ + +Operations can be applied to multiple image files. For example, all PNG images +in the current directory can be saved as JPEGs at reduced quality. + +:: + + import glob + from PIL import Image + + + def compress_image(source_path, dest_path): + with Image.open(source_path) as img: + if img.mode != "RGB": + img = img.convert("RGB") + img.save(dest_path, "JPEG", optimize=True, quality=80) + + + paths = glob.glob("*.png") + for path in paths: + compress_image(path, path[:-4] + ".jpg") + +Since images can also be opened from a ``Path`` from the ``pathlib`` module, +the example could be modified to use ``pathlib`` instead of the ``glob`` +module. + +:: + + from pathlib import Path + + paths = Path(".").glob("*.png") + for path in paths: + compress_image(path, path.stem + ".jpg") + + Controlling the decoder ----------------------- diff --git a/docs/handbook/writing-your-own-file-decoder.rst b/docs/handbook/writing-your-own-file-decoder.rst index 58e2bccc51c..f69da9a9441 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,19 @@ 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 +108,20 @@ 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: + +.. code-block:: python -To be able to read the file as well as just identifying it, the :py:attr:`tile` + 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 +147,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,20 +161,22 @@ 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", - raw mode, stride, orientation + raw_mode, stride, orientation ) When used in a tile descriptor, the parameter field should look like:: - (raw mode, stride, orientation) + (raw_mode, stride, orientation) The fields are used as follows: -**raw mode** +**raw_mode** The pixel layout used in the file, and is used to properly convert data to PIL’s internal layout. For a summary of the available formats, see the table below. @@ -175,43 +192,43 @@ 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 | -+===========+=================================================================+ -| ``1`` | 1-bit bilevel, stored with the leftmost pixel in the most | -| | significant bit. 0 means black, 1 means white. | -+-----------+-----------------------------------------------------------------+ -| ``1;I`` | 1-bit inverted bilevel, stored with the leftmost pixel in the | -| | most significant bit. 0 means white, 1 means black. | -+-----------+-----------------------------------------------------------------+ -| ``1;R`` | 1-bit reversed bilevel, stored with the leftmost pixel in the | -| | least significant bit. 0 means black, 1 means white. | -+-----------+-----------------------------------------------------------------+ -| ``L`` | 8-bit greyscale. 0 means black, 255 means white. | -+-----------+-----------------------------------------------------------------+ -| ``L;I`` | 8-bit inverted greyscale. 0 means white, 255 means black. | -+-----------+-----------------------------------------------------------------+ -| ``P`` | 8-bit palette-mapped image. | -+-----------+-----------------------------------------------------------------+ -| ``RGB`` | 24-bit true colour, stored as (red, green, blue). | -+-----------+-----------------------------------------------------------------+ -| ``BGR`` | 24-bit true colour, stored as (blue, green, red). | -+-----------+-----------------------------------------------------------------+ -| ``RGBX`` | 24-bit true colour, stored as (red, green, blue, pad). The pad | -| | pixels may vary. | -+-----------+-----------------------------------------------------------------+ -| ``RGB;L`` | 24-bit true colour, line interleaved (first all red pixels, then| -| | all green pixels, finally all blue pixels). | -+-----------+-----------------------------------------------------------------+ ++-----------+-------------------------------------------------------------------+ +| mode | description | ++===========+===================================================================+ +| ``1`` | | 1-bit bilevel, stored with the leftmost pixel in the most | +| | | significant bit. 0 means black, 1 means white. | ++-----------+-------------------------------------------------------------------+ +| ``1;I`` | | 1-bit inverted bilevel, stored with the leftmost pixel in the | +| | | most significant bit. 0 means white, 1 means black. | ++-----------+-------------------------------------------------------------------+ +| ``1;R`` | | 1-bit reversed bilevel, stored with the leftmost pixel in the | +| | | least significant bit. 0 means black, 1 means white. | ++-----------+-------------------------------------------------------------------+ +| ``L`` | 8-bit greyscale. 0 means black, 255 means white. | ++-----------+-------------------------------------------------------------------+ +| ``L;I`` | 8-bit inverted greyscale. 0 means white, 255 means black. | ++-----------+-------------------------------------------------------------------+ +| ``P`` | 8-bit palette-mapped image. | ++-----------+-------------------------------------------------------------------+ +| ``RGB`` | 24-bit true colour, stored as (red, green, blue). | ++-----------+-------------------------------------------------------------------+ +| ``BGR`` | 24-bit true colour, stored as (blue, green, red). | ++-----------+-------------------------------------------------------------------+ +| ``RGBX`` | | 24-bit true colour, stored as (red, green, blue, pad). The pad | +| | | pixels may vary. | ++-----------+-------------------------------------------------------------------+ +| ``RGB;L`` | | 24-bit true colour, line interleaved (first all red pixels, then| +| | | all green pixels, finally all blue pixels). | ++-----------+-------------------------------------------------------------------+ 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 +240,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. @@ -256,7 +273,9 @@ decoder that can be used to read various packed formats into a floating point image memory. To use the bit decoder with the :py:func:`PIL.Image.frombytes` function, use -the following syntax:: +the following syntax: + +.. code-block:: python image = Image.frombytes( mode, size, data, "bit", @@ -350,7 +369,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 +433,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 4f16ba960bb..0e16259f309 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -9,18 +9,6 @@ Pillow for enterprise is available via the Tidelift Subscription. `Learn more = 1.0 no longer supports "import Image". Please use "from PIL import Image" instead. - -.. warning:: Pillow >= 2.1.0 no longer supports "import _imaging". Please use "from PIL.Image import core as _imaging" instead. - -Notes ------ - -.. note:: Pillow is supported on the following 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 | -+--------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ +.. warning:: Pillow >= 1.0 no longer supports ``import Image``. Please use ``from PIL import Image`` instead. + +.. warning:: Pillow >= 2.1.0 no longer supports ``import _imaging``. Please use ``from PIL.Image import core as _imaging`` instead. + +Python Support +-------------- + +Pillow supports these Python versions. + ++----------------------+-----+-----+-----+-----+-----+-----+-----+-----+ +| Python |3.10 | 3.9 | 3.8 | 3.7 | 3.6 | 3.5 | 3.4 | 2.7 | ++======================+=====+=====+=====+=====+=====+=====+=====+=====+ +| Pillow >= 9.0 | Yes | Yes | Yes | Yes | | | | | ++----------------------+-----+-----+-----+-----+-----+-----+-----+-----+ +| Pillow 8.3.2 - 8.4 | Yes | Yes | Yes | Yes | Yes | | | | ++----------------------+-----+-----+-----+-----+-----+-----+-----+-----+ +| Pillow 8.0 - 8.3.1 | | 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 | ++----------------------+-----+-----+-----+-----+-----+-----+-----+-----+ + ++------------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+ +| Python | 3.6 | 3.5 | 3.4 | 3.3 | 3.2 | 2.7 | 2.6 | 2.5 | 2.4 | ++==================+=====+=====+=====+=====+=====+=====+=====+=====+=====+ +| 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 ------------------ @@ -56,20 +65,23 @@ Windows Installation We provide Pillow binaries for Windows compiled for the matrix of 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:: +These binaries include support for all optional libraries except +libimagequant and libxcb. Raqm support requires +FriBiDi to be installed separately:: 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 ^^^^^^^^^^^^^^^^^^ 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 and libxcb. Raqm support requires -libraqm, fribidi, and harfbuzz to be installed separately:: +libraries except libimagequant. Raqm support requires +FriBiDi to be installed separately:: python3 -m pip install --upgrade pip python3 -m pip install --upgrade Pillow @@ -80,14 +92,15 @@ Linux Installation We provide binaries for Linux for each of the supported Python 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:: +FriBiDi to be installed separately:: 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 -PIL e.g. ``python-imaging``. +Most major Linux distributions, including Fedora, Ubuntu and ArchLinux +also include Pillow in packages that previously contained PIL e.g. +``python-imaging``. Debian splits it into two packages, ``python3-pil`` +and ``python3-pil.imagetk``. FreeBSD Installation ^^^^^^^^^^^^^^^^^^^^ @@ -100,7 +113,7 @@ Pillow can be installed on FreeBSD via the official Ports or Packages systems: **Packages**:: - pkg install py36-pillow + pkg install py38-pillow .. note:: @@ -149,14 +162,14 @@ Many of Pillow's features require external libraries: * **libtiff** provides compressed TIFF functionality - * Pillow has been tested with libtiff versions **3.x** and **4.0-4.1** + * Pillow has been tested with libtiff versions **3.x** and **4.0-4.3** * **libfreetype** provides type related services * **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.12**. * **libwebp** provides the WebP format. @@ -168,13 +181,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.17.0** * Libimagequant is licensed GPLv3, which is more restrictive than the Pillow license, therefore we will not be distributing binaries with libimagequant support enabled. @@ -187,11 +200,15 @@ Many of Pillow's features require external libraries: * libraqm depends on the following libraries: FreeType, HarfBuzz, FriBiDi, make sure that you install them before installing libraqm if not available as package in your system. - * setting text direction or font features is not supported without - libraqm. - * libraqm is dynamically loaded in Pillow 5.0.0 and above, so support - is available if all the libraries are installed. - * Windows support: Raqm is not included in prebuilt wheels + * Setting text direction or font features is not supported without libraqm. + * Pillow wheels since version 8.2.0 include a modified version of libraqm that + loads libfribidi at runtime if it is installed. + On Windows this requires compiling FriBiDi and installing ``fribidi.dll`` + into a directory listed in the `Dynamic-Link Library Search Order (Microsoft Docs) + `_ + (``fribidi-0.dll`` or ``libfribidi-0.dll`` are also detected). + See `Build Options`_ to see how to build this version. + * Previous versions of Pillow (5.0.0 to 8.1.2) linked libraqm dynamically at runtime. * **libxcb** provides X11 screengrab support. @@ -240,6 +257,12 @@ Build Options an exception if the libraries are not found. Webpmux (WebP metadata) relies on WebP support. Tcl and Tk also must be used together. +* Build flags: ``--vendor-raqm --vendor-fribidi`` + These flags are used to compile a modified version of libraqm and + a shim that dynamically loads libfribidi at runtime. These are + used to compile the standard Pillow wheels. Compiling libraqm requires + a C99-compliant compiler. + * Build flag: ``--disable-platform-guessing``. Skips all of the platform dependent guessing of include and library directories for automated build systems that configure the proper paths in the @@ -252,10 +275,6 @@ Build Options Sample usage:: - MAX_CONCURRENCY=1 python3 setup.py build_ext --enable-[feature] install - -or using pip:: - python3 -m pip install --upgrade Pillow --global-option="build_ext" --global-option="--enable-[feature]" @@ -287,14 +306,51 @@ Now install Pillow with:: or from within the uncompressed source directory:: - python3 setup.py install + python3 -m pip 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 ^^^^^^^^^^^^^^^^^^^ @@ -326,9 +382,13 @@ In Fedora, the command is:: sudo dnf install python3-devel redhat-rpm-config +In Alpine, the command is:: + + sudo apk add python3-dev py3-setuptools + .. 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 \ @@ -336,15 +396,21 @@ Prerequisites are installed on **Ubuntu 16.04 LTS** with:: 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 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. +Prerequisites are installed for **Alpine** with:: + + sudo apk add tiff-dev jpeg-dev openjpeg-dev zlib-dev freetype-dev lcms2-dev \ + libwebp-dev tcl-dev tk-dev harfbuzz-dev fribidi-dev libimagequant-dev \ + libxcb-dev libpng-dev + See also the ``Dockerfile``\s in the Test Infrastructure repo (https://github.com/python-pillow/docker-images) for a known working install process for other tested distros. @@ -375,43 +441,42 @@ Continuous Integration Targets These platforms are built and tested for every change. -+----------------------------------+--------------------------+-----------------------+ -|**Operating system** |**Tested Python versions**|**Tested architecture**| -+----------------------------------+--------------------------+-----------------------+ -| Alpine | 3.8 |x86-64 | -+----------------------------------+--------------------------+-----------------------+ -| Arch | 3.8 |x86-64 | -+----------------------------------+--------------------------+-----------------------+ -| Amazon Linux 1 | 3.6 |x86-64 | -+----------------------------------+--------------------------+-----------------------+ -| Amazon Linux 2 | 3.7 |x86-64 | -+----------------------------------+--------------------------+-----------------------+ -| CentOS 6 | 3.6 |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 31 | 3.7 |x86-64 | -+----------------------------------+--------------------------+-----------------------+ -| macOS 10.15 Catalina | 3.5, 3.6, 3.7, 3.8, PyPy3|x86-64 | -+----------------------------------+--------------------------+-----------------------+ -| Ubuntu Linux 16.04 LTS | 3.5, 3.6, 3.7, 3.8, PyPy3|x86-64 | -+----------------------------------+--------------------------+-----------------------+ -| Windows Server 2012 R2 | 3.5, 3.8 |x86, x86-64 | -| +--------------------------+-----------------------+ -| | PyPy3, 3.7/MinGW |x86 | -+----------------------------------+--------------------------+-----------------------+ -| Windows Server 2019 | 3.5, 3.6, 3.7, 3.8 |x86, x86-64 | -| +--------------------------+-----------------------+ -| | PyPy3 |x86 | -+----------------------------------+--------------------------+-----------------------+ ++----------------------------------+----------------------------+---------------------+ +| Operating system | Tested Python versions | Tested architecture | ++==================================+============================+=====================+ +| Alpine | 3.9 | x86-64 | ++----------------------------------+----------------------------+---------------------+ +| Amazon Linux 2 | 3.7 | x86-64 | ++----------------------------------+----------------------------+---------------------+ +| Arch | 3.9 | x86-64 | ++----------------------------------+----------------------------+---------------------+ +| CentOS 7 | 3.9 | x86-64 | ++----------------------------------+----------------------------+---------------------+ +| CentOS 8 | 3.9 | x86-64 | ++----------------------------------+----------------------------+---------------------+ +| CentOS Stream 8 | 3.9 | x86-64 | ++----------------------------------+----------------------------+---------------------+ +| Debian 10 Buster | 3.7 | x86 | ++----------------------------------+----------------------------+---------------------+ +| Fedora 34 | 3.9 | x86-64 | ++----------------------------------+----------------------------+---------------------+ +| Fedora 35 | 3.10 | x86-64 | ++----------------------------------+----------------------------+---------------------+ +| macOS 10.15 Catalina | 3.7, 3.8, 3.9, 3.10, PyPy3 | x86-64 | ++----------------------------------+----------------------------+---------------------+ +| Ubuntu Linux 18.04 LTS (Bionic) | 3.9 | x86-64 | ++----------------------------------+----------------------------+---------------------+ +| Ubuntu Linux 20.04 LTS (Focal) | 3.7, 3.8, 3.9, 3.10, PyPy3 | x86-64 | +| +----------------------------+---------------------+ +| | 3.8 | arm64v8, ppc64le, | +| | | s390x | ++----------------------------------+----------------------------+---------------------+ +| Windows Server 2016 | 3.7 | x86-64 | ++----------------------------------+----------------------------+---------------------+ +| Windows Server 2019 | 3.7, 3.8, 3.9, 3.10, PyPy3 | x86, x86-64 | +| +----------------------------+---------------------+ +| | 3.9/MinGW | x86, x86-64 | ++----------------------------------+----------------------------+---------------------+ Other Platforms @@ -424,64 +489,79 @@ These platforms have been reported to work at the versions mentioned. Contributors please test Pillow on your platform then update this document and send a pull request. -+----------------------------------+------------------------------+--------------------------------+-----------------------+ -|**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 10.14 Mojave | 2.7, 3.5, 3.6, 3.7 | 6.0.0 |x86-64 | -| +------------------------------+--------------------------------+ + -| | 3.4 | 5.4.1 | | -+----------------------------------+------------------------------+--------------------------------+-----------------------+ -| macOS 10.13 High Sierra | 2.7, 3.4, 3.5, 3.6 | 4.2.1 |x86-64 | -+----------------------------------+------------------------------+--------------------------------+-----------------------+ -| macOS 10.12 Sierra | 2.7, 3.4, 3.5, 3.6 | 4.1.1 |x86-64 | -+----------------------------------+------------------------------+--------------------------------+-----------------------+ -| Mac OS X 10.11 El Capitan | 2.7, 3.4, 3.5, 3.6, 3.7 | 5.4.1 |x86-64 | -| +------------------------------+--------------------------------+ + -| | 3.3 | 4.1.0 | | -+----------------------------------+------------------------------+--------------------------------+-----------------------+ -| Mac OS X 10.9 Mavericks | 2.7, 3.2, 3.3, 3.4 | 3.0.0 |x86-64 | -+----------------------------------+------------------------------+--------------------------------+-----------------------+ -| Mac OS X 10.8 Mountain Lion | 2.6, 2.7, 3.2, 3.3 | |x86-64 | -+----------------------------------+------------------------------+--------------------------------+-----------------------+ -| Redhat Linux 6 | 2.6 | |x86 | -+----------------------------------+------------------------------+--------------------------------+-----------------------+ -| CentOS 6.3 | 2.7, 3.3 | |x86 | -+----------------------------------+------------------------------+--------------------------------+-----------------------+ -| 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 | -| | 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 | -+----------------------------------+------------------------------+--------------------------------+-----------------------+ -| Debian 8.2 Jessie | 2.7, 3.4 | 3.1.0 |x86-64 | -+----------------------------------+------------------------------+--------------------------------+-----------------------+ -| Raspbian Jessie | 2.7, 3.4 | 3.1.0 |arm | -+----------------------------------+------------------------------+--------------------------------+-----------------------+ -| Raspbian Stretch | 2.7, 3.5 | 4.0.0 |arm | -+----------------------------------+------------------------------+--------------------------------+-----------------------+ -| Gentoo Linux | 2.7, 3.2 | 2.1.0 |x86-64 | -+----------------------------------+------------------------------+--------------------------------+-----------------------+ -| FreeBSD 11.1 | 2.7, 3.4, 3.5, 3.6 | 4.3.0 |x86-64 | -+----------------------------------+------------------------------+--------------------------------+-----------------------+ -| FreeBSD 10.3 | 2.7, 3.4, 3.5 | 4.2.0 |x86-64 | -+----------------------------------+------------------------------+--------------------------------+-----------------------+ -| FreeBSD 10.2 | 2.7, 3.4 | 3.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 Server 2008 R2 Enterprise| 3.3 | |x86-64 | -+----------------------------------+------------------------------+--------------------------------+-----------------------+ ++----------------------------------+---------------------------+------------------+--------------+ +| Operating system | | Tested Python | | Latest tested | | Tested | +| | | versions | | Pillow version | | processors | ++==================================+===========================+==================+==============+ +| macOS 11.0 Big Sur | 3.7, 3.8, 3.9, 3.10 | 8.4.0 |arm | +| +---------------------------+------------------+--------------+ +| | 3.6, 3.7, 3.8, 3.9, 3.10 | 8.4.0 |x86-64 | ++----------------------------------+---------------------------+------------------+--------------+ +| macOS 10.15 Catalina | 3.6, 3.7, 3.8, 3.9 | 8.3.2 |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 | | ++----------------------------------+---------------------------+------------------+--------------+ +| macOS 10.13 High Sierra | 2.7, 3.4, 3.5, 3.6 | 4.2.1 |x86-64 | ++----------------------------------+---------------------------+------------------+--------------+ +| macOS 10.12 Sierra | 2.7, 3.4, 3.5, 3.6 | 4.1.1 |x86-64 | ++----------------------------------+---------------------------+------------------+--------------+ +| Mac OS X 10.11 El Capitan | 2.7, 3.4, 3.5, 3.6, 3.7 | 5.4.1 |x86-64 | +| +---------------------------+------------------+ | +| | 3.3 | 4.1.0 | | ++----------------------------------+---------------------------+------------------+--------------+ +| Mac OS X 10.9 Mavericks | 2.7, 3.2, 3.3, 3.4 | 3.0.0 |x86-64 | ++----------------------------------+---------------------------+------------------+--------------+ +| Mac OS X 10.8 Mountain Lion | 2.6, 2.7, 3.2, 3.3 | |x86-64 | ++----------------------------------+---------------------------+------------------+--------------+ +| Redhat Linux 6 | 2.6 | |x86 | ++----------------------------------+---------------------------+------------------+--------------+ +| CentOS 6.3 | 2.7, 3.3 | |x86 | ++----------------------------------+---------------------------+------------------+--------------+ +| Fedora 23 | 2.7, 3.4 | 3.1.0 |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 (Lucid) | 2.6 | 2.3.0 |x86,x86-64 | ++----------------------------------+---------------------------+------------------+--------------+ +| Debian 8.2 Jessie | 2.7, 3.4 | 3.1.0 |x86-64 | ++----------------------------------+---------------------------+------------------+--------------+ +| Raspbian Jessie | 2.7, 3.4 | 3.1.0 |arm | ++----------------------------------+---------------------------+------------------+--------------+ +| Raspbian Stretch | 2.7, 3.5 | 4.0.0 |arm | ++----------------------------------+---------------------------+------------------+--------------+ +| Raspberry Pi OS | 3.6, 3.7, 3.8, 3.9 | 8.2.0 |arm | +| +---------------------------+------------------+ | +| | 2.7 | 6.2.2 | | ++----------------------------------+---------------------------+------------------+--------------+ +| Gentoo Linux | 2.7, 3.2 | 2.1.0 |x86-64 | ++----------------------------------+---------------------------+------------------+--------------+ +| FreeBSD 11.1 | 2.7, 3.4, 3.5, 3.6 | 4.3.0 |x86-64 | ++----------------------------------+---------------------------+------------------+--------------+ +| FreeBSD 10.3 | 2.7, 3.4, 3.5 | 4.2.0 |x86-64 | ++----------------------------------+---------------------------+------------------+--------------+ +| 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 Professional | 3.7 | 7.0.0 |x86,x86-64 | ++----------------------------------+---------------------------+------------------+--------------+ +| Windows Server 2008 R2 Enterprise| 3.3 | |x86-64 | ++----------------------------------+---------------------------+------------------+--------------+ Old Versions ------------ diff --git a/docs/porting.rst b/docs/porting.rst index 3b14cde9d7d..2943d72fd69 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 9fc7cd13ba3..4567d4d3e79 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 8af56f6c1e0..c80b28a984b 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 @@ -22,8 +22,8 @@ Windows). .. code-block:: python from PIL import Image - im = Image.open("bride.jpg") - im.rotate(45).show() + with Image.open("hopper.jpg") as im: + im.rotate(45).show() Create thumbnails ^^^^^^^^^^^^^^^^^ @@ -40,9 +40,9 @@ current directory preserving aspect ratios with 128x128 max resolution. for infile in glob.glob("*.jpg"): file, ext = os.path.splitext(infile) - im = Image.open(infile) - im.thumbnail(size) - im.save(file + ".thumbnail", "JPEG") + with Image.open(infile) as im: + im.thumbnail(size) + im.save(file + ".thumbnail", "JPEG") Functions --------- @@ -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 + 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`. + + 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. + ``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 + .. _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 --------------- @@ -128,18 +145,20 @@ This crops the input image with the provided coordinates: from PIL import Image - im = Image.open("hopper.jpg") + with Image.open("hopper.jpg") as im: - # The crop method from the Image module takes four coordinates as input. - # The right can also be represented as (left+width) - # and lower can be represented as (upper+height). - (left, upper, right, lower) = (20, 20, 100, 100) + # The crop method from the Image module takes four coordinates as input. + # The right can also be represented as (left+width) + # and lower can be represented as (upper+height). + (left, upper, right, lower) = (20, 20, 100, 100) - # Here the image "im" is cropped and assigned to new variable im_crop - im_crop = im.crop((left, upper, right, lower)) + # Here the image "im" is cropped and assigned to new variable im_crop + im_crop = im.crop((left, upper, right, lower)) .. automethod:: PIL.Image.Image.draft +.. automethod:: PIL.Image.Image.effect_spread +.. automethod:: PIL.Image.Image.entropy .. automethod:: PIL.Image.Image.filter This blurs the input image using a filter from the ``ImageFilter`` module: @@ -148,11 +167,12 @@ This blurs the input image using a filter from the ``ImageFilter`` module: from PIL import Image, ImageFilter - im = Image.open("hopper.jpg") + with Image.open("hopper.jpg") as im: - # Blur the input image using the filter ImageFilter.BLUR - im_blurred = im.filter(filter=ImageFilter.BLUR) + # Blur the input image using the filter ImageFilter.BLUR + im_blurred = im.filter(filter=ImageFilter.BLUR) +.. automethod:: PIL.Image.Image.frombytes .. automethod:: PIL.Image.Image.getbands This helps to get the bands of the input image: @@ -161,8 +181,8 @@ This helps to get the bands of the input image: from PIL import Image - im = Image.open("hopper.jpg") - print(im.getbands()) # Returns ('R', 'G', 'B') + with Image.open("hopper.jpg") as im: + print(im.getbands()) # Returns ('R', 'G', 'B') .. automethod:: PIL.Image.Image.getbbox @@ -172,17 +192,19 @@ This helps to get the bounding box coordinates of the input image: from PIL import Image - im = Image.open("hopper.jpg") - print(im.getbbox()) - # Returns four coordinates in the format (left, upper, right, lower) + with Image.open("hopper.jpg") as im: + 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)``: @@ -198,13 +222,12 @@ This resizes the given image from ``(width, height)`` to ``(width/2, height/2)`` from PIL import Image - im = Image.open("hopper.jpg") + with Image.open("hopper.jpg") as im: - # Provide the target width and height of the image - (width, height) = (im.width // 2, im.height // 2) - im_resized = im.resize((width, height)) + # Provide the target width and height of the image + (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: @@ -213,103 +236,93 @@ This rotates the input image by ``theta`` degrees counter clockwise: from PIL import Image - im = Image.open("hopper.jpg") + with Image.open("hopper.jpg") as im: - # Rotate the image by 60 degrees counter clockwise - theta = 60 - # Angle is in degrees counter clockwise - im_rotated = im.rotate(angle=theta) + # Rotate the image by 60 degrees counter clockwise + theta = 60 + # Angle is in degrees counter clockwise + im_rotated = im.rotate(angle=theta) .. automethod:: PIL.Image.Image.save .. 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 from PIL import Image - im = Image.open("hopper.jpg") + with Image.open("hopper.jpg") as im: - # Flip the image from left to right - im_flipped = im.transpose(method=Image.FLIP_LEFT_RIGHT) - # To flip the image from top to bottom, - # use the method "Image.FLIP_TOP_BOTTOM" + # Flip the image from left to right + im_flipped = im.transpose(method=Image.FLIP_LEFT_RIGHT) + # To flip the image from top to bottom, + # use the method "Image.FLIP_TOP_BOTTOM" .. 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``. - - :type: :py:class:`string` or ``None`` + image), this attribute is set to :data:`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``. - - :type: :py:class:`~PIL.ImagePalette.ImagePalette` or ``None`` + Otherwise, it should be set to :data:`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,171 @@ 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. Default method, except for RGBA images. This method does not support + RGBA images. + +.. data:: MAXCOVERAGE + + Maximum coverage. This method does not support RGBA images. + +.. data:: FASTOCTREE + + Fast octree. Default method for RGBA images. + +.. data:: LIBIMAGEQUANT + + libimagequant + + Check support using :py:func:`PIL.features.check_feature` + with ``feature="libimagequant"``. diff --git a/docs/reference/ImageChops.rst b/docs/reference/ImageChops.rst index fb742254903..9519361a7e6 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”). @@ -39,12 +39,7 @@ operations in this module). .. autofunction:: PIL.ImageChops.soft_light .. autofunction:: PIL.ImageChops.hard_light .. autofunction:: PIL.ImageChops.overlay -.. 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.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 922e1685a13..f938e63a0bc 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 187306f1b77..20237eccf78 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. @@ -16,8 +16,11 @@ Color Names The ImageColor module supports the following string formats: -* Hexadecimal color specifiers, given as ``#rgb`` or ``#rrggbb``. For example, - ``#ff0000`` specifies pure red. +* Hexadecimal color specifiers, given as ``#rgb``, ``#rgba``, ``#rrggbb`` or + ``#rrggbbaa``, where ``r`` is red, ``g`` is green, ``b`` is blue and ``a`` is + alpha (also called 'opacity'). For example, ``#ff0000`` specifies pure red, + and ``#ff0000cc`` specifies red with 80% opacity (``cc`` is 204 in decimal + form, and 204 / 255 = 0.8). * RGB functions, given as ``rgb(red, green, blue)`` where the color values are integers in the range 0 to 255. Alternatively, the color values can be given diff --git a/docs/reference/ImageDraw.rst b/docs/reference/ImageDraw.rst index d888913c34a..b95d8d591a7 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. @@ -18,6 +18,7 @@ Example: Draw a gray cross over an image .. code-block:: python + import sys from PIL import Image, ImageDraw with Image.open("hopper.jpg") as im: @@ -80,32 +81,52 @@ Example: Draw Partial Opacity Text .. code-block:: python from PIL import Image, ImageDraw, ImageFont + # get an image - base = Image.open('Pillow/Tests/images/hopper.png').convert('RGBA') + with Image.open("Pillow/Tests/images/hopper.png").convert("RGBA") as base: + + # make a blank image for the text, initialized to transparent text color + txt = Image.new("RGBA", base.size, (255, 255, 255, 0)) + + # get a font + fnt = ImageFont.truetype("Pillow/Tests/fonts/FreeMono.ttf", 40) + # get a drawing context + d = ImageDraw.Draw(txt) + + # draw text, half opacity + d.text((10, 10), "Hello", font=fnt, fill=(255, 255, 255, 128)) + # draw text, full opacity + d.text((10, 60), "World", font=fnt, fill=(255, 255, 255, 255)) + + out = Image.alpha_composite(base, txt) + + out.show() + +Example: Draw Multiline Text +---------------------------- + +.. code-block:: python + + from PIL import Image, ImageDraw, ImageFont - # make a blank image for the text, initialized to transparent text color - txt = Image.new('RGBA', base.size, (255,255,255,0)) + # create an image + out = Image.new("RGB", (150, 100), (255, 255, 255)) # 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) - - # draw text, half opacity - d.text((10,10), "Hello", font=fnt, fill=(255,255,255,128)) - # draw text, full opacity - d.text((10,60), "World", font=fnt, fill=(255,255,255,255)) + d = ImageDraw.Draw(out) - out = Image.alpha_composite(base, txt) + # 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 +142,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 +164,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 +175,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=1) +.. 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 +189,7 @@ Methods .. versionadded:: 5.3.0 -.. py:method:: PIL.ImageDraw.ImageDraw.ellipse(xy, fill=None, outline=None, width=1) +.. py:method:: ImageDraw.ellipse(xy, fill=None, outline=None, width=1) Draws an ellipse inside the given bounding box. @@ -181,9 +202,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 +214,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=1) +.. 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 +235,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 +243,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, width=1) Draws a polygon. @@ -233,10 +253,29 @@ Methods :param xy: Sequence of either 2-tuples like ``[(x, y), (x, y), ...]`` or numeric values like ``[x, y, x, y, ...]``. + :param fill: Color to use for the fill. :param outline: Color to use for the outline. + :param width: The line width, in pixels. + + +.. 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:: PIL.ImageDraw.ImageDraw.rectangle(xy, fill=None, outline=None, width=1) + +.. py:method:: ImageDraw.rectangle(xy, fill=None, outline=None, width=1) Draws a rectangle. @@ -249,27 +288,53 @@ Methods .. versionadded:: 5.3.0 -.. py:method:: PIL.ImageDraw.ImageDraw.shape(shape, fill=None, outline=None) +.. py:method:: ImageDraw.rounded_rectangle(xy, radius=0, fill=None, outline=None, width=1) + + Draws a rounded rectangle. + + :param xy: Two points to define the bounding box. Sequence of either + ``[(x0, y0), (x1, y1)]`` or ``[x0, y0, x1, y1]``. The second point + is just outside the drawn rectangle. + :param radius: Radius of the corners. + :param outline: Color to use for the outline. + :param fill: Color to use for the fill. + :param width: The line width, in pixels. + + .. versionadded:: 8.2.0 + +.. 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 +342,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 +355,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 @@ -302,22 +365,37 @@ Methods .. versionadded:: 6.2.0 :param stroke_fill: Color to use for the text stroke. If not given, will default to - the ``fill`` parameter. + the ``fill`` parameter. - .. versionadded:: 6.2.0 + .. 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, CBDT, SBIX). + + .. 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: Top left corner of the text. - :param text: Text to be drawn. + :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". - :param direction: Direction of the text. It can be 'rtl' (right to - left), 'ltr' (left to right) or 'ttb' (top to bottom). + :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 @@ -325,12 +403,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 @@ -339,35 +416,56 @@ 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 -.. py:method:: PIL.ImageDraw.ImageDraw.textsize(text, font=None, spacing=4, direction=None, features=None, language=None, stroke_width=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, CBDT, SBIX). + + .. versionadded:: 8.0.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 +473,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 +482,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 +508,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 +521,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 +530,167 @@ 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, CBDT, SBIX). + +.. 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, CBDT, SBIX). + +.. 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, CBDT, SBIX). + +.. py:method:: getdraw(im=None, hints=None) .. warning:: This method is experimental. @@ -436,7 +701,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 +718,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 b172054b2e3..29ceee314cc 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 d93dfb3a3df..e0ce389e862 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 52a7d750033..c85da4fb57a 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 bb7538096b0..5f718ce19e4 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 ddd5bbbb516..ac83b225522 100644 --- a/docs/reference/ImageGrab.rst +++ b/docs/reference/ImageGrab.rst @@ -1,20 +1,18 @@ .. 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, xdisplay=None) +.. 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), 7.1.0 (Linux (X11)) @@ -28,15 +26,17 @@ or the clipboard to a PIL image memory. .. versionadded:: 6.2.0 - :param xdisplay: X11 Display address. Pass ``None`` to grab the default system screen. - Pass ``""`` to grab the default X11 screen on Windows or macOS. + :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 ca30244d1d5..63f88fddd21 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. @@ -15,11 +15,11 @@ Example: Using the :py:mod:`~PIL.ImageMath` module from PIL import Image, ImageMath - im1 = Image.open("image1.jpg") - im2 = Image.open("image2.jpg") + with Image.open("image1.jpg") as im1: + with Image.open("image2.jpg") as im2: - out = ImageMath.eval("convert(min(a, b), 'L')", a=im1, b=im2) - out.save("result.png") + out = ImageMath.eval("convert(min(a, b), 'L')", a=im1, b=im2) + out.save("result.png") .. py:function:: eval(expression, environment) @@ -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 be9d59348a8..d4522a06ae3 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 1c86d168ff0..d1c43cf6092 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. @@ -12,6 +12,7 @@ only work on L and RGB images. .. autofunction:: autocontrast .. autofunction:: colorize +.. autofunction:: contain .. autofunction:: pad .. autofunction:: crop .. autofunction:: scale diff --git a/docs/reference/ImagePalette.rst b/docs/reference/ImagePalette.rst index 15b8aed8f05..72ccfac7d83 100644 --- a/docs/reference/ImagePalette.rst +++ b/docs/reference/ImagePalette.rst @@ -1,18 +1,14 @@ .. 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:: - This module was never well-documented. It hasn't changed since 2001, - though, so it's probably safe for you to read the source code and puzzle - out the internals if you need to. - The :py:class:`~PIL.ImagePalette.ImagePalette` class has several methods, but they are all marked as "experimental." Read that as you will. The ``[source]`` link is there for a reason. diff --git a/docs/reference/ImagePath.rst b/docs/reference/ImagePath.rst index 5ab350ef381..b9bdfc50772 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 7dd7084dbfa..66f5880a37e 100644 --- a/docs/reference/ImageQt.rst +++ b/docs/reference/ImageQt.rst @@ -1,20 +1,20 @@ .. 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 -objects from PIL images. +The :py:mod:`~PIL.ImageQt` module contains support for creating PyQt6, PySide6, 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 QtGui.QImage, which means that you can pass the resulting objects directly - to PyQt5/PySide2 API functions and methods. + to PyQt6/PySide6/PyQt5/PySide2 API functions and methods. This operation is currently supported for mode 1, L, P, RGB, and RGBA images. To handle other modes, you need to convert the image first. diff --git a/docs/reference/ImageSequence.rst b/docs/reference/ImageSequence.rst index 353e8099ef4..1bfb554b653 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 @@ -17,7 +17,7 @@ Extracting frames from an animation with Image.open("animation.fli") as im: index = 1 for frame in ImageSequence.Iterator(im): - frame.save("frame%d.png" % index) + 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 00000000000..45b50c8469b --- /dev/null +++ b/docs/reference/ImageShow.rst @@ -0,0 +1,30 @@ +.. 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:: IPythonViewer +.. 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.XDGViewer + .. autoclass:: PIL.ImageShow.DisplayViewer + .. autoclass:: PIL.ImageShow.GmDisplayViewer + .. 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 32f5917c1f9..5bb73529635 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 7ee4af02980..134ef565188 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 ff3d6a7fc69..2ee3cadb70b 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 00000000000..aafae44cf4a --- /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 2b5b9b340b3..3e8512e7aa8 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 f28e58f8625..173a0bcc0e6 100644 --- a/docs/reference/PixelAccess.rst +++ b/docs/reference/PixelAccess.rst @@ -17,11 +17,12 @@ changes it. .. code-block:: python from PIL import Image - with Image.open('hopper.jpg') as im: + + with Image.open("hopper.jpg") as im: px = im.load() - print (px[4,4]) - px[4,4] = (0,0,0) - print (px[4,4]) + print(px[4, 4]) + px[4, 4] = (0, 0, 0) + print(px[4, 4]) Results in the following:: @@ -32,8 +33,8 @@ Access using negative indexes is also possible. .. code-block:: python - px[-1,-1] = (0,0,0) - print (px[-1,-1]) + px[-1, -1] = (0, 0, 0) + print(px[-1, -1]) diff --git a/docs/reference/PyAccess.rst b/docs/reference/PyAccess.rst index e00741c4323..e77944d2001 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,11 +18,12 @@ The following script loads an image, accesses one pixel from it, then changes it .. code-block:: python from PIL import Image - with Image.open('hopper.jpg') as im: + + with Image.open("hopper.jpg") as im: px = im.load() - print (px[4,4]) - px[4,4] = (0,0,0) - print (px[4,4]) + print(px[4, 4]) + px[4, 4] = (0, 0, 0) + print(px[4, 4]) Results in the following:: @@ -33,8 +34,8 @@ Access using negative indexes is also possible. .. code-block:: python - px[-1,-1] = (0,0,0) - print (px[-1,-1]) + px[-1, -1] = (0, 0, 0) + print(px[-1, -1]) diff --git a/docs/reference/TiffTags.rst b/docs/reference/TiffTags.rst index 3b261625a02..1441185dd14 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 400f236dcc5..1abe5280fbf 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/c_extension_debugging.rst b/docs/reference/c_extension_debugging.rst new file mode 100644 index 00000000000..2ba95b8a6bc --- /dev/null +++ b/docs/reference/c_extension_debugging.rst @@ -0,0 +1,470 @@ +C Extension debugging on Linux, with gbd/valgrind. +================================================== + +Install the tools +----------------- + +You need some basics in addition to the basic tools to build +pillow. These are what's required on Ubuntu, YMMV for other +distributions. + +- ``python3-dbg`` package for the gdb extensions and python symbols +- ``gdb`` and ``valgrind`` +- Potentially debug symbols for libraries. On ubuntu they're shipped + in package-dbgsym packages, from a different repo. + +:: + + deb http://ddebs.ubuntu.com focal main restricted universe multiverse + deb http://ddebs.ubuntu.com focal-updates main restricted universe multiverse + deb http://ddebs.ubuntu.com focal-proposed main restricted universe multiverse + +Then ``sudo apt-get update && sudo apt-get install libtiff5-dbgsym`` + +- There's a bug with the dbg package for at least python 3.8 on ubuntu + 20.04, and you need to add a new link or two to make it autoload when + running python: + +:: + + cd /usr/share/gdb/auto-load/usr/bin + ln -s python3.8m-gdb.py python3.8d-gdb.py + +- In Ubuntu 18.04, it's actually including the path to the virtualenv + in the search for the ``python3.*-gdb.py`` file, but you can + helpfully put in the same directory as the binary. + +- I also find that history is really useful for gdb, so I added this to + my ``~/.gdbinit`` file: + +:: + + set history filename ~/.gdb_history + set history save on + +- If the python stack isn't working in gdb, then + ``set debug auto-load`` can also be helpful in ``.gdbinit``. + +- Make a virtualenv with the debug python and activate it, then install + whatever dependencies are required and build. You want to build with + the debug python so you get symbols for your extension. + +:: + + virtualenv -p python3.8-dbg ~/vpy38-dbg + source ~/vpy38-dbg/bin/activate + cd ~/Pillow && pip install -r requirements.txt && make install + +Test Case +--------- + +Take your test image, and make a really simple harness. + +:: + + from PIL import Image + + with Image.open(path) as im: + im.load() + +- Run this through valgrind, but note that python triggers some issues + on its own, so you're looking for items within the Pillow hierarchy + that don't look like they're solely in the python call chain. In this + example, the ones we're interested are after the warnings, and have + ``decode.c`` and ``TiffDecode.c`` in the call stack: + +:: + + (vpy38-dbg) ubuntu@primary:~/Home/tests$ valgrind python test_tiff.py + ==51890== Memcheck, a memory error detector + ==51890== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al. + ==51890== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info + ==51890== Command: python test_tiff.py + ==51890== + ==51890== Invalid read of size 4 + ==51890== at 0x472E3D: address_in_range (obmalloc.c:1401) + ==51890== by 0x472EEA: pymalloc_free (obmalloc.c:1677) + ==51890== by 0x474960: _PyObject_Free (obmalloc.c:1896) + ==51890== by 0x473BAC: _PyMem_DebugRawFree (obmalloc.c:2187) + ==51890== by 0x473BD4: _PyMem_DebugFree (obmalloc.c:2318) + ==51890== by 0x474C08: PyObject_Free (obmalloc.c:709) + ==51890== by 0x45DD60: dictresize (dictobject.c:1259) + ==51890== by 0x45DD76: insertion_resize (dictobject.c:1019) + ==51890== by 0x464F30: PyDict_SetDefault (dictobject.c:2924) + ==51890== by 0x4D03BE: PyUnicode_InternInPlace (unicodeobject.c:15289) + ==51890== by 0x4D0700: PyUnicode_InternFromString (unicodeobject.c:15322) + ==51890== by 0x64D2FC: descr_new (descrobject.c:857) + ==51890== Address 0x4c1b020 is 384 bytes inside a block of size 1,160 free'd + ==51890== at 0x483CA3F: free (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so) + ==51890== by 0x4735D3: _PyMem_RawFree (obmalloc.c:127) + ==51890== by 0x473BAC: _PyMem_DebugRawFree (obmalloc.c:2187) + ==51890== by 0x474941: PyMem_RawFree (obmalloc.c:595) + ==51890== by 0x47496E: _PyObject_Free (obmalloc.c:1898) + ==51890== by 0x473BAC: _PyMem_DebugRawFree (obmalloc.c:2187) + ==51890== by 0x473BD4: _PyMem_DebugFree (obmalloc.c:2318) + ==51890== by 0x474C08: PyObject_Free (obmalloc.c:709) + ==51890== by 0x45DD60: dictresize (dictobject.c:1259) + ==51890== by 0x45DD76: insertion_resize (dictobject.c:1019) + ==51890== by 0x464F30: PyDict_SetDefault (dictobject.c:2924) + ==51890== by 0x4D03BE: PyUnicode_InternInPlace (unicodeobject.c:15289) + ==51890== Block was alloc'd at + ==51890== at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so) + ==51890== by 0x473646: _PyMem_RawMalloc (obmalloc.c:99) + ==51890== by 0x473529: _PyMem_DebugRawAlloc (obmalloc.c:2120) + ==51890== by 0x473565: _PyMem_DebugRawMalloc (obmalloc.c:2153) + ==51890== by 0x4748B1: PyMem_RawMalloc (obmalloc.c:572) + ==51890== by 0x475909: _PyObject_Malloc (obmalloc.c:1628) + ==51890== by 0x473529: _PyMem_DebugRawAlloc (obmalloc.c:2120) + ==51890== by 0x473565: _PyMem_DebugRawMalloc (obmalloc.c:2153) + ==51890== by 0x4736B0: _PyMem_DebugMalloc (obmalloc.c:2303) + ==51890== by 0x474B78: PyObject_Malloc (obmalloc.c:685) + ==51890== by 0x45C435: new_keys_object (dictobject.c:558) + ==51890== by 0x45DA95: dictresize (dictobject.c:1202) + ==51890== + ==51890== Invalid read of size 4 + ==51890== at 0x472E3D: address_in_range (obmalloc.c:1401) + ==51890== by 0x47594A: pymalloc_realloc (obmalloc.c:1929) + ==51890== by 0x475A02: _PyObject_Realloc (obmalloc.c:1982) + ==51890== by 0x473DCA: _PyMem_DebugRawRealloc (obmalloc.c:2240) + ==51890== by 0x473FF8: _PyMem_DebugRealloc (obmalloc.c:2326) + ==51890== by 0x4749FB: PyMem_Realloc (obmalloc.c:623) + ==51890== by 0x44A6FC: list_resize (listobject.c:70) + ==51890== by 0x44A872: app1 (listobject.c:340) + ==51890== by 0x44FD65: PyList_Append (listobject.c:352) + ==51890== by 0x514315: r_ref (marshal.c:945) + ==51890== by 0x516034: r_object (marshal.c:1139) + ==51890== by 0x516C70: r_object (marshal.c:1389) + ==51890== Address 0x4c41020 is 32 bytes before a block of size 1,600 in arena "client" + ==51890== + ==51890== Conditional jump or move depends on uninitialised value(s) + ==51890== at 0x472E46: address_in_range (obmalloc.c:1403) + ==51890== by 0x47594A: pymalloc_realloc (obmalloc.c:1929) + ==51890== by 0x475A02: _PyObject_Realloc (obmalloc.c:1982) + ==51890== by 0x473DCA: _PyMem_DebugRawRealloc (obmalloc.c:2240) + ==51890== by 0x473FF8: _PyMem_DebugRealloc (obmalloc.c:2326) + ==51890== by 0x4749FB: PyMem_Realloc (obmalloc.c:623) + ==51890== by 0x44A6FC: list_resize (listobject.c:70) + ==51890== by 0x44A872: app1 (listobject.c:340) + ==51890== by 0x44FD65: PyList_Append (listobject.c:352) + ==51890== by 0x5E3321: _posix_listdir (posixmodule.c:3823) + ==51890== by 0x5E33A8: os_listdir_impl (posixmodule.c:3879) + ==51890== by 0x5E4D77: os_listdir (posixmodule.c.h:1197) + ==51890== + ==51890== Use of uninitialised value of size 8 + ==51890== at 0x472E59: address_in_range (obmalloc.c:1403) + ==51890== by 0x47594A: pymalloc_realloc (obmalloc.c:1929) + ==51890== by 0x475A02: _PyObject_Realloc (obmalloc.c:1982) + ==51890== by 0x473DCA: _PyMem_DebugRawRealloc (obmalloc.c:2240) + ==51890== by 0x473FF8: _PyMem_DebugRealloc (obmalloc.c:2326) + ==51890== by 0x4749FB: PyMem_Realloc (obmalloc.c:623) + ==51890== by 0x44A6FC: list_resize (listobject.c:70) + ==51890== by 0x44A872: app1 (listobject.c:340) + ==51890== by 0x44FD65: PyList_Append (listobject.c:352) + ==51890== by 0x5E3321: _posix_listdir (posixmodule.c:3823) + ==51890== by 0x5E33A8: os_listdir_impl (posixmodule.c:3879) + ==51890== by 0x5E4D77: os_listdir (posixmodule.c.h:1197) + ==51890== + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 16908288 bytes but only got 0. Skipping tag 0 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 67895296 bytes but only got 0. Skipping tag 0 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 1572864 bytes but only got 0. Skipping tag 42 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 116647 bytes but only got 4867. Skipping tag 42738 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 3468830728 bytes but only got 4851. Skipping tag 279 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 2198732800 bytes but only got 0. Skipping tag 0 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 67239937 bytes but only got 4125. Skipping tag 0 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 33947764 bytes but only got 0. Skipping tag 139 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 17170432 bytes but only got 0. Skipping tag 0 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 80478208 bytes but only got 0. Skipping tag 1 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 787460 bytes but only got 4882. Skipping tag 20 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 1075 bytes but only got 0. Skipping tag 256 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 120586240 bytes but only got 0. Skipping tag 194 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 65536 bytes but only got 0. Skipping tag 3 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 198656 bytes but only got 0. Skipping tag 279 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 206848 bytes but only got 0. Skipping tag 64512 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 130968 bytes but only got 4882. Skipping tag 256 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 77848 bytes but only got 4689. Skipping tag 64270 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 262156 bytes but only got 0. Skipping tag 257 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 33624064 bytes but only got 0. Skipping tag 49152 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 67178752 bytes but only got 4627. Skipping tag 50688 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 33632768 bytes but only got 0. Skipping tag 56320 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 134386688 bytes but only got 4115. Skipping tag 2048 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 33912832 bytes but only got 0. Skipping tag 7168 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 151966208 bytes but only got 4627. Skipping tag 10240 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 119032832 bytes but only got 3859. Skipping tag 256 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 46535680 bytes but only got 0. Skipping tag 256 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 35651584 bytes but only got 0. Skipping tag 42 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 524288 bytes but only got 0. Skipping tag 0 + warnings.warn( + _TIFFVSetField: tempfile.tif: Null count for "Tag 769" (type 1, writecount -3, passcount 1). + _TIFFVSetField: tempfile.tif: Null count for "Tag 42754" (type 1, writecount -3, passcount 1). + _TIFFVSetField: tempfile.tif: Null count for "Tag 769" (type 1, writecount -3, passcount 1). + _TIFFVSetField: tempfile.tif: Null count for "Tag 42754" (type 1, writecount -3, passcount 1). + ZIPDecode: Decoding error at scanline 0, incorrect header check. + ==51890== Invalid write of size 4 + ==51890== at 0x61C39E6: putcontig8bitYCbCr22tile (tif_getimage.c:2146) + ==51890== by 0x61C5865: gtStripContig (tif_getimage.c:977) + ==51890== by 0x6094317: ReadStrip (TiffDecode.c:269) + ==51890== by 0x6094749: ImagingLibTiffDecode (TiffDecode.c:479) + ==51890== by 0x60615D1: _decode (decode.c:136) + ==51890== by 0x64BF47: method_vectorcall_VARARGS (descrobject.c:300) + ==51890== by 0x4EB73C: _PyObject_Vectorcall (abstract.h:127) + ==51890== by 0x4EB73C: call_function (ceval.c:4963) + ==51890== by 0x4EB73C: _PyEval_EvalFrameDefault (ceval.c:3486) + ==51890== by 0x4DF2EE: PyEval_EvalFrameEx (ceval.c:741) + ==51890== by 0x43627B: function_code_fastcall (call.c:283) + ==51890== by 0x436D21: _PyFunction_Vectorcall (call.c:410) + ==51890== by 0x4EB73C: _PyObject_Vectorcall (abstract.h:127) + ==51890== by 0x4EB73C: call_function (ceval.c:4963) + ==51890== by 0x4EB73C: _PyEval_EvalFrameDefault (ceval.c:3486) + ==51890== by 0x4DF2EE: PyEval_EvalFrameEx (ceval.c:741) + ==51890== Address 0x6f456d4 is 0 bytes after a block of size 68 alloc'd + ==51890== at 0x483DFAF: realloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so) + ==51890== by 0x60946D0: ImagingLibTiffDecode (TiffDecode.c:469) + ==51890== by 0x60615D1: _decode (decode.c:136) + ==51890== by 0x64BF47: method_vectorcall_VARARGS (descrobject.c:300) + ==51890== by 0x4EB73C: _PyObject_Vectorcall (abstract.h:127) + ==51890== by 0x4EB73C: call_function (ceval.c:4963) + ==51890== by 0x4EB73C: _PyEval_EvalFrameDefault (ceval.c:3486) + ==51890== by 0x4DF2EE: PyEval_EvalFrameEx (ceval.c:741) + ==51890== by 0x43627B: function_code_fastcall (call.c:283) + ==51890== by 0x436D21: _PyFunction_Vectorcall (call.c:410) + ==51890== by 0x4EB73C: _PyObject_Vectorcall (abstract.h:127) + ==51890== by 0x4EB73C: call_function (ceval.c:4963) + ==51890== by 0x4EB73C: _PyEval_EvalFrameDefault (ceval.c:3486) + ==51890== by 0x4DF2EE: PyEval_EvalFrameEx (ceval.c:741) + ==51890== by 0x4DFDFB: _PyEval_EvalCodeWithName (ceval.c:4298) + ==51890== by 0x436C40: _PyFunction_Vectorcall (call.c:435) + ==51890== + ==51890== Invalid write of size 4 + ==51890== at 0x61C39B5: putcontig8bitYCbCr22tile (tif_getimage.c:2145) + ==51890== by 0x61C5865: gtStripContig (tif_getimage.c:977) + ==51890== by 0x6094317: ReadStrip (TiffDecode.c:269) + ==51890== by 0x6094749: ImagingLibTiffDecode (TiffDecode.c:479) + ==51890== by 0x60615D1: _decode (decode.c:136) + ==51890== by 0x64BF47: method_vectorcall_VARARGS (descrobject.c:300) + ==51890== by 0x4EB73C: _PyObject_Vectorcall (abstract.h:127) + ==51890== by 0x4EB73C: call_function (ceval.c:4963) + ==51890== by 0x4EB73C: _PyEval_EvalFrameDefault (ceval.c:3486) + ==51890== by 0x4DF2EE: PyEval_EvalFrameEx (ceval.c:741) + ==51890== by 0x43627B: function_code_fastcall (call.c:283) + ==51890== by 0x436D21: _PyFunction_Vectorcall (call.c:410) + ==51890== by 0x4EB73C: _PyObject_Vectorcall (abstract.h:127) + ==51890== by 0x4EB73C: call_function (ceval.c:4963) + ==51890== by 0x4EB73C: _PyEval_EvalFrameDefault (ceval.c:3486) + ==51890== by 0x4DF2EE: PyEval_EvalFrameEx (ceval.c:741) + ==51890== Address 0x6f456d8 is 4 bytes after a block of size 68 alloc'd + ==51890== at 0x483DFAF: realloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so) + ==51890== by 0x60946D0: ImagingLibTiffDecode (TiffDecode.c:469) + ==51890== by 0x60615D1: _decode (decode.c:136) + ==51890== by 0x64BF47: method_vectorcall_VARARGS (descrobject.c:300) + ==51890== by 0x4EB73C: _PyObject_Vectorcall (abstract.h:127) + ==51890== by 0x4EB73C: call_function (ceval.c:4963) + ==51890== by 0x4EB73C: _PyEval_EvalFrameDefault (ceval.c:3486) + ==51890== by 0x4DF2EE: PyEval_EvalFrameEx (ceval.c:741) + ==51890== by 0x43627B: function_code_fastcall (call.c:283) + ==51890== by 0x436D21: _PyFunction_Vectorcall (call.c:410) + ==51890== by 0x4EB73C: _PyObject_Vectorcall (abstract.h:127) + ==51890== by 0x4EB73C: call_function (ceval.c:4963) + ==51890== by 0x4EB73C: _PyEval_EvalFrameDefault (ceval.c:3486) + ==51890== by 0x4DF2EE: PyEval_EvalFrameEx (ceval.c:741) + ==51890== by 0x4DFDFB: _PyEval_EvalCodeWithName (ceval.c:4298) + ==51890== by 0x436C40: _PyFunction_Vectorcall (call.c:435) + ==51890== + TIFFFillStrip: Invalid strip byte count 0, strip 1. + Traceback (most recent call last): + File "test_tiff.py", line 8, in + im.load() + File "/home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py", line 1087, in load + return self._load_libtiff() + File "/home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py", line 1191, in _load_libtiff + raise OSError(err) + OSError: -2 + sys:1: ResourceWarning: unclosed file <_io.BufferedReader name='crash-2020-10-test.tiff'> + ==51890== + ==51890== HEAP SUMMARY: + ==51890== in use at exit: 748,734 bytes in 444 blocks + ==51890== total heap usage: 6,320 allocs, 5,876 frees, 69,142,969 bytes allocated + ==51890== + ==51890== LEAK SUMMARY: + ==51890== definitely lost: 0 bytes in 0 blocks + ==51890== indirectly lost: 0 bytes in 0 blocks + ==51890== possibly lost: 721,538 bytes in 372 blocks + ==51890== still reachable: 27,196 bytes in 72 blocks + ==51890== suppressed: 0 bytes in 0 blocks + ==51890== Rerun with --leak-check=full to see details of leaked memory + ==51890== + ==51890== Use --track-origins=yes to see where uninitialised values come from + ==51890== For lists of detected and suppressed errors, rerun with: -s + ==51890== ERROR SUMMARY: 2556 errors from 6 contexts (suppressed: 0 from 0) + (vpy38-dbg) ubuntu@primary:~/Home/tests$ + +- Now that we've confirmed that there's something odd/bad going on, + it's time to gdb. +- Start with ``gdb python`` +- Set a break point starting with the valgrind stack trace. + ``b TiffDecode.c:269`` +- Run the script with ``r test_tiff.py`` +- When the break point is hit, explore the state with ``info locals``, + ``bt``, ``py-bt``, or ``p [variable]``. For pointers, + ``p *[variable]`` is useful. + +:: + + (vpy38-dbg) ubuntu@primary:~/Home/tests$ gdb python + GNU gdb (Ubuntu 9.2-0ubuntu1~20.04) 9.2 + Copyright (C) 2020 Free Software Foundation, Inc. + License GPLv3+: GNU GPL version 3 or later + This is free software: you are free to change and redistribute it. + There is NO WARRANTY, to the extent permitted by law. + Type "show copying" and "show warranty" for details. + This GDB was configured as "x86_64-linux-gnu". + Type "show configuration" for configuration details. + For bug reporting instructions, please see: + . + Find the GDB manual and other documentation resources online at: + . + + For help, type "help". + Type "apropos word" to search for commands related to "word"... + Reading symbols from python... + (gdb) b TiffDecode.c:269 + No source file named TiffDecode.c. + Make breakpoint pending on future shared library load? (y or [n]) y + Breakpoint 1 (TiffDecode.c:269) pending. + (gdb) r test_tiff.py + Starting program: /home/ubuntu/vpy38-dbg/bin/python test_tiff.py + [Thread debugging using libthread_db enabled] + Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 16908288 bytes but only got 0. Skipping tag 0 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 67895296 bytes but only got 0. Skipping tag 0 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 1572864 bytes but only got 0. Skipping tag 42 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 116647 bytes but only got 4867. Skipping tag 42738 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 3468830728 bytes but only got 4851. Skipping tag 279 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 2198732800 bytes but only got 0. Skipping tag 0 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 67239937 bytes but only got 4125. Skipping tag 0 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 33947764 bytes but only got 0. Skipping tag 139 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 17170432 bytes but only got 0. Skipping tag 0 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 80478208 bytes but only got 0. Skipping tag 1 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 787460 bytes but only got 4882. Skipping tag 20 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 1075 bytes but only got 0. Skipping tag 256 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 120586240 bytes but only got 0. Skipping tag 194 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 65536 bytes but only got 0. Skipping tag 3 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 198656 bytes but only got 0. Skipping tag 279 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 206848 bytes but only got 0. Skipping tag 64512 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 130968 bytes but only got 4882. Skipping tag 256 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 77848 bytes but only got 4689. Skipping tag 64270 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 262156 bytes but only got 0. Skipping tag 257 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 33624064 bytes but only got 0. Skipping tag 49152 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 67178752 bytes but only got 4627. Skipping tag 50688 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 33632768 bytes but only got 0. Skipping tag 56320 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 134386688 bytes but only got 4115. Skipping tag 2048 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 33912832 bytes but only got 0. Skipping tag 7168 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 151966208 bytes but only got 4627. Skipping tag 10240 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 119032832 bytes but only got 3859. Skipping tag 256 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 46535680 bytes but only got 0. Skipping tag 256 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 35651584 bytes but only got 0. Skipping tag 42 + warnings.warn( + /home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py:770: UserWarning: Possibly corrupt EXIF data. Expecting to read 524288 bytes but only got 0. Skipping tag 0 + warnings.warn( + _TIFFVSetField: tempfile.tif: Null count for "Tag 769" (type 1, writecount -3, passcount 1). + _TIFFVSetField: tempfile.tif: Null count for "Tag 42754" (type 1, writecount -3, passcount 1). + _TIFFVSetField: tempfile.tif: Null count for "Tag 769" (type 1, writecount -3, passcount 1). + _TIFFVSetField: tempfile.tif: Null count for "Tag 42754" (type 1, writecount -3, passcount 1). + + Breakpoint 1, ReadStrip (tiff=tiff@entry=0xae9b90, row=0, buffer=0xac2eb0) at src/libImaging/TiffDecode.c:269 + 269 ok = TIFFRGBAImageGet(&img, buffer, img.width, rows_to_read); + (gdb) p img + $1 = {tif = 0xae9b90, stoponerr = 0, isContig = 1, alpha = 0, width = 20, height = 1536, bitspersample = 8, samplesperpixel = 3, + orientation = 1, req_orientation = 1, photometric = 6, redcmap = 0x0, greencmap = 0x0, bluecmap = 0x0, get = + 0x7ffff71d0710 , put = {any = 0x7ffff71ce550 , + contig = 0x7ffff71ce550 , separate = 0x7ffff71ce550 }, Map = 0x0, + BWmap = 0x0, PALmap = 0x0, ycbcr = 0xaf24b0, cielab = 0x0, UaToAa = 0x0, Bitdepth16To8 = 0x0, row_offset = 0, col_offset = 0} + (gdb) up + #1 0x00007ffff736174a in ImagingLibTiffDecode (im=0xac1f90, state=0x7ffff76767e0, buffer=, bytes=) + at src/libImaging/TiffDecode.c:479 + 479 if (ReadStrip(tiff, state->y, (UINT32 *)state->buffer) == -1) { + (gdb) p *state + $2 = {count = 0, state = 0, errcode = 0, x = 0, y = 0, ystep = 0, xsize = 17, ysize = 108, xoff = 0, yoff = 0, + shuffle = 0x7ffff735f411 , bits = 32, bytes = 68, buffer = 0xac2eb0 "P\354\336\367\377\177", context = 0xa75440, fd = 0x0} + (gdb) py-bt + Traceback (most recent call first): + File "/home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py", line 1428, in _load_libtiff + + File "/home/ubuntu/vpy38-dbg/lib/python3.8/site-packages/Pillow-8.0.1-py3.8-linux-x86_64.egg/PIL/TiffImagePlugin.py", line 1087, in load + return self._load_libtiff() + File "test_tiff.py", line 8, in + im.load() + +- Poke around till you understand what's going on. In this case, + state->xsize and img.width are different, which led to an out of + bounds write, as the receiving buffer was sized for the smaller of + the two. + +Caveats +------- + +- If your program is running/hung in a docker container and your host + has the appropriate tools, you can run gdb as the superuser in the + host and you may be able to get a trace of where the process is hung. + You probably won't have the capability to do that from within the + docker container, as the trace capacity isn't allowed by default. + +- Variations of this are possible on the mac/windows, but the details + are going to be different. + +- IIRC, Fedora has the gdb bits working by default. Ubuntu has always + been a bit of a battle to make it work. diff --git a/docs/reference/features.rst b/docs/reference/features.rst new file mode 100644 index 00000000000..0a6381098da --- /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. +* ``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 8c09e7b67f7..5d6affa94ad 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 bbc9050cff0..2e2d3322f75 100644 --- a/docs/reference/internal_design.rst +++ b/docs/reference/internal_design.rst @@ -7,4 +7,5 @@ Internal Reference Docs open_files limits block_allocator - + internal_modules + c_extension_debugging diff --git a/docs/reference/internal_modules.rst b/docs/reference/internal_modules.rst new file mode 100644 index 00000000000..1105ff76e1f --- /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/limits.rst b/docs/reference/limits.rst index 79dc66e6779..a71b514b5aa 100644 --- a/docs/reference/limits.rst +++ b/docs/reference/limits.rst @@ -25,13 +25,6 @@ Internal Limits is smaller than 2GB, as calculated by ``y*stride`` (so 2Gpx for 'L' images, and .5Gpx for 'RGB' -* Any call to internal python size functions for buffers or strings - are currently returned as int32, not py_ssize_t. This limits the - maximum buffer to 2GB for operations like frombytes and frombuffer. - -* This also limits the size of buffers converted using a - decoder. (decode.c:127) - Format Size Limits ================== diff --git a/docs/reference/open_files.rst b/docs/reference/open_files.rst index ed0ab1a0ca6..6bfd50588ab 100644 --- a/docs/reference/open_files.rst +++ b/docs/reference/open_files.rst @@ -14,17 +14,17 @@ The following are all equivalent:: import io import pathlib - with Image.open('test.jpg') as im: + with Image.open("test.jpg") as im: ... - with Image.open(pathlib.Path('test.jpg')) as im2: + with Image.open(pathlib.Path("test.jpg")) as im2: ... - with open('test.jpg', 'rb') as f: + with open("test.jpg", "rb") as f: im3 = Image.open(f) ... - with open('test.jpg', 'rb') as f: + with open("test.jpg", "rb") as f: im4 = Image.open(io.BytesIO(f.read())) ... @@ -47,6 +47,10 @@ Image Lifecycle memory. The image can now be used independently of the underlying image file. + Any Pillow method that creates a new image instance based on another will + internally call ``load()`` on the original image and then read the data. + The new image instance will not be associated with the original image file. + If a filename or a ``Path`` object was passed to ``Image.open()``, then the file object was opened by Pillow and is considered to be used exclusively by Pillow. So if the image is a single-frame image, the file will be closed in @@ -55,10 +59,16 @@ Image Lifecycle ``Image.Image.seek()`` can load the appropriate frame. * ``Image.Image.close()`` Closes the file and destroys the core image object. - This is used in the Pillow context manager support. e.g.:: - with Image.open('test.jpg') as img: - ... # image operations here. + The Pillow context manager will also close the file, but will not destroy + the core image object. e.g.: + +.. code-block:: python + + with Image.open("test.jpg") as img: + img.load() + assert img.fp is None + img.save("test.png") The lifecycle of a single-frame image is relatively simple. The file must @@ -80,13 +90,13 @@ Complications * After a file has been closed, operations that require file access will fail:: - with open('test.jpg', 'rb') as f: + with open("test.jpg", "rb") as f: im5 = Image.open(f) - im5.load() # FAILS, closed file + im5.load() # FAILS, closed file - with Image.open('test.jpg') as im6: + with Image.open("test.jpg") as im6: pass - im6.load() # FAILS, closed file + im6.load() # FAILS, closed file Proposed File Handling diff --git a/docs/reference/plugins.rst b/docs/reference/plugins.rst index 46f657fce57..7094f87846c 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 931f9fd1e9f..660d331640c 100644 --- a/docs/releasenotes/2.7.0.rst +++ b/docs/releasenotes/2.7.0.rst @@ -14,7 +14,7 @@ Png text chunk size limits To prevent potential denial of service attacks using compressed text chunks, there are now limits to the decompressed size of text chunks decoded from PNG images. If the limits are exceeded when opening a PNG -image a ``ValueError`` will be raised. +image a :py:exc:`ValueError` will be raised. Individual text chunks are limited to :py:attr:`PIL.PngImagePlugin.MAX_TEXT_CHUNK`, set to 1MB by @@ -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 8c32a43e74e..38118ea39c4 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. + +.. code-block:: c - kk = malloc(xsize * kmax * sizeof(float)); - ... - xbounds = malloc(xsize * 2 * sizeof(int)); + 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 ddb6a2adacf..b5f7cfe9963 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 dc5d734790c..4d6598d8efa 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 e07fd90716d..1e9637f1e32 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 6fa554e237d..ea81fc45ea0 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 cce671c3206..bff56566b66 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 0145347f229..3e3b945a0a9 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 faf84e6bdc8..20a009cc177 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 a138c7d607f..79d4b88aac4 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 e0e76434249..80002b0ce71 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 index 346b9b49099..0024a537d12 100644 --- a/docs/releasenotes/7.1.0.rst +++ b/docs/releasenotes/7.1.0.rst @@ -63,12 +63,22 @@ 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 ``None`` using the default 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 out-of-bounds 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 ============= diff --git a/docs/releasenotes/7.1.1.rst b/docs/releasenotes/7.1.1.rst new file mode 100644 index 00000000000..2169e6a05b8 --- /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 00000000000..b12d84e33bd --- /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 00000000000..ff1b7c9e764 --- /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 00000000000..2ff9b3799ba --- /dev/null +++ b/docs/releasenotes/8.0.0.rst @@ -0,0 +1,180 @@ +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 ``python3 -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 SBIX fonts requires FreeType 2.5.1 compiled with libpng. +Support for COLR fonts requires FreeType 2.10. +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 00000000000..3584a5d72e9 --- /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 00000000000..8ed1d9d85cc --- /dev/null +++ b/docs/releasenotes/8.1.0.rst @@ -0,0 +1,93 @@ +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 out-of-bounds write error + +Out-of-bounds 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. + +LibTIFF in the macOS and Linux wheels has been updated from 4.1.0 to 4.2.0, including +security fixes discovered by fuzzers. + +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/8.1.1.rst b/docs/releasenotes/8.1.1.rst new file mode 100644 index 00000000000..4081c49ca5c --- /dev/null +++ b/docs/releasenotes/8.1.1.rst @@ -0,0 +1,27 @@ +8.1.1 +----- + +Security +======== + +:cve:`CVE-2021-25289`: The previous fix for :cve:`CVE-2020-35654` was insufficient +due to incorrect error checking in ``TiffDecode.c``. + +:cve:`CVE-2021-25290`: In ``TiffDecode.c``, there is a negative-offset ``memcpy`` +with an invalid size. + +:cve:`CVE-2021-25291`: In ``TiffDecode.c``, invalid tile boundaries could lead to +an out-of-bounds read in ``TIFFReadRGBATile``. + +:cve:`CVE-2021-25292`: The PDF parser has a catastrophic backtracking regex +that could be used as a DOS attack. + +:cve:`CVE-2021-25293`: There is an out-of-bounds read in ``SgiRleDecode.c``, +since Pillow 4.3.0. + + +Other Changes +============= + +A crash with the feature flags for libimagequant, libjpeg-turbo, WebP and XCB on +unreleased Python 3.10 has been fixed (:issue:`5193`). diff --git a/docs/releasenotes/8.1.2.rst b/docs/releasenotes/8.1.2.rst new file mode 100644 index 00000000000..50d132f3337 --- /dev/null +++ b/docs/releasenotes/8.1.2.rst @@ -0,0 +1,12 @@ +8.1.2 +----- + +Security +======== + +There is an exhaustion of memory DOS in the BLP (:cve:`CVE-2021-27921`), +ICNS (:cve:`CVE-2021-27922`) and ICO (:cve:`CVE-2021-27923`) container formats +where Pillow did not properly check the reported size of the contained image. +These images could cause arbitrarily large memory allocations. This was reported +by Jiayi Lin, Luke Shaffer, Xinran Xie, and Akshay Ajayan of +`Arizona State University `_. diff --git a/docs/releasenotes/8.2.0.rst b/docs/releasenotes/8.2.0.rst new file mode 100644 index 00000000000..c902ccf71fb --- /dev/null +++ b/docs/releasenotes/8.2.0.rst @@ -0,0 +1,230 @@ +8.2.0 +----- + +Deprecations +============ + +Categories +^^^^^^^^^^ + +``im.category`` is deprecated and will be removed in Pillow 10.0.0 (2023-07-01), +along with the related ``Image.NORMAL``, ``Image.SEQUENCE`` and +``Image.CONTAINER`` attributes. + +To determine if an image has multiple frames or not, +``getattr(im, "is_animated", False)`` can be used instead. + +Tk/Tcl 8.4 +^^^^^^^^^^ + +Support for Tk/Tcl 8.4 is deprecated and will be removed in Pillow 10.0.0 (2023-07-01), +when Tk/Tcl 8.5 will be the minimum supported. + +API Changes +=========== + +Image.alpha_composite: dest +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +When calling :py:meth:`~PIL.Image.Image.alpha_composite`, the ``dest`` argument now +accepts negative co-ordinates, like the upper left corner of the ``box`` argument of +:py:meth:`~PIL.Image.Image.paste` can be negative. Naturally, this has effect of +cropping the overlaid image. + +Image.getexif: EXIF and GPS IFD +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Previously, :py:meth:`~PIL.Image.Image.getexif` flattened the EXIF IFD into the rest of +the data, losing information. This information is now kept separate, moved under +``im.getexif().get_ifd(0x8769)``. + +Direct access to the GPS IFD dictionary was possible through ``im.getexif()[0x8825]``. +This is now consistent with other IFDs, and must be accessed through +``im.getexif().get_ifd(0x8825)``. + +These changes only affect :py:meth:`~PIL.Image.Image.getexif`, introduced in Pillow +6.0. The older ``_getexif()`` methods are unaffected. + +Image._MODEINFO +^^^^^^^^^^^^^^^ + +This internal dictionary had been deprecated by a comment since PIL, and is now +removed. Instead, ``Image.getmodebase()``, ``Image.getmodetype()``, +``Image.getmodebandnames()``, ``Image.getmodebands()`` or ``ImageMode.getmode()`` +can be used. + +API Additions +============= + +getxmp() for JPEG images +^^^^^^^^^^^^^^^^^^^^^^^^ + +A new method has been added to return +`XMP data `_ for JPEG +images. It reads the XML data into a dictionary of names and values. + +For example:: + + >>> from PIL import Image + >>> with Image.open("Tests/images/xmp_test.jpg") as im: + >>> print(im.getxmp()) + {'RDF': {}, 'Description': {'Version': '10.4', 'ProcessVersion': '10.0', ...}, ...} + +ImageDraw.rounded_rectangle +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Added :py:meth:`~PIL.ImageDraw.ImageDraw.rounded_rectangle`. It works the same as +:py:meth:`~PIL.ImageDraw.ImageDraw.rectangle`, except with an additional ``radius`` +argument. ``radius`` is limited to half of the width or the height, so that users can +create a circle, but not any other ellipse. + +.. code-block:: python + + from PIL import Image, ImageDraw + im = Image.new("RGB", (200, 200)) + draw = ImageDraw.Draw(im) + draw.rounded_rectangle(xy=(10, 20, 190, 180), radius=30, fill="red") + +ImageOps.autocontrast: preserve_tone +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The default behaviour of :py:meth:`~PIL.ImageOps.autocontrast` is to normalize +separate histograms for each color channel, changing the tone of the image. The new +``preserve_tone`` argument keeps the tone unchanged by using one luminance histogram +for all channels. + +ImageShow.GmDisplayViewer +^^^^^^^^^^^^^^^^^^^^^^^^^ + +If GraphicsMagick is present, this new :py:class:`PIL.ImageShow.Viewer` subclass will +be registered. It uses GraphicsMagick_, an ImageMagick_ fork, to display images. + +The GraphicsMagick based viewer has a lower priority than its ImageMagick +counterpart. Thus, if both ImageMagick and GraphicsMagick are installed, +``im.show()`` and :py:func:`.ImageShow.show()` prefer the viewer based on +ImageMagick, i.e the behaviour stays the same for Pillow users having +ImageMagick installed. + +ImageShow.IPythonViewer +^^^^^^^^^^^^^^^^^^^^^^^ + +If IPython is present, this new :py:class:`PIL.ImageShow.Viewer` subclass will be +registered. It displays images on all IPython frontends. This will be helpful +to users of Google Colab, allowing ``im.show()`` to display images. + +It is lower in priority than the other default :py:class:`PIL.ImageShow.Viewer` +instances, so it will only be used by ``im.show()`` or :py:func:`.ImageShow.show()` +if none of the other viewers are available. This means that the behaviour of +:py:class:`PIL.ImageShow` will stay the same for most Pillow users. + +Saving TIFF with ICC profile +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +As is already possible for JPEG, PNG and WebP, the ICC profile for TIFF files can now +be specified through a keyword argument:: + + im.save("out.tif", icc_profile=...) + + +Security +======== + +These were all found with `OSS-Fuzz`_. + +:cve:`CVE-2021-25287`, :cve:`CVE-2021-25288`: Fix OOB read in Jpeg2KDecode +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +* For J2k images with multiple bands, it's legal to have different widths for each band, + e.g. 1 byte for ``L``, 4 bytes for ``A``. +* This dates to Pillow 2.4.0. + +:cve:`CVE-2021-28675`: Fix DOS in PsdImagePlugin +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +* :py:class:`.PsdImagePlugin.PsdImageFile` did not sanity check the number of input + layers with regard to the size of the data block, this could lead to a + denial-of-service on :py:meth:`~PIL.Image.open` prior to + :py:meth:`~PIL.Image.Image.load`. +* This dates to the PIL fork. + +:cve:`CVE-2021-28676`: Fix FLI DOS +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +* ``FliDecode.c`` did not properly check that the block advance was non-zero, + potentially leading to an infinite loop on load. +* This dates to the PIL fork. + +:cve:`CVE-2021-28677`: Fix EPS DOS on _open +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +* The readline used in EPS has to deal with any combination of ``\r`` and ``\n`` as line + endings. It accidentally used a quadratic method of accumulating lines while looking + for a line ending. +* A malicious EPS file could use this to perform a denial-of-service of Pillow in the + open phase, before an image was accepted for opening. +* This dates to the PIL fork. + +:cve:`CVE-2021-28678`: Fix BLP DOS +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +* ``BlpImagePlugin`` did not properly check that reads after jumping to file offsets + returned data. This could lead to a denial-of-service where the decoder could be run a + large number of times on empty data. +* This dates to Pillow 5.1.0. + +Fix memory DOS in ImageFont +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +* A corrupt or specially crafted TTF font could have font metrics that lead to + unreasonably large sizes when rendering text in font. ``ImageFont.py`` did not check + the image size before allocating memory for it. +* This dates to the PIL fork. + +Other Changes +============= + +GIF writer uses LZW encoding +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +GIF files are now written using LZW encoding, which will generate smaller files, +typically about 70% of the size generated by the older encoder. + +The pixel data is encoded using the format specified in the `CompuServe GIF standard +`_. + +The older encoder used a variant of run-length encoding that was compatible but less +efficient. + +GraphicsMagick +^^^^^^^^^^^^^^ + +The test suite can now be run on systems which have GraphicsMagick_ but not +ImageMagick_ installed. If both are installed, the tests prefer ImageMagick. + +Libraqm and FriBiDi linking +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The way the libraqm dependency for complex text scripts is linked has been changed: + +Source builds will now link against the system version of libraqm at build time +rather than at runtime by default. + +Binary wheels now include a statically linked modified version of libraqm that +links against FriBiDi at runtime instead. This change is intended to address +issues with the previous implementation on some platforms. These are created +by building Pillow with the new build flags ``--vendor-raqm --vendor-fribidi``. + +Windows users will now need to install ``fribidi.dll`` (or ``fribidi-0.dll``) only, +``libraqm.dll`` is no longer used. + +See :doc:`installation documentation<../installation>` for more information. + +PyQt6 +^^^^^ + +Support has been added for PyQt6. If it is installed, it will be used instead of +PySide6, PyQt5 or PySide2. + +.. _GraphicsMagick: http://www.graphicsmagick.org/ +.. _ImageMagick: https://imagemagick.org/ +.. _OSS-Fuzz: https://github.com/google/oss-fuzz diff --git a/docs/releasenotes/8.3.0.rst b/docs/releasenotes/8.3.0.rst new file mode 100644 index 00000000000..0bfead14470 --- /dev/null +++ b/docs/releasenotes/8.3.0.rst @@ -0,0 +1,113 @@ +8.3.0 +----- + +Deprecations +============ + +JpegImagePlugin.convert_dict_qtables +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +JPEG ``quantization`` is now automatically converted, but still returned as a +dictionary. The :py:attr:`~PIL.JpegImagePlugin.convert_dict_qtables` method no longer +performs any operations on the data given to it, has been deprecated and will be +removed in Pillow 10.0.0 (2023-07-01). + +API Changes +=========== + +Changed WebP default "method" value when saving +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Previously, it was 0, for the best speed. The default has now been changed to 4, to +match WebP's default, for higher quality with still some speed optimisation. + +Default resampling filter for special image modes +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Pillow 7.0 changed the default resampling filter to ``Image.BICUBIC``. However, as this +is not supported yet for images with a custom number of bits, the default filter for +those modes has been reverted to ``Image.NEAREST``. + +ImageMorph incorrect mode errors +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +For ``apply()``, ``match()`` and ``get_on_pixels()``, if the image mode is not L, an +:py:exc:`Exception` was thrown. This has now been changed to a :py:exc:`ValueError`. + +getxmp() +^^^^^^^^ + +`XMP data `_ can now be +returned for PNG and TIFF images, through ``getxmp()`` for each format. + +The returned dictionary will start from the base of the XML, meaning that the top level +should contain an "xmpmeta" key. JPEG's ``getxmp()`` method has also been updated to +this structure. + +TIFF getexif() +^^^^^^^^^^^^^^ + +TIFF :py:attr:`~PIL.TiffImagePlugin.TiffImageFile.tag_v2` data can now be accessed +through :py:meth:`~PIL.Image.Image.getexif`. This also provides access to the GPS and +EXIF IFDs, through ``im.getexif().get_ifd(0x8825)`` and +``im.getexif().get_ifd(0x8769)`` respectively. + +API Additions +============= + +ImageOps.contain +^^^^^^^^^^^^^^^^ + +Returns a resized version of the image, set to the maximum width and height within +``size``, while maintaining the original aspect ratio. + +To compare it to other ImageOps methods: + +- :py:meth:`~PIL.ImageOps.fit` expands an image until is fills ``size``, cropping the + parts of the image that do not fit. +- :py:meth:`~PIL.ImageOps.pad` expands an image to fill ``size``, without cropping, but + instead filling the extra space with ``color``. +- :py:meth:`~PIL.ImageOps.contain` is similar to :py:meth:`~PIL.ImageOps.pad`, but it + does not fill the extra space. Instead, the original aspect ratio is maintained. So + unlike the other two methods, it is not guaranteed to return an image of ``size``. + +ICO saving: bitmap_format argument +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +By default, Pillow saves ICO files in the PNG format. They can now also be saved in BMP +format, through the new ``bitmap_format`` argument:: + + im.save("out.ico", bitmap_format="bmp") + +Security +======== + +Buffer overflow +^^^^^^^^^^^^^^^ + +This release addresses :cve:`CVE-2021-34552`. PIL since 1.1.4 and Pillow since 1.0 +allowed parameters passed into a convert function to trigger buffer overflow in +Convert.c. + +Parsing XML +^^^^^^^^^^^ + +Pillow previously parsed XMP data using Python's ``xml`` module. However, this module +is not secure. + +- :py:meth:`~PIL.Image.Image.getexif` has used ``xml`` to potentially retrieve + orientation data since Pillow 7.2.0. It has been refactored to use ``re`` instead. +- :py:meth:`~PIL.JpegImagePlugin.JpegImageFile.getxmp` was added in Pillow 8.2.0. It + will now use ``defusedxml`` instead. If the dependency is not present, an empty + dictionary will be returned and a warning raised. + +Other Changes +============= + +Added DDS BC5 reading and uncompressed saving +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Support has been added to read the BC5 format of DDS images, whether UNORM, SNORM or +TYPELESS. + +Support has also been added to write the uncompressed format of DDS images. diff --git a/docs/releasenotes/8.3.1.rst b/docs/releasenotes/8.3.1.rst new file mode 100644 index 00000000000..e97070c111c --- /dev/null +++ b/docs/releasenotes/8.3.1.rst @@ -0,0 +1,40 @@ +8.3.1 +----- + +Fixed regression converting to NumPy arrays +=========================================== + +This fixes a regression introduced in 8.3.0 when converting an image to a NumPy array +with a ``dtype`` argument. + +.. code-block:: pycon + + >>> from PIL import Image + >>> import numpy + >>> im = Image.new("RGB", (100, 100)) + >>> numpy.array(im, dtype=numpy.float64) + Traceback (most recent call last): + File "", line 1, in + TypeError: __array__() takes 1 positional argument but 2 were given + >>> + +Catch OSError when checking if destination is sys.stdout +======================================================== + +In 8.3.0, a check to see if the destination was ``sys.stdout`` when saving an image was +updated. This lead to an OSError being raised if the environment restricted access. + +The OSError is now silently caught. + +Fixed removing orientation in ImageOps.exif_transpose +===================================================== + +In 8.3.0, :py:meth:`~PIL.ImageOps.exif_transpose` was changed to ensure that the +original image EXIF data was not modified, and the orientation was only removed from +the modified copy. + +However, for certain images the orientation was already missing from the modified +image, leading to a KeyError. + +This error has been resolved, and the copying of metadata to the modified image +improved. diff --git a/docs/releasenotes/8.3.2.rst b/docs/releasenotes/8.3.2.rst new file mode 100644 index 00000000000..6b5c759fc0a --- /dev/null +++ b/docs/releasenotes/8.3.2.rst @@ -0,0 +1,41 @@ +8.3.2 +----- + +Security +======== + +* :cve:`CVE-2021-23437`: Avoid a potential ReDoS (regular expression denial of service) + in :py:class:`~PIL.ImageColor`'s :py:meth:`~PIL.ImageColor.getrgb` by raising + :py:exc:`ValueError` if the color specifier is too long. Present since Pillow 5.2.0. + +* Fix 6-byte out-of-bounds (OOB) read. The previous bounds check in ``FliDecode.c`` + incorrectly calculated the required read buffer size when copying a chunk, potentially + reading six extra bytes off the end of the allocated buffer from the heap. Present + since Pillow 7.1.0. This bug was found by Google's `OSS-Fuzz`_ `CIFuzz`_ runs. + +Other Changes +============= + +Python 3.10 wheels +^^^^^^^^^^^^^^^^^^ + +Pillow now includes binary wheels for Python 3.10. + +The Python 3.10 release candidate was released on 2021-08-03 with the final release due +2021-10-04 (:pep:`619`). The CPython core team strongly encourages maintainers of +third-party Python projects to prepare for 3.10 compatibility. And as there are `no ABI +changes`_ planned we are releasing wheels to help others prepare for 3.10, and ensure +Pillow can be used immediately on release day of 3.10.0 final. + +Fixed regressions +^^^^^^^^^^^^^^^^^ + +* Ensure TIFF ``RowsPerStrip`` is multiple of 8 for JPEG compression (:pr:`5588`). + +* Updates for :py:class:`~PIL.ImagePalette` channel order (:pr:`5599`). + +* Hide FriBiDi shim symbols to avoid conflict with real FriBiDi library (:pr:`5651`). + +.. _OSS-Fuzz: https://github.com/google/oss-fuzz +.. _CIFuzz: https://google.github.io/oss-fuzz/getting-started/continuous-integration/ +.. _no ABI changes: https://www.python.org/downloads/release/python-3100rc1/ diff --git a/docs/releasenotes/8.4.0.rst b/docs/releasenotes/8.4.0.rst new file mode 100644 index 00000000000..9becf91465e --- /dev/null +++ b/docs/releasenotes/8.4.0.rst @@ -0,0 +1,53 @@ +8.4.0 +----- + +API Changes +=========== + +Deprecations +^^^^^^^^^^^^ + +ImagePalette size parameter +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The ``size`` parameter will be removed in Pillow 10.0.0 (2023-07-01). + +Before Pillow 8.3.0, ``ImagePalette`` required palette data of particular lengths by +default, and the size parameter could be used to override that. Pillow 8.3.0 removed +the default required length, also removing the need for the size parameter. + +API Additions +============= + +Added "transparency" argument for loading EPS images +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +This new argument switches the Ghostscript device from "ppmraw" to "pngalpha", +generating an RGBA image with a transparent background instead of an RGB image with a +white background. + +.. code-block:: python + + with Image.open("sample.eps") as im: + im.load(transparency=True) + +Added WalImageFile class +^^^^^^^^^^^^^^^^^^^^^^^^ + +:py:func:`PIL.WalImageFile.open()` previously returned a generic +:py:class:`PIL.Image.Image` instance. It now returns a dedicated +:py:class:`PIL.WalImageFile.WalImageFile` class. + +Other Changes +============= + +Speed improvement when rotating square images +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Starting with Pillow 3.3.0, the speed of rotating images by 90 or 270 degrees was +improved by quickly returning :py:meth:`~PIL.Image.Image.transpose` instead, if the +rotate operation allowed for expansion and did not specify a center or post-rotate +translation. + +Since the ``expand`` flag makes no difference for square images though, Pillow now +uses this faster method for square images without the ``expand`` flag as well. diff --git a/docs/releasenotes/9.0.0.rst b/docs/releasenotes/9.0.0.rst new file mode 100644 index 00000000000..947ccd849e3 --- /dev/null +++ b/docs/releasenotes/9.0.0.rst @@ -0,0 +1,171 @@ +9.0.0 +----- + +Fredrik Lundh +============= + +This release is dedicated to the memory of Fredrik Lundh, aka Effbot, who died in +November 2021. Fredrik created PIL in 1995 and he was instrumental in the early +success of Python. + +`Guido wrote `_: + + Fredrik was an early Python contributor (e.g. Elementtree and the 're' + module) and his enthusiasm for the language and community were inspiring + for all who encountered him or his work. He spent countless hours on + comp.lang.python answering questions from newbies and advanced users alike. + + He also co-founded an early Python startup, Secret Labs AB, which among + other software released an IDE named PythonWorks. Fredrik also created the + Python Imaging Library (PIL) which is still THE way to interact with images + in Python, now most often through its Pillow fork. His effbot.org site was + a valuable resource for generations of Python users, especially its Tkinter + documentation. + +Thank you, Fredrik. + +Backwards Incompatible Changes +============================== + +Python 3.6 +^^^^^^^^^^ + +Pillow has dropped support for Python 3.6, which reached end-of-life on 2021-12-23. + +PILLOW_VERSION constant +^^^^^^^^^^^^^^^^^^^^^^^ + +``PILLOW_VERSION`` has been removed. Use ``__version__`` instead. + +FreeType 2.7 +^^^^^^^^^^^^ + +Support for FreeType 2.7 has been removed; FreeType 2.8 is 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`). + +.. _FreeType: https://www.freetype.org + +Image.show command parameter +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The ``command`` parameter has been removed. Use a subclass of +:py:class:`PIL.ImageShow.Viewer` instead. + +Image._showxv +^^^^^^^^^^^^^ + +``Image._showxv`` has been removed. 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`` +has been removed. Use ``ImageFile.raise_oserror`` instead. + + +API Changes +=========== + +Added line width parameter to ImageDraw polygon +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +An optional line ``width`` parameter has been added to ``ImageDraw.Draw.polygon``. + + +API Additions +============= + +ImageShow.XDGViewer +^^^^^^^^^^^^^^^^^^^ + +If ``xdg-open`` is present on Linux, this new :py:class:`PIL.ImageShow.Viewer` subclass +will be registered. It displays images using the application selected by the system. + +It is higher in priority than the other default :py:class:`PIL.ImageShow.Viewer` +instances, so it will be preferred by ``im.show()`` or :py:func:`.ImageShow.show()`. + +Added support for "title" argument to DisplayViewer +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Support has been added for the "title" argument in +:py:class:`~PIL.ImageShow.UnixViewer.DisplayViewer`, so that when ``im.show()`` or +:py:func:`.ImageShow.show()` use the ``display`` command line tool, the "title" +argument will also now be supported, e.g. ``im.show(title="My Image")`` and +``ImageShow.show(im, title="My Image")``. + +Security +======== + +Ensure JpegImagePlugin stops at the end of a truncated file +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +``JpegImagePlugin`` may append an EOF marker to the end of a truncated file, so that +the last segment of the data will still be processed by the decoder. + +If the EOF marker is not detected as such however, this could lead to an infinite +loop where ``JpegImagePlugin`` keeps trying to end the file. + +Remove consecutive duplicate tiles that only differ by their offset +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +To prevent attempts to slow down loading times for images, if an image has consecutive +duplicate tiles that only differ by their offset, only load the last tile. Credit to +Google's `OSS-Fuzz`_ project for finding this issue. + +Restrict builtins available to ImageMath.eval +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +:cve:`CVE-2022-22817`: To limit :py:class:`PIL.ImageMath` to working with images, Pillow +will now restrict the builtins available to :py:meth:`PIL.ImageMath.eval`. This will +help prevent problems arising if users evaluate arbitrary expressions, such as +``ImageMath.eval("exec(exit())")``. + +Fixed ImagePath.Path array handling +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +:cve:`CVE-2022-22815` (:cwe:`CWE-126`) and :cve:`CVE-2022-22816` (:cwe:`CWE-665`) were +found when initializing ``ImagePath.Path``. + +.. _OSS-Fuzz: https://github.com/google/oss-fuzz + +Other Changes +============= + +Convert subsequent GIF frames to RGB or RGBA +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Since each frame of a GIF can have up to 256 colors, after the first frame it is +possible for there to be too many colors to fit in a P mode image. To allow for this, +seeking to any subsequent GIF frame will now convert the image to RGB or RGBA, +depending on whether or not the first frame had transparency. + +Switched to libjpeg-turbo in macOS and Linux wheels +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The Pillow wheels from PyPI for macOS and Linux have switched from libjpeg to +libjpeg-turbo. It is a fork of libjpeg, popular for its speed. + +Added support for pickling TrueType fonts +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +TrueType fonts may now be pickled and unpickled. For example: + +.. code-block:: python + + import pickle + from PIL import ImageFont + + font = ImageFont.truetype("arial.ttf", size=30) + pickled_font = pickle.dumps(font, protocol=pickle.HIGHEST_PROTOCOL) + + # Later... + unpickled_font = pickle.loads(pickled_font) + +Added support for additional TGA orientations +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +TGA images with top right or bottom right orientations are now supported. diff --git a/docs/releasenotes/9.0.1.rst b/docs/releasenotes/9.0.1.rst new file mode 100644 index 00000000000..5d1b246bce9 --- /dev/null +++ b/docs/releasenotes/9.0.1.rst @@ -0,0 +1,23 @@ +9.0.1 +----- + +Security +======== + +This release addresses several security problems. + +:cve:`CVE-2022-24303`: If the path to the temporary directory on Linux or macOS +contained a space, this would break removal of the temporary image file after +``im.show()`` (and related actions), and potentially remove an unrelated file. This +been present since PIL. + +:cve:`CVE-2022-22817`: While Pillow 9.0 restricted top-level builtins available to +:py:meth:`PIL.ImageMath.eval`, it did not prevent builtins available to lambda +expressions. These are now also restricted. + +Other Changes +============= + +Pillow 9.0 added support for ``xdg-open`` as an image viewer, but there have been +reports that the temporary image file was removed too quickly to be loaded into the +final application. A delay has been added. diff --git a/docs/releasenotes/index.rst b/docs/releasenotes/index.rst index 1838803ded3..e9b11c220e8 100644 --- a/docs/releasenotes/index.rst +++ b/docs/releasenotes/index.rst @@ -1,11 +1,34 @@ 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 + 9.0.1 + 9.0.0 + 8.4.0 + 8.3.2 + 8.3.1 + 8.3.0 + 8.2.0 + 8.1.2 + 8.1.1 + 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 @@ -35,3 +58,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 00000000000..f7271ae2bf8 --- /dev/null +++ b/docs/releasenotes/template.rst @@ -0,0 +1,48 @@ +x.y.z +----- + +Backwards Incompatible Changes +============================== + +TODO +^^^^ + +Deprecations +============ + +TODO +^^^^ + +TODO + +API Changes +=========== + +TODO +^^^^ + +TODO + +API Additions +============= + +TODO +^^^^ + +TODO + +Security +======== + +TODO +^^^^ + +TODO + +Other Changes +============= + +TODO +^^^^ + +TODO diff --git a/docs/releasenotes/versioning.rst b/docs/releasenotes/versioning.rst new file mode 100644 index 00000000000..87f2ba422b3 --- /dev/null +++ b/docs/releasenotes/versioning.rst @@ -0,0 +1,30 @@ +.. _versioning: + +Versioning +========== + +Pillow follows `Semantic Versioning `_: + + 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 ``main`` 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 00000000000..a0648a10cb8 --- /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 00000000000..95da30ffde2 --- /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 00000000000..8866c07eabd --- /dev/null +++ b/docs/resources/css/dark.css @@ -0,0 +1,1996 @@ +@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(24, 26, 27); + } + + .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); + } + + @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 00000000000..04edd7b16b9 --- /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/css/styles.css b/docs/resources/css/styles.css new file mode 100644 index 00000000000..111f84085b7 --- /dev/null +++ b/docs/resources/css/styles.css @@ -0,0 +1,8 @@ +th p { + margin-bottom: 0; +} + +.rst-content tr .line-block { + font-size: 1rem; + margin-bottom: 0; +} diff --git a/docs/resources/js/script.js b/docs/resources/js/script.js index 3bc216c2d9e..5cb6494ea59 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 00000000000..1cc2006a6f7 Binary files /dev/null and b/docs/resources/pillow-logo.png differ diff --git a/requirements.txt b/requirements.txt index 14f934c9cc9..1e150e304b2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,12 +1,18 @@ # Development, documentation & testing requirements. -black; python_version >= '3.6' +black check-manifest coverage -jarn.viewdoc +defusedxml +markdown2 olefile -pycodestyle -pyflakes +packaging pyroma pytest pytest-cov -sphinx-rtd-theme +pytest-timeout +sphinx>=2.4 +sphinx-copybutton +sphinx-issues>=3.0.1 +sphinx-removed-in +sphinx-rtd-theme>=1.0 +sphinxext-opengraph diff --git a/selftest.py b/selftest.py index ea52256f79a..4ebd7cc00da 100755 --- a/selftest.py +++ b/selftest.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # minimal sanity check import sys @@ -14,17 +14,13 @@ pass -def _info(im): - im.load() - return im.format, im.mode, im.size - - def testimage(): """ PIL lets you create in-memory images with various pixel types: >>> from PIL import Image, ImageDraw, ImageFilter, ImageMath >>> im = Image.new("1", (128, 128)) # monochrome + >>> def _info(im): return (im.format, im.mode, im.size) >>> _info(im) (None, '1', (128, 128)) >>> _info(Image.new("L", (128, 128))) # grayscale (luminance) @@ -47,7 +43,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)) @@ -147,9 +143,7 @@ def testimage(): ('F', (128, 128)) PIL can do many other things, but I'll leave that for another - day. If you're curious, check the handbook, available from: - - http://www.pythonware.com + day. Cheers /F """ diff --git a/setup.cfg b/setup.cfg index 17e85bd216e..c3b5a319750 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,12 +1,47 @@ +[metadata] +name = Pillow +description = Python Imaging Library (Fork) +long_description = file: README.md +long_description_content_type = text/markdown +url = https://python-pillow.org +author = Alex Clark (PIL Fork Author) +author_email = aclark@python-pillow.org +license = HPND +classifiers = + Development Status :: 6 - Mature + License :: OSI Approved :: Historical Permission Notice and Disclaimer (HPND) + Programming Language :: Python :: 3 + Programming Language :: Python :: 3 :: Only + Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 + Programming Language :: Python :: 3.9 + Programming Language :: Python :: 3.10 + Programming Language :: Python :: Implementation :: CPython + Programming Language :: Python :: Implementation :: PyPy + Topic :: Multimedia :: Graphics + Topic :: Multimedia :: Graphics :: Capture :: Digital Camera + Topic :: Multimedia :: Graphics :: Capture :: Screen Capture + Topic :: Multimedia :: Graphics :: Graphics Conversion + Topic :: Multimedia :: Graphics :: Viewers +keywords = Imaging +project_urls = + Documentation=https://pillow.readthedocs.io + Source=https://github.com/python-pillow/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/main/CHANGES.rst + Twitter=https://twitter.com/PythonPillow + +[options] +python_requires = >=3.7 + [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 3e1a812b6a3..23d91a5f24d 100755 --- a/setup.py +++ b/setup.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # > pyroma . # ------------------------------ # Checking . @@ -14,37 +14,41 @@ 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__"] -NAME = "Pillow" PILLOW_VERSION = get_version() FREETYPE_ROOT = None +HARFBUZZ_ROOT = None +FRIBIDI_ROOT = None IMAGEQUANT_ROOT = None JPEG2K_ROOT = None JPEG_ROOT = None LCMS_ROOT = None 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, +FUZZING_BUILD = "LIB_FUZZING_ENGINE" in os.environ + +if sys.platform == "win32" and sys.version_info >= (3, 11): + 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 +135,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 +167,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" @@ -193,7 +185,7 @@ def _find_library_dirs_ldconfig(): return [] [data, _] = p.communicate() if isinstance(data, bytes): - data = data.decode() + data = data.decode("latin1") dirs = [] for dll in re.findall(expr, data): @@ -237,6 +229,19 @@ def _find_library_file(self, library): return ret +def _find_include_dir(self, dirname, include): + for directory in self.compiler.include_dirs: + _dbg("Checking for include file %s in %s", (include, directory)) + if os.path.isfile(os.path.join(directory, include)): + _dbg("Found %s in %s", (include, directory)) + return True + subdir = os.path.join(directory, dirname) + _dbg("Checking for include file %s in %s", (include, subdir)) + if os.path.isfile(os.path.join(subdir, include)): + _dbg("Found %s in %s", (include, subdir)) + return subdir + + def _cmd_exists(cmd): return any( os.access(os.path.join(path, cmd), os.X_OK) @@ -244,11 +249,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") @@ -281,6 +281,7 @@ class feature: "jpeg", "tiff", "freetype", + "raqm", "lcms", "webp", "webpmux", @@ -290,6 +291,7 @@ class feature: ] required = {"jpeg", "zlib"} + vendor = set() def __init__(self): for f in self.features: @@ -301,6 +303,9 @@ def require(self, feat): def want(self, feat): return getattr(self, feat) is None + def want_vendor(self, feat): + return feat in self.vendor + def __iter__(self): yield from self.features @@ -308,8 +313,12 @@ def __iter__(self): 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] + + [ + (f"vendor-{x}", None, f"Use vendored version of {x}") + for x in ("raqm", "fribidi") + ] + [ ("disable-platform-guessing", None, "Disable platform guessing on Linux"), ("debug", None, "Debug logging"), @@ -322,8 +331,10 @@ def initialize_options(self): 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) + for x in ("raqm", "fribidi"): + setattr(self, f"vendor_{x}", None) def finalize_options(self): build_ext.finalize_options(self) @@ -340,25 +351,85 @@ def finalize_options(self): 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 x == "freetype": + _dbg("--disable-freetype implies --disable-raqm") + if getattr(self, "enable_raqm"): + raise ValueError( + "Conflicting options: --enable-raqm and --disable-freetype" + ) + setattr(self, "disable_raqm", True) + if getattr(self, f"enable_{x}"): _dbg("Requiring %s", x) self.feature.required.add(x) + if x == "raqm": + _dbg("--enable-raqm implies --enable-freetype") + self.feature.required.add("freetype") + for x in ("raqm", "fribidi"): + if getattr(self, f"vendor_{x}"): + if getattr(self, "disable_raqm"): + raise ValueError( + f"Conflicting options: --vendor-{x} and --disable-raqm" + ) + if x == "fribidi" and not getattr(self, "vendor_raqm"): + raise ValueError( + f"Conflicting options: --vendor-{x} and not --vendor-raqm" + ) + _dbg("Using vendored version of %s", x) + self.feature.vendor.add(x) + + def _update_extension(self, name, libraries, define_macros=None, sources=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 sources is not None: + extension.sources += sources + if FUZZING_BUILD: + extension.language = "c++" + extension.extra_link_args = ["--stdlib=libc++"] + break + + def _remove_extension(self, name): + for extension in self.extensions: + if extension.name == name: + self.extensions.remove(extension) + break + + def get_macos_sdk_path(self): + try: + sdk_path = ( + subprocess.check_output(["xcrun", "--show-sdk-path"]) + .strip() + .decode("latin1") + ) + except Exception: + sdk_path = None + if ( + not sdk_path + or sdk_path == "/Applications/Xcode.app/Contents/Developer" + "/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk" + ): + commandlinetools_sdk_path = ( + "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk" + ) + if os.path.exists(commandlinetools_sdk_path): + sdk_path = commandlinetools_sdk_path + return sdk_path 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 @@ -371,6 +442,8 @@ def build_extensions(self): TIFF_ROOT=("libtiff-5", "libtiff-4"), ZLIB_ROOT="zlib", FREETYPE_ROOT="freetype2", + HARFBUZZ_ROOT="harfbuzz", + FRIBIDI_ROOT="fribidi", LCMS_ROOT="lcms2", IMAGEQUANT_ROOT="libimagequant", ).items(): @@ -383,12 +456,12 @@ def build_extensions(self): 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): @@ -418,10 +491,8 @@ def build_extensions(self): 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 @@ -466,6 +537,9 @@ def build_extensions(self): # 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): @@ -478,6 +552,10 @@ def build_extensions(self): _add_directory(library_dirs, "/usr/X11/lib") _add_directory(include_dirs, "/usr/X11/include") + sdk_path = self.get_macos_sdk_path() + 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") @@ -493,7 +571,11 @@ def build_extensions(self): # headers are at $PREFIX/include # user libs are at $PREFIX/lib _add_directory( - library_dirs, os.path.join(os.environ["ANDROID_ROOT"], "lib") + library_dirs, + os.path.join( + os.environ["ANDROID_ROOT"], + "lib" if struct.calcsize("l") == 4 else "lib64", + ), ) elif sys.platform.startswith("netbsd"): @@ -517,11 +599,6 @@ def build_extensions(self): _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", "") @@ -579,7 +656,7 @@ def build_extensions(self): try: listdir = os.listdir(directory) except Exception: - # WindowsError, FileNotFoundError + # OSError, FileNotFoundError continue for name in listdir: if name.startswith("openjpeg-") and os.path.isfile( @@ -648,6 +725,39 @@ def build_extensions(self): if subdir: _add_directory(self.compiler.include_dirs, subdir, 0) + if feature.freetype and feature.want("raqm"): + if not feature.want_vendor("raqm"): # want system Raqm + _dbg("Looking for Raqm") + if _find_include_file(self, "raqm.h"): + if _find_library_file(self, "raqm"): + feature.raqm = "raqm" + elif _find_library_file(self, "libraqm"): + feature.raqm = "libraqm" + else: # want to build Raqm from src/thirdparty + _dbg("Looking for HarfBuzz") + feature.harfbuzz = None + hb_dir = _find_include_dir(self, "harfbuzz", "hb.h") + if hb_dir: + if isinstance(hb_dir, str): + _add_directory(self.compiler.include_dirs, hb_dir, 0) + if _find_library_file(self, "harfbuzz"): + feature.harfbuzz = "harfbuzz" + if feature.harfbuzz: + if not feature.want_vendor("fribidi"): # want system FriBiDi + _dbg("Looking for FriBiDi") + feature.fribidi = None + fribidi_dir = _find_include_dir(self, "fribidi", "fribidi.h") + if fribidi_dir: + if isinstance(fribidi_dir, str): + _add_directory( + self.compiler.include_dirs, fribidi_dir, 0 + ) + if _find_library_file(self, "fribidi"): + feature.fribidi = "fribidi" + feature.raqm = True + else: # want to build FriBiDi shim from src/thirdparty + feature.raqm = True + if feature.want("lcms"): _dbg("Looking for lcms") if _find_include_file(self, "lcms2.h"): @@ -697,12 +807,6 @@ def build_extensions(self): # # 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: @@ -711,7 +815,7 @@ def build_extensions(self): 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) @@ -722,6 +826,12 @@ def build_extensions(self): if feature.tiff: libs.append(feature.tiff) defs.append(("HAVE_LIBTIFF", None)) + if sys.platform == "win32": + # This define needs to be defined if-and-only-if it was defined + # when compiling LibTIFF. LibTIFF doesn't expose it in `tiffconf.h`, + # so we have to guess; by default it is defined in all Windows builds. + # See #4237, #5243, #5359 for more information. + defs.append(("USE_WIN32_FILEIO", None)) if feature.xcb: libs.append(feature.xcb) defs.append(("HAVE_XCB", None)) @@ -730,39 +840,50 @@ def build_extensions(self): 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 if feature.freetype: + srcs = [] libs = ["freetype"] defs = [] - exts.append( - Extension( - "PIL._imagingft", - ["src/_imagingft.c"], - libraries=libs, - define_macros=defs, - ) - ) + if feature.raqm: + if not feature.want_vendor("raqm"): # using system Raqm + defs.append(("HAVE_RAQM", None)) + defs.append(("HAVE_RAQM_SYSTEM", None)) + libs.append(feature.raqm) + else: # building Raqm from src/thirdparty + defs.append(("HAVE_RAQM", None)) + srcs.append("src/thirdparty/raqm/raqm.c") + libs.append(feature.harfbuzz) + if not feature.want_vendor("fribidi"): # using system FriBiDi + defs.append(("HAVE_FRIBIDI_SYSTEM", None)) + libs.append(feature.fribidi) + else: # building FriBiDi shim from src/thirdparty + srcs.append("src/thirdparty/fribidi-shim/fribidi.c") + self._update_extension("PIL._imagingft", libs, defs, srcs) + + 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] @@ -773,26 +894,12 @@ def build_extensions(self): libs.append(feature.webpmux) libs.append(feature.webpmux.replace("pmux", "pdemux")) - exts.append( - Extension( - "PIL._webp", ["src/_webp.c"], libraries=libs, define_macros=defs - ) - ) - - 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._update_extension("PIL._webp", libs, defs) + else: + self._remove_extension("PIL._webp") - self.extensions[:] = exts + tk_libs = ["psapi"] if sys.platform in ("win32", "cygwin") else [] + self._update_extension("PIL._imagingtk", tk_libs) build_ext.build_extensions(self) @@ -806,13 +913,19 @@ def summary_report(self, feature): 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) + raqm_extra_info = "" + if feature.want_vendor("raqm"): + raqm_extra_info += "bundled" + if feature.want_vendor("fribidi"): + raqm_extra_info += ", FriBiDi shim" + options = [ (feature.jpeg, "JPEG"), (feature.jpeg2000, "OPENJPEG (JPEG2000)", feature.openjpeg_version), @@ -820,6 +933,7 @@ def summary_report(self, feature): (feature.imagequant, "LIBIMAGEQUANT"), (feature.tiff, "LIBTIFF"), (feature.freetype, "FREETYPE2"), + (feature.raqm, "RAQM (Text shaping)", raqm_extra_info), (feature.lcms, "LITTLECMS2"), (feature.webp, "WEBP"), (feature.webpmux, "WEBPMUX"), @@ -829,12 +943,12 @@ def summary_report(self, feature): all = 1 for option in options: if option[0]: - version = "" + extra_info = "" if len(option) >= 3 and option[2]: - version = " (%s)" % option[2] - print("--- {} support available{}".format(option[1], version)) + extra_info = f" ({option[2]})" + print(f"--- {option[1]} support available{extra_info}") else: - print("*** %s support not available" % option[1]) + print(f"*** {option[1]} support not available") all = 0 print("-" * 68) @@ -853,74 +967,52 @@ def summary_report(self, feature): def debug_build(): - return hasattr(sys, "gettotalrefcount") - + return hasattr(sys, "gettotalrefcount") or FUZZING_BUILD + + +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"]), +] try: setup( - name=NAME, version=PILLOW_VERSION, - description="Python Imaging Library (Fork)", - long_description=_read("README.rst").decode("utf-8"), - license="HPND", - author="Alex Clark (PIL Fork Author)", - author_email="aclark@python-pillow.org", - url="https://python-pillow.org", - project_urls={ - "Documentation": "https://pillow.readthedocs.io", - "Source": "https://github.com/python-pillow/Pillow", - "Funding": "https://tidelift.com/subscription/pkg/pypi-pillow?" - "utm_source=pypi-pillow&utm_medium=pypi", - }, - 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 :: Only", - "Programming Language :: Python :: Implementation :: CPython", - "Programming Language :: Python :: Implementation :: PyPy", - "Topic :: Multimedia :: Graphics", - "Topic :: Multimedia :: Graphics :: Capture :: Digital Camera", - "Topic :: Multimedia :: Graphics :: Capture :: Screen Capture", - "Topic :: Multimedia :: Graphics :: Graphics Conversion", - "Topic :: Multimedia :: Graphics :: Viewers", - ], - python_requires=">=3.5", 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"}, - keywords=["Imaging"], 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 7a485cf8009..102b72e1d5a 100644 --- a/src/PIL/BdfFontFile.py +++ b/src/PIL/BdfFontFile.py @@ -17,12 +17,12 @@ # 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) -# -------------------------------------------------------------------- +from . import FontFile, Image bdf_slant = { "R": "Roman", @@ -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 5ccba37dbd8..7b78597b443 100644 --- a/src/PIL/BlpImagePlugin.py +++ b/src/PIL/BlpImagePlugin.py @@ -250,7 +250,7 @@ def _open(self): 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,37 +282,40 @@ def decode(self, buffer): 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 _safe_read(self, length): + return ImageFile._safe_read(self.fd, length) + def _read_palette(self): ret = [] for i in range(256): try: - b, g, r, a = struct.unpack("<4B", self.fd.read(4)) + b, g, r, a = struct.unpack("<4B", self._safe_read(4)) except struct.error: break ret.append((b, g, r, a)) return ret def _read_blp_header(self): - (self._blp_compression,) = struct.unpack("= 52: 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 +146,7 @@ def _bitmap(self, header=0, offset=0): 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 @@ -156,15 +158,13 @@ def _bitmap(self, header=0, offset=0): if file_info.get("colors", 0) 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) + if offset == 14 + file_info["header_size"] and file_info["bits"] <= 8: + offset += 4 * file_info["colors"] # ---------------------- 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 @@ def _bitmap(self, header=0, offset=0): 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"]) @@ -259,14 +259,14 @@ def _bitmap(self, header=0, offset=0): ] def _open(self): - """ Open file, check magic number and read header """ + """Open file, check magic number and read header""" # 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 5bb0086f6e7..45e80b39af7 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. diff --git a/src/PIL/CurImagePlugin.py b/src/PIL/CurImagePlugin.py index 3a1b6d2e533..42af5cafcef 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 @@ def _open(self): # 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 7d2aff325aa..de21db8f082 100644 --- a/src/PIL/DcxImagePlugin.py +++ b/src/PIL/DcxImagePlugin.py @@ -46,7 +46,7 @@ def _open(self): # 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 @@ def _open(self): 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 9ba6e0ff842..260924fca0d 100644 --- a/src/PIL/DdsImagePlugin.py +++ b/src/PIL/DdsImagePlugin.py @@ -14,6 +14,7 @@ from io import BytesIO from . import Image, ImageFile +from ._binary import o32le as o32 # Magic ("DDS ") DDS_MAGIC = 0x20534444 @@ -94,6 +95,12 @@ # dxgiformat.h +DXGI_FORMAT_R8G8B8A8_TYPELESS = 27 +DXGI_FORMAT_R8G8B8A8_UNORM = 28 +DXGI_FORMAT_R8G8B8A8_UNORM_SRGB = 29 +DXGI_FORMAT_BC5_TYPELESS = 82 +DXGI_FORMAT_BC5_UNORM = 83 +DXGI_FORMAT_BC5_SNORM = 84 DXGI_FORMAT_BC7_TYPELESS = 97 DXGI_FORMAT_BC7_UNORM = 98 DXGI_FORMAT_BC7_UNORM_SRGB = 99 @@ -106,10 +113,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 @@ def _open(self): # 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 @@ def _open(self): 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 @@ def _palette(self, palette, shift): 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 @@ def _seek(self, frame): 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 979a1e33c81..c5fc80b37ba 100644 --- a/src/PIL/FontFile.py +++ b/src/PIL/FontFile.py @@ -23,18 +23,15 @@ 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 @@ def save(self, filename): # 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 8d252c79cf5..5e385469f9a 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 @@ def _open(self): 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 @@ def _open_index(self, index=1): # 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 @@ def _open_index(self, index=1): 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 @@ def _open_subimage(self, index=1, subimage=0): # 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 @@ def _open_subimage(self, index=1, subimage=0): 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 096ccacac69..3b169038c89 100644 --- a/src/PIL/FtexImagePlugin.py +++ b/src/PIL/FtexImagePlugin.py @@ -89,7 +89,7 @@ def _open(self): 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) @@ -98,9 +98,9 @@ def load_seek(self, pos): pass -def _validate(prefix): +def _accept(prefix): return prefix[:4] == MAGIC -Image.register_open(FtexImageFile.format, FtexImageFile, _validate) +Image.register_open(FtexImageFile.format, FtexImageFile, _accept) Image.register_extensions(FtexImageFile.format, [".ftc", ".ftu"]) diff --git a/src/PIL/GbrImagePlugin.py b/src/PIL/GbrImagePlugin.py index 292de435cef..0f230602db8 100644 --- a/src/PIL/GbrImagePlugin.py +++ b/src/PIL/GbrImagePlugin.py @@ -29,7 +29,7 @@ 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 @@ def _open(self): 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 @@ def _open(self): 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 @@ def _open(self): 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 54c88712c84..9c34adaa6c4 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 @@ def _open(self): # 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 1d94fc7c7c7..8c2180bc115 100644 --- a/src/PIL/GifImagePlugin.py +++ b/src/PIL/GifImagePlugin.py @@ -30,7 +30,9 @@ 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 @@ -122,17 +124,16 @@ def seek(self, frame): if not self._seek_check(frame): return if frame < self.__frame: - if frame != 0: - self.im = None + self.im = None self._seek(0) last_frame = self.__frame 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): @@ -143,15 +144,14 @@ def _seek(self, frame): self.dispose_extent = [0, 0, 0, 0] # x0, y0, x1, y1 self.__frame = -1 self.__fp.seek(self.__rewind) - self._prev_im = None self.disposal_method = 0 else: # ensure that the previous frame was loaded - if not self.im: + if self.tile: 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 = [] @@ -164,14 +164,25 @@ def _seek(self, frame): pass self.__offset = 0 + if self.__frame == 1: + self.pyaccess = None + if "transparency" in self.info: + self.mode = "RGBA" + self.im.putpalettealpha(self.info["transparency"], 0) + self.im = self.im.convert("RGBA", Image.FLOYDSTEINBERG) + + del self.info["transparency"] + else: + self.mode = "RGB" + self.im = self.im.convert("RGB", Image.FLOYDSTEINBERG) if self.dispose: self.im.paste(self.dispose, self.dispose_extent) - from copy import copy - - self.palette = copy(self.global_palette) + palette = None info = {} + frame_transparency = None + interlace = None while True: s = self.fp.read(1) @@ -184,14 +195,14 @@ def _seek(self, frame): # 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 + frame_transparency = block[3] + info["duration"] = i16(block, 1) * 10 # disposal method - find the value of bits 4 - 6 dispose_bits = 0b00011100 & flags @@ -202,7 +213,7 @@ def _seek(self, frame): # 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 +224,15 @@ def _seek(self, frame): 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,30 +243,36 @@ def _seek(self, frame): 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 if flags & 128: bits = (flags & 7) + 1 - self.palette = ImagePalette.raw("RGB", self.fp.read(3 << bits)) + 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)) - ] break else: pass - # raise IOError, "illegal GIF tag `%x`" % i8(s) + # raise OSError, "illegal GIF tag `%x`" % s[0] + + frame_palette = palette or self.global_palette + + def _rgb(color): + if frame_palette: + color = tuple(frame_palette.palette[color * 3 : color * 3 + 3]) + else: + color = (color, color, color) + return color try: if self.disposal_method < 2: @@ -263,48 +280,109 @@ def _seek(self, frame): self.dispose = None elif self.disposal_method == 2: # replace with background colour - Image._decompression_bomb_check(self.size) - self.dispose = Image.core.fill("P", self.size, self.info["background"]) + + # only dispose the extent in this frame + x0, y0, x1, y1 = self.dispose_extent + dispose_size = (x1 - x0, y1 - y0) + + Image._decompression_bomb_check(dispose_size) + + # by convention, attempt to use transparency first + color = self.info.get("transparency", frame_transparency) + if color is not None: + dispose_mode = "RGBA" + color = _rgb(color) + (0,) + else: + dispose_mode = "RGB" + color = _rgb(self.info.get("background", 0)) + self.dispose = Image.core.fill(dispose_mode, dispose_size, color) else: # replace with previous contents if self.im: - self.dispose = self.im.copy() - - # only dispose the extent in this frame - if self.dispose: - self.dispose = self._crop(self.dispose, self.dispose_extent) - except (AttributeError, KeyError): + # only dispose the extent in this frame + self.dispose = self._crop(self.im, self.dispose_extent) + elif frame_transparency is not None: + x0, y0, x1, y1 = self.dispose_extent + dispose_size = (x1 - x0, y1 - y0) + + Image._decompression_bomb_check(dispose_size) + self.dispose = Image.core.fill( + "RGBA", dispose_size, _rgb(frame_transparency) + (0,) + ) + except AttributeError: pass - if not self.tile: + if interlace is not None: + if frame == 0 and frame_transparency is not None: + self.info["transparency"] = frame_transparency + self.tile = [ + ( + "gif", + (x0, y0, x1, y1), + self.__offset, + (bits, interlace), + ) + ] + else: # self.__fp = None raise EOFError - for k in ["transparency", "duration", "comment", "extension", "loop"]: + for k in ["duration", "comment", "extension", "loop"]: if k in info: self.info[k] = info[k] elif k in self.info: del self.info[k] - self.mode = "L" - if self.palette: - self.mode = "P" + if frame == 0: + self.mode = "P" if frame_palette else "L" - def tell(self): - return self.__frame + if self.mode == "P" and not palette: + from copy import copy + + palette = copy(self.global_palette) + self.palette = palette + else: + self._frame_palette = frame_palette + self._frame_transparency = frame_transparency + + def load_prepare(self): + if self.__frame == 0: + if "transparency" in self.info: + self.im = Image.core.fill( + self.mode, self.size, self.info["transparency"] + ) + else: + self._prev_im = self.im + if self._frame_palette: + self.mode = "P" + self.im = Image.core.fill("P", self.size, self._frame_transparency or 0) + self.im.putpalette(*self._frame_palette.getdata()) + self._frame_palette = None + else: + self.mode = "L" + self.im = None + + super().load_prepare() def load_end(self): - ImageFile.ImageFile.load_end(self) - - # 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: - # 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() + if self.__frame == 0: + return + if self._frame_transparency is not None: + self.im.putpalettealpha(self._frame_transparency, 0) + frame_im = self.im.convert("RGBA") + else: + frame_im = self.im.convert("RGB") + frame_im = self._crop(frame_im, self.dispose_extent) + + self.im = self._prev_im + self.mode = self.im.mode + if frame_im.mode == "RGBA": + self.im.paste(frame_im, self.dispose_extent, frame_im) + else: + self.im.paste(frame_im, self.dispose_extent) + + def tell(self): + return self.__frame def _close__fp(self): try: @@ -347,7 +425,13 @@ def _normalize_mode(im, initial_call=False): palette_size = 256 if im.palette: palette_size = len(im.palette.getdata()[1]) // 3 - return im.convert("P", palette=Image.ADAPTIVE, colors=palette_size) + im = im.convert("P", palette=Image.ADAPTIVE, colors=palette_size) + if im.palette.mode == "RGBA": + for rgba in im.palette.colors.keys(): + if rgba[3] == 0: + im.info["transparency"] = im.palette.colors[rgba] + break + return im else: return im.convert("P") return im.convert("L") @@ -371,15 +455,7 @@ def _normalize_palette(im, palette, info): if isinstance(palette, (bytes, bytearray, list)): source_palette = bytearray(palette[:768]) if isinstance(palette, ImagePalette.ImagePalette): - source_palette = bytearray( - itertools.chain.from_iterable( - zip( - palette.palette[:256], - palette.palette[256:512], - palette.palette[512:768], - ) - ) - ) + source_palette = bytearray(palette.palette) if im.mode == "P": if not source_palette: @@ -389,9 +465,26 @@ def _normalize_palette(im, palette, info): source_palette = bytearray(i // 3 for i in range(768)) im.palette = ImagePalette.ImagePalette("RGB", palette=source_palette) - used_palette_colors = _get_optimize(im, info) - if used_palette_colors is not None: - return im.remap_palette(used_palette_colors, source_palette) + if palette: + used_palette_colors = [] + for i in range(0, len(source_palette), 3): + source_color = tuple(source_palette[i : i + 3]) + try: + index = im.palette.colors[source_color] + except KeyError: + index = None + used_palette_colors.append(index) + for i, index in enumerate(used_palette_colors): + if index is None: + for j in range(len(used_palette_colors)): + if j not in used_palette_colors: + used_palette_colors[i] = j + break + im = im.remap_palette(used_palette_colors) + else: + used_palette_colors = _get_optimize(im, info) + if used_palette_colors is not None: + return im.remap_palette(used_palette_colors, source_palette) im.palette.palette = source_palette return im @@ -447,10 +540,10 @@ def _write_multiple_frames(im, fp, palette): previous = im_frames[-1] if encoderinfo.get("disposal") == 2: if background_im is None: - background = _get_background( - im, - im.encoderinfo.get("background", im.info.get("background")), + color = im.encoderinfo.get( + "transparency", im.info.get("transparency", (0, 0, 0)) ) + background = _get_background(im_frame, color) background_im = Image.new("P", im_frame.size, background) background_im.putpalette(im_frames[0]["im"].palette) base_im = background_im @@ -482,7 +575,8 @@ def _write_multiple_frames(im, fp, palette): offset = (0, 0) else: # compress difference - frame_data["encoderinfo"]["include_color_table"] = True + if not palette: + frame_data["encoderinfo"]["include_color_table"] = True im_frame = im_frame.crop(frame_data["bbox"]) offset = frame_data["bbox"][:2] @@ -746,7 +840,15 @@ def _get_background(im, infoBackground): # WebPImagePlugin stores an RGBA value in info["background"] # So it must be converted to the same format as GifImagePlugin's # info["background"] - a global color table index - background = im.palette.getcolor(background) + try: + background = im.palette.getcolor(background, im) + except ValueError as e: + if str(e) == "cannot allocate more than 256 colors": + # If all 256 colors are in use, + # then there is no need for the background color + return 0 + else: + raise return background @@ -754,7 +856,7 @@ def _get_global_header(im, info): """Return a list of strings representing a GIF header""" # Header Block - # http://www.matthewflickinger.com/lab/whatsinagif/bits_and_bytes.asp + # https://www.matthewflickinger.com/lab/whatsinagif/bits_and_bytes.asp version = b"87a" for extensionKey in ["transparency", "duration", "loop", "comment"]: diff --git a/src/PIL/GimpGradientFile.py b/src/PIL/GimpGradientFile.py index 1cacf5718dc..7ab7f9990ac 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 @@ def getpalette(self, entries=256): 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 e3060ab8a8f..10fd3ad8174 100644 --- a/src/PIL/GimpPaletteFile.py +++ b/src/PIL/GimpPaletteFile.py @@ -18,11 +18,9 @@ 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 515c272f727..b9bdd16e3e3 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 c003926154e..6412d1cfb4b 100644 --- a/src/PIL/IcnsImagePlugin.py +++ b/src/PIL/IcnsImagePlugin.py @@ -6,30 +6,29 @@ # # history: # 2004-10-09 fl Turned into a PIL plugin; removed 2.3 dependencies. +# 2020-04-04 Allow saving on all operating systems. # # Copyright (c) 2004 by Bob Ippolito. # Copyright (c) 2004 by Secret Labs. # Copyright (c) 2004 by Fredrik Lundh. # Copyright (c) 2014 by Alastair Houghton. +# Copyright (c) 2020 by Pan Jing. # # See the README file for information on usage and redistribution. # import io import os -import shutil import struct -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 +MAGIC = b"icns" HEADERSIZE = 8 @@ -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} @@ -106,6 +105,7 @@ def read_png_or_jpeg2000(fobj, start_length, size): if sig[:8] == b"\x89PNG\x0d\x0a\x1a\x0a": fobj.seek(start) im = PngImagePlugin.PngImageFile(fobj) + Image._decompression_bomb_check(im.size) return {"RGBA": im} elif ( sig[:4] == b"\xff\x4f\xff\x51" @@ -122,6 +122,7 @@ def read_png_or_jpeg2000(fobj, start_length, size): jp2kstream = fobj.read(length) f = io.BytesIO(jp2kstream) im = Jpeg2KImagePlugin.Jpeg2KImageFile(f) + Image._decompression_bomb_check(im.size) if im.mode != "RGBA": im = im.convert("RGBA") return {"RGBA": im} @@ -166,7 +167,7 @@ def __init__(self, fobj): self.dct = dct = {} self.fobj = fobj sig, filesize = nextheader(fobj) - if sig != b"icns": + if sig != MAGIC: raise SyntaxError("not an icns file") i = HEADERSIZE while i < filesize: @@ -305,64 +306,78 @@ def load(self): def _save(im, fp, filename): """ Saves the image as a series of PNG files, - that are then converted to a .icns file - using the macOS command line utility 'iconutil'. - - macOS only. + that are then combined into a .icns file. """ if hasattr(fp, "flush"): fp.flush() - # create the temporary set of pngs - with tempfile.TemporaryDirectory(".iconset") as iconset: - provided_images = { - im.width: im for im in im.encoderinfo.get("append_images", []) - } - last_w = None - second_path = None - for w in [16, 32, 128, 256, 512]: - prefix = "icon_{}x{}".format(w, w) - - first_path = os.path.join(iconset, prefix + ".png") - if last_w == w: - shutil.copyfile(second_path, first_path) - else: - im_w = provided_images.get(w, im.resize((w, w), Image.LANCZOS)) - im_w.save(first_path) - - second_path = os.path.join(iconset, prefix + "@2x.png") - im_w2 = provided_images.get(w * 2, im.resize((w * 2, w * 2), Image.LANCZOS)) - im_w2.save(second_path) - last_w = w * 2 - - # iconutil -c icns -o {} {} - - convert_cmd = ["iconutil", "-c", "icns", "-o", filename, iconset] - convert_proc = subprocess.Popen( - convert_cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL + sizes = { + b"ic07": 128, + b"ic08": 256, + b"ic09": 512, + b"ic10": 1024, + b"ic11": 32, + b"ic12": 64, + b"ic13": 256, + b"ic14": 512, + } + provided_images = {im.width: im for im in im.encoderinfo.get("append_images", [])} + size_streams = {} + for size in set(sizes.values()): + image = ( + provided_images[size] + if size in provided_images + else im.resize((size, size)) ) - convert_proc.stdout.close() + temp = io.BytesIO() + image.save(temp, "png") + size_streams[size] = temp.getvalue() - retcode = convert_proc.wait() + entries = [] + for type, size in sizes.items(): + stream = size_streams[size] + entries.append( + {"type": type, "size": HEADERSIZE + len(stream), "stream": stream} + ) - if retcode: - raise subprocess.CalledProcessError(retcode, convert_cmd) + # Header + fp.write(MAGIC) + file_length = HEADERSIZE # Header + file_length += HEADERSIZE + 8 * len(entries) # TOC + file_length += sum(entry["size"] for entry in entries) + fp.write(struct.pack(">i", file_length)) + + # TOC + fp.write(b"TOC ") + fp.write(struct.pack(">i", HEADERSIZE + len(entries) * HEADERSIZE)) + for entry in entries: + fp.write(entry["type"]) + fp.write(struct.pack(">i", entry["size"])) + + # Data + for entry in entries: + fp.write(entry["type"]) + fp.write(struct.pack(">i", entry["size"])) + fp.write(entry["stream"]) + if hasattr(fp, "flush"): + fp.flush() -Image.register_open(IcnsImageFile.format, IcnsImageFile, lambda x: x[:4] == b"icns") -Image.register_extension(IcnsImageFile.format, ".icns") -if sys.platform == "darwin": - Image.register_save(IcnsImageFile.format, _save) +def _accept(prefix): + return prefix[:4] == MAGIC + - Image.register_mime(IcnsImageFile.format, "image/icns") +Image.register_open(IcnsImageFile.format, IcnsImageFile, _accept) +Image.register_extension(IcnsImageFile.format, ".icns") +Image.register_save(IcnsImageFile.format, _save) +Image.register_mime(IcnsImageFile.format, "image/icns") if __name__ == "__main__": - if len(sys.argv) < 2: - print("Syntax: python IcnsImagePlugin.py [file]") + print("Syntax: python3 IcnsImagePlugin.py [file]") sys.exit() with open(sys.argv[1], "rb") as fp: diff --git a/src/PIL/IcoImagePlugin.py b/src/PIL/IcoImagePlugin.py index e4a74321b6e..d9ff9b5e731 100644 --- a/src/PIL/IcoImagePlugin.py +++ b/src/PIL/IcoImagePlugin.py @@ -28,7 +28,9 @@ 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 +from ._binary import o32le as o32 # # -------------------------------------------------------------------- @@ -52,6 +54,8 @@ 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 @@ -174,6 +193,7 @@ def frame(self, idx): if data[:8] == PngImagePlugin._MAGIC: # png frame im = PngImagePlugin.PngImageFile(self.buf) + Image._decompression_bomb_check(im.size) else: # XOR + AND mask bmp frame im = BmpImagePlugin.DibImageFile(self.buf) @@ -185,13 +205,7 @@ def frame(self, idx): im.tile[0] = d, (0, 0) + im.size, o, a # figure out where AND mask image starts - mode = a[0] - bpp = 8 - for k, v in BmpImagePlugin.BIT2MODE.items(): - if mode == v[1]: - bpp = k - break - + bpp = header["bpp"] if 32 == bpp: # 32-bit color depth icon image allows semitransparent areas # PIL's DIB format ignores transparency bits, recover them. @@ -221,8 +235,8 @@ def frame(self, idx): # the total mask data is # padded row size * height / bits per char - and_mask_offset = o + int(im.size[0] * im.size[1] * (bpp / 8.0)) total_bytes = int((w * im.size[1]) / 8) + and_mask_offset = header["offset"] + header["size"] - total_bytes self.buf.seek(and_mask_offset) mask_data = self.buf.read(total_bytes) @@ -262,7 +276,8 @@ class IcoImageFile(ImageFile.ImageFile): Handles classic, XP and Vista icon formats. When saving, PNG compression is used. Support for this was only added in - Windows Vista. + Windows Vista. If you are unable to view the icon in Windows, convert the + image to "RGBA" mode before saving. This plugin is a refactored version of Win32IconImagePlugin by Bryan Davis . diff --git a/src/PIL/ImImagePlugin.py b/src/PIL/ImImagePlugin.py index 8b03f35da8c..1dfc808c402 100644 --- a/src/PIL/ImImagePlugin.py +++ b/src/PIL/ImImagePlugin.py @@ -30,7 +30,6 @@ import re from . import Image, ImageFile, ImagePalette -from ._binary import i8 # -------------------------------------------------------------------- # Standard tags @@ -86,16 +85,16 @@ # 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}") # -------------------------------------------------------------------- @@ -163,8 +162,8 @@ def _open(self): 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: @@ -223,14 +222,14 @@ def _open(self): 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" @@ -240,7 +239,7 @@ def _open(self): 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 @@ -341,12 +340,12 @@ 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: # Each line must be 100 characters or less, # or: SyntaxError("not an IM file") @@ -355,9 +354,9 @@ def _save(im, fp, filename): name, ext = os.path.splitext(os.path.basename(filename)) name = "".join([name[: 92 - len(ext)], ext]) - fp.write(("Name: %s\r\n" % name).encode("ascii")) + 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 3ced965e870..e5ea25fc44f 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -31,6 +31,7 @@ import math import numbers import os +import re import struct import sys import tempfile @@ -38,35 +39,30 @@ from collections.abc import Callable, MutableMapping from pathlib import Path +try: + import defusedxml.ElementTree as ElementTree +except ImportError: + ElementTree = None + # VERSION was removed in Pillow 6.0.0. -# PILLOW_VERSION is deprecated and will be removed in a future release. +# PILLOW_VERSION was removed in Pillow 9.0.0. # Use __version__ instead. -from . import ( - ImageMode, - TiffTags, - UnidentifiedImageError, - __version__, - _plugins, - _raise_version_warning, -) -from ._binary import i8, i32le +from . import ImageMode, TiffTags, UnidentifiedImageError, __version__, _plugins +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("module '{}' has no attribute '{}'".format(__name__, name)) - -else: - - from . import PILLOW_VERSION - - # Silence warning - assert PILLOW_VERSION +def __getattr__(name): + categories = {"NORMAL": 0, "SEQUENCE": 1, "CONTAINER": 2} + if name in categories: + warnings.warn( + "Image categories are deprecated and will be removed in Pillow 10 " + "(2023-07-01). Use is_animated instead.", + DeprecationWarning, + stacklevel=2, + ) + return categories[name] + raise AttributeError(f"module '{__name__}' has no attribute '{name}'") logger = logging.getLogger(__name__) @@ -80,7 +76,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) @@ -95,8 +91,8 @@ class DecompressionBombError(Exception): 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: @@ -142,8 +138,6 @@ def isImageType(t): # # Constants -NONE = 0 - # transpose FLIP_LEFT_RIGHT = 0 FLIP_TOP_BOTTOM = 1 @@ -186,11 +180,6 @@ def isImageType(t): FASTOCTREE = 2 LIBIMAGEQUANT = 3 -# categories -NORMAL = 0 -SEQUENCE = 1 -CONTAINER = 2 - if hasattr(core, "DEFAULT_STRATEGY"): DEFAULT_STRATEGY = core.DEFAULT_STRATEGY FILTERED = core.FILTERED @@ -212,28 +201,7 @@ def isImageType(t): ENCODERS = {} # -------------------------------------------------------------------- -# Modes supported by this version - -_MODEINFO = { - # NOTE: this table will be removed in future versions. use - # getmode* functions or ImageMode descriptors instead. - # official modes - "1": ("L", "L", ("1",)), - "L": ("L", "L", ("L",)), - "I": ("L", "I", ("I",)), - "F": ("L", "F", ("F",)), - "P": ("P", "L", ("P",)), - "RGB": ("RGB", "L", ("R", "G", "B")), - "RGBX": ("RGB", "L", ("R", "G", "B", "X")), - "RGBA": ("RGB", "L", ("R", "G", "B", "A")), - "CMYK": ("RGB", "L", ("C", "M", "Y", "K")), - "YCbCr": ("RGB", "L", ("Y", "Cb", "Cr")), - "LAB": ("RGB", "L", ("L", "A", "B")), - "HSV": ("RGB", "L", ("H", "S", "V")), - # Experimental modes include I;16, I;16L, I;16B, RGBa, BGR;15, and - # BGR;24. Use these modes only if you know exactly what you're - # doing... -} +# Modes if sys.byteorder == "little": _ENDIAN = "<" @@ -279,7 +247,7 @@ def _conv_type_shape(im): return (im.size[1], im.size[0], extra), typ -MODES = sorted(_MODEINFO) +MODES = ["1", "CMYK", "F", "HSV", "I", "L", "LAB", "P", "RGB", "RGBA", "RGBX", "YCbCr"] # raw modes that may be memory mapped. NOTE: if you change this, you # may have to modify the stride calculation in map.c too! @@ -402,7 +370,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) @@ -433,8 +401,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) @@ -456,8 +424,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) @@ -534,11 +502,22 @@ def __init__(self): self._size = (0, 0) self.palette = None self.info = {} - self.category = NORMAL + self._category = 0 self.readonly = 0 self.pyaccess = None self._exif = None + def __getattr__(self, name): + if name == "category": + warnings.warn( + "Image categories are deprecated and will be removed in Pillow 10 " + "(2023-07-01). Use is_animated instead.", + DeprecationWarning, + stacklevel=2, + ) + return self._category + raise AttributeError(name) + @property def width(self): return self.size[0] @@ -585,15 +564,16 @@ def close(self): This operation will destroy the image core and release its memory. The image data will be unusable afterward. - This function is only required to close images that have not - had their file read and closed by the - :py:meth:`~PIL.Image.Image.load` method. See - :ref:`file-handling` for more information. + This function is required to close images that have multiple frames or + have not had their file read and closed by the + :py:meth:`~PIL.Image.Image.load` method. See :ref:`file-handling` for + more information. """ 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) @@ -646,7 +626,7 @@ def __eq__(self, other): and self.mode == other.mode and self.size == other.size and self.info == other.info - and self.category == other.category + and self._category == other._category and self.readonly == other.readonly and self.getpalette() == other.getpalette() and self.tobytes() == other.tobytes() @@ -663,17 +643,25 @@ def __repr__(self): ) 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 - def __array_interface__(self): + class _ArrayData: + def __init__(self, new): + self.__array_interface__ = new + + def __array__(self, dtype=None): # numpy array interface support + import numpy as np + new = {} shape, typestr = _conv_type_shape(self) new["shape"] = shape @@ -685,7 +673,8 @@ def __array_interface__(self): new["data"] = self.tobytes("raw", "L") else: new["data"] = self.tobytes() - return new + + return np.array(self._ArrayData(new), dtype) def __getstate__(self): return [self.info, self.mode, self.size, self.getpalette(), self.tobytes()] @@ -716,7 +705,7 @@ def tobytes(self, encoder_name="raw", *args): :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 @@ -741,15 +730,10 @@ def tobytes(self, encoder_name="raw", *args): 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. @@ -767,9 +751,9 @@ def tobitmap(self, name="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"};", ] @@ -801,11 +785,6 @@ def frombytes(self, data, decoder_name="raw", *args): 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 @@ -823,16 +802,25 @@ def load(self): """ 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 + ) + palette_length = self.im.putpalette(mode, arr) self.palette.dirty = 0 - self.palette.mode = "RGB" self.palette.rawmode = None - if "transparency" in self.info: + if "transparency" in self.info and mode in ("LA", "PA"): if isinstance(self.info["transparency"], int): self.im.putpalettealpha(self.info["transparency"], 0) else: self.im.putpalettealphas(self.info["transparency"]) self.palette.mode = "RGBA" + else: + self.palette.mode = "RGB" + self.palette.palette = self.im.getpalette()[: palette_length * 3] if self.im: if cffi and USE_CFFI_ACCESS: @@ -864,7 +852,7 @@ def convert(self, mode=None, matrix=None, dither=None, palette=WEB, colors=256): 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"), @@ -875,24 +863,24 @@ def convert(self, mode=None, matrix=None, dither=None, palette=WEB, colors=256): 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 127 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. @@ -900,16 +888,18 @@ def convert(self, mode=None, matrix=None, dither=None, palette=WEB, colors=256): self.load() + has_transparency = self.info.get("transparency") is not None if not mode and self.mode == "P": # determine default mode if self.palette: mode = self.palette.mode else: mode = "RGB" + if mode == "RGB" and has_transparency: + mode = "RGBA" if not mode or (mode == self.mode and not matrix): return self.copy() - has_transparency = self.info.get("transparency") is not None if matrix: # matrix conversion if mode not in ("L", "RGB"): @@ -927,12 +917,8 @@ def convert_transparency(m, v): transparency = convert_transparency(matrix, transparency) elif len(mode) == 3: transparency = tuple( - [ - convert_transparency( - matrix[i * 4 : i * 4 + 4], transparency - ) - for i in range(0, len(transparency)) - ] + convert_transparency(matrix[i * 4 : i * 4 + 4], transparency) + for i in range(0, len(transparency)) ) new.info["transparency"] = transparency return new @@ -968,23 +954,30 @@ def convert_transparency(m, v): if self.mode == "P": trns_im.putpalette(self.palette) if isinstance(t, tuple): + err = "Couldn't allocate a palette color for transparency" try: - t = trns_im.palette.getcolor(t) - except Exception: - raise ValueError( - "Couldn't allocate a palette color for transparency" - ) - trns_im.putpixel((0, 0), t) - - if mode in ("L", "RGB"): - trns_im = trns_im.convert(mode) + t = trns_im.palette.getcolor(t, self) + except ValueError as e: + if str(e) == "cannot allocate more than 256 colors": + # If all 256 colors are in use, + # then there is no need for transparency + t = None + else: + raise ValueError(err) from e + if t is None: + trns = None else: - # can't just retrieve the palette number, got to do it - # after quantization. - trns_im = trns_im.convert("RGB") - trns = trns_im.getpixel((0, 0)) + trns_im.putpixel((0, 0), t) - elif self.mode == "P" and mode == "RGBA": + if mode in ("L", "RGB"): + trns_im = trns_im.convert(mode) + else: + # can't just retrieve the palette number, got to do it + # after quantization. + trns_im = trns_im.convert("RGB") + trns = trns_im.getpixel((0, 0)) + + elif self.mode == "P" and mode in ("LA", "PA", "RGBA"): t = self.info["transparency"] delete_trns = True @@ -1000,14 +993,14 @@ def convert_transparency(m, v): new = self._new(im) from . import ImagePalette - new.palette = ImagePalette.raw("RGB", new.im.getpalette("RGB")) + new.palette = ImagePalette.ImagePalette("RGB", new.im.getpalette("RGB")) if delete_trns: # This could possibly happen if we requantize to fewer colors. # The transparency would be totally off in that case. del new.info["transparency"] if trns is not None: try: - new.info["transparency"] = new.palette.getcolor(trns) + new.info["transparency"] = new.palette.getcolor(trns, new) except Exception: # if we can't make a transparent color, don't leave the old # transparency hanging around to mess us up. @@ -1026,20 +1019,29 @@ def convert_transparency(m, v): # 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 mode == "P" and palette != ADAPTIVE: + from . import ImagePalette + + new_im.palette = ImagePalette.ImagePalette("RGB", list(range(256)) * 3) if delete_trns: # crash fail if we leave a bytes transparency in an rgb/l mode. del new_im.info["transparency"] if trns is not None: if new_im.mode == "P": try: - new_im.info["transparency"] = new_im.palette.getcolor(trns) - except Exception: + new_im.info["transparency"] = new_im.palette.getcolor(trns, new_im) + except ValueError as e: del new_im.info["transparency"] - warnings.warn("Couldn't allocate palette entry for transparency") + if str(e) != "cannot allocate more than 256 colors": + # If all 256 colors are in use, + # then there is no need for transparency + warnings.warn( + "Couldn't allocate palette entry for transparency" + ) else: new_im.info["transparency"] = trns return new_im @@ -1050,16 +1052,24 @@ def quantize(self, colors=256, method=None, kmeans=0, palette=None, dither=1): 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"``). + + By default, :data:`MEDIANCUT` will be used. + + The exception to this is RGBA images. :data:`MEDIANCUT` and + :data:`MAXCOVERAGE` do not support RGBA images, so + :data:`FASTOCTREE` is used by default instead. :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 @@ -1069,11 +1079,11 @@ def quantize(self, colors=256, method=None, kmeans=0, palette=None, dither=1): if method is None: # defaults: - method = 0 + method = MEDIANCUT if self.mode == "RGBA": - method = 2 + method = FASTOCTREE - if self.mode == "RGBA" and method not in (2, 3): + if self.mode == "RGBA" and method not in (FASTOCTREE, LIBIMAGEQUANT): # Caller specified an invalid mode. raise ValueError( "Fast Octree (method == 2) and libimagequant (method == 3) " @@ -1090,14 +1100,17 @@ def quantize(self, colors=256, method=None, kmeans=0, palette=None, dither=1): "only RGB or L mode images can be quantized to a palette" ) im = self.im.convert("P", dither, palette.im) - return self._new(im) + new_im = self._new(im) + new_im.palette = palette.palette.copy() + return new_im im = self._new(self.im.quantize(colors, method, kmeans)) from . import ImagePalette mode = im.im.getpalettemode() - im.palette = ImagePalette.ImagePalette(mode, im.im.getpalette(mode, mode)) + palette = im.im.getpalette(mode, mode)[: colors * len(mode)] + im.palette = ImagePalette.ImagePalette(mode, palette) return im @@ -1187,7 +1200,7 @@ def filter(self, filter): 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 @@ -1212,7 +1225,7 @@ def filter(self, filter): 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 @@ -1238,6 +1251,10 @@ def getcolors(self, maxcolors=256): """ Returns a list of colors used in this image. + The colors will be in the image's mode. For example, an RGB image will + return a tuple of (red, green, blue) color values, and a P image will + return the index of the color in the palette. + :param maxcolors: Maximum number of colors. If this number is exceeded, this method returns None. The default limit is 256 colors. @@ -1266,7 +1283,7 @@ def getdata(self, band=None): 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 @@ -1297,10 +1314,61 @@ def getextrema(self): return tuple(extrema) return self.im.getextrema() + def _getxmp(self, xmp_tags): + def get_name(tag): + return tag.split("}")[1] + + def get_value(element): + value = {get_name(k): v for k, v in element.attrib.items()} + children = list(element) + if children: + for child in children: + name = get_name(child.tag) + child_value = get_value(child) + if name in value: + if not isinstance(value[name], list): + value[name] = [value[name]] + value[name].append(child_value) + else: + value[name] = child_value + elif value: + if element.text: + value["text"] = element.text + else: + return element.text + return value + + if ElementTree is None: + warnings.warn("XMP data cannot be read without defusedxml dependency") + return {} + else: + root = ElementTree.fromstring(xmp_tags) + return {get_name(root.tag): get_value(root)} + 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: + if "Raw profile type exif" in self.info: + exif_info = bytes.fromhex( + "".join(self.info["Raw profile type exif"].split("\n")[3:]) + ) + elif hasattr(self, "tag_v2"): + self._exif.endian = self.tag_v2._endian + self._exif.load_from_fp(self.fp, self.tag_v2._offset) + if exif_info is not None: + 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: + match = re.search(r'tiff:Orientation="([0-9])"', xmp_tags) + if match: + self._exif[0x0112] = int(match[1]) + return self._exif def getim(self): @@ -1352,7 +1420,7 @@ def getprojection(self): 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): """ @@ -1410,11 +1478,6 @@ def entropy(self, mask=None, extrema=None): 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 @@ -1496,7 +1559,7 @@ def paste(self, im, box=None, mask=None): 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 @@ -1519,8 +1582,6 @@ def alpha_composite(self, im, dest=(0, 0), source=(0, 0)): raise ValueError("Destination must be a 2-tuple") if min(source) < 0: raise ValueError("Source must be non-negative") - if min(dest) < 0: - raise ValueError("Destination must be non-negative") if len(source) == 2: source = source + im.size @@ -1553,6 +1614,13 @@ def point(self, lut, mode=None): 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 @@ -1601,16 +1669,16 @@ def putalpha(self, alpha): 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 @@ -1638,13 +1706,14 @@ def putalpha(self, alpha): def putdata(self, data, scale=1.0, offset=0.0): """ - Copies pixel data to this image. This method copies data from a - sequence object into the image, starting at the upper left - corner (0, 0), and continuing until either the image or the - sequence ends. The scale and offset values are used to adjust - the sequence values: **pixel = value*scale + offset**. + Copies pixel data from a flattened sequence object into the image. The + values should start at the upper left corner (0, 0), continue to the + end of the line, followed directly by the first value of the second + line, and so on. Data will be read until either the image or the + sequence ends. The scale and offset values are used to adjust the + sequence values: **pixel = value*scale + offset**. - :param data: A sequence object. + :param data: A flattened sequence object. :param scale: An optional scale value. The default is 1.0. :param offset: An optional offset value. The default is 0.0. """ @@ -1655,21 +1724,27 @@ def putdata(self, data, scale=1.0, offset=0.0): 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 at most 256 colors, made up of one + integer value for each channel in the raw mode. + For example, if the raw mode is "RGB", then it can contain at most 768 + values, made up of red, green and blue values for the corresponding pixel + index in the 256 colors. + If the raw mode is "RGBA", then it can contain at most 1024 values, + containing red, green, blue and alpha values. + + Alternatively, an 8-bit string may be used instead of an integer sequence. :param data: A palette sequence (either a list or a string). - :param rawmode: The raw mode of the palette. + :param rawmode: The raw mode of the palette. Either "RGB", "RGBA", or a + mode that can be transformed to "RGB" (e.g. "R", "BGR;15", "RGBA;L"). """ from . import ImagePalette if self.mode not in ("L", "LA", "P", "PA"): raise ValueError("illegal image mode") - self.load() if isinstance(data, ImagePalette.ImagePalette): palette = ImagePalette.raw(data.rawmode, data.palette) else: @@ -1716,7 +1791,7 @@ def putpixel(self, xy, value): and len(value) in [3, 4] ): # RGB or RGBA value for a P image - value = self.palette.getcolor(value) + value = self.palette.getcolor(value, self) return self.im.putpixel(xy, value) def remap_palette(self, dest_map, source_palette=None): @@ -1724,7 +1799,7 @@ def remap_palette(self, dest_map, source_palette=None): 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. @@ -1737,18 +1812,17 @@ def remap_palette(self, dest_map, source_palette=None): if source_palette is None: if self.mode == "P": - real_source_palette = self.im.getpalette("RGB")[:768] + self.load() + source_palette = self.im.getpalette("RGB")[:768] else: # L-mode - real_source_palette = bytearray(i // 3 for i in range(768)) - else: - real_source_palette = source_palette + source_palette = bytearray(i // 3 for i in range(768)) palette_bytes = b"" new_positions = [0] * 256 # pick only the used colors from the palette for i, oldPosition in enumerate(dest_map): - palette_bytes += real_source_palette[oldPosition * 3 : oldPosition * 3 + 3] + palette_bytes += source_palette[oldPosition * 3 : oldPosition * 3 + 3] new_positions[oldPosition] = i # replace the palette color id of all pixel with the new id @@ -1774,23 +1848,19 @@ def remap_palette(self, dest_map, source_palette=None): m_im = self.copy() m_im.mode = "P" - m_im.palette = ImagePalette.ImagePalette( - "RGB", palette=mapping_palette * 3, size=768 - ) + m_im.palette = ImagePalette.ImagePalette("RGB", palette=mapping_palette * 3) # possibly set palette dirty, then # m_im.putpalette(mapping_palette, 'L') # converts to 'P' # or just force it. # UNDONE -- this is part of the general issue with palettes - m_im.im.putpalette(*m_im.palette.getdata()) + m_im.im.putpalette("RGB;L", m_im.palette.tobytes()) m_im = m_im.convert("L") # Internally, we require 768 bytes for a palette. new_palette_bytes = palette_bytes + (768 - len(palette_bytes)) * b"\x00" m_im.putpalette(new_palette_bytes) - m_im.palette = ImagePalette.ImagePalette( - "RGB", palette=palette_bytes, size=len(palette_bytes) - ) + m_im.palette = ImagePalette.ImagePalette("RGB", palette=palette_bytes) return m_im @@ -1811,19 +1881,21 @@ def _get_safe_box(self, size, resample, box): min(self.size[1], math.ceil(box[3] + support_y)), ) - def resize(self, size, resample=BICUBIC, box=None, reducing_gap=None): + def resize(self, size, resample=None, box=None, reducing_gap=None): """ Returns a resized copy of this 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`. - If the image has mode "1" or "P", it is - always set to :py:attr:`PIL.Image.NEAREST`. + 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`. + If the image has mode "1" or "P", it is always set to + :py:data:`PIL.Image.NEAREST`. + If the image mode specifies a number of bits, such as "I;16", then the + default filter is :py:data:`PIL.Image.NEAREST`. + Otherwise, the default filter is :py:data:`PIL.Image.BICUBIC`. See: :ref:`concept-filters`. :param box: An optional 4-tuple of floats providing the source image region to be scaled. @@ -1844,11 +1916,14 @@ def resize(self, size, resample=BICUBIC, box=None, reducing_gap=None): :returns: An :py:class:`~PIL.Image.Image` object. """ - if resample not in (NEAREST, BILINEAR, BICUBIC, LANCZOS, BOX, HAMMING): - message = "Unknown resampling filter ({}).".format(resample) + if resample is None: + type_special = ";" in self.mode + resample = NEAREST if type_special else BICUBIC + elif resample not in (NEAREST, BILINEAR, BICUBIC, LANCZOS, BOX, HAMMING): + message = f"Unknown resampling filter ({resample})." filters = [ - "{} ({})".format(filter[1], filter[0]) + f"{filter[1]} ({filter[0]})" for filter in ( (NEAREST, "Image.NEAREST"), (LANCZOS, "Image.LANCZOS"), @@ -1878,8 +1953,8 @@ def resize(self, size, resample=BICUBIC, box=None, reducing_gap=None): if self.mode in ("1", "P"): resample = NEAREST - if self.mode in ["LA", "RGBA"]: - im = self.convert(self.mode[:-1] + "a") + if self.mode in ["LA", "RGBA"] and resample != NEAREST: + im = self.convert({"LA": "La", "RGBA": "RGBa"}[self.mode]) im = im.resize(size, resample, box) return im.convert(self.mode) @@ -1906,16 +1981,16 @@ def resize(self, size, resample=BICUBIC, box=None, reducing_gap=None): 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) @@ -1929,7 +2004,7 @@ def reduce(self, factor, box=None): return self.copy() if self.mode in ["LA", "RGBA"]: - im = self.convert(self.mode[:-1] + "a") + im = self.convert({"LA": "La", "RGBA": "RGBa"}[self.mode]) im = im.reduce(factor, box) return im.convert(self.mode) @@ -1953,12 +2028,12 @@ def rotate( :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 @@ -1980,10 +2055,8 @@ def rotate( return self.copy() if angle == 180: return self.transpose(ROTATE_180) - if angle == 90 and expand: - return self.transpose(ROTATE_90) - if angle == 270 and expand: - return self.transpose(ROTATE_270) + if angle in (90, 270) and (expand or self.width == self.height): + return self.transpose(ROTATE_90 if angle == 90 else ROTATE_270) # Calculate the affine matrix. Note that this is the reverse # transformation (from destination image to source) because we @@ -2080,18 +2153,23 @@ def save(self, fp, format=None, **params): :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. """ filename = "" open_fp = False - if isPath(fp): - filename = fp - open_fp = True - elif isinstance(fp, Path): + if isinstance(fp, Path): filename = str(fp) open_fp = True + elif isPath(fp): + filename = fp + open_fp = True + elif fp == sys.stdout: + try: + fp = sys.stdout.buffer + except AttributeError: + pass if not filename and hasattr(fp, "name") and isPath(fp.name): # only set the name for metadata purposes filename = fp.name @@ -2112,8 +2190,8 @@ def save(self, fp, format=None, **params): 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() @@ -2141,11 +2219,14 @@ def seek(self, frame): """ 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. @@ -2155,10 +2236,12 @@ def seek(self, frame): if frame != 0: raise EOFError - def show(self, title=None, command=None): + def show(self, title=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. @@ -2170,12 +2253,10 @@ def show(self, title=None, command=None): 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. """ - _show(self, title=title, command=command) + _show(self, title=title) def split(self): """ @@ -2214,8 +2295,8 @@ def getchannel(self, channel): 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)) @@ -2223,6 +2304,9 @@ def tell(self): """ 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 @@ -2243,10 +2327,12 @@ def thumbnail(self, size, resample=BICUBIC, reducing_gap=2.0): :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 @@ -2276,7 +2362,9 @@ def round_aspect(number, key): 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: abs(aspect - x / n)) + y = round_aspect( + x / aspect, key=lambda n: 0 if n == 0 else abs(aspect - x / n) + ) size = (x, y) box = None @@ -2307,36 +2395,37 @@ def transform( :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 @@ -2344,18 +2433,11 @@ def getdata(self): :returns: An :py:class:`~PIL.Image.Image` object. """ - if self.mode == "LA": - return ( - self.convert("La") - .transform(size, method, data, resample, fill, fillcolor) - .convert("LA") - ) - - if self.mode == "RGBA": + if self.mode in ("LA", "RGBA") and resample != NEAREST: return ( - self.convert("RGBa") + self.convert({"LA": "La", "RGBA": "RGBa"}[self.mode]) .transform(size, method, data, resample, fill, fillcolor) - .convert("RGBA") + .convert(self.mode) ) if isinstance(method, ImageTransformHandler): @@ -2369,6 +2451,8 @@ def getdata(self): raise ValueError("missing method data") im = new(self.mode, size, fillcolor) + if self.mode == "P" and self.palette: + im.palette = self.palette.copy() im.info = self.info.copy() if method == MESH: # list of quads @@ -2429,12 +2513,12 @@ def __transformer(self, box, image, method, data, resample=NEAREST, fill=1): 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]) + f"{filter[1]} ({filter[0]})" for filter in ( (NEAREST, "Image.NEAREST"), (BILINEAR, "Image.BILINEAR"), @@ -2458,10 +2542,10 @@ def transpose(self, method): """ 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. """ @@ -2499,12 +2583,20 @@ def toqpixmap(self): 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 @@ -2616,12 +2708,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. @@ -2633,7 +2719,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 @@ -2680,14 +2766,14 @@ 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:: from PIL import Image import numpy as np - im = Image.open('hopper.jpg') + im = Image.open("hopper.jpg") a = np.asarray(im) Then this can be used to convert it to a Pillow image:: @@ -2695,8 +2781,21 @@ def fromarray(obj, mode=None): im = Image.fromarray(a) :param obj: Object with array interface - :param mode: Mode to use (will be determined from type if None) - See: :ref:`concept-modes`. + :param mode: Optional mode to use when reading ``obj``. Will be determined from + type if ``None``. + + This will not be used to convert the data after reading, but will be used to + change how the data is read:: + + from PIL import Image + import numpy as np + a = np.full((1, 1), 300) + im = Image.fromarray(a, mode="L") + im.getpixel((0, 0)) # 44 + im = Image.fromarray(a, mode="RGB") + im.getpixel((0, 0)) # (44, 1, 0) + + See: :ref:`concept-modes` for general information about modes. :returns: An image object. .. versionadded:: 1.1.6 @@ -2708,12 +2807,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"]: @@ -2723,9 +2822,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() @@ -2789,19 +2888,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. @@ -2812,26 +2911,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 ``python3 -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): @@ -2855,8 +2965,11 @@ 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: + i = i.upper() + if i not in OPEN: + init() try: factory, accept = OPEN[i] result = not accept or accept(prefix) @@ -2878,11 +2991,11 @@ def _open_core(fp, filename, prefix): 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 @@ -3111,18 +3224,13 @@ def register_encoder(name, encoder): # -------------------------------------------------------------------- -# Simple display support. User code may override this. +# Simple display support. def _show(image, **options): - # override me, as necessary - _showxv(image, **options) - - -def _showxv(image, title=None, **options): from . import ImageShow - ImageShow.show(image, title, **options) + ImageShow.show(image, **options) # -------------------------------------------------------------------- @@ -3198,13 +3306,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() @@ -3212,7 +3320,7 @@ def _apply_env_variables(env=None): class Exif(MutableMapping): - endian = "<" + endian = None def __init__(self): self._data = {} @@ -3222,31 +3330,37 @@ def __init__(self): 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()} - def _get_ifd_dict(self, tag): + def _get_ifd_dict(self, offset): try: # an offset pointer to the location of the nested embedded IFD. # It should be a long, but may be corrupted. - self.fp.seek(self[tag]) + self.fp.seek(offset) except (KeyError, TypeError): pass 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) + def _get_head(self): + if self.endian == "<": + return b"II\x2A\x00\x08\x00\x00\x00" + else: + return b"MM\x00\x2A\x00\x00\x00\x08" + def load(self, data): # Extract EXIF information. This is highly experimental, # and is likely to be replaced with something better in a future @@ -3259,120 +3373,166 @@ def load(self, data): self._loaded_exif = data self._data.clear() self._ifds.clear() - self._info = None if not data: + self._info = None 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) - # get EXIF extension - ifd = self._get_ifd_dict(0x8769) - if ifd: - self._data.update(ifd) - self._ifds[0x8769] = ifd + def load_from_fp(self, fp, offset=None): + self._loaded_exif = None + self._data.clear() + self._ifds.clear() - def tobytes(self, offset=0): + # process dictionary from . import TiffImagePlugin - if self.endian == "<": - head = b"II\x2A\x00\x08\x00\x00\x00" + self.fp = fp + if offset is not None: + self.head = self._get_head() else: - head = b"MM\x00\x2A\x00\x00\x00\x08" + self.head = self.fp.read(8) + self._info = TiffImagePlugin.ImageFileDirectory_v2(self.head) + if self.endian is None: + self.endian = self._info._endian + if offset is None: + offset = self._info.next + self.fp.seek(offset) + self._info.load(self.fp) + + def _get_merged_dict(self): + merged_dict = dict(self) + + # get EXIF extension + if 0x8769 in self: + ifd = self._get_ifd_dict(self[0x8769]) + if ifd: + merged_dict.update(ifd) + + # GPS + if 0x8825 in self: + merged_dict[0x8825] = self._get_ifd_dict(self[0x8825]) + + return merged_dict + + def tobytes(self, offset=8): + from . import TiffImagePlugin + + head = self._get_head() ifd = TiffImagePlugin.ImageFileDirectory_v2(ifh=head) for tag, value in self.items(): + if tag in [0x8769, 0x8225, 0x8825] and not isinstance(value, dict): + value = self.get_ifd(tag) + if ( + tag == 0x8769 + and 0xA005 in value + and not isinstance(value[0xA005], dict) + ): + value = value.copy() + value[0xA005] = self.get_ifd(0xA005) ifd[tag] = value return b"Exif\x00\x00" + head + ifd.tobytes(offset) def get_ifd(self, tag): - if tag not in self._ifds and tag in self: - if tag in [0x8825, 0xA005]: - # gpsinfo, interop - self._ifds[tag] = self._get_ifd_dict(tag) - elif tag == 0x927C: # makernote - from .TiffImagePlugin import ImageFileDirectory_v2 - - if self[0x927C][:8] == b"FUJIFILM": - exif_data = self[0x927C] - ifd_offset = i32le(exif_data[8:12]) - ifd_data = exif_data[ifd_offset:] - - makernote = {} - for i in range(0, struct.unpack(" 4: - (offset,) = struct.unpack(" 4: + (offset,) = struct.unpack("H", ifd_data[:2])[0]): - ifd_tag, typ, count, data = struct.unpack( - ">HHL4s", ifd_data[i * 12 + 2 : (i + 1) * 12 + 2] - ) - if ifd_tag == 0x1101: - # CameraInfo - (offset,) = struct.unpack(">L", data) - self.fp.seek(offset) - - camerainfo = {"ModelID": self.fp.read(4)} - - self.fp.read(4) - # Seconds since 2000 - camerainfo["TimeStamp"] = i32le(self.fp.read(12)) - - self.fp.read(4) - camerainfo["InternalSerialNumber"] = self.fp.read(4) - - self.fp.read(12) - parallax = self.fp.read(4) - handler = ImageFileDirectory_v2._load_dispatch[ - TiffTags.FLOAT - ][1] - camerainfo["Parallax"] = handler( - ImageFileDirectory_v2(), parallax, False + makernote[ifd_tag] = handler( + ImageFileDirectory_v2(), data, False ) + self._ifds[tag] = dict(self._fixup_dict(makernote)) + elif self.get(0x010F) == "Nintendo": + makernote = {} + for i in range(0, struct.unpack(">H", tag_data[:2])[0]): + ifd_tag, typ, count, data = struct.unpack( + ">HHL4s", tag_data[i * 12 + 2 : (i + 1) * 12 + 2] + ) + if ifd_tag == 0x1101: + # CameraInfo + (offset,) = struct.unpack(">L", data) + self.fp.seek(offset) + + camerainfo = {"ModelID": self.fp.read(4)} + + self.fp.read(4) + # Seconds since 2000 + camerainfo["TimeStamp"] = i32le(self.fp.read(12)) + + self.fp.read(4) + camerainfo["InternalSerialNumber"] = self.fp.read(4) + + self.fp.read(12) + parallax = self.fp.read(4) + handler = ImageFileDirectory_v2._load_dispatch[ + TiffTags.FLOAT + ][1] + camerainfo["Parallax"] = handler( + ImageFileDirectory_v2(), parallax, False + ) - self.fp.read(4) - camerainfo["Category"] = self.fp.read(2) + self.fp.read(4) + camerainfo["Category"] = self.fp.read(2) - makernote = {0x1101: dict(self._fixup_dict(camerainfo))} - self._ifds[0x927C] = makernote + makernote = {0x1101: dict(self._fixup_dict(camerainfo))} + self._ifds[tag] = makernote + else: + # interop + self._ifds[tag] = self._get_ifd_dict(tag_data) return self._ifds.get(tag, {}) def __str__(self): @@ -3392,8 +3552,6 @@ def __len__(self): def __getitem__(self, tag): if self._info is not None and tag not in self._data and tag in self._info: self._data[tag] = self._fixup(self._info[tag]) - if tag == 0x8825: - self._data[tag] = self.get_ifd(tag) del self._info[tag] return self._data[tag] @@ -3408,7 +3566,8 @@ def __setitem__(self, tag, value): 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 2d13b529fef..61d3a295b3c 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 @@ -179,7 +177,6 @@ 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 @@ -196,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 @@ -212,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 @@ -228,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 @@ -244,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 @@ -260,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 @@ -276,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 @@ -293,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` """ @@ -303,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` """ @@ -313,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 661c3f33be4..60e700f0905 100644 --- a/src/PIL/ImageCms.py +++ b/src/PIL/ImageCms.py @@ -34,10 +34,10 @@ a Python / PIL interface to the littleCMS ICC Color Management System Copyright (C) 2002-2003 Kevin Cazabon kevin@cazabon.com - http://www.cazabon.com + https://www.cazabon.com - pyCMS home page: http://www.cazabon.com/pyCMS - littleCMS home page: http://www.littlecms.com + pyCMS home page: https://www.cazabon.com/pyCMS + littleCMS home page: https://www.littlecms.com (littleCMS is Copyright (C) 1998-2001 Marti Maria) Originally released under LGPL. Graciously donated to PIL in @@ -159,6 +159,14 @@ def __init__(self, profile): """ 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,8 +258,10 @@ def apply_in_place(self, im): 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": @@ -275,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 @@ -292,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. - - This function applies an ICC transformation to im from inputProfile's - color space to outputProfile's color space using the specified rendering + :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 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 @@ -332,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: """ @@ -369,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 @@ -381,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. @@ -393,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( @@ -405,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 @@ -432,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). @@ -474,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( @@ -488,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 @@ -514,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 @@ -585,7 +596,7 @@ def buildProofTransform( flags, ) except (OSError, TypeError, ValueError) as v: - raise PyCMSError(v) + raise PyCMSError(v) from v buildTransformFromOpenProfiles = buildTransform @@ -596,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: """ @@ -639,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 @@ -648,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 @@ -673,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): @@ -696,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 @@ -727,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 @@ -770,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. @@ -798,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. @@ -826,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. @@ -855,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. @@ -884,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 @@ -923,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): @@ -931,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. @@ -974,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 9cf7a991274..25f92f2c732 100644 --- a/src/PIL/ImageColor.py +++ b/src/PIL/ImageColor.py @@ -24,14 +24,16 @@ def getrgb(color): """ - Convert a color string to an RGB tuple. If the string cannot be parsed, - this function raises a :py:exc:`ValueError` exception. + Convert a color string to an RGB or RGBA tuple. If the string cannot be + parsed, this function raises a :py:exc:`ValueError` exception. .. versionadded:: 1.1.4 :param color: A color string :return: ``(red, green, blue[, alpha])`` """ + if len(color) > 100: + raise ValueError("color specifier is too long") color = color.lower() rgb = colormap.get(color, None) @@ -113,7 +115,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 7abd459f97d..610ccd4c7ee 100644 --- a/src/PIL/ImageDraw.py +++ b/src/PIL/ImageDraw.py @@ -33,8 +33,7 @@ import math import numbers -from . import Image, ImageColor - +from . import Image, ImageColor, ImageFont """ A simple 2D drawing interface for PIL images. @@ -71,6 +70,7 @@ def __init__(self, im, mode=None): self.palette = im.palette else: self.palette = None + self._image = im self.im = im.im self.draw = Image.core.draw(self.im, blend) self.mode = mode @@ -109,17 +109,17 @@ def _getink(self, ink, fill=None): if isinstance(ink, str): ink = ImageColor.getcolor(ink, self.mode) if self.palette and not isinstance(ink, numbers.Number): - ink = self.palette.getcolor(ink) + ink = self.palette.getcolor(ink, self._image) ink = self.draw.draw_ink(ink) if fill is not None: if isinstance(fill, str): fill = ImageColor.getcolor(fill, self.mode) if self.palette and not isinstance(fill, numbers.Number): - fill = self.palette.getcolor(fill) + fill = self.palette.getcolor(fill, self._image) 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: @@ -156,6 +156,8 @@ def line(self, xy, fill=None, width=0, joint=None): 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 = [ @@ -172,13 +174,11 @@ def coord_at_angle(coord, angle): angle -= 90 distance = width / 2 - 1 return tuple( - [ - p + (math.floor(p_d) if p_d > 0 else math.ceil(p_d)) - for p, p_d in ( - (x, distance * math.cos(math.radians(angle))), - (y, distance * math.sin(math.radians(angle))), - ) - ] + p + (math.floor(p_d) if p_d > 0 else math.ceil(p_d)) + for p, p_d in ( + (x, distance * math.cos(math.radians(angle))), + (y, distance * math.sin(math.radians(angle))), + ) ) flipped = ( @@ -233,13 +233,42 @@ def point(self, xy, fill=None): if ink is not None: self.draw.draw_points(xy, ink) - def polygon(self, xy, fill=None, outline=None): + def polygon(self, xy, fill=None, outline=None, width=1): """Draw a polygon.""" ink, fill = self._getink(outline, fill) if fill is not None: self.draw.draw_polygon(xy, fill, 1) - if ink is not None and ink != fill: - self.draw.draw_polygon(xy, ink, 0) + if ink is not None and ink != fill and width != 0: + if width == 1: + self.draw.draw_polygon(xy, ink, 0, width) + else: + # To avoid expanding the polygon outwards, + # use the fill as a mask + mask = Image.new("1", self.im.size) + mask_ink = self._getink(1)[0] + + fill_im = mask.copy() + draw = Draw(fill_im) + draw.draw.draw_polygon(xy, mask_ink, 1) + + ink_im = mask.copy() + draw = Draw(ink_im) + width = width * 2 - 1 + draw.draw.draw_polygon(xy, mask_ink, 0, width) + + mask.paste(ink_im, mask=fill_im) + + im = Image.new(self.mode, self.im.size) + draw = Draw(im) + draw.draw.draw_polygon(xy, ink, 0, width) + self.im.paste(im.im, (0, 0) + im.size, mask.im) + + 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.""" @@ -249,6 +278,89 @@ def rectangle(self, xy, fill=None, outline=None, width=1): if ink is not None and ink != fill and width != 0: self.draw.draw_rectangle(xy, ink, 0, width) + def rounded_rectangle(self, xy, radius=0, fill=None, outline=None, width=1): + """Draw a rounded rectangle.""" + if isinstance(xy[0], (list, tuple)): + (x0, y0), (x1, y1) = xy + else: + x0, y0, x1, y1 = xy + + d = radius * 2 + + full_x = d >= x1 - x0 + if full_x: + # The two left and two right corners are joined + d = x1 - x0 + full_y = d >= y1 - y0 + if full_y: + # The two top and two bottom corners are joined + d = y1 - y0 + if full_x and full_y: + # If all corners are joined, that is a circle + return self.ellipse(xy, fill, outline, width) + + if d == 0: + # If the corners have no curve, that is a rectangle + return self.rectangle(xy, fill, outline, width) + + r = d // 2 + ink, fill = self._getink(outline, fill) + + def draw_corners(pieslice): + if full_x: + # Draw top and bottom halves + parts = ( + ((x0, y0, x0 + d, y0 + d), 180, 360), + ((x0, y1 - d, x0 + d, y1), 0, 180), + ) + elif full_y: + # Draw left and right halves + parts = ( + ((x0, y0, x0 + d, y0 + d), 90, 270), + ((x1 - d, y0, x1, y0 + d), 270, 90), + ) + else: + # Draw four separate corners + parts = ( + ((x1 - d, y0, x1, y0 + d), 270, 360), + ((x1 - d, y1 - d, x1, y1), 0, 90), + ((x0, y1 - d, x0 + d, y1), 90, 180), + ((x0, y0, x0 + d, y0 + d), 180, 270), + ) + for part in parts: + if pieslice: + self.draw.draw_pieslice(*(part + (fill, 1))) + else: + self.draw.draw_arc(*(part + (ink, width))) + + if fill is not None: + draw_corners(True) + + if full_x: + self.draw.draw_rectangle((x0, y0 + r + 1, x1, y1 - r - 1), fill, 1) + else: + self.draw.draw_rectangle((x0 + r + 1, y0, x1 - r - 1, y1), fill, 1) + if not full_x and not full_y: + self.draw.draw_rectangle((x0, y0 + r + 1, x0 + r, y1 - r - 1), fill, 1) + self.draw.draw_rectangle((x1 - r, y0 + r + 1, x1, y1 - r - 1), fill, 1) + if ink is not None and ink != fill and width != 0: + draw_corners(False) + + if not full_x: + self.draw.draw_rectangle( + (x0 + r + 1, y0, x1 - r - 1, y0 + width - 1), ink, 1 + ) + self.draw.draw_rectangle( + (x0 + r + 1, y1 - width + 1, x1 - r - 1, y1), ink, 1 + ) + if not full_y: + self.draw.draw_rectangle( + (x0, y0 + r + 1, x0 + width - 1, y1 - r - 1), ink, 1 + ) + self.draw.draw_rectangle( + (x1 - width + 1, y0 + r + 1, x1, y1 - r - 1), ink, 1 + ) + def _multiline_check(self, text): """Draw text.""" split_character = "\n" if isinstance(text, str) else b"\n" @@ -274,8 +386,9 @@ def text( 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 +404,12 @@ def text( 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 +420,20 @@ def getink(fill): 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 +442,13 @@ def draw_text(ink, stroke_width=0, stroke_offset=None): try: mask = font.getmask( text, - self.fontmode, + mode, direction, features, language, stroke_width, + anchor, + ink, *args, **kwargs, ) @@ -332,7 +456,15 @@ def draw_text(ink, stroke_width=0, stroke_offset=None): 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 +477,7 @@ def draw_text(ink, stroke_width=0, stroke_offset=None): 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 +496,18 @@ def multiline_text( 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 +515,38 @@ def multiline_text( 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 +558,9 @@ def multiline_text( 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 +604,174 @@ def multiline_textsize( 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() + if not isinstance(font, ImageFont.FreeTypeFont): + raise ValueError("Only supported for TrueType fonts") + 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,11 +877,128 @@ 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. """ if isinstance(color2, tuple): - return sum([abs(color1[i] - color2[i]) for i in range(0, len(color2))]) + return sum(abs(color1[i] - color2[i]) for i in range(0, len(color2))) else: return abs(color1 - color2) diff --git a/src/PIL/ImageDraw2.py b/src/PIL/ImageDraw2.py index 20b5fe4c499..1f63110fd26 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 @@ def __init__(self, color, file, size=12): 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 @@ def render(self, op, xy, pen, brush=None): 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 6287968652e..3374a5b1dae 100644 --- a/src/PIL/ImageFile.py +++ b/src/PIL/ImageFile.py @@ -28,6 +28,7 @@ # import io +import itertools import struct import sys @@ -39,6 +40,7 @@ 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,23 +49,24 @@ -8: "bad configuration", -9: "out of memory error", } +"""Dict of known error codes returned from :meth:`.PyDecoder.decode`.""" -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 = "decoder error %d" % error + message = f"decoder error {error}" raise OSError(message + " when reading image file") -# -# -------------------------------------------------------------------- -# Helpers - - def _tilesort(t): # sort on offset return t[2] @@ -75,7 +78,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 +88,8 @@ def __init__(self, fp=None, filename=None): 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 +117,7 @@ def __init__(self, fp=None, filename=None): 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 +145,10 @@ def verify(self): 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 @@ -178,24 +183,14 @@ def load(self): and args[0] in Image._MAPMODES ): try: - if hasattr(Image.core, "map"): - # use built-in mapper WIN32 only - self.map = Image.core.map(self.filename) - self.map.seek(offset) - self.im = self.map.readimage( - self.mode, self.size, args[1], args[2] - ) - else: - # use mmap, if possible - import mmap - - with open(self.filename, "r") as fp: - self.map = mmap.mmap( - fp.fileno(), 0, access=mmap.ACCESS_READ - ) - self.im = Image.core.map_buffer( - self.map, self.size, decoder_name, offset, args - ) + # use mmap, if possible + import mmap + + with open(self.filename) as fp: + self.map = mmap.mmap(fp.fileno(), 0, access=mmap.ACCESS_READ) + self.im = Image.core.map_buffer( + self.map, self.size, decoder_name, offset, args + ) readonly = 1 # After trashing self.im, # we might need to reload the palette data. @@ -216,6 +211,13 @@ def load(self): except AttributeError: prefix = b"" + # Remove consecutive duplicates that only differ by their offset + self.tile = [ + list(tiles)[-1] + for _, tiles in itertools.groupby( + self.tile, lambda tile: (tile[0], tile[1], tile[3]) + ) + ] for decoder_name, extents, offset, args in self.tile: decoder = Image._getdecoder( self.mode, decoder_name, args, self.decoderconfig @@ -231,12 +233,12 @@ def load(self): 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 +246,7 @@ def load(self): else: raise OSError( "image file is truncated " - "(%d bytes not processed)" % len(b) + f"({len(b)} bytes not processed)" ) b = b + s @@ -267,7 +269,7 @@ def load(self): 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 +322,7 @@ def _open(self): 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 +360,7 @@ def feed(self, data): (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 +392,7 @@ def feed(self, data): if e < 0: # decoding error self.image = None - raise_ioerror(e) + raise_oserror(e) else: # end of image return @@ -444,7 +446,7 @@ def close(self): (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. """ @@ -489,13 +491,10 @@ def _save(im, fp, tile, bufsize=0): # But, it would need at least the image size in most cases. RawEncode is # a tricky case. bufsize = max(MAXBLOCK, bufsize, im.size[0] * 4) # see RawEncode.c - if fp == sys.stdout: - fp.flush() - return 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 +511,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 +526,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() @@ -541,19 +540,28 @@ def _safe_read(fp, size): :param fp: File handle. Must implement a read method. :param size: Number of bytes to read. - :returns: A string containing up to size bytes of data. + :returns: A string containing size bytes of data. + + Raises an OSError if the file is truncated and the read cannot be completed + """ if size <= 0: return b"" if size <= SAFEBLOCK: - return fp.read(size) + data = fp.read(size) + if len(data) < size: + raise OSError("Truncated File Read") + return data data = [] - while size > 0: - block = fp.read(min(size, SAFEBLOCK)) + remaining_size = size + while remaining_size > 0: + block = fp.read(min(remaining_size, SAFEBLOCK)) if not block: break data.append(block) - size -= len(block) + remaining_size -= len(block) + if sum(len(d) for d in data) < size: + raise OSError("Truncated File Read") return b"".join(data) @@ -571,7 +579,7 @@ def extents(self): 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 +611,9 @@ def decode(self, buffer): 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 6b0f5eb376b..d2ece37520e 100644 --- a/src/PIL/ImageFilter.py +++ b/src/PIL/ImageFilter.py @@ -16,11 +16,6 @@ # import functools -try: - import numpy -except ImportError: # pragma: no cover - numpy = None - class Filter: pass @@ -49,7 +44,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 +64,7 @@ def __init__(self, size, kernel, scale=None, offset=0): 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, @@ -154,9 +149,11 @@ def filter(self, image): class GaussianBlur(MultibandFilter): - """Gaussian blur filter. + """Blurs the image with a sequence of extended box filters, which + approximates a Gaussian kernel. For details on accuracy see + - :param radius: Blur radius. + :param radius: Standard deviation of the Gaussian kernel. """ name = "GaussianBlur" @@ -369,6 +366,13 @@ def __init__(self, size, table, channels=3, target_mode=None, **kwargs): items = size[0] * size[1] * size[2] wrong_size = False + numpy = None + if hasattr(table, "shape"): + try: + import numpy + except ImportError: # pragma: no cover + pass + if numpy and isinstance(table, numpy.ndarray): if copy_table: table = table.copy() @@ -401,9 +405,8 @@ def __init__(self, size, table, channels=3, target_mode=None, **kwargs): 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 +414,10 @@ def __init__(self, size, table, channels=3, target_mode=None, **kwargs): 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 +516,12 @@ def transform(self, callback, with_normals=False, channels=None, target_mode=Non 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 027e4c42e6c..805c8fff96b 100644 --- a/src/PIL/ImageFont.py +++ b/src/PIL/ImageFont.py @@ -196,6 +196,13 @@ def load_from_bytes(f): else: load_from_bytes(font) + def __getstate__(self): + return [self.path, self.size, self.index, self.encoding, self.layout_engine] + + def __setstate__(self, state): + path, size, index, encoding, layout_engine = state + self.__init__(path, size, index, encoding, layout_engine) + def _multiline_split(self, text): split_character = "\n" if isinstance(text, str) else b"\n" return text.split(split_character) @@ -215,6 +222,147 @@ def getmetrics(self): """ 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 ): @@ -222,6 +370,15 @@ def getsize( 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 @@ -248,7 +405,7 @@ def getsize( 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 @@ -259,9 +416,11 @@ def getsize( :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], ) @@ -303,7 +462,7 @@ def getsize_multiline( 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 @@ -345,12 +504,15 @@ def getmask( 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 @@ -384,7 +546,7 @@ def getmask( 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 @@ -393,6 +555,16 @@ def getmask( .. 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. """ @@ -403,6 +575,8 @@ def getmask( features=features, language=language, stroke_width=stroke_width, + anchor=anchor, + ink=ink, )[0] def getmask2( @@ -414,14 +588,17 @@ def getmask2( 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 @@ -455,7 +632,7 @@ def getmask2( 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 @@ -464,15 +641,29 @@ def getmask2( .. 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 + Image._decompression_bomb_check(size) + 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 @@ -499,18 +690,18 @@ def font_variant( 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): @@ -529,12 +720,12 @@ def set_variation_by_name(self, name): 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 @@ -542,12 +733,12 @@ def get_variation_axes(self): 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: @@ -586,7 +777,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) @@ -636,9 +827,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): @@ -698,7 +894,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 66e2e856009..b93ec3f2a6c 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 @@ -21,8 +21,8 @@ if sys.platform == "darwin": import os - import tempfile import subprocess + import tempfile def grab(bbox=None, include_layered_windows=False, all_screens=False, xdisplay=None): @@ -60,7 +60,7 @@ def grab(bbox=None, include_layered_windows=False, all_screens=False, xdisplay=N return im # use xdisplay=None for default display on non-win32/macOS systems if not Image.core.HAVE_XCB: - raise IOError("Pillow was built without XCB support") + 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: @@ -93,12 +93,28 @@ def grabclipboard(): os.unlink(filepath) return im elif sys.platform == "win32": - data = Image.core.grabclipboard_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 adbb940000e..47ad3c9aefb 100644 --- a/src/PIL/ImageMath.py +++ b/src/PIL/ImageMath.py @@ -41,7 +41,7 @@ def __fixup(self, im1): 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 @@ def apply(self, op, im1, im2=None, mode=None): 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 @@ def apply(self, op, im1, im2=None, mode=None): 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) @@ -246,7 +246,19 @@ def eval(expression, _dict={}, **kw): if hasattr(v, "im"): args[k] = _Operand(v) - out = builtins.eval(expression, args) + compiled_code = compile(expression, "", "eval") + + def scan(code): + for const in code.co_consts: + if type(const) == type(compiled_code): + scan(const) + + for name in code.co_names: + if name not in args and name != "abs": + raise ValueError(f"'{name}' not allowed") + + scan(compiled_code) + out = builtins.eval(expression, {"__builtins": {"abs": abs}}, args) try: return out.im except AttributeError: diff --git a/src/PIL/ImageMode.py b/src/PIL/ImageMode.py index 988288329b8..0afcf9fe129 100644 --- a/src/PIL/ImageMode.py +++ b/src/PIL/ImageMode.py @@ -35,18 +35,28 @@ def getmode(mode): global _modes if not _modes: # initialize mode cache - - from . import Image - modes = {} - # core modes - for m, (basemode, basetype, bands) in Image._MODEINFO.items(): + for m, (basemode, basetype, bands) in { + # core modes + "1": ("L", "L", ("1",)), + "L": ("L", "L", ("L",)), + "I": ("L", "I", ("I",)), + "F": ("L", "F", ("F",)), + "P": ("P", "L", ("P",)), + "RGB": ("RGB", "L", ("R", "G", "B")), + "RGBX": ("RGB", "L", ("R", "G", "B", "X")), + "RGBA": ("RGB", "L", ("R", "G", "B", "A")), + "CMYK": ("RGB", "L", ("C", "M", "Y", "K")), + "YCbCr": ("RGB", "L", ("Y", "Cb", "Cr")), + "LAB": ("RGB", "L", ("L", "A", "B")), + "HSV": ("RGB", "L", ("H", "S", "V")), + # extra experimental modes + "RGBa": ("RGB", "L", ("R", "G", "B", "a")), + "LA": ("L", "L", ("L", "A")), + "La": ("L", "L", ("L", "a")), + "PA": ("RGB", "L", ("P", "A")), + }.items(): modes[m] = ModeDescriptor(m, bands, basemode, basetype) - # extra experimental modes - modes["RGBa"] = ModeDescriptor("RGBa", ("R", "G", "B", "a"), "RGB", "L") - modes["LA"] = ModeDescriptor("LA", ("L", "A"), "L", "L") - modes["La"] = ModeDescriptor("La", ("L", "a"), "L", "L") - modes["PA"] = ModeDescriptor("PA", ("P", "A"), "RGB", "L") # mapping modes for i16mode in ( "I;16", diff --git a/src/PIL/ImageMorph.py b/src/PIL/ImageMorph.py index d1ec09eace4..fe0083754f5 100644 --- a/src/PIL/ImageMorph.py +++ b/src/PIL/ImageMorph.py @@ -28,36 +28,36 @@ 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() """ @@ -196,7 +196,7 @@ def apply(self, image): raise Exception("No operator loaded") if image.mode != "L": - raise Exception("Image must be binary, meaning it must use mode L") + raise ValueError("Image mode must be L") outimage = Image.new(image.mode, image.size, None) count = _imagingmorph.apply(bytes(self.lut), image.im.id, outimage.im.id) return count, outimage @@ -211,7 +211,7 @@ def match(self, image): raise Exception("No operator loaded") if image.mode != "L": - raise Exception("Image must be binary, meaning it must use mode L") + raise ValueError("Image mode must be L") return _imagingmorph.match(bytes(self.lut), image.im.id) def get_on_pixels(self, image): @@ -221,7 +221,7 @@ def get_on_pixels(self, image): of all matching pixels. See :ref:`coordinate-system`.""" if image.mode != "L": - raise Exception("Image must be binary, meaning it must use mode L") + raise ValueError("Image mode must be L") return _imagingmorph.get_on_pixels(image.im.id) def load_lut(self, filename): diff --git a/src/PIL/ImageOps.py b/src/PIL/ImageOps.py index e4e0840b8a9..b170e9d8cc9 100644 --- a/src/PIL/ImageOps.py +++ b/src/PIL/ImageOps.py @@ -19,6 +19,7 @@ import functools import operator +import re from . import Image @@ -61,20 +62,33 @@ def _lut(image, lut): # actions -def autocontrast(image, cutoff=0, ignore=None): +def autocontrast(image, cutoff=0, ignore=None, mask=None, preserve_tone=False): """ 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. + :param preserve_tone: Preserve image tone in Photoshop-like style autocontrast. + + .. versionadded:: 8.2.0 + :return: An image. """ - histogram = image.histogram() + if preserve_tone: + histogram = image.convert("L").histogram(mask) + else: + histogram = image.histogram(mask) + lut = [] for layer in range(0, len(histogram), 256): h = histogram[layer : layer + 256] @@ -88,12 +102,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 +119,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 +158,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. @@ -221,15 +237,43 @@ def colorize(image, black, white, mid=None, blackpoint=0, whitepoint=255, midpoi return _lut(image, red + green + blue) +def contain(image, size, method=Image.BICUBIC): + """ + Returns a resized version of the image, set to the maximum width and height + within the requested size, while maintaining the original aspect ratio. + + :param image: The image to resize and crop. + :param size: The requested output size in pixels, given as a + (width, height) tuple. + :param method: Resampling method to use. Default is + :py:attr:`PIL.Image.BICUBIC`. See :ref:`concept-filters`. + :return: An image. + """ + + im_ratio = image.width / image.height + dest_ratio = size[0] / size[1] + + if im_ratio != dest_ratio: + if im_ratio > dest_ratio: + new_height = int(image.height / image.width * size[0]) + if new_height != size[1]: + size = (size[0], new_height) + else: + new_width = int(image.width / image.height * size[1]) + if new_width != size[0]: + size = (new_width, size[1]) + return image.resize(size, resample=method) + + def pad(image, size, method=Image.BICUBIC, color=None, centering=(0.5, 0.5)): """ - Returns a sized and padded version of the image, expanded to fill the + Returns a resized and padded version of the image, expanded to fill the requested aspect ratio and size. - :param image: The image to size and crop. + :param image: The image to resize and crop. :param size: The requested output size in pixels, given as a (width, height) tuple. - :param method: What resampling method to use. Default is + :param method: Resampling method to use. Default is :py:attr:`PIL.Image.BICUBIC`. See :ref:`concept-filters`. :param color: The background color of the padded image. :param centering: Control the position of the original image within the @@ -242,27 +286,17 @@ def pad(image, size, method=Image.BICUBIC, color=None, centering=(0.5, 0.5)): :return: An image. """ - im_ratio = image.width / image.height - dest_ratio = size[0] / size[1] - - if im_ratio == dest_ratio: - out = image.resize(size, resample=method) + resized = contain(image, size, method) + if resized.size == size: + out = resized else: out = Image.new(image.mode, size, color) - if im_ratio > dest_ratio: - new_height = int(image.height / image.width * size[0]) - if new_height != size[1]: - image = image.resize((size[0], new_height), resample=method) - - y = int((size[1] - new_height) * max(0, min(centering[1], 1))) - out.paste(image, (0, y)) + if resized.width != size[0]: + x = int((size[0] - resized.width) * max(0, min(centering[0], 1))) + out.paste(resized, (x, 0)) else: - new_width = int(image.width / image.height * size[1]) - if new_width != size[0]: - image = image.resize((new_width, size[1]), resample=method) - - x = int((size[0] - new_width) * max(0, min(centering[0], 1))) - out.paste(image, (x, 0)) + y = int((size[1] - resized.height) * max(0, min(centering[1], 1))) + out.paste(resized, (0, y)) return out @@ -289,7 +323,7 @@ def scale(image, factor, resample=Image.BICUBIC): :param image: The image to rescale. :param factor: The expansion factor, as a float. - :param resample: What resampling method to use. Default is + :param resample: Resampling method to use. Default is :py:attr:`PIL.Image.BICUBIC`. See :ref:`concept-filters`. :returns: An :py:class:`~PIL.Image.Image` object. """ @@ -308,7 +342,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. @@ -359,22 +393,32 @@ def expand(image, border=0, fill=0): left, top, right, bottom = _border(border) width = left + image.size[0] + right height = top + image.size[1] + bottom - out = Image.new(image.mode, (width, height), _color(fill, image.mode)) + color = _color(fill, image.mode) + if image.mode == "P" and image.palette: + image.load() + palette = image.palette.copy() + if isinstance(color, tuple): + color = palette.getcolor(color) + else: + palette = None + out = Image.new(image.mode, (width, height), color) + if palette: + out.putpalette(palette.palette) out.paste(image, (left, top)) return out def fit(image, size, method=Image.BICUBIC, bleed=0.0, centering=(0.5, 0.5)): """ - Returns a sized and cropped version of the image, cropped to the + Returns a resized and cropped version of the image, cropped to the requested aspect ratio and size. This function was contributed by Kevin Cazabon. - :param image: The image to size and crop. + :param image: The image to resize and crop. :param size: The requested output size in pixels, given as a (width, height) tuple. - :param method: What resampling method to use. Default is + :param method: Resampling method to use. Default is :py:attr:`PIL.Image.BICUBIC`. See :ref:`concept-filters`. :param bleed: Remove a border around the outside of the image from all four edges. The value is a decimal percentage (use 0.01 for @@ -395,7 +439,7 @@ def fit(image, size, method=Image.BICUBIC, bleed=0.0, centering=(0.5, 0.5)): # by Kevin Cazabon, Feb 17/2000 # kevin@cazabon.com - # http://www.cazabon.com + # https://www.cazabon.com # ensure centering is mutable centering = list(centering) @@ -545,7 +589,20 @@ def exif_transpose(image): }.get(orientation) if method is not None: transposed_image = image.transpose(method) - del exif[0x0112] - transposed_image.info["exif"] = exif.tobytes() + transposed_exif = transposed_image.getexif() + if 0x0112 in transposed_exif: + del transposed_exif[0x0112] + if "exif" in transposed_image.info: + transposed_image.info["exif"] = transposed_exif.tobytes() + elif "Raw profile type exif" in transposed_image.info: + transposed_image.info[ + "Raw profile type exif" + ] = transposed_exif.tobytes().hex() + elif "XML:com.adobe.xmp" in transposed_image.info: + transposed_image.info["XML:com.adobe.xmp"] = re.sub( + r'tiff:Orientation="([0-9])"', + "", + transposed_image.info["XML:com.adobe.xmp"], + ) return transposed_image return image.copy() diff --git a/src/PIL/ImagePalette.py b/src/PIL/ImagePalette.py index e0d439c9841..1e0d36b4167 100644 --- a/src/PIL/ImagePalette.py +++ b/src/PIL/ImagePalette.py @@ -17,6 +17,7 @@ # import array +import warnings from . import GimpGradientFile, GimpPaletteFile, ImageColor, PaletteFile @@ -25,27 +26,45 @@ class ImagePalette: """ Color palette for palette mapped images - :param mode: The mode to use for the Palette. See: + :param mode: The mode to use for the palette. See: :ref:`concept-modes`. Defaults to "RGB" :param palette: An optional palette. If given, it must be a bytearray, - an array or a list of ints between 0-255 and of length ``size`` - times the number of colors in ``mode``. The list must be aligned - by channel (All R values must be contiguous in the list before G - and B values.) Defaults to 0 through 255 per channel. - :param size: An optional palette size. If given, it cannot be equal to - or greater than 256. Defaults to 0. + an array or a list of ints between 0-255. The list must consist of + all channels for one color followed by the next color (e.g. RGBRGBRGB). + Defaults to an empty palette. + :param size: An optional palette size. If given, an error is raised + if ``palette`` is not of equal length. """ def __init__(self, mode="RGB", palette=None, size=0): self.mode = mode self.rawmode = None # if set, palette contains raw data - self.palette = palette or bytearray(range(256)) * len(self.mode) - self.colors = {} + self.palette = palette or bytearray() self.dirty = None - if (size == 0 and len(self.mode) * 256 != len(self.palette)) or ( - size != 0 and size != len(self.palette) - ): - raise ValueError("wrong palette size") + if size != 0: + warnings.warn( + "The size parameter is deprecated and will be removed in Pillow 10 " + "(2023-07-01).", + DeprecationWarning, + ) + if size != len(self.palette): + raise ValueError("wrong palette size") + + @property + def palette(self): + return self._palette + + @palette.setter + def palette(self, palette): + self._palette = palette + + mode_len = len(self.mode) + self.colors = {} + for i in range(0, len(self.palette), mode_len): + color = tuple(self.palette[i : i + mode_len]) + if color in self.colors: + continue + self.colors[color] = i // mode_len def copy(self): new = ImagePalette() @@ -54,7 +73,6 @@ def copy(self): new.rawmode = self.rawmode if self.palette is not None: new.palette = self.palette[:] - new.colors = self.colors.copy() new.dirty = self.dirty return new @@ -68,7 +86,7 @@ def getdata(self): """ if self.rawmode: return self.rawmode, self.palette - return self.mode + ";L", self.tobytes() + return self.mode, self.tobytes() def tobytes(self): """Convert palette to bytes. @@ -80,14 +98,12 @@ def tobytes(self): if isinstance(self.palette, bytes): return self.palette arr = array.array("B", self.palette) - if hasattr(arr, "tobytes"): - return arr.tobytes() - return arr.tostring() + return arr.tobytes() # Declare tostring as an alias for tobytes tostring = tobytes - def getcolor(self, color): + def getcolor(self, color, image=None): """Given an rgb tuple, allocate palette entry. .. warning:: This method is experimental. @@ -95,23 +111,49 @@ def getcolor(self, color): if self.rawmode: raise ValueError("palette contains raw palette data") if isinstance(color, tuple): + if self.mode == "RGB": + if len(color) == 4 and color[3] == 255: + color = color[:3] + elif self.mode == "RGBA": + if len(color) == 3: + color += (255,) 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 not isinstance(self.palette, bytearray): + self._palette = bytearray(self.palette) + index = len(self.palette) // 3 + special_colors = () + if image: + special_colors = ( + image.info.get("background"), + image.info.get("transparency"), + ) + while index in special_colors: + index += 1 if index >= 256: - raise ValueError("cannot allocate more than 256 colors") + if image: + # Search for an unused index + for i, count in reversed(list(enumerate(image.histogram()))): + if count == 0 and i not in special_colors: + index = i + break + if index >= 256: + 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] - self.palette[index + 512] = color[2] + if index * 3 < len(self.palette): + self._palette = ( + self.palette[: index * 3] + + bytes(color) + + self.palette[index * 3 + 3 :] + ) + else: + self._palette += bytes(color) 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 +165,12 @@ def save(self, fp): 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") @@ -169,9 +211,9 @@ def make_gamma_lut(exp): def negative(mode="RGB"): - palette = list(range(256)) + palette = list(range(256 * len(mode))) palette.reverse() - return ImagePalette(mode, palette * len(mode)) + return ImagePalette(mode, [i // len(mode) for i in palette]) def random(mode="RGB"): @@ -184,15 +226,13 @@ def random(mode="RGB"): def sepia(white="#fff0c0"): - r, g, b = ImageColor.getrgb(white) - r = make_linear_lut(0, r) - g = make_linear_lut(0, g) - b = make_linear_lut(0, b) - return ImagePalette("RGB", r + g + b) + bands = [make_linear_lut(0, band) for band in ImageColor.getrgb(white)] + return ImagePalette("RGB", [bands[i % 3][i // 3] for i in range(256 * 3)]) def wedge(mode="RGB"): - return ImagePalette(mode, list(range(256)) * len(mode)) + palette = list(range(256 * len(mode))) + return ImagePalette(mode, [i // len(mode) for i in palette]) def load(filename): diff --git a/src/PIL/ImageQt.py b/src/PIL/ImageQt.py index dfe2f80bd81..db8fa0fa9f9 100644 --- a/src/PIL/ImageQt.py +++ b/src/PIL/ImageQt.py @@ -22,18 +22,29 @@ from . import Image from ._util import isPath -qt_versions = [["5", "PyQt5"], ["side2", "PySide2"]] +qt_versions = [ + ["6", "PyQt6"], + ["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 == "PyQt6": + from PyQt6.QtCore import QBuffer, QIODevice + from PyQt6.QtGui import QImage, QPixmap, qRgba + elif 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 @@ -52,11 +63,17 @@ def rgb(r, g, b, a=255): def fromqimage(im): """ - :param im: A PIL Image object, or a file name - (given either as Python string or a PyQt string object) + :param im: QImage or PIL ImageQt object """ buffer = QBuffer() - buffer.open(QIODevice.ReadWrite) + if qt_version == "6": + try: + qt_openmode = QIODevice.OpenModeFlag + except AttributeError: + qt_openmode = QIODevice.OpenMode + else: + qt_openmode = QIODevice + buffer.open(qt_openmode.ReadWrite) # preserve alpha channel with png # otherwise ppm is more friendly with Image.open if im.hasAlphaChannel(): @@ -91,7 +108,7 @@ def align8to32(bytes, width, mode): converts each scanline of data from 8 bit to 32 bit aligned """ - bits_per_pixel = {"1": 1, "L": 8, "P": 8}[mode] + bits_per_pixel = {"1": 1, "L": 8, "P": 8, "I;16": 16}[mode] # calculate bytes per line and the extra padding if needed bits_per_line = bits_per_pixel * width @@ -117,6 +134,7 @@ def align8to32(bytes, width, mode): def _toqclass_helper(im): data = None colortable = None + exclusive_fp = False # handle filename, if given instead of image name if hasattr(im, "toUtf8"): @@ -124,36 +142,45 @@ def _toqclass_helper(im): im = str(im.toUtf8(), "utf-8") if isPath(im): im = Image.open(im) + exclusive_fp = True + qt_format = QImage.Format if qt_version == "6" else QImage if im.mode == "1": - format = QImage.Format_Mono + format = qt_format.Format_Mono elif im.mode == "L": - format = QImage.Format_Indexed8 + format = qt_format.Format_Indexed8 colortable = [] for i in range(256): colortable.append(rgb(i, i, i)) elif im.mode == "P": - format = QImage.Format_Indexed8 + format = qt_format.Format_Indexed8 colortable = [] palette = im.getpalette() for i in range(0, len(palette), 3): colortable.append(rgb(*palette[i : i + 3])) elif im.mode == "RGB": - data = im.tobytes("raw", "BGRX") - format = QImage.Format_RGB32 + # Populate the 4th channel with 255 + im = im.convert("RGBA") + + data = im.tobytes("raw", "BGRA") + format = qt_format.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)) - format = QImage.Format_ARGB32 + data = im.tobytes("raw", "BGRA") + format = qt_format.Format_ARGB32 + elif im.mode == "I;16" and hasattr(qt_format, "Format_Grayscale16"): # Qt 5.13+ + im = im.point(lambda i: i * 256) + + format = qt_format.Format_Grayscale16 else: - raise ValueError("unsupported image mode %r" % im.mode) + if exclusive_fp: + im.close() + 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} + size = im.size + __data = data or align8to32(im.tobytes(), size[0], im.mode) + if exclusive_fp: + im.close() + return {"data": __data, "size": size, "format": format, "colortable": colortable} if qt_is_installed: @@ -175,8 +202,8 @@ def __init__(self, im): self.__data = im_data["data"] super().__init__( self.__data, - im_data["im"].size[0], - im_data["im"].size[1], + im_data["size"][0], + im_data["size"][1], im_data["format"], ) if im_data["colortable"]: @@ -190,11 +217,7 @@ def toqimage(im): def toqpixmap(im): # # This doesn't work. For now using a dumb approach. # im_data = _toqclass_helper(im) - # result = QPixmap(im_data['im'].size[0], im_data['im'].size[1]) - # result.loadFromData(im_data['data']) - # Fix some strange bug that causes - if im.mode == "RGB": - im = im.convert("RGBA") - + # result = QPixmap(im_data["size"][0], im_data["size"][1]) + # result.loadFromData(im_data["data"]) qimage = toqimage(im) return QPixmap.fromImage(qimage) diff --git a/src/PIL/ImageSequence.py b/src/PIL/ImageSequence.py index 4e9f5c210b7..9df910a4330 100644 --- a/src/PIL/ImageSequence.py +++ b/src/PIL/ImageSequence.py @@ -38,8 +38,8 @@ def __getitem__(self, ix): 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 @@ def __next__(self): 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 fc50894236b..c928fc30590 100644 --- a/src/PIL/ImageShow.py +++ b/src/PIL/ImageShow.py @@ -15,7 +15,6 @@ import shutil import subprocess import sys -import tempfile from shlex import quote from PIL import Image @@ -24,6 +23,14 @@ 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 +38,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 +47,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 +63,14 @@ 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,21 +81,27 @@ def show(self, image, **options): # 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): @@ -92,115 +109,200 @@ def show_file(self, file, **options): os.system(self.get_command(file, **options)) return 1 + def _remove_file_after_delay(self, file): + subprocess.Popen( + [ + sys.executable, + "-c", + "import os, sys, time; time.sleep(20); os.remove(sys.argv[1])", + file, + ] + ) + # -------------------------------------------------------------------- -if sys.platform == "win32": +class WindowsViewer(Viewer): + """The default viewer on Windows is the default system application for PNG files.""" - class WindowsViewer(Viewer): - format = "PNG" - options = {"compress_level": 1} + 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}"' + ) - def get_command(self, file, **options): - return ( - 'start "Pillow" /WAIT "%s" ' - "&& ping -n 2 127.0.0.1 >NUL " - '&& del /f "%s"' % (file, file) - ) +if sys.platform == "win32": register(WindowsViewer) -elif sys.platform == "darwin": - - class MacViewer(Viewer): - 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 = "({} {}; sleep 20; rm -f {})&".format( - command, quote(file), 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, "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 +class MacViewer(Viewer): + """The default viewer on macOS using ``Preview.app``.""" + + 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""" + subprocess.call(["open", "-a", "Preview.app", file]) + self._remove_file_after_delay(file) + return 1 + + +if sys.platform == "darwin": register(MacViewer) -else: - # unixoids - - class UnixViewer(Viewer): - format = "PNG" - options = {"compress_level": 1} - - def get_command(self, file, **options): - command = self.get_command_ex(file, **options)[0] - return "({} {}; rm -f {})&".format(command, quote(file), quote(file)) - - 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 +class UnixViewer(Viewer): + format = "PNG" + options = {"compress_level": 1} - # implementations + def get_command(self, file, **options): + command = self.get_command_ex(file, **options)[0] + return f"({command} {quote(file)}; rm -f {quote(file)})&" - class DisplayViewer(UnixViewer): - def get_command_ex(self, file, **options): - command = executable = "display" - return command, executable - if shutil.which("display"): - register(DisplayViewer) +class XDGViewer(UnixViewer): + """ + The freedesktop.org ``xdg-open`` command. + """ - class EogViewer(UnixViewer): - def get_command_ex(self, file, **options): - command = executable = "eog" - return command, executable + def get_command_ex(self, file, **options): + command = executable = "xdg-open" + return command, executable - if shutil.which("eog"): - register(EogViewer) + def show_file(self, file, **options): + """Display given file""" + subprocess.Popen(["xdg-open", file]) + self._remove_file_after_delay(file) + return 1 - 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 +class DisplayViewer(UnixViewer): + """ + The ImageMagick ``display`` command. + This viewer supports the ``title`` parameter. + """ + + def get_command_ex(self, file, title=None, **options): + command = executable = "display" + if title: + command += f" -name {quote(title)}" + return command, executable + + def show_file(self, file, **options): + """Display given file""" + args = ["display"] + if "title" in options: + args += ["-name", options["title"]] + args.append(file) + + subprocess.Popen(args) + os.remove(file) + return 1 + + +class GmDisplayViewer(UnixViewer): + """The GraphicsMagick ``gm display`` command.""" + + def get_command_ex(self, file, **options): + executable = "gm" + command = "gm display" + return command, executable + + def show_file(self, file, **options): + """Display given file""" + subprocess.Popen(["gm", "display", file]) + os.remove(file) + return 1 + + +class EogViewer(UnixViewer): + """The GNOME Image Viewer ``eog`` command.""" + + def get_command_ex(self, file, **options): + executable = "eog" + command = "eog -n" + return command, executable + + def show_file(self, file, **options): + """Display given file""" + subprocess.Popen(["eog", "-n", file]) + os.remove(file) + return 1 + + +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 + + def show_file(self, file, **options): + """Display given file""" + args = ["xv"] + if "title" in options: + args += ["-name", options["title"]] + args.append(file) + + subprocess.Popen(args) + os.remove(file) + return 1 + + +if sys.platform not in ("win32", "darwin"): # unixoids + if shutil.which("xdg-open"): + register(XDGViewer) + if shutil.which("display"): + register(DisplayViewer) + if shutil.which("gm"): + register(GmDisplayViewer) + if shutil.which("eog"): + register(EogViewer) if shutil.which("xv"): register(XVViewer) + +class IPythonViewer(Viewer): + """The viewer for IPython frontends.""" + + def show_image(self, image, **options): + ipython_display(image) + return 1 + + +try: + from IPython.display import display as ipython_display +except ImportError: + pass +else: + register(IPythonViewer) + + if __name__ == "__main__": if len(sys.argv) < 2: - print("Syntax: python ImageShow.py imagefile [title]") + print("Syntax: python3 ImageShow.py imagefile [title]") sys.exit() with Image.open(sys.argv[1]) as im: diff --git a/src/PIL/ImageTk.py b/src/PIL/ImageTk.py index ee707cffb5b..62db7a717c6 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 @@ def __init__(self, image=None, **kw): 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 927b1694b3e..ca9b14c8adf 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 @@ def expose(self, handle): 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 @@ def frombytes(self, buffer): 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 b2f976dda0d..0bbe50668d8 100644 --- a/src/PIL/IptcImagePlugin.py +++ b/src/PIL/IptcImagePlugin.py @@ -18,7 +18,10 @@ 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 @@ def field(self): 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 @@ def field(self): 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 @@ def _open(self): # 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): @@ -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 0b0d433db41..cc7980278b3 100644 --- a/src/PIL/Jpeg2KImagePlugin.py +++ b/src/PIL/Jpeg2KImagePlugin.py @@ -6,6 +6,7 @@ # # History: # 2014-03-12 ajh Created +# 2021-06-30 rogermb Extract dpi information from the 'resc' header box # # Copyright (c) 2014 Coriolis Systems Limited # Copyright (c) 2014 Alastair Houghton @@ -19,6 +20,79 @@ from . import Image, ImageFile +class BoxReader: + """ + A small helper class to read fields stored in JPEG2000 header boxes + and to easily step into and read sub-boxes. + """ + + def __init__(self, fp, length=-1): + self.fp = fp + self.has_length = length >= 0 + self.length = length + self.remaining_in_box = -1 + + def _can_read(self, num_bytes): + if self.has_length and self.fp.tell() + num_bytes > self.length: + # Outside box: ensure we don't read past the known file length + return False + if self.remaining_in_box >= 0: + # Inside box contents: ensure read does not go past box boundaries + return num_bytes <= self.remaining_in_box + else: + return True # No length known, just read + + def _read_bytes(self, num_bytes): + if not self._can_read(num_bytes): + raise SyntaxError("Not enough data in header") + + data = self.fp.read(num_bytes) + if len(data) < num_bytes: + raise OSError( + f"Expected to read {num_bytes} bytes but only got {len(data)}." + ) + + if self.remaining_in_box > 0: + self.remaining_in_box -= num_bytes + return data + + def read_fields(self, field_format): + size = struct.calcsize(field_format) + data = self._read_bytes(size) + return struct.unpack(field_format, data) + + def read_boxes(self): + size = self.remaining_in_box + data = self._read_bytes(size) + return BoxReader(io.BytesIO(data), size) + + def has_next_box(self): + if self.has_length: + return self.fp.tell() + self.remaining_in_box < self.length + else: + return True + + def next_box_type(self): + # Skip the rest of the box if it has not been read + if self.remaining_in_box > 0: + self.fp.seek(self.remaining_in_box, os.SEEK_CUR) + self.remaining_in_box = -1 + + # Read the length and type of the next box + lbox, tbox = self.read_fields(">I4s") + if lbox == 1: + lbox = self.read_fields(">Q")[0] + hlen = 16 + else: + hlen = 8 + + if lbox < hlen or not self._can_read(lbox - hlen): + raise SyntaxError("Invalid header length") + + self.remaining_in_box = lbox - hlen + return tbox + + def _parse_codestream(fp): """Parse the JPEG 2000 codestream to extract the size and component count from the SIZ marker segment, returning a PIL (size, mode) tuple.""" @@ -53,101 +127,71 @@ def _parse_codestream(fp): return (size, mode) +def _res_to_dpi(num, denom, exp): + """Convert JPEG2000's (numerator, denominator, exponent-base-10) resolution, + calculated as (num / denom) * 10^exp and stored in dots per meter, + to floating-point dots per inch.""" + if denom != 0: + return (254 * num * (10 ** exp)) / (10000 * denom) + + def _parse_jp2_header(fp): - """Parse the JP2 header box to extract size, component count and - color space information, returning a (size, mode, mimetype) tuple.""" + """Parse the JP2 header box to extract size, component count, + color space information, and optionally DPI information, + returning a (size, mode, mimetype, dpi) tuple.""" # Find the JP2 header box + reader = BoxReader(fp) header = None mimetype = None - while True: - lbox, tbox = struct.unpack(">I4s", fp.read(8)) - if lbox == 1: - lbox = struct.unpack(">Q", fp.read(8))[0] - hlen = 16 - else: - hlen = 8 - - if lbox < hlen: - raise SyntaxError("Invalid JP2 header length") + while reader.has_next_box(): + tbox = reader.next_box_type() if tbox == b"jp2h": - header = fp.read(lbox - hlen) + header = reader.read_boxes() break elif tbox == b"ftyp": - if fp.read(4) == b"jpx ": + if reader.read_fields(">4s")[0] == b"jpx ": mimetype = "image/jpx" - fp.seek(lbox - hlen - 4, os.SEEK_CUR) - else: - fp.seek(lbox - hlen, os.SEEK_CUR) - - if header is None: - raise SyntaxError("could not find JP2 header") size = None mode = None bpc = None nc = None + dpi = None # 2-tuple of DPI info, or None - hio = io.BytesIO(header) - while True: - lbox, tbox = struct.unpack(">I4s", hio.read(8)) - if lbox == 1: - lbox = struct.unpack(">Q", hio.read(8))[0] - hlen = 16 - else: - hlen = 8 - - content = hio.read(lbox - hlen) + while header.has_next_box(): + tbox = header.next_box_type() if tbox == b"ihdr": - height, width, nc, bpc, c, unkc, ipr = struct.unpack(">IIHBBBB", content) + height, width, nc, bpc = header.read_fields(">IIHB") size = (width, height) - if unkc: - if nc == 1 and (bpc & 0x7F) > 8: - mode = "I;16" - elif nc == 1: - mode = "L" - elif nc == 2: - mode = "LA" - elif nc == 3: - mode = "RGB" - elif nc == 4: - mode = "RGBA" - break - elif tbox == b"colr": - meth, prec, approx = struct.unpack_from(">BBB", content) - if meth == 1: - cs = struct.unpack_from(">I", content, 3)[0] - if cs == 16: # sRGB - if nc == 1 and (bpc & 0x7F) > 8: - mode = "I;16" - elif nc == 1: - mode = "L" - elif nc == 3: - mode = "RGB" - elif nc == 4: - mode = "RGBA" - break - elif cs == 17: # grayscale - if nc == 1 and (bpc & 0x7F) > 8: - mode = "I;16" - elif nc == 1: - mode = "L" - elif nc == 2: - mode = "LA" - break - elif cs == 18: # sYCC - if nc == 3: - mode = "RGB" - elif nc == 4: - mode = "RGBA" + if nc == 1 and (bpc & 0x7F) > 8: + mode = "I;16" + elif nc == 1: + mode = "L" + elif nc == 2: + mode = "LA" + elif nc == 3: + mode = "RGB" + elif nc == 4: + mode = "RGBA" + elif tbox == b"res ": + res = header.read_boxes() + while res.has_next_box(): + tres = res.next_box_type() + if tres == b"resc": + vrcn, vrcd, hrcn, hrcd, vrce, hrce = res.read_fields(">HHHHBB") + hres = _res_to_dpi(hrcn, hrcd, hrce) + vres = _res_to_dpi(vrcn, vrcd, vrce) + if hres is not None and vres is not None: + dpi = (hres, vres) break if size is None or mode is None: - raise SyntaxError("Malformed jp2 header") + raise SyntaxError("Malformed JP2 header") - return (size, mode, mimetype) + return (size, mode, mimetype, dpi) ## @@ -169,7 +213,9 @@ def _open(self): if sig == b"\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a": self.codec = "jp2" header = _parse_jp2_header(self.fp) - self._size, self.mode, self.custom_mimetype = header + self._size, self.mode, self.custom_mimetype, dpi = header + if dpi is not None: + self.info["dpi"] = dpi else: raise SyntaxError("not a JPEG 2000 file") diff --git a/src/PIL/JpegImagePlugin.py b/src/PIL/JpegImagePlugin.py index 2aa029efbff..ccdcc20a896 100644 --- a/src/PIL/JpegImagePlugin.py +++ b/src/PIL/JpegImagePlugin.py @@ -33,14 +33,18 @@ # import array import io +import math 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 +75,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 +115,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 +125,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,8 +140,8 @@ def APP(self, marker): self.info["adobe"] = i16(s, 5) # extract Adobe custom properties try: - adobe_transform = i8(s[1]) - except Exception: + adobe_transform = s[11] + except IndexError: pass else: self.info["adobe_transform"] = adobe_transform @@ -158,15 +162,17 @@ def APP(self, marker): dpi = float(x_resolution[0]) / x_resolution[1] except TypeError: dpi = x_resolution + if math.isnan(dpi): + raise ValueError if resolution_unit == 3: # cm # 1 dpcm = 2.54 dpi dpi *= 2.54 - self.info["dpi"] = int(dpi + 0.5), int(dpi + 0.5) - except (KeyError, SyntaxError, ValueError, ZeroDivisionError): + self.info["dpi"] = dpi, dpi + except (TypeError, KeyError, SyntaxError, ValueError, ZeroDivisionError): # SyntaxError for invalid/unreadable EXIF # KeyError for dpi not included # ZeroDivisionError for invalid dpi rational value - # ValueError for x_resolution[0] being an invalid float + # ValueError or TypeError for dpi being an invalid float self.info["dpi"] = 72, 72 @@ -191,13 +197,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: @@ -205,7 +211,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 @@ -213,7 +219,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:]) @@ -221,19 +227,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. @@ -241,15 +246,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[i] for i in zigzag_index] + s = s[qt_length:] # @@ -323,7 +329,8 @@ def DQT(self, 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" ## @@ -337,10 +344,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 @@ -356,7 +364,7 @@ def _open(self): while True: - i = i8(s) + i = s[0] if i == 0xFF: s = s + self.fp.read(1) i = i16(s) @@ -393,9 +401,10 @@ def load_read(self, read_bytes): """ s = self.fp.read(read_bytes) - if not s and ImageFile.LOAD_TRUNCATED_IMAGES: + if not s and ImageFile.LOAD_TRUNCATED_IMAGES and not hasattr(self, "_ended"): # Premature EOF. # Pretend file is finished adding EOI marker + self._ended = True return b"\xFF\xD9" return s @@ -469,24 +478,31 @@ def _getexif(self): def _getmp(self): return _getmp(self) + def getxmp(self): + """ + Returns a dictionary containing the XMP tags. + Requires defusedxml to be installed. + :returns: XMP tags in a dictionary. + """ -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) + for segment, content in self.applist: + if segment == "APP1": + marker, xmp_tags = content.rsplit(b"\x00", 1) + if marker == b"http://ns.adobe.com/xap/1.0/": + return self._getxmp(xmp_tags) + return {} def _getexif(self): if "exif" not in self.info: return None - return dict(self.getexif()) + return self.getexif()._get_merged_dict() 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. @@ -503,20 +519,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)) @@ -545,8 +561,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. @@ -586,16 +602,18 @@ def _getmp(self): def convert_dict_qtables(qtables): - qtables = [qtables[key] for key in range(len(qtables)) if key in qtables] - for idx, table in enumerate(qtables): - qtables[idx] = [table[i] for i in zigzag_index] + warnings.warn( + "convert_dict_qtables is deprecated and will be removed in Pillow 10" + "(2023-07-01). Conversion is no longer needed.", + DeprecationWarning, + ) return 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 @@ -610,8 +628,8 @@ 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 @@ -663,13 +681,15 @@ def validate_qtables(qtables): 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)): if isinstance(qtables, dict): - qtables = convert_dict_qtables(qtables) + qtables = [ + qtables[key] for key in range(len(qtables)) if key in qtables + ] elif isinstance(qtables, tuple): qtables = list(qtables) if not (0 < len(qtables) < 5): @@ -678,9 +698,9 @@ def validate_qtables(qtables): 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 diff --git a/src/PIL/JpegPresets.py b/src/PIL/JpegPresets.py index 012bf81b01c..e5a5d178a16 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 @@ im.save("image_name.jpg", quality="web_high") - Subsampling ----------- @@ -33,7 +34,7 @@ 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) @@ -51,19 +52,11 @@ im.quantization -This will return a dict with a number of arrays. You can pass this dict +This will return a dict with a number of lists. You can pass this dict directly as the qtables argument when saving a JPEG. -The tables format between im.quantization and quantization in presets differ in -3 ways: - -1. The base container of the preset is a list with sublists instead of dict. - dict[0] -> list[0], dict[1] -> list[1], ... -2. Each table in a preset is a list instead of an array. -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. +The quantization table format in presets is a list with sublists. These formats +are interchangeable. Libjpeg ref.: https://web.archive.org/web/20120328125543/http://www.jpegcameras.com/libjpeg/libjpeg-3.html @@ -71,7 +64,7 @@ """ # fmt: off -presets = { # noqa: E128 +presets = { 'web_low': {'subsampling': 2, # "4:2:0" 'quantization': [ [20, 16, 25, 39, 50, 46, 62, 68, @@ -112,16 +105,16 @@ ]}, '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 +124,18 @@ ]}, '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 +182,8 @@ '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 +200,16 @@ ]}, '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 +219,18 @@ ]}, '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 8610988fcd2..9248b1b6550 100644 --- a/src/PIL/MicImagePlugin.py +++ b/src/PIL/MicImagePlugin.py @@ -46,8 +46,8 @@ def _open(self): 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 @@ def _open(self): 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._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 e97176d572b..7ccf27c420b 100644 --- a/src/PIL/MpoImagePlugin.py +++ b/src/PIL/MpoImagePlugin.py @@ -21,9 +21,8 @@ from . import Image, ImageFile, JpegImagePlugin from ._binary import i16be as i16 - -def _accept(prefix): - return JpegImagePlugin._accept(prefix) +# def _accept(prefix): +# return JpegImagePlugin._accept(prefix) def _save(im, fp, filename): @@ -48,15 +47,16 @@ def _open(self): 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 +67,6 @@ def _after_jpeg_open(self, mpheader=None): 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 @@ -89,9 +81,11 @@ def seek(self, frame): n = i16(self.fp.read(2)) - 2 self.info["exif"] = ImageFile._safe_read(self.fp, n) - exif = self.getexif() - if 40962 in exif and 40963 in exif: - self._size = (exif[40962], exif[40963]) + mptype = self.mpinfo[0xB002][frame]["Attribute"]["MPType"] + if mptype.startswith("Large Thumbnail"): + exif = self.getexif().get_ifd(0x8769) + if 40962 in exif and 40963 in exif: + self._size = (exif[40962], exif[40963]) elif "exif" in self.info: del self.info["exif"] diff --git a/src/PIL/MspImagePlugin.py b/src/PIL/MspImagePlugin.py index 2b2937ecfcf..32b28d44d5f 100644 --- a/src/PIL/MspImagePlugin.py +++ b/src/PIL/MspImagePlugin.py @@ -21,13 +21,14 @@ # Figure 205. Windows Paint Version 1: "DanM" Format # Figure 206. Windows Paint Version 2: "LinS" Format. Used in Windows V2.03 # -# See also: http://www.fileformat.info/format/mspaint/egff.htm +# See also: https://www.fileformat.info/format/mspaint/egff.htm 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 @@ def _open(self): # 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))] @@ -72,7 +73,7 @@ def _open(self): class MspDecoder(ImageFile.PyDecoder): # The algo for the MSP decoder is from - # http://www.fileformat.info/format/mspaint/egff.htm + # https://www.fileformat.info/format/mspaint/egff.htm # cc-by-attribution -- That page references is taken from the # Encyclopedia of Graphics File Formats and is licensed by # O'Reilly under the Creative Common/Attribution license @@ -114,10 +115,10 @@ def decode(self, buffer): 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 @@ def decode(self, buffer): ) 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 @@ def decode(self, buffer): 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 @@ def decode(self, buffer): 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 762d31e887a..743c35f0128 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,45 +20,42 @@ 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, + ``sys.stdout.buffer`` or ``sys.stdout`` is assumed. """ def __init__(self, fp=None): if not fp: - fp = sys.stdout + try: + fp = sys.stdout.buffer + except AttributeError: + fp = sys.stdout self.fp = fp - def _fp_write(self, to_write): - if self.fp == sys.stdout: - self.fp.write(to_write) - else: - 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" - "save\n" - "/showpage { } def\n" - "%%EndComments\n" - "%%BeginDocument\n" + self.fp.write( + b"%!PS-Adobe-3.0\n" + b"save\n" + b"/showpage { } def\n" + b"%%EndComments\n" + b"%%BeginDocument\n" ) - # self._fp_write(ERROR_PS) # debugging! - self._fp_write(EDROFF_PS) - self._fp_write(VDI_PS) - self._fp_write("%%EndProlog\n") + # self.fp.write(ERROR_PS) # debugging! + self.fp.write(EDROFF_PS) + self.fp.write(VDI_PS) + self.fp.write(b"%%EndProlog\n") self.isofont = {} def end_document(self): - """Ends printing. (Write Postscript DSC footer.)""" - self._fp_write("%%EndDocument\nrestore showpage\n%%End\n") + """Ends printing. (Write PostScript DSC footer.)""" + self.fp.write(b"%%EndDocument\nrestore showpage\n%%End\n") if hasattr(self.fp, "flush"): self.fp.flush() @@ -66,24 +63,24 @@ def setfont(self, font, size): """ Selects which font to use. - :param font: A Postscript font name + :param font: A PostScript font name :param size: Size in points. """ + font = bytes(font, "UTF-8") if font not in self.isofont: # reencode font - self._fp_write("/PSDraw-{} ISOLatin1Encoding /{} E\n".format(font, font)) + self.fp.write(b"/PSDraw-%s ISOLatin1Encoding /%s E\n" % (font, font)) self.isofont[font] = 1 # rough - self._fp_write("/F0 %d /PSDraw-%s F\n" % (size, font)) + self.fp.write(b"/F0 %d /PSDraw-%s F\n" % (size, font)) 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(b"%d %d %d %d Vl\n" % (*xy0, *xy1)) def rectangle(self, box): """ @@ -98,17 +95,18 @@ def rectangle(self, box): %d %d M %d %d 0 Vr\n """ - self._fp_write("%d %d M %d %d 0 Vr\n" % box) + self.fp.write(b"%d %d M %d %d 0 Vr\n" % box) def text(self, xy, text): """ Draws text at the given position. You must use :py:meth:`~PIL.PSDraw.PSDraw.setfont` before calling this method. """ - text = "\\(".join(text.split("(")) - text = "\\)".join(text.split(")")) - xy = xy + (text,) - self._fp_write("%d %d M (%s) S\n" % xy) + text = bytes(text, "UTF-8") + text = b"\\(".join(text.split(b"(")) + text = b"\\)".join(text.split(b")")) + xy += (text,) + self.fp.write(b"%d %d M (%s) S\n" % xy) def image(self, box, im, dpi=None): """Draw a PIL image, centered in the given box.""" @@ -132,21 +130,21 @@ def image(self, box, im, dpi=None): 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(b"gsave\n%f %f translate\n" % (dx, dy)) 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(b"%f %f scale\n" % (sx, sy)) EpsImagePlugin._save(im, self.fp, None, 0) - self._fp_write("\ngrestore\n") + self.fp.write(b"\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) @@ -155,7 +153,7 @@ def image(self, box, im, dpi=None): # -EDROFF_PS = """\ +EDROFF_PS = b"""\ /S { show } bind def /P { moveto show } bind def /M { moveto } bind def @@ -176,7 +174,7 @@ def image(self, box, im, dpi=None): """ # -# 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) @@ -184,7 +182,7 @@ def image(self, box, im, dpi=None): # Copyright (c) Fredrik Lundh 1994. # -VDI_PS = """\ +VDI_PS = b"""\ /Vm { moveto } bind def /Va { newpath arcn stroke } bind def /Vl { moveto lineto stroke } bind def @@ -209,7 +207,7 @@ def image(self, box, im, dpi=None): # 89-11-21 fl: created (pslist 1.10) # -ERROR_PS = """\ +ERROR_PS = b"""\ /landscape false def /errorBUF 200 string def /errorNL { currentpoint 10 sub exch pop 72 exch moveto } def diff --git a/src/PIL/PaletteFile.py b/src/PIL/PaletteFile.py index 73f1b4b27c4..6ccaa1f536f 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 804ece34a6e..700f10e3f79 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 @@ (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 @@ (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 625f5564666..38caf5c63c1 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 @@ def _open(self): 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 c463533cd01..6a4eb22a629 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,11 +52,8 @@ 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" diff --git a/src/PIL/PcxImagePlugin.py b/src/PIL/PcxImagePlugin.py index 6cf10deb3f7..d2e166bdd77 100644 --- a/src/PIL/PcxImagePlugin.py +++ b/src/PIL/PcxImagePlugin.py @@ -29,13 +29,15 @@ 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 @@ def _open(self): 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] + provided_stride = i16(s, 66) logger.debug( "PCX version %s, bits %s, planes %s, stride %s", version, bits, planes, - stride, + provided_stride, ) self.info["dpi"] = i16(s, 12), i16(s, 14) @@ -88,7 +90,7 @@ def _open(self): # 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,16 @@ def _open(self): self.mode = mode self._size = bbox[2] - bbox[0], bbox[3] - bbox[1] + # Don't trust the passed in stride. + # Calculate the approximate position for ourselves. + # CVE-2020-35653 + stride = (self._size[0] * bits + 7) // 8 + + # While the specification states that this must be even, + # not all images follow this + if provided_stride != stride: + stride += stride % 2 + bbox = (0, 0) + self.size logger.debug("size: %sx%s", *self.size) @@ -131,8 +143,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 47500baf7d0..1131c63256e 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,20 +121,21 @@ def _save(im, fp, filename, save_all=False): bits = 8 params = None + decode = None if im.mode == "1": - filter = "ASCIIHexDecode" + filter = "DCTDecode" colorspace = PdfParser.PdfName("DeviceGray") procset = "ImageB" # grayscale 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": filter = "ASCIIHexDecode" - palette = im.im.getpalette("RGB") + palette = im.getpalette() colorspace = [ PdfParser.PdfName("Indexed"), PdfParser.PdfName("DeviceRGB"), @@ -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 @@ -159,12 +161,6 @@ def _save(im, fp, filename, save_all=False): op = io.BytesIO() if filter == "ASCIIHexDecode": - if bits == 1: - # FIXME: the hex encoder doesn't support packed 1-bit - # images; do things the hard way... - data = im.tobytes("raw", "1") - im = Image.new("L", im.size) - im.putdata(data) ImageFile._save(im, op, [("hex", (0, 0) + im.size, 0, im.mode)]) elif filter == "DCTDecode": Image.SAVE["JPEG"](im, op, filename) @@ -173,7 +169,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 +185,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, ) @@ -205,8 +202,8 @@ def _save(im, fp, filename, save_all=False): MediaBox=[ 0, 0, - int(width * 72.0 / resolution), - int(height * 72.0 / resolution), + width * 72.0 / resolution, + height * 72.0 / resolution, ], Contents=contents_refs[pageNumber], ) @@ -214,9 +211,9 @@ def _save(im, fp, filename, save_all=False): # # page contents - page_contents = b"q %d 0 0 %d 0 0 cm /image Do Q\n" % ( - int(width * 72.0 / resolution), - int(height * 72.0 / resolution), + page_contents = b"q %f 0 0 %f 0 0 cm /image Do Q\n" % ( + width * 72.0 / resolution, + height * 72.0 / resolution, ) existing_pdf.write_obj(contents_refs[pageNumber], stream=page_contents) diff --git a/src/PIL/PdfParser.py b/src/PIL/PdfParser.py index fdb35eded56..6ac9c7a7c67 100644 --- a/src/PIL/PdfParser.py +++ b/src/PIL/PdfParser.py @@ -183,8 +183,8 @@ def write(self, f): 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 @@ def __hash__(self): 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 @@ def __setattr__(self, key, value): 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 @@ def decode(self): 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" ) @@ -330,6 +330,8 @@ def pdf_repr(x): return bytes(x) elif isinstance(x, int): return str(x).encode("us-ascii") + elif isinstance(x, float): + return str(x).encode("us-ascii") elif isinstance(x, time.struct_time): return b"(D:" + time.strftime("%Y%m%d%H%M%SZ", x).encode("us-ascii") + b")" elif isinstance(x, dict): @@ -423,7 +425,7 @@ def write_header(self): 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()) def write_catalog(self): self.del_root() @@ -580,8 +582,10 @@ def next_object_id(self, offset=None): whitespace_or_hex = br"[\000\011\012\014\015\0400-9a-fA-F]" whitespace_optional = whitespace + b"*" whitespace_mandatory = whitespace + b"+" + # No "\012" aka "\n" or "\015" aka "\r": + whitespace_optional_no_nl = br"[\000\011\014\040]*" newline_only = br"[\r\n]+" - newline = whitespace_optional + newline_only + whitespace_optional + newline = whitespace_optional_no_nl + newline_only + whitespace_optional_no_nl re_trailer_end = re.compile( whitespace_mandatory + br"trailer" @@ -811,11 +815,11 @@ def get_value(cls, data, offset, expect_indirect=None, max_nesting=-1): 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") @@ -859,7 +863,7 @@ def get_value(cls, data, offset, expect_indirect=None, max_nesting=-1): if m: # filter out whitespace hex_string = bytearray( - [b for b in m.group(1) if b in b"0123456789abcdefABCDEF"] + b for b in m.group(1) if b in b"0123456789abcdefABCDEF" ) if len(hex_string) % 2 == 1: # append a 0 if the length is not even - yes, at the end @@ -966,9 +970,8 @@ def read_indirect(self, ref, max_nesting=-1): 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 5ea32ba89be..c4860b6c4f3 100644 --- a/src/PIL/PixarImagePlugin.py +++ b/src/PIL/PixarImagePlugin.py @@ -43,16 +43,16 @@ def _open(self): # 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 81a9e36af5e..0f596f1fdb3 100644 --- a/src/PIL/PngImagePlugin.py +++ b/src/PIL/PngImagePlugin.py @@ -39,7 +39,11 @@ import zlib from . import Image, ImageChops, ImageFile, ImagePalette, ImageSequence -from ._binary import i8, i16be as i16, i32be as i32, o8, o16be as o16, o32be as o32 +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__) @@ -76,21 +80,50 @@ _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): @@ -130,7 +163,7 @@ def read(self): 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 @@ -159,7 +192,7 @@ def crc(self, cid, data): # 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 @@ -167,9 +200,13 @@ def crc(self, cid, data): 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""" @@ -186,8 +223,8 @@ def verify(self, endchunk=b"IEND"): 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 @@ -228,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. @@ -320,8 +362,8 @@ def check_text_memory(self, chunklen): 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): @@ -347,12 +389,10 @@ def chunk_iCCP(self, pos, length): # 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: @@ -369,14 +409,14 @@ def chunk_IHDR(self, pos, length): # 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 @@ -424,7 +464,7 @@ def chunk_tRNS(self, pos, length): 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): @@ -450,17 +490,17 @@ def chunk_sRGB(self, pos, length): # 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) + dpi = px * 0.0254, py * 0.0254 self.im_info["dpi"] = dpi elif unit == 0: self.im_info["aspect"] = px, py @@ -478,10 +518,11 @@ def chunk_tEXt(self, pos, length): 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 @@ -495,13 +536,11 @@ def chunk_zTXt(self, pos, length): 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: @@ -531,7 +570,7 @@ def chunk_iTXt(self, pos, length): 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: @@ -579,7 +618,7 @@ def chunk_acTL(self, pos, length): 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_info["loop"] = i32(s, 4) self.im_custom_mimetype = "image/apng" return s @@ -591,18 +630,18 @@ def chunk_fcTL(self, pos, length): ): 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:]) + 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:]) + 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"] = i8(s[24]) - self.im_info["blend"] = i8(s[25]) + self.im_info["disposal"] = s[24] + self.im_info["blend"] = s[25] return s def chunk_fdAT(self, pos, length): @@ -633,13 +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 or fDAT chunk + self.private_chunks = [] self.png = PngStream(self.fp) while True: @@ -656,6 +697,8 @@ def _open(self): 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) @@ -672,7 +715,7 @@ def _open(self): 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 + self.n_frames = self.png.im_n_frames or 1 self.default_image = self.info.get("default_image", False) if self.png.im_palette: @@ -684,15 +727,16 @@ def _open(self): else: self.__prepare_idat = length # used by load_prepare() - if self._n_frames is not None: + 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.n_frames += 1 self._seek(0) + self.is_animated = self.n_frames > 1 @property def text(self): @@ -709,16 +753,6 @@ def text(self): self.seek(frame) return self._text - @property - def n_frames(self): - if self._n_frames is None: - return 1 - return self._n_frames - - @property - def is_animated(self): - return self._n_frames is not None and self._n_frames > 1 - def verify(self): """Verify PNG file""" @@ -745,9 +779,9 @@ def seek(self, frame): 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 APNG file") + raise EOFError("no more images in APNG file") from e def _seek(self, frame, rewind=False): if frame == 0: @@ -768,60 +802,76 @@ def _seek(self, frame, rewind=False): self.blend_op = self.info.get("blend") self.dispose_extent = self.info.get("bbox") self.__frame = 0 - return else: if frame != self.__frame + 1: - raise ValueError("cannot seek to frame %d" % frame) + raise ValueError(f"cannot seek to frame {frame}") - # ensure previous frame was loaded - self.load() + # ensure previous frame was loaded + self.load() - self.fp = self.__fp + if self.dispose: + self.im.paste(self.dispose, self.dispose_extent) + self._prev_im = self.im.copy() - # 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 + self.fp = self.__fp - try: - cid, pos, length = self.png.read() - except (struct.error, SyntaxError): - break + # 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 - 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: + cid, pos, length = self.png.read() + except (struct.error, SyntaxError): + break - try: - self.png.call(cid, pos, length) - except UnicodeDecodeError: - break - except EOFError: - if cid == b"fdAT": - length -= 4 + if cid == b"IEND": + raise EOFError("No more images in APNG file") + if cid == b"fcTL": 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) + # there must be at least one fdAT chunk between fcTL chunks + raise SyntaxError("APNG missing frame data") + frame_start = True - 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") + 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 not self.tile: - raise EOFError + 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 @@ -870,6 +920,8 @@ def load_read(self, read_bytes): def load_end(self): """internal: finished reading image data""" + if self.__idat != 0: + self.fp.read(self.__idat) while True: self.fp.read(4) # CRC @@ -896,25 +948,14 @@ def load_end(self): 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 if not self.is_animated: self.png.close() self.png = None else: - # setup frame disposal (actual disposal done when needed in _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: - dispose = self._prev_im.copy() - dispose = self._crop(dispose, self.dispose_extent) - elif self.dispose_op == APNG_DISPOSE_OP_BACKGROUND: - dispose = Image.core.fill("RGBA", self.size, (0, 0, 0, 0)) - dispose = self._crop(dispose, self.dispose_extent) - else: - dispose = None - if self._prev_im and self.blend_op == APNG_BLEND_OP_OVER: updated = self._crop(self.im, self.dispose_extent) self._prev_im.paste( @@ -923,32 +964,31 @@ def load_end(self): self.im = self._prev_im if self.pyaccess: self.pyaccess = None - self._prev_im = self.im.copy() - - if dispose: - self._prev_im.paste(dispose, self.dispose_extent) def _getexif(self): if "exif" not in self.info: self.load() if "exif" not in self.info and "Raw profile type exif" not in self.info: return None - return dict(self.getexif()) + return self.getexif()._get_merged_dict() def getexif(self): if "exif" not in self.info: self.load() - if self._exif is None: - self._exif = Image.Exif() + return super().getexif() - 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) - return self._exif + def getxmp(self): + """ + Returns a dictionary containing the XMP tags. + Requires defusedxml to be installed. + :returns: XMP tags in a dictionary. + """ + return ( + self._getxmp(self.info["XML:com.adobe.xmp"]) + if "XML:com.adobe.xmp" in self.info + else {} + ) def _close__fp(self): try: @@ -1021,8 +1061,10 @@ 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")) + disposal = im.encoderinfo.get( + "disposal", im.info.get("disposal", APNG_DISPOSE_OP_NONE) + ) + blend = im.encoderinfo.get("blend", im.info.get("blend", APNG_BLEND_OP_SOURCE)) if default_image: chain = itertools.chain(im.encoderinfo.get("append_images", [])) @@ -1053,7 +1095,7 @@ def _write_multiple_frames(im, fp, chunk, rawmode): 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 + prev_disposal = APNG_DISPOSE_OP_BACKGROUND if prev_disposal == APNG_DISPOSE_OP_BACKGROUND: base_im = previous["im"] @@ -1077,12 +1119,8 @@ def _write_multiple_frames(im, fp, chunk, rawmode): 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 + if isinstance(duration, (list, tuple)): + previous["encoderinfo"]["duration"] += encoderinfo["duration"] continue else: bbox = None @@ -1090,7 +1128,10 @@ def _write_multiple_frames(im, fp, chunk, rawmode): # animation control chunk( - fp, b"acTL", o32(len(im_frames)), o32(loop), # 0: num_frames # 4: num_plays + fp, + b"acTL", + o32(len(im_frames)), # 0: num_frames + o32(loop), # 4: num_plays ) # default image IDAT (if it exists) @@ -1106,9 +1147,10 @@ def _write_multiple_frames(im, fp, chunk, rawmode): 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) + encoderinfo = frame_data["encoderinfo"] + frame_duration = int(round(encoderinfo.get("duration", duration))) + frame_disposal = encoderinfo.get("disposal", disposal) + frame_blend = encoderinfo.get("blend", blend) # frame control chunk( fp, @@ -1118,10 +1160,10 @@ def _write_multiple_frames(im, fp, chunk, rawmode): o32(size[1]), # height o32(bbox[0]), # x_offset o32(bbox[1]), # y_offset - o16(duration), # delay_numerator + o16(frame_duration), # delay_numerator o16(1000), # delay_denominator - o8(disposal), # dispose_op - o8(blend), # blend_op + o8(frame_disposal), # dispose_op + o8(frame_blend), # blend_op ) seq_num += 1 # frame data @@ -1135,7 +1177,9 @@ def _write_multiple_frames(im, fp, chunk, rawmode): else: fdat_chunks = _fdat(fp, chunk, seq_num) ImageFile._save( - im_frame, fdat_chunks, [("zip", (0, 0) + im_frame.size, 0, rawmode)], + im_frame, + fdat_chunks, + [("zip", (0, 0) + im_frame.size, 0, rawmode)], ) seq_num = fdat_chunks.seq_num @@ -1155,24 +1199,22 @@ def _save(im, fp, filename, chunk=putchunk, save_all=False): # attempt to minimize storage requirements for palette images if "bits" in im.encoderinfo: # number of bits specified by user - colors = 1 << im.encoderinfo["bits"] + colors = min(1 << im.encoderinfo["bits"], 256) else: # check palette contents if im.palette: - colors = max(min(len(im.palette.getdata()[1]) // 3, 256), 2) + colors = max(min(len(im.palette.getdata()[1]) // 3, 256), 1) else: colors = 256 - if colors <= 2: - bits = 1 - elif colors <= 4: - bits = 2 - elif colors <= 16: - bits = 4 - else: - bits = 8 - if bits != 8: - mode = "%s;%d" % (mode, bits) + if colors <= 16: + if colors <= 2: + bits = 1 + elif colors <= 4: + bits = 2 + else: + bits = 4 + mode = f"{mode};{bits}" # encoder options im.encoderconfig = ( @@ -1185,8 +1227,8 @@ def _save(im, fp, filename, chunk=putchunk, save_all=False): # 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 @@ -1225,15 +1267,21 @@ def _save(im, fp, filename, chunk=putchunk, save_all=False): 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 + palette_byte_number = colors * 3 palette_bytes = im.im.getpalette("RGB")[:palette_byte_number] while len(palette_bytes) < palette_byte_number: palette_bytes += b"\0" @@ -1244,7 +1292,7 @@ def _save(im, fp, filename, chunk=putchunk, save_all=False): if transparency or transparency == 0: if im.mode == "P": # limit to actual palette size - alpha_bytes = 2 ** bits + alpha_bytes = colors if isinstance(transparency, bytes): chunk(fp, b"tRNS", transparency[:alpha_bytes]) else: @@ -1265,7 +1313,7 @@ def _save(im, fp, filename, chunk=putchunk, save_all=False): else: if im.mode == "P" and im.im.getpalettemode() == "RGBA": alpha = im.im.getpalette("RGBA", "A") - alpha_bytes = 2 ** bits + alpha_bytes = colors chunk(fp, b"tRNS", alpha[:alpha_bytes]) dpi = im.encoderinfo.get("dpi") @@ -1280,7 +1328,8 @@ def _save(im, fp, filename, chunk=putchunk, save_all=False): 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) @@ -1298,6 +1347,15 @@ def _save(im, fp, filename, chunk=putchunk, save_all=False): 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"") if hasattr(fp, "flush"): diff --git a/src/PIL/PpmImagePlugin.py b/src/PIL/PpmImagePlugin.py index 35a77bafb37..abf4d651dc5 100644 --- a/src/PIL/PpmImagePlugin.py +++ b/src/PIL/PpmImagePlugin.py @@ -104,7 +104,7 @@ def _open(self): # 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 cceb85c5b35..04b21e3debd 100644 --- a/src/PIL/PsdImagePlugin.py +++ b/src/PIL/PsdImagePlugin.py @@ -19,7 +19,10 @@ 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 +from ._binary import si16be as si16 MODES = { # (photoshop mode, bits) -> (pil mode, required channels) @@ -61,12 +64,12 @@ def _open(self): # 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 +77,7 @@ def _open(self): 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 @@ -117,8 +120,11 @@ def _open(self): end = self.fp.tell() + size size = i32(read(4)) if size: - self.layers = _layerinfo(self.fp) + _layer_data = io.BytesIO(ImageFile._safe_read(self.fp, size)) + self.layers = _layerinfo(_layer_data, size) self.fp.seek(end) + self.n_frames = len(self.layers) + self.is_animated = self.n_frames > 1 # # image descriptor @@ -130,14 +136,6 @@ def _open(self): 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 +148,8 @@ def seek(self, layer): 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) @@ -175,11 +173,20 @@ def _close__fp(self): self.__fp = None -def _layerinfo(file): +def _layerinfo(fp, ct_bytes): # read layerinfo block layers = [] - read = file.read - for i in range(abs(i16(read(2)))): + + def read(size): + return ImageFile._safe_read(fp, size) + + ct = si16(read(2)) + + # sanity check + if ct_bytes < (abs(ct) * 20): + raise SyntaxError("Layer block too short for number of layers requested") + + for i in range(abs(ct)): # bounding box y0 = i32(read(4)) @@ -190,7 +197,8 @@ def _layerinfo(file): # image info info = [] mode = [] - types = list(range(i16(read(2)))) + ct_types = i16(read(2)) + types = list(range(ct_types)) if len(types) > 4: continue @@ -223,16 +231,16 @@ def _layerinfo(file): size = i32(read(4)) # length of the extra data field combined = 0 if size: - data_end = file.tell() + size + data_end = fp.tell() + size length = i32(read(4)) if length: - file.seek(length - 16, io.SEEK_CUR) + fp.seek(length - 16, io.SEEK_CUR) combined += length + 4 length = i32(read(4)) if length: - file.seek(length, io.SEEK_CUR) + fp.seek(length, io.SEEK_CUR) combined += length + 4 length = i8(read(1)) @@ -242,7 +250,7 @@ def _layerinfo(file): name = read(length).decode("latin-1", "replace") combined += length + 1 - file.seek(data_end) + fp.seek(data_end) layers.append((name, mode, (x0, y0, x1, y1))) # get tiles @@ -250,7 +258,7 @@ def _layerinfo(file): for name, mode, bbox in layers: tile = [] for m in mode: - t = _maketile(file, m, bbox, 1) + t = _maketile(fp, m, bbox, 1) if t: tile.extend(t) layers[i] = name, mode, bbox, tile @@ -295,7 +303,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 +321,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 359a9491975..eeaa0ccc472 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) @@ -48,6 +54,7 @@ def __init__(self, img, readonly=False): self.image32 = ffi.cast("int **", vals["image32"]) self.image = ffi.cast("unsigned char **", vals["image"]) self.xsize, self.ysize = img.im.size + self._img = img # Keep pointer to im object to prevent dereferencing. self._im = img.im @@ -87,7 +94,7 @@ def __setitem__(self, xy, color): and len(color) in [3, 4] ): # RGB or RGBA value for a P image - color = self._palette.getcolor(color) + color = self._palette.getcolor(color, self._img) return self.set_pixel(x, y, color) @@ -121,7 +128,7 @@ def check_xy(self, xy): class _PyAccess32_2(PyAccess): - """ PA, LA, stored in first and last bytes of a 32 bit word """ + """PA, LA, stored in first and last bytes of a 32 bit word""" def _post_init(self, *args, **kwargs): self.pixels = ffi.cast("struct Pixel_RGBA **", self.image32) @@ -138,7 +145,7 @@ def set_pixel(self, x, y, color): class _PyAccess32_3(PyAccess): - """ RGB and friends, stored in the first three bytes of a 32 bit word """ + """RGB and friends, stored in the first three bytes of a 32 bit word""" def _post_init(self, *args, **kwargs): self.pixels = ffi.cast("struct Pixel_RGBA **", self.image32) @@ -157,7 +164,7 @@ def set_pixel(self, x, y, color): class _PyAccess32_4(PyAccess): - """ RGBA etc, all 4 bytes of a 32 bit word """ + """RGBA etc, all 4 bytes of a 32 bit word""" def _post_init(self, *args, **kwargs): self.pixels = ffi.cast("struct Pixel_RGBA **", self.image32) @@ -176,7 +183,7 @@ def set_pixel(self, x, y, color): class _PyAccess8(PyAccess): - """ 1, L, P, 8 bit images stored as uint8 """ + """1, L, P, 8 bit images stored as uint8""" def _post_init(self, *args, **kwargs): self.pixels = self.image8 @@ -194,7 +201,7 @@ def set_pixel(self, x, y, color): class _PyAccessI16_N(PyAccess): - """ I;16 access, native bitendian without conversion """ + """I;16 access, native bitendian without conversion""" def _post_init(self, *args, **kwargs): self.pixels = ffi.cast("unsigned short **", self.image) @@ -212,7 +219,7 @@ def set_pixel(self, x, y, color): class _PyAccessI16_L(PyAccess): - """ I;16L access, with conversion """ + """I;16L access, with conversion""" def _post_init(self, *args, **kwargs): self.pixels = ffi.cast("struct Pixel_I16 **", self.image) @@ -233,7 +240,7 @@ def set_pixel(self, x, y, color): class _PyAccessI16_B(PyAccess): - """ I;16B access, with conversion """ + """I;16B access, with conversion""" def _post_init(self, *args, **kwargs): self.pixels = ffi.cast("struct Pixel_I16 **", self.image) @@ -254,7 +261,7 @@ def set_pixel(self, x, y, color): class _PyAccessI32_N(PyAccess): - """ Signed Int32 access, native endian """ + """Signed Int32 access, native endian""" def _post_init(self, *args, **kwargs): self.pixels = self.image32 @@ -267,7 +274,7 @@ def set_pixel(self, x, y, color): class _PyAccessI32_Swap(PyAccess): - """ I;32L/B access, with byteswapping conversion """ + """I;32L/B access, with byteswapping conversion""" def _post_init(self, *args, **kwargs): self.pixels = self.image32 @@ -286,7 +293,7 @@ def set_pixel(self, x, y, color): class _PyAccessF(PyAccess): - """ 32 bit float access """ + """32 bit float access""" def _post_init(self, *args, **kwargs): self.pixels = ffi.cast("float **", self.image32) diff --git a/src/PIL/SgiImagePlugin.py b/src/PIL/SgiImagePlugin.py index ddd3de379aa..5f1ef6edc1b 100644 --- a/src/PIL/SgiImagePlugin.py +++ b/src/PIL/SgiImagePlugin.py @@ -26,7 +26,8 @@ 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 @@ def _open(self): 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 @@ -195,7 +193,8 @@ def _save(im, fp, filename): for channel in im.split(): fp.write(channel.tobytes("raw", rawmode, 0, orientation)) - fp.close() + if hasattr(fp, "flush"): + fp.flush() class SGI16Decoder(ImageFile.PyDecoder): diff --git a/src/PIL/SpiderImagePlugin.py b/src/PIL/SpiderImagePlugin.py index cbd31cf82ed..062af9f983e 100644 --- a/src/PIL/SpiderImagePlugin.py +++ b/src/PIL/SpiderImagePlugin.py @@ -111,8 +111,8 @@ def _open(self): 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: @@ -296,7 +296,7 @@ def _save_spider(im, fp, filename): if __name__ == "__main__": if len(sys.argv) < 2: - print("Syntax: python SpiderImagePlugin.py [infile] [outfile]") + print("Syntax: python3 SpiderImagePlugin.py [infile] [outfile]") sys.exit() filename = sys.argv[1] @@ -318,7 +318,7 @@ def _save_spider(im, fp, filename): # 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) + 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 fd7ca8a403e..c03759a01e6 100644 --- a/src/PIL/SunImagePlugin.py +++ b/src/PIL/SunImagePlugin.py @@ -53,18 +53,18 @@ def _open(self): # 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 ede64645358..d108362fc9f 100644 --- a/src/PIL/TarIO.py +++ b/src/PIL/TarIO.py @@ -18,12 +18,10 @@ 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 fd71e545d62..ed63da95f22 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 @@ def _open(self): # 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 ( @@ -91,9 +93,10 @@ def _open(self): # orientation orientation = flags & 0x30 - if orientation == 0x20: + self._flip_horizontally = orientation in [0x10, 0x30] + if orientation in [0x20, 0x30]: orientation = 1 - elif not orientation: + elif orientation in [0, 0x10]: orientation = -1 else: raise SyntaxError("unknown TGA orientation") @@ -108,10 +111,10 @@ def _open(self): 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), s[7] if mapdepth == 16: self.palette = ImagePalette.raw( - "BGR;16", b"\0" * 2 * start + self.fp.read(2 * size) + "BGR;15", b"\0" * 2 * start + self.fp.read(2 * size) ) elif mapdepth == 24: self.palette = ImagePalette.raw( @@ -147,6 +150,10 @@ def _open(self): except KeyError: pass # cannot decode + def load_end(self): + if self._flip_horizontally: + self.im = self.im.transpose(Image.FLIP_LEFT_RIGHT) + # # -------------------------------------------------------------------- @@ -167,8 +174,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 74fb695162f..5df5c4f4cd5 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 @@ -47,16 +48,17 @@ from fractions import Fraction from numbers import Number, Rational -from . import Image, ImageFile, ImagePalette, TiffTags -from ._binary import i8, o8 +from . import Image, ImageFile, ImageOps, ImagePalette, TiffTags +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 WRITE_LIBTIFF = False IFD_LEGACY_API = True +STRIP_SIZE = 65536 II = b"II" # little-endian (Intel style) MM = b"MM" # big-endian (Motorola style) @@ -87,10 +89,15 @@ ARTIST = 315 PREDICTOR = 317 COLORMAP = 320 +TILEWIDTH = 322 +TILELENGTH = 323 TILEOFFSETS = 324 +TILEBYTECOUNTS = 325 +SUBIFD = 330 EXTRASAMPLES = 338 SAMPLEFORMAT = 339 JPEGTABLES = 347 +YCBCRSUBSAMPLING = 530 REFERENCEBLACKWHITE = 532 COPYRIGHT = 33432 IPTC_NAA_CHUNK = 33723 # newsphoto properties @@ -285,7 +292,7 @@ def _limit_signed_rational(val, max_val, min_val): 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,7 +359,22 @@ def __hash__(self): return self._val.__hash__() def __eq__(self, other): - return self._val == other + val = self._val + if isinstance(other, IFDRational): + other = other._val + if isinstance(other, float): + val = float(val) + return val == other + + def __getstate__(self): + return [self._val, self._numerator, self._denominator] + + def __setstate__(self, state): + IFDRational.__init__(self, 0) + _val, _numerator, _denominator = state + self._val = _val + self._numerator = _numerator + self._denominator = _denominator def _delegate(op): def delegate(self, *args): @@ -413,45 +435,51 @@ 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. Data Structures: - * self.tagtype = {} + * ``self.tagtype = {}`` - * Key: numerical tiff tag number + * Key: numerical TIFF tag number * Value: integer corresponding to the data type from - ~PIL.TiffTags.TYPES` + :py:data:`.TiffTags.TYPES` - .. versionadded:: 3.0.0 - """ + .. versionadded:: 3.0.0 - """ - Documentation: - - 'internal' data structures: - * self._tags_v2 = {} Key: numerical tiff tag number - Value: decoded data, as tuple for multiple values - * self._tagdata = {} Key: numerical tiff tag number - Value: undecoded byte string from file - * self._tags_v1 = {} Key: numerical tiff tag number - Value: decoded data in the v1 format - - Tags will be found in the private attributes self._tagdata, and in - self._tags_v2 once decoded. - - Self.legacy_api is a value for internal use, and shouldn't be - changed from outside code. In cooperation with the - ImageFileDirectory_v1 class, if legacy_api is true, then decoded - tags will be populated into both _tags_v1 and _tags_v2. _Tags_v2 - will be used if this IFD is used in the TIFF save routine. Tags - should be read from tags_v1 if legacy_api == true. + 'Internal' data structures: + + * ``self._tags_v2 = {}`` + + * Key: numerical TIFF tag number + * Value: decoded data, as tuple for multiple values + + * ``self._tagdata = {}`` + + * Key: numerical TIFF tag number + * Value: undecoded byte string from file + + * ``self._tags_v1 = {}`` + + * Key: numerical TIFF tag number + * Value: decoded data in the v1 format + + Tags will be found in the private attributes ``self._tagdata``, and in + ``self._tags_v2`` once decoded. + + ``self.legacy_api`` is a value for internal use, and shouldn't be changed + from outside code. In cooperation with + :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v1`, if ``legacy_api`` + is true, then decoded tags will be populated into both ``_tags_v1`` and + ``_tags_v2``. ``_tags_v2`` will be used if this IFD is used in the TIFF + save routine. Tags should be read from ``_tags_v1`` if + ``legacy_api == true``. """ - def __init__(self, ifh=b"II\052\0\0\0\0\0", prefix=None): + def __init__(self, ifh=b"II\052\0\0\0\0\0", prefix=None, group=None): """Initialize an ImageFileDirectory. To construct an ImageFileDirectory from a real file, pass the 8-byte @@ -463,7 +491,7 @@ def __init__(self, ifh=b"II\052\0\0\0\0\0", prefix=None): :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 +499,9 @@ def __init__(self, ifh=b"II\052\0\0\0\0\0", prefix=None): self._endian = "<" else: raise SyntaxError("not a TIFF IFD") + self.group = group + self.tagtype = {} + """ Dictionary of tag types """ self.reset() (self.next,) = self._unpack("L", ifh[4:]) self._legacy_api = False @@ -500,7 +531,10 @@ def named(self): Returns the complete tag dictionary, with named tags where possible. """ - return {TiffTags.lookup(code).name: value for code, value in self.items()} + return { + TiffTags.lookup(code, self.group).name: value + for code, value in self.items() + } def __len__(self): return len(set(self._tagdata) | set(self._tags_v2)) @@ -525,7 +559,7 @@ def __setitem__(self, tag, value): def _setitem(self, tag, value, legacy_api): basetypes = (Number, bytes, str) - info = TiffTags.lookup(tag) + info = TiffTags.lookup(tag, self.group) values = [value] if isinstance(value, basetypes) else value if tag not in self.tagtype: @@ -552,18 +586,22 @@ def _setitem(self, tag, value, legacy_api): ) 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 = [ - value.encode("ascii", "replace") if isinstance(value, str) else value + v.encode("ascii", "replace") if isinstance(v, str) else v + for v in values ] 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 +610,10 @@ def _setitem(self, tag, value, legacy_api): # 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 +626,8 @@ def _setitem(self, tag, value, legacy_api): 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] @@ -637,7 +677,7 @@ def _register_basic(idx_fmt_name): _load_dispatch[idx] = ( # noqa: F821 size, lambda self, data, legacy_api=True: ( - self._unpack("{}{}".format(len(data) // size, fmt), data) + self._unpack(f"{len(data) // size}{fmt}", data) ), ) _write_dispatch[idx] = lambda self, *values: ( # noqa: F821 @@ -655,6 +695,7 @@ def _register_basic(idx_fmt_name): (TiffTags.SIGNED_LONG, "l", "signed long"), (TiffTags.FLOAT, "f", "float"), (TiffTags.DOUBLE, "d", "double"), + (TiffTags.IFD, "L", "long"), ], ) ) @@ -680,7 +721,7 @@ def write_string(self, value): @_register_loader(5, 8) def load_rational(self, data, legacy_api=True): - vals = self._unpack("{}L".format(len(data) // 4), data) + vals = self._unpack(f"{len(data) // 4}L", data) def combine(a, b): return (a, b) if legacy_api else IFDRational(a, b) @@ -703,7 +744,7 @@ def write_undefined(self, value): @_register_loader(10, 8) def load_signed_rational(self, data, legacy_api=True): - vals = self._unpack("{}l".format(len(data) // 4), data) + vals = self._unpack(f"{len(data) // 4}l", data) def combine(a, b): return (a, b) if legacy_api else IFDRational(a, b) @@ -722,7 +763,7 @@ def _ensure_read(self, fp, size): 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 +775,21 @@ def load(self, fp): 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, self.group).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 +799,23 @@ def load(self, fp): 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 +836,34 @@ def tobytes(self, offset=0): 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, group=tag) + values = self._tags_v2[tag] + for ifd_tag, ifd_value in values.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, self.group).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 +884,7 @@ def tobytes(self, offset=0): # 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 +932,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 +946,13 @@ def __init__(self, *args, **kwargs): 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 +969,7 @@ def from_v2(cls, original): 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 +1025,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 +1052,9 @@ def _open(self): 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 +1069,6 @@ def n_frames(self): 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): @@ -1032,27 +1082,32 @@ def seek(self, frame): def _seek(self, frame): self.fp = self.__fp + + # reset buffered io handle in case fp + # was passed to libtiff, invalidating the buffer + self.fp.tell() + 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()) - ) - # reset buffered io handle in case fp - # was passed to libtiff, invalidating the buffer - self.fp.tell() + logger.debug( + f"Seeking to frame {frame}, on frame {self.__frame}, " + f"__next {self.__next}, location: {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.tag_v2.next in self._frame_pos: + # This IFD has already been processed + # Declare this to be the end of the image + self.__next = 0 + else: + 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) @@ -1065,8 +1120,16 @@ def tell(self): """Return the current frame number""" return self.__frame + def getxmp(self): + """ + Returns a dictionary containing the XMP tags. + Requires defusedxml to be installed. + :returns: XMP tags in a dictionary. + """ + return self._getxmp(self.tag_v2[700]) if 700 in self.tag_v2 else {} + 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 +1150,25 @@ def load_end(self): # 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 """ + # reset buffered io handle in case fp + # was passed to libtiff, invalidating the buffer + self.fp.tell() - pixel = Image.Image.load(self) + # load IFD data from fp before it is closed + exif = self.getexif() + for key in TiffTags.TAGS_V2_GROUPS.keys(): + if key not in exif: + continue + exif.get_ifd(key) + + def _load_libtiff(self): + """Overload method triggered when we detect a compressed tiff + Calls out to libtiff""" - 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 +1192,7 @@ def _load_libtiff(self): 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 +1204,10 @@ def _load_libtiff(self): ) 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 +1216,20 @@ def _load_libtiff(self): # 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 +1269,19 @@ def _setup(self): 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: @@ -1242,6 +1306,13 @@ def _setup(self): if bps_count > len(bps_tuple) and len(bps_tuple) == 1: bps_tuple = bps_tuple * bps_count + samplesPerPixel = self.tag_v2.get( + SAMPLESPERPIXEL, + 3 if self._compression == "tiff_jpeg" and photo in (2, 6) else 1, + ) + if len(bps_tuple) != samplesPerPixel: + raise SyntaxError("unknown data organization") + # mode: check photometric interpretation and bits per pixel key = ( self.tag_v2.prefix, @@ -1251,18 +1322,15 @@ def _setup(self): 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 @@ -1272,11 +1340,11 @@ def _setup(self): if xres and yres: resunit = self.tag_v2.get(RESOLUTION_UNIT) if resunit == 2: # dots per inch - self.info["dpi"] = int(xres + 0.5), int(yres + 0.5) + self.info["dpi"] = (xres, yres) elif resunit == 3: # dots per centimeter. convert to dpi - self.info["dpi"] = int(xres * 2.54 + 0.5), int(yres * 2.54 + 0.5) + self.info["dpi"] = (xres * 2.54, yres * 2.54) elif resunit is None: # used to default to 1, but now 2) - self.info["dpi"] = int(xres + 0.5), int(yres + 0.5) + self.info["dpi"] = (xres, yres) # For backward compatibility, # we also preserve the old behavior self.info["resolution"] = xres, yres @@ -1303,8 +1371,7 @@ def _setup(self): 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 @@ -1320,6 +1387,15 @@ def _setup(self): if ";16L" in rawmode: rawmode = rawmode.replace(";16L", ";16N") + # YCbCr images with new jpeg compression with pixels in one plane + # unpacked straight into RGB values + if ( + photo == 6 + and self._compression == "jpeg" + and self._planar_configuration == 1 + ): + rawmode = "RGB" + # Offset in the tile tuple is 0, we go from 0,0 to # w,h, and we only do this once -- eds a = (rawmode, self._compression, False, self.tag_v2.offset) @@ -1366,8 +1442,7 @@ def _setup(self): 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 +1503,21 @@ 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")) + encoderinfo = im.encoderinfo + encoderconfig = im.encoderconfig + compression = 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" + elif compression == "tiff_deflate": + compression = "tiff_adobe_deflate" libtiff = WRITE_LIBTIFF or compression != "raw" @@ -1446,13 +1528,24 @@ def _save(im, fp, filename): ifd[IMAGELENGTH] = im.size[1] # write any arbitrary tags passed in as an ImageFileDirectory - info = im.encoderinfo.get("tiffinfo", {}) - if DEBUG: - print("Tiffinfo Keys: %s" % list(info)) + if "tiffinfo" in encoderinfo: + info = encoderinfo["tiffinfo"] + elif "exif" in encoderinfo: + info = encoderinfo["exif"] + if isinstance(info, bytes): + exif = Image.Exif() + exif.load(info) + info = exif + else: + info = {} + logger.debug("Tiffinfo Keys: %s" % list(info)) if isinstance(info, ImageFileDirectory_v1): info = info.to_v2() for key in info: - ifd[key] = info.get(key) + if isinstance(info, Image.Exif) and key in TiffTags.TAGS_V2_GROUPS.keys(): + ifd[key] = info.get_ifd(key) + else: + ifd[key] = info.get(key) try: ifd.tagtype[key] = info.tagtype[key] except Exception: @@ -1476,8 +1569,9 @@ def _save(im, fp, filename): # preserve ICC profile (should also work when saving other formats # which support profiles as TIFF) -- 2008-06-06 Florian Hoech - if "icc_profile" in im.info: - ifd[ICCPROFILE] = im.info["icc_profile"] + icc = encoderinfo.get("icc_profile", im.info.get("icc_profile")) + if icc: + ifd[ICCPROFILE] = icc for key, name in [ (IMAGEDESCRIPTION, "description"), @@ -1491,14 +1585,14 @@ def _save(im, fp, filename): (ARTIST, "artist"), (COPYRIGHT, "copyright"), ]: - if name in im.encoderinfo: - ifd[key] = im.encoderinfo[name] + if name in encoderinfo: + ifd[key] = encoderinfo[name] - dpi = im.encoderinfo.get("dpi") + dpi = encoderinfo.get("dpi") if dpi: ifd[RESOLUTION_UNIT] = 2 - ifd[X_RESOLUTION] = int(dpi[0] + 0.5) - ifd[Y_RESOLUTION] = int(dpi[1] + 0.5) + ifd[X_RESOLUTION] = dpi[0] + ifd[Y_RESOLUTION] = dpi[1] if bits != (1,): ifd[BITSPERSAMPLE] = bits @@ -1509,22 +1603,59 @@ def _save(im, fp, filename): if format != 1: ifd[SAMPLEFORMAT] = format - ifd[PHOTOMETRIC_INTERPRETATION] = photo + if PHOTOMETRIC_INTERPRETATION not in ifd: + ifd[PHOTOMETRIC_INTERPRETATION] = photo + elif im.mode in ("1", "L") and ifd[PHOTOMETRIC_INTERPRETATION] == 0: + if im.mode == "1": + inverted_im = im.copy() + px = inverted_im.load() + for y in range(inverted_im.height): + for x in range(inverted_im.width): + px[x, y] = 0 if px[x, y] == 255 else 255 + im = inverted_im + else: + im = ImageOps.invert(im) 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] - ifd[STRIPOFFSETS] = 0 # this is adjusted by IFD writer + # aim for given strip size (64 KB by default) when using libtiff writer + if libtiff: + rows_per_strip = 1 if stride == 0 else min(STRIP_SIZE // stride, im.size[1]) + # JPEG encoder expects multiple of 8 rows + if compression == "jpeg": + rows_per_strip = min(((rows_per_strip + 7) // 8) * 8, im.size[1]) + else: + rows_per_strip = im.size[1] + if rows_per_strip == 0: + rows_per_strip = 1 + strip_byte_counts = 1 if stride == 0 else stride * rows_per_strip + strips_per_image = (im.size[1] + rows_per_strip - 1) // rows_per_strip + ifd[ROWSPERSTRIP] = rows_per_strip + if strip_byte_counts >= 2 ** 16: + ifd.tagtype[STRIPBYTECOUNTS] = TiffTags.LONG + ifd[STRIPBYTECOUNTS] = (strip_byte_counts,) * (strips_per_image - 1) + ( + stride * im.size[1] - strip_byte_counts * (strips_per_image - 1), + ) + ifd[STRIPOFFSETS] = tuple( + range(0, strip_byte_counts * strips_per_image, strip_byte_counts) + ) # this is adjusted by IFD writer # no compression by default: ifd[COMPRESSION] = COMPRESSION_INFO_REV.get(compression, 1) + if im.mode == "YCbCr": + for tag, value in { + YCBCRSUBSAMPLING: (1, 1), + REFERENCEBLACKWHITE: (0, 255, 128, 255, 128, 255), + }.items(): + ifd.setdefault(tag, value) + + blocklist = [TILEWIDTH, TILELENGTH, TILEOFFSETS, TILEBYTECOUNTS] if libtiff: - if "quality" in im.encoderinfo: - quality = im.encoderinfo["quality"] + if "quality" in encoderinfo: + quality = encoderinfo["quality"] if not isinstance(quality, int) or quality < 0 or quality > 100: raise ValueError("Invalid quality setting") if compression != "jpeg": @@ -1533,9 +1664,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: @@ -1546,20 +1676,18 @@ def _save(im, fp, filename): # optional types for non core tags types = {} - # SAMPLEFORMAT is determined by the image format and should not be copied - # from legacy_ifd. # STRIPOFFSETS and STRIPBYTECOUNTS are added by the library # based on the data in the strip. # 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. - blocklist = [ - COLORMAP, + # SUBIFD may also cause a segfault. + blocklist += [ REFERENCEBLACKWHITE, - SAMPLEFORMAT, STRIPBYTECOUNTS, STRIPOFFSETS, TRANSFERFUNCTION, + SUBIFD, ] atts = {} @@ -1571,24 +1699,30 @@ def _save(im, fp, filename): legacy_ifd = {} if hasattr(im, "tag"): legacy_ifd = im.tag.to_v2() - for tag, value in itertools.chain( - ifd.items(), getattr(im, "tag_v2", {}).items(), legacy_ifd.items() - ): + + # SAMPLEFORMAT is determined by the image format and should not be copied + # from legacy_ifd. + supplied_tags = {**getattr(im, "tag_v2", {}), **legacy_ifd} + if SAMPLEFORMAT in supplied_tags: + del supplied_tags[SAMPLEFORMAT] + + for tag, value in itertools.chain(ifd.items(), supplied_tags.items()): # Libtiff can only process certain core items without adding # them to the custom dictionary. # 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 +1731,10 @@ def _save(im, fp, filename): else: atts[tag] = value - if DEBUG: - print("Converted items: %s" % sorted(atts.items())) + if SAMPLEFORMAT in atts and len(atts[SAMPLEFORMAT]) == 1: + atts[SAMPLEFORMAT] = atts[SAMPLEFORMAT][0] + + 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 @@ -1613,7 +1749,7 @@ def _save(im, fp, filename): tags = list(atts.items()) tags.sort() a = (rawmode, compression, _fp, filename, tags, types) - e = Image._getencoder(im.mode, "libtiff", a, im.encoderconfig) + e = Image._getencoder(im.mode, "libtiff", a, encoderconfig) e.setimage(im.im, (0, 0) + im.size) while True: # undone, change to self.decodermaxblock: @@ -1623,9 +1759,11 @@ 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: + for tag in blocklist: + del ifd[tag] offset = ifd.save(fp) ImageFile._save( @@ -1633,7 +1771,7 @@ def _save(im, fp, filename): ) # -- helper for multi-page save -- - if "_debug_multipage" in im.encoderinfo: + if "_debug_multipage" in encoderinfo: # just to access o32 and o16 (using correct byte order) im._debug_multipage = ifd @@ -1789,29 +1927,29 @@ def rewriteLastShortToLong(self, value): 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 6cc9ff7f349..88856aa92d5 100644 --- a/src/PIL/TiffTags.py +++ b/src/PIL/TiffTags.py @@ -33,7 +33,7 @@ def cvt_enum(self, value): return self.enum.get(value, value) if self.enum else value -def lookup(tag): +def lookup(tag, group=None): """ :param tag: Integer tag number :returns: Taginfo namedtuple, From the TAGS_V2 info if possible, @@ -42,7 +42,11 @@ def lookup(tag): """ - return TAGS_V2.get(tag, TagInfo(tag, TAGS.get(tag, "unknown"))) + if group is not None: + info = TAGS_V2_GROUPS[group].get(tag) if group in TAGS_V2_GROUPS else None + else: + info = TAGS_V2.get(tag) + return info or TagInfo(tag, TAGS.get(tag, "unknown")) ## @@ -69,6 +73,7 @@ def lookup(tag): SIGNED_RATIONAL = 10 FLOAT = 11 DOUBLE = 12 +IFD = 13 TAGS_V2 = { 254: ("NewSubfileType", LONG, 1), @@ -177,12 +182,15 @@ def lookup(tag): 532: ("ReferenceBlackWhite", RATIONAL, 6), 700: ("XMP", BYTE, 0), 33432: ("Copyright", ASCII, 1), - 33723: ("IptcNaaInfo", UNDEFINED, 0), + 33723: ("IptcNaaInfo", UNDEFINED, 1), 34377: ("PhotoshopInfo", BYTE, 0), # FIXME add more tags here 34665: ("ExifIFD", LONG, 1), 34675: ("ICCProfile", UNDEFINED, 1), 34853: ("GPSInfoIFD", LONG, 1), + 36864: ("ExifVersion", UNDEFINED, 1), + 40965: ("InteroperabilityIFD", LONG, 1), + 41730: ("CFAPattern", UNDEFINED, 1), # MPInfo 45056: ("MPFVersion", UNDEFINED, 1), 45057: ("NumberOfImages", LONG, 1), @@ -203,11 +211,25 @@ def lookup(tag): 45579: ("YawAngle", SIGNED_RATIONAL, 1), 45580: ("PitchAngle", SIGNED_RATIONAL, 1), 45581: ("RollAngle", SIGNED_RATIONAL, 1), + 40960: ("FlashPixVersion", UNDEFINED, 1), 50741: ("MakerNoteSafety", SHORT, 1, {"Unsafe": 0, "Safe": 1}), 50780: ("BestQualityScale", RATIONAL, 1), 50838: ("ImageJMetaDataByteCounts", LONG, 0), # Can be more than one 50839: ("ImageJMetaData", UNDEFINED, 1), # see Issue #2006 } +TAGS_V2_GROUPS = { + # ExifIFD + 34665: { + 36864: ("ExifVersion", UNDEFINED, 1), + 40960: ("FlashPixVersion", UNDEFINED, 1), + 40965: ("InteroperabilityIFD", LONG, 1), + 41730: ("CFAPattern", UNDEFINED, 1), + }, + # GPSInfoIFD + 34853: {}, + # InteroperabilityIFD + 40965: {1: ("InteropIndex", ASCII, 1), 2: ("InteropVersion", UNDEFINED, 1)}, +} # Legacy Tags structure # these tags aren't included above, but were in the previous versions @@ -366,6 +388,10 @@ def _populate(): TAGS_V2[k] = TagInfo(k, *v) + for group, tags in TAGS_V2_GROUPS.items(): + for k, v in tags.items(): + tags[k] = TagInfo(k, *v) + _populate() ## @@ -483,10 +509,6 @@ def _populate(): 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 - LIBTIFF_CORE.remove(255) # We don't have support for subfiletypes LIBTIFF_CORE.remove(322) # We don't have support for writing tiled images with libtiff LIBTIFF_CORE.remove(323) # Tiled images diff --git a/src/PIL/WalImageFile.py b/src/PIL/WalImageFile.py index d5a5c8e67bb..1354ad32b52 100644 --- a/src/PIL/WalImageFile.py +++ b/src/PIL/WalImageFile.py @@ -12,62 +12,66 @@ # 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. - -import builtins - -from . import Image +""" +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. +""" + +from . import Image, ImageFile from ._binary import i32le as i32 -def open(filename): - """ - Load texture from a Quake2 WAL texture file. +class WalImageFile(ImageFile.ImageFile): - By default, a Quake2 standard palette is attached to the texture. - To override the palette, use the putpalette method. + format = "WAL" + format_description = "Quake2 Texture" - :param filename: WAL file name, or an opened file handle. - :returns: An image instance. - """ - # FIXME: modify to return a WalImageFile instance instead of - # plain Image object ? + def _open(self): + self.mode = "P" - def imopen(fp): # read header fields - header = fp.read(32 + 24 + 32 + 12) - size = i32(header, 32), i32(header, 36) - offset = i32(header, 40) + header = self.fp.read(32 + 24 + 32 + 12) + self._size = i32(header, 32), i32(header, 36) + Image._decompression_bomb_check(self.size) # load pixel data - fp.seek(offset) - - Image._decompression_bomb_check(size) - im = Image.frombytes("P", size, fp.read(size[0] * size[1])) - im.putpalette(quake2palette) - - im.format = "WAL" - im.format_description = "Quake2 Texture" + offset = i32(header, 40) + self.fp.seek(offset) # strings are null-terminated - im.info["name"] = header[:32].split(b"\0", 1)[0] + self.info["name"] = header[:32].split(b"\0", 1)[0] next_name = header[56 : 56 + 32].split(b"\0", 1)[0] if next_name: - im.info["next_name"] = next_name + self.info["next_name"] = next_name + + def load(self): + if self.im: + # Already loaded + return + + self.im = Image.core.new(self.mode, self.size) + self.frombytes(self.fp.read(self.size[0] * self.size[1])) + self.putpalette(quake2palette) + Image.Image.load(self) + + +def open(filename): + """ + Load texture from a Quake2 WAL texture file. - return im + By default, a Quake2 standard palette is attached to the texture. + To override the palette, use the :py:func:`PIL.Image.Image.putpalette()` method. - if hasattr(filename, "read"): - return imopen(filename) - else: - with builtins.open(filename, "rb") as fp: - return imopen(fp) + :param filename: WAL file name, or an opened file handle. + :returns: An image instance. + """ + return WalImageFile(filename) quake2palette = ( diff --git a/src/PIL/WebPImagePlugin.py b/src/PIL/WebPImagePlugin.py index eda6855087d..590161f3ecc 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 @@ def _open(self): 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 @@ def _open(self): 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 @@ def _open(self): # 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 + return self.getexif()._get_merged_dict() 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 @@ -203,7 +192,7 @@ def _save_all(im, fp, filename): r, g, b = palette[background * 3 : (background + 1) * 3] background = (r, g, b, 0) - duration = im.encoderinfo.get("duration", 0) + duration = im.encoderinfo.get("duration", im.info.get("duration")) loop = im.encoderinfo.get("loop", 0) minimize_size = im.encoderinfo.get("minimize_size", False) kmin = im.encoderinfo.get("kmin", None) @@ -213,7 +202,7 @@ def _save_all(im, fp, filename): lossless = im.encoderinfo.get("lossless", False) quality = im.encoderinfo.get("quality", 80) method = im.encoderinfo.get("method", 0) - icc_profile = im.encoderinfo.get("icc_profile", "") + icc_profile = im.encoderinfo.get("icc_profile") or "" exif = im.encoderinfo.get("exif", "") if isinstance(exif, Image.Exif): exif = exif.tobytes() @@ -320,17 +309,18 @@ def _save_all(im, fp, filename): def _save(im, fp, filename): lossless = im.encoderinfo.get("lossless", False) quality = im.encoderinfo.get("quality", 80) - icc_profile = im.encoderinfo.get("icc_profile", "") + icc_profile = im.encoderinfo.get("icc_profile") or "" exif = im.encoderinfo.get("exif", "") if isinstance(exif, Image.Exif): exif = exif.tobytes() xmp = im.encoderinfo.get("xmp", "") + method = im.encoderinfo.get("method", 4) if im.mode not in _VALID_WEBP_LEGACY_MODES: alpha = ( "A" in im.mode or "a" in im.mode - or (im.mode == "P" and "A" in im.im.getpalettemode()) + or (im.mode == "P" and "transparency" in im.info) ) im = im.convert("RGBA" if alpha else "RGB") @@ -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 024222c9b79..27f5d2f870c 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 @@ -124,8 +127,8 @@ def _open(self): size = x1 - x0, y1 - y0 # calculate dots per inch from bbox and frame - xdpi = int(2540.0 * (x1 - y0) / (frame[2] - frame[0]) + 0.5) - ydpi = int(2540.0 * (y1 - y0) / (frame[3] - frame[1]) + 0.5) + xdpi = 2540.0 * (x1 - y0) / (frame[2] - frame[0]) + ydpi = 2540.0 * (y1 - y0) / (frame[3] - frame[1]) self.info["wmf_bbox"] = x0, y0, x1, y1 @@ -149,7 +152,7 @@ def _load(self): def load(self, dpi=None): if dpi is not None and self._inch is not None: - self.info["dpi"] = int(dpi + 0.5) + self.info["dpi"] = dpi x0, y0, x1, y1 = self.info["wmf_bbox"] self._size = ( (x1 - x0) * self.info["dpi"] // self._inch, diff --git a/src/PIL/XVThumbImagePlugin.py b/src/PIL/XVThumbImagePlugin.py index c0d8db09afc..4efedb77ea7 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 @@ def _open(self): 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 ead9722c88e..644cfb39bc6 100644 --- a/src/PIL/XbmImagePlugin.py +++ b/src/PIL/XbmImagePlugin.py @@ -69,15 +69,15 @@ def _open(self): 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 d8bd00a1b59..ebd65ba587b 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 @@ def _open(self): 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 f9cb15772dd..45fef241ee0 100644 --- a/src/PIL/__init__.py +++ b/src/PIL/__init__.py @@ -13,72 +13,12 @@ ;-) """ -import sys -import warnings - from . import _version # VERSION was removed in Pillow 6.0.0. -__version__ = _version.__version__ - - -# PILLOW_VERSION is deprecated and will be removed in a future release. +# PILLOW_VERSION was removed in Pillow 9.0.0. # Use __version__ instead. -def _raise_version_warning(): - warnings.warn( - "PILLOW_VERSION is deprecated and will be removed in a future release. " - "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("module '{}' has no attribute '{}'".format(__name__, 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__) - +__version__ = _version.__version__ del _version @@ -131,5 +71,9 @@ def __le__(self, other): ] -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 529b8c94b78..a74ee9eb6f3 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 @@ -43,6 +47,16 @@ def si16le(c, o=0): return unpack_from("h", c, o)[0] + + def i32le(c, o=0): """ Converts a 4-bytes (32 bits) string to an unsigned integer. diff --git a/src/PIL/_tkinter_finder.py b/src/PIL/_tkinter_finder.py index 30493066af5..ba4d045e681 100644 --- a/src/PIL/_tkinter_finder.py +++ b/src/PIL/_tkinter_finder.py @@ -1,16 +1,20 @@ """ Find compiled module linking to Tcl / Tk libraries """ import sys +import tkinter +import warnings 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__ + +tk_version = str(tkinter.TkVersion) +if tk_version == "8.4": + warnings.warn( + "Support for Tk/Tcl 8.4 is deprecated and will be removed" + " in Pillow 10 (2023-07-01). Please upgrade to Tk/Tcl 8.5 " + "or newer.", + DeprecationWarning, + ) diff --git a/src/PIL/_util.py b/src/PIL/_util.py index 755b4b27233..0c5d3892ecb 100644 --- a/src/PIL/_util.py +++ b/src/PIL/_util.py @@ -1,20 +1,9 @@ import os -import sys +from pathlib import Path -py36 = sys.version_info[0:2] >= (3, 6) - -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 1e1f1af9343..694841ad217 100644 --- a/src/PIL/_version.py +++ b/src/PIL/_version.py @@ -1,2 +1,2 @@ # Master version for Pillow -__version__ = "7.1.0" +__version__ = "9.0.1" diff --git a/src/PIL/features.py b/src/PIL/features.py index ac06c0f7142..3838568f3a6 100644 --- a/src/PIL/features.py +++ b/src/PIL/features.py @@ -8,19 +8,26 @@ 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", "tk_version"), + "freetype2": ("PIL._imagingft", "freetype2_version"), + "littlecms2": ("PIL._imagingcms", "littlecms_version"), + "webp": ("PIL._webp", "webpdecoder_version"), } def check_module(feature): + """ + Checks if a module is available. + + :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("Unknown module %s" % feature) + raise ValueError(f"Unknown module {feature}") - module = modules[feature] + module, ver = modules[feature] try: __import__(module) @@ -29,42 +36,108 @@ 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): + """ + Checks if a codec is available. + + :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("Unknown codec %s" % feature) + raise ValueError(f"Unknown codec {feature}") - codec = codecs[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"), - "xcb": ("PIL._imaging", "HAVE_XCB"), + "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"), + "fribidi": ("PIL._imagingft", "HAVE_FRIBIDI", "fribidi_version"), + "harfbuzz": ("PIL._imagingft", "HAVE_HARFBUZZ", "harfbuzz_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): + """ + Checks if a feature is available. + + :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("Unknown feature %s" % feature) + raise ValueError(f"Unknown feature {feature}") - module, flag = features[feature] + module, flag, ver = features[feature] try: imported_module = __import__(module, fromlist=["PIL"]) @@ -73,22 +146,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): + """ + :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("Unknown feature '%s'." % feature, stacklevel=2) + 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()) @@ -96,24 +216,34 @@ def get_supported(): def pilinfo(out=None, supported_formats=True): + """ + Prints information about this installation of Pillow. + This function can be called with ``python3 -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) @@ -136,7 +266,24 @@ def pilinfo(out=None, supported_formats=True): ("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" + if name == "raqm": + for f in ("fribidi", "harfbuzz"): + v2 = version_feature(f) + if v2 is not None: + v += f", {f} {v2}" + 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) @@ -147,9 +294,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 adc470532ef..9852fc9d688 100644 --- a/src/Tk/_tkmini.h +++ b/src/Tk/_tkmini.h @@ -1,7 +1,7 @@ /* Small excerpts from the Tcl / Tk 8.6 headers * * License terms copied from: - * http://www.tcl.tk/software/tcltk/license.html + * https://www.tcl.tk/software/tcltk/license.html * as of 20 May 2016. * * Copyright (c) 1987-1994 The Regents of the University of California. @@ -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 59801f58eb9..9ae7edff108 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); } /* @@ -211,7 +219,7 @@ TkImaging_Init(Tcl_Interp* interp) #define TKINTER_FINDER "PIL._tkinter_finder" -#if defined(_WIN32) || defined(__WIN32__) || defined(WIN32) +#if defined(_WIN32) || defined(__WIN32__) || defined(WIN32) || defined(__CYGWIN__) /* * On Windows, we can't load the tkinter module to get the Tcl or Tk symbols, @@ -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 0c3d766f34e..2a42c046109 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,98 @@ 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_GET_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 (PyTuple_GET_SIZE(color) != 1) { + PyErr_SetString(PyExc_TypeError, "color must be int or single-element tuple"); + return NULL; + } else 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; + int tupleSize = PyTuple_GET_SIZE(color); + if (im->bands == 2) { + if (tupleSize != 1 && tupleSize != 2) { + PyErr_SetString(PyExc_TypeError, "color must be int, or tuple of one or two elements"); + return NULL; + } else if (!PyArg_ParseTuple(color, "L|i", &r, &a)) { + return NULL; + } + g = b = r; + } else { + if (tupleSize != 3 && tupleSize != 4) { + PyErr_SetString(PyExc_TypeError, "color must be int, or tuple of one, three or four elements"); + return NULL; + } 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 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; - ink[0] = (UINT8) r; - ink[1] = (UINT8) (r >> 8); - ink[2] = ink[3] = 0; + } + 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 +600,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 +628,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 +752,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 +774,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 +803,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 +882,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 +904,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 +954,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 +968,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 +1026,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 +1034,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 +1060,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 +1085,88 @@ _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) { 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 - goto badval; + } else if (PyFloat_Check(value)) { + *x = (int)PyFloat_AS_DOUBLE(value); + } else { + PyObject *int_value = PyObject_CallMethod(value, "__int__", NULL); + if (int_value != NULL && PyLong_Check(int_value)) { + *x = PyLong_AS_LONG(int_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 - goto badval; + } else if (PyFloat_Check(value)) { + *y = (int)PyFloat_AS_DOUBLE(value); + } else { + PyObject *int_value = PyObject_CallMethod(value, "__int__", NULL); + if (int_value != NULL && PyLong_Check(int_value)) { + *y = PyLong_AS_LONG(int_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 +1182,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)) - 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)) + 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->f[0] = (FLOAT32) f0; - ep->f[1] = (FLOAT32) f1; - break; - default: - return NULL; } } else { return NULL; @@ -1194,31 +1220,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 +1261,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 +1310,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; + ImagingObject *maskp = NULL; + if (!PyArg_ParseTuple( + args, "O(iiii)|O!", &source, &x0, &y0, &x1, &y1, &Imaging_Type, &maskp)) { + return NULL; + } - if (PyImaging_Check(source)) + if (PyImaging_Check(source)) { status = ImagingPaste( - self->image, PyImaging_AsImaging(source), + self->image, + PyImaging_AsImaging(source), (maskp) ? maskp->image : NULL, - x0, y0, x1, y1 - ); + x0, + y0, + x1, + y1); - else { - if (!getink(source, self->image, ink)) + } else { + if (!getink(source, self->image, ink)) { return NULL; + } status = ImagingFill2( - self->image, ink, - (maskp) ? maskp->image : NULL, - x0, y0, x1, y1 - ); + self->image, ink, (maskp) ? maskp->image : NULL, x0, y0, x1, y1); } - if (status < 0) + 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 +1454,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); @@ -1459,53 +1494,64 @@ _putdata(ImagingObject* self, PyObject* args) return NULL; } +#define set_value_to_item(seq, i) \ +op = PySequence_Fast_GET_ITEM(seq, i); \ +if (PySequence_Check(op)) { \ + PyErr_SetString(PyExc_TypeError, "sequence must be flattened"); \ + return NULL; \ +} else { \ + value = PyFloat_AsDouble(op); \ +} 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; + } + double value; + if (scale == 1.0 && offset == 0.0) { + /* Clipped data */ + for (i = x = y = 0; i < n; i++) { + set_value_to_item(seq, i); + image->image8[y][x] = (UINT8)CLIP8(value); + 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++) { + set_value_to_item(seq, i); + image->image8[y][x] = CLIP8(value * scale + offset); + if (++x >= (int)image->xsize) { + x = 0, y++; + } + } + } + PyErr_Clear(); /* Avoid weird exceptions */ } } else { /* 32-bit images */ @@ -1515,50 +1561,52 @@ _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++) { + double value; + set_value_to_item(seq, i); + IMAGING_PIXEL_INT32(image, x, y) = + (INT32)(value * 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++) { + double value; + set_value_to_item(seq, i); + IMAGING_PIXEL_FLOAT32(image, x, y) = + (FLOAT32)(value * 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; - } - /* FIXME: what about scale and offset? */ - image->image32[y][x] = u.inkint; - if (++x >= (int) image->xsize){ - x = 0, y++; + 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++; + } } - } - PyErr_Clear(); /* Avoid weird exceptions */ - break; + PyErr_Clear(); /* Avoid weird exceptions */ + break; } } @@ -1570,37 +1618,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 +1660,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; } @@ -1627,17 +1673,16 @@ _putpalette(ImagingObject* self, PyObject* args) unpack(self->image->palette->palette, palette, palettesize * 8 / bits); - Py_INCREF(Py_None); - return Py_None; + return PyLong_FromLong(palettesize * 8 / bits); } -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 +1695,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 +1754,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 +1791,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 +1821,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 +1856,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 +1896,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 +1921,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; - } - - 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); + case 0: /* flip left right */ + case 1: /* flip top bottom */ + case 3: /* rotate 180 */ + imOut = ImagingNewDirty(imIn->mode, imIn->xsize, imIn->ysize); break; - case 5: - (void) ImagingTranspose(imOut, imIn); - break; - case 6: - (void) ImagingTransverse(imOut, imIn); + 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) { + 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; } + } 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 +2113,13 @@ _box_blur(ImagingObject* self, PyObject* args) /* -------------------------------------------------------------------- */ -static PyObject* -_isblock(ImagingObject* self, PyObject* args) -{ +static PyObject * +_isblock(ImagingObject *self) { return PyBool_FromLong(self->image->block != NULL); } -static PyObject* -_getbbox(ImagingObject* self, PyObject* args) -{ +static PyObject * +_getbbox(ImagingObject *self) { int bbox[4]; if (!ImagingGetBBox(self->image, bbox)) { Py_INCREF(Py_None); @@ -2059,20 +2129,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 +2151,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 +2163,8 @@ _getcolors(ImagingObject* self, PyObject* args) return out; } -static PyObject* -_getextrema(ImagingObject* self, PyObject* args) -{ +static PyObject * +_getextrema(ImagingObject *self) { union { UINT8 u[2]; INT32 i[2]; @@ -2105,33 +2174,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) { + unsigned char *xprofile; + unsigned char *yprofile; + PyObject *result; /* malloc check ok */ xprofile = malloc(self->image->xsize); @@ -2140,14 +2210,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 +2231,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) { 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,211 +2346,204 @@ _split(ImagingObject* self, PyObject* args) #ifdef WITH_IMAGECHOPS -static PyObject* -_chop_invert(ImagingObject* self, PyObject* args) -{ +static PyObject * +_chop_invert(ImagingObject *self) { 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)); } -static PyObject* -_chop_soft_light(ImagingObject* self, PyObject* args) -{ - ImagingObject* imagep; +static PyObject * +_chop_soft_light(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(ImagingChopSoftLight(self->image, imagep->image)); } -static PyObject* -_chop_hard_light(ImagingObject* self, PyObject* args) -{ - ImagingObject* imagep; +static PyObject * +_chop_hard_light(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(ImagingChopHardLight(self->image, imagep->image)); } -static PyObject* -_chop_overlay(ImagingObject* self, PyObject* args) -{ - ImagingObject* imagep; +static PyObject * +_chop_overlay(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(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); @@ -2466,8 +2551,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; @@ -2486,10 +2572,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; } @@ -2500,37 +2588,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; @@ -2540,7 +2628,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 { @@ -2553,23 +2641,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; } @@ -2581,50 +2667,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) { @@ -2637,26 +2726,27 @@ _font_getsize(ImagingFontObject* self, PyObject* args) } static struct PyMethodDef _font_methods[] = { - {"getmask", (PyCFunction)_font_getmask, 1}, - {"getsize", (PyCFunction)_font_getsize, 1}, + {"getmask", (PyCFunction)_font_getmask, METH_VARARGS}, + {"getsize", (PyCFunction)_font_getsize, METH_VARARGS}, {NULL, NULL} /* sentinel */ }; /* -------------------------------------------------------------------- */ -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); @@ -2666,223 +2756,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; } @@ -2895,28 +3015,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)) + if (!PyArg_ParseTuple(args, "Oi", &data, &ink)) { return NULL; + } n = PyPath_Flatten(data, &xy); - if (n < 0) + 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; - } + 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); @@ -2925,21 +3046,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) { @@ -2947,9 +3069,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; @@ -2957,79 +3079,92 @@ _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)) + int width = 0; + 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, - "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, width, self->blend) < 0) { free(ixy); return NULL; } @@ -3040,38 +3175,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; @@ -3080,38 +3222,38 @@ _draw_rectangle(ImagingDrawObject* self, PyObject* args) static struct PyMethodDef _draw_methods[] = { #ifdef WITH_IMAGEDRAW /* Graphics (ImageDraw) */ - {"draw_lines", (PyCFunction)_draw_lines, 1}, + {"draw_lines", (PyCFunction)_draw_lines, METH_VARARGS}, #ifdef WITH_ARROW - {"draw_outline", (PyCFunction)_draw_outline, 1}, + {"draw_outline", (PyCFunction)_draw_outline, METH_VARARGS}, #endif - {"draw_polygon", (PyCFunction)_draw_polygon, 1}, - {"draw_rectangle", (PyCFunction)_draw_rectangle, 1}, - {"draw_points", (PyCFunction)_draw_points, 1}, - {"draw_arc", (PyCFunction)_draw_arc, 1}, - {"draw_bitmap", (PyCFunction)_draw_bitmap, 1}, - {"draw_chord", (PyCFunction)_draw_chord, 1}, - {"draw_ellipse", (PyCFunction)_draw_ellipse, 1}, - {"draw_pieslice", (PyCFunction)_draw_pieslice, 1}, - {"draw_ink", (PyCFunction)_draw_ink, 1}, + {"draw_polygon", (PyCFunction)_draw_polygon, METH_VARARGS}, + {"draw_rectangle", (PyCFunction)_draw_rectangle, METH_VARARGS}, + {"draw_points", (PyCFunction)_draw_points, METH_VARARGS}, + {"draw_arc", (PyCFunction)_draw_arc, METH_VARARGS}, + {"draw_bitmap", (PyCFunction)_draw_bitmap, METH_VARARGS}, + {"draw_chord", (PyCFunction)_draw_chord, METH_VARARGS}, + {"draw_ellipse", (PyCFunction)_draw_ellipse, METH_VARARGS}, + {"draw_pieslice", (PyCFunction)_draw_pieslice, METH_VARARGS}, + {"draw_ink", (PyCFunction)_draw_ink, METH_VARARGS}, #endif {NULL, NULL} /* sentinel */ }; #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); @@ -3119,40 +3261,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; @@ -3166,11 +3307,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); @@ -3183,43 +3326,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; - - if (!PyArg_ParseTuple(args, "|(ii)(dddd)i", &xsize, &ysize, - &extent[0], &extent[1], &extent[2], &extent[3], - &quality)) - return NULL; + 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; + } 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)); } @@ -3230,29 +3382,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); @@ -3262,23 +3418,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 */ @@ -3286,362 +3441,350 @@ _save_ppm(ImagingObject* self, PyObject* args) static struct PyMethodDef methods[] = { /* Put commonly used methods first */ - {"getpixel", (PyCFunction)_getpixel, 1}, - {"putpixel", (PyCFunction)_putpixel, 1}, + {"getpixel", (PyCFunction)_getpixel, METH_VARARGS}, + {"putpixel", (PyCFunction)_putpixel, METH_VARARGS}, - {"pixel_access", (PyCFunction)pixel_access_new, 1}, + {"pixel_access", (PyCFunction)pixel_access_new, METH_VARARGS}, /* Standard processing methods (Image) */ - {"color_lut_3d", (PyCFunction)_color_lut_3d, 1}, - {"convert", (PyCFunction)_convert, 1}, - {"convert2", (PyCFunction)_convert2, 1}, - {"convert_matrix", (PyCFunction)_convert_matrix, 1}, - {"convert_transparent", (PyCFunction)_convert_transparent, 1}, - {"copy", (PyCFunction)_copy, 1}, - {"crop", (PyCFunction)_crop, 1}, - {"expand", (PyCFunction)_expand_image, 1}, - {"filter", (PyCFunction)_filter, 1}, - {"histogram", (PyCFunction)_histogram, 1}, - {"entropy", (PyCFunction)_entropy, 1}, + {"color_lut_3d", (PyCFunction)_color_lut_3d, METH_VARARGS}, + {"convert", (PyCFunction)_convert, METH_VARARGS}, + {"convert2", (PyCFunction)_convert2, METH_VARARGS}, + {"convert_matrix", (PyCFunction)_convert_matrix, METH_VARARGS}, + {"convert_transparent", (PyCFunction)_convert_transparent, METH_VARARGS}, + {"copy", (PyCFunction)_copy, METH_VARARGS}, + {"crop", (PyCFunction)_crop, METH_VARARGS}, + {"expand", (PyCFunction)_expand_image, METH_VARARGS}, + {"filter", (PyCFunction)_filter, METH_VARARGS}, + {"histogram", (PyCFunction)_histogram, METH_VARARGS}, + {"entropy", (PyCFunction)_entropy, METH_VARARGS}, #ifdef WITH_MODEFILTER - {"modefilter", (PyCFunction)_modefilter, 1}, + {"modefilter", (PyCFunction)_modefilter, METH_VARARGS}, #endif - {"offset", (PyCFunction)_offset, 1}, - {"paste", (PyCFunction)_paste, 1}, - {"point", (PyCFunction)_point, 1}, - {"point_transform", (PyCFunction)_point_transform, 1}, - {"putdata", (PyCFunction)_putdata, 1}, + {"offset", (PyCFunction)_offset, METH_VARARGS}, + {"paste", (PyCFunction)_paste, METH_VARARGS}, + {"point", (PyCFunction)_point, METH_VARARGS}, + {"point_transform", (PyCFunction)_point_transform, METH_VARARGS}, + {"putdata", (PyCFunction)_putdata, METH_VARARGS}, #ifdef WITH_QUANTIZE - {"quantize", (PyCFunction)_quantize, 1}, + {"quantize", (PyCFunction)_quantize, METH_VARARGS}, #endif #ifdef WITH_RANKFILTER - {"rankfilter", (PyCFunction)_rankfilter, 1}, + {"rankfilter", (PyCFunction)_rankfilter, METH_VARARGS}, #endif - {"resize", (PyCFunction)_resize, 1}, - {"reduce", (PyCFunction)_reduce, 1}, - {"transpose", (PyCFunction)_transpose, 1}, - {"transform2", (PyCFunction)_transform2, 1}, + {"resize", (PyCFunction)_resize, METH_VARARGS}, + {"reduce", (PyCFunction)_reduce, METH_VARARGS}, + {"transpose", (PyCFunction)_transpose, METH_VARARGS}, + {"transform2", (PyCFunction)_transform2, METH_VARARGS}, - {"isblock", (PyCFunction)_isblock, 1}, + {"isblock", (PyCFunction)_isblock, METH_NOARGS}, - {"getbbox", (PyCFunction)_getbbox, 1}, - {"getcolors", (PyCFunction)_getcolors, 1}, - {"getextrema", (PyCFunction)_getextrema, 1}, - {"getprojection", (PyCFunction)_getprojection, 1}, + {"getbbox", (PyCFunction)_getbbox, METH_NOARGS}, + {"getcolors", (PyCFunction)_getcolors, METH_VARARGS}, + {"getextrema", (PyCFunction)_getextrema, METH_NOARGS}, + {"getprojection", (PyCFunction)_getprojection, METH_NOARGS}, - {"getband", (PyCFunction)_getband, 1}, - {"putband", (PyCFunction)_putband, 1}, - {"split", (PyCFunction)_split, 1}, - {"fillband", (PyCFunction)_fillband, 1}, + {"getband", (PyCFunction)_getband, METH_VARARGS}, + {"putband", (PyCFunction)_putband, METH_VARARGS}, + {"split", (PyCFunction)_split, METH_NOARGS}, + {"fillband", (PyCFunction)_fillband, METH_VARARGS}, - {"setmode", (PyCFunction)im_setmode, 1}, + {"setmode", (PyCFunction)im_setmode, METH_VARARGS}, - {"getpalette", (PyCFunction)_getpalette, 1}, - {"getpalettemode", (PyCFunction)_getpalettemode, 1}, - {"putpalette", (PyCFunction)_putpalette, 1}, - {"putpalettealpha", (PyCFunction)_putpalettealpha, 1}, - {"putpalettealphas", (PyCFunction)_putpalettealphas, 1}, + {"getpalette", (PyCFunction)_getpalette, METH_VARARGS}, + {"getpalettemode", (PyCFunction)_getpalettemode, METH_NOARGS}, + {"putpalette", (PyCFunction)_putpalette, METH_VARARGS}, + {"putpalettealpha", (PyCFunction)_putpalettealpha, METH_VARARGS}, + {"putpalettealphas", (PyCFunction)_putpalettealphas, METH_VARARGS}, #ifdef WITH_IMAGECHOPS /* Channel operations (ImageChops) */ - {"chop_invert", (PyCFunction)_chop_invert, 1}, - {"chop_lighter", (PyCFunction)_chop_lighter, 1}, - {"chop_darker", (PyCFunction)_chop_darker, 1}, - {"chop_difference", (PyCFunction)_chop_difference, 1}, - {"chop_multiply", (PyCFunction)_chop_multiply, 1}, - {"chop_screen", (PyCFunction)_chop_screen, 1}, - {"chop_add", (PyCFunction)_chop_add, 1}, - {"chop_subtract", (PyCFunction)_chop_subtract, 1}, - {"chop_add_modulo", (PyCFunction)_chop_add_modulo, 1}, - {"chop_subtract_modulo", (PyCFunction)_chop_subtract_modulo, 1}, - {"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}, + {"chop_invert", (PyCFunction)_chop_invert, METH_NOARGS}, + {"chop_lighter", (PyCFunction)_chop_lighter, METH_VARARGS}, + {"chop_darker", (PyCFunction)_chop_darker, METH_VARARGS}, + {"chop_difference", (PyCFunction)_chop_difference, METH_VARARGS}, + {"chop_multiply", (PyCFunction)_chop_multiply, METH_VARARGS}, + {"chop_screen", (PyCFunction)_chop_screen, METH_VARARGS}, + {"chop_add", (PyCFunction)_chop_add, METH_VARARGS}, + {"chop_subtract", (PyCFunction)_chop_subtract, METH_VARARGS}, + {"chop_add_modulo", (PyCFunction)_chop_add_modulo, METH_VARARGS}, + {"chop_subtract_modulo", (PyCFunction)_chop_subtract_modulo, METH_VARARGS}, + {"chop_and", (PyCFunction)_chop_and, METH_VARARGS}, + {"chop_or", (PyCFunction)_chop_or, METH_VARARGS}, + {"chop_xor", (PyCFunction)_chop_xor, METH_VARARGS}, + {"chop_soft_light", (PyCFunction)_chop_soft_light, METH_VARARGS}, + {"chop_hard_light", (PyCFunction)_chop_hard_light, METH_VARARGS}, + {"chop_overlay", (PyCFunction)_chop_overlay, METH_VARARGS}, #endif #ifdef WITH_UNSHARPMASK /* Kevin Cazabon's unsharpmask extension */ - {"gaussian_blur", (PyCFunction)_gaussian_blur, 1}, - {"unsharp_mask", (PyCFunction)_unsharp_mask, 1}, + {"gaussian_blur", (PyCFunction)_gaussian_blur, METH_VARARGS}, + {"unsharp_mask", (PyCFunction)_unsharp_mask, METH_VARARGS}, #endif - {"box_blur", (PyCFunction)_box_blur, 1}, + {"box_blur", (PyCFunction)_box_blur, METH_VARARGS}, #ifdef WITH_EFFECTS /* Special effects */ - {"effect_spread", (PyCFunction)_effect_spread, 1}, + {"effect_spread", (PyCFunction)_effect_spread, METH_VARARGS}, #endif /* Misc. */ - {"new_block", (PyCFunction)_new_block, 1}, + {"new_block", (PyCFunction)_new_block, METH_VARARGS}, - {"save_ppm", (PyCFunction)_save_ppm, 1}, + {"save_ppm", (PyCFunction)_save_ppm, METH_VARARGS}, {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 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 }, - { 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}, + {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) - 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)); + 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)); 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; @@ -3653,39 +3796,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"); @@ -3703,22 +3846,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; } @@ -3728,41 +3869,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); @@ -3776,256 +3914,320 @@ _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); +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_MapBuffer(PyObject *self, PyObject *args); static PyMethodDef functions[] = { /* Object factories */ - {"alpha_composite", (PyCFunction)_alpha_composite, 1}, - {"blend", (PyCFunction)_blend, 1}, - {"fill", (PyCFunction)_fill, 1}, - {"new", (PyCFunction)_new, 1}, - {"merge", (PyCFunction)_merge, 1}, + {"alpha_composite", (PyCFunction)_alpha_composite, METH_VARARGS}, + {"blend", (PyCFunction)_blend, METH_VARARGS}, + {"fill", (PyCFunction)_fill, METH_VARARGS}, + {"new", (PyCFunction)_new, METH_VARARGS}, + {"merge", (PyCFunction)_merge, METH_VARARGS}, /* Functions */ - {"convert", (PyCFunction)_convert2, 1}, + {"convert", (PyCFunction)_convert2, METH_VARARGS}, /* Codecs */ - {"bcn_decoder", (PyCFunction)PyImaging_BcnDecoderNew, 1}, - {"bit_decoder", (PyCFunction)PyImaging_BitDecoderNew, 1}, - {"eps_encoder", (PyCFunction)PyImaging_EpsEncoderNew, 1}, - {"fli_decoder", (PyCFunction)PyImaging_FliDecoderNew, 1}, - {"gif_decoder", (PyCFunction)PyImaging_GifDecoderNew, 1}, - {"gif_encoder", (PyCFunction)PyImaging_GifEncoderNew, 1}, - {"hex_decoder", (PyCFunction)PyImaging_HexDecoderNew, 1}, - {"hex_encoder", (PyCFunction)PyImaging_EpsEncoderNew, 1}, /* EPS=HEX! */ + {"bcn_decoder", (PyCFunction)PyImaging_BcnDecoderNew, METH_VARARGS}, + {"bit_decoder", (PyCFunction)PyImaging_BitDecoderNew, METH_VARARGS}, + {"eps_encoder", (PyCFunction)PyImaging_EpsEncoderNew, METH_VARARGS}, + {"fli_decoder", (PyCFunction)PyImaging_FliDecoderNew, METH_VARARGS}, + {"gif_decoder", (PyCFunction)PyImaging_GifDecoderNew, METH_VARARGS}, + {"gif_encoder", (PyCFunction)PyImaging_GifEncoderNew, METH_VARARGS}, + {"hex_decoder", (PyCFunction)PyImaging_HexDecoderNew, METH_VARARGS}, + {"hex_encoder", (PyCFunction)PyImaging_EpsEncoderNew, METH_VARARGS}, /* EPS=HEX! */ #ifdef HAVE_LIBJPEG - {"jpeg_decoder", (PyCFunction)PyImaging_JpegDecoderNew, 1}, - {"jpeg_encoder", (PyCFunction)PyImaging_JpegEncoderNew, 1}, + {"jpeg_decoder", (PyCFunction)PyImaging_JpegDecoderNew, METH_VARARGS}, + {"jpeg_encoder", (PyCFunction)PyImaging_JpegEncoderNew, METH_VARARGS}, #endif #ifdef HAVE_OPENJPEG - {"jpeg2k_decoder", (PyCFunction)PyImaging_Jpeg2KDecoderNew, 1}, - {"jpeg2k_encoder", (PyCFunction)PyImaging_Jpeg2KEncoderNew, 1}, + {"jpeg2k_decoder", (PyCFunction)PyImaging_Jpeg2KDecoderNew, METH_VARARGS}, + {"jpeg2k_encoder", (PyCFunction)PyImaging_Jpeg2KEncoderNew, METH_VARARGS}, #endif #ifdef HAVE_LIBTIFF - {"libtiff_decoder", (PyCFunction)PyImaging_LibTiffDecoderNew, 1}, - {"libtiff_encoder", (PyCFunction)PyImaging_LibTiffEncoderNew, 1}, + {"libtiff_decoder", (PyCFunction)PyImaging_LibTiffDecoderNew, METH_VARARGS}, + {"libtiff_encoder", (PyCFunction)PyImaging_LibTiffEncoderNew, METH_VARARGS}, #endif - {"packbits_decoder", (PyCFunction)PyImaging_PackbitsDecoderNew, 1}, - {"pcd_decoder", (PyCFunction)PyImaging_PcdDecoderNew, 1}, - {"pcx_decoder", (PyCFunction)PyImaging_PcxDecoderNew, 1}, - {"pcx_encoder", (PyCFunction)PyImaging_PcxEncoderNew, 1}, - {"raw_decoder", (PyCFunction)PyImaging_RawDecoderNew, 1}, - {"raw_encoder", (PyCFunction)PyImaging_RawEncoderNew, 1}, - {"sgi_rle_decoder", (PyCFunction)PyImaging_SgiRleDecoderNew, 1}, - {"sun_rle_decoder", (PyCFunction)PyImaging_SunRleDecoderNew, 1}, - {"tga_rle_decoder", (PyCFunction)PyImaging_TgaRleDecoderNew, 1}, - {"tga_rle_encoder", (PyCFunction)PyImaging_TgaRleEncoderNew, 1}, - {"xbm_decoder", (PyCFunction)PyImaging_XbmDecoderNew, 1}, - {"xbm_encoder", (PyCFunction)PyImaging_XbmEncoderNew, 1}, + {"packbits_decoder", (PyCFunction)PyImaging_PackbitsDecoderNew, METH_VARARGS}, + {"pcd_decoder", (PyCFunction)PyImaging_PcdDecoderNew, METH_VARARGS}, + {"pcx_decoder", (PyCFunction)PyImaging_PcxDecoderNew, METH_VARARGS}, + {"pcx_encoder", (PyCFunction)PyImaging_PcxEncoderNew, METH_VARARGS}, + {"raw_decoder", (PyCFunction)PyImaging_RawDecoderNew, METH_VARARGS}, + {"raw_encoder", (PyCFunction)PyImaging_RawEncoderNew, METH_VARARGS}, + {"sgi_rle_decoder", (PyCFunction)PyImaging_SgiRleDecoderNew, METH_VARARGS}, + {"sun_rle_decoder", (PyCFunction)PyImaging_SunRleDecoderNew, METH_VARARGS}, + {"tga_rle_decoder", (PyCFunction)PyImaging_TgaRleDecoderNew, METH_VARARGS}, + {"tga_rle_encoder", (PyCFunction)PyImaging_TgaRleEncoderNew, METH_VARARGS}, + {"xbm_decoder", (PyCFunction)PyImaging_XbmDecoderNew, METH_VARARGS}, + {"xbm_encoder", (PyCFunction)PyImaging_XbmEncoderNew, METH_VARARGS}, #ifdef HAVE_LIBZ - {"zip_decoder", (PyCFunction)PyImaging_ZipDecoderNew, 1}, - {"zip_encoder", (PyCFunction)PyImaging_ZipEncoderNew, 1}, + {"zip_decoder", (PyCFunction)PyImaging_ZipDecoderNew, METH_VARARGS}, + {"zip_encoder", (PyCFunction)PyImaging_ZipEncoderNew, METH_VARARGS}, #endif - /* Memory mapping */ +/* Memory mapping */ #ifdef WITH_MAPPING -#ifdef _WIN32 - {"map", (PyCFunction)PyImaging_Mapper, 1}, -#endif - {"map_buffer", (PyCFunction)PyImaging_MapBuffer, 1}, + {"map_buffer", (PyCFunction)PyImaging_MapBuffer, METH_VARARGS}, #endif - /* Display support */ +/* Display support */ #ifdef _WIN32 - {"display", (PyCFunction)PyImaging_DisplayWin32, 1}, - {"display_mode", (PyCFunction)PyImaging_DisplayModeWin32, 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}, + {"display", (PyCFunction)PyImaging_DisplayWin32, METH_VARARGS}, + {"display_mode", (PyCFunction)PyImaging_DisplayModeWin32, METH_VARARGS}, + {"grabscreen_win32", (PyCFunction)PyImaging_GrabScreenWin32, METH_VARARGS}, + {"grabclipboard_win32", (PyCFunction)PyImaging_GrabClipboardWin32, METH_VARARGS}, + {"createwindow", (PyCFunction)PyImaging_CreateWindowWin32, METH_VARARGS}, + {"eventloop", (PyCFunction)PyImaging_EventLoopWin32, METH_VARARGS}, + {"listwindows", (PyCFunction)PyImaging_ListWindowsWin32, METH_VARARGS}, + {"drawwmf", (PyCFunction)PyImaging_DrawWmf, METH_VARARGS}, #endif #ifdef HAVE_XCB - {"grabscreen_x11", (PyCFunction)PyImaging_GrabScreenX11, 1}, + {"grabscreen_x11", (PyCFunction)PyImaging_GrabScreenX11, METH_VARARGS}, #endif /* Utilities */ - {"getcodecstatus", (PyCFunction)_getcodecstatus, 1}, + {"getcodecstatus", (PyCFunction)_getcodecstatus, METH_VARARGS}, - /* Special effects (experimental) */ +/* Special effects (experimental) */ #ifdef WITH_EFFECTS - {"effect_mandelbrot", (PyCFunction)_effect_mandelbrot, 1}, - {"effect_noise", (PyCFunction)_effect_noise, 1}, - {"linear_gradient", (PyCFunction)_linear_gradient, 1}, - {"radial_gradient", (PyCFunction)_radial_gradient, 1}, - {"wedge", (PyCFunction)_linear_gradient, 1}, /* Compatibility */ + {"effect_mandelbrot", (PyCFunction)_effect_mandelbrot, METH_VARARGS}, + {"effect_noise", (PyCFunction)_effect_noise, METH_VARARGS}, + {"linear_gradient", (PyCFunction)_linear_gradient, METH_VARARGS}, + {"radial_gradient", (PyCFunction)_radial_gradient, METH_VARARGS}, + {"wedge", (PyCFunction)_linear_gradient, METH_VARARGS}, /* Compatibility */ #endif - /* Drawing support stuff */ +/* Drawing support stuff */ #ifdef WITH_IMAGEDRAW - {"font", (PyCFunction)_font_new, 1}, - {"draw", (PyCFunction)_draw_new, 1}, + {"font", (PyCFunction)_font_new, METH_VARARGS}, + {"draw", (PyCFunction)_draw_new, METH_VARARGS}, #endif - /* Experimental path stuff */ +/* Experimental path stuff */ #ifdef WITH_IMAGEPATH - {"path", (PyCFunction)PyPath_Create, 1}, + {"path", (PyCFunction)PyPath_Create, METH_VARARGS}, #endif - /* Experimental arrow graphics stuff */ +/* Experimental arrow graphics stuff */ #ifdef WITH_ARROW - {"outline", (PyCFunction)PyOutline_Create, 1}, + {"outline", (PyCFunction)PyOutline_Create, METH_VARARGS}, #endif /* Resource management */ - {"get_stats", (PyCFunction)_get_stats, 1}, - {"reset_stats", (PyCFunction)_reset_stats, 1}, - {"get_alignment", (PyCFunction)_get_alignment, 1}, - {"get_block_size", (PyCFunction)_get_block_size, 1}, - {"get_blocks_max", (PyCFunction)_get_blocks_max, 1}, - {"set_alignment", (PyCFunction)_set_alignment, 1}, - {"set_block_size", (PyCFunction)_set_block_size, 1}, - {"set_blocks_max", (PyCFunction)_set_blocks_max, 1}, - {"clear_cache", (PyCFunction)_clear_cache, 1}, + {"get_stats", (PyCFunction)_get_stats, METH_VARARGS}, + {"reset_stats", (PyCFunction)_reset_stats, METH_VARARGS}, + {"get_alignment", (PyCFunction)_get_alignment, METH_VARARGS}, + {"get_block_size", (PyCFunction)_get_block_size, METH_VARARGS}, + {"get_blocks_max", (PyCFunction)_get_blocks_max, METH_VARARGS}, + {"set_alignment", (PyCFunction)_set_alignment, METH_VARARGS}, + {"set_block_size", (PyCFunction)_set_block_size, METH_VARARGS}, + {"set_blocks_max", (PyCFunction)_set_blocks_max, METH_VARARGS}, + {"clear_cache", (PyCFunction)_clear_cache, METH_VARARGS}, {NULL, NULL} /* sentinel */ }; 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 + PyObject *have_libjpegturbo; #ifdef LIBJPEG_TURBO_VERSION - PyModule_AddObject(m, "HAVE_LIBJPEGTURBO", Py_True); + 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); + have_libjpegturbo = Py_False; #endif + Py_INCREF(have_libjpegturbo); + PyModule_AddObject(m, "HAVE_LIBJPEGTURBO", have_libjpegturbo); + PyObject *have_libimagequant; #ifdef HAVE_LIBIMAGEQUANT - PyModule_AddObject(m, "HAVE_LIBIMAGEQUANT", Py_True); + 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); + have_libimagequant = Py_False; #endif + Py_INCREF(have_libimagequant); + PyModule_AddObject(m, "HAVE_LIBIMAGEQUANT", have_libimagequant); #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())); - - // 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; + { + 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; #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 + PyObject *have_xcb; #ifdef HAVE_XCB - PyModule_AddObject(m, "HAVE_XCB", Py_True); + have_xcb = Py_True; #else - PyModule_AddObject(m, "HAVE_XCB", Py_False); + have_xcb = Py_False; #endif + Py_INCREF(have_xcb); + PyModule_AddObject(m, "HAVE_XCB", have_xcb); PyDict_SetItemString(d, "PILLOW_VERSION", PyUnicode_FromString(version)); @@ -4034,20 +4236,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 0b22ab69547..9b5a121d7d3 100644 --- a/src/_imagingcms.c +++ b/src/_imagingcms.c @@ -3,35 +3,36 @@ * a Python / PIL interface to the littleCMS ICC Color Management System * Copyright (C) 2002-2003 Kevin Cazabon * kevin@cazabon.com - * http://www.cazabon.com + * https://www.cazabon.com * Adapted/reworked for PIL by Fredrik Lundh * Copyright (c) 2009 Fredrik Lundh * Updated to LCMS2 * Copyright (c) 2013 Eric Soroos * - * pyCMS home page: http://www.cazabon.com/pyCMS - * littleCMS home page: http://www.littlecms.com + * pyCMS home page: https://www.cazabon.com/pyCMS + * littleCMS home page: https://www.littlecms.com * (littleCMS is Copyright (C) 1998-2001 Marti Maria) * * Originally released under LGPL. Graciously donated to PIL in * 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\ kevin@cazabon.com\n\ -http://www.cazabon.com\n\ +https://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 @@ kevin@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 @@ kevin@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; - - for (x = 0; x < xSize; x++) - memcpy(pDstExtras + x * dstChunkSize, pSrcExtras + x * srcChunkSize, 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); + } } } } 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; - - /* 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)) + 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)) { 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); @@ -906,277 +959,171 @@ _is_intent_supported(CmsProfileObject* self, int clut) static PyMethodDef pyCMSdll_methods[] = { - {"profile_open", cms_profile_open, 1}, - {"profile_frombytes", cms_profile_fromstring, 1}, - {"profile_fromstring", cms_profile_fromstring, 1}, - {"profile_tobytes", cms_profile_tobytes, 1}, + {"profile_open", cms_profile_open, METH_VARARGS}, + {"profile_frombytes", cms_profile_fromstring, METH_VARARGS}, + {"profile_fromstring", cms_profile_fromstring, METH_VARARGS}, + {"profile_tobytes", cms_profile_tobytes, METH_VARARGS}, /* profile and transform functions */ - {"buildTransform", buildTransform, 1}, - {"buildProofTransform", buildProofTransform, 1}, - {"createProfile", createProfile, 1}, + {"buildTransform", buildTransform, METH_VARARGS}, + {"buildProofTransform", buildProofTransform, METH_VARARGS}, + {"createProfile", createProfile, METH_VARARGS}, - /* platform specific tools */ +/* platform specific tools */ #ifdef _WIN32 - {"get_display_profile_win32", cms_get_display_profile_win32, 1}, + {"get_display_profile_win32", cms_get_display_profile_win32, METH_VARARGS}, #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, METH_VARARGS}, {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 f6a5b7d59b3..8f19b763c5c 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -20,23 +20,23 @@ #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) -#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,237 +45,118 @@ #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 } \ + } \ + ; + +#ifdef HAVE_RAQM +# ifdef HAVE_RAQM_SYSTEM +# include +# else +# include "thirdparty/raqm/raqm.h" +# ifdef HAVE_FRIBIDI_SYSTEM +# include +# else +# include "thirdparty/fribidi-shim/fribidi.h" +# include +# endif +# endif +#endif -#include +static int have_raqm = 0; #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); +/* round a 26.6 pixel coordinate to the nearest integer */ +#define PIXEL(x) ((((x) + 32) & -64) >> 6) -typedef struct { - void* raqm; - int version; - t_raqm_version_atleast version_atleast; - t_raqm_create create; - t_raqm_set_text set_text; - t_raqm_set_text_utf8 set_text_utf8; - t_raqm_set_par_direction set_par_direction; - t_raqm_set_language set_language; - t_raqm_add_font_feature add_font_feature; - t_raqm_set_freetype_face set_freetype_face; - t_raqm_layout layout; - t_raqm_get_glyphs get_glyphs; - t_raqm_get_glyphs_01 get_glyphs_01; - t_raqm_destroy destroy; -} p_raqm_func; - -static p_raqm_func p_raqm; - - -/* 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"); - return NULL; -} - -static int -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) - 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"); -#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"); - 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_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.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")) { - 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"); - } - 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)) { - 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.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.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"); - 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"); - } - 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; - } -#endif - - return 0; + PyErr_SetString(PyExc_OSError, "unknown freetype error"); + return NULL; } -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 +171,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,58 +206,44 @@ 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; } return 0; } +#ifdef HAVE_RAQM + 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; - raqm_glyph_t_01 *glyphs_01 = NULL; raqm_direction_t direction; - rq = (*p_raqm.create)(); + rq = raqm_create(); if (rq == NULL) { PyErr_SetString(PyExc_ValueError, "raqm_create() failed."); 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); @@ -380,44 +252,46 @@ text_layout_raqm(PyObject* string, FontObject* self, const char* dir, PyObject * and raqm fails with empty strings */ goto failed; } - int set_text = (*p_raqm.set_text)(rq, text, size); + int set_text = raqm_set_text(rq, text, size); PyMem_Free(text); if (!set_text) { PyErr_SetString(PyExc_ValueError, "raqm_set_text() failed"); goto failed; } if (lang) { - if (!(*p_raqm.set_language)(rq, lang, start, size)) { + if (!raqm_set_language(rq, lang, start, size)) { PyErr_SetString(PyExc_ValueError, "raqm_set_language() failed"); 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"); - goto failed; - } +#if !defined(RAQM_VERSION_ATLEAST) + /* RAQM_VERSION_ATLEAST was added in Raqm 0.7.0 */ + PyErr_SetString( + PyExc_ValueError, + "libraqm 0.7 or greater required for 'ttb' direction"); + goto failed; +#endif } 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; } } - if (!(*p_raqm.set_par_direction)(rq, direction)) { + if (!raqm_set_par_direction(rq, direction)) { PyErr_SetString(PyExc_ValueError, "raqm_set_par_direction() failed"); goto failed; } @@ -443,42 +317,34 @@ 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); } - if (!(*p_raqm.add_font_feature)(rq, feature, size)) { + if (!raqm_add_font_feature(rq, feature, size)) { PyErr_SetString(PyExc_ValueError, "raqm_add_font_feature() failed"); goto failed; } } } - if (!(*p_raqm.set_freetype_face)(rq, self->face)) { + if (!raqm_set_freetype_face(rq, self->face)) { PyErr_SetString(PyExc_RuntimeError, "raqm_set_freetype_face() failed."); goto failed; } - if (!(*p_raqm.layout)(rq)) { + if (!raqm_layout(rq)) { PyErr_SetString(PyExc_RuntimeError, "raqm_layout() failed."); goto failed; } - if (p_raqm.version == 1) { - glyphs_01 = (*p_raqm.get_glyphs_01)(rq, &count); - if (glyphs_01 == NULL) { - PyErr_SetString(PyExc_ValueError, "raqm_get_glyphs() failed."); - count = 0; - goto failed; - } - } else { /* version == 2 */ - glyphs = (*p_raqm.get_glyphs)(rq, &count); - if (glyphs == NULL) { - PyErr_SetString(PyExc_ValueError, "raqm_get_glyphs() failed."); - count = 0; - goto failed; - } + glyphs = raqm_get_glyphs(rq, &count); + if (glyphs == NULL) { + PyErr_SetString(PyExc_ValueError, "raqm_get_glyphs() failed."); + count = 0; + goto failed; } (*glyph_info) = PyMem_New(GlyphInfo, count); @@ -488,35 +354,32 @@ text_layout_raqm(PyObject* string, FontObject* self, const char* dir, PyObject * goto failed; } - if (p_raqm.version == 1) { - for (i = 0; i < count; i++) { - (*glyph_info)[i].index = glyphs_01[i].index; - (*glyph_info)[i].x_offset = glyphs_01[i].x_offset; - (*glyph_info)[i].x_advance = glyphs_01[i].x_advance; - (*glyph_info)[i].y_offset = glyphs_01[i].y_offset; - (*glyph_info)[i].y_advance = glyphs_01[i].y_advance; - (*glyph_info)[i].cluster = glyphs_01[i].cluster; - } - } else { - for (i = 0; i < count; i++) { - (*glyph_info)[i].index = glyphs[i].index; - (*glyph_info)[i].x_offset = glyphs[i].x_offset; - (*glyph_info)[i].x_advance = glyphs[i].x_advance; - (*glyph_info)[i].y_offset = glyphs[i].y_offset; - (*glyph_info)[i].y_advance = glyphs[i].y_advance; - (*glyph_info)[i].cluster = glyphs[i].cluster; - } + for (i = 0; i < count; i++) { + (*glyph_info)[i].index = glyphs[i].index; + (*glyph_info)[i].x_offset = glyphs[i].x_offset; + (*glyph_info)[i].x_advance = glyphs[i].x_advance; + (*glyph_info)[i].y_offset = glyphs[i].y_offset; + (*glyph_info)[i].y_advance = glyphs[i].y_advance; + (*glyph_info)[i].cluster = glyphs[i].cluster; } failed: - (*p_raqm.destroy)(rq); + raqm_destroy(rq); return count; } +#endif + 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 +389,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 +401,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 +413,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,19 +430,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; } @@ -579,116 +455,192 @@ 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); - } else { - count = text_layout_fallback(string, self, dir, features, lang, glyph_info, mask); +#ifdef HAVE_RAQM + if (have_raqm && self->layout_engine == LAYOUT_RAQM) { + count = text_layout_raqm( + string, self, dir, features, lang, glyph_info, mask, color); + } else +#endif + { + 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; + } + + 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, 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; + + 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++) { - 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; - } - } - } - - 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; - - 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; - - 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; + 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 { - 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; + 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; } + } + + error = FT_Load_Glyph(face, glyph_info[i].index, load_flags); + if (error) { + return geterror(error); + } + + error = FT_Get_Glyph(face->glyph, &glyph); + if (error) { + return geterror(error); + } - 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); @@ -699,71 +651,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; + + /* render string into given buffer (the buffer *must* have + the right size, or this will crash) */ - if (!PyArg_ParseTuple(args, "On|izOzi:render", &string, &id, &mask, &dir, &features, &lang, - &stroke_width)) { + 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; } @@ -777,20 +824,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; + im = (Imaging)id; + load_flags = stroke_width ? FT_LOAD_NO_BITMAP : 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 | FT_LOAD_RENDER); + 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); } @@ -798,21 +861,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); } @@ -834,236 +906,312 @@ 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) { + FT_Bitmap_Init(&bitmap_converted); + 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 { - // use antialiased rendering - int k; + 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] < source[k]) - target[k] = source[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 { + 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) - { - 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) - return geterror(error); +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) { + return geterror(error); + } - num_namedstyles = master->num_namedstyles; - list_names = PyList_New(num_namedstyles); + 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); + 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); + } - for (j = 0; j < num_namedstyles; j++) { - if (PyList_GetItem(list_names, j) != NULL) - continue; + 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 (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); + FT_Done_MM_Var(library, master); - return list_names; + 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); } - 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); - 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); - - if (name.name_id == axis.strid) { - axis_name = Py_BuildValue("y#", name.string, name.string_len); - PyDict_SetItemString(list_axis, "name", axis_name); - break; - } + 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); } - PyList_SetItem(list_axes, i, list_axis); + 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_axes; + PyList_SetItem(list_axes, i, list_axis); } - static PyObject* - font_setvarname(FontObject* self, PyObject* args) - { - int error; + FT_Done_MM_Var(library, master); - int instance_index; - if (!PyArg_ParseTuple(args, "i", &instance_index)) - return NULL; + return list_axes; +} - error = FT_Set_Named_Instance(self->face, instance_index); - if (error) - return geterror(error); +static PyObject * +font_setvarname(FontObject *self, PyObject *args) { + int error; - Py_INCREF(Py_None); - return Py_None; + int instance_index; + if (!PyArg_ParseTuple(args, "i", &instance_index)) { + return NULL; } - static PyObject* - font_setvaraxes(FontObject* self, PyObject* args) - { - int error; + error = FT_Set_Named_Instance(self->face, instance_index); + if (error) { + return geterror(error); + } - PyObject *axes, *item; - Py_ssize_t i, num_coords; - FT_Fixed *coords; - FT_Fixed coord; - if (!PyArg_ParseTuple(args, "O", &axes)) - return NULL; + Py_INCREF(Py_None); + return Py_None; +} - if (!PyList_Check(axes)) { - PyErr_SetString(PyExc_TypeError, "argument must be a list"); - return NULL; - } +static PyObject * +font_setvaraxes(FontObject *self, PyObject *args) { + int error; - 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; - } + PyObject *axes, *item; + Py_ssize_t i, num_coords; + FT_Fixed *coords; + FT_Fixed coord; + if (!PyArg_ParseTuple(args, "O", &axes)) { + return NULL; + } - error = FT_Set_Var_Design_Coordinates(self->face, num_coords, coords); - free(coords); - if (error) - return geterror(error); + if (!PyList_Check(axes)) { + PyErr_SetString(PyExc_TypeError, "argument must be a list"); + return NULL; + } - Py_INCREF(Py_None); - return Py_None; + 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; +} #endif static void -font_dealloc(FontObject* self) -{ +font_dealloc(FontObject *self) { if (self->face) { FT_Done_Face(self->face); } @@ -1074,125 +1222,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_NOARGS }, - {"getvaraxes", (PyCFunction) font_getvaraxes, METH_NOARGS }, - {"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); @@ -1200,38 +1338,82 @@ 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); +#ifdef HAVE_RAQM +#if defined(HAVE_RAQM_SYSTEM) || defined(HAVE_FRIBIDI_SYSTEM) + have_raqm = 1; +#else + load_fribidi(); + have_raqm = !!p_fribidi; +#endif +#else + have_raqm = 0; +#endif - setraqm(); - v = PyBool_FromLong(!!p_raqm.raqm); + /* if we have Raqm, we have all three (but possibly no version info) */ + v = PyBool_FromLong(have_raqm); PyDict_SetItemString(d, "HAVE_RAQM", v); + PyDict_SetItemString(d, "HAVE_FRIBIDI", v); + PyDict_SetItemString(d, "HAVE_HARFBUZZ", v); + if (have_raqm) { +#ifdef RAQM_VERSION_MAJOR + v = PyUnicode_FromString(raqm_version_string()); +#else + v = Py_None; +#endif + PyDict_SetItemString(d, "raqm_version", v); + +#ifdef FRIBIDI_MAJOR_VERSION + { + const char *a = strchr(fribidi_version_info, ')'); + const char *b = strchr(fribidi_version_info, '\n'); + if (a && b && a + 2 < b) { + v = PyUnicode_FromStringAndSize(a + 2, b - (a + 2)); + } else { + v = Py_None; + } + } +#else + v = Py_None; +#endif + PyDict_SetItemString(d, "fribidi_version", v); + +#ifdef HB_VERSION_STRING + v = PyUnicode_FromString(hb_version_string()); +#else + v = Py_None; +#endif + PyDict_SetItemString(d, "harfbuzz_version", v); + } 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 bc66a581a22..067c165b248 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 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 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 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 050ae9f0287..c0644b61609 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]; - - 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 93cf7ae85f8..fd99116cb41 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,12 +21,14 @@ #endif -void ImagingSectionEnter(ImagingSectionCookie* cookie) { - *cookie = (PyThreadState *) PyEval_SaveThread(); +void +ImagingSectionEnter(ImagingSectionCookie *cookie) { + *cookie = (PyThreadState *)PyEval_SaveThread(); } -void ImagingSectionLeave(ImagingSectionCookie* cookie) { - PyEval_RestoreThread((PyThreadState*) *cookie); +void +ImagingSectionLeave(ImagingSectionCookie *cookie) { + PyEval_RestoreThread((PyThreadState *)*cookie); } /* -------------------------------------------------------------------- */ @@ -35,12 +37,15 @@ void ImagingSectionLeave(ImagingSectionCookie* cookie) { #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); @@ -52,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"); @@ -70,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: @@ -90,8 +97,7 @@ PyObject* HandleMuxError(WebPMuxError err, char* chunk) { // Encoder type typedef struct { - PyObject_HEAD - WebPAnimEncoder* enc; + PyObject_HEAD WebPAnimEncoder *enc; WebPPicture frame; } WebPAnimEncoderObject; @@ -99,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; @@ -119,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; - - if (!PyArg_ParseTuple(args, "iiIiiiiii", - &width, &height, &bgcolor, &loop_count, &minimize_size, - &kmin, &kmax, &allow_mixed, &verbose)) { + WebPAnimEncoderObject *encp = NULL; + WebPAnimEncoder *enc = NULL; + + if (!PyArg_ParseTuple( + args, + "iiIiiiiii", + &width, + &height, + &bgcolor, + &loop_count, + &minimize_size, + &kmin, + &kmax, + &allow_mixed, + &verbose)) { return NULL; } @@ -154,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)); } @@ -164,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); - - if (!PyArg_ParseTuple(args, "z#iiisifi", - (char**)&rgb, &size, ×tamp, &width, &height, &mode, - &lossless, &quality_factor, &method)) { + 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)) { return NULL; } @@ -218,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); @@ -236,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; - - if (!PyArg_ParseTuple(args, "s#s#s#", - &icc_bytes, &icc_size, &exif_bytes, &exif_size, &xmp_bytes, &xmp_size)) { + 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)) { return NULL; } @@ -270,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) { @@ -312,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 @@ -324,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; @@ -359,43 +389,45 @@ PyObject* _anim_decoder_new(PyObject* self, PyObject* args) if (dec) { if (WebPAnimDecoderGetInfo(dec, &(decp->info))) { decp->dec = dec; - return (PyObject*)decp; + return (PyObject *)decp; } } + WebPDataClear(&(decp->data)); } PyObject_Del(decp); } - PyErr_SetString(PyExc_RuntimeError, "could not create decoder object"); + PyErr_SetString(PyExc_OSError, "could not create decoder object"); 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) -{ - 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; @@ -408,27 +440,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) -{ - 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); @@ -436,9 +468,9 @@ PyObject* _anim_decoder_get_next(PyObject* self) return ret; } -PyObject* _anim_decoder_reset(PyObject* self) -{ - WebPAnimDecoderObject* decp = (WebPAnimDecoderObject *)self; +PyObject * +_anim_decoder_reset(PyObject *self) { + WebPAnimDecoderObject *decp = (WebPAnimDecoderObject *)self; WebPAnimDecoderReset(decp->dec); Py_RETURN_NONE; } @@ -454,39 +486,38 @@ static struct PyMethodDef _anim_encoder_methods[] = { {NULL, NULL} /* sentinel */ }; -// WebPAnimDecoder type definition +// WebPAnimEncoder 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 @@ -500,37 +531,36 @@ static struct PyMethodDef _anim_decoder_methods[] = { // 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 @@ -539,160 +569,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#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; + } + + rgba_mode = strcmp(mode, "RGBA") == 0; + if (!rgba_mode && strcmp(mode, "RGB") != 0) { + Py_RETURN_NONE; + } - 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)) { + 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; } - if (strcmp(mode, "RGBA")==0){ - if (size < width * height * 4){ - Py_RETURN_NONE; - } - #if WEBP_ENCODER_ABI_VERSION >= 0x0100 - if (lossless) { - ImagingSectionEnter(&cookie); - ret_size = WebPEncodeLosslessRGBA(rgb, width, height, 4 * width, &output); - ImagingSectionLeave(&cookie); - } else - #endif - { - ImagingSectionEnter(&cookie); - ret_size = WebPEncodeRGBA(rgb, width, height, 4 * width, quality_factor, &output); - ImagingSectionLeave(&cookie); - } - } else if (strcmp(mode, "RGB")==0){ - if (size < width * height * 3){ - Py_RETURN_NONE; - } - #if WEBP_ENCODER_ABI_VERSION >= 0x0100 - if (lossless) { - ImagingSectionEnter(&cookie); - ret_size = WebPEncodeLosslessRGB(rgb, width, height, 3 * width, &output); - ImagingSectionLeave(&cookie); - } else - #endif - { - ImagingSectionEnter(&cookie); - ret_size = WebPEncodeRGB(rgb, width, height, 3 * width, quality_factor, &output); - ImagingSectionLeave(&cookie); - } + 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 { - Py_RETURN_NONE; + 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_Format(PyExc_ValueError, "encoding error %d", (&pic)->error_code); + 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; - - 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); - - 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) { + /* 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. + + WebPMux *mux = WebPMuxNew(); + WebPMuxSetImage(mux, &image, copy_data); + 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); } - err = WebPMuxSetChunk(mux, "ICCP", &icc_profile, copy_data); - if (err != WEBP_MUX_OK) { - return HandleMuxError(err, "ICCP"); + + 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"); + } } - } - 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; @@ -702,7 +775,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) { @@ -716,58 +789,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)) { - WebPMuxDelete(mux); - goto end; - } + 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; + } - webp = image.bitstream.bytes; - size = image.bitstream.size; + if (WEBP_MUX_OK != WebPMuxGetFrame(mux, 1, &image)) { + WebPMuxDelete(mux); + goto end; + } - vp8_status_code = WebPDecode(webp, size, &config); + webp = image.bitstream.bytes; + size = image.bitstream.size; - if (WEBP_MUX_OK == WebPMuxGetChunk(mux, "ICCP", &icc_profile_data)) - icc_profile = PyBytes_FromStringAndSize((const char*)icc_profile_data.bytes, icc_profile_data.size); + vp8_status_code = WebPDecode(webp, size, &config); - if (WEBP_MUX_OK == WebPMuxGetChunk(mux, "EXIF", &exif_data)) - exif = PyBytes_FromStringAndSize((const char*)exif_data.bytes, exif_data.size); + if (WEBP_MUX_OK == WebPMuxGetChunk(mux, "ICCP", &icc_profile_data)) { + icc_profile = PyBytes_FromStringAndSize( + (const char *)icc_profile_data.bytes, icc_profile_data.size); + } - WebPDataClear(&image.bitstream); - WebPMuxDelete(mux); + 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); @@ -777,27 +859,45 @@ PyObject* WebPDecode_wrapper(PyObject* self, PyObject* args) 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 * +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 * +WebPDecoderBuggyAlpha_wrapper() { return Py_BuildValue("i", WebPDecoderBuggyAlpha()); } @@ -805,8 +905,7 @@ PyObject* WebPDecoderBuggyAlpha_wrapper() { /* 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"}, @@ -814,60 +913,78 @@ static PyMethodDef webpMethods[] = {"WebPEncode", WebPEncode_wrapper, METH_VARARGS, "WebPEncode"}, {"WebPDecode", WebPDecode_wrapper, METH_VARARGS, "WebPDecode"}, {"WebPDecoderVersion", WebPDecoderVersion_wrapper, METH_NOARGS, "WebPVersion"}, - {"WebPDecoderBuggyAlpha", WebPDecoderBuggyAlpha_wrapper, METH_NOARGS, "WebPDecoderBuggyAlpha"}, - {NULL, NULL} -}; - -void addMuxFlagToModule(PyObject* m) { + {"WebPDecoderBuggyAlpha", + WebPDecoderBuggyAlpha_wrapper, + METH_NOARGS, + "WebPDecoderBuggyAlpha"}, + {NULL, NULL}}; + +void +addMuxFlagToModule(PyObject *m) { + PyObject *have_webpmux; #ifdef HAVE_WEBPMUX - PyModule_AddObject(m, "HAVE_WEBPMUX", Py_True); + have_webpmux = Py_True; #else - PyModule_AddObject(m, "HAVE_WEBPMUX", Py_False); + have_webpmux = Py_False; #endif + Py_INCREF(have_webpmux); + PyModule_AddObject(m, "HAVE_WEBPMUX", have_webpmux); } -void addAnimFlagToModule(PyObject* m) { +void +addAnimFlagToModule(PyObject *m) { + PyObject *have_webpanim; #ifdef HAVE_WEBPANIM - PyModule_AddObject(m, "HAVE_WEBPANIM", Py_True); + have_webpanim = Py_True; #else - PyModule_AddObject(m, "HAVE_WEBPANIM", Py_False); + have_webpanim = Py_False; #endif + Py_INCREF(have_webpanim); + PyModule_AddObject(m, "HAVE_WEBPANIM", have_webpanim); } -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 5ab6ca9d156..e236264cdb4 100644 --- a/src/decode.c +++ b/src/decode.c @@ -32,57 +32,58 @@ #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/Bit.h" +#include "libImaging/Bcn.h" +#include "libImaging/Gif.h" +#include "libImaging/Raw.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 +103,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 +114,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 +138,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 +162,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 +185,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 +194,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 *)calloc(1, state->bytes); + if (!state->buffer) { + return ImagingError_MemoryError(); + } } /* Keep a reference to the image object, to make sure it doesn't @@ -217,14 +216,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,75 +234,72 @@ _setfd(ImagingDecoderObject* decoder, PyObject* args) return Py_None; } - static PyObject * -_get_pulls_fd(ImagingDecoderObject *decoder) -{ +_get_pulls_fd(ImagingDecoderObject *decoder, void *closure) { return PyBool_FromLong(decoder->pulls_fd); } static struct PyMethodDef methods[] = { - {"decode", (PyCFunction)_decode, 1}, - {"cleanup", (PyCFunction)_decode_cleanup, 1}, - {"setimage", (PyCFunction)_setimage, 1}, - {"setfd", (PyCFunction)_setfd, 1}, + {"decode", (PyCFunction)_decode, METH_VARARGS}, + {"cleanup", (PyCFunction)_decode_cleanup, METH_VARARGS}, + {"setimage", (PyCFunction)_setimage, METH_VARARGS}, + {"setfd", (PyCFunction)_setfd, METH_VARARGS}, {NULL, NULL} /* sentinel */ }; 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; unpack = ImagingFindUnpacker(mode, rawmode, &bits); if (!unpack) { Py_DECREF(decoder); - PyErr_SetString(PyExc_ValueError, "unknown raw mode"); + PyErr_SetString(PyExc_ValueError, "unknown raw mode for given image mode"); return -1; } @@ -313,25 +309,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 +333,58 @@ 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)) + char *pixel_format = ""; + if (!PyArg_ParseTuple(args, "si|s", &mode, &n, &pixel_format)) { 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 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 5: /* BC5: 2-channel 8-bit via 2 BC3 alpha blocks */ + actual = "RGB"; + 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) { @@ -393,51 +392,50 @@ PyImaging_BcnDecoderNew(PyObject* self, PyObject* args) return NULL; } - decoder = PyImaging_DecoderNew(0); - if (decoder == NULL) + decoder = PyImaging_DecoderNew(sizeof(char *)); + if (decoder == NULL) { return NULL; + } decoder->decode = ImagingBcnDecode; decoder->state.state = n; - decoder->state.ystep = ystep; + ((BCNSTATE *)decoder->state.context)->pixel_format = pixel_format; - 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 +443,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; + uint32_t 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 +809,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 - -#include "Jpeg.h" - -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 */ +#undef HAVE_PROTOTYPES +#undef HAVE_STDDEF_H +#undef HAVE_STDLIB_H +#undef UINT8 +#undef UINT16 +#undef UINT32 +#undef INT8 +#undef INT16 +#undef INT32 + +#include "libImaging/Jpeg.h" + +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 */ 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 +851,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 +874,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 +921,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 21869b26ebe..0ce10e2493c 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,103 +213,94 @@ _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[] = { - {"draw", (PyCFunction)_draw, 1}, - {"expose", (PyCFunction)_expose, 1}, - {"paste", (PyCFunction)_paste, 1}, - {"query_palette", (PyCFunction)_query_palette, 1}, - {"getdc", (PyCFunction)_getdc, 1}, - {"releasedc", (PyCFunction)_releasedc, 1}, - {"frombytes", (PyCFunction)_frombytes, 1}, - {"tobytes", (PyCFunction)_tobytes, 1}, - {"fromstring", (PyCFunction)_frombytes, 1}, - {"tostring", (PyCFunction)_tobytes, 1}, + {"draw", (PyCFunction)_draw, METH_VARARGS}, + {"expose", (PyCFunction)_expose, METH_VARARGS}, + {"paste", (PyCFunction)_paste, METH_VARARGS}, + {"query_palette", (PyCFunction)_query_palette, METH_VARARGS}, + {"getdc", (PyCFunction)_getdc, METH_VARARGS}, + {"releasedc", (PyCFunction)_releasedc, METH_VARARGS}, + {"frombytes", (PyCFunction)_frombytes, METH_VARARGS}, + {"tobytes", (PyCFunction)_tobytes, METH_VARARGS}, {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 @@ PyImaging_GrabScreenWin32(PyObject* self, PyObject* args) 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}; + + if (!OpenClipboard(NULL)) { + PyErr_SetString(PyExc_OSError, "failed to open clipboard"); + return NULL; + } - clip = OpenClipboard(NULL); - /* FIXME: check error status */ + // 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; + } + } + } - 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; + 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 */ + + 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", + "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; + + 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_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, "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"); - - 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_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); @@ -817,7 +880,7 @@ PyImaging_DrawWmf(PyObject* self, PyObject* args) #endif /* _WIN32 */ /* -------------------------------------------------------------------- */ -/* X11 support */ +/* X11 support */ #ifdef HAVE_XCB #include @@ -825,27 +888,30 @@ PyImaging_DrawWmf(PyObject* self, PyObject* args) /* -------------------------------------------------------------------- */ /* X11 screen grabber */ -PyObject* -PyImaging_GrabScreenX11(PyObject* self, PyObject* args) -{ +PyObject * +PyImaging_GrabScreenX11(PyObject *self, PyObject *args) { int width, height; - char* display_name; - xcb_connection_t* connection; + 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; + 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)) + 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_IOError, "X connection failed: error %i", xcb_connection_has_error(connection)); + PyErr_Format( + PyExc_OSError, + "X connection failed: error %i", + xcb_connection_has_error(connection)); xcb_disconnect(connection); return NULL; } @@ -860,7 +926,7 @@ PyImaging_GrabScreenX11(PyObject* self, PyObject* args) if (screen == NULL || screen->root == 0) { // this case is usually caught with "X connection failed: error 6" above xcb_disconnect(connection); - PyErr_SetString(PyExc_IOError, "X screen not found"); + PyErr_SetString(PyExc_OSError, "X screen not found"); return NULL; } @@ -869,13 +935,26 @@ PyImaging_GrabScreenX11(PyObject* self, PyObject* args) /* 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); + 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_IOError, "X get_image failed: error %i (%i, %i, %i)", - error->error_code, error->major_code, error->minor_code, error->resource_id); + 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; @@ -884,17 +963,18 @@ PyImaging_GrabScreenX11(PyObject* self, PyObject* args) /* store data in Python buffer */ if (reply->depth == 24) { - buffer = PyBytes_FromStringAndSize((char*)xcb_get_image_data(reply), - xcb_get_image_data_length(reply)); + buffer = PyBytes_FromStringAndSize( + (char *)xcb_get_image_data(reply), xcb_get_image_data_length(reply)); } else { - PyErr_Format(PyExc_IOError, "unsupported bit depth: %i", reply->depth); + PyErr_Format(PyExc_OSError, "unsupported bit depth: %i", reply->depth); } free(reply); xcb_disconnect(connection); - if (!buffer) + if (!buffer) { return NULL; + } return Py_BuildValue("(ii)N", width, height, buffer); } diff --git a/src/encode.c b/src/encode.c index 41ba124c4ee..2ecf9723bc6 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 *)calloc(1, 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,68 +299,66 @@ _setfd(ImagingEncoderObject* encoder, PyObject* args) } static PyObject * -_get_pushes_fd(ImagingEncoderObject *encoder) -{ +_get_pushes_fd(ImagingEncoderObject *encoder, void *closure) { return PyBool_FromLong(encoder->pushes_fd); } static struct PyMethodDef methods[] = { - {"encode", (PyCFunction)_encode, 1}, - {"cleanup", (PyCFunction)_encode_cleanup, 1}, - {"encode_to_file", (PyCFunction)_encode_to_file, 1}, - {"encode_to_pyfd", (PyCFunction)_encode_to_pyfd, 1}, - {"setimage", (PyCFunction)_setimage, 1}, - {"setfd", (PyCFunction)_setfd, 1}, + {"encode", (PyCFunction)_encode, METH_VARARGS}, + {"cleanup", (PyCFunction)_encode_cleanup, METH_VARARGS}, + {"encode_to_file", (PyCFunction)_encode_to_file, METH_VARARGS}, + {"encode_to_pyfd", (PyCFunction)_encode_to_pyfd, METH_VARARGS}, + {"setimage", (PyCFunction)_setimage, METH_VARARGS}, + {"setfd", (PyCFunction)_setfd, METH_VARARGS}, {NULL, NULL} /* sentinel */ }; 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, 301, 532}; 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 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 (key_int == TIFFTAG_YCBCRSUBSAMPLING) { + status = ImagingLibTiffSetField( + &encoder->state, + (ttag_t)key_int, + (UINT16)PyLong_AsLong(PyTuple_GetItem(value, 0)), + (UINT16)PyLong_AsLong(PyTuple_GetItem(value, 1))); } else if (type == TIFF_SHORT) { UINT16 *av; /* 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 +831,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 +843,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 +855,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 +867,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 +879,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 +891,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 +942,9 @@ PyImaging_LibTiffEncoderNew(PyObject* self, PyObject* args) } } - encoder->encode = ImagingLibTiffEncode; + encoder->encode = ImagingLibTiffEncode; - return (PyObject*) encoder; + return (PyObject *)encoder; } #endif @@ -928,26 +958,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 - -#include "Jpeg.h" - -static unsigned int* get_qtables_arrays(PyObject* qtables, int* qtablesLen) { - PyObject* tables; - PyObject* table; - PyObject* table_data; +#undef HAVE_PROTOTYPES +#undef HAVE_STDDEF_H +#undef HAVE_STDLIB_H +#undef UINT8 +#undef UINT16 +#undef UINT32 +#undef INT8 +#undef INT16 +#undef INT32 + +#include "libImaging/Jpeg.h" + +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 +990,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 +1014,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 +1033,9 @@ static unsigned int* get_qtables_arrays(PyObject* qtables, int* qtablesLen) { 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 +1046,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 +1087,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); - - ((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; + 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; + + 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 +1190,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 +1261,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 15ffa11fc77..514fb292913 100644 --- a/src/libImaging/Access.c +++ b/src/libImaging/Access.c @@ -9,32 +9,34 @@ * See the README file for information on usage and redistribution. */ - #include "Imaging.h" -/* use Tests/make_hash.py to calculate these values */ +/* use make_hash.py from the pillow-scripts repository to calculate these values */ #define ACCESS_TABLE_SIZE 27 #define ACCESS_TABLE_HASH 3078 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 a074334aaab..6d728f9088b 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 7fff0448639..e1b16b34ac0 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/Bcn.h b/src/libImaging/Bcn.h new file mode 100644 index 00000000000..1a6fbee4576 --- /dev/null +++ b/src/libImaging/Bcn.h @@ -0,0 +1,3 @@ +typedef struct { + char *pixel_format; +} BCNSTATE; diff --git a/src/libImaging/BcnDecode.c b/src/libImaging/BcnDecode.c index c2c4f21e7c3..22b36eb7acc 100644 --- a/src/libImaging/BcnDecode.c +++ b/src/libImaging/BcnDecode.c @@ -11,846 +11,879 @@ * https://creativecommons.org/publicdomain/zero/1.0/ */ - #include "Imaging.h" +#include "Bcn.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) +typedef struct { + INT8 a0, a1; + UINT8 lut[6]; +} bc5s_alpha; -#define LOAD32(p) \ - (p)[0] | ((p)[1] << 8) | ((p)[2] << 16) | ((p)[3] << 24) +#define LOAD16(p) (p)[0] | ((p)[1] << 8) -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); -} +#define LOAD32(p) (p)[0] | ((p)[1] << 8) | ((p)[2] << 16) | ((p)[3] << 24) -static void bc3_alpha_load(bc3_alpha *dst, const UINT8 *src) { - memcpy(dst, src, sizeof(bc3_alpha)); +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 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); - - 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_bc1_color(rgba *dst, const UINT8 *src, int separate_alpha) { + 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; + + + /* NOTE: BC2 and BC3 reuse BC1 color blocks but always act like c0 > c1 */ + if (col.c0 > col.c1 || separate_alpha) { + 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); - - 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_bc3_alpha(char *dst, const UINT8 *src, int stride, int o, int sign) { + UINT16 a0, a1; + UINT8 a[8]; + int n, lut1, lut2, aw; + if (sign == 1) { + bc5s_alpha b; + memcpy(&b, src, sizeof(bc5s_alpha)); + a0 = (b.a0 + 255) / 2; + a1 = (b.a1 + 255) / 2; + lut1 = b.lut[0] | (b.lut[1] << 8) | (b.lut[2] << 16); + lut2 = b.lut[3] | (b.lut[4] << 8) | (b.lut[5] << 16); + } else { + bc3_alpha b; + memcpy(&b, src, sizeof(bc3_alpha)); + a0 = b.a0; + a1 = b.a1; + lut1 = b.lut[0] | (b.lut[1] << 8) | (b.lut[2] << 16); + lut2 = b.lut[3] | (b.lut[4] << 8) | (b.lut[5] << 16); + } + + 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; + } + for (n = 0; n < 8; n++) { + aw = 7 & (lut1 >> (3 * n)); + dst[stride * n + o] = a[aw]; + } + for (n = 0; n < 8; n++) { + aw = 7 & (lut2 >> (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, 0); } -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, 1); + 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, 1); + decode_bc3_alpha((char *)col, src, sizeof(col[0]), 3, 0); } -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, 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, int sign) { + decode_bc3_alpha((char *)col, src, sizeof(col[0]), 0, sign); + decode_bc3_alpha((char *)col, src + 8, sizeof(col[0]), 1, sign); } /* 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}; - -static const char *bc7_get_weights(int n) { - if (n == 2) { - return bc7_weights2; - } - if (n == 3) { - return bc7_weights3; - } - return bc7_weights4; + 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 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; - - /* 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; - - /* 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; - } - - /* 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; - } - - /* p-bits */ +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); + +#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; + } + + /* 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; + } + + /* alpha */ + for (i = 0; i < numep; i++) { + if (ab) { + LOAD(val, ab); + } else { + val = 255; + } + endpoints[i].a = val; + } + + /* 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; - - 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]); - } + 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]); + } #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}}; - -static void bc6_sign_extend(UINT16 *v, int prec) { - int x = *v; - if (x & (1 << (prec - 1))) { - x |= -1 << prec; - } - *v = (UINT16)x; + {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 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 (s) { - return -x; - } - return 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 (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; - - bc6_lerp(&col[i], &ueps[s], &ueps[s+3], cw[i0], 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; + + 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, char *pixel_format) { + 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); + case 5: + while (bytes >= 16) { + rgba col[16]; + memset(col, 0, 16 * sizeof(col[0])); + decode_bc5_block(col, ptr, strcmp(pixel_format, "BC5S") == 0 ? 1 : 0); + put_block(im, state, (const char *)col, sizeof(col[0]), C); + ptr += 16; + bytes -= 16; + if (state->y >= ymax) { + return -1; + } + } + break; + 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; + int C = (width & 3) | (height & 3) ? 1 : 0; + char *pixel_format = ((BCNSTATE *)state->context)->pixel_format; + return decode_bcn(im, state, buf, bytes, N, C, pixel_format); } diff --git a/src/libImaging/Bit.h b/src/libImaging/Bit.h index 56e3a17d2e7..f64bfb46990 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 7120b332138..28baa8b7ea8 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; - - if (bitstate->sign) - bitstate->signmask = (1<<(bitstate->bits-1)); + bitstate->mask = (1 << bitstate->bits) - 1; - /* check image orientation */ - if (state->ystep < 0) { - state->y = state->ysize-1; - state->ystep = -1; - } else - state->ystep = 1; + if (bitstate->sign) { + bitstate->signmask = (1 << (bitstate->bits - 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 19a080d6d5b..a53ae0fad53 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 9537c4f98ec..2e45a33587c 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,14 +282,12 @@ 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; - // from http://www.mia.uni-saarland.de/Publications/gwosdek-ssvm11.pdf + // from https://www.mia.uni-saarland.de/Publications/gwosdek-ssvm11.pdf // [7] Box length. L = sqrt(12.0 * sigma2 + 1.0); // [11] Integer part of box radius. diff --git a/src/libImaging/Chops.c b/src/libImaging/Chops.c index a1673dff6c5..f9c005efe3a 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,97 +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 ); +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); +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); +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 f01d38993d1..fd6e268b585 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 9f57225431f..517a4dbe363 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); + INT32 v = in[0] + ((int)in[1] << 8); memcpy(out_, &v, sizeof(v)); } } - static void -I16B_I(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) { - INT32 v = in[1] + ((int) in[0] << 8); + INT32 v = in[1] + ((int)in[0] << 8); memcpy(out_, &v, sizeof(v)); } } static void -I16L_F(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); + FLOAT32 v = in[0] + ((int)in[1] << 8); memcpy(out_, &v, sizeof(v)); } } - static void -I16B_F(UINT8* out_, const UINT8* in, int xsize) -{ +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); + FLOAT32 v = in[1] + ((int)in[0] << 8); memcpy(out_, &v, sizeof(v)); } } static void -L_I16L(UINT8* out, const UINT8* in, int xsize) -{ +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 }, - - { "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", "LA", la2lA }, - - { "I", "L", i2l }, - { "I", "F", i2f }, - { "I", "RGB", i2rgb }, - { "I", "RGBA", i2rgb }, - { "I", "RGBX", i2rgb }, - { "I", "HSV", i2hsv }, - - { "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 }, - - { "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 }, - - { "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 }, - - { "YCbCr", "L", ycbcr2l }, - { "YCbCr", "LA", ycbcr2la }, - { "YCbCr", "RGB", ImagingConvertYCbCr2RGB }, - - { "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;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 }, - - { "I;16", "F", I16L_F }, - { "I;16L", "F", I16L_F }, - { "I;16B", "F", I16B_F }, - - { NULL } -}; + {"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}, + + {"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}, + + {"I", "L", i2l}, + {"I", "F", i2f}, + {"I", "RGB", i2rgb}, + {"I", "RGBA", i2rgb}, + {"I", "RGBX", i2rgb}, + {"I", "HSV", i2hsv}, + + {"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}, + + {"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}, + + {"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}, + + {"YCbCr", "L", ycbcr2l}, + {"YCbCr", "LA", ycbcr2la}, + {"YCbCr", "RGB", ImagingConvertYCbCr2RGB}, + + {"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;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}, + + {"I;16", "F", I16L_F}, + {"I;16L", "F", I16L_F}, + {"I;16B", "F", I16B_F}, + + {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++ = L24(&palette[in[x] * 4]) >> 16; + } } 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++ = L24(&palette[in[0] * 4]) >> 16; + } } 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]; - out[0] = out[1] = out[2] = L(rgba) / 1000; + for (x = 0; x < xsize; x++, out += 4) { + const UINT8 *rgba = &palette[*in++ * 4]; + out[0] = out[1] = out[2] = L24(rgba) >> 16; 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] = L24(&palette[in[0] * 4]) >> 16; 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 = L24(&palette[in[x] * 4]) >> 16; 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++ = L24(&palette[in[0] * 4]) >> 16; + } } 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,117 @@ 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) + if (!convert) { #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", imIn->mode, mode); - return (Imaging) ImagingError_ValueError(buf); - } + static char buf[100]; + snprintf(buf, 100, "conversion from %.10s to %.10s 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[100]; + snprintf( + buf, + 100, + "conversion from %.10s to %.10s not supported in convert_transparent", + imIn->mode, + mode); + return (Imaging)ImagingError_ValueError(buf); } #endif @@ -1703,41 +1669,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 6ce549111e4..142f065e57d 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) -{ +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) { - + 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; + 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[0] = (UINT8)y; + out[1] = (UINT8)cb; + out[2] = (UINT8)cr; out[3] = a; } } void -ImagingConvertYCbCr2RGB(UINT8* out, const UINT8* in, int pixels) -{ +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 1bc9b1a709a..571133e14b6 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 4407c1b1d2d..2425b4cd589 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 5042902316d..f8a2901b8c7 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 58adc1b6355..0e4899b5b49 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,26 +396,24 @@ 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) -{ +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) { @@ -439,7 +435,7 @@ draw_horizontal_lines(Imaging im, int n, Edge *e, int ink, int *x_pos, int y, hl } (*hline)(im, xmin, e[i].ymin, xmax, ink); - *x_pos = xmax+1; + *x_pos = xmax + 1; } } } @@ -448,12 +444,9 @@ draw_horizontal_lines(Imaging im, int n, Edge *e, int ink, int *x_pos, int y, hl * 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, int hasAlpha) { + Edge **edge_table; + float *xx; int edge_count = 0; int ymin = im->ysize - 1; int ymax = 0; @@ -465,21 +458,24 @@ 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++) { - if (e[i].ymin == e[i].ymax) { - 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) { + if (hasAlpha != 1) { + (*hline)(im, e[i].xmin, e[i].ymin, e[i].xmax, ink); + } + continue; + } edge_table[edge_count++] = (e + i); } if (ymin < 0) { @@ -498,9 +494,8 @@ 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; } @@ -511,31 +506,38 @@ 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) { - 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 + if (hasAlpha == 1) { + int x_pos = 0; + for (i = 1; i < j; i += 2) { + 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); + } else { + for (i = 1; i < j; i += 2) { + (*hline)(im, ROUND_UP(xx[i - 1]), ymin, ROUND_DOWN(xx[i]), ink); } - (*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); @@ -544,47 +546,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) -{ - return polygon_generic(im, n, e, ink, eofill, hline8); +polygon8(Imaging im, int n, Edge *e, int ink, int eofill) { + return polygon_generic(im, n, e, ink, eofill, hline8, 0); } static inline int -polygon32(Imaging im, int n, Edge *e, int ink, int eofill) -{ - return polygon_generic(im, n, e, ink, eofill, hline32); +polygon32(Imaging im, int n, Edge *e, int ink, int eofill) { + return polygon_generic(im, n, e, ink, eofill, hline32, 0); } static inline int -polygon32rgba(Imaging im, int n, Edge *e, int ink, int eofill) -{ - return polygon_generic(im, n, e, ink, eofill, hline32rgba); +polygon32rgba(Imaging im, int n, Edge *e, int ink, int eofill) { + return polygon_generic(im, n, e, ink, eofill, hline32rgba, 1); } 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; @@ -598,27 +599,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(); @@ -629,10 +629,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(); @@ -643,10 +641,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; @@ -655,14 +652,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; @@ -676,13 +673,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); } @@ -690,34 +686,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 */ @@ -725,10 +731,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 + width, x1 - i, y1 - width + 1, ink); + draw->line(im, x0 + i, y0 + width, x0 + i, y1 - width + 1, ink); } } @@ -736,267 +742,939 @@ 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) -{ - int i, n; - DRAW* draw; +ImagingDrawPolygon(Imaging im, int count, int *xy, const void *ink_, int fill, int width, int op) { + int i, n, x0, y0, x1, y1; + 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++) { + x0 = xy[i * 2]; + y0 = xy[i * 2 + 1]; + x1 = xy[i * 2 + 2]; + y1 = xy[i * 2 + 3]; + if (y0 == y1 && i != 0 && y0 == xy[i * 2 - 1]) { + // This is a horizontal line, + // that immediately follows another horizontal line + Edge *last_e = &e[n-1]; + if (x1 > x0 && x0 > xy[i * 2 - 2]) { + // They are both increasing in x + last_e->xmax = x1; + continue; + } else if (x1 < x0 && x0 < xy[i * 2 - 2]) { + // They are both decreasing in x + last_e->xmin = x1; + continue; + } + } + add_edge(&e[n++], x0, y0, x1, y1); + } + if (xy[i * 2] != xy[0] || xy[i * 2 + 1] != xy[1]) { + add_edge(&e[n++], xy[i * 2], xy[i * 2 + 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); - + if (width == 1) { + for (i = 0; i < count - 1; i++) { + draw->line(im, xy[i * 2], xy[i * 2 + 1], xy[i * 2 + 2], xy[i * 2 + 3], ink); + } + draw->line(im, xy[i * 2], xy[i * 2 + 1], xy[0], xy[1], ink); + } else { + for (i = 0; i < count - 1; i++) { + ImagingDrawWideLine(im, xy[i * 2], xy[i * 2 + 1], xy[i * 2 + 2], xy[i * 2 + 3], ink_, width, op); + } + ImagingDrawWideLine(im, xy[i * 2], xy[i * 2 + 1], xy[0], xy[1], ink_, width, op); + } } 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); + 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; + } +} + +// 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); +} + +int8_t +quarter_next(quarter_state *s, int32_t *ret_x, int32_t *ret_y) { + if (s->finished) { + return -1; } - if (modf(y_f, &modf_int) == 0.5) { - *y = i_sin > 0 ? FLOOR(y_f) : CEIL(y_f); + *ret_x = s->cx; + *ret_y = s->cy; + if (s->cx == s->ex && s->cy == s->ey) { + s->finished = 1; } else { - *y = FLOOR(y_f + 0.5); + // 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; + } + newdelta = quarter_delta(s, s->cx - 2, s->cy); + if (ndelta > newdelta) { + nx = s->cx - 2; + ny = s->cy; + } + } + s->cx = nx; + s->cy = ny; } + return 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; +// 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. - DRAWINIT(); - - while (end < start) - end += 360; +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; - if (end - start > 360) { - // no need to go in loops - end = start + 361; +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; } +} - w = x1 - x0; - h = y1 - y0; - if (w <= 0 || h <= 0) - return 0; +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; - cx = (x0 + x1) / 2; - cy = (y0 + y1) / 2; + 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; +} - 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); - } +// 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; } - } else { - inner = (mode == ARC || !fill) ? 1 : 0; + clip_tree_transpose(root->l); + clip_tree_transpose(root->r); + } +} - // Build edge list - // malloc check UNDONE, FLOAT? - maxEdgeCount = ceil(end - start); - if (inner) { - maxEdgeCount *= 2; +// 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 +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; } - maxEdgeCount += 3; - e = calloc(maxEdgeCount, sizeof(Edge)); - if (!e) { + event_list *end = malloc(sizeof(event_list)); + if (!end) { + free(start); ImagingError_MemoryError(); return -1; } - - // Outer circle - n = 0; - for (i = start; i < end+1; i++) { - if (i > end) { - i = end; + 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; } - ellipsePoint(cx, cy, w, h, i, &x, &y); - if (i == start) { - sx = x, sy = y; - } else { - add_edge(&e[n++], lx, ly, x, y); + } 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)); } - 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; + 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 { - 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; + 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; +} - 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); +// 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; } } + } +} - draw->polygon(im, n, e, ink, 0); +// 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; +} - free(e); +// 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; + + // add one more semiplane to avoid spikes + if (ar - al < 90) { + clip_node *old_root = s->root; + clip_node *spike_clipper = s->nodes + s->node_count++; + s->root = s->nodes + s->node_count++; + s->root->l = old_root; + s->root->r = spike_clipper; + s->root->type = CT_AND; + + spike_clipper->l = spike_clipper->r = NULL; + spike_clipper->type = CT_CLIP; + spike_clipper->a = (xl + xr) / 2.0; + spike_clipper->b = (yl + yr) / 2.0; + spike_clipper->c = 0; + } +} + +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 -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); +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 -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); +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 -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); +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) -{ - return ellipse(im, x0, y0, x1, y1, start, end, ink, fill, width, PIESLICE, op); +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); + } } /* -------------------------------------------------------------------- */ @@ -1007,7 +1685,6 @@ ImagingDrawPieslice(Imaging im, int x0, int y0, int x1, int y1, itself */ struct ImagingOutlineInstance { - float x0, y0; float x, y; @@ -1016,18 +1693,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; @@ -1038,22 +1713,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 */ @@ -1062,14 +1736,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; } @@ -1081,8 +1756,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; @@ -1090,15 +1764,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; @@ -1107,18 +1781,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; @@ -1126,22 +1806,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; @@ -1151,81 +1829,81 @@ 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; - /* FIXME: ugly! */ - outline->edges = NULL; - outline->count = outline->size = 0; - eOut = allocate(outline, n); if (!eOut) { - outline->edges = eIn; - outline->count = outline->size = n; ImagingError_MemoryError(); return -1; } 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); + free(outline->edges); + + /* FIXME: ugly! */ + outline->edges = NULL; + outline->count = outline->size = 0; return 0; } 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 7b4ff0b4380..93e7af0bce9 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 45fab0a6ed4..3f2cb33b2d2 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 (bytes < 2) - break; - - 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++; - - 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->state == NEWLINE) { + if (bytes < 1) { + break; + } + *ptr++ = '\n'; + bytes--; + state->state = HEXBYTE; + } + + if (bytes < 2) { + break; + } + + 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++; + } + + 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 515d85d1fe9..f42ff9aec9e 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 6f014c1f822..76d0abccc4f 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 d641a59962a..f7206022843 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); @@ -73,22 +76,34 @@ ImagingFillLinearGradient(const char *mode) return NULL; } - for (y = 0; y < 256; y++) { - memset(im->image8[y], (unsigned char) y, 256); + if (im->image8) { + for (y = 0; y < 256; y++) { + memset(im->image8[y], (unsigned char)y, 256); + } + } else { + int x; + for (y = 0; y < 256; y++) { + for (x = 0; x < 256; x++) { + if (im->type == IMAGING_TYPE_FLOAT32) { + IMAGING_PIXEL_FLOAT32(im, x, y) = y; + } else { + IMAGING_PIXEL_INT32(im, x, y) = y; + } + } + } } 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,11 +113,19 @@ 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 { + d = 255; + } + if (im->image8) { im->image8[y][x] = d; + } else { + if (im->type == IMAGING_TYPE_FLOAT32) { + IMAGING_PIXEL_FLOAT32(im, x, y) = d; + } else { + IMAGING_PIXEL_INT32(im, x, y) = d; + } } } } diff --git a/src/libImaging/Filter.c b/src/libImaging/Filter.c index b033abf6623..fab3b494819 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"); + 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) + 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_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);\ +#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 108e1edf93a..d6e4ea0ff9d 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,26 +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; \ + } -#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; @@ -41,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 */ @@ -50,8 +46,10 @@ ImagingFliDecode(Imaging im, ImagingCodecState state, UINT8* buf, Py_ssize_t byt ptr = buf; framesize = I32(ptr); - if (framesize < I32(ptr)) - return 0; + // there can be one pad byte in the framesize + if (bytes + (bytes % 2) < framesize) { + return 0; + } /* Make sure this is a frame chunk. The Python driver takes case of other chunk types. */ @@ -60,184 +58,210 @@ 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) */ - /* 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; + 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 (INT32_MAX / state->xsize < state->ysize) { + /* Integer overflow, bail */ + state->errcode = IMAGING_CODEC_OVERRUN; + return -1; + } + /* Note, have to check Data + size, not just ptr + size) */ + if (data + (state->xsize * state->ysize) > ptr + bytes) { + /* not enough data for frame */ + /* UNDONE Unclear that we're actually going to leave the buffer at the right place. */ + 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 ) { + // If there's no advance, we're in an infinite loop + state->errcode = IMAGING_CODEC_BROKEN; + return -1; + } + 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 fd5e2595824..0c591579217 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 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 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_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_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(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); \ + } 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; } - 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_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 ea7a35e4870..e73153600d0 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,55 +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; - } 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) { + 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; + mask = 0x000000ff; #else - mask = 0xff000000; + mask = 0xff000000; #endif - } - GETBBOX(image32, mask); + } + 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; @@ -91,148 +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; - UINT8* pixel = *im->image8; + 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); + v = pixel[0] + (pixel[1] << 8); #else - memcpy(&v, pixel, sizeof(v)); + memcpy(&v, pixel, sizeof(v)); #endif - imin = imax = v; - for (y = 0; y < im->ysize; y++) { - for (x = 0; x < im->xsize; x++) { - pixel = im->image[y] + x * sizeof(v); + 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); + v = pixel[0] + (pixel[1] << 8); #else - memcpy(&v, pixel, sizeof(v)); + 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; + 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; @@ -243,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; } @@ -264,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 */ @@ -289,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; @@ -300,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; @@ -318,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; + } } } } @@ -327,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 2cb95efd289..4029bbfe5f1 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; - } - - /* Clear code */ - context->clear = 1 << context->bits; - - /* End code */ - context->end = context->clear + 1; - - /* Interlace */ - if (context->interlace) { - context->interlace = 1; - context->step = context->repeat = 8; - } else - context->step = 1; - - state->state = 1; + /* Initialise state */ + if (context->bits < 0 || context->bits > 12) { + state->errcode = IMAGING_CODEC_CONFIG; + return -1; + } + + /* Clear code */ + context->clear = 1 << context->bits; + + /* End code */ + context->end = context->clear + 1; + + /* Interlace */ + if (context->interlace) { + context->interlace = 1; + context->step = context->repeat = 8; + } else { + context->step = 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; + + /* Initial code size */ + context->codesize = context->bits + 1; + context->codemask = (1 << context->codesize) - 1; + + /* Buffer pointer. We fill the buffer from right, which + allows us to return all of it in one operation. */ + context->bufferindex = GIFBUFFER; - if (state->state == 1) { - - /* First free entry in table */ - context->next = context->clear + 2; - - /* Initial code size */ - context->codesize = context->bits + 1; - context->codemask = (1 << context->codesize) - 1; - - /* Buffer pointer. We fill the buffer from right, which - allows us to return all of it in one operation. */ - context->bufferindex = GIFBUFFER; - - state->state = 2; - } - - if (context->bufferindex < GIFBUFFER) { - - /* Return whole buffer in one chunk */ - i = GIFBUFFER - context->bufferindex; - p = &context->buffer[context->bufferindex]; - - context->bufferindex = GIFBUFFER; - - } else { - - /* Get current symbol */ - - while (context->bitcount < context->codesize) { - - if (context->blocksize > 0) { - - /* Read next byte */ - c = *ptr++; bytes--; - - context->blocksize--; - - /* New bits are shifted in from from the left. */ - context->bitbuffer |= (INT32) c << context->bitcount; - context->bitcount += 8; - - } else { - - /* New GIF block */ - - /* 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; - - context->blocksize = c; - - ptr++; bytes--; - - } - } - - /* Extract current symbol from bit buffer. */ - c = (int) context->bitbuffer & context->codemask; - - /* Adjust buffer */ - context->bitbuffer >>= context->codesize; - context->bitcount -= context->codesize; - - /* 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->clear) { - if (state->state != 2) - state->state = 1; - continue; - } - - if (c == context->end) - break; - - i = 1; - p = &context->lastdata; - - 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; - - } else { - - thiscode = c; - - if (c > context->next) { - state->errcode = IMAGING_CODEC_BROKEN; - return -1; - } - - if (c == context->next) { - - /* c == next is allowed. not sure why. */ - - if (context->bufferindex <= 0) { - state->errcode = IMAGING_CODEC_BROKEN; - return -1; - } - - context->buffer[--context->bufferindex] = - context->lastdata; - - c = context->lastcode; - - } - - 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; + state->state = 2; + } - if (context->next < GIFTABLE) { + if (context->bufferindex < GIFBUFFER) { + /* Return whole buffer in one chunk */ + i = GIFBUFFER - context->bufferindex; + p = &context->buffer[context->bufferindex]; - /* 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; + context->bufferindex = GIFBUFFER; - if (context->next == context->codemask && - context->codesize < GIFBITS) { + } else { + /* Get current symbol */ - /* Expand code size */ - context->codesize++; - context->codemask = (1 << context->codesize) - 1; - } + while (context->bitcount < context->codesize) { + if (context->blocksize > 0) { + /* Read next byte */ + c = *ptr++; + bytes--; - context->next++; + context->blocksize--; - } + /* New bits are shifted in from from the left. */ + context->bitbuffer |= (INT32)c << context->bitcount; + context->bitcount += 8; - context->lastcode = thiscode; + } else { + /* New GIF block */ - } - } + /* 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; + } - /* Copy the bytes into the image */ - if (state->y >= state->ysize) { - state->errcode = IMAGING_CODEC_OVERRUN; - return -1; - } + context->blocksize = c; - /* To squeeze some extra pixels out of this loop, we test for - some common cases and handle them separately. */ + ptr++; + bytes--; + } + } - /* FIXME: should we handle the transparency index in here??? */ + /* Extract current symbol from bit buffer. */ + c = (int)context->bitbuffer & context->codemask; - 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); + /* Adjust buffer */ + context->bitbuffer >>= context->codesize; + context->bitcount -= context->codesize; + + /* 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->clear) { + if (state->state != 2) { + state->state = 1; + } + continue; + } + + if (c == context->end) { + break; + } + + i = 1; + p = &context->lastdata; + + 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; + + } else { + thiscode = c; + + if (c > context->next) { + state->errcode = IMAGING_CODEC_BROKEN; + return -1; + } + + if (c == context->next) { + /* c == next is allowed. not sure why. */ + + if (context->bufferindex <= 0) { + state->errcode = IMAGING_CODEC_BROKEN; + return -1; + } + + context->buffer[--context->bufferindex] = context->lastdata; + + c = context->lastcode; + } + + 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 the advice, 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. */ + + 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; - } - - /* No shortcut, copy pixel by pixel */ - for (c = 0; c < i; c++) { - *out++ = p[c]; - if (++state->x >= state->xsize) { - NEWLINE(state, context); - } - } + 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); + } + } } return ptr - buffer; diff --git a/src/libImaging/GifEncode.c b/src/libImaging/GifEncode.c index f211814ed03..f232454052a 100644 --- a/src/libImaging/GifEncode.c +++ b/src/libImaging/GifEncode.c @@ -5,11 +5,12 @@ * 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 + * 2020-12-12 rdg Reworked for LZW compression. * * Copyright (c) Secret Labs AB 1997-99. * Copyright (c) Fredrik Lundh 1997. @@ -21,193 +22,294 @@ #include "Gif.h" -/* codes from 0 to 255 are literals */ -#define CLEAR_CODE 256 -#define EOF_CODE 257 -#define FIRST_CODE 258 -#define LAST_CODE 511 - -enum { INIT, ENCODE, ENCODE_EOF, FLUSH, EXIT }; - -/* to make things a little less complicated, we use a simple output - queue to hold completed blocks. the following inlined function - adds a byte to the current block. it allocates a new block if - necessary. */ - -static inline int -emit(GIFENCODERSTATE *context, int byte) -{ - /* write a byte to the output buffer */ - - if (!context->block || context->block->size == 255) { - GIFENCODERBLOCK* block; - - /* no room in the current block (or no current block); - allocate a new one */ - - /* add current block to end of flush queue */ - if (context->block) { - block = context->flush; - while (block && block->next) - block = block->next; - if (block) - block->next = context->block; - else - context->flush = context->block; - } +enum { INIT, ENCODE, FINISH }; - /* get a new block */ - if (context->free) { - block = context->free; - context->free = NULL; - } else { - /* malloc check ok, small constant allocation */ - block = malloc(sizeof(GIFENCODERBLOCK)); - if (!block) - return 0; - } +/* GIF LZW encoder by Raymond Gardner. */ +/* Released here under PIL license. */ - block->size = 0; - block->next = NULL; +/* This LZW encoder conforms to the GIF LZW format specified in the original + * Compuserve GIF 87a and GIF 89a specifications (see e.g. + * https://www.w3.org/Graphics/GIF/spec-gif87.txt Appendix C and + * https://www.w3.org/Graphics/GIF/spec-gif89a.txt Appendix F). + */ - context->block = block; +/* Return values */ +#define GLZW_OK 0 +#define GLZW_NO_INPUT_AVAIL 1 +#define GLZW_NO_OUTPUT_AVAIL 2 +#define GLZW_INTERNAL_ERROR 3 - } +#define CODE_LIMIT 4096 - /* write new byte to block */ - context->block->data[context->block->size++] = byte; +/* Values of entry_state */ +enum { LZW_INITIAL, LZW_TRY_IN1, LZW_TRY_IN2, LZW_TRY_OUT1, LZW_TRY_OUT2, + LZW_FINISHED }; - return 1; -} +/* Values of control_state */ +enum { PUT_HEAD, PUT_INIT_CLEAR, PUT_CLEAR, PUT_LAST_HEAD, PUT_END }; -/* 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;\ - }\ +static void glzwe_reset(GIFENCODERSTATE *st) { + st->next_code = st->end_code + 1; + st->max_code = 2 * st->clear_code - 1; + st->code_width = st->bits + 1; + memset(st->codes, 0, sizeof(st->codes)); } -/* 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;\ - }\ - }\ +static void glzwe_init(GIFENCODERSTATE *st) { + st->clear_code = 1 << st->bits; + st->end_code = st->clear_code + 1; + glzwe_reset(st); + st->entry_state = LZW_INITIAL; + st->buf_bits_left = 8; + st->code_buffer = 0; } -int -ImagingGifEncode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) -{ - UINT8* ptr; - int this; - - GIFENCODERBLOCK* block; - GIFENCODERSTATE *context = (GIFENCODERSTATE*) state->context; - - if (!state->state) { +static int glzwe(GIFENCODERSTATE *st, const UINT8 *in_ptr, UINT8 *out_ptr, + UINT32 *in_avail, UINT32 *out_avail, + UINT32 end_of_data) { + switch (st->entry_state) { - /* place a clear code in the output buffer */ - context->bitbuffer = CLEAR_CODE; - context->bitcount = 9; - - state->count = FIRST_CODE; - - if (context->interlace) { - context->interlace = 1; - context->step = 8; - } else - context->step = 1; + case LZW_TRY_IN1: +get_first_byte: + if (!*in_avail) { + if (end_of_data) { + goto end_of_data; + } + st->entry_state = LZW_TRY_IN1; + return GLZW_NO_INPUT_AVAIL; + } + st->head = *in_ptr++; + (*in_avail)--; + + case LZW_TRY_IN2: +encode_loop: + if (!*in_avail) { + if (end_of_data) { + st->code = st->head; + st->put_state = PUT_LAST_HEAD; + goto put_code; + } + st->entry_state = LZW_TRY_IN2; + return GLZW_NO_INPUT_AVAIL; + } + st->tail = *in_ptr++; + (*in_avail)--; + + /* Knuth TAOCP vol 3 sec. 6.4 algorithm D. */ + /* Hash found experimentally to be pretty good. */ + /* This works ONLY with TABLE_SIZE a power of 2. */ + st->probe = ((st->head ^ (st->tail << 6)) * 31) & (TABLE_SIZE - 1); + while (st->codes[st->probe]) { + if ((st->codes[st->probe] & 0xFFFFF) == + ((st->head << 8) | st->tail)) { + st->head = st->codes[st->probe] >> 20; + goto encode_loop; + } else { + /* Reprobe decrement must be nonzero and relatively prime to table + * size. So, any odd positive number for power-of-2 size. */ + if ((st->probe -= ((st->tail << 2) | 1)) < 0) { + st->probe += TABLE_SIZE; + } + } + } + /* Key not found, probe is at empty slot. */ + st->code = st->head; + st->put_state = PUT_HEAD; + goto put_code; +insert_code_or_clear: /* jump here after put_code */ + if (st->next_code < CODE_LIMIT) { + st->codes[st->probe] = (st->next_code << 20) | + (st->head << 8) | st->tail; + if (st->next_code > st->max_code) { + st->max_code = st->max_code * 2 + 1; + st->code_width++; + } + st->next_code++; + } else { + st->code = st->clear_code; + st->put_state = PUT_CLEAR; + goto put_code; +reset_after_clear: /* jump here after put_code */ + glzwe_reset(st); + } + st->head = st->tail; + goto encode_loop; + + case LZW_INITIAL: + glzwe_reset(st); + st->code = st->clear_code; + st->put_state = PUT_INIT_CLEAR; +put_code: + st->code_bits_left = st->code_width; +check_buf_bits: + if (!st->buf_bits_left) { /* out buffer full */ + + case LZW_TRY_OUT1: + if (!*out_avail) { + st->entry_state = LZW_TRY_OUT1; + return GLZW_NO_OUTPUT_AVAIL; + } + *out_ptr++ = st->code_buffer; + (*out_avail)--; + st->code_buffer = 0; + st->buf_bits_left = 8; + } + /* code bits to pack */ + UINT32 n = st->buf_bits_left < st->code_bits_left + ? st->buf_bits_left : st->code_bits_left; + st->code_buffer |= + (st->code & ((1 << n) - 1)) << (8 - st->buf_bits_left); + st->code >>= n; + st->buf_bits_left -= n; + st->code_bits_left -= n; + if (st->code_bits_left) { + goto check_buf_bits; + } + switch (st->put_state) { + case PUT_INIT_CLEAR: + goto get_first_byte; + case PUT_HEAD: + goto insert_code_or_clear; + case PUT_CLEAR: + goto reset_after_clear; + case PUT_LAST_HEAD: + goto end_of_data; + case PUT_END: + goto flush_code_buffer; + default: + return GLZW_INTERNAL_ERROR; + } - context->last = -1; +end_of_data: + st->code = st->end_code; + st->put_state = PUT_END; + goto put_code; +flush_code_buffer: /* jump here after put_code */ + if (st->buf_bits_left < 8) { + + case LZW_TRY_OUT2: + if (!*out_avail) { + st->entry_state = LZW_TRY_OUT2; + return GLZW_NO_OUTPUT_AVAIL; + } + *out_ptr++ = st->code_buffer; + (*out_avail)--; + } + st->entry_state = LZW_FINISHED; + return GLZW_OK; - /* sanity check */ - if (state->xsize <= 0 || state->ysize <= 0) - state->state = ENCODE_EOF; + case LZW_FINISHED: + return GLZW_OK; + default: + return GLZW_INTERNAL_ERROR; } +} +/* -END- GIF LZW encoder. */ - ptr = buf; - - for (;;) - - switch (state->state) { - - case INIT: - case ENCODE: +int +ImagingGifEncode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) { + UINT8* ptr; + UINT8* sub_block_ptr; + UINT8* sub_block_limit; + UINT8* buf_limit; + GIFENCODERSTATE *context = (GIFENCODERSTATE*) state->context; + int r; - /* identify and store a run of pixels */ + UINT32 in_avail, in_used; + UINT32 out_avail, out_used; - if (state->x == 0 || state->x >= state->xsize) { + if (state->state == INIT) { + state->state = ENCODE; + glzwe_init(context); - if (!context->interlace && state->y >= state->ysize) { - state->state = ENCODE_EOF; - break; - } + if (context->interlace) { + context->interlace = 1; + context->step = 8; + } else { + context->step = 1; + } - if (context->flush) { - state->state = FLUSH; - break; + /* Need at least 2 bytes for data sub-block; 5 for empty image */ + if (bytes < 5) { + state->errcode = IMAGING_CODEC_CONFIG; + return 0; + } + /* sanity check */ + if (state->xsize <= 0 || state->ysize <= 0) { + /* Is this better than an error return? */ + /* This will handle any legal "LZW Minimum Code Size" */ + memset(buf, 0, 5); + in_avail = 0; + out_avail = 5; + r = glzwe(context, (const UINT8 *)"", buf + 1, &in_avail, &out_avail, 1); + if (r == GLZW_OK) { + r = 5 - out_avail; + if (r < 1 || r > 3) { + state->errcode = IMAGING_CODEC_BROKEN; + return 0; } + buf[0] = r; + state->errcode = IMAGING_CODEC_END; + return r + 2; + } else { + /* Should not be possible unless something external to this + * routine messes with our state data */ + state->errcode = IMAGING_CODEC_BROKEN; + return 0; + } + } + /* Init state->x to make if() below true the first time through. */ + state->x = state->xsize; + } - /* 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; + buf_limit = buf + bytes; + sub_block_limit = sub_block_ptr = ptr = buf; + + /* On entry, buf is output buffer, bytes is space available in buf. + * Loop here getting input until buf is full or image is all encoded. */ + for (;;) { + /* Set up sub-block ptr and limit. sub_block_ptr stays at beginning + * of sub-block until it is full. ptr will advance when any data is + * placed in buf. + */ + if (ptr >= sub_block_limit) { + if (buf_limit - ptr < 2) { /* Need at least 2 for data sub-block */ + return ptr - buf; + } + sub_block_ptr = ptr; + sub_block_limit = sub_block_ptr + + (256 < buf_limit - sub_block_ptr ? + 256 : buf_limit - sub_block_ptr); + *ptr++ = 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; - } + /* Get next row of pixels. */ + /* This if() originally tested state->x==0 for the first time through. + * This no longer works, as the loop will not advance state->x if + * glzwe() does not consume any input; this would advance the row + * spuriously. Now pre-init state->x above for first time, and avoid + * entering if() when state->state is FINISH, or it will loop + * infinitely. + */ + if (state->x >= state->xsize && state->state == ENCODE) { + if (!context->interlace && state->y >= state->ysize) { + state->state = FINISH; + continue; + } - /* step forward, according to the interlace settings */ - state->y += context->step; - while (context->interlace && state->y >= state->ysize) - switch (context->interlace) { + /* 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; + + /* 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; @@ -225,96 +327,35 @@ ImagingGifEncode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) default: /* just make sure we don't loop forever */ context->interlace = 0; - } - - } - - this = state->buffer[state->x++]; - - if (this == context->last) - context->count++; - else { - EMIT_RUN(label1); - context->last = this; - context->count = 1; - } - break; - - - 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; } - 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; + in_avail = state->xsize - state->x; /* bytes left in line */ + out_avail = sub_block_limit - ptr; /* bytes left in sub-block */ + r = glzwe(context, &state->buffer[state->x], ptr, &in_avail, + &out_avail, state->state == FINISH); + out_used = sub_block_limit - ptr - out_avail; + *sub_block_ptr += out_used; + ptr += out_used; + in_used = state->xsize - state->x - in_avail; + state->x += in_used; + + if (r == GLZW_OK) { + /* Should not be possible when end-of-data flag is false. */ + state->errcode = IMAGING_CODEC_END; + return ptr - buf; + } else if (r == GLZW_NO_INPUT_AVAIL) { + /* Used all the input line; get another line */ + continue; + } else if (r == GLZW_NO_OUTPUT_AVAIL) { + /* subblock is full */ + continue; + } else { + /* Should not be possible unless something external to this + * routine messes with our state data */ + state->errcode = IMAGING_CODEC_BROKEN; + return 0; } + } } diff --git a/src/libImaging/HexDecode.c b/src/libImaging/HexDecode.c index 8bd9bf67fa7..bd16cdbe1da 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]); - - if (a < 0 || b < 0) { - - ptr++; - bytes--; - - } else { - - ptr += 2; - bytes -= 2; + a = HEX(ptr[0]); + b = HEX(ptr[1]); - state->buffer[state->x] = (a<<4) + b; + if (a < 0 || b < 0) { + ptr++; + bytes--; - if (++state->x >= state->bytes) { + } else { + ptr += 2; + bytes -= 2; - /* Got a full line, unpack it */ - state->shuffle((UINT8*) im->image[state->y], state->buffer, - state->xsize); + state->buffer[state->x] = (a << 4) + b; - state->x = 0; + if (++state->x >= state->bytes) { + /* Got a full line, unpack it */ + state->shuffle( + (UINT8 *)im->image[state->y], state->buffer, state->xsize); - 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/Histo.c b/src/libImaging/Histo.c index 5c2824ab03e..c5a547a647b 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; - } + 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) { + /* 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]]++; + for (y = 0; y < im->ysize; y++) { + for (x = 0; x < im->xsize; x++) { + h->histogram[im->image8[y][x]]++; + } + } ImagingSectionLeave(&cookie); - } else { + } 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]++; + 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_INT32: - if (!minmax) { - ImagingHistogramDelete(h); - return ImagingError_ValueError("min/max not given"); - } - if (!im->xsize || !im->ysize) + ImagingSectionLeave(&cookie); 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]++; + case IMAGING_TYPE_INT32: + if (!minmax) { + ImagingHistogramDelete(h); + return ImagingError_ValueError("min/max not given"); } - } - 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) + 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; - 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_FLOAT32: + if (!minmax) { + ImagingHistogramDelete(h); + return ImagingError_ValueError("min/max not given"); } - } - ImagingSectionLeave(&cookie); - break; + 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 e5a2cc0f639..91ff3f322ff 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 b2d4db78594..af9996ca98c 100644 --- a/src/libImaging/ImPlatform.h +++ b/src/libImaging/ImPlatform.h @@ -9,11 +9,6 @@ #include "Python.h" -/* Workaround issue #2479 */ -#if PY_VERSION_HEX < 0x03070000 && defined(PySlice_GetIndicesEx) && !defined(PYPY_VERSION) -#undef PySlice_GetIndicesEx -#endif - /* Check that we have an ANSI compliant compiler */ #ifndef HAVE_PROTOTYPES #error Sorry, this library requires support for ANSI prototypes. @@ -30,57 +25,62 @@ #endif #endif -#ifdef _WIN32 +#if defined(_WIN32) || defined(__CYGWIN__) #define WIN32_LEAN_AND_MEAN #include +#ifdef __CYGWIN__ +#undef _WIN64 +#undef _WIN32 +#undef __WIN32__ +#undef WIN32 +#endif + #else /* 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 9032fcf0783..9b1c1024dc4 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,160 +78,178 @@ 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; /* Colour palette (for "P" images only) */ 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_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_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]) 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 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 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 ImagingHistogramDelete(ImagingHistogram histogram); - -extern void ImagingAccessInit(void); -extern ImagingAccess ImagingAccessNew(Imaging im); -extern void _ImagingAccessDelete(Imaging im, ImagingAccess access); +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 +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 void +ImagingCopyPalette(Imaging destination, Imaging source); + +extern void +ImagingHistogramDelete(ImagingHistogram histogram); + +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 */ /* ------------------- */ @@ -244,7 +259,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 @@ -253,260 +267,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 ImagingCopy2(Imaging imOut, Imaging imIn); -extern Imaging ImagingConvert2(Imaging imOut, Imaging imIn); +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[8], + 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); /* 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 ImagingChopSoftLight(Imaging imIn1, Imaging imIn2); -extern Imaging ImagingChopHardLight(Imaging imIn1, Imaging imIn2); -extern Imaging ImagingOverlay(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 width, 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 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 ImagingOutlineClose(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 +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); /* 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); - -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); +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 +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 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 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 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; @@ -522,30 +667,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 21c2688d840..0c0c1eda917 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 DIV255(a, tmp)\ - (tmp = (a) + 128, SHIFTFORDIV255(tmp)) +#define MULDIV255(a, b, tmp) (tmp = (a) * (b) + 128, SHIFTFORDIV255(tmp)) -#define BLEND(mask, in1, in2, tmp1)\ - DIV255(in1 * (255 - mask) + in2 * mask, tmp1) +#define DIV255(a, tmp) (tmp = (a) + 128, SHIFTFORDIV255(tmp)) -#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) + See: https://stackoverflow.com/a/26588074/253146 */ +#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 7ff658c3231..a876d3bb6d9 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,7 +60,6 @@ typedef struct { } JPEGDESTINATION; typedef struct { - /* CONFIGURATION */ /* Quality (0-100, -1 means default) */ @@ -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 7bb14eb2c0d..f749ecfb21c 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 d304511d1a9..8f27d87d88c 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,33 @@ 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; + /* bool indicating if unpacker supports subsampling */ + int subsampling; + 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,46 +103,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; @@ -154,45 +159,53 @@ 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]; + UINT8 *row = (UINT8 *)im->image[y0 + y] + x0; + for (x = 0; x < w; ++x) { + UINT16 pixel = j2ku_shift(offset + *data++, shift); + *row++ = pixel; + *row++ = pixel >> 8; + } + } + 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; @@ -203,56 +216,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; @@ -267,15 +284,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; @@ -287,15 +308,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); @@ -307,14 +344,17 @@ 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; int shifts[3], offsets[3], csiz[3]; + unsigned dx[3], dy[3]; const UINT8 *cdata[3]; const UINT8 *cptr = tiledata; unsigned n, x, y; @@ -324,30 +364,41 @@ j2ku_srgb_rgb(opj_image_t *in, const JPEG2KTILEINFO *tileinfo, shifts[n] = 8 - in->comps[n].prec; offsets[n] = in->comps[n].sgnd ? 1 << (in->comps[n].prec - 1) : 0; csiz[n] = (in->comps[n].prec + 7) >> 3; + dx[n] = (in->comps[n].dx); + dy[n] = (in->comps[n].dy); - 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; + cptr += csiz[n] * (w / dx[n]) * (h / dy[n]); } 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) - data[n] = &cdata[n][csiz[n] * y * w]; + for (n = 0; n < 3; ++n) { + data[n] = &cdata[n][csiz[n] * (y / dy[n]) * (w / dx[n])]; + } 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][x / dx[n]]; + break; + case 2: + word = ((const UINT16 *)data[n])[x / dx[n]]; + break; + case 4: + word = ((const UINT32 *)data[n])[x / dx[n]]; + break; } row[n] = j2ku_shift(offsets[n] + word, shifts[n]); @@ -359,14 +410,17 @@ 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; int shifts[3], offsets[3], csiz[3]; + unsigned dx[3], dy[3]; const UINT8 *cdata[3]; const UINT8 *cptr = tiledata; unsigned n, x, y; @@ -376,31 +430,42 @@ j2ku_sycc_rgb(opj_image_t *in, const JPEG2KTILEINFO *tileinfo, shifts[n] = 8 - in->comps[n].prec; offsets[n] = in->comps[n].sgnd ? 1 << (in->comps[n].prec - 1) : 0; csiz[n] = (in->comps[n].prec + 7) >> 3; + dx[n] = (in->comps[n].dx); + dy[n] = (in->comps[n].dy); - 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; + cptr += csiz[n] * (w / dx[n]) * (h / dy[n]); } for (y = 0; y < h; ++y) { const UINT8 *data[3]; UINT8 *row = (UINT8 *)im->image[y0 + y] + x0 * 4; UINT8 *row_start = row; - for (n = 0; n < 3; ++n) - data[n] = &cdata[n][csiz[n] * y * w]; + for (n = 0; n < 3; ++n) { + data[n] = &cdata[n][csiz[n] * (y / dy[n]) * (w / dx[n])]; + } 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][x / dx[n]]; + break; + case 2: + word = ((const UINT16 *)data[n])[x / dx[n]]; + break; + case 4: + word = ((const UINT32 *)data[n])[x / dx[n]]; + break; } row[n] = j2ku_shift(offsets[n] + word, shifts[n]); @@ -414,14 +479,17 @@ 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; int shifts[4], offsets[4], csiz[4]; + unsigned dx[4], dy[4]; const UINT8 *cdata[4]; const UINT8 *cptr = tiledata; unsigned n, x, y; @@ -431,30 +499,41 @@ j2ku_srgba_rgba(opj_image_t *in, const JPEG2KTILEINFO *tileinfo, shifts[n] = 8 - in->comps[n].prec; offsets[n] = in->comps[n].sgnd ? 1 << (in->comps[n].prec - 1) : 0; csiz[n] = (in->comps[n].prec + 7) >> 3; + dx[n] = (in->comps[n].dx); + dy[n] = (in->comps[n].dy); - 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; + cptr += csiz[n] * (w / dx[n]) * (h / dy[n]); } 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) - data[n] = &cdata[n][csiz[n] * y * w]; + for (n = 0; n < 4; ++n) { + data[n] = &cdata[n][csiz[n] * (y / dy[n]) * (w / dx[n])]; + } 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][x / dx[n]]; + break; + case 2: + word = ((const UINT16 *)data[n])[x / dx[n]]; + break; + case 4: + word = ((const UINT32 *)data[n])[x / dx[n]]; + break; } row[n] = j2ku_shift(offsets[n] + word, shifts[n]); @@ -465,14 +544,17 @@ 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; int shifts[4], offsets[4], csiz[4]; + unsigned dx[4], dy[4]; const UINT8 *cdata[4]; const UINT8 *cptr = tiledata; unsigned n, x, y; @@ -482,31 +564,42 @@ j2ku_sycca_rgba(opj_image_t *in, const JPEG2KTILEINFO *tileinfo, shifts[n] = 8 - in->comps[n].prec; offsets[n] = in->comps[n].sgnd ? 1 << (in->comps[n].prec - 1) : 0; csiz[n] = (in->comps[n].prec + 7) >> 3; + dx[n] = (in->comps[n].dx); + dy[n] = (in->comps[n].dy); - 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; + cptr += csiz[n] * (w / dx[n]) * (h / dy[n]); } for (y = 0; y < h; ++y) { const UINT8 *data[4]; UINT8 *row = (UINT8 *)im->image[y0 + y] + x0 * 4; UINT8 *row_start = row; - for (n = 0; n < 4; ++n) - data[n] = &cdata[n][csiz[n] * y * w]; + for (n = 0; n < 4; ++n) { + data[n] = &cdata[n][csiz[n] * (y / dy[n]) * (w / dx[n])]; + } 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][x / dx[n]]; + break; + case 2: + word = ((const UINT16 *)data[n])[x / dx[n]]; + break; + case 4: + word = ((const UINT32 *)data[n])[x / dx[n]]; + break; } row[n] = j2ku_shift(offsets[n] + word, shifts[n]); @@ -519,22 +612,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, 0, j2ku_gray_l}, + {"I;16", OPJ_CLRSPC_GRAY, 1, 0, j2ku_gray_i}, + {"I;16B", OPJ_CLRSPC_GRAY, 1, 0, j2ku_gray_i}, + {"LA", OPJ_CLRSPC_GRAY, 2, 0, j2ku_graya_la}, + {"RGB", OPJ_CLRSPC_GRAY, 1, 0, j2ku_gray_rgb}, + {"RGB", OPJ_CLRSPC_GRAY, 2, 0, j2ku_gray_rgb}, + {"RGB", OPJ_CLRSPC_SRGB, 3, 1, j2ku_srgb_rgb}, + {"RGB", OPJ_CLRSPC_SYCC, 3, 1, j2ku_sycc_rgb}, + {"RGB", OPJ_CLRSPC_SRGB, 4, 1, j2ku_srgb_rgb}, + {"RGB", OPJ_CLRSPC_SYCC, 4, 1, j2ku_sycc_rgb}, + {"RGBA", OPJ_CLRSPC_GRAY, 1, 0, j2ku_gray_rgb}, + {"RGBA", OPJ_CLRSPC_GRAY, 2, 0, j2ku_graya_la}, + {"RGBA", OPJ_CLRSPC_SRGB, 3, 1, j2ku_srgb_rgb}, + {"RGBA", OPJ_CLRSPC_SYCC, 3, 1, j2ku_sycc_rgb}, + {"RGBA", OPJ_CLRSPC_SRGB, 4, 1, j2ku_srgba_rgba}, + {"RGBA", OPJ_CLRSPC_SYCC, 4, 1, j2ku_sycca_rgba}, }; /* -------------------------------------------------------------------- */ @@ -549,9 +642,8 @@ 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; @@ -560,8 +652,8 @@ j2k_decode_entry(Imaging im, ImagingCodecState state) j2k_unpacker_t unpack = NULL; size_t buffer_size = 0, tile_bytes = 0; unsigned n, tile_height, tile_width; - int components; - + int subsampling; + int total_component_width = 0; stream = opj_stream_create(BUFFER_SIZE, OPJ_TRUE); @@ -584,10 +676,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 */ @@ -615,18 +708,23 @@ 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; } - for (n = 1; n < image->numcomps; ++n) { + /* + * Find first component with subsampling. + * + * This is a heuristic to determine the colorspace if unspecified. + */ + subsampling = -1; + for (n = 0; n < image->numcomps; ++n) { if (image->comps[n].dx != 1 || image->comps[n].dy != 1) { - state->errcode = IMAGING_CODEC_BROKEN; - state->state = J2K_STATE_FAILED; - goto quick_exit; + subsampling = n; + break; } } @@ -642,12 +740,14 @@ j2k_decode_entry(Imaging im, ImagingCodecState state) If colorspace is unspecified, we assume: - Number of components Colorspace - ----------------------------------------- - 1 gray - 2 gray (+ alpha) - 3 sRGB - 4 sRGB (+ alpha) + Number of components Subsampling Colorspace + ------------------------------------------------------- + 1 Any gray + 2 Any gray (+ alpha) + 3 -1, 0 sRGB + 3 1, 2 YCbCr + 4 -1, 0, 3 sRGB (+ alpha) + 4 1, 2 YCbCr (+ alpha) */ @@ -656,15 +756,32 @@ 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: + switch (subsampling) { + case -1: + case 0: + case 3: + color_space = OPJ_CLRSPC_SRGB; + break; + case 1: + case 2: + color_space = OPJ_CLRSPC_SYCC; + break; + } + 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 && + (j2k_unpackers[n].subsampling || (subsampling == -1)) && + strcmp(im->mode, j2k_unpackers[n].mode) == 0) { unpack = j2k_unpackers[n].unpacker; break; } @@ -683,21 +800,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) */ @@ -709,34 +830,51 @@ j2k_decode_entry(Imaging im, ImagingCodecState state) /* 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 (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; + } + + if (tile_info.nb_comps != image->numcomps) { 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 + is less than sum(comp_bytes)*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 ))) { + + /* Total component width = sum (component_width) e.g, it's + legal for an la file to have a 1 byte width for l, and 4 for + a, and then a malicious file could have a smaller tile_bytes + */ + + for (n=0; n < tile_info.nb_comps; n++) { + // see csize /acsize calcs + int csize = (image->comps[n].prec + 7) >> 3; + csize = (csize == 3) ? 4 : csize; + total_component_width += csize; + } + if ((tile_width > UINT_MAX / total_component_width) || + (tile_height > UINT_MAX / total_component_width) || + (tile_width > UINT_MAX / (tile_height * total_component_width)) || + (tile_height > UINT_MAX / (tile_width * total_component_width))) { state->errcode = IMAGING_CODEC_BROKEN; state->state = J2K_STATE_FAILED; goto quick_exit; } - tile_bytes = tile_width * tile_height * components; + tile_bytes = tile_width * tile_height * total_component_width; if (tile_bytes > tile_info.data_size) { tile_info.data_size = tile_bytes; @@ -744,22 +882,26 @@ j2k_decode_entry(Imaging im, ImagingCodecState state) if (buffer_size < tile_info.data_size) { /* malloc check ok, overflow and tile size sanity check above */ - UINT8 *new = realloc (state->buffer, tile_info.data_size); + UINT8 *new = realloc(state->buffer, tile_info.data_size); if (!new) { state->errcode = IMAGING_CODEC_MEMORY; state->state = J2K_STATE_FAILED; goto quick_exit; } + /* Undefined behavior, sometimes decode_tile_data doesn't + fill the buffer and we do things with it later, leading + to valgrind errors. */ + memset(new, 0, tile_info.data_size); state->buffer = new; 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)) { + 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; @@ -778,34 +920,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; @@ -830,7 +974,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; @@ -839,8 +983,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 aef1a4b949e..86cd7d5af26 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,46 +88,46 @@ 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) { - *ptr++ = *data++; - *ptr++ = *data++; +#ifdef WORDS_BIGENDIAN + ptr[0] = data[1]; + ptr[1] = data[0]; +#else + ptr[0] = data[0]; + ptr[1] = data[1]; +#endif + ptr += 2; + data += 2; } } } - 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 +139,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 +156,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 +182,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 +204,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 +229,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 +263,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 +304,29 @@ 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 || 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, "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 +385,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 ((size_t)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 +414,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 +441,33 @@ 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; - } - - if (context->cinema_mode != OPJ_OFF) + 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->num_resolutions) { + while (tile_width < (1 << (params.numresolution - 1U)) || tile_height < (1 << (params.numresolution - 1U))) { + params.numresolution -= 1; + } + } + + 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 +497,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 +527,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 +543,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 +557,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 +575,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 +617,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 39d96de531b..55d10a81aec 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; - - /* 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; +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 */ + + 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); - - } while (ok == JPEG_HEADER_TABLES_ONLY); + /* 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); - 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; - } - - 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 */ - - case 2: + } +#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; + } - /* 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; + 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 3: + case 2: - /* 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: + } - /* Finish decompression */ - if (!jpeg_finish_decompress(&context->cinfo)) { - /* FIXME: add strictness mode test */ - if (state->y < state->ysize) + 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) { + 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; - - /* Clean up */ - jpeg_destroy_decompress(&context->cinfo); - return -1; +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; } #endif - diff --git a/src/libImaging/JpegEncode.c b/src/libImaging/JpegEncode.c index 9d838279162..a44debcafe8 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: + + 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; + } + + /* 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); + } + + /* 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 */ + + 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 { + break; + } + } else { + state->state++; + } - case 1: + 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 */ - context->cinfo.image_width = state->xsize; - context->cinfo.image_height = state->ysize; + case 5: - 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); - - /* 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++) { - // 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 != -1) { - 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.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 + /* Finish compression */ + if (context->destination.pub.free_in_buffer < 100) { break; - } else - state->state++; - - case 4: - if (1024 > context->destination.pub.free_in_buffer){ + } + 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 5cc7795a4be..137ed242a41 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; - - 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; - } - } + 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 (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; - - 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(); + 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 (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 5237d07328f..757cbc3fb86 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 4dedcb24517..70b96c39772 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 b3d9425fb65..91ee91083cc 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; - -#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 (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]; \ + } \ + } + + if (im->image8) { + OFFSET(image8) + } else { + OFFSET(image32) + } return imOut; } diff --git a/src/libImaging/Pack.c b/src/libImaging/Pack.c index a239464d4a0..0c7c0497efe 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,318 @@ 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}, +#ifdef WORDS_BIGENDIAN + {"I;16", "I;16B", 16, packI16N_I16}, +#else + {"I;16", "I;16B", 16, packI16N_I16B}, +#endif + {"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 ef54f3c9a4f..7dd432b91c2 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 (ptr[0] & 0x80) { - - if (ptr[0] == 0x80) { - /* Nop */ - ptr++; bytes--; - continue; - } - - /* Run */ - if (bytes < 2) - return ptr - buf; - - 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]; - } - - ptr += 2; bytes -= 2; - - } else { - - /* Literal */ - n = ptr[0]+2; - - if (bytes < n) - return ptr - buf; - - 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; - } - } - + if (bytes < 1) { + return ptr - buf; + } + + if (ptr[0] & 0x80) { + if (ptr[0] == 0x80) { + /* Nop */ + ptr++; + bytes--; + continue; + } + + /* Run */ + if (bytes < 2) { + return ptr - buf; + } + + 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]; + } + + ptr += 2; + bytes -= 2; + + } else { + /* Literal */ + n = ptr[0] + 2; + + if (bytes < n) { + return ptr - buf; + } + + 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; + } + } } } diff --git a/src/libImaging/Palette.c b/src/libImaging/Palette.c index 7aee6e8eef4..43bea61e327 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 0bda25739ae..be26cd260b9 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); + 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++) + 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,36 +398,58 @@ 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; + if (strncmp(imOut->mode, "I;16", 4) == 0) { + out += dx; + } + UINT8 *mask = imMask->image8[y + sy] + sx; for (x = 0; x < xsize; x++) { *out = BLEND(*mask, *out, ink[0], tmp1); + if (strncmp(imOut->mode, "I;16", 4) == 0) { + out++; + *out = BLEND(*mask, *out, ink[0], tmp1); + } out++, mask++; } } } 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 != 0) { + 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 +457,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 +485,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 +502,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 +530,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 +547,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 +571,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 +601,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 8ff264edfc8..f13803cb688 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; - - /* 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); - - 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; - } - - state->shuffle((UINT8*) im->image[state->y], - state->buffer, state->xsize); - - if (++state->y >= state->ysize) - return -1; - - ptr += chunk; - bytes -= chunk; - + /* 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; + } + + state->shuffle((UINT8 *)im->image[state->y], state->buffer, state->xsize); + + 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; + } + + state->shuffle((UINT8 *)im->image[state->y], state->buffer, state->xsize); + + if (++state->y >= state->ysize) { + return -1; + } + + ptr += chunk; + bytes -= chunk; } } diff --git a/src/libImaging/PcxDecode.c b/src/libImaging/PcxDecode.c index e5a38f4bec1..c95ffc8692c 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,14 +13,12 @@ * 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 ((state->xsize * state->bits + 7) / 8 > state->bytes) { state->errcode = IMAGING_CODEC_OVERRUN; @@ -30,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; - - n = ptr[0] & 0x3F; - - while (n > 0) { - if (state->x >= state->bytes) { - state->errcode = IMAGING_CODEC_OVERRUN; - break; - } - state->buffer[state->x++] = ptr[1]; - n--; - } + if ((*ptr & 0xC0) == 0xC0) { + /* Run */ + if (bytes < 2) { + return ptr - buf; + } - ptr += 2; bytes -= 2; + n = ptr[0] & 0x3F; - } else { + while (n > 0) { + if (state->x >= state->bytes) { + state->errcode = IMAGING_CODEC_OVERRUN; + break; + } + state->buffer[state->x++] = ptr[1]; + n--; + } - /* 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 163b09b139a..549614bfd39 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: - - /* 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->y += 1; - - state->count = 1; - state->LAST = state->buffer[0]; - - state->x = 1; - - state->state = ENCODE; - /* fall through */ - - 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; + case FETCH: - state->count = 0; - } - - this = state->buffer[state->x]; - - if (this == state->LAST) { - /* extend the current run */ - state->x += 1; - state->count += 1; + /* get a line of data */ + if (state->y >= state->ysize) { + state->errcode = IMAGING_CODEC_END; + return ptr - buf; + } - } else { - /* start a new run */ - if (state->count == 1 && (state->LAST < 0xc0)) { - if (bytes < 1) { + state->shuffle( + state->buffer, + (UINT8 *)im->image[state->y + state->yoff] + + state->xoff * im->pixelsize, + state->xsize); + + state->y += 1; + + state->count = 1; + state->LAST = state->buffer[0]; + + state->x = 1; + + state->state = ENCODE; + /* fall through */ + + 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] = 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->LAST = this; + state->count = 1; - state->x += 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 9b4bf6b754b..8883578cbff 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; + 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; - } - /* FALL THROUGH */ - default: - ImagingDelete(imOut); - return (Imaging) ImagingError_ValueError("internal error"); + 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 b94dc6e1d09..1c6b9d6a2d7 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,686 @@ 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; - - PIXEL_SCALE(&pixel,&q,d->scale); - - /* 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; - } +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); + + /* 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; + } } 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; - - 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; +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 (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; - - PixelList *l,*r,*c,*n; - int i; - int nRight,nLeft; - int splitColourVal; +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; #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; - - 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; +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; #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; - - 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]); - } - } - - 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", + { + 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", _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; - - 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)) { +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; + + 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; } +typedef struct { + uint32_t *distance; + uint32_t index; +} DistanceWithIndex; + 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:((Adistance == *B->distance) { + return A->index < B->index ? -1 : +1; + } + return *A->distance < *B->distance ? -1 : +1; } static int -resort_distance_tables(uint32_t *avgDist, - uint32_t **avgDistSortKey, - Pixel *p, - uint32_t nEntries) -{ - uint32_t i,j,k; - uint32_t **skRow; - uint32_t *skElt; - - for (i=0;i*(skRow[k]));k--) { - skRow[k]=skRow[k-1]; - } - if (k!=j) skRow[k]=skElt; - } - } - return 1; +resort_distance_tables( + uint32_t *avgDist, uint32_t **avgDistSortKey, Pixel *p, uint32_t nEntries) { + uint32_t i, j, k; + uint32_t **skRow; + uint32_t *skElt; + + 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; - - 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) { + uint32_t *qp) { + uint32_t i; + + 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; - } - } - - /* 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; } +k_means( + Pixel *pixelData, + uint32_t nPixels, + Pixel *paletteData, + uint32_t nPaletteEntries, + uint32_t *qp, + int threshold) { + uint32_t *avg[3]; + uint32_t *count; + uint32_t i; + uint32_t *avgDist; + uint32_t **avgDistSortKey; + int changes; + int built = 0; + + 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; + } + + /* 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); + if (!build_distance_tables( + avgDist, avgDistSortKey, paletteData, nPaletteEntries)) { + goto error_3; + } + 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; - - uint32_t *avgDist; - uint32_t **avgDistSortKey; - Pixel *p; +static 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; + + 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; - - 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; - } - - /* 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; } - - 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); - - *paletteLength=nQuantPixels; - *palette=p; - *quantizedPixels=qp; - free(avgDistSortKey); - free(avgDist); - return 1; +static 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; + + 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 < nPixels; i++) { + hashtable_insert(h, pixelData[i], 0xffffffff); + mean[0] += pixelData[i].c.r; + mean[1] += pixelData[i].c.g; + mean[2] += pixelData[i].c.b; + } + data.new.c.r = (int)(.5 + (double)mean[0] / (double)nPixels); + data.new.c.g = (int)(.5 + (double)mean[1] / (double)nPixels); + data.new.c.b = (int)(.5 + (double)mean[2] / (double)nPixels); + for (i = 0; i < nQuantPixels; i++) { + data.furthestDistance = 0; + data.secondPixel = (i == 1) ? 1 : 0; + hashtable_foreach_update(h, compute_distances, &data); + p[i].v = data.furthest.v; + data.new.v = data.furthest.v; + } + hashtable_free(h); + + /* malloc check ok, using calloc */ + qp = calloc(nPixels, sizeof(uint32_t)); + if (!qp) { + goto error_1; + } + + 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 */ + avgDistSortKey = calloc(nQuantPixels * nQuantPixels, sizeof(uint32_t *)); + if (!avgDistSortKey) { + goto error_3; + } + + 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); + } + + *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 +1689,112 @@ 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++) + withAlpha = !strcmp(im->mode, "RGBA"); + int transparency = 0; + unsigned char r, g, b; + for (i = y = 0; y < im->ysize; y++) { + for (x = 0; x < im->xsize; x++, i++) { p[i].v = im->image32[y][x]; + if (withAlpha && p[i].c.a == 0) { + if (transparency == 0) { + transparency = 1; + r = p[i].c.r; + g = p[i].c.g; + b = p[i].c.b; + } else { + /* Set all subsequent transparent pixels + to the same colour as the first */ + p[i].c.r = r; + p[i].c.g = g; + p[i].c.b = b; + } + } + } + } } 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: + 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 - ); + 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 +1804,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 +1841,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 3fcbf3c0263..ea75d6037f9 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); - 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; - } + 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; +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; - } + 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; +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; + 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; } -int hashtable_insert(HashTable *h,HashKey_t key,HashVal_t val) { - return _hashtable_insert(h,key,val,1,0); +int +hashtable_insert(HashTable *h, HashKey_t key, HashVal_t val) { + return _hashtable_insert(h, key, val, 1, 0); } -void hashtable_foreach_update(HashTable *h,IteratorUpdateFunc 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_foreach(HashTable *h,IteratorFunc i,void *u) { - HashNode *n; - uint32_t x; +void +hashtable_foreach(HashTable *h, IteratorFunc 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_free(HashTable *h) { + HashNode *n, *nn; + uint32_t i; - 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 (i = 0; i < h->length; i++) { + for (n = h->table[i]; n; n = nn) { + nn = n->next; + free(n); + } + } + free(h->table); + } + free(h); } -void hashtable_rehash_compute(HashTable *h,CollisionFunc cf) { - _hashtable_rehash(h,cf,h->length); +void +hashtable_rehash_compute(HashTable *h, CollisionFunc cf) { + _hashtable_rehash(h, cf, h->length); } -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; +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; + 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; } -uint32_t hashtable_get_count(const HashTable *h) { - return h->count; +uint32_t +hashtable_get_count(const HashTable *h) { + return h->count; } -void *hashtable_get_user_data(const HashTable *h) { - return h->userData; +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; +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 9874114e581..fc1a9900376 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 498d44b1dfb..6fb52d8902e 100644 --- a/src/libImaging/QuantHeap.c +++ b/src/libImaging/QuantHeap.c @@ -25,10 +25,10 @@ #include "QuantHeap.h" struct _Heap { - void **heap; - unsigned int heapsize; - unsigned 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,unsigned 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) { - 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; +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) { - unsigned 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 77bf0d9d550..c5286dff2ba 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 83d9875446b..5e79bce358a 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; - unsigned 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; - - /* 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); - - /* 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 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; - - /* 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; + ColorCube cube; + + /* 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); + + /* 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 << cube->rBits; + cube->gWidth = 1 << cube->gBits; + cube->bWidth = 1 << cube->bBits; + cube->aWidth = 1 << cube->aBits; + + /* 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; + + /* 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; } 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 unsigned long count_used_color_buckets(const ColorCube cube) { - unsigned long usedBuckets = 0; - unsigned 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); - - qsort(buckets, cube->size, sizeof(struct _ColorBucket), - (int (*)(void const *, void const *))&compare_bucket_count); - - return buckets; + 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); + + 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, - 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; - - 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] - ); +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; + } + + 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 < 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; - - 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; - } + ColorBucket minuend, subtrahend; + long i; + Pixel p; + for (i = 0; i < nBuckets; i++) { + subtrahend = &buckets[i]; + + // 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; + } } 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= offset; i--) { + avg_color_from_color_bucket(&palette[i], &p); + set_lookup_value(cube, &p, i); + } } ColorBucket -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; +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; - - /* malloc check ok, calloc for overflow */ - paletteArray = calloc(paletteLength, sizeof(Pixel)); - if (!paletteArray) return NULL; - - for (i=0; i 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 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; - - /* 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; - - /* 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); - } - - /* 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; - - /* 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; - - /* 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); - - /* convert palette buckets to RGB pixel palette */ - *palette = create_palette_array(paletteBuckets, nQuantPixels); - if (!(*palette)) goto error; - - *quantizedPixels = qp; - *paletteLength = nQuantPixels; - - free_color_cube(coarseCube); - free_color_cube(fineCube); - free_color_cube(lookupCube); - free_color_cube(coarseLookupCube); - free(paletteBuckets); - return 1; +int +quantize_octree( + Pixel *pixelData, + uint32_t nPixels, + uint32_t nQuantPixels, + Pixel **palette, + uint32_t *paletteLength, + uint32_t **quantizedPixels, + int withAlpha) { + ColorCube fineCube = NULL; + ColorCube coarseCube = NULL; + ColorCube lookupCube = NULL; + ColorCube coarseLookupCube = NULL; + ColorBucket paletteBucketsCoarse = NULL; + ColorBucket paletteBucketsFine = NULL; + ColorBucket paletteBuckets = NULL; + uint32_t *qp = NULL; + long i; + unsigned long nCoarseColors, nFineColors, nAlreadySubtracted; + const unsigned int *cubeBits; + + if (withAlpha) { + cubeBits = CUBE_LEVELS_ALPHA; + } else { + cubeBits = CUBE_LEVELS; + } + + /* + 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 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 < nPixels; i++) { + add_color_to_color_cube(fineCube, &pixelData[i]); + } + + /* create coarse cube */ + coarseCube = + copy_color_cube(fineCube, cubeBits[4], cubeBits[5], cubeBits[6], cubeBits[7]); + if (!coarseCube) { + goto error; + } + nCoarseColors = count_used_color_buckets(coarseCube); + + /* limit to nQuantPixels */ + if (nCoarseColors > nQuantPixels) { + nCoarseColors = nQuantPixels; + } + + /* 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; + } + + /* 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); + } + + /* 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; + } + + /* 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; + } + + /* 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); + + /* convert palette buckets to RGB pixel palette */ + *palette = create_palette_array(paletteBuckets, nQuantPixels); + if (!(*palette)) { + goto error; + } + + *quantizedPixels = qp; + *paletteLength = nQuantPixels; + + 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 968644eda02..e1c50407402 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 a9a547540bf..7a36300e408 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 d539a7a0d24..d65e42590ca 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 411485498ed..986b70806dc 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 0164861bb07..73a6baecbb2 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 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 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]; \ + } -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) - - if (im->image8) +#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) { 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 4d28fa54665..ab718837f4a 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 c069bdb8864..24abe48041f 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 */ - - /* 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; - + /* 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; + } + + /* 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) { - - /* Skip padding between lines */ + if (bytes < rawstate->skip) { + return ptr - buf; + } - if (bytes < rawstate->skip) - return ptr - buf; + ptr += rawstate->skip; + bytes -= rawstate->skip; - ptr += rawstate->skip; - bytes -= rawstate->skip; + state->state = LINE; + } - state->state = LINE; + if (bytes < state->bytes) { + return ptr - buf; + } - } + /* Unpack data */ + state->shuffle( + (UINT8 *)im->image[state->y + state->yoff] + state->xoff * im->pixelsize, + ptr, + state->xsize); - if (bytes < state->bytes) - return ptr - buf; + ptr += state->bytes; + bytes -= state->bytes; - /* Unpack data */ - state->shuffle((UINT8*) im->image[state->y + state->yoff] + - state->xoff * im->pixelsize, ptr, state->xsize); + state->y += state->ystep; - 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 a3b74b8cffc..50de8d98275 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 */ - - 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; - - /* 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; - + /* 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; + + /* 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 */ + + 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 d6ef92f5b41..60928d2bc36 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]; - } - 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]; + 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 += line[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); } - 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]; - } - 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]; + 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 += line[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; } - 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; - 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]; + 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); } - 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; + 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; - for (yy = box[1] + y*yscale; yy < box[1] + box[3]; yy++) { + 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 = xx_from; xx < xx_from + xscale; 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[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]; + 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; + 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; } } - IMAGING_PIXEL_I(imOut, x, y) = ROUND_UP(ss * multiplier); - } - 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; - 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]; + 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; } - 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; + if (box[2] % xscale && box[3] % yscale) { + double multiplier = 1.0 / ((box[2] % xscale) * (box[3] % yscale)); double ss = 0; - for (yy = box[1] + y*yscale; yy < box[1] + box[3]; yy++) { + 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 = xx_from; xx < xx_from + xscale; 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[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); - } 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); + 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 { - ImagingReduce5x5(imOut, imIn, box); + ImagingReduceNxN(imOut, imIn, box, xscale, yscale); } - } 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 0dc08611da0..cf79d8a4e4d 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 8015d6661e4..39dd6882567 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 3f9400a5bf9..4eef44ba510 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,105 +20,182 @@ #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) -{ +/* + SgiRleDecoding is done in a single channel row oriented set of RLE chunks. + + * The file is arranged as + - SGI Header + - Rle Offset Table + - Rle Length Table + - Scanline Data + + * Each RLE atom is c->bpc bytes wide (1 or 2) + + * Each RLE Chunk is [specifier atom] [ 1 or n data atoms ] + + * Copy Atoms are a byte with the high bit set, and the low 7 are + the number of bytes to copy from the source to the + destination. e.g. + + CBBBBBBBB or 0CHLHLHLHLHLHL (B=byte, H/L = Hi low bytes) + + * Run atoms do not have the high bit set, and the low 7 bits are + the number of copies of the next atom to copy to the + destination. e.g.: + + RB -> BBBBB or RHL -> HLHLHLHLHL + + The upshot of this is, there is no way to determine the required + length of the input buffer from reloffset and rlelength without + going through the data at that scan line. + + Furthermore, there's no requirement that individual scan lines + pointed to from the rleoffset table are in any sort of order or + used only once, or even disjoint. There's also no requirement that + all of the data in the scan line area of the image file be used + + */ +static int +expandrow(UINT8 *dest, UINT8 *src, int n, int z, int xsize, UINT8 *end_of_buffer) { + /* + * n here is the number of rlechunks + * z is the number of channels, for calculating the interleave + * offset to go to RGBA style pixels + * xsize is the row width + * end_of_buffer is the address of the end of the input buffer + */ + UINT8 pixel, count; int x = 0; - for (;n > 0; n--) - { + for (; n > 0; n--) { + if (src > end_of_buffer) { + return -1; + } 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 (x + count > xsize) { return -1; } x += count; if (pixel & RLE_COPY_FLAG) { - while(count--) { + if (src + count > end_of_buffer) { + return -1; + } + while (count--) { *dest = *src++; dest += z; } - } - else { + } else { + if (src > end_of_buffer) { + return -1; + } 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 *end_of_buffer) { UINT8 pixel, count; - int x = 0; - for (;n > 0; n--) - { + for (; n > 0; n--) { + if (src + 1 > end_of_buffer) { + return -1; + } 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 (x + count > xsize) { return -1; } x += count; if (pixel & RLE_COPY_FLAG) { - while(count--) { + if (src + 2 * count > end_of_buffer) { + return -1; + } + while (count--) { memcpy(dest, src, 2); src += 2; dest += z * 2; } - } - else { + } else { + if (src + 2 > end_of_buffer) { + return -1; + } 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); + if (_imaging_read_pyFd(state->fd, (char *)ptr, c->bufsize) != c->bufsize) { + state->errcode = IMAGING_CODEC_UNKNOWN; + return -1; + } /* decoder initialization */ @@ -130,80 +207,82 @@ 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) { + // Check for underflow of rleoffset-SGI_HEADER_SIZE + if (c->rleoffset < SGI_HEADER_SIZE) { state->errcode = IMAGING_CODEC_OVERRUN; - return -1; + goto sgi_finish_decode; } + c->rleoffset -= SGI_HEADER_SIZE; + /* 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, + &ptr[c->bufsize-1]); + } else { + status = expandrow2( + &state->buffer[c->channo * 2], + &ptr[c->rleoffset], + c->rlelength, + im->bands, + im->xsize, + &ptr[c->bufsize-1]); } if (status == -1) { state->errcode = IMAGING_CODEC_OVERRUN; - return -1; + goto sgi_finish_decode; } else if (status == 1) { goto sgi_finish_decode; } - state->count += c->rlelength; } /* 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; + return 0; } diff --git a/src/libImaging/Storage.c b/src/libImaging/Storage.c index ab476939ac0..76750aaf7f2 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 */ @@ -115,8 +112,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; @@ -135,7 +133,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) { @@ -143,7 +141,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) { @@ -151,7 +149,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) { @@ -159,7 +157,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) { @@ -204,7 +202,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 */ @@ -212,21 +210,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; @@ -235,31 +235,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. */ @@ -267,17 +268,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); @@ -287,14 +291,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; } } @@ -304,8 +308,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); @@ -314,8 +317,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) { @@ -323,16 +325,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; @@ -352,9 +354,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; @@ -368,15 +369,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); @@ -384,8 +383,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}; @@ -393,22 +391,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 */ @@ -423,9 +422,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 */ @@ -449,41 +448,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++) { @@ -501,17 +497,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; @@ -529,29 +525,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; @@ -562,33 +556,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 e627c2c9a28..9d8e1292a47 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 d1971e54623..273ecdffd6b 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; - - 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; } 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) { - - /* Run (1 + pixelsize bytes) */ - - if (bytes < 1 + depth) - break; + if (bytes < 1 + depth) { + break; + } - n = depth * ((ptr[0] & 0x7f) + 1); + n = depth * ((ptr[0] & 0x7f) + 1); - 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; + } - 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; - - } else { - - /* Literal (1+n+1 bytes block) */ - n = depth * (ptr[0] + 1); + bytes -= 1 + depth; - if (bytes < 1 + n) - break; + } else { + /* Literal (1+n+1 bytes block) */ + n = depth * (ptr[0] + 1); - if (state->x + n > state->bytes) { - state->errcode = IMAGING_CODEC_OVERRUN; - return -1; - } - - memcpy(state->buffer + state->x, ptr + 1, n); + if (bytes < 1 + n) { + break; + } - ptr += 1 + n; - bytes -= 1 + n; + if (state->x + n > state->bytes) { + state->errcode = IMAGING_CODEC_OVERRUN; + return -1; + } - } + memcpy(state->buffer + state->x, ptr + 1, n); - state->x += n; + ptr += 1 + n; + bytes -= 1 + n; + } - if (state->x >= state->bytes) { + state->x += n; - /* Got a full line, unpack it */ - state->shuffle((UINT8*) im->image[state->y + state->yoff] + - state->xoff * im->pixelsize, state->buffer, - state->xsize); + 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 2fb831e6b14..aa7e7b96d81 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 532db1f685f..38deb53607e 100644 --- a/src/libImaging/TiffDecode.c +++ b/src/libImaging/TiffDecode.c @@ -20,33 +20,57 @@ #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; TRACE(("_tiffReadProc: %d \n", (int)size)); dump_state(state); + if (state->loc > state->eof) { + TIFFError("_tiffReadProc", "Invalid Read at loc %" PRIu64 ", eof: %" PRIu64, state->loc, state->eof); + return 0; + } to_read = min(size, min(state->size, (tsize_t)state->eof) - (tsize_t)state->loc); TRACE(("to_read: %d\n", (int)to_read)); _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 +78,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 +110,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 +141,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 +151,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 +164,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_t 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,319 +212,514 @@ int ImagingLibTiffInit(ImagingCodecState state, int fp, uint32 offset) { return 1; } +int +_pickUnpackers(Imaging im, ImagingCodecState state, TIFF *tiff, uint16_t planarconfig, ImagingShuffler *unpackers) { + // if number of bands is 1, there is no difference with contig case + if (planarconfig == PLANARCONFIG_SEPARATE && im->bands > 1) { + uint16_t bits_per_sample = 8; -int ReadTile(TIFF* tiff, UINT32 col, UINT32 row, UINT32* buffer) { - uint16 photometric = 0; + TIFFGetFieldDefaulted(tiff, TIFFTAG_BITSPERSAMPLE, &bits_per_sample); + if (bits_per_sample != 8 && bits_per_sample != 16) { + TRACE(("Invalid value for bits per sample: %d\n", bits_per_sample)); + state->errcode = IMAGING_CODEC_BROKEN; + return -1; + } - TIFFGetField(tiff, TIFFTAG_PHOTOMETRIC, &photometric); + // We'll pick appropriate set of unpackers depending on planar_configuration + // It does not matter if data is RGB(A), CMYK or LUV really, + // we just copy it plane by plane + unpackers[0] = ImagingFindUnpacker("RGBA", bits_per_sample == 16 ? "R;16N" : "R", NULL); + unpackers[1] = ImagingFindUnpacker("RGBA", bits_per_sample == 16 ? "G;16N" : "G", NULL); + unpackers[2] = ImagingFindUnpacker("RGBA", bits_per_sample == 16 ? "B;16N" : "B", NULL); + unpackers[3] = ImagingFindUnpacker("RGBA", bits_per_sample == 16 ? "A;16N" : "A", NULL); - // 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; + return im->bands; + } else { + unpackers[0] = state->shuffle; - TIFFGetField(tiff, TIFFTAG_TILEWIDTH, &tile_width); - TIFFGetField(tiff, TIFFTAG_TILELENGTH, &tile_height); + return 1; + } +} - swap_line_size = tile_width * sizeof(UINT32); - if (tile_width != swap_line_size / sizeof(UINT32)) { - return -1; - } +int +_decodeAsRGBA(Imaging im, ImagingCodecState state, TIFF *tiff) { + // To avoid dealing with YCbCr subsampling and other complications, let libtiff handle it + // 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. + + INT32 current_row; + UINT8 *new_data; + UINT32 rows_per_block, row_byte_size, rows_to_read; + int ret; + TIFFRGBAImage img; + char emsg[1024] = ""; + + // Since using TIFFRGBAImage* functions, we can read whole tiff into rastrr in one call + // Let's select smaller block size. Multiplying image width by (tile length OR rows per strip) + // gives us manageable block size in pixels + if (TIFFIsTiled(tiff)) { + ret = TIFFGetFieldDefaulted(tiff, TIFFTAG_TILELENGTH, &rows_per_block); + } + else { + ret = TIFFGetFieldDefaulted(tiff, TIFFTAG_ROWSPERSTRIP, &rows_per_block); + } - /* Read the tile into an RGBA array */ - if (!TIFFReadRGBATile(tiff, col, row, buffer)) { - return -1; - } + if (ret != 1 || rows_per_block==(UINT32)(-1)) { + rows_per_block = state->ysize; + } - 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); - } + TRACE(("RowsPerBlock: %u \n", rows_per_block)); - free(swap_line); + 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; + } - return 0; + img.req_orientation = ORIENTATION_TOPLEFT; + img.col_offset = 0; + + /* overflow check for row byte size */ + if (INT_MAX / 4 < img.width) { + state->errcode = IMAGING_CODEC_MEMORY; + goto decodergba_err; } - if (TIFFReadTile(tiff, (tdata_t)buffer, col, row, 0, 0) == -1) { - TRACE(("Decode Error, Tile at %dx%d\n", col, row)); - return -1; + // TiffRGBAImages are 32bits/pixel. + row_byte_size = img.width * 4; + + /* overflow check for realloc */ + if (INT_MAX / row_byte_size < rows_per_block) { + state->errcode = IMAGING_CODEC_MEMORY; + goto decodergba_err; } - TRACE(("Successfully read tile at %dx%d; \n\n", col, row)); + state->bytes = rows_per_block * row_byte_size; - return 0; -} + TRACE(("BlockSize: %d \n", state->bytes)); -int ReadStrip(TIFF* tiff, UINT32 row, UINT32* buffer) { - uint16 photometric = 0; // init to not PHOTOMETRIC_YCBCR - TIFFGetField(tiff, TIFFTAG_PHOTOMETRIC, &photometric); + /* realloc to fit whole strip */ + /* malloc check above */ + new_data = realloc(state->buffer, state->bytes); + if (!new_data) { + state->errcode = IMAGING_CODEC_MEMORY; + goto decodergba_err; + } - // 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; + state->buffer = new_data; + for (; state->y < state->ysize; state->y += rows_per_block) { + img.row_offset = state->y; + rows_to_read = min(rows_per_block, img.height - state->y); - 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 (!TIFFRGBAImageGet(&img, (UINT32 *)state->buffer, img.width, rows_to_read)) { + TRACE(("Decode Error, y: %d\n", state->y)); + state->errcode = IMAGING_CODEC_BROKEN; + goto decodergba_err; } - 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; +#if WORDS_BIGENDIAN + TIFFSwabArrayOfLong((UINT32 *)state->buffer, img.width * rows_to_read); +#endif - rows_to_read = min(rows_per_strip, img.height - row); + TRACE(("Decoded strip for row %d \n", state->y)); - TRACE(("rows to read: %d\n", rows_to_read)); - ok = TIFFRGBAImageGet(&img, buffer, img.width, rows_to_read); + // iterate over each row in the strip and stuff data into image + for (current_row = 0; + current_row < min((INT32)rows_per_block, state->ysize - state->y); + current_row++) { + TRACE(("Writing data into line %d ; \n", state->y + current_row)); - TIFFRGBAImageEnd(&img); - } else { - ok = 0; - } + // UINT8 * bbb = state->buffer + current_row * (state->bytes / + // rows_per_block); TRACE(("chars: %x %x %x %x\n", ((UINT8 *)bbb)[0], + // ((UINT8 *)bbb)[1], ((UINT8 *)bbb)[2], ((UINT8 *)bbb)[3])); - if (ok == 0) { - TRACE(("Decode Error, row %d; msg: %s\n", row, emsg)); - return -1; + state->shuffle( + (UINT8 *)im->image[state->y + state->yoff + current_row] + + state->xoff * im->pixelsize, + state->buffer + current_row * row_byte_size, + state->xsize); } - - return 0; } - if (TIFFReadEncodedStrip(tiff, TIFFComputeStrip(tiff, row, 0), (tdata_t)buffer, -1) == -1) { - TRACE(("Decode Error, strip %d\n", TIFFComputeStrip(tiff, row, 0))); +decodergba_err: + TIFFRGBAImageEnd(&img); + if (state->errcode != 0) { return -1; } - return 0; } -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; - - /* 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: 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)); +int +_decodeTile(Imaging im, ImagingCodecState state, TIFF *tiff, int planes, ImagingShuffler *unpackers) { + INT32 x, y, tile_y, current_tile_length, current_tile_width; + UINT32 tile_width, tile_length; + tsize_t tile_bytes_size, row_byte_size; + UINT8 *new_data; - dump_state(clientstate); - clientstate->size = bytes; - clientstate->eof = clientstate->size; - clientstate->loc = 0; - clientstate->data = (tdata_t)buffer; - clientstate->flrealloc = 0; - dump_state(clientstate); - - TIFFSetWarningHandler(NULL); - TIFFSetWarningHandlerExt(NULL); + tile_bytes_size = TIFFTileSize(tiff); - 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); - } else { - TRACE(("Opening from string\n")); - tiff = TIFFClientOpen(filename, mode, - (thandle_t) clientstate, - _tiffReadProc, _tiffWriteProc, - _tiffSeekProc, _tiffCloseProc, _tiffSizeProc, - _tiffMapProc, _tiffUnmapProc); + if (tile_bytes_size == 0) { + TRACE(("Decode Error, Can not calculate TileSize\n")); + state->errcode = IMAGING_CODEC_BROKEN; + return -1; } - if (!tiff){ - TRACE(("Error, didn't get the tiff\n")); + row_byte_size = TIFFTileRowSize(tiff); + + if (row_byte_size == 0 || row_byte_size > tile_bytes_size) { + TRACE(("Decode Error, Can not calculate TileRowSize\n")); state->errcode = IMAGING_CODEC_BROKEN; return -1; } - if (clientstate->ifd){ - int rv; - uint32 ifdoffset = clientstate->ifd; - TRACE(("reading tiff ifd %u\n", ifdoffset)); - rv = TIFFSetSubDirectory(tiff, ifdoffset); - if (!rv){ - TRACE(("error in TIFFSetSubDirectory")); - return -1; - } + /* overflow check for realloc */ + if (tile_bytes_size > INT_MAX - 1) { + state->errcode = IMAGING_CODEC_MEMORY; + return -1; } - if (TIFFIsTiled(tiff)) { - UINT32 x, y, tile_y, row_byte_size; - UINT32 tile_width, tile_length, current_tile_width; - UINT8 *new_data; - - TIFFGetField(tiff, TIFFTAG_TILEWIDTH, &tile_width); - TIFFGetField(tiff, TIFFTAG_TILELENGTH, &tile_length); + 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 realloc */ - if (INT_MAX / row_byte_size < tile_length) { - state->errcode = IMAGING_CODEC_MEMORY; - TIFFClose(tiff); - return -1; - } - - state->bytes = row_byte_size * tile_length; + if (tile_width > INT_MAX || tile_length > INT_MAX) { + // state->x and state->y are ints + state->errcode = IMAGING_CODEC_MEMORY; + return -1; + } - 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; - TIFFClose(tiff); - return -1; - } + if (tile_bytes_size > ((tile_length * state->bits / planes + 7) / 8) * tile_width) { + // If the tile size as expected by LibTiff isn't what we're expecting, abort. + // man: TIFFTileSize returns the equivalent size for a tile of data as it would be returned in a + // call to TIFFReadTile ... + state->errcode = IMAGING_CODEC_BROKEN; + return -1; + } - /* realloc to fit whole tile */ - /* malloc check above */ - new_data = realloc (state->buffer, state->bytes); - if (!new_data) { - state->errcode = IMAGING_CODEC_MEMORY; - TIFFClose(tiff); - return -1; - } + state->bytes = tile_bytes_size; - state->buffer = new_data; + TRACE(("TIFFTileSize: %d\n", state->bytes)); - TRACE(("TIFFTileSize: %d\n", state->bytes)); + /* realloc to fit whole tile */ + /* 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 (y = state->yoff; y < state->ysize; y += tile_length) { + for (y = state->yoff; y < state->ysize; y += tile_length) { + int plane; + for (plane = 0; plane < planes; plane++) { + ImagingShuffler shuffler = unpackers[plane]; for (x = state->xoff; x < state->xsize; x += tile_width) { - if (ReadTile(tiff, x, y, (UINT32*) state->buffer) == -1) { + if (TIFFReadTile(tiff, (tdata_t)state->buffer, x, y, 0, plane) == -1) { TRACE(("Decode Error, Tile at %dx%d\n", x, y)); state->errcode = IMAGING_CODEC_BROKEN; - TIFFClose(tiff); return -1; } 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++) { + 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])); - state->shuffle((UINT8*) im->image[tile_y + y] + x * im->pixelsize, - state->buffer + tile_y * row_byte_size, - current_tile_width - ); + shuffler((UINT8*) im->image[tile_y + y] + x * im->pixelsize, + state->buffer + tile_y * 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; + return 0; +} - /* overflow check for realloc */ - if (INT_MAX / row_byte_size < rows_per_strip) { - state->errcode = IMAGING_CODEC_MEMORY; - TIFFClose(tiff); - return -1; - } +int +_decodeStrip(Imaging im, ImagingCodecState state, TIFF *tiff, int planes, ImagingShuffler *unpackers) { + INT32 strip_row = 0; + UINT8 *new_data; + UINT32 rows_per_strip; + int ret; + tsize_t strip_size, row_byte_size, unpacker_row_byte_size; + + ret = TIFFGetField(tiff, TIFFTAG_ROWSPERSTRIP, &rows_per_strip); + if (ret != 1 || rows_per_strip==(UINT32)(-1)) { + rows_per_strip = state->ysize; + } - state->bytes = rows_per_strip * row_byte_size; + if (rows_per_strip > INT_MAX) { + state->errcode = IMAGING_CODEC_MEMORY; + return -1; + } - TRACE(("StripSize: %d \n", state->bytes)); + TRACE(("RowsPerStrip: %u\n", rows_per_strip)); - 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 ... + strip_size = TIFFStripSize(tiff); + if (strip_size > INT_MAX - 1) { + state->errcode = IMAGING_CODEC_MEMORY; + return -1; + } - state->errcode = IMAGING_CODEC_MEMORY; - TIFFClose(tiff); - return -1; - } + unpacker_row_byte_size = (state->xsize * state->bits / planes + 7) / 8; + if (strip_size > (unpacker_row_byte_size * rows_per_strip)) { + // 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_BROKEN; + 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; - TIFFClose(tiff); - return -1; - } + state->bytes = strip_size; + + TRACE(("StripSize: %d \n", state->bytes)); + + row_byte_size = TIFFScanlineSize(tiff); + + // if the unpacker calculated row size is > row byte size, (at least) the last + // row of the strip will have a read buffer overflow. + if (row_byte_size == 0 || unpacker_row_byte_size > row_byte_size) { + state->errcode = IMAGING_CODEC_BROKEN; + return -1; + } - state->buffer = new_data; + TRACE(("RowsByteSize: %u \n", row_byte_size)); - for (; state->y < state->ysize; state->y += rows_per_strip) { - if (ReadStrip(tiff, state->y, (UINT32 *)state->buffer) == -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) { + int plane; + for (plane = 0; plane < planes; plane++) { + ImagingShuffler shuffler = unpackers[plane]; + if (TIFFReadEncodedStrip(tiff, TIFFComputeStrip(tiff, state->y, plane), (tdata_t)state->buffer, strip_size) == -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++) { + 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); + shuffler( + (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) { + TIFFSTATE *clientstate = (TIFFSTATE *)state->context; + char *filename = "tempfile.tif"; + char *mode = "rC"; + TIFF *tiff; + uint16_t photometric = 0; // init to not PHOTOMETRIC_YCBCR + uint16_t compression; + int readAsRGBA = 0; + uint16_t planarconfig = 0; + int planes = 1; + ImagingShuffler unpackers[4]; + UINT32 img_width, img_height; + + memset(unpackers, 0, sizeof(ImagingShuffler) * 4); + + /* 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: 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)); + + dump_state(clientstate); + clientstate->size = bytes; + clientstate->eof = clientstate->size; + clientstate->loc = 0; + clientstate->data = (tdata_t)buffer; + clientstate->flrealloc = 0; + dump_state(clientstate); + + TIFFSetWarningHandler(NULL); + 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(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); + } + + if (!tiff) { + TRACE(("Error, didn't get the tiff\n")); + state->errcode = IMAGING_CODEC_BROKEN; + return -1; + } + + if (clientstate->ifd) { + int rv; + uint32_t ifdoffset = clientstate->ifd; + TRACE(("reading tiff ifd %u\n", ifdoffset)); + rv = TIFFSetSubDirectory(tiff, ifdoffset); + if (!rv) { + TRACE(("error in TIFFSetSubDirectory")); + goto decode_err; + } + } + + TIFFGetField(tiff, TIFFTAG_IMAGEWIDTH, &img_width); + TIFFGetField(tiff, TIFFTAG_IMAGELENGTH, &img_height); + + 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 decode_err; + } + + + TIFFGetField(tiff, TIFFTAG_PHOTOMETRIC, &photometric); + TIFFGetField(tiff, TIFFTAG_COMPRESSION, &compression); + TIFFGetFieldDefaulted(tiff, TIFFTAG_PLANARCONFIG, &planarconfig); + + // Dealing with YCbCr images is complicated in case if subsampling + // Let LibTiff read them as RGBA + readAsRGBA = photometric == PHOTOMETRIC_YCBCR; + + if (readAsRGBA && compression == COMPRESSION_JPEG && planarconfig == PLANARCONFIG_CONTIG) { + // If using new JPEG compression, let libjpeg do RGB conversion for performance reasons + TIFFSetField(tiff, TIFFTAG_JPEGCOLORMODE, JPEGCOLORMODE_RGB); + readAsRGBA = 0; + } + + if (readAsRGBA) { + _decodeAsRGBA(im, state, tiff); + } + else { + planes = _pickUnpackers(im, state, tiff, planarconfig, unpackers); + if (planes <= 0) { + goto decode_err; + } + + if (TIFFIsTiled(tiff)) { + _decodeTile(im, state, tiff, planes, unpackers); + } + else { + _decodeStrip(im, state, tiff, planes, unpackers); + } + + if (!state->errcode) { + // Check if raw mode was RGBa and it was stored on separate planes + // so we have to convert it to RGBA + if (planes > 3 && strcmp(im->mode, "RGBA") == 0) { + uint16_t extrasamples; + uint16_t* sampleinfo; + ImagingShuffler shuffle; + INT32 y; + + TIFFGetFieldDefaulted(tiff, TIFFTAG_EXTRASAMPLES, &extrasamples, &sampleinfo); + + if (extrasamples >= 1 && + (sampleinfo[0] == EXTRASAMPLE_UNSPECIFIED || sampleinfo[0] == EXTRASAMPLE_ASSOCALPHA) + ) { + shuffle = ImagingFindUnpacker("RGBA", "RGBa", NULL); + + for (y = state->yoff; y < state->ysize; y++) { + UINT8* ptr = (UINT8*) im->image[y + state->yoff] + + state->xoff * im->pixelsize; + shuffle(ptr, ptr, state->xsize); + } + } } } } + decode_err: TIFFClose(tiff); TRACE(("Done Decoding, Returning \n")); // Returning -1 here to force ImageFile.load to break, rather than @@ -489,7 +727,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. @@ -498,21 +737,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; @@ -520,27 +768,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) { @@ -549,27 +803,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; + uint32_t 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; @@ -582,7 +842,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); @@ -590,7 +851,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; @@ -602,8 +864,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. @@ -617,35 +879,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); - - if (TIFFWriteScanline(tiff, (tdata_t)(state->buffer), (uint32)state->y, 0) == -1) { + 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_t)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; @@ -654,7 +946,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)) { @@ -662,7 +954,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; @@ -671,13 +963,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; @@ -690,9 +988,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 08ef35cfd3c..c7c7d48ed02 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 + tdata_t data; /* tdata_t == void* */ + toff_t loc; /* toff_t == uint32 */ + tsize_t size; /* tsize_t == int32 */ + int fp; + uint32_t 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 */ + 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_t 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 adf2dd27700..5dac95c1d01 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++) { @@ -1261,9 +1363,97 @@ band3I(UINT8* out, const UINT8* in, int pixels) } } +static void +band016B(UINT8* out, const UINT8* in, int pixels) +{ + int i; + /* band 0 only, big endian */ + for (i = 0; i < pixels; i++) { + out[0] = in[0]; + out += 4; in += 2; + } +} + +static void +band116B(UINT8* out, const UINT8* in, int pixels) +{ + int i; + /* band 1 only, big endian */ + for (i = 0; i < pixels; i++) { + out[1] = in[0]; + out += 4; in += 2; + } +} + +static void +band216B(UINT8* out, const UINT8* in, int pixels) +{ + int i; + /* band 2 only, big endian */ + for (i = 0; i < pixels; i++) { + out[2] = in[0]; + out += 4; in += 2; + } +} + +static void +band316B(UINT8* out, const UINT8* in, int pixels) +{ + int i; + /* band 3 only, big endian */ + for (i = 0; i < pixels; i++) { + out[3] = in[0]; + out += 4; in += 2; + } +} + +static void +band016L(UINT8* out, const UINT8* in, int pixels) +{ + int i; + /* band 0 only, little endian */ + for (i = 0; i < pixels; i++) { + out[0] = in[1]; + out += 4; in += 2; + } +} + +static void +band116L(UINT8* out, const UINT8* in, int pixels) +{ + int i; + /* band 1 only, little endian */ + for (i = 0; i < pixels; i++) { + out[1] = in[1]; + out += 4; in += 2; + } +} + +static void +band216L(UINT8* out, const UINT8* in, int pixels) +{ + int i; + /* band 2 only, little endian */ + for (i = 0; i < pixels; i++) { + out[2] = in[1]; + out += 4; in += 2; + } +} + +static void +band316L(UINT8* out, const UINT8* in, int pixels) +{ + int i; + /* band 3 only, little endian */ + for (i = 0; i < pixels; i++) { + out[3] = in[1]; + out += 4; in += 2; + } +} + static struct { - const char* mode; - const char* rawmode; + const char *mode; + const char *rawmode; int bits; ImagingShuffler unpack; } unpackers[] = { @@ -1280,254 +1470,285 @@ 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;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;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", 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}, + {"RGB", "R;16L", 16, band016L}, + {"RGB", "G;16L", 16, band116L}, + {"RGB", "B;16L", 16, band216L}, + {"RGB", "R;16B", 16, band016B}, + {"RGB", "G;16B", 16, band116B}, + {"RGB", "B;16B", 16, band216B}, /* 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}, + {"RGBA", "R;16L", 16, band016L}, + {"RGBA", "G;16L", 16, band116L}, + {"RGBA", "B;16L", 16, band216L}, + {"RGBA", "A;16L", 16, band316L}, + {"RGBA", "R;16B", 16, band016B}, + {"RGBA", "G;16B", 16, band116B}, + {"RGBA", "B;16B", 16, band216B}, + {"RGBA", "A;16B", 16, band316B}, #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}, + {"RGB", "R;16N", 16, band016B}, + {"RGB", "G;16N", 16, band116B}, + {"RGB", "B;16N", 16, band216B}, + + {"RGBA", "R;16N", 16, band016B}, + {"RGBA", "G;16N", 16, band116B}, + {"RGBA", "B;16N", 16, band216B}, + {"RGBA", "A;16N", 16, band316B}, #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, unpackRGBA16L}, + {"RGB", "R;16N", 16, band016L}, + {"RGB", "G;16N", 16, band116L}, + {"RGB", "B;16N", 16, band216L}, + + + {"RGBA", "R;16N", 16, band016L}, + {"RGBA", "G;16N", 16, band116L}, + {"RGBA", "B;16N", 16, band216L}, + {"RGBA", "A;16N", 16, band316L}, #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 19da1f65443..0b177bdd4f5 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 a034bebf2fe..643ced49f17 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 75b4961abaa..d6690de3d28 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) { - - /* Skip forward until next 'x' */ - - while (bytes > 0) { - if (*ptr == 'x') - break; - ptr++; - bytes--; - } + while (bytes > 0) { + if (*ptr == 'x') { + break; + } + ptr++; + bytes--; + } - if (bytes == 0) - return ptr - buf; + if (bytes == 0) { + return ptr - buf; + } - state->state = BYTE; + state->state = BYTE; + } - } + if (bytes < 3) { + return ptr - buf; + } - if (bytes < 3) - return ptr - buf; + state->buffer[state->x] = (HEX(ptr[1]) << 4) + HEX(ptr[2]); - 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 (++state->x >= state->bytes) { + state->x = 0; - /* Got a full line, unpack it */ - state->shuffle((UINT8*) im->image[state->y], state->buffer, - state->xsize); + 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; - } - } - - 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 e066fd6b583..eec4c0d8462 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); - - if (state->y < state->ysize-1) { - - /* any line but the last */ - 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]; - *ptr++ = ','; - bytes -= 5; - - if (++state->count >= 79/5) { - *ptr++ = '\n'; - bytes--; - state->count = 0; - } - - } - - state->y++; - - } 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->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]; + + *ptr++ = '0'; + *ptr++ = 'x'; + *ptr++ = hex[(i >> 4) & 15]; + *ptr++ = hex[i & 15]; + *ptr++ = ','; + bytes -= 5; + + if (++state->count >= 79 / 5) { + *ptr++ = '\n'; + bytes--; + state->count = 0; + } + } + + state->y++; + + } 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; + } } return ptr - buf; diff --git a/src/libImaging/Zip.h b/src/libImaging/Zip.h deleted file mode 100644 index 21a336f908a..00000000000 --- 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 00000000000..50218b6c69a --- /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 43601c38eeb..8749678341e 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; + 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]; + + /* 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 4: - /* paeth filtering */ + case ZIP_TIFF_PREDICTOR: 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]; - - /* 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; - + 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 fa1c4e72876..edbce36822c 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: + + /* 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; + } - 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; + /* Stuff image data into the compressor */ + state->shuffle( + state->buffer + 1, + (UINT8 *)im->image[state->y + state->yoff] + + state->xoff * im->pixelsize, + state->xsize); - if (context->mode == ZIP_PNG) { + state->y++; - /* 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) */ + context->output = state->buffer; - bpp = (state->bits + 7) / 8; + 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) */ - /* 0. No filter */ - for (i = 1, sum = 0; i <= state->bytes; i++) { - UINT8 v = state->buffer[i]; - sum += (v < 128) ? v : 256 - v; - } + bpp = (state->bits + 7) / 8; - /* 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 */ - } - } - - /* 1. Prior */ - if (sum > 0) { - for (i = 1, s = 0; i <= bpp; i++) { + /* 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; - } - 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 */ + sum += (v < 128) ? v : 256 - v; } - } - /* 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; + /* 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->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; + /* 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 */ + } } - 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; + + /* 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; + } } - if (s < sum) { - context->output = context->paeth; - 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; + } } } - } - /* 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; - } + /* Compress this line */ + context->z_stream.next_in = context->output; + context->z_stream.avail_in = state->bytes + 1; - /* Swap buffer pointers */ - ptr = state->buffer; - state->buffer = context->previous; - context->previous = ptr; + 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; + } - if (context->z_stream.avail_out == 0) - break; /* Buffer full */ + /* Swap buffer pointers */ + ptr = state->buffer; + state->buffer = context->previous; + context->previous = ptr; + } - case 2: + if (context->z_stream.avail_out == 0) { + break; /* Buffer full */ + } - /* End of image data; flush compressor buffers */ + case 2: - while (context->z_stream.avail_out > 0) { + /* End of image data; flush compressor buffers */ - err = deflate(&context->z_stream, Z_FINISH); + while (context->z_stream.avail_out > 0) { + err = deflate(&context->z_stream, Z_FINISH); - if (err == Z_STREAM_END) { + if (err == Z_STREAM_END) { + free(context->paeth); + free(context->average); + free(context->up); + free(context->prior); + free(context->previous); - free(context->paeth); - free(context->average); - free(context->up); - free(context->prior); - free(context->previous); + deflateEnd(&context->z_stream); - deflateEnd(&context->z_stream); + state->errcode = IMAGING_CODEC_END; - state->errcode = IMAGING_CODEC_END; + break; + } - break; + if (context->z_stream.avail_out == 0) { + break; /* Buffer full */ + } } - - 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 5cde31cdc5d..5261681107b 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/map.c b/src/map.c index 54e0fdb2296..c298bd1482a 100644 --- a/src/map.c +++ b/src/map.c @@ -20,307 +20,61 @@ #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; -#ifdef _WIN32 - HANDLE hFile; - HANDLE hMap; -#endif -} ImagingMapperObject; - -static PyTypeObject ImagingMapperType; - -ImagingMapperObject* -PyImaging_MapperNew(const char* filename, int readonly) -{ - ImagingMapperObject *mapper; - - if (PyType_Ready(&ImagingMapperType) < 0) - return NULL; - - mapper = PyObject_New(ImagingMapperObject, &ImagingMapperType); - if (mapper == NULL) - return NULL; - - mapper->base = NULL; - mapper->size = mapper->offset = 0; - -#ifdef _WIN32 - mapper->hFile = (HANDLE)-1; - mapper->hMap = (HANDLE)-1; - - /* FIXME: currently supports readonly mappings only */ - mapper->hFile = CreateFile( - filename, - GENERIC_READ, - FILE_SHARE_READ, - NULL, OPEN_EXISTING, - FILE_ATTRIBUTE_NORMAL, - NULL); - if (mapper->hFile == (HANDLE)-1) { - PyErr_SetString(PyExc_IOError, "cannot open file"); - Py_DECREF(mapper); - return 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"); - Py_DECREF(mapper); - return NULL; - } - - mapper->base = (char*) MapViewOfFile( - mapper->hMap, - FILE_MAP_READ, - 0, 0, 0); - - mapper->size = GetFileSize(mapper->hFile, 0); -#endif - - return mapper; -} - -static void -mapping_dealloc(ImagingMapperObject* mapper) -{ -#ifdef _WIN32 - if (mapper->base != 0) - UnmapViewOfFile(mapper->base); - if (mapper->hMap != (HANDLE)-1) - CloseHandle(mapper->hMap); - if (mapper->hFile != (HANDLE)-1) - CloseHandle(mapper->hFile); - mapper->base = 0; - mapper->hMap = mapper->hFile = (HANDLE)-1; -#endif - PyObject_Del(mapper); -} - -/* -------------------------------------------------------------------- */ -/* standard file operations */ - -static PyObject* -mapping_read(ImagingMapperObject* mapper, PyObject* args) -{ - PyObject* buf; - - int size = -1; - if (!PyArg_ParseTuple(args, "|i", &size)) - return NULL; - - /* check size */ - if (size < 0 || mapper->offset + size > mapper->size) - size = mapper->size - mapper->offset; - if (size < 0) - size = 0; - - buf = PyBytes_FromStringAndSize(NULL, size); - if (!buf) - return NULL; - - if (size > 0) { - memcpy(PyBytes_AsString(buf), mapper->base + mapper->offset, size); - mapper->offset += size; - } - - return buf; -} - -static PyObject* -mapping_seek(ImagingMapperObject* mapper, PyObject* args) -{ - int offset; - int whence = 0; - if (!PyArg_ParseTuple(args, "i|i", &offset, &whence)) - return NULL; - - switch (whence) { - case 0: /* SEEK_SET */ - mapper->offset = offset; - break; - case 1: /* SEEK_CUR */ - mapper->offset += offset; - break; - case 2: /* SEEK_END */ - mapper->offset = mapper->size + offset; - break; - default: - /* FIXME: raise ValueError? */ - break; - } - - Py_INCREF(Py_None); - return Py_None; -} - -/* -------------------------------------------------------------------- */ -/* map entire image */ - -extern PyObject*PyImagingNew(Imaging im); - -static void -ImagingDestroyMap(Imaging im) -{ - return; /* nothing to do! */ -} - -static PyObject* -mapping_readimage(ImagingMapperObject* mapper, PyObject* args) -{ - int y, size; - Imaging im; - - char* mode; - int xsize; - int ysize; - int stride; - int 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")) - stride = xsize; - else if (!strcmp(mode, "I;16") || !strcmp(mode, "I;16B")) - stride = xsize * 2; - else - stride = xsize * 4; - } - - size = ysize * stride; - - if (mapper->offset + size > mapper->size) { - PyErr_SetString(PyExc_IOError, "image file truncated"); - return NULL; - } - - im = ImagingNewPrologue(mode, xsize, ysize); - if (!im) - return NULL; - - /* setup file pointers */ - 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; - - im->destroy = ImagingDestroyMap; - - mapper->offset += size; - - return PyImagingNew(im); -} - -static struct PyMethodDef methods[] = { - /* standard file interface */ - {"read", (PyCFunction)mapping_read, 1}, - {"seek", (PyCFunction)mapping_seek, 1}, - /* extensions */ - {"readimage", (PyCFunction)mapping_readimage, 1}, - {NULL, NULL} /* sentinel */ -}; - -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*/ -}; - -PyObject* -PyImaging_Mapper(PyObject* self, PyObject* args) -{ - char* filename; - if (!PyArg_ParseTuple(args, "s", &filename)) - return NULL; - - return (PyObject*) PyImaging_MapperNew(filename, 1); -} +extern PyObject * +PyImagingNew(Imaging im); /* -------------------------------------------------------------------- */ /* Buffer mapper */ 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"); @@ -328,12 +82,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) { @@ -341,7 +96,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"); @@ -349,8 +104,9 @@ 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"); @@ -363,27 +119,28 @@ PyImaging_MapBuffer(PyObject* self, PyObject* args) return NULL; } - im = ImagingNewPrologueSubtype( - mode, xsize, ysize, sizeof(ImagingBufferInstance)); + im = ImagingNewPrologueSubtype(mode, xsize, ysize, sizeof(ImagingBufferInstance)); if (!im) { PyBuffer_Release(&view); return NULL; } /* 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 25e63aeaf56..0a9a3646ef0 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); @@ -149,44 +145,43 @@ _outline_transform(OutlineObject* self, PyObject* args) } static struct PyMethodDef _outline_methods[] = { - {"line", (PyCFunction)_outline_line, 1}, - {"curve", (PyCFunction)_outline_curve, 1}, - {"move", (PyCFunction)_outline_move, 1}, - {"close", (PyCFunction)_outline_close, 1}, - {"transform", (PyCFunction)_outline_transform, 1}, + {"line", (PyCFunction)_outline_line, METH_VARARGS}, + {"curve", (PyCFunction)_outline_curve, METH_VARARGS}, + {"move", (PyCFunction)_outline_move, METH_VARARGS}, + {"close", (PyCFunction)_outline_close, METH_VARARGS}, + {"transform", (PyCFunction)_outline_transform, METH_VARARGS}, {NULL, NULL} /* sentinel */ }; 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 f69755d1685..dea274ee336 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 = calloc(2 * count * sizeof(double) + 1, sizeof(double)); + if (!xy) { + ImagingError_MemoryError(); } - xy = malloc(2 * count * sizeof(double) + 1); - if (!xy) - PyErr_NoMemory(); 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,97 +314,107 @@ 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; - x0 = x1 = xy[0]; - y0 = y1 = xy[1]; + if (self->count == 0) { + x0 = x1 = 0; + y0 = y1 = 0; + } else { + x0 = x1 = xy[0]; + 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]; + 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]; + } + } } 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 +423,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 +484,8 @@ path_tolist(PyPathObject* self, PyObject* args) 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,141 +493,135 @@ 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; } static struct PyMethodDef methods[] = { - {"getbbox", (PyCFunction)path_getbbox, 1}, - {"tolist", (PyCFunction)path_tolist, 1}, - {"compact", (PyCFunction)path_compact, 1}, - {"map", (PyCFunction)path_map, 1}, - {"transform", (PyCFunction)path_transform, 1}, + {"getbbox", (PyCFunction)path_getbbox, METH_VARARGS}, + {"tolist", (PyCFunction)path_tolist, METH_VARARGS}, + {"compact", (PyCFunction)path_compact, METH_VARARGS}, + {"map", (PyCFunction)path_map, METH_VARARGS}, + {"transform", (PyCFunction)path_transform, METH_VARARGS}, {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/src/thirdparty/fribidi-shim/fribidi.c b/src/thirdparty/fribidi-shim/fribidi.c new file mode 100644 index 00000000000..76fd5b9a498 --- /dev/null +++ b/src/thirdparty/fribidi-shim/fribidi.c @@ -0,0 +1,104 @@ + +#ifndef _WIN32 +#include +#else +#define WIN32_LEAN_AND_MEAN +#include +#endif + +#define FRIBIDI_SHIM_IMPLEMENTATION + +#include "fribidi.h" + + +/* FriBiDi>=1.0.0 adds bracket_types param, ignore and call legacy function */ +static FriBidiLevel fribidi_get_par_embedding_levels_ex_compat( + const FriBidiCharType *bidi_types, + const FriBidiBracketType *bracket_types, + const FriBidiStrIndex len, + FriBidiParType *pbase_dir, + FriBidiLevel *embedding_levels) +{ + return fribidi_get_par_embedding_levels( + bidi_types, len, pbase_dir, embedding_levels); +} + +/* FriBiDi>=1.0.0 gets bracket types here, ignore */ +static void fribidi_get_bracket_types_compat( + const FriBidiChar *str, + const FriBidiStrIndex len, + const FriBidiCharType *types, + FriBidiBracketType *btypes) +{ /* no-op*/ } + + +int load_fribidi(void) { + int error = 0; + + p_fribidi = 0; + + /* Microsoft needs a totally different system */ +#ifndef _WIN32 +#define LOAD_FUNCTION(func) \ + func = (t_##func)dlsym(p_fribidi, #func); \ + error = error || (func == 0); + + p_fribidi = dlopen("libfribidi.so", RTLD_LAZY); + if (!p_fribidi) { + p_fribidi = dlopen("libfribidi.so.0", RTLD_LAZY); + } + if (!p_fribidi) { + p_fribidi = dlopen("libfribidi.dylib", RTLD_LAZY); + } +#else +#define LOAD_FUNCTION(func) \ + func = (t_##func)GetProcAddress(p_fribidi, #func); \ + error = error || (func == 0); + + p_fribidi = LoadLibrary("fribidi"); + if (!p_fribidi) { + p_fribidi = LoadLibrary("fribidi-0"); + } + /* MSYS2 */ + if (!p_fribidi) { + p_fribidi = LoadLibrary("libfribidi-0"); + } +#endif + + if (!p_fribidi) { + return 1; + } + + /* load FriBiDi>=1.0.0 functions first, use error to detect version */ + LOAD_FUNCTION(fribidi_get_par_embedding_levels_ex); + LOAD_FUNCTION(fribidi_get_bracket_types); + if (error) { + /* using FriBiDi<1.0.0, ignore new parameters */ + error = 0; + fribidi_get_par_embedding_levels_ex = &fribidi_get_par_embedding_levels_ex_compat; + fribidi_get_bracket_types = &fribidi_get_bracket_types_compat; + } + + LOAD_FUNCTION(fribidi_unicode_to_charset); + LOAD_FUNCTION(fribidi_charset_to_unicode); + LOAD_FUNCTION(fribidi_get_bidi_types); + LOAD_FUNCTION(fribidi_get_par_embedding_levels); + +#ifndef _WIN32 + fribidi_version_info = *(const char**)dlsym(p_fribidi, "fribidi_version_info"); + if (dlerror() || error || (fribidi_version_info == 0)) { + dlclose(p_fribidi); + p_fribidi = 0; + return 2; + } +#else + fribidi_version_info = *(const char**)GetProcAddress(p_fribidi, "fribidi_version_info"); + if (error || (fribidi_version_info == 0)) { + FreeLibrary(p_fribidi); + p_fribidi = 0; + return 2; + } +#endif + + return 0; +} diff --git a/src/thirdparty/fribidi-shim/fribidi.h b/src/thirdparty/fribidi-shim/fribidi.h new file mode 100644 index 00000000000..7e175c3db80 --- /dev/null +++ b/src/thirdparty/fribidi-shim/fribidi.h @@ -0,0 +1,115 @@ + +#define FRIBIDI_MAJOR_VERSION 1 + +/* fribidi-types.h */ + +# if defined (_SVR4) || defined (SVR4) || defined (__OpenBSD__) || \ + defined (_sgi) || defined (__sun) || defined (sun) || \ + defined (__digital__) || defined (__HP_cc) +# include +# elif defined (_AIX) +# include +# else +# include +# endif + +typedef uint32_t FriBidiChar; +typedef int FriBidiStrIndex; + +typedef FriBidiChar FriBidiBracketType; + + + +/* fribidi-char-sets.h */ + +typedef enum +{ + _FRIBIDI_CHAR_SET_NOT_FOUND, + FRIBIDI_CHAR_SET_UTF8, + FRIBIDI_CHAR_SET_CAP_RTL, + FRIBIDI_CHAR_SET_ISO8859_6, + FRIBIDI_CHAR_SET_ISO8859_8, + FRIBIDI_CHAR_SET_CP1255, + FRIBIDI_CHAR_SET_CP1256, + _FRIBIDI_CHAR_SETS_NUM_PLUS_ONE +} +FriBidiCharSet; + + + +/* fribidi-bidi-types.h */ + +typedef signed char FriBidiLevel; + +#define FRIBIDI_TYPE_LTR_VAL 0x00000110L +#define FRIBIDI_TYPE_RTL_VAL 0x00000111L +#define FRIBIDI_TYPE_ON_VAL 0x00000040L + +typedef uint32_t FriBidiCharType; +#define FRIBIDI_TYPE_LTR FRIBIDI_TYPE_LTR_VAL + +typedef uint32_t FriBidiParType; +#define FRIBIDI_PAR_LTR FRIBIDI_TYPE_LTR_VAL +#define FRIBIDI_PAR_RTL FRIBIDI_TYPE_RTL_VAL +#define FRIBIDI_PAR_ON FRIBIDI_TYPE_ON_VAL + +#define FRIBIDI_LEVEL_IS_RTL(lev) ((lev) & 1) +#define FRIBIDI_DIR_TO_LEVEL(dir) ((FriBidiLevel) (FRIBIDI_IS_RTL(dir) ? 1 : 0)) +#define FRIBIDI_IS_RTL(p) ((p) & 0x00000001L) +#define FRIBIDI_IS_EXPLICIT_OR_BN_OR_WS(p) ((p) & 0x00901000L) + + + +/* functions */ + +#ifdef FRIBIDI_SHIM_IMPLEMENTATION +#ifdef _MSC_VER +#define FRIBIDI_ENTRY +#else +#define FRIBIDI_ENTRY __attribute__((visibility ("hidden"))) +#endif +#else +#define FRIBIDI_ENTRY extern +#endif + +#define FRIBIDI_FUNC(ret, name, ...) \ + typedef ret (*t_##name) (__VA_ARGS__); \ + FRIBIDI_ENTRY t_##name name; + +FRIBIDI_FUNC(FriBidiStrIndex, fribidi_unicode_to_charset, + FriBidiCharSet, const FriBidiChar *, FriBidiStrIndex, char *); + +FRIBIDI_FUNC(FriBidiStrIndex, fribidi_charset_to_unicode, + FriBidiCharSet, const char *, FriBidiStrIndex, FriBidiChar *); + +FRIBIDI_FUNC(void, fribidi_get_bidi_types, + const FriBidiChar *, const FriBidiStrIndex, FriBidiCharType *); + +FRIBIDI_FUNC(FriBidiLevel, fribidi_get_par_embedding_levels, + const FriBidiCharType *, const FriBidiStrIndex, FriBidiParType *, + FriBidiLevel *); + +/* FriBiDi>=1.0.0 */ +FRIBIDI_FUNC(FriBidiLevel, fribidi_get_par_embedding_levels_ex, + const FriBidiCharType *, const FriBidiBracketType *, const FriBidiStrIndex, + FriBidiParType *, FriBidiLevel *); + +/* FriBiDi>=1.0.0 */ +FRIBIDI_FUNC(void, fribidi_get_bracket_types, + const FriBidiChar *, const FriBidiStrIndex, const FriBidiCharType *, + FriBidiBracketType *); + +#undef FRIBIDI_FUNC + +/* constant, not a function */ +FRIBIDI_ENTRY const char *fribidi_version_info; + + + +/* shim */ + +FRIBIDI_ENTRY void *p_fribidi; + +FRIBIDI_ENTRY int load_fribidi(void); + +#undef FRIBIDI_ENTRY diff --git a/src/thirdparty/raqm/AUTHORS b/src/thirdparty/raqm/AUTHORS new file mode 100644 index 00000000000..bd5c3ac6b6a --- /dev/null +++ b/src/thirdparty/raqm/AUTHORS @@ -0,0 +1,9 @@ +Abderraouf Adjal +Ali Yousuf +Anood Almuharbi +Asma Albahanta +Fahad Alsaidi +Ibtisam Almabsali +Khaled Hosny +Mazoon Almaamari +Shamsa Alqassabi diff --git a/src/thirdparty/raqm/COPYING b/src/thirdparty/raqm/COPYING new file mode 100644 index 00000000000..196511ef688 --- /dev/null +++ b/src/thirdparty/raqm/COPYING @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright © 2015 Information Technology Authority (ITA) +Copyright © 2016 Khaled Hosny + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/thirdparty/raqm/NEWS b/src/thirdparty/raqm/NEWS new file mode 100644 index 00000000000..c49176a95bf --- /dev/null +++ b/src/thirdparty/raqm/NEWS @@ -0,0 +1,103 @@ +Overview of changes leading to 0.7.1 +Monday, September 27, 2021 +==================================== + +Fix test failure with newer HarfBuzz versions. + +Apply FT_Face transformation matrix when built against FreeType 2.11 or later. + +Add meson build system. Autotools build system will be dropped in next release. + +Improve MSVC support. + +Build and documentation fixes. + +Overview of changes leading to 0.7.1 +Sunday, November 22, 2020 +==================================== + +Require HarfBuzz >= 2.0.0 + +Build and documentation fixes. + +Overview of changes leading to 0.7.0 +Monday, May 27, 2019 +==================================== + +New API: + * raqm_version + * raqm_version_string + * raqm_version_atleast + * RAQM_VERSION_MAJOR + * RAQM_VERSION_MICRO + * RAQM_VERSION_MINOR + * RAQM_VERSION_STRING + * RAQM_VERSION_ATLEAST + +Overview of changes leading to 0.6.0 +Sunday, May 5, 2019 +==================================== + +Fix TTB direction regression from the previous release. + +Correctly detect script of Common and Inherite characters at start of text. + +Undef HAVE_CONFIG_H workaround, for older versions of Fribidi. + +Drop test suite dependency on GLib. + +Port test runner to Python instead of shell script. + +New API: +* raqm_set_invisible_glyph() + +Overview of changes leading to 0.5.0 +Saturday, February 24, 2018 +==================================== + +Use FriBiDi 1.x API when available. + +Overview of changes leading to 0.4.0 +Sunday, January 21, 2018 +==================================== + +Set begin-of-text and end-of-text HarfBuzz buffer flags. + +Dynamically allocate memory instead of using stack allocation for input text. + +Accept zero length text and do nothing instead of treating it as error. + +Overview of changes leading to 0.3.0 +Monday, August 21, 2017 +==================================== + +Fix stack corruption on MSVC. + +New API: +* raqm_set_freetype_load_flags + +Overview of changes leading to 0.2.0 +Wednesday, August 25, 2016 +==================================== + +Fix building with MSVC due to lacking C99 support. + +Make multiple fonts support actually work. Start and length now respect the +input encoding. + +New API: +* raqm_index_to_position +* raqm_position_to_index +* raqm_set_language + +Overview of changes leading to 0.1.1 +Sunday, May 1, 2016 +==================================== + +Fix make check on 32-bit systems. + +Overview of changes leading to 0.1.0 +Wednesday, January 20, 2016 +==================================== + +First release. diff --git a/src/thirdparty/raqm/README.md b/src/thirdparty/raqm/README.md new file mode 100644 index 00000000000..64937343a6f --- /dev/null +++ b/src/thirdparty/raqm/README.md @@ -0,0 +1,83 @@ +Raqm +==== + +[![Build](https://github.com/HOST-Oman/libraqm/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/HOST-Oman/libraqm/actions) + +Raqm is a small library that encapsulates the logic for complex text layout and +provides a convenient API. + +It currently provides bidirectional text support (using [FriBiDi][1]), shaping +(using [HarfBuzz][2]), and proper script itemization. As a result, +Raqm can support most writing systems covered by Unicode. + +The documentation can be accessed on the web at: +> http://host-oman.github.io/libraqm/ + +Raqm (Arabic: رَقْم) is writing, also number or digit and the Arabic word for +digital (رَقَمِيّ) shares the same root, so it is a play on “digital writing”. + +Building +-------- + +Raqm depends on the following libraries: +* [FreeType][3] +* [HarfBuzz][2] +* [FriBiDi][1] + +To build the documentation you will also need: +* [GTK-Doc][4] + +To install dependencies on Fedora: + + sudo dnf install freetype-devel harfbuzz-devel fribidi-devel meson gtk-doc + +To install dependencies on Ubuntu: + + sudo apt-get install libfreetype6-dev libharfbuzz-dev libfribidi-dev meson gtk-doc-tools + +On Mac OS X you can use Homebrew: + + brew install freetype harfbuzz fribidi meson gtk-doc + export XML_CATALOG_FILES="/usr/local/etc/xml/catalog" # for the docs + +Once you have the source code and the dependencies, you can proceed to build. +To do that, run the customary sequence of commands in the source code +directory: + + $ meson build + $ ninja -C build + $ ninja -C build install + +To build the documentation, pass `-Ddocs=enable` to the `meson`. + +To run the tests: + + $ ninja -C test + +Contributing +------------ + +Once you have made a change that you are happy with, contribute it back, we’ll +be happy to integrate it! Just fork the repository and make a pull request. + +Projects using Raqm +------------------- + +1. [ImageMagick](https://github.com/ImageMagick/ImageMagick) +2. [LibGD](https://github.com/libgd/libgd) +3. [FontView](https://github.com/googlei18n/fontview) +4. [Pillow](https://github.com/python-pillow) +5. [mplcairo](https://github.com/anntzer/mplcairo) + +The following projects have patches to support complex text layout using Raqm: + +2. SDL_ttf: https://bugzilla.libsdl.org/show_bug.cgi?id=3211 +3. Pygame: https://bitbucket.org/pygame/pygame/pull-requests/52 +4. Blender: https://developer.blender.org/D1809 + + + +[1]: http://fribidi.org +[2]: http://harfbuzz.org +[3]: https://www.freetype.org +[4]: https://www.gtk.org/gtk-doc diff --git a/src/thirdparty/raqm/raqm-version.h b/src/thirdparty/raqm/raqm-version.h new file mode 100644 index 00000000000..8b115f612c6 --- /dev/null +++ b/src/thirdparty/raqm/raqm-version.h @@ -0,0 +1,44 @@ +/* + * Copyright © 2011 Google, Inc. + * + * This is part of HarfBuzz, a text shaping library. + * + * Permission is hereby granted, without written agreement and without + * license or royalty fees, to use, copy, modify, and distribute this + * software and its documentation for any purpose, provided that the + * above copyright notice and the following two paragraphs appear in + * all copies of this software. + * + * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR + * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES + * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN + * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, + * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS + * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO + * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. + * + * Google Author(s): Behdad Esfahbod + */ + +#ifndef _RAQM_H_IN_ +#error "Include instead." +#endif + +#ifndef _RAQM_VERSION_H_ +#define _RAQM_VERSION_H_ + +#define RAQM_VERSION_MAJOR 0 +#define RAQM_VERSION_MINOR 7 +#define RAQM_VERSION_MICRO 2 + +#define RAQM_VERSION_STRING "0.7.2" + +#define RAQM_VERSION_ATLEAST(major,minor,micro) \ + ((major)*10000+(minor)*100+(micro) <= \ + RAQM_VERSION_MAJOR*10000+RAQM_VERSION_MINOR*100+RAQM_VERSION_MICRO) + +#endif /* _RAQM_VERSION_H_ */ diff --git a/src/thirdparty/raqm/raqm.c b/src/thirdparty/raqm/raqm.c new file mode 100644 index 00000000000..31161c9d91d --- /dev/null +++ b/src/thirdparty/raqm/raqm.c @@ -0,0 +1,2118 @@ +/* + * Copyright © 2015 Information Technology Authority (ITA) + * Copyright © 2016 Khaled Hosny + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#undef HAVE_CONFIG_H // Workaround for Fribidi 1.0.5 and earlier +#endif + +#include +#include + +#ifdef HAVE_FRIBIDI_SYSTEM +#include +#else +#include "../fribidi-shim/fribidi.h" +#endif + +#include +#include + +#if FREETYPE_MAJOR > 2 || \ + FREETYPE_MAJOR == 2 && FREETYPE_MINOR >= 11 +#define HAVE_FT_GET_TRANSFORM +#endif + +#if HB_VERSION_ATLEAST(2, 0, 0) +#define HAVE_HB_BUFFER_SET_INVISIBLE_GLYPH +#endif + +#if HB_VERSION_ATLEAST(1, 8, 0) +#define HAVE_DECL_HB_BUFFER_FLAG_REMOVE_DEFAULT_IGNORABLES 1 +#else +#define HAVE_DECL_HB_BUFFER_FLAG_REMOVE_DEFAULT_IGNORABLES 0 +#endif + +#include "raqm.h" + +#if FRIBIDI_MAJOR_VERSION >= 1 +#define USE_FRIBIDI_EX_API +#endif + +/** + * SECTION:raqm + * @title: Raqm + * @short_description: A library for complex text layout + * @include: raqm.h + * + * Raqm is a light weight text layout library with strong emphasis on + * supporting languages and writing systems that require complex text layout. + * + * The main object in Raqm API is #raqm_t, it stores all the states of the + * input text, its properties, and the output of the layout process. + * + * To start, you create a #raqm_t object, add text and font(s) to it, run the + * layout process, and finally query about the output. For example: + * + * |[ + * #include "raqm.h" + * + * int + * main (int argc, char *argv[]) + * { + * const char *fontfile; + * const char *text; + * const char *direction; + * const char *language; + * int ret = 1; + * + * FT_Library library = NULL; + * FT_Face face = NULL; + * + * if (argc < 5) + * { + * printf ("Usage: %s FONT_FILE TEXT DIRECTION LANG\n", argv[0]); + * return 1; + * } + * + * fontfile = argv[1]; + * text = argv[2]; + * direction = argv[3]; + * language = argv[4]; + * + * if (FT_Init_FreeType (&library) == 0) + * { + * if (FT_New_Face (library, fontfile, 0, &face) == 0) + * { + * if (FT_Set_Char_Size (face, face->units_per_EM, 0, 0, 0) == 0) + * { + * raqm_t *rq = raqm_create (); + * if (rq != NULL) + * { + * raqm_direction_t dir = RAQM_DIRECTION_DEFAULT; + * + * if (strcmp (direction, "r") == 0) + * dir = RAQM_DIRECTION_RTL; + * else if (strcmp (direction, "l") == 0) + * dir = RAQM_DIRECTION_LTR; + * + * if (raqm_set_text_utf8 (rq, text, strlen (text)) && + * raqm_set_freetype_face (rq, face) && + * raqm_set_par_direction (rq, dir) && + * raqm_set_language (rq, language, 0, strlen (text)) && + * raqm_layout (rq)) + * { + * size_t count, i; + * raqm_glyph_t *glyphs = raqm_get_glyphs (rq, &count); + * + * ret = !(glyphs != NULL || count == 0); + * + * printf("glyph count: %zu\n", count); + * for (i = 0; i < count; i++) + * { + * printf ("gid#%d off: (%d, %d) adv: (%d, %d) idx: %d\n", + * glyphs[i].index, + * glyphs[i].x_offset, + * glyphs[i].y_offset, + * glyphs[i].x_advance, + * glyphs[i].y_advance, + * glyphs[i].cluster); + * } + * } + * + * raqm_destroy (rq); + * } + * } + * + * FT_Done_Face (face); + * } + * + * FT_Done_FreeType (library); + * } + * + * return ret; + * } + * ]| + * To compile this example: + * |[ + * cc -o test test.c `pkg-config --libs --cflags raqm` + * ]| + */ + +/* For enabling debug mode */ +/*#define RAQM_DEBUG 1*/ +#ifdef RAQM_DEBUG +#define RAQM_DBG(...) fprintf (stderr, __VA_ARGS__) +#else +#define RAQM_DBG(...) +#endif + +#ifdef RAQM_TESTING +# define RAQM_TEST(...) printf (__VA_ARGS__) +# define SCRIPT_TO_STRING(script) \ + char buff[5]; \ + hb_tag_to_string (hb_script_to_iso15924_tag (script), buff); \ + buff[4] = '\0'; +#else +# define RAQM_TEST(...) +#endif + +typedef enum { + RAQM_FLAG_NONE = 0, + RAQM_FLAG_UTF8 = 1 << 0 +} _raqm_flags_t; + +typedef struct { + FT_Face ftface; + hb_language_t lang; + hb_script_t script; +} _raqm_text_info; + +typedef struct _raqm_run raqm_run_t; + +struct _raqm { + int ref_count; + + uint32_t *text; + char *text_utf8; + size_t text_len; + + _raqm_text_info *text_info; + + raqm_direction_t base_dir; + raqm_direction_t resolved_dir; + + hb_feature_t *features; + size_t features_len; + + raqm_run_t *runs; + raqm_glyph_t *glyphs; + + _raqm_flags_t flags; + + int ft_loadflags; + int invisible_glyph; +}; + +struct _raqm_run { + int pos; + int len; + + hb_direction_t direction; + hb_script_t script; + hb_font_t *font; + hb_buffer_t *buffer; + + raqm_run_t *next; +}; + +static uint32_t +_raqm_u8_to_u32_index (raqm_t *rq, + uint32_t index); + +static bool +_raqm_init_text_info (raqm_t *rq) +{ + hb_language_t default_lang; + + if (rq->text_info) + return true; + + rq->text_info = malloc (sizeof (_raqm_text_info) * rq->text_len); + if (!rq->text_info) + return false; + + default_lang = hb_language_get_default (); + for (size_t i = 0; i < rq->text_len; i++) + { + rq->text_info[i].ftface = NULL; + rq->text_info[i].lang = default_lang; + rq->text_info[i].script = HB_SCRIPT_INVALID; + } + + return true; +} + +static void +_raqm_free_text_info (raqm_t *rq) +{ + if (!rq->text_info) + return; + + for (size_t i = 0; i < rq->text_len; i++) + { + if (rq->text_info[i].ftface) + FT_Done_Face (rq->text_info[i].ftface); + } + + free (rq->text_info); + rq->text_info = NULL; +} + +static bool +_raqm_compare_text_info (_raqm_text_info a, + _raqm_text_info b) +{ + if (a.ftface != b.ftface) + return false; + + if (a.lang != b.lang) + return false; + + if (a.script != b.script) + return false; + + return true; +} + +/** + * raqm_create: + * + * Creates a new #raqm_t with all its internal states initialized to their + * defaults. + * + * Return value: + * A newly allocated #raqm_t with a reference count of 1. The initial reference + * count should be released with raqm_destroy() when you are done using the + * #raqm_t. Returns %NULL in case of error. + * + * Since: 0.1 + */ +raqm_t * +raqm_create (void) +{ + raqm_t *rq; + + rq = malloc (sizeof (raqm_t)); + if (!rq) + return NULL; + + rq->ref_count = 1; + + rq->text = NULL; + rq->text_utf8 = NULL; + rq->text_len = 0; + + rq->text_info = NULL; + + rq->base_dir = RAQM_DIRECTION_DEFAULT; + rq->resolved_dir = RAQM_DIRECTION_DEFAULT; + + rq->features = NULL; + rq->features_len = 0; + + rq->runs = NULL; + rq->glyphs = NULL; + + rq->flags = RAQM_FLAG_NONE; + + rq->ft_loadflags = -1; + rq->invisible_glyph = 0; + + return rq; +} + +/** + * raqm_reference: + * @rq: a #raqm_t. + * + * Increases the reference count on @rq by one. This prevents @rq from being + * destroyed until a matching call to raqm_destroy() is made. + * + * Return value: + * The referenced #raqm_t. + * + * Since: 0.1 + */ +raqm_t * +raqm_reference (raqm_t *rq) +{ + if (rq) + rq->ref_count++; + + return rq; +} + +static void +_raqm_free_runs (raqm_t *rq) +{ + raqm_run_t *runs = rq->runs; + while (runs) + { + raqm_run_t *run = runs; + runs = runs->next; + + hb_buffer_destroy (run->buffer); + hb_font_destroy (run->font); + free (run); + } +} + +/** + * raqm_destroy: + * @rq: a #raqm_t. + * + * Decreases the reference count on @rq by one. If the result is zero, then @rq + * and all associated resources are freed. + * See cairo_reference(). + * + * Since: 0.1 + */ +void +raqm_destroy (raqm_t *rq) +{ + if (!rq || --rq->ref_count != 0) + return; + + free (rq->text); + free (rq->text_utf8); + _raqm_free_text_info (rq); + _raqm_free_runs (rq); + free (rq->glyphs); + free (rq); +} + +/** + * raqm_set_text: + * @rq: a #raqm_t. + * @text: a UTF-32 encoded text string. + * @len: the length of @text. + * + * Adds @text to @rq to be used for layout. It must be a valid UTF-32 text, any + * invalid character will be replaced with U+FFFD. The text should typically + * represent a full paragraph, since doing the layout of chunks of text + * separately can give improper output. + * + * Return value: + * %true if no errors happened, %false otherwise. + * + * Since: 0.1 + */ +bool +raqm_set_text (raqm_t *rq, + const uint32_t *text, + size_t len) +{ + if (!rq || !text) + return false; + + rq->text_len = len; + + /* Empty string, don’t fail but do nothing */ + if (!len) + return true; + + free (rq->text); + + rq->text = malloc (sizeof (uint32_t) * rq->text_len); + if (!rq->text) + return false; + + _raqm_free_text_info (rq); + if (!_raqm_init_text_info (rq)) + return false; + + memcpy (rq->text, text, sizeof (uint32_t) * rq->text_len); + + return true; +} + +/** + * raqm_set_text_utf8: + * @rq: a #raqm_t. + * @text: a UTF-8 encoded text string. + * @len: the length of @text in UTF-8 bytes. + * + * Same as raqm_set_text(), but for text encoded in UTF-8 encoding. + * + * Return value: + * %true if no errors happened, %false otherwise. + * + * Since: 0.1 + */ +bool +raqm_set_text_utf8 (raqm_t *rq, + const char *text, + size_t len) +{ + uint32_t *unicode; + size_t ulen; + bool ok; + + if (!rq || !text) + return false; + + /* Empty string, don’t fail but do nothing */ + if (!len) + { + rq->text_len = len; + return true; + } + + rq->flags |= RAQM_FLAG_UTF8; + + rq->text_utf8 = malloc (sizeof (char) * len); + if (!rq->text_utf8) + return false; + + unicode = malloc (sizeof (uint32_t) * len); + if (!unicode) + return false; + + memcpy (rq->text_utf8, text, sizeof (char) * len); + + ulen = fribidi_charset_to_unicode (FRIBIDI_CHAR_SET_UTF8, + text, len, unicode); + + ok = raqm_set_text (rq, unicode, ulen); + + free (unicode); + return ok; +} + +/** + * raqm_set_par_direction: + * @rq: a #raqm_t. + * @dir: the direction of the paragraph. + * + * Sets the paragraph direction, also known as block direction in CSS. For + * horizontal text, this controls the overall direction in the Unicode + * Bidirectional Algorithm, so when the text is mainly right-to-left (with or + * without some left-to-right) text, then the base direction should be set to + * #RAQM_DIRECTION_RTL and vice versa. + * + * The default is #RAQM_DIRECTION_DEFAULT, which determines the paragraph + * direction based on the first character with strong bidi type (see [rule + * P2](https://unicode.org/reports/tr9/#P2) in Unicode Bidirectional Algorithm), + * which can be good enough for many cases but has problems when a mainly + * right-to-left paragraph starts with a left-to-right character and vice versa + * as the detected paragraph direction will be the wrong one, or when text does + * not contain any characters with string bidi types (e.g. only punctuation or + * numbers) as this will default to left-to-right paragraph direction. + * + * For vertical, top-to-bottom text, #RAQM_DIRECTION_TTB should be used. Raqm, + * however, provides limited vertical text support and does not handle rotated + * horizontal text in vertical text, instead everything is treated as vertical + * text. + * + * Return value: + * %true if no errors happened, %false otherwise. + * + * Since: 0.1 + */ +bool +raqm_set_par_direction (raqm_t *rq, + raqm_direction_t dir) +{ + if (!rq) + return false; + + rq->base_dir = dir; + + return true; +} + +/** + * raqm_set_language: + * @rq: a #raqm_t. + * @lang: a BCP47 language code. + * @start: index of first character that should use @face. + * @len: number of characters using @face. + * + * Sets a [BCP47 language + * code](https://www.w3.org/International/articles/language-tags/) to be used + * for @len-number of characters staring at @start. The @start and @len are + * input string array indices (i.e. counting bytes in UTF-8 and scaler values + * in UTF-32). + * + * This method can be used repeatedly to set different languages for different + * parts of the text. + * + * Return value: + * %true if no errors happened, %false otherwise. + * + * Stability: + * Unstable + * + * Since: 0.2 + */ +bool +raqm_set_language (raqm_t *rq, + const char *lang, + size_t start, + size_t len) +{ + hb_language_t language; + size_t end = start + len; + + if (!rq) + return false; + + if (!rq->text_len) + return true; + + if (rq->flags & RAQM_FLAG_UTF8) + { + start = _raqm_u8_to_u32_index (rq, start); + end = _raqm_u8_to_u32_index (rq, end); + } + + if (start >= rq->text_len || end > rq->text_len) + return false; + + if (!rq->text_info) + return false; + + language = hb_language_from_string (lang, -1); + for (size_t i = start; i < end; i++) + { + rq->text_info[i].lang = language; + } + + return true; +} + +/** + * raqm_add_font_feature: + * @rq: a #raqm_t. + * @feature: (transfer none): a font feature string. + * @len: length of @feature, -1 for %NULL-terminated. + * + * Adds a font feature to be used by the #raqm_t 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. + * + * @feature is string representing a single font feature, in the syntax + * understood by hb_feature_from_string(). + * + * This function can be called repeatedly, new features will be appended to the + * end of the features list and can potentially override previous features. + * + * Return value: + * %true if parsing @feature succeeded, %false otherwise. + * + * Since: 0.1 + */ +bool +raqm_add_font_feature (raqm_t *rq, + const char *feature, + int len) +{ + hb_bool_t ok; + hb_feature_t fea; + + if (!rq) + return false; + + ok = hb_feature_from_string (feature, len, &fea); + if (ok) + { + rq->features_len++; + rq->features = realloc (rq->features, + sizeof (hb_feature_t) * (rq->features_len)); + if (!rq->features) + return false; + + rq->features[rq->features_len - 1] = fea; + } + + return ok; +} + +static hb_font_t * +_raqm_create_hb_font (raqm_t *rq, + FT_Face face) +{ + hb_font_t *font = hb_ft_font_create_referenced (face); + + if (rq->ft_loadflags >= 0) + hb_ft_font_set_load_flags (font, rq->ft_loadflags); + + return font; +} + +static bool +_raqm_set_freetype_face (raqm_t *rq, + FT_Face face, + size_t start, + size_t end) +{ + if (!rq) + return false; + + if (!rq->text_len) + return true; + + if (start >= rq->text_len || end > rq->text_len) + return false; + + if (!rq->text_info) + return false; + + for (size_t i = start; i < end; i++) + { + if (rq->text_info[i].ftface) + FT_Done_Face (rq->text_info[i].ftface); + rq->text_info[i].ftface = face; + FT_Reference_Face (face); + } + + return true; +} + +/** + * raqm_set_freetype_face: + * @rq: a #raqm_t. + * @face: an #FT_Face. + * + * Sets an #FT_Face to be used for all characters in @rq. + * + * See also raqm_set_freetype_face_range(). + * + * Return value: + * %true if no errors happened, %false otherwise. + * + * Since: 0.1 + */ +bool +raqm_set_freetype_face (raqm_t *rq, + FT_Face face) +{ + return _raqm_set_freetype_face (rq, face, 0, rq->text_len); +} + +/** + * raqm_set_freetype_face_range: + * @rq: a #raqm_t. + * @face: an #FT_Face. + * @start: index of first character that should use @face. + * @len: number of characters using @face. + * + * Sets an #FT_Face to be used for @len-number of characters staring at @start. + * The @start and @len are input string array indices (i.e. counting bytes in + * UTF-8 and scaler values in UTF-32). + * + * This method can be used repeatedly to set different faces for different + * parts of the text. It is the responsibility of the client to make sure that + * face ranges cover the whole text. + * + * See also raqm_set_freetype_face(). + * + * Return value: + * %true if no errors happened, %false otherwise. + * + * Since: 0.1 + */ +bool +raqm_set_freetype_face_range (raqm_t *rq, + FT_Face face, + size_t start, + size_t len) +{ + size_t end = start + len; + + if (!rq) + return false; + + if (!rq->text_len) + return true; + + if (rq->flags & RAQM_FLAG_UTF8) + { + start = _raqm_u8_to_u32_index (rq, start); + end = _raqm_u8_to_u32_index (rq, end); + } + + return _raqm_set_freetype_face (rq, face, start, end); +} + +/** + * raqm_set_freetype_load_flags: + * @rq: a #raqm_t. + * @flags: FreeType load flags. + * + * Sets the load flags passed to FreeType when loading glyphs, should be the + * same flags used by the client when rendering FreeType glyphs. + * + * This requires version of HarfBuzz that has hb_ft_font_set_load_flags(), for + * older version the flags will be ignored. + * + * Return value: + * %true if no errors happened, %false otherwise. + * + * Since: 0.3 + */ +bool +raqm_set_freetype_load_flags (raqm_t *rq, + int flags) +{ + if (!rq) + return false; + + rq->ft_loadflags = flags; + + return true; +} + +/** + * raqm_set_invisible_glyph: + * @rq: a #raqm_t. + * @gid: glyph id to use for invisible glyphs. + * + * Sets the glyph id to be used for invisible glyhphs. + * + * If @gid is negative, invisible glyphs will be suppressed from the output. + * This requires HarfBuzz 1.8.0 or later. If raqm is used with an earlier + * HarfBuzz version, the return value will be %false and the shaping behavior + * does not change. + * + * If @gid is zero, invisible glyphs will be rendered as space. + * This works on all versions of HarfBuzz. + * + * If @gid is a positive number, it will be used for invisible glyphs. + * This requires a version of HarfBuzz that has + * hb_buffer_set_invisible_glyph(). For older versions, the return value + * will be %false and the shaping behavior does not change. + * + * Return value: + * %true if no errors happened, %false otherwise. + * + * Since: 0.6 + */ +bool +raqm_set_invisible_glyph (raqm_t *rq, + int gid) +{ + if (!rq) + return false; + +#ifndef HAVE_HB_BUFFER_SET_INVISIBLE_GLYPH + if (gid > 0) + return false; +#endif + +#if !defined(HAVE_DECL_HB_BUFFER_FLAG_REMOVE_DEFAULT_IGNORABLES) || \ + !HAVE_DECL_HB_BUFFER_FLAG_REMOVE_DEFAULT_IGNORABLES + if (gid < 0) + return false; +#endif + + rq->invisible_glyph = gid; + return true; +} + +static bool +_raqm_itemize (raqm_t *rq); + +static bool +_raqm_shape (raqm_t *rq); + +/** + * raqm_layout: + * @rq: a #raqm_t. + * + * Run the text layout process on @rq. This is the main Raqm function where the + * Unicode Bidirectional Text algorithm will be applied to the text in @rq, + * text shaping, and any other part of the layout process. + * + * Return value: + * %true if the layout process was successful, %false otherwise. + * + * Since: 0.1 + */ +bool +raqm_layout (raqm_t *rq) +{ + if (!rq) + return false; + + if (!rq->text_len) + return true; + + if (!rq->text_info) + return false; + + for (size_t i = 0; i < rq->text_len; i++) + { + if (!rq->text_info[i].ftface) + return false; + } + + if (!_raqm_itemize (rq)) + return false; + + if (!_raqm_shape (rq)) + return false; + + return true; +} + +static uint32_t +_raqm_u32_to_u8_index (raqm_t *rq, + uint32_t index); + +/** + * raqm_get_glyphs: + * @rq: a #raqm_t. + * @length: (out): output array length. + * + * Gets the final result of Raqm layout process, an array of #raqm_glyph_t + * containing the glyph indices in the font, their positions and other possible + * information. + * + * Return value: (transfer none): + * An array of #raqm_glyph_t, or %NULL in case of error. This is owned by @rq + * and must not be freed. + * + * Since: 0.1 + */ +raqm_glyph_t * +raqm_get_glyphs (raqm_t *rq, + size_t *length) +{ + size_t count = 0; + + if (!rq || !rq->runs || !length) + { + if (length) + *length = 0; + return NULL; + } + + for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) + count += hb_buffer_get_length (run->buffer); + + *length = count; + + if (rq->glyphs) + free (rq->glyphs); + + rq->glyphs = malloc (sizeof (raqm_glyph_t) * count); + if (!rq->glyphs) + { + *length = 0; + return NULL; + } + + RAQM_TEST ("Glyph information:\n"); + + count = 0; + for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) + { + size_t len; + hb_glyph_info_t *info; + hb_glyph_position_t *position; + + len = hb_buffer_get_length (run->buffer); + info = hb_buffer_get_glyph_infos (run->buffer, NULL); + position = hb_buffer_get_glyph_positions (run->buffer, NULL); + + for (size_t i = 0; i < len; i++) + { + rq->glyphs[count + i].index = info[i].codepoint; + rq->glyphs[count + i].cluster = info[i].cluster; + rq->glyphs[count + i].x_advance = position[i].x_advance; + rq->glyphs[count + i].y_advance = position[i].y_advance; + rq->glyphs[count + i].x_offset = position[i].x_offset; + rq->glyphs[count + i].y_offset = position[i].y_offset; + rq->glyphs[count + i].ftface = rq->text_info[info[i].cluster].ftface; + + RAQM_TEST ("glyph [%d]\tx_offset: %d\ty_offset: %d\tx_advance: %d\tfont: %s\n", + rq->glyphs[count + i].index, rq->glyphs[count + i].x_offset, + rq->glyphs[count + i].y_offset, rq->glyphs[count + i].x_advance, + rq->glyphs[count + i].ftface->family_name); + } + + count += len; + } + + if (rq->flags & RAQM_FLAG_UTF8) + { +#ifdef RAQM_TESTING + RAQM_TEST ("\nUTF-32 clusters:"); + for (size_t i = 0; i < count; i++) + RAQM_TEST (" %02d", rq->glyphs[i].cluster); + RAQM_TEST ("\n"); +#endif + + for (size_t i = 0; i < count; i++) + rq->glyphs[i].cluster = _raqm_u32_to_u8_index (rq, + rq->glyphs[i].cluster); + +#ifdef RAQM_TESTING + RAQM_TEST ("UTF-8 clusters: "); + for (size_t i = 0; i < count; i++) + RAQM_TEST (" %02d", rq->glyphs[i].cluster); + RAQM_TEST ("\n"); +#endif + } + return rq->glyphs; +} + +static bool +_raqm_resolve_scripts (raqm_t *rq); + +static hb_direction_t +_raqm_hb_dir (raqm_t *rq, FriBidiLevel level) +{ + hb_direction_t dir = HB_DIRECTION_LTR; + + if (rq->base_dir == RAQM_DIRECTION_TTB) + dir = HB_DIRECTION_TTB; + else if (FRIBIDI_LEVEL_IS_RTL (level)) + dir = HB_DIRECTION_RTL; + + return dir; +} + +typedef struct { + size_t pos; + size_t len; + FriBidiLevel level; +} _raqm_bidi_run; + +static void +_raqm_reverse_run (_raqm_bidi_run *run, const size_t len) +{ + assert (run); + + for (size_t i = 0; i < len / 2; i++) + { + _raqm_bidi_run temp = run[i]; + run[i] = run[len - 1 - i]; + run[len - 1 - i] = temp; + } +} + +static _raqm_bidi_run * +_raqm_reorder_runs (const FriBidiCharType *types, + const size_t len, + const FriBidiParType base_dir, + /* input and output */ + FriBidiLevel *levels, + /* output */ + size_t *run_count) +{ + FriBidiLevel level; + FriBidiLevel last_level = -1; + FriBidiLevel max_level = 0; + size_t run_start = 0; + size_t run_index = 0; + _raqm_bidi_run *runs = NULL; + size_t count = 0; + + if (len == 0) + { + *run_count = 0; + return NULL; + } + + assert (types); + assert (levels); + + /* L1. Reset the embedding levels of some chars: + 4. any sequence of white space characters at the end of the line. */ + for (int i = len - 1; + i >= 0 && FRIBIDI_IS_EXPLICIT_OR_BN_OR_WS (types[i]); i--) + { + levels[i] = FRIBIDI_DIR_TO_LEVEL (base_dir); + } + + /* Find max_level of the line. We don't reuse the paragraph + * max_level, both for a cleaner API, and that the line max_level + * may be far less than paragraph max_level. */ + for (int i = len - 1; i >= 0; i--) + { + if (levels[i] > max_level) + max_level = levels[i]; + } + + for (size_t i = 0; i < len; i++) + { + if (levels[i] != last_level) + count++; + + last_level = levels[i]; + } + + runs = malloc (sizeof (_raqm_bidi_run) * count); + + while (run_start < len) + { + size_t run_end = run_start; + while (run_end < len && levels[run_start] == levels[run_end]) + { + run_end++; + } + + runs[run_index].pos = run_start; + runs[run_index].level = levels[run_start]; + runs[run_index].len = run_end - run_start; + run_start = run_end; + run_index++; + } + + /* L2. Reorder. */ + for (level = max_level; level > 0; level--) + { + for (int i = count - 1; i >= 0; i--) + { + if (runs[i].level >= level) + { + int end = i; + for (i--; (i >= 0 && runs[i].level >= level); i--) + ; + _raqm_reverse_run (runs + i + 1, end - i); + } + } + } + + *run_count = count; + return runs; +} + +static bool +_raqm_itemize (raqm_t *rq) +{ + FriBidiParType par_type = FRIBIDI_PAR_ON; + FriBidiCharType *types; +#ifdef USE_FRIBIDI_EX_API + FriBidiBracketType *btypes; +#endif + FriBidiLevel *levels; + _raqm_bidi_run *runs = NULL; + raqm_run_t *last; + int max_level; + size_t run_count; + bool ok = true; + +#ifdef RAQM_TESTING + switch (rq->base_dir) + { + case RAQM_DIRECTION_RTL: + RAQM_TEST ("Direction is: RTL\n\n"); + break; + case RAQM_DIRECTION_LTR: + RAQM_TEST ("Direction is: LTR\n\n"); + break; + case RAQM_DIRECTION_TTB: + RAQM_TEST ("Direction is: TTB\n\n"); + break; + case RAQM_DIRECTION_DEFAULT: + default: + RAQM_TEST ("Direction is: DEFAULT\n\n"); + break; + } +#endif + + types = calloc (rq->text_len, sizeof (FriBidiCharType)); +#ifdef USE_FRIBIDI_EX_API + btypes = calloc (rq->text_len, sizeof (FriBidiBracketType)); +#endif + levels = calloc (rq->text_len, sizeof (FriBidiLevel)); + if (!types || !levels +#ifdef USE_FRIBIDI_EX_API + || !btypes +#endif + ) + { + ok = false; + goto done; + } + + if (rq->base_dir == RAQM_DIRECTION_RTL) + par_type = FRIBIDI_PAR_RTL; + else if (rq->base_dir == RAQM_DIRECTION_LTR) + par_type = FRIBIDI_PAR_LTR; + + if (rq->base_dir == RAQM_DIRECTION_TTB) + { + /* Treat every thing as LTR in vertical text */ + max_level = 1; + memset (types, FRIBIDI_TYPE_LTR, rq->text_len); + memset (levels, 0, rq->text_len); + rq->resolved_dir = RAQM_DIRECTION_LTR; + } + else + { + fribidi_get_bidi_types (rq->text, rq->text_len, types); +#ifdef USE_FRIBIDI_EX_API + fribidi_get_bracket_types (rq->text, rq->text_len, types, btypes); + max_level = fribidi_get_par_embedding_levels_ex (types, btypes, + rq->text_len, &par_type, + levels); +#else + max_level = fribidi_get_par_embedding_levels (types, rq->text_len, + &par_type, levels); +#endif + + if (par_type == FRIBIDI_PAR_LTR) + rq->resolved_dir = RAQM_DIRECTION_LTR; + else + rq->resolved_dir = RAQM_DIRECTION_RTL; + } + + if (max_level == 0) + { + ok = false; + goto done; + } + + if (!_raqm_resolve_scripts (rq)) + { + ok = false; + goto done; + } + + /* Get the number of bidi runs */ + runs = _raqm_reorder_runs (types, rq->text_len, par_type, levels, &run_count); + if (!runs) + { + ok = false; + goto done; + } + +#ifdef RAQM_TESTING + RAQM_TEST ("Number of runs before script itemization: %zu\n\n", run_count); + + RAQM_TEST ("Fribidi Runs:\n"); + for (size_t i = 0; i < run_count; i++) + { + RAQM_TEST ("run[%zu]:\t start: %zu\tlength: %zu\tlevel: %d\n", + i, runs[i].pos, runs[i].len, runs[i].level); + } + RAQM_TEST ("\n"); +#endif + + last = NULL; + for (size_t i = 0; i < run_count; i++) + { + raqm_run_t *run = calloc (1, sizeof (raqm_run_t)); + if (!run) + { + ok = false; + goto done; + } + + if (!rq->runs) + rq->runs = run; + + if (last) + last->next = run; + + run->direction = _raqm_hb_dir (rq, runs[i].level); + + if (HB_DIRECTION_IS_BACKWARD (run->direction)) + { + run->pos = runs[i].pos + runs[i].len - 1; + run->script = rq->text_info[run->pos].script; + run->font = _raqm_create_hb_font (rq, rq->text_info[run->pos].ftface); + for (int j = runs[i].len - 1; j >= 0; j--) + { + _raqm_text_info info = rq->text_info[runs[i].pos + j]; + if (!_raqm_compare_text_info (rq->text_info[run->pos], info)) + { + raqm_run_t *newrun = calloc (1, sizeof (raqm_run_t)); + if (!newrun) + { + ok = false; + goto done; + } + newrun->pos = runs[i].pos + j; + newrun->len = 1; + newrun->direction = _raqm_hb_dir (rq, runs[i].level); + newrun->script = info.script; + newrun->font = _raqm_create_hb_font (rq, info.ftface); + run->next = newrun; + run = newrun; + } + else + { + run->len++; + run->pos = runs[i].pos + j; + } + } + } + else + { + run->pos = runs[i].pos; + run->script = rq->text_info[run->pos].script; + run->font = _raqm_create_hb_font (rq, rq->text_info[run->pos].ftface); + for (size_t j = 0; j < runs[i].len; j++) + { + _raqm_text_info info = rq->text_info[runs[i].pos + j]; + if (!_raqm_compare_text_info (rq->text_info[run->pos], info)) + { + raqm_run_t *newrun = calloc (1, sizeof (raqm_run_t)); + if (!newrun) + { + ok = false; + goto done; + } + newrun->pos = runs[i].pos + j; + newrun->len = 1; + newrun->direction = _raqm_hb_dir (rq, runs[i].level); + newrun->script = info.script; + newrun->font = _raqm_create_hb_font (rq, info.ftface); + run->next = newrun; + run = newrun; + } + else + run->len++; + } + } + + last = run; + last->next = NULL; + } + +#ifdef RAQM_TESTING + run_count = 0; + for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) + run_count++; + RAQM_TEST ("Number of runs after script itemization: %zu\n\n", run_count); + + run_count = 0; + RAQM_TEST ("Final Runs:\n"); + for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) + { + SCRIPT_TO_STRING (run->script); + RAQM_TEST ("run[%zu]:\t start: %d\tlength: %d\tdirection: %s\tscript: %s\tfont: %s\n", + run_count++, run->pos, run->len, + hb_direction_to_string (run->direction), buff, + rq->text_info[run->pos].ftface->family_name); + } + RAQM_TEST ("\n"); +#endif + +done: + free (runs); + free (types); +#ifdef USE_FRIBIDI_EX_API + free (btypes); +#endif + free (levels); + + return ok; +} + +/* Stack to handle script detection */ +typedef struct { + size_t capacity; + size_t size; + int *pair_index; + hb_script_t *script; +} _raqm_stack_t; + +/* Special paired characters for script detection */ +static size_t paired_len = 34; +static const FriBidiChar paired_chars[] = +{ + 0x0028, 0x0029, /* ascii paired punctuation */ + 0x003c, 0x003e, + 0x005b, 0x005d, + 0x007b, 0x007d, + 0x00ab, 0x00bb, /* guillemets */ + 0x2018, 0x2019, /* general punctuation */ + 0x201c, 0x201d, + 0x2039, 0x203a, + 0x3008, 0x3009, /* chinese paired punctuation */ + 0x300a, 0x300b, + 0x300c, 0x300d, + 0x300e, 0x300f, + 0x3010, 0x3011, + 0x3014, 0x3015, + 0x3016, 0x3017, + 0x3018, 0x3019, + 0x301a, 0x301b +}; + +static void +_raqm_stack_free (_raqm_stack_t *stack) +{ + free (stack->script); + free (stack->pair_index); + free (stack); +} + +/* Stack handling functions */ +static _raqm_stack_t * +_raqm_stack_new (size_t max) +{ + _raqm_stack_t *stack; + stack = calloc (1, sizeof (_raqm_stack_t)); + if (!stack) + return NULL; + + stack->script = malloc (sizeof (hb_script_t) * max); + if (!stack->script) + { + _raqm_stack_free (stack); + return NULL; + } + + stack->pair_index = malloc (sizeof (int) * max); + if (!stack->pair_index) + { + _raqm_stack_free (stack); + return NULL; + } + + stack->size = 0; + stack->capacity = max; + + return stack; +} + +static bool +_raqm_stack_pop (_raqm_stack_t *stack) +{ + if (!stack->size) + { + RAQM_DBG ("Stack is Empty\n"); + return false; + } + + stack->size--; + + return true; +} + +static hb_script_t +_raqm_stack_top (_raqm_stack_t *stack) +{ + if (!stack->size) + { + RAQM_DBG ("Stack is Empty\n"); + return HB_SCRIPT_INVALID; /* XXX: check this */ + } + + return stack->script[stack->size]; +} + +static bool +_raqm_stack_push (_raqm_stack_t *stack, + hb_script_t script, + int pair_index) +{ + if (stack->size == stack->capacity) + { + RAQM_DBG ("Stack is Full\n"); + return false; + } + + stack->size++; + stack->script[stack->size] = script; + stack->pair_index[stack->size] = pair_index; + + return true; +} + +static int +_get_pair_index (const FriBidiChar ch) +{ + int lower = 0; + int upper = paired_len - 1; + + while (lower <= upper) + { + int mid = (lower + upper) / 2; + if (ch < paired_chars[mid]) + upper = mid - 1; + else if (ch > paired_chars[mid]) + lower = mid + 1; + else + return mid; + } + + return -1; +} + +#define STACK_IS_EMPTY(script) ((script)->size <= 0) +#define IS_OPEN(pair_index) (((pair_index) & 1) == 0) + +/* Resolve the script for each character in the input string, if the character + * script is common or inherited it takes the script of the character before it + * except paired characters which we try to make them use the same script. We + * then split the BiDi runs, if necessary, on script boundaries. + */ +static bool +_raqm_resolve_scripts (raqm_t *rq) +{ + int last_script_index = -1; + int last_set_index = -1; + hb_script_t last_script = HB_SCRIPT_INVALID; + _raqm_stack_t *stack = NULL; + hb_unicode_funcs_t* unicode_funcs = hb_unicode_funcs_get_default (); + + for (size_t i = 0; i < rq->text_len; ++i) + rq->text_info[i].script = hb_unicode_script (unicode_funcs, rq->text[i]); + +#ifdef RAQM_TESTING + RAQM_TEST ("Before script detection:\n"); + for (size_t i = 0; i < rq->text_len; ++i) + { + SCRIPT_TO_STRING (rq->text_info[i].script); + RAQM_TEST ("script for ch[%zu]\t%s\n", i, buff); + } + RAQM_TEST ("\n"); +#endif + + stack = _raqm_stack_new (rq->text_len); + if (!stack) + return false; + + for (int i = 0; i < (int) rq->text_len; i++) + { + if (rq->text_info[i].script == HB_SCRIPT_COMMON && last_script_index != -1) + { + int pair_index = _get_pair_index (rq->text[i]); + if (pair_index >= 0) + { + if (IS_OPEN (pair_index)) + { + /* is a paired character */ + rq->text_info[i].script = last_script; + last_set_index = i; + _raqm_stack_push (stack, rq->text_info[i].script, pair_index); + } + else + { + /* is a close paired character */ + /* find matching opening (by getting the last even index for current + * odd index) */ + while (!STACK_IS_EMPTY (stack) && + stack->pair_index[stack->size] != (pair_index & ~1)) + { + _raqm_stack_pop (stack); + } + if (!STACK_IS_EMPTY (stack)) + { + rq->text_info[i].script = _raqm_stack_top (stack); + last_script = rq->text_info[i].script; + last_set_index = i; + } + else + { + rq->text_info[i].script = last_script; + last_set_index = i; + } + } + } + else + { + rq->text_info[i].script = last_script; + last_set_index = i; + } + } + else if (rq->text_info[i].script == HB_SCRIPT_INHERITED && + last_script_index != -1) + { + rq->text_info[i].script = last_script; + last_set_index = i; + } + else + { + for (int j = last_set_index + 1; j < i; ++j) + rq->text_info[j].script = rq->text_info[i].script; + last_script = rq->text_info[i].script; + last_script_index = i; + last_set_index = i; + } + } + + /* Loop backwards and change any remaining Common or Inherit characters to + * take the script if the next character. + * https://github.com/HOST-Oman/libraqm/issues/95 + */ + for (int i = rq->text_len - 2; i >= 0; --i) + { + if (rq->text_info[i].script == HB_SCRIPT_INHERITED || + rq->text_info[i].script == HB_SCRIPT_COMMON) + rq->text_info[i].script = rq->text_info[i + 1].script; + } + +#ifdef RAQM_TESTING + RAQM_TEST ("After script detection:\n"); + for (size_t i = 0; i < rq->text_len; ++i) + { + SCRIPT_TO_STRING (rq->text_info[i].script); + RAQM_TEST ("script for ch[%zu]\t%s\n", i, buff); + } + RAQM_TEST ("\n"); +#endif + + _raqm_stack_free (stack); + + return true; +} + +static void +_raqm_ft_transform (int *x, + int *y, + FT_Matrix matrix) +{ + FT_Vector vector; + vector.x = *x; + vector.y = *y; + + FT_Vector_Transform (&vector, &matrix); + + *x = vector.x; + *y = vector.y; +} + +static bool +_raqm_shape (raqm_t *rq) +{ + hb_buffer_flags_t hb_buffer_flags = HB_BUFFER_FLAG_BOT | HB_BUFFER_FLAG_EOT; + +#if defined(HAVE_DECL_HB_BUFFER_FLAG_REMOVE_DEFAULT_IGNORABLES) && \ + HAVE_DECL_HB_BUFFER_FLAG_REMOVE_DEFAULT_IGNORABLES + if (rq->invisible_glyph < 0) + hb_buffer_flags |= HB_BUFFER_FLAG_REMOVE_DEFAULT_IGNORABLES; +#endif + + for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) + { + run->buffer = hb_buffer_create (); + + hb_buffer_add_utf32 (run->buffer, rq->text, rq->text_len, + run->pos, run->len); + hb_buffer_set_script (run->buffer, run->script); + hb_buffer_set_language (run->buffer, rq->text_info[run->pos].lang); + hb_buffer_set_direction (run->buffer, run->direction); + hb_buffer_set_flags (run->buffer, hb_buffer_flags); + +#ifdef HAVE_HB_BUFFER_SET_INVISIBLE_GLYPH + if (rq->invisible_glyph > 0) + hb_buffer_set_invisible_glyph (run->buffer, rq->invisible_glyph); +#endif + + hb_shape_full (run->font, run->buffer, rq->features, rq->features_len, + NULL); + +#ifdef HAVE_FT_GET_TRANSFORM + { + FT_Matrix matrix; + hb_glyph_position_t *pos; + unsigned int len; + + FT_Get_Transform (hb_ft_font_get_face (run->font), &matrix, NULL); + pos = hb_buffer_get_glyph_positions (run->buffer, &len); + for (unsigned int i = 0; i < len; i++) + { + _raqm_ft_transform (&pos[i].x_advance, &pos[i].y_advance, matrix); + _raqm_ft_transform (&pos[i].x_offset, &pos[i].y_offset, matrix); + } + } +#endif + } + + return true; +} + +/* Convert index from UTF-32 to UTF-8 */ +static uint32_t +_raqm_u32_to_u8_index (raqm_t *rq, + uint32_t index) +{ + FriBidiStrIndex length; + char *output = malloc ((sizeof (char) * 4 * index) + 1); + + length = fribidi_unicode_to_charset (FRIBIDI_CHAR_SET_UTF8, + rq->text, + index, + output); + + free (output); + return length; +} + +/* Convert index from UTF-8 to UTF-32 */ +static uint32_t +_raqm_u8_to_u32_index (raqm_t *rq, + uint32_t index) +{ + FriBidiStrIndex length; + uint32_t *output = malloc (sizeof (uint32_t) * (index + 1)); + + length = fribidi_charset_to_unicode (FRIBIDI_CHAR_SET_UTF8, + rq->text_utf8, + index, + output); + + free (output); + return length; +} + +static bool +_raqm_allowed_grapheme_boundary (hb_codepoint_t l_char, + hb_codepoint_t r_char); + +static bool +_raqm_in_hangul_syllable (hb_codepoint_t ch); + +/** + * raqm_index_to_position: + * @rq: a #raqm_t. + * @index: (inout): character index. + * @x: (out): output x position. + * @y: (out): output y position. + * + * Calculates the cursor position after the character at @index. If the character + * is right-to-left, then the cursor will be at the left of it, whereas if the + * character is left-to-right, then the cursor will be at the right of it. + * + * Return value: + * %true if the process was successful, %false otherwise. + * + * Since: 0.2 + */ +bool +raqm_index_to_position (raqm_t *rq, + size_t *index, + int *x, + int *y) +{ + /* We don't currently support multiline, so y is always 0 */ + *y = 0; + *x = 0; + + if (rq == NULL) + return false; + + if (rq->flags & RAQM_FLAG_UTF8) + *index = _raqm_u8_to_u32_index (rq, *index); + + if (*index >= rq->text_len) + return false; + + RAQM_TEST ("\n"); + + while (*index < rq->text_len) + { + if (_raqm_allowed_grapheme_boundary (rq->text[*index], rq->text[*index + 1])) + break; + + ++*index; + } + + for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) + { + size_t len; + hb_glyph_info_t *info; + hb_glyph_position_t *position; + len = hb_buffer_get_length (run->buffer); + info = hb_buffer_get_glyph_infos (run->buffer, NULL); + position = hb_buffer_get_glyph_positions (run->buffer, NULL); + + for (size_t i = 0; i < len; i++) + { + uint32_t curr_cluster = info[i].cluster; + uint32_t next_cluster = curr_cluster; + *x += position[i].x_advance; + + if (run->direction == HB_DIRECTION_LTR) + { + for (size_t j = i + 1; j < len && next_cluster == curr_cluster; j++) + next_cluster = info[j].cluster; + } + else + { + for (int j = i - 1; i != 0 && j >= 0 && next_cluster == curr_cluster; + j--) + next_cluster = info[j].cluster; + } + + if (next_cluster == curr_cluster) + next_cluster = run->pos + run->len; + + if (*index < next_cluster && *index >= curr_cluster) + { + if (run->direction == HB_DIRECTION_RTL) + *x -= position[i].x_advance; + *index = curr_cluster; + goto found; + } + } + } + +found: + if (rq->flags & RAQM_FLAG_UTF8) + *index = _raqm_u32_to_u8_index (rq, *index); + RAQM_TEST ("The position is %d at index %zu\n",*x ,*index); + return true; +} + +/** + * raqm_position_to_index: + * @rq: a #raqm_t. + * @x: x position. + * @y: y position. + * @index: (out): output character index. + * + * Returns the @index of the character at @x and @y position within text. + * If the position is outside the text, the last character is chosen as + * @index. + * + * Return value: + * %true if the process was successful, %false in case of error. + * + * Since: 0.2 + */ +bool +raqm_position_to_index (raqm_t *rq, + int x, + int y, + size_t *index) +{ + int delta_x = 0, current_x = 0; + (void)y; + + if (rq == NULL) + return false; + + if (x < 0) /* Get leftmost index */ + { + if (rq->resolved_dir == RAQM_DIRECTION_RTL) + *index = rq->text_len; + else + *index = 0; + return true; + } + + RAQM_TEST ("\n"); + + for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) + { + size_t len; + hb_glyph_info_t *info; + hb_glyph_position_t *position; + len = hb_buffer_get_length (run->buffer); + info = hb_buffer_get_glyph_infos (run->buffer, NULL); + position = hb_buffer_get_glyph_positions (run->buffer, NULL); + + for (size_t i = 0; i < len; i++) + { + delta_x = position[i].x_advance; + if (x < (current_x + delta_x)) + { + bool before = false; + if (run->direction == HB_DIRECTION_LTR) + before = (x < current_x + (delta_x / 2)); + else + before = (x > current_x + (delta_x / 2)); + + if (before) + *index = info[i].cluster; + else + { + uint32_t curr_cluster = info[i].cluster; + uint32_t next_cluster = curr_cluster; + if (run->direction == HB_DIRECTION_LTR) + for (size_t j = i + 1; j < len && next_cluster == curr_cluster; j++) + next_cluster = info[j].cluster; + else + for (int j = i - 1; i != 0 && j >= 0 && next_cluster == curr_cluster; + j--) + next_cluster = info[j].cluster; + + if (next_cluster == curr_cluster) + next_cluster = run->pos + run->len; + + *index = next_cluster; + } + if (_raqm_allowed_grapheme_boundary (rq->text[*index],rq->text[*index + 1])) + { + RAQM_TEST ("The start-index is %zu at position %d \n", *index, x); + return true; + } + + while (*index < (unsigned)run->pos + run->len) + { + if (_raqm_allowed_grapheme_boundary (rq->text[*index], + rq->text[*index + 1])) + { + *index += 1; + break; + } + *index += 1; + } + RAQM_TEST ("The start-index is %zu at position %d \n", *index, x); + return true; + } + else + current_x += delta_x; + } + } + + /* Get rightmost index*/ + if (rq->resolved_dir == RAQM_DIRECTION_RTL) + *index = 0; + else + *index = rq->text_len; + + RAQM_TEST ("The start-index is %zu at position %d \n", *index, x); + + return true; +} + +typedef enum +{ + RAQM_GRAPHEM_CR, + RAQM_GRAPHEM_LF, + RAQM_GRAPHEM_CONTROL, + RAQM_GRAPHEM_EXTEND, + RAQM_GRAPHEM_REGIONAL_INDICATOR, + RAQM_GRAPHEM_PREPEND, + RAQM_GRAPHEM_SPACING_MARK, + RAQM_GRAPHEM_HANGUL_SYLLABLE, + RAQM_GRAPHEM_OTHER +} _raqm_grapheme_t; + +static _raqm_grapheme_t +_raqm_get_grapheme_break (hb_codepoint_t ch, + hb_unicode_general_category_t category); + +static bool +_raqm_allowed_grapheme_boundary (hb_codepoint_t l_char, + hb_codepoint_t r_char) +{ + hb_unicode_general_category_t l_category; + hb_unicode_general_category_t r_category; + _raqm_grapheme_t l_grapheme, r_grapheme; + hb_unicode_funcs_t* unicode_funcs = hb_unicode_funcs_get_default (); + + l_category = hb_unicode_general_category (unicode_funcs, l_char); + r_category = hb_unicode_general_category (unicode_funcs, r_char); + l_grapheme = _raqm_get_grapheme_break (l_char, l_category); + r_grapheme = _raqm_get_grapheme_break (r_char, r_category); + + if (l_grapheme == RAQM_GRAPHEM_CR && r_grapheme == RAQM_GRAPHEM_LF) + return false; /*Do not break between a CR and LF GB3*/ + if (l_grapheme == RAQM_GRAPHEM_CONTROL || l_grapheme == RAQM_GRAPHEM_CR || + l_grapheme == RAQM_GRAPHEM_LF || r_grapheme == RAQM_GRAPHEM_CONTROL || + r_grapheme == RAQM_GRAPHEM_CR || r_grapheme == RAQM_GRAPHEM_LF) + return true; /*Break before and after CONTROL GB4, GB5*/ + if (r_grapheme == RAQM_GRAPHEM_HANGUL_SYLLABLE) + return false; /*Do not break Hangul syllable sequences. GB6, GB7, GB8*/ + if (l_grapheme == RAQM_GRAPHEM_REGIONAL_INDICATOR && + r_grapheme == RAQM_GRAPHEM_REGIONAL_INDICATOR) + return false; /*Do not break between regional indicator symbols. GB8a*/ + if (r_grapheme == RAQM_GRAPHEM_EXTEND) + return false; /*Do not break before extending characters. GB9*/ + /*Do not break before SpacingMarks, or after Prepend characters.GB9a, GB9b*/ + if (l_grapheme == RAQM_GRAPHEM_PREPEND) + return false; + if (r_grapheme == RAQM_GRAPHEM_SPACING_MARK) + return false; + return true; /*Otherwise, break everywhere. GB1, GB2, GB10*/ +} + +static _raqm_grapheme_t +_raqm_get_grapheme_break (hb_codepoint_t ch, + hb_unicode_general_category_t category) +{ + _raqm_grapheme_t gb_type; + + gb_type = RAQM_GRAPHEM_OTHER; + switch ((int)category) + { + case HB_UNICODE_GENERAL_CATEGORY_FORMAT: + if (ch == 0x200C || ch == 0x200D) + gb_type = RAQM_GRAPHEM_EXTEND; + else + gb_type = RAQM_GRAPHEM_CONTROL; + break; + + case HB_UNICODE_GENERAL_CATEGORY_CONTROL: + if (ch == 0x000D) + gb_type = RAQM_GRAPHEM_CR; + else if (ch == 0x000A) + gb_type = RAQM_GRAPHEM_LF; + else + gb_type = RAQM_GRAPHEM_CONTROL; + break; + + case HB_UNICODE_GENERAL_CATEGORY_SURROGATE: + case HB_UNICODE_GENERAL_CATEGORY_LINE_SEPARATOR: + case HB_UNICODE_GENERAL_CATEGORY_PARAGRAPH_SEPARATOR: + case HB_UNICODE_GENERAL_CATEGORY_UNASSIGNED: + if ((ch >= 0xFFF0 && ch <= 0xFFF8) || + (ch >= 0xE0000 && ch <= 0xE0FFF)) + gb_type = RAQM_GRAPHEM_CONTROL; + break; + + case HB_UNICODE_GENERAL_CATEGORY_NON_SPACING_MARK: + case HB_UNICODE_GENERAL_CATEGORY_ENCLOSING_MARK: + case HB_UNICODE_GENERAL_CATEGORY_SPACING_MARK: + if (ch != 0x102B && ch != 0x102C && ch != 0x1038 && + (ch < 0x1062 || ch > 0x1064) && (ch < 0x1067 || ch > 0x106D) && + ch != 0x1083 && (ch < 0x1087 || ch > 0x108C) && ch != 0x108F && + (ch < 0x109A || ch > 0x109C) && ch != 0x1A61 && ch != 0x1A63 && + ch != 0x1A64 && ch != 0xAA7B && ch != 0xAA70 && ch != 0x11720 && + ch != 0x11721) /**/ + gb_type = RAQM_GRAPHEM_SPACING_MARK; + + else if (ch == 0x09BE || ch == 0x09D7 || + ch == 0x0B3E || ch == 0x0B57 || ch == 0x0BBE || ch == 0x0BD7 || + ch == 0x0CC2 || ch == 0x0CD5 || ch == 0x0CD6 || + ch == 0x0D3E || ch == 0x0D57 || ch == 0x0DCF || ch == 0x0DDF || + ch == 0x1D165 || (ch >= 0x1D16E && ch <= 0x1D172)) + gb_type = RAQM_GRAPHEM_EXTEND; + break; + + case HB_UNICODE_GENERAL_CATEGORY_OTHER_LETTER: + if (ch == 0x0E33 || ch == 0x0EB3) + gb_type = RAQM_GRAPHEM_SPACING_MARK; + break; + + case HB_UNICODE_GENERAL_CATEGORY_OTHER_SYMBOL: + if (ch >= 0x1F1E6 && ch <= 0x1F1FF) + gb_type = RAQM_GRAPHEM_REGIONAL_INDICATOR; + break; + + default: + gb_type = RAQM_GRAPHEM_OTHER; + break; + } + + if (_raqm_in_hangul_syllable (ch)) + gb_type = RAQM_GRAPHEM_HANGUL_SYLLABLE; + + return gb_type; +} + +static bool +_raqm_in_hangul_syllable (hb_codepoint_t ch) +{ + (void)ch; + return false; +} + +/** + * raqm_version: + * @major: (out): Library major version component. + * @minor: (out): Library minor version component. + * @micro: (out): Library micro version component. + * + * Returns library version as three integer components. + * + * Since: 0.7 + **/ +void +raqm_version (unsigned int *major, + unsigned int *minor, + unsigned int *micro) +{ + *major = RAQM_VERSION_MAJOR; + *minor = RAQM_VERSION_MINOR; + *micro = RAQM_VERSION_MICRO; +} + +/** + * raqm_version_string: + * + * Returns library version as a string with three components. + * + * Return value: library version string. + * + * Since: 0.7 + **/ +const char * +raqm_version_string (void) +{ + return RAQM_VERSION_STRING; +} + +/** + * raqm_version_atleast: + * @major: Library major version component. + * @minor: Library minor version component. + * @micro: Library micro version component. + * + * Checks if library version is less than or equal the specified version. + * + * Return value: + * %true if library version is less than or equal the specfied version, %false + * otherwise. + * + * Since: 0.7 + **/ +bool +raqm_version_atleast (unsigned int major, + unsigned int minor, + unsigned int micro) +{ + return RAQM_VERSION_ATLEAST (major, minor, micro); +} + +/** + * RAQM_VERSION_ATLEAST: + * @major: Library major version component. + * @minor: Library minor version component. + * @micro: Library micro version component. + * + * Checks if library version is less than or equal the specified version. + * + * Return value: + * %true if library version is less than or equal the specfied version, %false + * otherwise. + * + * Since: 0.7 + **/ + +/** + * RAQM_VERSION_STRING: + * + * Library version as a string with three components. + * + * Since: 0.7 + **/ + +/** + * RAQM_VERSION_MAJOR: + * + * Library major version component. + * + * Since: 0.7 + **/ + +/** + * RAQM_VERSION_MINOR: + * + * Library minor version component. + * + * Since: 0.7 + **/ + +/** + * RAQM_VERSION_MICRO: + * + * Library micro version component. + * + * Since: 0.7 + **/ diff --git a/src/libImaging/raqm.h b/src/thirdparty/raqm/raqm.h similarity index 84% rename from src/libImaging/raqm.h rename to src/thirdparty/raqm/raqm.h index eae1f43b7ea..342afc8b29b 100644 --- a/src/libImaging/raqm.h +++ b/src/thirdparty/raqm/raqm.h @@ -24,17 +24,18 @@ #ifndef _RAQM_H_ #define _RAQM_H_ +#define _RAQM_H_IN_ #ifdef HAVE_CONFIG_H #include "config.h" #endif -#ifndef bool -typedef int bool; -#endif -#ifndef uint32_t -typedef UINT32 uint32_t; +#ifndef RAQM_API +#define RAQM_API #endif + +#include +#include #include #include FT_FREETYPE_H @@ -42,6 +43,8 @@ typedef UINT32 uint32_t; extern "C" { #endif +#include "raqm-version.h" + /** * raqm_t: * @@ -94,87 +97,93 @@ typedef struct raqm_glyph_t { FT_Face ftface; } raqm_glyph_t; -/** - * version 0.1 of the raqm_glyph_t structure - */ -typedef struct raqm_glyph_t_01 { - unsigned int index; - int x_advance; - int y_advance; - int x_offset; - int y_offset; - uint32_t cluster; -} raqm_glyph_t_01; - - -raqm_t * +RAQM_API raqm_t * raqm_create (void); -raqm_t * +RAQM_API raqm_t * raqm_reference (raqm_t *rq); -void +RAQM_API void raqm_destroy (raqm_t *rq); -bool +RAQM_API bool raqm_set_text (raqm_t *rq, const uint32_t *text, size_t len); -bool +RAQM_API bool raqm_set_text_utf8 (raqm_t *rq, const char *text, size_t len); -bool +RAQM_API bool raqm_set_par_direction (raqm_t *rq, raqm_direction_t dir); -bool +RAQM_API bool raqm_set_language (raqm_t *rq, const char *lang, size_t start, size_t len); -bool +RAQM_API bool raqm_add_font_feature (raqm_t *rq, const char *feature, int len); -bool +RAQM_API bool raqm_set_freetype_face (raqm_t *rq, FT_Face face); -bool +RAQM_API bool raqm_set_freetype_face_range (raqm_t *rq, FT_Face face, size_t start, size_t len); -bool +RAQM_API bool raqm_set_freetype_load_flags (raqm_t *rq, int flags); -bool +RAQM_API bool +raqm_set_invisible_glyph (raqm_t *rq, + int gid); + +RAQM_API bool raqm_layout (raqm_t *rq); -raqm_glyph_t * +RAQM_API raqm_glyph_t * raqm_get_glyphs (raqm_t *rq, size_t *length); -bool +RAQM_API bool raqm_index_to_position (raqm_t *rq, size_t *index, int *x, int *y); -bool +RAQM_API bool raqm_position_to_index (raqm_t *rq, int x, int y, size_t *index); +RAQM_API void +raqm_version (unsigned int *major, + unsigned int *minor, + unsigned int *micro); + +RAQM_API const char * +raqm_version_string (void); + +RAQM_API bool +raqm_version_atleast (unsigned int major, + unsigned int minor, + unsigned int micro); + + #ifdef __cplusplus } #endif +#undef _RAQM_H_IN_ #endif /* _RAQM_H_ */ diff --git a/tox.ini b/tox.ini index 9f310ca3a05..bdedc2bd5dd 100644 --- a/tox.ini +++ b/tox.ini @@ -1,18 +1,18 @@ # 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{37,38,39,310,py3} minversion = 1.9 [testenv] commands = - {envpython} setup.py clean - {envpython} setup.py build_ext --inplace + make clean + {envpython} -m pip install --global-option="build_ext" --global-option="--inplace" . {envpython} selftest.py {envpython} -m pytest -W always {posargs} deps = @@ -24,9 +24,10 @@ deps = [testenv:lint] commands = - pre-commit run --all-files + pre-commit run --all-files --show-diff-on-failure check-manifest deps = pre-commit check-manifest skip_install = true +passenv = PRE_COMMIT_COLOR diff --git a/winbuild/README.md b/winbuild/README.md index 471b61a574f..611d1ed1a89 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 489f9411eac..00000000000 --- 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 4cc01082d98..00000000000 --- 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 75a22ca59e4..00000000000 --- 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 e565226bd92..00000000000 --- 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 1d20840447a..b30a94226d7 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 -^^^^^^^ +Compilers +^^^^^^^^^ -The build routines expect Python to be installed at C:\PythonXX for -32-bit versions or C:\PythonXXx64 for the 64-bit versions. +Download and install: -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.) +* `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) -Download the rest of the Pythons by opening a command window, changing -to the ``winbuild`` directory, and running ``python -get_pythons.py``. +* `CMake 3.12 or newer `_ + (also available as Visual Studio component C++ CMake tools for Windows) -UNDONE -- gpg verify the signatures (note that we can download from -https) +* `NASM `_ -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. +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``. -Compilers -^^^^^^^^^ +Build configuration +------------------- -Download and install: +The following environment variables, if set, will override the default +behaviour of ``build_prepare.py``: -* `Microsoft Windows SDK for Windows 7 and .NET Framework - 4 `_ +* ``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. -* `CMake-2.8.10.2-win32-x86.exe - `_ +``build_prepare.py`` also supports the following command line parameters: -The samples and the .NET SDK portions aren't required, just the -compilers and other tools. UNDONE -- check exact wording. +* ``-v`` will print generated scripts. +* ``--no-imagequant`` will skip GPL-licensed ``libimagequant`` optional dependency +* ``--no-fribidi`` or ``--no-raqm`` will skip optional LGPL-licensed dependency FriBiDi + (required for Raqm text shaping). +* ``--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. ``fribidi.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 77857013938..00000000000 --- 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 00000000000..0589baf2136 --- /dev/null +++ b/winbuild/build_prepare.py @@ -0,0 +1,584 @@ +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.1.2/libjpeg-turbo-2.1.2.tar.gz", + "filename": "libjpeg-turbo-2.1.2.tar.gz", + "dir": "libjpeg-turbo-2.1.2", + "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.3.0.tar.gz", + "filename": "tiff-4.3.0.tar.gz", + "dir": "tiff-4.3.0", + "build": [ + cmd_cmake("-DBUILD_SHARED_LIBS:BOOL=OFF"), + cmd_nmake(target="clean"), + cmd_nmake(target="tiff"), + ], + "headers": [r"libtiff\tiff*.h"], + "libs": [r"libtiff\*.lib"], + # "bins": [r"libtiff\*.dll"], + }, + "libwebp": { + "url": "http://downloads.webmproject.org/releases/webp/libwebp-1.2.1.tar.gz", + "filename": "libwebp-1.2.1.tar.gz", + "dir": "libwebp-1.2.1", + "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.11.1.tar.gz", # noqa: E501 + "filename": "freetype-2.11.1.tar.gz", + "dir": "freetype-2.11.1", + "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.12/lcms2-2.12.tar.gz", + "filename": "lcms2-2.12.tar.gz", + "dir": "lcms2-2.12", + "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:Rebuild" + ), + 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": { + # commit: Merge branch 'master' into msvc (matches 2.17.0 tag) + "url": "https://github.com/ImageOptim/libimagequant/archive/e4c1334be0eff290af5e2b4155057c2953a313ab.zip", # noqa: E501 + "filename": "libimagequant-e4c1334be0eff290af5e2b4155057c2953a313ab.zip", + "dir": "libimagequant-e4c1334be0eff290af5e2b4155057c2953a313ab", + "patch": { + "CMakeLists.txt": { + "if(OPENMP_FOUND)": "if(false)", + "install": "#install", + } + }, + "build": [ + # lint: do not inline + cmd_cmake(), + cmd_nmake(target="clean"), + cmd_nmake(target="imagequant_a"), + cmd_copy("imagequant_a.lib", "imagequant.lib"), + ], + "headers": [r"*.h"], + "libs": [r"imagequant.lib"], + }, + "harfbuzz": { + "url": "https://github.com/harfbuzz/harfbuzz/archive/3.2.0.zip", + "filename": "harfbuzz-3.2.0.zip", + "dir": "harfbuzz-3.2.0", + "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.11.zip", + "filename": "fribidi-1.0.11.zip", + "dir": "fribidi-1.0.11", + "build": [ + cmd_copy(r"{winbuild_dir}\fribidi.cmake", r"CMakeLists.txt"), + cmd_cmake(), + cmd_nmake(target="clean"), + cmd_nmake(target="fribidi"), + ], + "bins": [r"*.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: + print(f"Patching {patch_file}") + 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 + r'"{python_dir}\{python_exe}" setup.py build_ext --vendor-raqm --vendor-fribidi %*', # noqa: E501 + ] + + 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" or arg == "--no-fribidi": + disabled += ["fribidi"] + 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 93413d1e573..00000000000 --- 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.4.zip", - "filename": "harfbuzz-2.6.4.zip", - "dir": "harfbuzz-2.6.4", - }, - "fribidi": { - "url": "https://github.com/fribidi/fribidi/archive/v1.0.9.zip", - "filename": "fribidi-1.0.9.zip", - "dir": "fribidi-1.0.9", - }, - "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 adc45429a44..00000000000 --- 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 247e79e4cdd..27b8d17a8ed 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) @@ -93,10 +93,10 @@ fribidi_tab(brackets-type unidata/BidiBrackets.txt) file(GLOB FRIBIDI_SOURCES lib/*.c) file(GLOB FRIBIDI_HEADERS lib/*.h) -add_library(fribidi STATIC +add_library(fribidi SHARED ${FRIBIDI_SOURCES} ${FRIBIDI_HEADERS} ${FRIBIDI_SOURCES_GENERATED}) fribidi_definitions(fribidi) target_compile_definitions(fribidi - PUBLIC -DFRIBIDI_ENTRY=extern) + PUBLIC "-DFRIBIDI_BUILD") diff --git a/winbuild/get_pythons.py b/winbuild/get_pythons.py deleted file mode 100644 index a853fc6f7ea..00000000000 --- 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 7fc48c034e5..00000000000 --- 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 deleted file mode 100644 index 88eb7f28479..00000000000 --- a/winbuild/raqm.cmake +++ /dev/null @@ -1,39 +0,0 @@ -cmake_minimum_required(VERSION 3.13) - -project(libraqm) - - -find_library(fribidi NAMES fribidi) -find_library(harfbuzz NAMES harfbuzz) -find_library(freetype NAMES freetype) - -add_definitions(-DFRIBIDI_ENTRY=extern) - - -function(raqm_conf) - file(READ configure.ac RAQM_CONF) - string(REGEX MATCH "\\[([0-9]+)\\.([0-9]+)\\.([0-9]+)\\]," _ "${RAQM_CONF}") - set(RAQM_VERSION_MAJOR "${CMAKE_MATCH_1}") - set(RAQM_VERSION_MINOR "${CMAKE_MATCH_2}") - set(RAQM_VERSION_MICRO "${CMAKE_MATCH_3}") - set(RAQM_VERSION "${RAQM_VERSION_MAJOR}.${RAQM_VERSION_MINOR}.${RAQM_VERSION_MICRO}") - message("detected libraqm version ${RAQM_VERSION}") - configure_file(src/raqm-version.h.in src/raqm-version.h @ONLY) -endfunction() -raqm_conf() - - -set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) -set(RAQM_SOURCES - src/raqm.c) -set(RAQM_HEADERS - src/raqm.h - src/raqm-version.h) - -add_library(libraqm SHARED - ${RAQM_SOURCES} - ${RAQM_HEADERS}) -target_link_libraries(libraqm - ${fribidi} - ${harfbuzz} - ${freetype}) diff --git a/winbuild/test.py b/winbuild/test.py deleted file mode 100755 index a05a20b1892..00000000000 --- 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 deleted file mode 100644 index 16acabc26de..00000000000 --- a/winbuild/tiff.opt +++ /dev/null @@ -1,216 +0,0 @@ -# $Id: nmake.opt,v 1.18 2006/06/07 16:33:45 dron Exp $ -# -# Copyright (C) 2004, Andrey Kiselev -# -# Permission to use, copy, modify, distribute, and sell this software and -# its documentation for any purpose is hereby granted without fee, provided -# that (i) the above copyright notices and this permission notice appear in -# all copies of the software and related documentation, and (ii) the names of -# Sam Leffler and Silicon Graphics may not be used in any advertising or -# publicity relating to the software without the specific, prior written -# permission of Sam Leffler and Silicon Graphics. -# -# THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, -# EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY -# WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. -# -# IN NO EVENT SHALL SAM LEFFLER OR SILICON GRAPHICS BE LIABLE FOR -# ANY SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, -# OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, -# WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF -# LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE -# OF THIS SOFTWARE. - -# Compile time parameters for MS Visual C++ compiler. -# You may edit this file to specify building options. - -# -###### Edit the following lines to choose a feature set you need. ####### -# - -# -# Select WINMODE_CONSOLE to build a library which reports errors to stderr, or -# WINMODE_WINDOWED to build such that errors are reported via MessageBox(). -# -WINMODE_CONSOLE = 1 -#WINMODE_WINDOWED = 1 - -# -# Comment out the following lines to disable internal codecs. -# -# Support for CCITT Group 3 & 4 algorithms -CCITT_SUPPORT = 1 -# Support for Macintosh PackBits algorithm -PACKBITS_SUPPORT = 1 -# Support for LZW algorithm -LZW_SUPPORT = 1 -# Support for ThunderScan 4-bit RLE algorithm -THUNDER_SUPPORT = 1 -# Support for NeXT 2-bit RLE algorithm -NEXT_SUPPORT = 1 -# Support for LogLuv high dynamic range encoding -LOGLUV_SUPPORT = 1 - -# -# Uncomment and edit following lines to enable JPEG support. -# -JPEG_SUPPORT = 1 -JPEG_INCLUDE = -I$(INCLIB) -JPEG_LIB = $(INCLIB)/libjpeg.lib - -# -# Uncomment and edit following lines to enable ZIP support -# (required for Deflate compression and Pixar log-format) -# -ZIP_SUPPORT = 1 -ZLIB_INCLUDE = -I$(INCLIB) -ZLIB_LIB = $(INCLIB)/zlib.lib - -# -# Uncomment and edit following lines to enable ISO JBIG support -# -#JBIG_SUPPORT = 1 -#JBIGDIR = d:/projects/jbigkit -#JBIG_INCLUDE = -I$(JBIGDIR)/libjbig -#JBIG_LIB = $(JBIGDIR)/libjbig/jbig.lib - -# -# Uncomment following line to enable Pixar log-format algorithm -# (Zlib required). -# -#PIXARLOG_SUPPORT = 1 - -# -# Comment out the following lines to disable strip chopping -# (whether or not to convert single-strip uncompressed images to multiple -# strips of specified size to reduce memory usage). Default strip size -# is 8192 bytes, it can be configured via the STRIP_SIZE_DEFAULT parameter -# -STRIPCHOP_SUPPORT = 1 -STRIP_SIZE_DEFAULT = 8192 - -# -# Comment out the following lines to disable treating the fourth sample with -# no EXTRASAMPLE_ value as being ASSOCALPHA. Many packages produce RGBA -# files but don't mark the alpha properly. -# -EXTRASAMPLE_AS_ALPHA_SUPPORT = 1 - -# -# Comment out the following lines to disable picking up YCbCr subsampling -# info from the JPEG data stream to support files lacking the tag. -# See Bug 168 in Bugzilla, and JPEGFixupTestSubsampling() for details. -# -CHECK_JPEG_YCBCR_SUBSAMPLING = 1 - -# -####################### Compiler related options. ####################### -# - -# -# Pick debug or optimized build flags. We default to an optimized build -# with no debugging information. -# NOTE: /EHsc option required if you want to build the C++ stream API -# -OPTFLAGS = /Ox /MD /EHsc /W3 /D_CRT_SECURE_NO_DEPRECATE -#OPTFLAGS = /Zi - -# -# Uncomment following line to enable using Windows Common RunTime Library -# instead of Windows specific system calls. See notes on top of tif_unix.c -# module for details. -# -USE_WIN_CRT_LIB = 1 - -# Compiler specific options. You may probably want to adjust compilation -# parameters in CFLAGS variable. Refer to your compiler documentation -# for the option reference. -# -MAKE = nmake /nologo -CC = cl /nologo -CXX = cl /nologo -AR = lib /nologo -LD = link /nologo - -CFLAGS = $(OPTFLAGS) $(INCL) $(EXTRAFLAGS) -CXXFLAGS = $(OPTFLAGS) $(INCL) $(EXTRAFLAGS) -EXTRAFLAGS = -LIBS = - -# Name of the output shared library -DLLNAME = libtiff.dll - -# -########### There is nothing to edit below this line normally. ########### -# - -# Set the native cpu bit order -EXTRAFLAGS = -DFILLODER_LSB2MSB $(EXTRAFLAGS) - -!IFDEF WINMODE_WINDOWED -EXTRAFLAGS = -DTIF_PLATFORM_WINDOWED $(EXTRAFLAGS) -LIBS = user32.lib $(LIBS) -!ELSE -EXTRAFLAGS = -DTIF_PLATFORM_CONSOLE $(EXTRAFLAGS) -!ENDIF - -# Codec stuff -!IFDEF CCITT_SUPPORT -EXTRAFLAGS = -DCCITT_SUPPORT $(EXTRAFLAGS) -!ENDIF - -!IFDEF PACKBITS_SUPPORT -EXTRAFLAGS = -DPACKBITS_SUPPORT $(EXTRAFLAGS) -!ENDIF - -!IFDEF LZW_SUPPORT -EXTRAFLAGS = -DLZW_SUPPORT $(EXTRAFLAGS) -!ENDIF - -!IFDEF THUNDER_SUPPORT -EXTRAFLAGS = -DTHUNDER_SUPPORT $(EXTRAFLAGS) -!ENDIF - -!IFDEF NEXT_SUPPORT -EXTRAFLAGS = -DNEXT_SUPPORT $(EXTRAFLAGS) -!ENDIF - -!IFDEF LOGLUV_SUPPORT -EXTRAFLAGS = -DLOGLUV_SUPPORT $(EXTRAFLAGS) -!ENDIF - -!IFDEF JPEG_SUPPORT -LIBS = $(LIBS) $(JPEG_LIB) -EXTRAFLAGS = -DJPEG_SUPPORT -DOJPEG_SUPPORT $(EXTRAFLAGS) -!ENDIF - -!IFDEF ZIP_SUPPORT -LIBS = $(LIBS) $(ZLIB_LIB) -EXTRAFLAGS = -DZIP_SUPPORT $(EXTRAFLAGS) -!IFDEF PIXARLOG_SUPPORT -EXTRAFLAGS = -DPIXARLOG_SUPPORT $(EXTRAFLAGS) -!ENDIF -!ENDIF - -!IFDEF JBIG_SUPPORT -LIBS = $(LIBS) $(JBIG_LIB) -EXTRAFLAGS = -DJBIG_SUPPORT $(EXTRAFLAGS) -!ENDIF - -!IFDEF STRIPCHOP_SUPPORT -EXTRAFLAGS = -DSTRIPCHOP_DEFAULT=TIFF_STRIPCHOP -DSTRIP_SIZE_DEFAULT=$(STRIP_SIZE_DEFAULT) $(EXTRAFLAGS) -!ENDIF - -!IFDEF EXTRASAMPLE_AS_ALPHA_SUPPORT -EXTRAFLAGS = -DDEFAULT_EXTRASAMPLE_AS_ALPHA $(EXTRAFLAGS) -!ENDIF - -!IFDEF CHECK_JPEG_YCBCR_SUBSAMPLING -EXTRAFLAGS = -DCHECK_JPEG_YCBCR_SUBSAMPLING $(EXTRAFLAGS) -!ENDIF - -!IFDEF USE_WIN_CRT_LIB -EXTRAFLAGS = -DAVOID_WIN32_FILEIO $(EXTRAFLAGS) -!ELSE -EXTRAFLAGS = -DUSE_WIN32_FILEIO $(EXTRAFLAGS) -!ENDIF diff --git a/winbuild/untar.py b/winbuild/untar.py deleted file mode 100644 index f2713b2f27d..00000000000 --- 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 eb17a2e6330..00000000000 --- 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])