├── .cirrus.star ├── .gitattributes ├── .github ├── .codecov.yml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── config.yml │ └── feature_request.yml ├── PULL_REQUEST_TEMPLATE.md ├── SECURITY.md ├── renovate.json ├── transform_to-pi_heif.py └── workflows │ ├── analysis-coverage.yml │ ├── close-stale.yml │ ├── nightly-src-build.yml │ ├── publish-wheels.yml │ ├── test-src-build-linux.yml │ ├── test-src-build-macos.yml │ ├── test-src-build-windows.yml │ ├── test-wheels-pi_heif.yml │ ├── test-wheels.yml │ ├── wheels-pi_heif.yml │ └── wheels-pillow_heif.yml ├── .gitignore ├── .pre-commit-config.yaml ├── .readthedocs.yml ├── CHANGELOG.md ├── LICENSE.txt ├── LICENSES_bundled.txt ├── MANIFEST.in ├── README.md ├── benchmarks ├── benchmark_decode.py ├── benchmark_encode.py ├── image_large.heic ├── measure_decode.py ├── measure_encode.py ├── results_decode_numpy_bgr_Linux.png ├── results_decode_numpy_bgr_Windows.png ├── results_decode_numpy_bgr_macOS.png ├── results_decode_numpy_rgb_Linux.png ├── results_decode_numpy_rgb_Windows.png ├── results_decode_numpy_rgb_macOS.png ├── results_decode_pillow_load_Linux.png ├── results_decode_pillow_load_Windows.png ├── results_decode_pillow_load_macOS.png ├── results_encode_Linux.png ├── results_encode_Windows.png └── results_encode_macOS.png ├── ci └── cirrus_general_ci.yml ├── docker ├── from_src │ ├── Almalinux_9.Dockerfile │ ├── Alpine_3_20.Dockerfile │ ├── Alpine_3_21.Dockerfile │ └── Debian_12.Dockerfile └── test_wheels.Dockerfile ├── docs ├── Makefile ├── benchmarks.rst ├── conf.py ├── heif-file.rst ├── image-modes.rst ├── index.rst ├── installation.rst ├── make.bat ├── options.rst ├── pillow-plugin.rst ├── reference │ ├── API.rst │ ├── HeifFile.rst │ ├── HeifImage.rst │ ├── HeifImagePlugin.rst │ ├── constants.rst │ ├── index.rst │ └── links.rst ├── resources │ ├── css │ │ ├── dark.css │ │ ├── light.css │ │ └── styles.css │ ├── js │ │ └── script.js │ └── pillow-heif-logo.png ├── saving-images.rst └── workaround-orientation.rst ├── examples ├── heif_create_sequence.py ├── heif_dump_info.py ├── heif_enumerate.py ├── heif_remove_image.py ├── heif_save.py ├── pillow_create_sequence.py ├── pillow_dump_info.py ├── pillow_enumerate.py ├── pillow_remove_image.py └── pillow_save.py ├── libheif ├── heif.h ├── linux_build_libs.py ├── macos │ └── libheif.rb └── windows │ └── mingw-w64-libheif │ └── PKGBUILD ├── pi-heif ├── LICENSES_bundled.txt ├── README.md ├── libheif │ ├── macos │ │ └── libheif.rb │ └── windows │ │ └── mingw-w64-libheif │ │ └── PKGBUILD └── setup.cfg ├── pillow_heif ├── HeifImagePlugin.py ├── __init__.py ├── _deffered_error.py ├── _lib_info.py ├── _ph_postprocess.h ├── _pillow_heif.c ├── _version.py ├── as_plugin.py ├── constants.py ├── heif.py ├── misc.py └── options.py ├── pyproject.toml ├── setup.cfg ├── setup.py └── tests ├── basic_test.py ├── dataset.py ├── helpers.py ├── images ├── heif │ ├── LA_8__128x128.heif │ ├── LA_8__29x100.heif │ ├── L_10__128x128.heif │ ├── L_10__29x100.heif │ ├── L_12__128x128.heif │ ├── L_12__29x100.heif │ ├── L_8__128x128.heif │ ├── L_8__29x100.heif │ ├── L_xmp.heif │ ├── RGBA_10__128x128.heif │ ├── RGBA_10__29x100.heif │ ├── RGBA_12__128x128.heif │ ├── RGBA_12__29x100.heif │ ├── RGBA_8__128x128.heif │ ├── RGBA_8__29x100.heif │ ├── RGB_10__128x128.heif │ ├── RGB_10__29x100.heif │ ├── RGB_12__128x128.heif │ ├── RGB_12__29x100.heif │ ├── RGB_8__128x128.heif │ ├── RGB_8__29x100.heif │ └── zPug_3.heic ├── heif_corrupted │ ├── corrupted.heic │ ├── corrupted.hif │ └── empty.heic ├── heif_other │ ├── L_exif_xmp_iptc.heic │ ├── L_xmp_latin1.heic │ ├── RGB_8_chroma444.heif │ ├── arrow.heic │ ├── cat.hif │ ├── empty_icc.heic │ ├── invalid_id.heic │ ├── nokia │ │ ├── COPYRIGHT.txt │ │ ├── README.txt │ │ ├── alpha.heic │ │ └── bird_burst.heic │ ├── pug.heic │ └── spatial_photo.heic ├── heif_special │ ├── 200MP.heic │ ├── L_8__128(64)x128(64).heif │ ├── L_8__29(255)x100.heif │ ├── L_8__29x100(100x29).heif │ ├── L_8__29x100(255).heif │ ├── guitar_cw90.hif │ └── xiaomi.heic ├── heif_truncated │ └── truncated.heic └── non_heif │ ├── LA_16__128x128.png │ ├── LA_16__29x100.png │ ├── LA_8__128x128.png │ ├── LA_8__29x100.png │ ├── L_16__128x128.png │ ├── L_16__29x100.png │ ├── L_8__128x128.png │ ├── L_8__29x100.png │ ├── RGBA_16__128x128.png │ ├── RGBA_16__29x100.png │ ├── RGBA_8__128x128.png │ ├── RGBA_8__29x100.png │ ├── RGB_16__128x128.png │ ├── RGB_16__29x100.png │ ├── RGB_8__128x128.png │ ├── RGB_8__29x100.png │ ├── chi.gif │ ├── pug_depth.png │ ├── xmp.jpeg │ ├── xmp.png │ ├── xmp.tiff │ └── xmp.webp ├── import_error_test.py ├── leaks_test.py ├── metadata_etc_test.py ├── metadata_exif_test.py ├── metadata_xmp_test.py ├── modes_test.py ├── numpy_test.py ├── opencv_bug.py ├── opencv_test.py ├── options_test.py ├── orientation_test.py ├── read_test.py ├── thumbnails_test.py └── write_test.py /.cirrus.star: -------------------------------------------------------------------------------- 1 | # The guide to programming cirrus-ci tasks using starlark is found at https://cirrus-ci.org/guide/programming-tasks/ 2 | # 3 | # In this simple starlark script we simply check conditions for whether a CI run should go ahead. 4 | # If the conditions are met, then we just return the yaml containing the tasks to be run. 5 | 6 | load("cirrus", "env", "fs", "http") 7 | 8 | def main(ctx): 9 | 10 | if env.get("CIRRUS_REPO_FULL_NAME") != "bigcat88/pillow_heif": 11 | return [] 12 | 13 | # Obtain commit message for the event. Unfortunately CIRRUS_CHANGE_MESSAGE 14 | # only contains the actual commit message on a non-PR trigger event. 15 | # For a PR event it contains the PR title and description. 16 | SHA = env.get("CIRRUS_CHANGE_IN_REPO") 17 | url = "https://api.github.com/repos/bigcat88/pillow_heif/git/commits/" + SHA 18 | dct = http.get(url).json() 19 | if "[skip cirrus]" in dct["message"] or "[skip ci]" in dct["message"]: 20 | return [] 21 | 22 | # this configuration(default) runs FreeBSD build from source. 23 | return fs.read("ci/cirrus_general_ci.yml") 24 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Declare files that always have LF line endings on checkout 2 | * text eol=lf 3 | 4 | # Denote all files that are truly binary and should not be modified 5 | *.heif binary 6 | *.heic binary 7 | *.hif binary 8 | *.avif binary 9 | *.png binary 10 | *.gif binary 11 | *.webp binary 12 | *.tiff binary 13 | *.jpeg binary 14 | *.jpg binary 15 | 16 | # Files to exclude from GitHub Languages statistics 17 | *.h linguist-vendored=true 18 | *.Dockerfile linguist-vendored=true 19 | -------------------------------------------------------------------------------- /.github/.codecov.yml: -------------------------------------------------------------------------------- 1 | codecov: 2 | notify: 3 | after_n_builds: 5 4 | 5 | comment: 6 | require_changes: true 7 | layout: "diff, files" 8 | 9 | coverage: 10 | status: 11 | project: 12 | default: 13 | target: auto 14 | threshold: 2% 15 | patch: off 16 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | Be openness, as well as friendly and didactic in discussions. 4 | 5 | Treat everybody equally, and value their contributions. 6 | 7 | Decisions are made based on technical merit and consensus. 8 | 9 | Try to follow most principles described here: https://nextcloud.com/code-of-conduct/ 10 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to pillow-heif 2 | 3 | Bug fixes, feature additions, tests, documentation and more can be contributed via [issues](https://github.com/bigcat88/pillow_heif/issues) and/or [pull requests](https://github.com/bigcat88/pillow_heif/pulls). All contributions are welcome. 4 | 5 | ## Bug fixes, feature additions, etc. 6 | 7 | Please send a pull request to the `master` branch. Feel free to ask questions [via issues](https://github.com/bigcat88/pillow_heif/issues) or [discussions](https://github.com/bigcat88/pillow_heif/discussions) 8 | 9 | - Fork the pillow-heif repository. 10 | - Create a branch from `master`. 11 | - Install dev requirements with `pip install ".[dev]"` 12 | - Develop bug fixes, features, tests, etc. 13 | - Run the test suite. Run `coverage run -m pytest && coverage html` to see if the changed code is covered by tests. 14 | - Install `pylint` locally, it will run during `pre-commit`. 15 | - Install `pre-commit` hooks by `pre-commit install` command. 16 | - Create a pull request to pull the changes from your branch to the pillow-heif `master`. 17 | 18 | ### Guidelines 19 | 20 | - Separate code commits from reformatting commits. 21 | - Provide tests for any newly added code. 22 | - Follow PEP 8. 23 | - Update CHANGELOG.md as needed or appropriate with your bug fixes, feature additions and tests. 24 | 25 | ## Security vulnerabilities 26 | 27 | Please see our [security policy](https://github.com/bigcat88/pillow_heif/blob/master/.github/SECURITY.md). 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: Create a report to help reproduce and correct the bug 3 | labels: ['Bug', 'Needs Triage'] 4 | 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: > 9 | #### Before submitting a bug, please make sure the issue hasn't been already 10 | addressed by searching through [the past issues](https://github.com/bigcat88/pillow_heif/issues). 11 | - type: textarea 12 | attributes: 13 | label: Describe the bug 14 | description: > 15 | A clear and concise description of what the bug is. 16 | validations: 17 | required: true 18 | - type: textarea 19 | attributes: 20 | label: Steps/Code to Reproduce 21 | description: | 22 | Please add a minimal code example that can reproduce the error when running it. Be as succinct as possible. 23 | 24 | If the code is too long, feel free to put it in a public gist and link it in the issue: https://gist.github.com. 25 | 26 | placeholder: | 27 | ``` 28 | Sample code to reproduce the problem 29 | ``` 30 | validations: 31 | required: true 32 | - type: textarea 33 | attributes: 34 | label: Expected Results 35 | description: > 36 | Please paste or describe the expected results. 37 | placeholder: > 38 | Example: No error is thrown. 39 | validations: 40 | required: true 41 | - type: textarea 42 | attributes: 43 | label: Actual Results 44 | description: | 45 | Please paste or describe the results you observe instead of the expected results. If you observe an error, please paste the error message including the **full traceback** of the exception. 46 | placeholder: > 47 | Please paste or specifically describe the actual output or traceback. 48 | validations: 49 | required: true 50 | - type: textarea 51 | attributes: 52 | label: Versions 53 | render: shell 54 | description: | 55 | Please run the following and paste the output below(python, os, cpu, pillow_heif and libheif version and codecs). 56 | ```python 57 | import platform, sys, pillow_heif 58 | print(sys.version, platform.platform(), pillow_heif.__version__, pillow_heif.libheif_info(), sep='\n') 59 | ``` 60 | validations: 61 | required: true 62 | - type: markdown 63 | attributes: 64 | value: > 65 | Thanks for contributing 🎉! 66 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: true 2 | contact_links: 3 | - name: Discussions 4 | url: https://github.com/bigcat88/pillow_heif/discussions/new 5 | about: Ask questions and discuss 6 | - name: Blank issue 7 | url: https://github.com/bigcat88/pillow_heif/issues/new 8 | about: Please note that Github Discussions should be used in most cases instead 9 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: Feature request 2 | description: Suggest a new feature, enhancement to an existing, etc. 3 | labels: ['New Feature', 'Needs Triage'] 4 | 5 | body: 6 | - type: textarea 7 | attributes: 8 | label: Describe why it is important and where it will be useful 9 | validations: 10 | required: true 11 | - type: textarea 12 | attributes: 13 | label: Describe your proposed solution 14 | validations: 15 | required: true 16 | - type: textarea 17 | attributes: 18 | label: Describe alternatives you've considered, if relevant 19 | - type: textarea 20 | attributes: 21 | label: Additional context 22 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Fixes # . 2 | 3 | Changes proposed in this pull request: 4 | 5 | * 6 | * 7 | * 8 | -------------------------------------------------------------------------------- /.github/SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Only the latest non-alpha/beta version is supported. 6 | 7 | Fix time is approximately from 1 to 5 days. 8 | 9 | ## Reporting a Vulnerability 10 | 11 | Transparency and Security are vital. 12 | All builds are made using GitHub Actions only and for macOS Arm64 using CirrusCI. 13 | No part of the releases is published from any local source, the release process is fully automated from start to finish. 14 | 15 | Please report security issues using GitHub's `Private vulnerability` reporting feature. 16 | 17 | You are also welcome to report security vulnerabilities via email to `bigcat88@icloud.com`, ensuring to include `Pillow-Heif` in the subject line. 18 | 19 | If you do not receive a response within 48 hours, please create an issue without including technical details—to notify about it. 20 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:base", 5 | ":disableDependencyDashboard" 6 | ], 7 | "ignorePaths": [ 8 | "**/docker/**", 9 | "**/docs/**", 10 | "**/examples/**", 11 | "**/libheif/**", 12 | "**/tests/**" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /.github/transform_to-pi_heif.py: -------------------------------------------------------------------------------- 1 | """Script to transform the project to `pi-heif` in place. Should be used only with GA Actions.""" 2 | 3 | import os 4 | import pathlib 5 | 6 | DEV_NAME_ADD = "" # This is only for debugging purposes of this script. 7 | 8 | 9 | # From project folder run: `python3 .github/transform_to-pi_heif.py` 10 | if __name__ == "__main__": 11 | # change `pillow_heif` to `pi_heif` 12 | files_list = [ 13 | "setup.py", 14 | "docker/test_wheels.Dockerfile", 15 | "MANIFEST.in", 16 | "pillow_heif/_pillow_heif.c", 17 | ] 18 | for dir_name in ("pillow_heif", "tests"): 19 | for x in os.listdir(dir_name): 20 | if x.endswith(".py"): 21 | files_list += [os.path.join(dir_name, x)] 22 | 23 | for file_name in files_list: 24 | data = pathlib.Path(file_name).read_text(encoding="utf-8") 25 | modified_data = data.replace("pillow_heif", "pi_heif") 26 | if modified_data != data: 27 | pathlib.Path(file_name + DEV_NAME_ADD).write_text(modified_data) 28 | 29 | os.rename("pillow_heif/_pillow_heif.c", "pillow_heif/_pi_heif.c") 30 | os.rename("pillow_heif", "pi_heif") 31 | -------------------------------------------------------------------------------- /.github/workflows/close-stale.yml: -------------------------------------------------------------------------------- 1 | name: 'Close stale issues' 2 | 3 | on: 4 | schedule: 5 | - cron: '0 10 * * 0' # Every day at 12:00 PM (GMT+2) 6 | 7 | jobs: 8 | stale: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/stale@v9 12 | with: 13 | days-before-stale: 28 14 | days-before-close: 14 15 | days-before-pr-close: -1 # Never close PR's automatically 16 | any-of-labels: 'question, invalid, fixed' 17 | stale-issue-message: 'This issue did not receive an update in the last 4 weeks. 18 | Please take a look again and update the issue with new details, 19 | otherwise it will be automatically closed in 2 weeks. Thank you!' 20 | exempt-all-pr-milestones: true 21 | -------------------------------------------------------------------------------- /.github/workflows/nightly-src-build.yml: -------------------------------------------------------------------------------- 1 | name: Nightly build(source) 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | - cron: '0 0 */4 * *' 7 | 8 | jobs: 9 | src-build-linux: 10 | uses: ./.github/workflows/test-src-build-linux.yml 11 | src-build-macos: 12 | uses: ./.github/workflows/test-src-build-macos.yml 13 | src-build-windows: 14 | uses: ./.github/workflows/test-src-build-windows.yml 15 | -------------------------------------------------------------------------------- /.github/workflows/publish-wheels.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | 7 | jobs: 8 | pillow_heif_build: 9 | if: "contains(github.event.head_commit.message, '[publish]')" 10 | name: Pillow-Heif 11 | uses: ./.github/workflows/wheels-pillow_heif.yml 12 | 13 | pi_heif_build: 14 | needs: [pillow_heif_build] 15 | if: "contains(github.event.head_commit.message, '[publish]')" 16 | name: Pi-Heif 17 | uses: ./.github/workflows/wheels-pi_heif.yml 18 | 19 | publish_pypi: 20 | needs: [pi_heif_build] 21 | if: "contains(github.event.head_commit.message, '[publish]')" 22 | name: Upload to PyPi 23 | runs-on: ubuntu-22.04 24 | 25 | steps: 26 | - uses: actions/checkout@v4 27 | - name: Get tag 28 | run: | 29 | RELEASE_VERSION=$(sed -n "s/^__version__.*\"\(.*\)\"$/\\1/p" ./pillow_heif/_version.py) 30 | echo RELEASE_TAG="v$RELEASE_VERSION" >> $GITHUB_ENV 31 | CHANGELOG=$(grep -oPz "(?s)##\s\[$RELEASE_VERSION.+?(?=##\s\[|$)" ./CHANGELOG.md | tr -d '\0' | sed /^$/d | sed '1d') 32 | CHANGELOG=$(echo "$CHANGELOG" | sed '$!N;s/^###.*\n#/#/;P;D' | sed '$!N;s/^###.*\n#/#/;P;D' | sed '${/^###/d;}') 33 | echo "CHANGELOG<> $GITHUB_ENV 34 | echo "$CHANGELOG" >> $GITHUB_ENV 35 | echo "EOF" >> $GITHUB_ENV 36 | 37 | - name: Pillow-Heif sdist and wheels 38 | uses: actions/download-artifact@v4 39 | with: 40 | path: wheelhouse_pillow_heif 41 | pattern: wheels_pillow_heif-* 42 | merge-multiple: true 43 | 44 | - name: Pi-Heif sdist and wheels 45 | uses: actions/download-artifact@v4 46 | with: 47 | path: wheelhouse_pi_heif 48 | pattern: wheels_pi_heif-* 49 | merge-multiple: true 50 | 51 | - name: Publish Pillow-Heif 52 | run: | 53 | python3 -m pip install twine 54 | python3 -m twine upload --skip-existing wheelhouse_pillow_heif/*.whl 55 | python3 -m twine upload --skip-existing wheelhouse_pillow_heif/*tar.gz 56 | env: 57 | TWINE_USERNAME: __token__ 58 | TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN_PILLOW_HEIF }} 59 | 60 | - name: Publish Pi-Heif 61 | run: | 62 | python3 -m twine upload --skip-existing wheelhouse_pi_heif/*.whl 63 | python3 -m twine upload --skip-existing wheelhouse_pi_heif/*tar.gz 64 | env: 65 | TWINE_USERNAME: __token__ 66 | TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN_PI_HEIF }} 67 | 68 | - name: Create release draft 69 | uses: ncipollo/release-action@v1.16.0 70 | with: 71 | name: ${{ env.RELEASE_TAG }} 72 | tag: ${{ env.RELEASE_TAG }} 73 | commit: ${{ github.ref }} 74 | draft: false 75 | body: ${{ env.CHANGELOG }} 76 | token: ${{ secrets.PAT_PH }} 77 | -------------------------------------------------------------------------------- /.github/workflows/test-src-build-linux.yml: -------------------------------------------------------------------------------- 1 | name: From source(Linux) 2 | 3 | on: 4 | workflow_dispatch: 5 | workflow_call: 6 | pull_request: 7 | paths: 8 | - '.github/workflows/test-src-build-linux.yml' 9 | - 'docker/from_src/*.Dockerfile' 10 | - 'libheif/linux/**' 11 | - 'libheif/linux_*.py' 12 | - 'setup.*' 13 | - 'pyproject.toml' 14 | - 'pillow_heif/_pillow_heif.c' 15 | push: 16 | branches: [master] 17 | paths: 18 | - '.github/workflows/test-src-build-linux.yml' 19 | - 'docker/from_src/*.Dockerfile' 20 | - 'libheif/linux/**' 21 | - 'libheif/linux_*.py' 22 | - 'setup.*' 23 | - 'pyproject.toml' 24 | - 'pillow_heif/_pillow_heif.c' 25 | 26 | concurrency: 27 | group: src_linux-${{ github.ref }} 28 | cancel-in-progress: true 29 | 30 | jobs: 31 | linux_amd64: 32 | name: AMD64 • ${{ matrix.docker_file }} 33 | runs-on: ubuntu-latest 34 | 35 | strategy: 36 | fail-fast: false 37 | matrix: 38 | docker_file: ["Alpine_3_20", "Alpine_3_21", "Almalinux_9", "Debian_12"] 39 | 40 | steps: 41 | - uses: actions/checkout@v4 42 | 43 | - name: Setup Docker Buildx 44 | uses: docker/setup-buildx-action@v3 45 | 46 | - name: Build Requirements 47 | uses: docker/build-push-action@v6 48 | with: 49 | context: . 50 | file: docker/from_src/${{ matrix.docker_file }}.Dockerfile 51 | platforms: linux/amd64 52 | push: false 53 | cache-from: type=gha,scope=${{ matrix.docker_file }}-amd64 54 | cache-to: type=gha,mode=min,scope=${{ matrix.docker_file }}-amd64 55 | target: base 56 | 57 | - name: Build & Test Pillow-Heif 58 | uses: docker/build-push-action@v6 59 | with: 60 | context: . 61 | file: docker/from_src/${{ matrix.docker_file }}.Dockerfile 62 | platforms: linux/amd64 63 | push: false 64 | cache-from: type=gha,scope=${{ matrix.docker_file }}-amd64 65 | target: build_test 66 | 67 | linux_arm64: 68 | name: ARM64 • ${{ matrix.docker_file }} 69 | runs-on: ubuntu-24.04-arm 70 | 71 | strategy: 72 | fail-fast: false 73 | matrix: 74 | docker_file: ["Alpine_3_20", "Alpine_3_21", "Almalinux_9", "Debian_12"] 75 | 76 | steps: 77 | - uses: actions/checkout@v4 78 | 79 | - name: Setup Docker Buildx 80 | uses: docker/setup-buildx-action@v3 81 | 82 | - name: Build Requirements 83 | uses: docker/build-push-action@v6 84 | with: 85 | context: . 86 | file: docker/from_src/${{ matrix.docker_file }}.Dockerfile 87 | platforms: linux/arm64 88 | push: false 89 | cache-from: type=gha,scope=${{ matrix.docker_file }}-arm64 90 | cache-to: type=gha,mode=min,scope=${{ matrix.docker_file }}-arm64 91 | target: base 92 | 93 | - name: Build & Test Pillow-Heif 94 | uses: docker/build-push-action@v6 95 | with: 96 | context: . 97 | file: docker/from_src/${{ matrix.docker_file }}.Dockerfile 98 | platforms: linux/arm64 99 | push: false 100 | cache-from: type=gha,scope=${{ matrix.docker_file }}-arm64 101 | target: build_test 102 | -------------------------------------------------------------------------------- /.github/workflows/test-src-build-macos.yml: -------------------------------------------------------------------------------- 1 | name: From source(macOS) 2 | 3 | on: 4 | workflow_dispatch: 5 | workflow_call: 6 | pull_request: 7 | paths: 8 | - '.github/workflows/test-src-build-macos.yml' 9 | - 'libheif/macos/**' 10 | - 'setup.*' 11 | - 'pyproject.toml' 12 | - 'pi-heif/libheif/macos/**' 13 | - 'pi-heif/setup.cfg' 14 | - 'pillow_heif/_pillow_heif.c' 15 | push: 16 | branches: [master] 17 | paths: 18 | - '.github/workflows/test-src-build-macos.yml' 19 | - 'libheif/macos/**' 20 | - 'setup.*' 21 | - 'pyproject.toml' 22 | - 'pi-heif/libheif/macos/**' 23 | - 'pi-heif/setup.cfg' 24 | - 'pillow_heif/_pillow_heif.c' 25 | 26 | concurrency: 27 | group: src_macos-${{ github.ref }} 28 | cancel-in-progress: true 29 | 30 | jobs: 31 | full_macos_13: 32 | name: macOS:13-x86_64 33 | runs-on: macos-13 34 | env: 35 | TEST_DECODE_THREADS: 0 # This test fails on GitHub on macOS. We have such enabled test on Cirrus. 36 | PH_FULL_ACTION: 1 37 | EXP_PH_LIBHEIF_VERSION: "" 38 | 39 | steps: 40 | - uses: actions/checkout@v4 41 | - name: Install libheif from formula 42 | run: | 43 | brew uninstall --force --ignore-dependencies imagemagick libheif aom 44 | brew install --formula ./libheif/macos/libheif.rb 45 | 46 | - name: Installing Pillow-Heif 47 | run: python3 -m pip -v install ".[dev]" 48 | 49 | - name: LibHeif info 50 | run: python3 -c "import pillow_heif; print(pillow_heif.libheif_info())" 51 | 52 | - name: Perform tests 53 | run: python3 -m pytest 54 | 55 | full_macos_14: 56 | name: macOS:14-Arm 57 | runs-on: macos-14 58 | env: 59 | PH_FULL_ACTION: 1 60 | EXP_PH_LIBHEIF_VERSION: "" 61 | 62 | steps: 63 | - uses: actions/checkout@v4 64 | - uses: actions/setup-python@v5 65 | with: 66 | python-version: '3.12' 67 | 68 | - name: Install libheif from formula 69 | run: | 70 | brew uninstall --force --ignore-dependencies imagemagick libheif aom 71 | brew install --formula ./libheif/macos/libheif.rb 72 | 73 | - name: Installing Pillow-Heif 74 | run: python3 -m pip -v install ".[dev]" 75 | 76 | - name: LibHeif info 77 | run: python3 -c "import pillow_heif; print(pillow_heif.libheif_info())" 78 | 79 | - name: Perform tests 80 | run: python3 -m pytest 81 | 82 | lite_macos_13: 83 | name: macOS:13-x86_64(Pi-Heif) 84 | runs-on: macos-13 85 | env: 86 | TEST_DECODE_THREADS: 0 87 | PH_LIGHT_ACTION: 1 88 | EXP_PH_LIBHEIF_VERSION: "" 89 | 90 | steps: 91 | - uses: actions/checkout@v4 92 | - name: Transform to Pi-Heif 93 | run: | 94 | cp -r -v ./pi-heif/* . 95 | python3 .github/transform_to-pi_heif.py 96 | 97 | - name: Install libheif from formula 98 | run: | 99 | brew uninstall --force --ignore-dependencies imagemagick libheif x265 aom 100 | brew install --formula ./libheif/macos/libheif.rb 101 | 102 | - name: Installing Pi-Heif 103 | run: python3 -m pip -v install ".[tests]" 104 | 105 | - name: LibHeif info 106 | run: python3 -c "import pi_heif; print(pi_heif.libheif_info())" 107 | 108 | - name: Perform tests 109 | run: python3 -m pytest 110 | 111 | lite_macos_14: 112 | name: macOS:14-Arm(Pi-Heif) 113 | runs-on: macos-14 114 | env: 115 | PH_LIGHT_ACTION: 1 116 | EXP_PH_LIBHEIF_VERSION: "" 117 | 118 | steps: 119 | - uses: actions/checkout@v4 120 | - uses: actions/setup-python@v5 121 | with: 122 | python-version: '3.12' 123 | 124 | - name: Transform to Pi-Heif 125 | run: | 126 | cp -r -v ./pi-heif/* . 127 | python3 .github/transform_to-pi_heif.py 128 | 129 | - name: Install libheif from formula 130 | run: | 131 | brew uninstall --force --ignore-dependencies imagemagick libheif x265 aom 132 | brew install --formula ./libheif/macos/libheif.rb 133 | 134 | - name: Installing Pi-Heif 135 | run: python3 -m pip -v install ".[tests]" 136 | 137 | - name: LibHeif info 138 | run: python3 -c "import pi_heif; print(pi_heif.libheif_info())" 139 | 140 | - name: Perform tests 141 | run: python3 -m pytest 142 | -------------------------------------------------------------------------------- /.github/workflows/test-src-build-windows.yml: -------------------------------------------------------------------------------- 1 | name: From source(Windows) 2 | 3 | on: 4 | workflow_dispatch: 5 | workflow_call: 6 | pull_request: 7 | paths: 8 | - '.github/workflows/test-src-build-windows.yml' 9 | - 'libheif/windows/**' 10 | - 'libheif/heif.h' 11 | - 'setup.*' 12 | - 'pyproject.toml' 13 | - 'pi-heif/libheif/windows/**' 14 | - 'pi-heif/setup.cfg' 15 | - 'pillow_heif/_pillow_heif.c' 16 | push: 17 | branches: [master] 18 | paths: 19 | - '.github/workflows/test-src-build-windows.yml' 20 | - 'libheif/windows/**' 21 | - 'libheif/heif.h' 22 | - 'setup.*' 23 | - 'pyproject.toml' 24 | - 'pi-heif/libheif/windows/**' 25 | - 'pi-heif/setup.cfg' 26 | - 'pillow_heif/_pillow_heif.c' 27 | 28 | concurrency: 29 | group: src_windows-${{ github.ref }} 30 | cancel-in-progress: true 31 | 32 | jobs: 33 | full_windows_2019: 34 | name: Windows:2019-x86_64 35 | runs-on: windows-2019 36 | env: 37 | MSYS2_PREFIX: "C:/temp/msys64/mingw64" 38 | PH_FULL_ACTION: 1 39 | EXP_PH_LIBHEIF_VERSION: "" 40 | 41 | steps: 42 | - uses: actions/checkout@v4 43 | - uses: actions/setup-python@v5 44 | with: 45 | python-version: '3.11' 46 | 47 | - uses: msys2/setup-msys2@v2 48 | with: 49 | location: C:/temp 50 | update: true 51 | install: >- 52 | mingw-w64-x86_64-binutils 53 | 54 | - name: Build libheif and dependencies 55 | shell: msys2 {0} 56 | run: | 57 | cd libheif/windows/mingw-w64-libheif 58 | makepkg-mingw --syncdeps --noconfirm -f 59 | pacman -U mingw-w64-x86_64-libheif-*-any.pkg.tar.zst --noconfirm 60 | 61 | - name: Installing Pillow-Heif 62 | run: python -m pip -v install ".[dev]" 63 | 64 | - name: Copy DLLs from MSYS2 65 | run: | 66 | $site_packages=(python -c 'import sysconfig; print(sysconfig.get_paths()["platlib"])') 67 | Get-ChildItem -Path $Env:MSYS2_PREFIX/bin -Force | Format-List 68 | cp ${{ env.MSYS2_PREFIX }}/bin/libheif.dll $site_packages/ 69 | cp ${{ env.MSYS2_PREFIX }}/bin/libde265-0.dll $site_packages/ 70 | cp ${{ env.MSYS2_PREFIX }}/bin/libx265-215.dll $site_packages/ 71 | cp ${{ env.MSYS2_PREFIX }}/bin/libwinpthread-1.dll $site_packages/ 72 | cp ${{ env.MSYS2_PREFIX }}/bin/libgcc_s_seh-1.dll $site_packages/ 73 | cp ${{ env.MSYS2_PREFIX }}/bin/libstdc++-6.dll $site_packages/ 74 | 75 | - name: LibHeif info 76 | run: python -c "import pillow_heif; print(pillow_heif.libheif_info())" 77 | 78 | - name: Perform tests 79 | run: python -m pytest 80 | 81 | lite_windows_2019: 82 | name: Windows:2019-x86_64(Pi-Heif) 83 | runs-on: windows-2019 84 | env: 85 | MSYS2_PREFIX: "C:/temp/msys64/mingw64" 86 | PH_LIGHT_ACTION: 1 87 | EXP_PH_LIBHEIF_VERSION: "" 88 | 89 | steps: 90 | - uses: actions/checkout@v4 91 | - uses: actions/setup-python@v5 92 | with: 93 | python-version: '3.10' 94 | 95 | - name: Transform to Pi-Heif 96 | run: | 97 | cp -r -v -force ./pi-heif/* . 98 | python3 .github/transform_to-pi_heif.py 99 | 100 | - uses: msys2/setup-msys2@v2 101 | with: 102 | location: C:/temp 103 | install: >- 104 | mingw-w64-x86_64-binutils 105 | 106 | - name: Build libheif and dependencies 107 | shell: msys2 {0} 108 | run: | 109 | cd libheif/windows/mingw-w64-libheif 110 | makepkg-mingw --syncdeps --noconfirm -f 111 | pacman -U mingw-w64-x86_64-libheif-*-any.pkg.tar.zst --noconfirm 112 | 113 | - name: Installing Pi-Heif 114 | run: python -m pip -v install ".[tests]" 115 | 116 | - name: Copy DLLs from MSYS2 117 | run: | 118 | $site_packages=(python -c 'import sysconfig; print(sysconfig.get_paths()["platlib"])') 119 | cp ${{ env.MSYS2_PREFIX }}/bin/libheif.dll $site_packages/ 120 | cp ${{ env.MSYS2_PREFIX }}/bin/libde265-0.dll $site_packages/ 121 | cp ${{ env.MSYS2_PREFIX }}/bin/libwinpthread-1.dll $site_packages/ 122 | cp ${{ env.MSYS2_PREFIX }}/bin/libgcc_s_seh-1.dll $site_packages/ 123 | cp ${{ env.MSYS2_PREFIX }}/bin/libstdc++-6.dll $site_packages/ 124 | 125 | - name: LibHeif info 126 | run: python -c "import pi_heif; print(pi_heif.libheif_info())" 127 | 128 | - name: Perform tests 129 | run: python -m pytest 130 | -------------------------------------------------------------------------------- /.github/workflows/test-wheels.yml: -------------------------------------------------------------------------------- 1 | name: Wheels test 2 | 3 | on: 4 | release: 5 | types: [ published ] 6 | workflow_dispatch: 7 | 8 | jobs: 9 | docker-tests-amd64: 10 | name: ${{ matrix.i['os'] }} • ${{ matrix.i['ver'] }} • AMD64 11 | runs-on: ubuntu-24.04 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | i: [ 16 | { "os": "amazonlinux", "ver": "latest" }, 17 | { "os": "oraclelinux", "ver": "9" }, 18 | { "os": "fedora", "ver": "37" }, 19 | { "os": "debian", "ver": "11" }, 20 | { "os": "debian", "ver": "12" }, 21 | { "os": "alpine", "ver": "3.19" }, 22 | { "os": "alpine", "ver": "3.20" }, 23 | ] 24 | 25 | steps: 26 | - name: Delay, waiting Pypi to update. 27 | if: ${{ github.event_name != 'workflow_dispatch' }} 28 | run: sleep 480 29 | 30 | - uses: actions/checkout@v4 31 | 32 | - name: Test type is Full 33 | run: echo TEST_TYPE="export PH_FULL_ACTION=1" >> $GITHUB_ENV 34 | 35 | - name: Preparing debian 36 | if: matrix.i['os'] == 'debian' || matrix.i['os'] == 'ubuntu' 37 | run: | 38 | echo PREPARE_CMD="apt update" >> $GITHUB_ENV 39 | echo INSTALL_CMD="apt install -y \ 40 | python3-minimal python3-distutils python3-pip python3-dev python3-venv \ 41 | zlib1g-dev libjpeg62-turbo-dev liblcms2-dev libwebp-dev libfribidi-dev libharfbuzz-dev" >> $GITHUB_ENV 42 | 43 | - name: Preparing musli 44 | if: matrix.i['os'] == 'alpine' 45 | run: echo INSTALL_CMD="apk add --no-cache python3 py3-pip py3-pillow py3-numpy" >> $GITHUB_ENV 46 | 47 | - name: Preparing centos 48 | if: matrix.i['os'] != 'debian' && matrix.i['os'] != 'ubuntu' && matrix.i['os'] != 'alpine' 49 | run: | 50 | echo PREPARE_CMD="yum makecache" >> $GITHUB_ENV 51 | echo INSTALL_CMD="yum install -y python3 python3-pip" >> $GITHUB_ENV 52 | 53 | - name: Build image & Run tests 54 | run: | 55 | docker buildx build \ 56 | --platform linux/amd64 \ 57 | --build-arg BASE_IMAGE="${{ matrix.i['os'] }}:${{ matrix.i['ver'] }}" \ 58 | --build-arg PREPARE_CMD="${{ env.PREPARE_CMD }}" \ 59 | --build-arg INSTALL_CMD="${{ env.INSTALL_CMD }}" \ 60 | --build-arg TEST_TYPE="${{ env.TEST_TYPE }}" \ 61 | -f docker/test_wheels.Dockerfile . 62 | 63 | docker-tests-arm64: 64 | name: ${{ matrix.i['os'] }} • ${{ matrix.i['ver'] }} • ARM64 65 | runs-on: ubuntu-24.04-arm 66 | strategy: 67 | fail-fast: false 68 | matrix: 69 | i: [ 70 | { "os": "amazonlinux", "ver": "latest" }, 71 | { "os": "oraclelinux", "ver": "9" }, 72 | { "os": "debian", "ver": "11" }, 73 | { "os": "debian", "ver": "12" }, 74 | { "os": "alpine", "ver": "3.19" }, 75 | { "os": "alpine", "ver": "3.20" }, 76 | ] 77 | 78 | steps: 79 | - name: Delay, waiting Pypi to update. 80 | if: ${{ github.event_name != 'workflow_dispatch' }} 81 | run: sleep 480 82 | 83 | - uses: actions/checkout@v4 84 | 85 | - name: Test type is Full 86 | run: echo TEST_TYPE="export PH_FULL_ACTION=1" >> $GITHUB_ENV 87 | 88 | - name: Preparing debian 89 | if: matrix.i['os'] == 'debian' || matrix.i['os'] == 'ubuntu' 90 | run: | 91 | echo PREPARE_CMD="apt update" >> $GITHUB_ENV 92 | echo INSTALL_CMD="apt install -y \ 93 | python3-minimal python3-distutils python3-pip python3-dev python3-venv \ 94 | zlib1g-dev libjpeg62-turbo-dev liblcms2-dev libwebp-dev libfribidi-dev libharfbuzz-dev" >> $GITHUB_ENV 95 | 96 | - name: Preparing musli 97 | if: matrix.i['os'] == 'alpine' 98 | run: echo INSTALL_CMD="apk add --no-cache python3 py3-pip py3-pillow py3-numpy" >> $GITHUB_ENV 99 | 100 | - name: Preparing centos 101 | if: matrix.i['os'] != 'debian' && matrix.i['os'] != 'ubuntu' && matrix.i['os'] != 'alpine' 102 | run: | 103 | echo PREPARE_CMD="yum makecache" >> $GITHUB_ENV 104 | echo INSTALL_CMD="yum install -y python3 python3-pip" >> $GITHUB_ENV 105 | 106 | - name: Build image & Run tests 107 | run: | 108 | docker buildx build \ 109 | --platform linux/arm64 \ 110 | --build-arg BASE_IMAGE="${{ matrix.i['os'] }}:${{ matrix.i['ver'] }}" \ 111 | --build-arg PREPARE_CMD="${{ env.PREPARE_CMD }}" \ 112 | --build-arg INSTALL_CMD="${{ env.INSTALL_CMD }}" \ 113 | --build-arg TEST_TYPE="${{ env.TEST_TYPE }}" \ 114 | -f docker/test_wheels.Dockerfile . 115 | 116 | windows-wheels: 117 | name: Windows • 2019 • ${{ matrix.python-version }} 118 | runs-on: windows-2019 119 | strategy: 120 | matrix: 121 | python-version: ["pypy-3.9", "pypy-3.10", "3.9", "3.10", "3.11", "3.12", "3.13"] 122 | env: 123 | PH_FULL_ACTION: 1 124 | 125 | steps: 126 | - name: Delay, waiting Pypi to update. 127 | if: ${{ github.event_name != 'workflow_dispatch' }} 128 | run: Start-Sleep -s 480 129 | shell: powershell 130 | 131 | - uses: actions/checkout@v4 132 | - name: Set up Python 133 | uses: actions/setup-python@v5 134 | with: 135 | python-version: ${{ matrix.python-version }} 136 | 137 | - name: Preparations 138 | run: | 139 | python3 -m pip install --prefer-binary pillow 140 | python3 -m pip install pytest packaging defusedxml 141 | python3 -m pip install --only-binary=:all: pillow_heif 142 | 143 | - name: Test wheel 144 | run: cd .. && python3 -m pytest pillow_heif 145 | 146 | macos-wheels: 147 | name: macOS • 13 • ${{ matrix.python-version }} 148 | runs-on: macos-13 149 | strategy: 150 | matrix: 151 | python-version: ["pypy-3.9", "pypy-3.10", "3.9", "3.10", "3.11", "3.12", "3.13"] 152 | 153 | steps: 154 | - name: Delay, waiting Pypi to update. 155 | if: ${{ github.event_name != 'workflow_dispatch' }} 156 | run: sleep 480 157 | 158 | - uses: actions/checkout@v4 159 | - name: Set up Python 160 | uses: actions/setup-python@v5 161 | with: 162 | python-version: ${{ matrix.python-version }} 163 | 164 | - name: Preparations 165 | run: | 166 | brew install libjpeg little-cms2 167 | python3 -m pip install --prefer-binary pillow 168 | python3 -m pip install pytest packaging defusedxml 169 | python3 -m pip install --only-binary=:all: pillow_heif 170 | 171 | - name: Test wheel 172 | run: | 173 | export TEST_DECODE_THREADS=0 174 | export PH_FULL_ACTION=1 175 | cd .. && python3 -m pytest pillow_heif 176 | 177 | manylinux-wheels: 178 | name: Ubuntu-Focal • ${{ matrix.python-version }} 179 | runs-on: ubuntu-20.04 180 | strategy: 181 | matrix: 182 | python-version: ["pypy-3.9", "pypy-3.10", "3.9", "3.10", "3.11", "3.12", "3.13"] 183 | 184 | steps: 185 | - name: Delay, waiting Pypi to update. 186 | if: ${{ github.event_name != 'workflow_dispatch' }} 187 | run: sleep 480 188 | 189 | - uses: actions/checkout@v4 190 | - name: Set up Python 191 | uses: actions/setup-python@v5 192 | with: 193 | python-version: ${{ matrix.python-version }} 194 | 195 | - name: Preparations 196 | run: | 197 | sudo apt update && sudo apt install libjpeg-dev liblcms2-dev 198 | python3 -m pip install --prefer-binary pillow 199 | python3 -m pip install pytest packaging defusedxml 200 | python3 -m pip install --only-binary=:all: pillow_heif 201 | 202 | - name: Test wheel 203 | run: | 204 | export PH_FULL_ACTION=1 205 | cd .. && python3 -m pytest pillow_heif 206 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | converted/ 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | cover/ 54 | 55 | # Translations 56 | *.mo 57 | *.pot 58 | 59 | # Django stuff: 60 | *.log 61 | local_settings.py 62 | db.sqlite3 63 | db.sqlite3-journal 64 | 65 | # Flask stuff: 66 | instance/ 67 | .webassets-cache 68 | 69 | # Scrapy stuff: 70 | .scrapy 71 | 72 | # Sphinx documentation 73 | docs/_build/ 74 | 75 | # PyBuilder 76 | .pybuilder/ 77 | target/ 78 | 79 | # Jupyter Notebook 80 | .ipynb_checkpoints 81 | 82 | # IPython 83 | profile_default/ 84 | ipython_config.py 85 | 86 | # pyenv 87 | # For a library or package, you might want to ignore these files since the code is 88 | # intended to run in multiple environments; otherwise, check them in: 89 | # .python-version 90 | 91 | # pipenv 92 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 93 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 94 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 95 | # install all needed dependencies. 96 | #Pipfile.lock 97 | 98 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 99 | __pypackages__/ 100 | 101 | # Celery stuff 102 | celerybeat-schedule 103 | celerybeat.pid 104 | 105 | # SageMath parsed files 106 | *.sage.py 107 | 108 | # Environments 109 | .env 110 | .venv 111 | env/ 112 | venv/ 113 | ENV/ 114 | env.bak/ 115 | venv.bak/ 116 | 117 | # Spyder project settings 118 | .spyderproject 119 | .spyproject 120 | 121 | # Rope project settings 122 | .ropeproject 123 | 124 | # mkdocs documentation 125 | /site 126 | 127 | # mypy 128 | .mypy_cache/ 129 | .dmypy.json 130 | dmypy.json 131 | 132 | # Pyre type checker 133 | .pyre/ 134 | 135 | # pytype static type analyzer 136 | .pytype/ 137 | 138 | # Cython debug symbols 139 | cython_debug/ 140 | 141 | # Pycharm settings 142 | .idea/ 143 | 144 | # VSCode workspace settings 145 | .vscode 146 | 147 | .DS_Store 148 | 149 | /out 150 | /dev/ 151 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | ci: 2 | skip: [pylint] 3 | 4 | repos: 5 | - repo: https://github.com/pre-commit/pre-commit-hooks 6 | rev: v5.0.0 7 | hooks: 8 | - id: check-yaml 9 | - id: end-of-file-fixer 10 | exclude: libheif/(linux|macos|windows)/ 11 | - id: trailing-whitespace 12 | exclude: libheif/(linux|macos|windows)/ 13 | - id: check-toml 14 | - id: mixed-line-ending 15 | 16 | - repo: https://github.com/PyCQA/isort 17 | rev: 6.0.1 18 | hooks: 19 | - id: isort 20 | 21 | - repo: https://github.com/psf/black 22 | rev: 25.1.0 23 | hooks: 24 | - id: black 25 | 26 | - repo: https://github.com/tox-dev/pyproject-fmt 27 | rev: v2.6.0 28 | hooks: 29 | - id: pyproject-fmt 30 | 31 | - repo: https://github.com/astral-sh/ruff-pre-commit 32 | rev: v0.11.12 33 | hooks: 34 | - id: ruff 35 | 36 | - repo: https://github.com/mgedmin/check-manifest 37 | rev: "0.50" 38 | hooks: 39 | - id: check-manifest 40 | args: [--no-build-isolation] 41 | 42 | - repo: local 43 | hooks: 44 | - id: pylint 45 | name: pylint 46 | entry: pylint "setup.py" "pillow_heif/" 47 | language: system 48 | types: [ python ] 49 | pass_filenames: false 50 | args: 51 | [ 52 | "-rn", # Only display messages 53 | "-sn", # Don't display the score 54 | ] 55 | -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | build: 4 | os: ubuntu-22.04 5 | tools: 6 | python: "3.9" 7 | 8 | sphinx: 9 | configuration: docs/conf.py 10 | 11 | python: 12 | install: 13 | - method: pip 14 | path: . 15 | extra_requirements: 16 | - docs 17 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021-2023, Pillow-Heif contributors. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are 6 | met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above 12 | copyright notice, this list of conditions and the following 13 | disclaimer in the documentation and/or other materials provided 14 | with the distribution. 15 | 16 | * Neither the name of the Pillow-Heif nor the names of any 17 | contributors may be used to endorse or promote products derived 18 | from this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /LICENSES_bundled.txt: -------------------------------------------------------------------------------- 1 | License for "pillow-heif" binary wheels: GPLv2, due to base library licenses. 2 | 3 | Binary wheels combine several license-compatible libraries. Here they are listed. 4 | 5 | Name: libheif 6 | License: LGPLv3 7 | Files: libheif.[dylib|so|dll] 8 | For details, see https://github.com/strukturag/libheif/tree/v1.18.1/COPYING 9 | Source code: https://github.com/strukturag/libheif/tree/v1.18.1 10 | 11 | Name: libde265 12 | License: LGPLv3 13 | Files: libde265.[dylib|so|dll] 14 | For details, see https://github.com/strukturag/libde265/tree/v1.0.15/COPYING 15 | Source code: https://github.com/strukturag/libde265/tree/v1.0.15 16 | 17 | Name: x265 18 | License: GPLv2 19 | Files: libx265.[dylib|so|dll] 20 | For details, see https://bitbucket.org/multicoreware/x265_git/src/Release_3.4/COPYING 21 | Source code: https://bitbucket.org/multicoreware/x265_git/src/Release_3.4 22 | 23 | Name: libaom 24 | License: BSD 3-Clause 25 | Files: libaom.[dylib|so|dll] 26 | For details, see https://aomedia.googlesource.com/aom/+/refs/tags/v3.6.1/LICENSE 27 | Source code: https://aomedia.googlesource.com/aom/+/refs/tags/v3.6.1 28 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md 2 | include CHANGELOG.md 3 | include pyproject.toml 4 | 5 | recursive-include pillow_heif *.c *.h 6 | 7 | graft libheif 8 | graft tests 9 | graft examples 10 | 11 | exclude *.yaml 12 | exclude *.yml 13 | exclude .cirrus.star 14 | 15 | prune ci 16 | prune docs 17 | prune docker 18 | prune benchmarks 19 | 20 | recursive-exclude **/__pycache__ * 21 | recursive-exclude pi-heif * 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pillow-heif 2 | 3 | [![Analysis & Coverage](https://github.com/bigcat88/pillow_heif/actions/workflows/analysis-coverage.yml/badge.svg)](https://github.com/bigcat88/pillow_heif/actions/workflows/analysis-coverage.yml) 4 | [![Nightly build](https://github.com/bigcat88/pillow_heif/actions/workflows/nightly-src-build.yml/badge.svg)](https://github.com/bigcat88/pillow_heif/actions/workflows/nightly-src-build.yml) 5 | [![Wheels test](https://github.com/bigcat88/pillow_heif/actions/workflows/test-wheels.yml/badge.svg)](https://github.com/bigcat88/pillow_heif/actions/workflows/test-wheels.yml) 6 | [![docs](https://readthedocs.org/projects/pillow-heif/badge/?version=latest)](https://pillow-heif.readthedocs.io/en/latest/?badge=latest) 7 | [![codecov](https://codecov.io/gh/bigcat88/pillow_heif/branch/master/graph/badge.svg?token=JY64F2OL6V)](https://codecov.io/gh/bigcat88/pillow_heif) 8 | 9 | ![PythonVersion](https://img.shields.io/badge/python-3.9%20%7C%203.10%20%7C%203.11%20%7C%203.12%20%7C%203.13-blue) 10 | ![impl](https://img.shields.io/pypi/implementation/pillow_heif) 11 | ![pypi](https://img.shields.io/pypi/v/pillow_heif.svg) 12 | [![Downloads](https://static.pepy.tech/personalized-badge/pillow-heif?period=total&units=international_system&left_color=grey&right_color=orange&left_text=Downloads)](https://pepy.tech/project/pillow-heif) 13 | [![Downloads](https://static.pepy.tech/personalized-badge/pillow-heif?period=month&units=international_system&left_color=grey&right_color=orange&left_text=Downloads/Month)](https://pepy.tech/project/pillow-heif) 14 | 15 | ![Mac OS](https://img.shields.io/badge/mac%20os-FCC624?style=for-the-badge&logoColor=white) 16 | ![Windows](https://img.shields.io/badge/Windows-0078D6?style=for-the-badge&logo=windows&logoColor=white) 17 | ![Linux](https://img.shields.io/badge/Linux-FCC624?style=for-the-badge&logo=linux&logoColor=black) 18 | ![Alpine Linux](https://img.shields.io/badge/Alpine_Linux-0078D6.svg?style=for-the-badge&logo=alpine-linux&logoColor=white) 19 | ![Raspberry Pi](https://img.shields.io/badge/Rasberry_Pi-FCC624.svg?style=for-the-badge&logo=raspberry-pi&logoColor=red) 20 | 21 | Python bindings to [libheif](https://github.com/strukturag/libheif) for working with HEIF images and plugin for Pillow. 22 | 23 | Features: 24 | * Decoding of `8`, `10`, `12` bit HEIC files. 25 | * Encoding of `8`, `10`, `12` bit HEIC files. 26 | * `EXIF`, `XMP`, `IPTC` read & write support. 27 | * Support of multiple images in one file and a `PrimaryImage` attribute. 28 | * Adding & removing `thumbnails`. 29 | * Reading of `Depth Images`. 30 | * (beta) Reading of `Auxiliary Images` by [johncf](https://github.com/johncf) 31 | * Adding HEIF support to Pillow in one line of code as a plugin. 32 | 33 | Note: Here is a light version [pi-heif](https://pypi.org/project/pi-heif/) of this project without encoding capabilities. 34 | 35 | ### Install 36 | ```console 37 | python3 -m pip install -U pip 38 | python3 -m pip install pillow-heif 39 | ``` 40 | 41 | ### Example of use as a Pillow plugin 42 | ```python3 43 | from PIL import Image 44 | from pillow_heif import register_heif_opener 45 | 46 | register_heif_opener() 47 | 48 | im = Image.open("image.heic") # do whatever need with a Pillow image 49 | im = im.rotate(13) 50 | im.save(f"rotated_image.heic", quality=90) 51 | ``` 52 | 53 | ### 16 bit PNG to 10 bit HEIF using OpenCV 54 | ```python3 55 | import cv2 56 | import pillow_heif 57 | 58 | cv_img = cv2.imread("16bit_with_alpha.png", cv2.IMREAD_UNCHANGED) 59 | heif_file = pillow_heif.from_bytes( 60 | mode="BGRA;16", 61 | size=(cv_img.shape[1], cv_img.shape[0]), 62 | data=bytes(cv_img) 63 | ) 64 | heif_file.save("RGBA_10bit.heic", quality=-1) 65 | ``` 66 | 67 | ### 8/10/12 bit HEIF to 8/16 bit PNG using OpenCV 68 | ```python3 69 | import numpy as np 70 | import cv2 71 | import pillow_heif 72 | 73 | heif_file = pillow_heif.open_heif("image.heic", convert_hdr_to_8bit=False, bgr_mode=True) 74 | np_array = np.asarray(heif_file) 75 | cv2.imwrite("image.png", np_array) 76 | ``` 77 | 78 | ### Accessing decoded image data 79 | ```python3 80 | import pillow_heif 81 | 82 | if pillow_heif.is_supported("image.heic"): 83 | heif_file = pillow_heif.open_heif("image.heic", convert_hdr_to_8bit=False) 84 | print("image size:", heif_file.size) 85 | print("image mode:", heif_file.mode) 86 | print("image data length:", len(heif_file.data)) 87 | print("image data stride:", heif_file.stride) 88 | ``` 89 | 90 | ### Get decoded image data as a Numpy array 91 | ```python3 92 | import numpy as np 93 | import pillow_heif 94 | 95 | if pillow_heif.is_supported("input.heic"): 96 | heif_file = pillow_heif.open_heif("input.heic") 97 | np_array = np.asarray(heif_file) 98 | ``` 99 | 100 | ### Accessing Depth Images 101 | 102 | ```python3 103 | from PIL import Image 104 | from pillow_heif import register_heif_opener 105 | import numpy as np 106 | 107 | register_heif_opener() 108 | 109 | im = Image.open("../tests/images/heif_other/pug.heic") 110 | if im.info["depth_images"]: 111 | depth_im = im.info["depth_images"][0] # Access the first depth image (usually there will be only one). 112 | # Depth images are instances of `class HeifDepthImage(BaseImage)`, 113 | # so work with them as you would with any usual image in pillow_heif. 114 | # Depending on what you need the depth image for, you can convert it to a NumPy array or convert it to a Pillow image. 115 | pil_im = depth_im.to_pillow() 116 | np_im = np.asarray(depth_im) 117 | print(pil_im) 118 | print(pil_im.info["metadata"]) 119 | ``` 120 | 121 | 122 | ### More Information 123 | 124 | - [Documentation](https://pillow-heif.readthedocs.io/) 125 | - [Installation](https://pillow-heif.readthedocs.io/en/latest/installation.html) 126 | - [Pillow plugin](https://pillow-heif.readthedocs.io/en/latest/pillow-plugin.html) 127 | - [Using HeifFile](https://pillow-heif.readthedocs.io/en/latest/heif-file.html) 128 | - [Image modes](https://pillow-heif.readthedocs.io/en/latest/image-modes.html) 129 | - [Options](https://pillow-heif.readthedocs.io/en/latest/options.html) 130 | - [Examples](https://github.com/bigcat88/pillow_heif/tree/master/examples) 131 | - [Contribute](https://github.com/bigcat88/pillow_heif/blob/master/.github/CONTRIBUTING.md) 132 | - [Discussions](https://github.com/bigcat88/pillow_heif/discussions) 133 | - [Issues](https://github.com/bigcat88/pillow_heif/issues) 134 | - [Changelog](https://github.com/bigcat88/pillow_heif/blob/master/CHANGELOG.md) 135 | 136 | ### Wheels 137 | 138 | | **_Wheels table_** | macOS
Intel | macOS
Silicon | Windows
| musllinux* | manylinux* | 139 | |--------------------|:---------------:|:-----------------:|:------------:|:----------:|:----------:| 140 | | CPython 3.9 | ✅ | ✅ | ✅ | ✅ | ✅ | 141 | | CPython 3.10 | ✅ | ✅ | ✅ | ✅ | ✅ | 142 | | CPython 3.11 | ✅ | ✅ | ✅ | ✅ | ✅ | 143 | | CPython 3.12 | ✅ | ✅ | ✅ | ✅ | ✅ | 144 | | CPython 3.13 | ✅ | ✅ | ✅ | ✅ | ✅ | 145 | | PyPy 3.9 v7.3 | ✅ | ✅ | ✅ | N/A | ✅ | 146 | | PyPy 3.10 v7.3 | ✅ | ✅ | ✅ | N/A | ✅ | 147 | 148 | * **x86_64**, **aarch64** wheels. 149 | -------------------------------------------------------------------------------- /benchmarks/benchmark_decode.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from enum import IntEnum 3 | from os import path 4 | from subprocess import run 5 | 6 | import matplotlib.pyplot as plt 7 | from cpuinfo import get_cpu_info 8 | 9 | 10 | class OperationType(IntEnum): 11 | PILLOW_LOAD = 0 12 | NUMPY_BGR = 1 13 | NUMPY_RGB = 2 14 | 15 | 16 | VERSIONS = ["0.7.2", "0.8.0", "0.9.3", "0.11.1", "0.12.0"] 17 | N_ITER_SMALL = 100 18 | N_ITER_LARGE = 50 19 | 20 | 21 | def measure_decode(image, n_iterations, op_type: int): 22 | measure_file = path.join(path.dirname(path.abspath(__file__)), "measure_decode.py") 23 | cmd = f"{sys.executable} {measure_file} {n_iterations} {image} {op_type}".split() 24 | result = run(cmd, check=True, capture_output=True) 25 | return float(result.stdout.decode(encoding="utf-8").strip()) / n_iterations 26 | 27 | 28 | if __name__ == "__main__": # argv: OperationType 29 | operation_type = OperationType(int(sys.argv[1])) 30 | print(f"Operation type: {operation_type}") 31 | tests_images_path = path.join(path.dirname(path.dirname(path.abspath(__file__))), "tests/images/heif_other") 32 | cat_image_path = path.join(tests_images_path, "cat.hif") 33 | pug_image_path = path.join(tests_images_path, "pug.heic") 34 | large_image_path = "image_large.heic" 35 | cat_image_results = [] 36 | pug_image_results = [] 37 | large_image_results = [] 38 | for v in VERSIONS: 39 | run(f"{sys.executable} -m pip install pillow-heif=={v}".split(), check=True) 40 | cat_image_results.append(measure_decode(cat_image_path, N_ITER_SMALL, operation_type)) 41 | pug_image_results.append(measure_decode(pug_image_path, N_ITER_SMALL, operation_type)) 42 | large_image_results.append(measure_decode(large_image_path, N_ITER_LARGE, operation_type)) 43 | fig, ax = plt.subplots() 44 | ax.plot(VERSIONS, cat_image_results, label="cat image") 45 | ax.plot(VERSIONS, pug_image_results, label="pug image") 46 | ax.plot(VERSIONS, large_image_results, label="large image") 47 | plt.ylabel("time to decode(s)") 48 | if sys.platform.lower() == "darwin": 49 | _os = "macOS" 50 | elif sys.platform.lower() == "win32": 51 | _os = "Windows" 52 | else: 53 | _os = "Linux" 54 | plt.xlabel(f"{_os} - {get_cpu_info()['brand_raw']}") 55 | ax.legend() 56 | plt.savefig(f"results_decode_{operation_type.name.lower()}_{_os}.png", dpi=200) 57 | -------------------------------------------------------------------------------- /benchmarks/benchmark_encode.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from os import path 3 | from subprocess import run 4 | from time import sleep 5 | 6 | import matplotlib.pyplot as plt 7 | from cpuinfo import get_cpu_info 8 | 9 | VERSIONS = ["0.7.2", "0.8.0", "0.9.3", "0.11.1", "0.12.0"] 10 | N_ITER = 30 11 | 12 | 13 | def measure_encode(image, n_iterations): 14 | measure_file = path.join(path.dirname(path.abspath(__file__)), "measure_encode.py") 15 | cmd = f"{sys.executable} {measure_file} {n_iterations} {image}".split() 16 | run(cmd, check=True) 17 | result = run(cmd, check=True, capture_output=True) 18 | return float(result.stdout.decode(encoding="utf-8").strip()) / n_iterations 19 | 20 | 21 | if __name__ == "__main__": 22 | rgba_image_results = [] 23 | rgb_image_results = [] 24 | la_image_results = [] 25 | l_image_results = [] 26 | pug_image_results = [] 27 | for v in VERSIONS: 28 | run(f"{sys.executable} -m pip install pillow-heif=={v}".split(), check=True) 29 | sleep(N_ITER) 30 | rgba_image_results.append(measure_encode("RGBA", N_ITER)) 31 | rgb_image_results.append(measure_encode("RGB", N_ITER)) 32 | la_image_results.append(measure_encode("LA", N_ITER)) 33 | l_image_results.append(measure_encode("L", N_ITER)) 34 | pug_image_results.append(measure_encode("PUG", N_ITER)) 35 | fig, ax = plt.subplots() 36 | ax.plot(VERSIONS, rgba_image_results, label="RGBA image") 37 | ax.plot(VERSIONS, rgb_image_results, label="RGB image") 38 | ax.plot(VERSIONS, la_image_results, label="LA image") 39 | ax.plot(VERSIONS, l_image_results, label="L image") 40 | ax.plot(VERSIONS, pug_image_results, label="PUG image(RGB)") 41 | plt.ylabel("time to encode(s)") 42 | if sys.platform.lower() == "darwin": 43 | _os = "macOS" 44 | elif sys.platform.lower() == "win32": 45 | _os = "Windows" 46 | else: 47 | _os = "Linux" 48 | plt.xlabel(f"{_os} - {get_cpu_info()['brand_raw']}") 49 | ax.legend() 50 | plt.savefig(f"results_encode_{_os}.png", dpi=200) 51 | -------------------------------------------------------------------------------- /benchmarks/image_large.heic: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bigcat88/pillow_heif/aa52e60de06e7d8ac773df0b91866aebc6c3ca21/benchmarks/image_large.heic -------------------------------------------------------------------------------- /benchmarks/measure_decode.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from time import perf_counter 3 | 4 | import numpy as np 5 | from PIL import Image 6 | 7 | import pillow_heif 8 | 9 | PILLOW_LOAD = 0 10 | NUMPY_BGR = 1 11 | NUMPY_RGB = 2 12 | 13 | 14 | if __name__ == "__main__": 15 | pillow_heif.register_heif_opener() 16 | start_time = perf_counter() 17 | if int(sys.argv[3]) == PILLOW_LOAD: 18 | for _ in range(int(sys.argv[1])): 19 | im = Image.open(sys.argv[2]) 20 | im.load() 21 | elif int(sys.argv[3]) == NUMPY_BGR: 22 | for _ in range(int(sys.argv[1])): 23 | if pillow_heif.__version__.startswith("0.1"): 24 | im = pillow_heif.open_heif(sys.argv[2], bgr_mode=True, convert_hdr_to_8bit=False) 25 | else: 26 | im = pillow_heif.open_heif(sys.argv[2], convert_hdr_to_8bit=False) 27 | im.convert_to("BGR" if im.bit_depth == 8 else "BGR;16") 28 | np_array = np.asarray(im) 29 | elif int(sys.argv[3]) == NUMPY_RGB: 30 | for _ in range(int(sys.argv[1])): 31 | if pillow_heif.__version__.startswith("0.1"): 32 | im = pillow_heif.open_heif(sys.argv[2], convert_hdr_to_8bit=False) 33 | else: 34 | im = pillow_heif.open_heif(sys.argv[2], convert_hdr_to_8bit=False) 35 | if im.bit_depth != 8: 36 | im.convert_to("RGB;16") 37 | np_array = np.asarray(im) 38 | total_time = perf_counter() - start_time 39 | print(total_time) 40 | sys.exit(0) 41 | -------------------------------------------------------------------------------- /benchmarks/measure_encode.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from io import BytesIO 3 | from os import path 4 | from time import perf_counter 5 | 6 | from PIL import Image 7 | 8 | from pillow_heif import register_heif_opener 9 | 10 | L_IMAGE = Image.effect_mandelbrot((4096, 4096), (-3, -2.5, 2, 2.5), 100) 11 | LA_IMAGE = Image.merge("LA", [L_IMAGE, L_IMAGE.transpose(Image.ROTATE_90)]) 12 | RGB_IMAGE = Image.merge("RGB", [L_IMAGE, L_IMAGE.transpose(Image.ROTATE_90), L_IMAGE.transpose(Image.ROTATE_180)]) 13 | RGBA_IMAGE = Image.merge( 14 | "RGBA", 15 | [ 16 | L_IMAGE, 17 | L_IMAGE.transpose(Image.ROTATE_90), 18 | L_IMAGE.transpose(Image.ROTATE_180), 19 | L_IMAGE.transpose(Image.ROTATE_270), 20 | ], 21 | ) 22 | 23 | 24 | if __name__ == "__main__": 25 | _args = {} 26 | register_heif_opener(**_args) 27 | if sys.argv[2] == "PUG": 28 | tests_images_path = path.join(path.dirname(path.dirname(path.abspath(__file__))), "tests/images/heif_other") 29 | img = Image.open(path.join(tests_images_path, "pug.heic")) 30 | elif sys.argv[2] == "RGBA": 31 | img = RGBA_IMAGE 32 | elif sys.argv[2] == "RGB": 33 | img = RGB_IMAGE 34 | elif sys.argv[2] == "LA": 35 | img = LA_IMAGE 36 | else: 37 | img = L_IMAGE 38 | start_time = perf_counter() 39 | for _ in range(int(sys.argv[1])): 40 | buf = BytesIO() 41 | img.save(buf, format="HEIF") 42 | total_time = perf_counter() - start_time 43 | print(total_time) 44 | sys.exit(0) 45 | -------------------------------------------------------------------------------- /benchmarks/results_decode_numpy_bgr_Linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bigcat88/pillow_heif/aa52e60de06e7d8ac773df0b91866aebc6c3ca21/benchmarks/results_decode_numpy_bgr_Linux.png -------------------------------------------------------------------------------- /benchmarks/results_decode_numpy_bgr_Windows.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bigcat88/pillow_heif/aa52e60de06e7d8ac773df0b91866aebc6c3ca21/benchmarks/results_decode_numpy_bgr_Windows.png -------------------------------------------------------------------------------- /benchmarks/results_decode_numpy_bgr_macOS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bigcat88/pillow_heif/aa52e60de06e7d8ac773df0b91866aebc6c3ca21/benchmarks/results_decode_numpy_bgr_macOS.png -------------------------------------------------------------------------------- /benchmarks/results_decode_numpy_rgb_Linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bigcat88/pillow_heif/aa52e60de06e7d8ac773df0b91866aebc6c3ca21/benchmarks/results_decode_numpy_rgb_Linux.png -------------------------------------------------------------------------------- /benchmarks/results_decode_numpy_rgb_Windows.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bigcat88/pillow_heif/aa52e60de06e7d8ac773df0b91866aebc6c3ca21/benchmarks/results_decode_numpy_rgb_Windows.png -------------------------------------------------------------------------------- /benchmarks/results_decode_numpy_rgb_macOS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bigcat88/pillow_heif/aa52e60de06e7d8ac773df0b91866aebc6c3ca21/benchmarks/results_decode_numpy_rgb_macOS.png -------------------------------------------------------------------------------- /benchmarks/results_decode_pillow_load_Linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bigcat88/pillow_heif/aa52e60de06e7d8ac773df0b91866aebc6c3ca21/benchmarks/results_decode_pillow_load_Linux.png -------------------------------------------------------------------------------- /benchmarks/results_decode_pillow_load_Windows.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bigcat88/pillow_heif/aa52e60de06e7d8ac773df0b91866aebc6c3ca21/benchmarks/results_decode_pillow_load_Windows.png -------------------------------------------------------------------------------- /benchmarks/results_decode_pillow_load_macOS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bigcat88/pillow_heif/aa52e60de06e7d8ac773df0b91866aebc6c3ca21/benchmarks/results_decode_pillow_load_macOS.png -------------------------------------------------------------------------------- /benchmarks/results_encode_Linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bigcat88/pillow_heif/aa52e60de06e7d8ac773df0b91866aebc6c3ca21/benchmarks/results_encode_Linux.png -------------------------------------------------------------------------------- /benchmarks/results_encode_Windows.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bigcat88/pillow_heif/aa52e60de06e7d8ac773df0b91866aebc6c3ca21/benchmarks/results_encode_Windows.png -------------------------------------------------------------------------------- /benchmarks/results_encode_macOS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bigcat88/pillow_heif/aa52e60de06e7d8ac773df0b91866aebc6c3ca21/benchmarks/results_encode_macOS.png -------------------------------------------------------------------------------- /ci/cirrus_general_ci.yml: -------------------------------------------------------------------------------- 1 | test_src_build_full_freebsd_task: 2 | only_if: "changesInclude( 3 | 'ci/cirrus_general_ci.yml', 4 | 'setup.*', 5 | 'pyproject.toml')" 6 | 7 | name: From source(FreeBSD) / FreeBSD:14-amd64 8 | freebsd_instance: 9 | image_family: freebsd-14-2 10 | 11 | env: 12 | PH_FULL_ACTION: 1 13 | EXP_PH_LIBHEIF_VERSION: "" 14 | 15 | install_libheif_script: 16 | - pkg install -y gcc cmake aom x265 17 | - pkg install -y py311-pip 18 | - pkg install -y py311-pillow py311-numpy 19 | - python3.11 libheif/linux_build_libs.py 20 | install_pillow_heif_script: 21 | - python3.11 -m pip -v install --break-system-packages ".[tests-min]" 22 | libheif_info_script: 23 | - python3.11 -c "import pillow_heif; print(pillow_heif.libheif_info())" 24 | perform_tests_script: 25 | - python3.11 -m pytest 26 | -------------------------------------------------------------------------------- /docker/from_src/Almalinux_9.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM almalinux:9 as base 2 | 3 | RUN \ 4 | dnf install --nogpgcheck https://mirrors.rpmfusion.org/free/el/rpmfusion-free-release-$(rpm -E %rhel).noarch.rpm -y && \ 5 | dnf makecache && \ 6 | dnf install -y python3 python3-devel python3-pip cmake && \ 7 | dnf install -y x265-devel libaom-devel && \ 8 | dnf groupinstall -y 'Development Tools' 9 | 10 | RUN \ 11 | python3 -m pip install --upgrade pip 12 | 13 | FROM base as build_test 14 | 15 | COPY . /pillow_heif 16 | 17 | RUN \ 18 | python3 pillow_heif/libheif/linux_build_libs.py && \ 19 | python3 -m pip install -v "pillow_heif/.[tests]"; \ 20 | echo "**** Build Done ****" && \ 21 | python3 -c "import pillow_heif; print(pillow_heif.libheif_info())" && \ 22 | pytest pillow_heif && \ 23 | echo "**** Test Done ****" 24 | -------------------------------------------------------------------------------- /docker/from_src/Alpine_3_20.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.20 as base 2 | 3 | RUN \ 4 | apk add --no-cache \ 5 | python3-dev \ 6 | py3-pip \ 7 | alpine-sdk \ 8 | cmake \ 9 | nasm \ 10 | x265-dev \ 11 | libde265-dev \ 12 | py3-numpy \ 13 | py3-pillow 14 | 15 | FROM base as build_test 16 | 17 | COPY . /pillow_heif 18 | 19 | RUN \ 20 | python3 -m venv --system-site-packages myenv && \ 21 | source myenv/bin/activate && \ 22 | python3 pillow_heif/libheif/linux_build_libs.py && \ 23 | python3 -m pip install -v "pillow_heif/.[tests]"; \ 24 | echo "**** Build Done ****" && \ 25 | python3 -c "import pillow_heif; print(pillow_heif.libheif_info())" && \ 26 | pytest pillow_heif && \ 27 | echo "**** Test Done ****" 28 | -------------------------------------------------------------------------------- /docker/from_src/Alpine_3_21.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.21 as base 2 | 3 | RUN \ 4 | apk add --no-cache \ 5 | python3-dev \ 6 | py3-pip \ 7 | alpine-sdk \ 8 | libheif-dev \ 9 | py3-numpy \ 10 | py3-pillow 11 | 12 | FROM base as build_test 13 | 14 | COPY . /pillow_heif 15 | 16 | RUN \ 17 | python3 -m venv --system-site-packages myenv && \ 18 | source myenv/bin/activate && \ 19 | python3 -m pip install -v "pillow_heif/.[tests]"; \ 20 | echo "**** Build Done ****" && \ 21 | python3 -c "import pillow_heif; print(pillow_heif.libheif_info())" && \ 22 | pytest pillow_heif && \ 23 | echo "**** Test Done ****" 24 | -------------------------------------------------------------------------------- /docker/from_src/Debian_12.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:bookworm-slim as base 2 | 3 | RUN \ 4 | apt-get -qq update && \ 5 | apt-get -y -q install \ 6 | python3-pip \ 7 | python3-dev \ 8 | python3-setuptools \ 9 | libtiff5-dev libjpeg62-turbo-dev libopenjp2-7-dev zlib1g-dev \ 10 | libfreetype6-dev liblcms2-dev libwebp-dev tcl8.6-dev tk8.6-dev python3-tk \ 11 | libharfbuzz-dev libfribidi-dev libxcb1-dev \ 12 | libffi-dev \ 13 | libtool \ 14 | git \ 15 | cmake \ 16 | nasm \ 17 | wget \ 18 | libde265-dev \ 19 | libx265-dev 20 | 21 | FROM base as build_test 22 | 23 | COPY . /pillow_heif 24 | 25 | RUN \ 26 | python3 pillow_heif/libheif/linux_build_libs.py && \ 27 | python3 -m pip install -v --break-system-packages "pillow_heif/.[tests]"; \ 28 | echo "**** Build Done ****" && \ 29 | python3 -c "import pillow_heif; print(pillow_heif.libheif_info())" && \ 30 | pytest pillow_heif && \ 31 | echo "**** Test Done ****" 32 | -------------------------------------------------------------------------------- /docker/test_wheels.Dockerfile: -------------------------------------------------------------------------------- 1 | ARG BASE_IMAGE 2 | FROM $BASE_IMAGE 3 | 4 | ARG PREPARE_CMD 5 | RUN $PREPARE_CMD 6 | ARG INSTALL_CMD 7 | RUN $INSTALL_CMD 8 | 9 | COPY . /pillow_heif 10 | 11 | ARG EX_ARG 12 | ARG TEST_TYPE 13 | 14 | RUN \ 15 | $TEST_TYPE && \ 16 | python3 -m venv --system-site-packages venv && \ 17 | . venv/bin/activate && \ 18 | python3 -m pip install --upgrade pip || echo "pip upgrade failed" && \ 19 | python3 -m pip install --prefer-binary pillow && \ 20 | python3 -m pip install pytest pympler defusedxml && \ 21 | python3 -m pip install --only-binary=:all: numpy || true && \ 22 | python3 -m pip install $EX_ARG --no-deps --only-binary=:all: pillow_heif && \ 23 | \ 24 | python3 -m pytest -v pillow_heif && \ 25 | echo "**** Test Done ****" && \ 26 | python3 -m pip show pillow_heif 27 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | BUILDDIR = _build 9 | 10 | # Put it first so that "make" without argument is like "make help". 11 | help: 12 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 13 | 14 | .PHONY: help Makefile 15 | 16 | # Catch-all target: route all unknown targets to Sphinx using the new 17 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 18 | %: Makefile 19 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 20 | -------------------------------------------------------------------------------- /docs/benchmarks.rst: -------------------------------------------------------------------------------- 1 | Benchmarks 2 | ========== 3 | 4 | Decode benchmarks 5 | ----------------- 6 | 7 | Images info: 8 | 9 | pug.heic - 4032x3024 = 12MP, photo taken on iPhone with all metadata. 10 | 11 | cat.hif - 2832x4240 = 12MP, 10 bit image, with Exif and IPTC data. 12 | 13 | image_large.heic - 6000x8000 = 48MP, with Exif and IPTC data. 14 | 15 | Pillow load 16 | ^^^^^^^^^^^ 17 | 18 | .. image:: ../benchmarks/results_decode_pillow_load_Windows.png 19 | 20 | .. image:: ../benchmarks/results_decode_pillow_load_Linux.png 21 | 22 | .. image:: ../benchmarks/results_decode_pillow_load_macOS.png 23 | 24 | Numpy BGR 25 | ^^^^^^^^^ 26 | 27 | .. image:: ../benchmarks/results_decode_numpy_bgr_Windows.png 28 | 29 | .. image:: ../benchmarks/results_decode_numpy_bgr_Linux.png 30 | 31 | .. image:: ../benchmarks/results_decode_numpy_bgr_macOS.png 32 | 33 | Numpy RGB 34 | ^^^^^^^^^ 35 | 36 | .. image:: ../benchmarks/results_decode_numpy_rgb_Linux.png 37 | 38 | .. image:: ../benchmarks/results_decode_numpy_rgb_Windows.png 39 | 40 | .. image:: ../benchmarks/results_decode_numpy_rgb_macOS.png 41 | 42 | Encode benchmarks 43 | ----------------- 44 | 45 | All images with size 4096x4096 = 16MP, without exif, xmp or other data, except `pug.heic`. 46 | 47 | .. image:: ../benchmarks/results_encode_Windows.png 48 | 49 | .. image:: ../benchmarks/results_encode_Linux.png 50 | 51 | .. image:: ../benchmarks/results_encode_macOS.png 52 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | 13 | import os 14 | import sys 15 | 16 | sys.path.insert(0, os.path.abspath("../.")) 17 | 18 | import sphinx_rtd_theme # noqa 19 | 20 | import pillow_heif # noqa 21 | 22 | # -- Project information ----------------------------------------------------- 23 | 24 | project = "pillow-heif" 25 | copyright = "2021-2022, Alexander Piskun and Contributors" # noqa 26 | author = "Alexander Piskun and Contributors" 27 | 28 | # The short X.Y version. 29 | version = pillow_heif.__version__ 30 | # The full version, including alpha/beta/rc tags 31 | release = pillow_heif.__version__ 32 | 33 | # -- General configuration --------------------------------------------------- 34 | 35 | # If your documentation needs a minimal Sphinx version, state it here. 36 | # 37 | needs_sphinx = "4.4" 38 | 39 | # Add any Sphinx extension module names here, as strings. They can be 40 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 41 | # ones. 42 | extensions = [ 43 | "sphinx_issues", 44 | "sphinx.ext.autodoc", 45 | "sphinx.ext.intersphinx", 46 | "sphinx.ext.viewcode", 47 | ] 48 | 49 | intersphinx_mapping = { 50 | "python": ("https://docs.python.org/3", None), 51 | "pillow": ("https://pillow.readthedocs.io/en/stable", None), 52 | } 53 | 54 | # The suffix(es) of source filenames. 55 | # You can specify multiple suffix as a list of string: 56 | # source_suffix = ['.rst', '.md'] 57 | source_suffix = ".rst" 58 | 59 | # List of patterns, relative to source directory, that match files and 60 | # directories to ignore when looking for source files. 61 | # This pattern also affects html_static_path and html_extra_path. 62 | exclude_patterns = ["_build"] 63 | 64 | # The name of the Pygments (syntax highlighting) style to use. 65 | pygments_style = "sphinx" 66 | 67 | # If true, `todos` produce output, else they produce nothing. 68 | todo_include_todos = False 69 | 70 | # If true, Sphinx will warn about all references where the target cannot be found. 71 | # Default is False. You can activate this mode temporarily using the -n command-line 72 | # switch. 73 | nitpicky = True 74 | 75 | # -- Options for HTML output ------------------------------------------------- 76 | 77 | # The theme to use for HTML and HTML Help pages. See the documentation for 78 | # a list of builtin themes. 79 | # 80 | html_theme = "sphinx_rtd_theme" 81 | html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] 82 | 83 | html_logo = "resources/pillow-heif-logo.png" 84 | 85 | # Add any paths that contain custom static files (such as style sheets) here, 86 | # relative to this directory. They are copied after the builtin static files, 87 | # so a file named "default.css" will overwrite the builtin "default.css". 88 | html_static_path = ["resources"] 89 | 90 | 91 | def setup(app): 92 | app.add_js_file("js/script.js") 93 | app.add_css_file("css/styles.css") 94 | app.add_css_file("css/dark.css") 95 | app.add_css_file("css/light.css") 96 | 97 | 98 | # GitHub repo for sphinx-issues 99 | issues_github_path = "bigcat88/pillow_heif" 100 | 101 | # 'short' - Suppress the leading module names of the typehints (ex. io.StringIO -> StringIO) 102 | autodoc_typehints_format = "short" 103 | 104 | # Do not sort members by alphabet. 105 | autodoc_member_order = "bysource" 106 | -------------------------------------------------------------------------------- /docs/heif-file.rst: -------------------------------------------------------------------------------- 1 | Using HeifFile 2 | ============== 3 | 4 | Opening 5 | ------- 6 | 7 | .. code-block:: python 8 | 9 | if pillow_heif.is_supported("image.heif"): 10 | heif_file = pillow_heif.open_heif("image.heif") 11 | 12 | ``open_heif`` is preferred over ``read_heif``, it does not decode images immediately. 13 | All image data supports `lazy loading` and will be automatically decoded when you request it, 14 | e.g. when access to ``data`` property occurs. 15 | 16 | Creating from Pillow 17 | -------------------- 18 | 19 | .. code-block:: python 20 | 21 | heif_file = pillow_heif.from_pillow(Image.open("image.gif")): 22 | 23 | Creating from bytes 24 | ------------------- 25 | 26 | .. code-block:: python 27 | 28 | import cv2 # OpenCV 29 | 30 | cv_img = cv2.imread("image_16bit.png", cv2.IMREAD_UNCHANGED) 31 | heif_file = pillow_heif.from_bytes( 32 | mode="BGRA;16", 33 | size=(cv_img.shape[1], cv_img.shape[0]), 34 | data=bytes(cv_img) 35 | ) 36 | 37 | Enumerating images 38 | ------------------ 39 | 40 | .. code-block:: python 41 | 42 | print("number of images in file:", len(heif_file)) 43 | for img in heif_file: 44 | print(img) 45 | 46 | .. note:: ``HeifFile`` itself points to the primary image in the container. 47 | 48 | Adding images 49 | ------------- 50 | 51 | Add all images from second file: 52 | 53 | .. code-block:: python 54 | 55 | heif_file_to_add = pillow_heif.open_heif("file2.heif") 56 | heif_file.add_from_heif(heif_file_to_add) 57 | 58 | Add only first image from second file: 59 | 60 | .. code-block:: python 61 | 62 | heif_file_to_add = pillow_heif.open_heif("file2.heif") 63 | heif_file.add_from_heif(heif_file_to_add[0]) 64 | 65 | Add image from Pillow: 66 | 67 | .. code-block:: python 68 | 69 | heif_file.add_from_pillow(Image.open("file2.jpg")) 70 | 71 | Add image from bytes: 72 | 73 | .. code-block:: python 74 | 75 | heif_file.add_frombytes( 76 | mode="BGRA", # depends on image in `cv_img` 77 | size=(cv_img.shape[1], cv_img.shape[0]), 78 | data=bytes(cv_img) 79 | ) 80 | 81 | Removing images 82 | --------------- 83 | 84 | Remove image at position with index ``0``: 85 | 86 | .. code-block:: python 87 | 88 | del heif_file[0] 89 | 90 | Saving 91 | ------ 92 | 93 | Refer to :py:meth:`~pillow_heif.HeifFile.save` to see what additional parameters is supported and to :ref:`saving-images`. 94 | 95 | .. code-block:: python 96 | 97 | heif_file.save("output.heif", quality=-1) 98 | 99 | .. code-block:: python 100 | 101 | cv_img = cv2.imread("images/non_heif/RGBA_16__128x128.png", cv2.IMREAD_UNCHANGED) 102 | pillow_heif.encode( 103 | mode="BGRA;16", 104 | size=(cv_img.shape[1], cv_img.shape[0]), 105 | data=bytes(cv_img), 106 | fp="RGBA_10bit.heic", 107 | quality=-1) 108 | 109 | .. _image_data: 110 | 111 | Accessing image data 112 | -------------------- 113 | 114 | Decoded image data available throw :py:attr:`~pillow_heif.HeifImage.data` property. 115 | 116 | Accessing `Primary` image in a file: 117 | 118 | .. code-block:: python 119 | 120 | print(len(heif_file.data)) 121 | 122 | Or you can access image by index: 123 | 124 | .. code-block:: python 125 | 126 | print(len(heif_file[0].data)) 127 | 128 | Numpy array interface 129 | --------------------- 130 | 131 | Next code gets decoded primary image data as a numpy array(in the same format as ``Pillow`` does): 132 | 133 | .. code-block:: python 134 | 135 | heif_file = pillow_heif.open_heif("file.heif") 136 | np_array = np.asarray(heif_file) 137 | 138 | Accessing image by index(for multi-frame images): 139 | 140 | .. code-block:: python 141 | 142 | heif_file = pillow_heif.open_heif("file.heif") 143 | np_array = np.asarray(heif_file[0]) # accessing image by index. 144 | 145 | After that you can load it at any library that supports numpy arrays. 146 | -------------------------------------------------------------------------------- /docs/image-modes.rst: -------------------------------------------------------------------------------- 1 | .. _image-modes: 2 | 3 | Modes 4 | ===== 5 | 6 | Possible :py:attr:`~pillow_heif.HeifImage.mode` modes are: 7 | 8 | * ``BGRA;16`` or ``BGRa;16`` 9 | * ``BGR;16`` 10 | * ``RGBA;16`` or ``RGBa;16`` 11 | * ``RGB;16`` 12 | * ``L;16`` 13 | * ``LA;16`` 14 | * ``I;16`` 15 | * ``I;16L`` 16 | * ``BGRA`` or ``BGRa`` 17 | * ``BGR`` 18 | * ``RGBA`` or ``RGBa`` 19 | * ``RGB`` 20 | * ``LA`` 21 | * ``L`` 22 | 23 | .. note:: By default ``convert_hdr_to_8bit`` parameter in ``open_heif`` is ``True`` so all images will be opened as a 8 bit ones. 24 | To open images in `BGR` mode, set ``bgr_mode`` to `True` in ``open_heif/read_heif``. 25 | 26 | Starting from `0.11.2` version you can set ``hdr_to_16bit`` to `False` in ``open_heif/read_heif`` to get 10/12 bit images without bits shifted to 16 bit. 27 | Those will be RGB(A);10(12), BGR(A);10(12) modes instead of RGB(A);16, BGR(A);16. 28 | 29 | When saving image from `Pillow` to `HEIF` format, next modes will be converted automatically before encoding: 30 | 31 | * ``P`` with transparency will be converted to ``RGBA`` 32 | * ``P`` will be converted to ``RGB`` 33 | * ``I`` will be converted to ``I;16L`` 34 | * ``1`` will be converted to ``L`` 35 | * ``CMYK`` will be converted to ``RGBA`` 36 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. pillow-heif documentation master file, created by 2 | sphinx-quickstart on Sat Apr 23 12:03:22 2022. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Pillow-Heif documentation 7 | ========================= 8 | 9 | | Pillow-Heif is a library for reading & writing **HEIF** image files. 10 | | It can be used as a Pillow's plugin or be used as a standalone. 11 | | 12 | | As underlying layer it uses `libheif `_ library. 13 | 14 | .. toctree:: 15 | :maxdepth: 1 16 | 17 | installation.rst 18 | pillow-plugin.rst 19 | heif-file.rst 20 | image-modes.rst 21 | options.rst 22 | saving-images.rst 23 | reference/index.rst 24 | workaround-orientation.rst 25 | benchmarks.rst 26 | 27 | Indices and tables 28 | ================== 29 | 30 | * :ref:`genindex` 31 | * :ref:`modindex` 32 | * :ref:`search` 33 | -------------------------------------------------------------------------------- /docs/installation.rst: -------------------------------------------------------------------------------- 1 | Installation 2 | ============ 3 | 4 | Basic Installation 5 | ------------------ 6 | 7 | .. note:: 8 | 9 | This is a recommended way of installation for use. 10 | 11 | Install Pillow-Heif with :command:`pip`:: 12 | 13 | python3 -m pip install --upgrade pip 14 | python3 -m pip install --upgrade pillow-heif 15 | 16 | 17 | Wheels are present for most popular systems with help of `cibuildwheel `_ 18 | 19 | Building From Source 20 | -------------------- 21 | 22 | .. role:: bash(code) 23 | :language: bash 24 | 25 | Linux 26 | ^^^^^ 27 | 28 | .. note:: 29 | 30 | | Here is a 31 | `GH Action `_ 32 | and in `docker/from_src `_ folder there are docker files for different Linuxes with examples 33 | how to build from source. 34 | | 35 | | **And of course you can build your own libheif library with your preferred encoders and decoders and use what you like.** 36 | 37 | There is many different ways how to build it from source. Main requirements are: 38 | * ``libheif`` should be version >= ``1.17.0`` version(recommended version is ``1.17.3`` or higher). 39 | * ``x265`` should support 10 - 12 bit encoding(if you want to save in that bitness) 40 | * ``aom`` should be >= ``3.3.0`` version 41 | * ``libde265`` should be >= ``1.0.8`` version 42 | 43 | 44 | On `Ubuntu`: 45 | 46 | | :bash:`sudo add-apt-repository ppa:strukturag/libheif` 47 | | :bash:`sudo apt update` 48 | | :bash:`sudo apt -y install libheif-dev` 49 | 50 | On `Alpine 19`: 51 | 52 | | :bash:`sudo apk add --no-cache libheif-dev` 53 | 54 | Now install Pillow-Heif with:: 55 | 56 | python3 -m pip install --upgrade pillow-heif --no-binary :all: 57 | 58 | or from within the uncompressed source directory:: 59 | 60 | python3 -m pip install . 61 | 62 | .. note:: 63 | 64 | Refer to `libheif repo `_ for additional information of how to build it with what features you want. 65 | 66 | *If you have questions about build from sources you can ask them in discussions or create an issue.* 67 | 68 | FreeBSD 69 | ^^^^^^^ 70 | 71 | `Action to test build on FreeBSD from source `_ 72 | 73 | Since Python itself does not support binary wheels for BSD systems, you should install libheif and then simply install Pillow-Heif from source. 74 | 75 | Install `gcc`, `cmake`, `aom` and `x265`:: 76 | 77 | - pkg install -y gcc cmake aom x265 78 | - pkg install -y py39-pip 79 | - pkg install -y py39-pillow py39-numpy 80 | - python3 libheif/linux_build_libs.py 81 | 82 | Install Python and Pillow:: 83 | 84 | pkg install -y py39-pip 85 | pkg install -y py39-pillow 86 | 87 | Install Pillow-Heif:: 88 | 89 | python3 -m pip install . 90 | 91 | macOS 92 | ^^^^^ 93 | 94 | `GA Action to test build on macOS from source `_ 95 | 96 | First install `Homebrew `_, if it is not installed and run:: 97 | 98 | brew install x265 libjpeg libde265 libheif 99 | python3 -m pip install --upgrade pip 100 | 101 | Now install Pillow-Heif with:: 102 | 103 | python3 -m pip install --upgrade pillow-heif --no-binary :all: 104 | 105 | or from within the uncompressed source directory:: 106 | 107 | python3 -m pip install . 108 | 109 | Windows 110 | ^^^^^^^ 111 | 112 | `GA Action to test build on Windows from source `_ 113 | 114 | .. note:: 115 | | On Windows, use prebuilt binaries. Installing from source on Windows is tricky. 116 | | First install `msys2 `_, if it is not installed. 117 | | By default, build script assumes that **msys2** builds libs in :bash:`C:/msys64/mingw64` 118 | | You can set **MSYS2_PREFIX** environment variable to your custom path, e.g.: 119 | | :bash:`setx MSYS2_PREFIX "D:/msys64/mingw64"` 120 | 121 | Using **msys2** terminal change working directory and install `libheif`:: 122 | 123 | cd .../pillow_heif/libheif/windows/mingw-w64-libheif 124 | makepkg-mingw --syncdeps 125 | pacman -U mingw-w64-x86_64-libheif-*-any.pkg.tar.zst 126 | 127 | .. note:: 128 | This is needed, so we dont want to `dav1d`, `rav1e` or `libSvtAv1Enc` to be installed as the dependencies. 129 | 130 | Now inside Pillow-Heif directory install it with pip from source:: 131 | 132 | python -m pip install . 133 | 134 | | After that copy **libheif.dll**, **libaom.dll**, **libde265-0.dll**, **libx265.dll**, 135 | **libgcc_s_seh-1.dll**, **libstdc++-6.dll** and **libwinpthread-1.dll** from 136 | *msys64\\mingw64\\bin* to python site-packages root. 137 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set BUILDDIR=build 11 | 12 | if "%1" == "" goto help 13 | 14 | %SPHINXBUILD% >NUL 2>NUL 15 | if errorlevel 9009 ( 16 | echo. 17 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 18 | echo.installed, then set the SPHINXBUILD environment variable to point 19 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 20 | echo.may add the Sphinx directory to PATH. 21 | echo. 22 | echo.If you don't have Sphinx installed, grab it from 23 | echo.https://www.sphinx-doc.org/ 24 | exit /b 1 25 | ) 26 | 27 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 28 | goto end 29 | 30 | :help 31 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 32 | 33 | :end 34 | popd 35 | -------------------------------------------------------------------------------- /docs/options.rst: -------------------------------------------------------------------------------- 1 | .. _options: 2 | 3 | Options 4 | ------- 5 | 6 | .. autodata:: pillow_heif.options.DECODE_THREADS 7 | .. autodata:: pillow_heif.options.THUMBNAILS 8 | .. autodata:: pillow_heif.options.DEPTH_IMAGES 9 | .. autodata:: pillow_heif.options.QUALITY 10 | .. autodata:: pillow_heif.options.SAVE_HDR_TO_12_BIT 11 | .. autodata:: pillow_heif.options.ALLOW_INCORRECT_HEADERS 12 | .. autodata:: pillow_heif.options.SAVE_NCLX_PROFILE 13 | .. autodata:: pillow_heif.options.PREFERRED_ENCODER 14 | .. autodata:: pillow_heif.options.PREFERRED_DECODER 15 | 16 | Example of use 17 | """""""""""""" 18 | 19 | .. code-block:: python 20 | 21 | import pillow_heif 22 | 23 | pillow_heif.options.THUMBNAILS = False 24 | pillow_heif.options.DECODE_THREADS = 1 25 | 26 | Overriding multiple options at once 27 | """"""""""""""""""""""""""""""""""" 28 | 29 | When registering a Pillow plugin with :py:func:`pillow_heif.register_heif_opener` 30 | 31 | .. code-block:: python 32 | 33 | register_heif_opener(thumbnails=False, quality=100, decode_threads=10) 34 | -------------------------------------------------------------------------------- /docs/pillow-plugin.rst: -------------------------------------------------------------------------------- 1 | Pillow Plugin 2 | ============= 3 | 4 | For using it as a Pillow plugin, refer to Pillow's documentation: 5 | `Pillow Tutorial `_ 6 | and to examples started with ``pillow_``. 7 | 8 | Here are described only some differences and peculiarities. 9 | 10 | .. _registering-plugin: 11 | 12 | Registering plugin 13 | ****************** 14 | 15 | There are two ways to register it as a plugin, here are both of them: 16 | 17 | Automatic 18 | """"""""" 19 | 20 | .. code-block:: python 21 | 22 | from PIL import Image, ImageFilter 23 | from pillow_heif import HeifImagePlugin 24 | 25 | with Image.open("image.heic") as im: 26 | im.filter(filter=ImageFilter.BLUR).save("blurred_image.heic") 27 | 28 | Manual 29 | """""" 30 | 31 | .. code-block:: python 32 | 33 | from PIL import Image 34 | from pillow_heif import register_heif_opener 35 | 36 | register_heif_opener() 37 | with Image.open("image.heic") as im: 38 | im.rotate(45).save("rotated_image.heic") 39 | 40 | Tips & Tricks 41 | """"""""""""" 42 | 43 | If you do not need HEIF thumbnails functionality, then it is a good idea 44 | to disable them during plugin registration: 45 | 46 | .. code-block:: python 47 | 48 | register_heif_opener(thumbnails=False) 49 | 50 | Remember, then you can pass multiply config values to :py:func:`~pillow_heif.register_heif_opener` at once: 51 | 52 | .. code-block:: python 53 | 54 | register_heif_opener(thumbnails=False, quality=-1) 55 | 56 | Image Modes 57 | *********** 58 | 59 | Currently all images are opened in ``RGB`` or ``RGBA`` 8 bit modes. 60 | There is a restriction in ``libheif`` that we cant check before decoding if an image is ``monochrome`` or not. 61 | 62 | See :ref:`image-modes` for a list of supported modes for saving. 63 | 64 | Metadata 65 | ******** 66 | 67 | Available metadata are stored in ``info`` dictionary as in other Pillow plugins. 68 | 69 | It is the same as in :py:class:`~pillow_heif.HeifImage` class. 70 | 71 | During saving operation all known metadata in ``info`` dictionary are **saved**. 72 | So it can be edited in place. 73 | 74 | Removing EXIF and XMP information inside ``info`` dictionary: 75 | 76 | .. code-block:: python 77 | 78 | image = Image.open(Path("test.heic")) 79 | del image.info["exif"] 80 | del image.info["xmp"] 81 | image.save("output.heic") 82 | 83 | Removing EXIF and XMP specifying them when calling ``save``: 84 | 85 | .. code-block:: python 86 | 87 | image = Image.open(Path("test.heic")) 88 | image.save("output.heic", exif=None, xmp=None) 89 | 90 | Limitations of second code variant is that when file has multiply images inside, 91 | setting ``exif`` or ``xmp`` during ``save`` affects only Primary(Main) image and not all images. 92 | 93 | To edit metadata of all images in a file just iterate throw all images and change metadata in place. 94 | 95 | Here are two ways as an example: 96 | 97 | Edit ``info["exif"]`` field of each copy of image: 98 | 99 | .. code-block:: python 100 | 101 | heic_pillow = Image.open(Path("test.heic")) 102 | output_wo_exif = [] 103 | for frame in ImageSequence.Iterator(heic_pillow): 104 | copied_frame = frame.copy() 105 | copied_frame.info["exif"] = None 106 | output_wo_exif.append(copied_frame) 107 | empty_pillow = Image.new("P", (0, 0)) 108 | empty_pillow.save("no_exif.heic", save_all=True, append_images=output_wo_exif) 109 | 110 | Or editing ``info["exif"]`` in place: 111 | 112 | .. code-block:: python 113 | 114 | heic_pillow = Image.open(Path("test.heic")) 115 | for frame in ImageSequence.Iterator(heic_pillow): 116 | frame.info["exif"] = None 117 | heic_pillow.save("no_exif.heic", save_all=True) 118 | 119 | Save operation 120 | ************** 121 | 122 | For `HEIF` next extensions are registered: ``.heic``, ``.heics``, ``.heif``, ``.heifs`` and ``.hif`` 123 | 124 | Also images can be saved to memory, using ``format`` parameter: 125 | 126 | .. code-block:: python 127 | 128 | output_buffer = BytesIO() 129 | with Image.open("image.heic") as im: 130 | im.save(output_buffer, format="HEIF") 131 | 132 | See here :ref:`save-parameters` for additional information. 133 | 134 | Changing order of images 135 | ************************ 136 | 137 | There is no such easy way to change order as for `HeifFile` usage, but the standard Pillow way to do so looks fine. 138 | Let's create image where second image will be primary: 139 | 140 | .. code-block:: python 141 | 142 | img1 = Image.open(Path("images/jpeg_gif_png/1.png")) 143 | img2 = Image.open(Path("images/jpeg_gif_png/2.png")) 144 | img3 = Image.open(Path("images/jpeg_gif_png/3.png")) 145 | img1.save("1_2P_3.heic", append_images=[img2, img3], save_all=True, primary_index=1, quality=-1) 146 | 147 | Now as example lets change primary image in a HEIC file: 148 | 149 | .. code-block:: python 150 | 151 | img1 = Image.open(Path("1_2P_3.heic")) 152 | img1.save("1_2_3P.heic", save_all=True, primary_index=-1, quality=-1) 153 | 154 | .. note:: 155 | 156 | As a ``primary`` field are in `info` dictionary, you can change it in a place like with metadata before. 157 | 158 | And here is an example how we can change order of images in container: 159 | 160 | .. code-block:: python 161 | 162 | src_img = Image.open(Path("1_2_3P.heic")) 163 | img3 = ImageSequence.Iterator(src_img)[2].copy() 164 | img2 = ImageSequence.Iterator(src_img)[1].copy() 165 | img1 = ImageSequence.Iterator(src_img)[0].copy() 166 | img3.save("3P_1_2.heic", save_all=True, append_images=[img1, img2], quality=-1) 167 | -------------------------------------------------------------------------------- /docs/reference/API.rst: -------------------------------------------------------------------------------- 1 | .. py:currentmodule:: pillow_heif 2 | 3 | Public API 4 | ========== 5 | 6 | Opening HEIF file 7 | ----------------- 8 | 9 | .. autofunction:: is_supported 10 | .. autofunction:: open_heif 11 | .. autofunction:: read_heif 12 | .. autofunction:: from_pillow 13 | .. autofunction:: from_bytes 14 | .. autofunction:: encode 15 | 16 | Low Level API 17 | ------------- 18 | 19 | .. autofunction:: get_file_mimetype 20 | .. autofunction:: set_orientation 21 | -------------------------------------------------------------------------------- /docs/reference/HeifFile.rst: -------------------------------------------------------------------------------- 1 | .. py:currentmodule:: pillow_heif 2 | 3 | HeifFile object 4 | =============== 5 | 6 | The :py:class:`~pillow_heif.HeifFile` provide all necessary methods for HEIF image manipulations. 7 | 8 | .. autoclass:: HeifFile 9 | :members: 10 | -------------------------------------------------------------------------------- /docs/reference/HeifImage.rst: -------------------------------------------------------------------------------- 1 | .. py:currentmodule:: pillow_heif 2 | 3 | HeifImage object 4 | ================ 5 | 6 | .. autoclass:: pillow_heif.HeifImage 7 | :show-inheritance: 8 | :inherited-members: 9 | :members: 10 | 11 | .. py:attribute:: info["exif"] 12 | :type: bytes 13 | 14 | .. note:: In HEIF `orientation` tag is only for information purposes and must not be used to rotate image. 15 | 16 | EXIF metadata. Can be `None` 17 | 18 | .. py:attribute:: info["xmp"] 19 | :type: bytes 20 | 21 | XMP metadata. String in bytes in UTF-8 encoding. Absent if `xmp` data is missing. 22 | 23 | .. py:attribute:: info["metadata"] 24 | :type: list[dict] 25 | 26 | Other metadata(IPTC for example). List of dictionaries. Usual will be empty. Keys: 27 | 28 | * `type`: str 29 | * `content_type`: str 30 | * `data`: bytes 31 | 32 | .. py:attribute:: info["primary"] 33 | :type: bool 34 | 35 | A boolean value that specifies whether the image is the main image when the file 36 | contains more than one image. 37 | 38 | .. py:attribute:: info["bit_depth"] 39 | :type: int 40 | 41 | Shows the bit-depth of image in file(not the decoded one, so it may differs from bit depth of mode). 42 | Possible values: 8, 10 and 12. 43 | 44 | .. py:attribute:: info["thumbnails"] 45 | :type: list[int] 46 | 47 | List of thumbnail boxes sizes. Can be empty. 48 | 49 | .. py:attribute:: info["icc_profile"] 50 | :type: bytes 51 | 52 | ICC Profile. Can be absent. Can be empty. 53 | 54 | .. py:attribute:: info["icc_profile_type"] 55 | :type: str 56 | 57 | Possible values: ``prof`` or ``rICC``. Can be absent. 58 | 59 | .. py:attribute:: info["nclx_profile"] 60 | :type: dict 61 | 62 | NCLX color profile. Can be absent. Keys: 63 | 64 | * `color_primaries`: :py:class:`HeifColorPrimaries` 65 | * `transfer_characteristics`: :py:class:`HeifTransferCharacteristics` 66 | * `matrix_coefficients`: :py:class:`HeifMatrixCoefficients` 67 | * `full_range_flag`: `bool` 68 | 69 | .. py:attribute:: info["depth_images"] 70 | :type: list 71 | 72 | List of :py:class:`~pillow_heif.heif.HeifDepthImage` if any present for image. 73 | Currently `libheif` does not support writing of them, only reading. 74 | 75 | .. autoclass:: pillow_heif.heif.BaseImage 76 | :show-inheritance: 77 | :inherited-members: 78 | :members: 79 | 80 | .. autoclass:: pillow_heif.heif.HeifDepthImage 81 | :show-inheritance: 82 | :inherited-members: 83 | :members: 84 | 85 | .. py:attribute:: info["metadata"] 86 | :type: dict 87 | 88 | Represents `libheif` ``heif_depth_representation_info`` struct as a dictionary. 89 | 90 | If someone have an example when this struct got filled let me know. 91 | -------------------------------------------------------------------------------- /docs/reference/HeifImagePlugin.rst: -------------------------------------------------------------------------------- 1 | Pillow Plugin 2 | ============= 3 | 4 | HeifImageFile object 5 | -------------------- 6 | 7 | | Plugin supports decoding and encoding multiply image frames. 8 | | How to register it see: :ref:`registering-plugin` 9 | | It supports all functionality, that supported by other Pillow's image plugins. 10 | 11 | .. autoclass:: pillow_heif.as_plugin._LibHeifImageFile 12 | :show-inheritance: 13 | :members: 14 | 15 | .. py:attribute:: info 16 | :type: dict 17 | 18 | A dictionary holding data associated with the image. 19 | 20 | .. note:: 21 | Known to this plugin keys and values in dictionary will be saved to the image. 22 | They are the same as in :py:class:`~pillow_heif.HeifImage` class. 23 | 24 | Specific keys for this plugin that is always present are: 25 | exif, xmp, metadata, primary, bit_depth, thumbnails 26 | Optional there can be also such keys: 27 | icc_profile, icc_profile_type, nclx_profile 28 | 29 | .. py:method:: get_format_mimetype 30 | 31 | Returns the same as :py:func:`~pillow_heif.get_file_mimetype` 32 | 33 | .. autoclass:: pillow_heif.HeifImageFile 34 | :show-inheritance: 35 | 36 | .. autofunction:: pillow_heif.register_heif_opener 37 | -------------------------------------------------------------------------------- /docs/reference/constants.rst: -------------------------------------------------------------------------------- 1 | .. py:currentmodule:: pillow_heif 2 | 3 | Constants 4 | --------- 5 | 6 | .. autoclass:: HeifColorPrimaries 7 | :members: 8 | 9 | .. autoclass:: HeifTransferCharacteristics 10 | :members: 11 | 12 | .. autoclass:: HeifMatrixCoefficients 13 | :members: 14 | 15 | .. autoclass:: HeifDepthRepresentationType 16 | :members: 17 | -------------------------------------------------------------------------------- /docs/reference/index.rst: -------------------------------------------------------------------------------- 1 | Reference 2 | ========= 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | 7 | HeifImagePlugin 8 | API 9 | HeifFile 10 | HeifImage 11 | constants 12 | links 13 | -------------------------------------------------------------------------------- /docs/reference/links.rst: -------------------------------------------------------------------------------- 1 | Useful Links 2 | ============ 3 | 4 | .. _hevc-encoder: 5 | 6 | HEVC encoder 7 | ------------ 8 | `x265 documentation `_ 9 | -------------------------------------------------------------------------------- /docs/resources/css/light.css: -------------------------------------------------------------------------------- 1 | @media (prefers-color-scheme: light) { 2 | 3 | .wy-menu-vertical li.toctree-l2.current a, 4 | .wy-menu-vertical li.toctree-l3.current a { 5 | background-color: #c9c9c9; 6 | } 7 | 8 | } 9 | -------------------------------------------------------------------------------- /docs/resources/css/styles.css: -------------------------------------------------------------------------------- 1 | th p { 2 | margin-bottom: 0; 3 | } 4 | 5 | .rst-content tr .line-block { 6 | font-size: 1rem; 7 | margin-bottom: 0; 8 | } 9 | -------------------------------------------------------------------------------- /docs/resources/js/script.js: -------------------------------------------------------------------------------- 1 | jQuery(document).ready(function ($) { 2 | setTimeout(function () { 3 | var sectionID = 'base'; 4 | var search = function ($section, $sidebarItem) { 5 | $section.children('.section, .function, .method').each(function () { 6 | if ($(this).hasClass('section')) { 7 | sectionID = $(this).attr('id'); 8 | search($(this), $sidebarItem.parent().find('[href="#'+sectionID+'"]')); 9 | } else { 10 | var $dt = $(this).children('dt'); 11 | var id = $dt.attr('id'); 12 | if (id === undefined) { 13 | return; 14 | } 15 | 16 | var $functionsUL = $sidebarItem.siblings('[data-sectionID='+sectionID+']'); 17 | if (!$functionsUL.length) { 18 | $functionsUL = $('