mirror of
				https://github.com/python-pillow/Pillow.git
				synced 2025-10-31 16:07:30 +03:00 
			
		
		
		
	Add AVIF plugin (using libavif)
This commit is contained in:
		
							parent
							
								
									11c654c187
								
							
						
					
					
						commit
						3878b588a4
					
				|  | @ -23,7 +23,8 @@ if [[ $(uname) != CYGWIN* ]]; then | |||
|     sudo apt-get -qq install libfreetype6-dev liblcms2-dev python3-tk\ | ||||
|                              ghostscript libjpeg-turbo-progs libopenjp2-7-dev\ | ||||
|                              cmake meson imagemagick libharfbuzz-dev libfribidi-dev\ | ||||
|                              sway wl-clipboard libopenblas-dev | ||||
|                              sway wl-clipboard libopenblas-dev\ | ||||
|                              ninja-build build-essential nasm | ||||
| fi | ||||
| 
 | ||||
| python3 -m pip install --upgrade pip | ||||
|  | @ -62,6 +63,9 @@ if [[ $(uname) != CYGWIN* ]]; then | |||
|     # raqm | ||||
|     pushd depends && ./install_raqm.sh && popd | ||||
| 
 | ||||
|     # libavif | ||||
|     pushd depends && ./install_libavif.sh && popd | ||||
| 
 | ||||
|     # extra test images | ||||
|     pushd depends && ./install_extra_test_images.sh && popd | ||||
| else | ||||
|  |  | |||
							
								
								
									
										9
									
								
								.github/workflows/macos-install.sh
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										9
									
								
								.github/workflows/macos-install.sh
									
									
									
									
										vendored
									
									
								
							|  | @ -13,7 +13,11 @@ brew install \ | |||
|     libtiff \ | ||||
|     little-cms2 \ | ||||
|     openjpeg \ | ||||
|     webp | ||||
|     webp \ | ||||
|     dav1d \ | ||||
|     aom \ | ||||
|     rav1e \ | ||||
|     ninja | ||||
| if [[ "$ImageOS" == "macos13" ]]; then | ||||
|     brew install --ignore-dependencies libraqm | ||||
| else | ||||
|  | @ -31,5 +35,8 @@ python3 -m pip install -U pytest-timeout | |||
| python3 -m pip install pyroma | ||||
| python3 -m pip install numpy | ||||
| 
 | ||||
| # libavif | ||||
| pushd depends && ./install_libavif.sh && popd | ||||
| 
 | ||||
| # extra test images | ||||
| pushd depends && ./install_extra_test_images.sh && popd | ||||
|  |  | |||
							
								
								
									
										1
									
								
								.github/workflows/test-mingw.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.github/workflows/test-mingw.yml
									
									
									
									
										vendored
									
									
								
							|  | @ -61,6 +61,7 @@ jobs: | |||
|               mingw-w64-x86_64-libimagequant \ | ||||
|               mingw-w64-x86_64-libjpeg-turbo \ | ||||
|               mingw-w64-x86_64-libraqm \ | ||||
|               mingw-w64-x86_64-libavif \ | ||||
|               mingw-w64-x86_64-libtiff \ | ||||
|               mingw-w64-x86_64-libwebp \ | ||||
|               mingw-w64-x86_64-openjpeg2 \ | ||||
|  |  | |||
							
								
								
									
										14
									
								
								.github/workflows/test-windows.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										14
									
								
								.github/workflows/test-windows.yml
									
									
									
									
										vendored
									
									
								
							|  | @ -86,6 +86,8 @@ jobs: | |||
|         choco install nasm --no-progress | ||||
|         echo "C:\Program Files\NASM" >> $env:GITHUB_PATH | ||||
| 
 | ||||
|         python -m pip install meson | ||||
| 
 | ||||
|         choco install ghostscript --version=10.4.0 --no-progress | ||||
|         echo "C:\Program Files\gs\gs10.04.0\bin" >> $env:GITHUB_PATH | ||||
| 
 | ||||
|  | @ -137,6 +139,18 @@ jobs: | |||
|       if: steps.build-cache.outputs.cache-hit != 'true' | ||||
|       run: "& winbuild\\build\\build_dep_libpng.cmd" | ||||
| 
 | ||||
|     - name: Build dependencies / rav1e | ||||
|       if: steps.build-cache.outputs.cache-hit != 'true' | ||||
|       run: "& winbuild\\build\\build_dep_rav1e.cmd" | ||||
| 
 | ||||
|     - name: Build dependencies / meson | ||||
|       if: steps.build-cache.outputs.cache-hit != 'true' | ||||
|       run: "& winbuild\\build\\install_meson.cmd" | ||||
| 
 | ||||
|     - name: Build dependencies / libavif | ||||
|       if: steps.build-cache.outputs.cache-hit != 'true' | ||||
|       run: "& winbuild\\build\\build_dep_libavif.cmd" | ||||
| 
 | ||||
|     # for FreeType WOFF2 font support | ||||
|     - name: Build dependencies / brotli | ||||
|       if: steps.build-cache.outputs.cache-hit != 'true' | ||||
|  |  | |||
							
								
								
									
										82
									
								
								.github/workflows/wheels-dependencies.sh
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										82
									
								
								.github/workflows/wheels-dependencies.sh
									
									
									
									
										vendored
									
									
								
							|  | @ -37,6 +37,8 @@ LIBWEBP_VERSION=1.4.0 | |||
| BZIP2_VERSION=1.0.8 | ||||
| LIBXCB_VERSION=1.17.0 | ||||
| BROTLI_VERSION=1.1.0 | ||||
| LIBAVIF_VERSION=1.1.1 | ||||
| RAV1E_VERSION=0.7.1 | ||||
| 
 | ||||
| function build_brotli { | ||||
|     local cmake=$(get_modern_cmake) | ||||
|  | @ -63,6 +65,71 @@ function build_harfbuzz { | |||
|     fi | ||||
| } | ||||
| 
 | ||||
| function install_rav1e { | ||||
|     if [[ -n "$IS_MACOS" ]] && [[ "$PLAT" == "arm64" ]]; then | ||||
|         librav1e_tgz=librav1e-${RAV1E_VERSION}-macos-aarch64.tar.gz | ||||
|     elif [ -n "$IS_MACOS" ]; then | ||||
|         librav1e_tgz=librav1e-${RAV1E_VERSION}-macos.tar.gz | ||||
|     elif [ "$PLAT" == "aarch64" ]; then | ||||
|         librav1e_tgz=librav1e-${RAV1E_VERSION}-linux-aarch64.tar.gz | ||||
|     else | ||||
|         librav1e_tgz=librav1e-${RAV1E_VERSION}-linux-generic.tar.gz | ||||
|     fi | ||||
| 
 | ||||
|     curl -sLo - \ | ||||
|         https://github.com/xiph/rav1e/releases/download/v$RAV1E_VERSION/$librav1e_tgz \ | ||||
|         | tar -C $BUILD_PREFIX --exclude LICENSE --exclude LICENSE --exclude '*.so' --exclude '*.dylib' -zxf - | ||||
| 
 | ||||
|     if [ ! -n "$IS_MACOS" ]; then | ||||
|         sed -i 's/-lgcc_s/-lgcc_eh/g' "${BUILD_PREFIX}/lib/pkgconfig/rav1e.pc" | ||||
|     fi | ||||
| 
 | ||||
|     # Force libavif to treat system rav1e as if it were local | ||||
|     mkdir -p /tmp/cmake/Modules | ||||
|     cat <<EOF > /tmp/cmake/Modules/Findrav1e.cmake | ||||
|     add_library(rav1e::rav1e STATIC IMPORTED GLOBAL) | ||||
|     set_target_properties(rav1e::rav1e PROPERTIES | ||||
|         IMPORTED_LOCATION "$BUILD_PREFIX/lib/librav1e.a" | ||||
|         AVIF_LOCAL ON | ||||
|         INTERFACE_INCLUDE_DIRECTORIES "$BUILD_PREFIX/include/rav1e" | ||||
|     ) | ||||
| EOF | ||||
| } | ||||
| 
 | ||||
| function build_libavif { | ||||
|     install_rav1e | ||||
|     python -m pip install meson ninja | ||||
| 
 | ||||
|     if [[ "$PLAT" == "x86_64" ]]; then | ||||
|         build_simple nasm 2.15.05 https://www.nasm.us/pub/nasm/releasebuilds/2.15.05/ | ||||
|     fi | ||||
| 
 | ||||
|     local cmake=$(get_modern_cmake) | ||||
|     local out_dir=$(fetch_unpack https://github.com/AOMediaCodec/libavif/archive/refs/tags/v$LIBAVIF_VERSION.tar.gz libavif-$LIBAVIF_VERSION.tar.gz) | ||||
| 
 | ||||
|     (cd $out_dir \ | ||||
|         && $cmake \ | ||||
|             -DCMAKE_INSTALL_PREFIX=$BUILD_PREFIX \ | ||||
|             -DCMAKE_INSTALL_NAME_DIR=$BUILD_PREFIX/lib \ | ||||
|             -DCMAKE_BUILD_TYPE=Release \ | ||||
|             -DBUILD_SHARED_LIBS=OFF \ | ||||
|             -DAVIF_LIBSHARPYUV=LOCAL \ | ||||
|             -DAVIF_LIBYUV=LOCAL \ | ||||
|             -DAVIF_CODEC_RAV1E=SYSTEM \ | ||||
|             -DAVIF_CODEC_AOM=LOCAL \ | ||||
|             -DAVIF_CODEC_DAV1D=LOCAL \ | ||||
|             -DAVIF_CODEC_SVT=LOCAL \ | ||||
|             -DENABLE_NASM=ON \ | ||||
|             -DCMAKE_MODULE_PATH=/tmp/cmake/Modules \ | ||||
|             . \ | ||||
|         && make install) | ||||
| 
 | ||||
|     if [[ "$MB_ML_LIBC" == "manylinux" ]]; then | ||||
|         cp /usr/local/lib64/libavif.a /usr/local/lib | ||||
|         cp /usr/local/lib64/pkgconfig/libavif.pc /usr/local/lib/pkgconfig | ||||
|     fi | ||||
| } | ||||
| 
 | ||||
| function build { | ||||
|     if [[ -n "$IS_MACOS" ]] && [[ "$CIBW_ARCHS" == "arm64" ]]; then | ||||
|         sudo chown -R runner /usr/local | ||||
|  | @ -73,6 +140,13 @@ function build { | |||
|     fi | ||||
|     build_new_zlib | ||||
| 
 | ||||
|     ORIGINAL_LDFLAGS=$LDFLAGS | ||||
|     if [[ -n "$IS_MACOS" ]] && [[ "$CIBW_ARCHS" == "arm64" ]]; then | ||||
|         LDFLAGS="${LDFLAGS} -ld64" | ||||
|     fi | ||||
|     build_libavif | ||||
|     LDFLAGS=$ORIGINAL_LDFLAGS | ||||
| 
 | ||||
|     build_simple xcb-proto 1.17.0 https://xorg.freedesktop.org/archive/individual/proto | ||||
|     if [ -n "$IS_MACOS" ]; then | ||||
|         build_simple xorgproto 2024.1 https://www.x.org/pub/individual/proto | ||||
|  | @ -127,15 +201,19 @@ if [[ -n "$IS_MACOS" ]]; then | |||
|   # remove lcms2 and libpng to fix building openjpeg on arm64 | ||||
|   # remove jpeg-turbo to avoid inclusion on arm64 | ||||
|   # remove webp and zstd to avoid inclusion on x86_64 | ||||
|   # remove aom and libavif to fix building on arm64 | ||||
|   # curl from brew requires zstd, use system curl | ||||
|   brew remove --ignore-dependencies libpng libtiff libxcb libxau libxdmcp curl cairo lcms2 zstd | ||||
|   if [[ "$CIBW_ARCHS" == "arm64" ]]; then | ||||
|     brew remove --ignore-dependencies jpeg-turbo | ||||
|   else | ||||
|     brew remove --ignore-dependencies webp | ||||
|     brew remove --ignore-dependencies webp aom libavif | ||||
|   fi | ||||
| 
 | ||||
|   brew install pkg-config | ||||
|   brew install meson pkg-config | ||||
| 
 | ||||
|   # clear bash path cache for curl | ||||
|   hash -d curl | ||||
| fi | ||||
| 
 | ||||
| wrap_wheel_builder build | ||||
|  |  | |||
							
								
								
									
										44
									
								
								Tests/check_avif_leaks.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								Tests/check_avif_leaks.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,44 @@ | |||
| from __future__ import annotations | ||||
| 
 | ||||
| from io import BytesIO | ||||
| 
 | ||||
| import pytest | ||||
| 
 | ||||
| from PIL import Image | ||||
| 
 | ||||
| from .helper import is_win32, skip_unless_feature | ||||
| 
 | ||||
| # Limits for testing the leak | ||||
| mem_limit = 1024 * 1048576 | ||||
| stack_size = 8 * 1048576 | ||||
| iterations = int((mem_limit / stack_size) * 2) | ||||
| test_file = "Tests/images/avif/hopper.avif" | ||||
| 
 | ||||
| pytestmark = [ | ||||
|     pytest.mark.skipif(is_win32(), reason="requires Unix or macOS"), | ||||
|     skip_unless_feature("avif"), | ||||
| ] | ||||
| 
 | ||||
| 
 | ||||
| def test_leak_load(): | ||||
|     from resource import RLIMIT_AS, RLIMIT_STACK, setrlimit | ||||
| 
 | ||||
|     setrlimit(RLIMIT_STACK, (stack_size, stack_size)) | ||||
|     setrlimit(RLIMIT_AS, (mem_limit, mem_limit)) | ||||
|     for _ in range(iterations): | ||||
|         with Image.open(test_file) as im: | ||||
|             im.load() | ||||
| 
 | ||||
| 
 | ||||
| def test_leak_save(): | ||||
|     from resource import RLIMIT_AS, RLIMIT_STACK, setrlimit | ||||
| 
 | ||||
|     setrlimit(RLIMIT_STACK, (stack_size, stack_size)) | ||||
|     setrlimit(RLIMIT_AS, (mem_limit, mem_limit)) | ||||
|     for _ in range(iterations): | ||||
|         with Image.open(test_file) as im: | ||||
|             im.load() | ||||
|             test_output = BytesIO() | ||||
|             im.save(test_output, "AVIF") | ||||
|             test_output.seek(0) | ||||
|             test_output.read() | ||||
|  | @ -1,12 +1,14 @@ | |||
| from __future__ import annotations | ||||
| 
 | ||||
| import platform | ||||
| import struct | ||||
| import sys | ||||
| 
 | ||||
| from PIL import features | ||||
| 
 | ||||
| 
 | ||||
| def test_wheel_modules() -> None: | ||||
|     expected_modules = {"pil", "tkinter", "freetype2", "littlecms2", "webp"} | ||||
|     expected_modules = {"pil", "tkinter", "freetype2", "littlecms2", "webp", "avif"} | ||||
| 
 | ||||
|     # tkinter is not available in cibuildwheel installed CPython on Windows | ||||
|     try: | ||||
|  | @ -16,6 +18,11 @@ def test_wheel_modules() -> None: | |||
|     except ImportError: | ||||
|         expected_modules.remove("tkinter") | ||||
| 
 | ||||
|     # libavif is not available on windows for x86 and ARM64 architectures | ||||
|     if sys.platform == "win32": | ||||
|         if platform.machine() == "ARM64" or struct.calcsize("P") == 4: | ||||
|             expected_modules.remove("avif") | ||||
| 
 | ||||
|     assert set(features.get_supported_modules()) == expected_modules | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										
											BIN
										
									
								
								Tests/images/avif/chimera-missing-pixi.avif
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Tests/images/avif/chimera-missing-pixi.avif
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								Tests/images/avif/exif.avif
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Tests/images/avif/exif.avif
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								Tests/images/avif/hopper.avif
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Tests/images/avif/hopper.avif
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								Tests/images/avif/hopper_avif_write.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Tests/images/avif/hopper_avif_write.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 30 KiB | 
							
								
								
									
										
											BIN
										
									
								
								Tests/images/avif/icc_profile.avif
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Tests/images/avif/icc_profile.avif
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								Tests/images/avif/icc_profile_none.avif
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Tests/images/avif/icc_profile_none.avif
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								Tests/images/avif/rgba10.heif
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Tests/images/avif/rgba10.heif
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								Tests/images/avif/star.avifs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Tests/images/avif/star.avifs
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								Tests/images/avif/star.gif
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Tests/images/avif/star.gif
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 2.8 KiB | 
							
								
								
									
										
											BIN
										
									
								
								Tests/images/avif/star.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Tests/images/avif/star.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 3.8 KiB | 
							
								
								
									
										
											BIN
										
									
								
								Tests/images/avif/star180.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Tests/images/avif/star180.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 9.0 KiB | 
							
								
								
									
										
											BIN
										
									
								
								Tests/images/avif/star270.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Tests/images/avif/star270.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 9.2 KiB | 
							
								
								
									
										
											BIN
										
									
								
								Tests/images/avif/star90.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Tests/images/avif/star90.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 9.1 KiB | 
							
								
								
									
										
											BIN
										
									
								
								Tests/images/avif/transparency.avif
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Tests/images/avif/transparency.avif
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								Tests/images/avif/xmp_tags_orientation.avif
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Tests/images/avif/xmp_tags_orientation.avif
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										809
									
								
								Tests/test_file_avif.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										809
									
								
								Tests/test_file_avif.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,809 @@ | |||
| from __future__ import annotations | ||||
| 
 | ||||
| import gc | ||||
| import os | ||||
| import re | ||||
| import warnings | ||||
| import xml.etree.ElementTree | ||||
| from contextlib import contextmanager | ||||
| from io import BytesIO | ||||
| from struct import unpack | ||||
| 
 | ||||
| import pytest | ||||
| 
 | ||||
| from PIL import AvifImagePlugin, Image, ImageDraw, UnidentifiedImageError, features | ||||
| 
 | ||||
| from .helper import ( | ||||
|     PillowLeakTestCase, | ||||
|     assert_image, | ||||
|     assert_image_similar, | ||||
|     assert_image_similar_tofile, | ||||
|     hopper, | ||||
|     skip_unless_feature, | ||||
| ) | ||||
| 
 | ||||
| try: | ||||
|     from PIL import _avif | ||||
| 
 | ||||
|     HAVE_AVIF = True | ||||
| except ImportError: | ||||
|     HAVE_AVIF = False | ||||
| 
 | ||||
| 
 | ||||
| TEST_AVIF_FILE = "Tests/images/avif/hopper.avif" | ||||
| 
 | ||||
| 
 | ||||
| def assert_xmp_orientation(xmp, expected): | ||||
|     assert isinstance(xmp, bytes) | ||||
|     root = xml.etree.ElementTree.fromstring(xmp) | ||||
|     orientation = None | ||||
|     for elem in root.iter(): | ||||
|         if elem.tag.endswith("}Description"): | ||||
|             orientation = elem.attrib.get("{http://ns.adobe.com/tiff/1.0/}Orientation") | ||||
|             if orientation: | ||||
|                 orientation = int(orientation) | ||||
|                 break | ||||
|     assert orientation == expected | ||||
| 
 | ||||
| 
 | ||||
| def roundtrip(im, **options): | ||||
|     out = BytesIO() | ||||
|     im.save(out, "AVIF", **options) | ||||
|     out.seek(0) | ||||
|     return Image.open(out) | ||||
| 
 | ||||
| 
 | ||||
| def skip_unless_avif_decoder(codec_name): | ||||
|     reason = f"{codec_name} decode not available" | ||||
|     return pytest.mark.skipif( | ||||
|         not HAVE_AVIF or not _avif.decoder_codec_available(codec_name), reason=reason | ||||
|     ) | ||||
| 
 | ||||
| 
 | ||||
| def skip_unless_avif_encoder(codec_name): | ||||
|     reason = f"{codec_name} encode not available" | ||||
|     return pytest.mark.skipif( | ||||
|         not HAVE_AVIF or not _avif.encoder_codec_available(codec_name), reason=reason | ||||
|     ) | ||||
| 
 | ||||
| 
 | ||||
| def is_docker_qemu(): | ||||
|     try: | ||||
|         init_proc_exe = os.readlink("/proc/1/exe") | ||||
|     except:  # noqa: E722 | ||||
|         return False | ||||
|     else: | ||||
|         return "qemu" in init_proc_exe | ||||
| 
 | ||||
| 
 | ||||
| def has_alpha_premultiplied(im_bytes): | ||||
|     stream = BytesIO(im_bytes) | ||||
|     length = len(im_bytes) | ||||
|     while stream.tell() < length: | ||||
|         start = stream.tell() | ||||
|         size, boxtype = unpack(">L4s", stream.read(8)) | ||||
|         if not all(0x20 <= c <= 0x7E for c in boxtype): | ||||
|             # Not ascii | ||||
|             return False | ||||
|         if size == 1:  # 64bit size | ||||
|             (size,) = unpack(">Q", stream.read(8)) | ||||
|         end = start + size | ||||
|         version, _ = unpack(">B3s", stream.read(4)) | ||||
|         if boxtype in (b"ftyp", b"hdlr", b"pitm", b"iloc", b"iinf"): | ||||
|             # Skip these boxes | ||||
|             stream.seek(end) | ||||
|             continue | ||||
|         elif boxtype == b"meta": | ||||
|             # Container box possibly including iref prem, continue to parse boxes | ||||
|             # inside it | ||||
|             continue | ||||
|         elif boxtype == b"iref": | ||||
|             while stream.tell() < end: | ||||
|                 _, iref_type = unpack(">L4s", stream.read(8)) | ||||
|                 version, _ = unpack(">B3s", stream.read(4)) | ||||
|                 if iref_type == b"prem": | ||||
|                     return True | ||||
|                 stream.read(2 if version == 0 else 4) | ||||
|         else: | ||||
|             return False | ||||
|     return False | ||||
| 
 | ||||
| 
 | ||||
| class TestUnsupportedAvif: | ||||
|     def test_unsupported(self, monkeypatch): | ||||
|         monkeypatch.setattr(AvifImagePlugin, "SUPPORTED", False) | ||||
| 
 | ||||
|         file_path = "Tests/images/avif/hopper.avif" | ||||
|         pytest.warns( | ||||
|             UserWarning, | ||||
|             lambda: pytest.raises(UnidentifiedImageError, Image.open, file_path), | ||||
|         ) | ||||
| 
 | ||||
|     def test_unsupported_open(self, monkeypatch): | ||||
|         monkeypatch.setattr(AvifImagePlugin, "SUPPORTED", False) | ||||
| 
 | ||||
|         file_path = "Tests/images/avif/hopper.avif" | ||||
|         with pytest.raises(SyntaxError): | ||||
|             AvifImagePlugin.AvifImageFile(file_path) | ||||
| 
 | ||||
| 
 | ||||
| @skip_unless_feature("avif") | ||||
| class TestFileAvif: | ||||
|     def test_version(self): | ||||
|         _avif.AvifCodecVersions() | ||||
|         assert re.search(r"\d+\.\d+\.\d+$", features.version_module("avif")) | ||||
| 
 | ||||
|     def test_read(self): | ||||
|         """ | ||||
|         Can we read an AVIF file without error? | ||||
|         Does it have the bits we expect? | ||||
|         """ | ||||
| 
 | ||||
|         with Image.open("Tests/images/avif/hopper.avif") as image: | ||||
|             assert image.mode == "RGB" | ||||
|             assert image.size == (128, 128) | ||||
|             assert image.format == "AVIF" | ||||
|             assert image.get_format_mimetype() == "image/avif" | ||||
|             image.load() | ||||
|             image.getdata() | ||||
| 
 | ||||
|             # generated with: | ||||
|             # avifdec hopper.avif hopper_avif_write.png | ||||
|             assert_image_similar_tofile( | ||||
|                 image, "Tests/images/avif/hopper_avif_write.png", 12.0 | ||||
|             ) | ||||
| 
 | ||||
|     def _roundtrip(self, tmp_path, mode, epsilon, args={}): | ||||
|         temp_file = str(tmp_path / "temp.avif") | ||||
| 
 | ||||
|         hopper(mode).save(temp_file, **args) | ||||
|         with Image.open(temp_file) as image: | ||||
|             assert image.mode == "RGB" | ||||
|             assert image.size == (128, 128) | ||||
|             assert image.format == "AVIF" | ||||
|             image.load() | ||||
|             image.getdata() | ||||
| 
 | ||||
|             if mode == "RGB": | ||||
|                 # avifdec hopper.avif avif/hopper_avif_write.png | ||||
|                 assert_image_similar_tofile( | ||||
|                     image, "Tests/images/avif/hopper_avif_write.png", 12.0 | ||||
|                 ) | ||||
| 
 | ||||
|             # This test asserts that the images are similar. If the average pixel | ||||
|             # difference between the two images is less than the epsilon value, | ||||
|             # then we're going to accept that it's a reasonable lossy version of | ||||
|             # the image. | ||||
|             target = hopper(mode) | ||||
|             if mode != "RGB": | ||||
|                 target = target.convert("RGB") | ||||
|             assert_image_similar(image, target, epsilon) | ||||
| 
 | ||||
|     def test_write_rgb(self, tmp_path): | ||||
|         """ | ||||
|         Can we write a RGB mode file to avif without error? | ||||
|         Does it have the bits we expect? | ||||
|         """ | ||||
| 
 | ||||
|         self._roundtrip(tmp_path, "RGB", 12.5) | ||||
| 
 | ||||
|     def test_AvifEncoder_with_invalid_args(self): | ||||
|         """ | ||||
|         Calling encoder functions with no arguments should result in an error. | ||||
|         """ | ||||
|         with pytest.raises(TypeError): | ||||
|             _avif.AvifEncoder() | ||||
| 
 | ||||
|     def test_AvifDecoder_with_invalid_args(self): | ||||
|         """ | ||||
|         Calling decoder functions with no arguments should result in an error. | ||||
|         """ | ||||
|         with pytest.raises(TypeError): | ||||
|             _avif.AvifDecoder() | ||||
| 
 | ||||
|     def test_encoder_finish_none_error(self, monkeypatch, tmp_path): | ||||
|         """Save should raise an OSError if AvifEncoder.finish returns None""" | ||||
| 
 | ||||
|         class _mock_avif: | ||||
|             class AvifEncoder: | ||||
|                 def __init__(self, *args, **kwargs): | ||||
|                     pass | ||||
| 
 | ||||
|                 def add(self, *args, **kwargs): | ||||
|                     pass | ||||
| 
 | ||||
|                 def finish(self): | ||||
|                     return None | ||||
| 
 | ||||
|         monkeypatch.setattr(AvifImagePlugin, "_avif", _mock_avif) | ||||
| 
 | ||||
|         im = Image.new("RGB", (150, 150)) | ||||
|         test_file = str(tmp_path / "temp.avif") | ||||
|         with pytest.raises(OSError): | ||||
|             im.save(test_file) | ||||
| 
 | ||||
|     def test_no_resource_warning(self, tmp_path): | ||||
|         with Image.open(TEST_AVIF_FILE) as image: | ||||
|             temp_file = str(tmp_path / "temp.avif") | ||||
|             with warnings.catch_warnings(): | ||||
|                 image.save(temp_file) | ||||
| 
 | ||||
|     @pytest.mark.parametrize("major_brand", [b"avif", b"avis", b"mif1", b"msf1"]) | ||||
|     def test_accept_ftyp_brands(self, major_brand): | ||||
|         data = b"\x00\x00\x00\x1cftyp%s\x00\x00\x00\x00" % major_brand | ||||
|         assert AvifImagePlugin._accept(data) is True | ||||
| 
 | ||||
|     def test_file_pointer_could_be_reused(self): | ||||
|         with open(TEST_AVIF_FILE, "rb") as blob: | ||||
|             with Image.open(blob) as im: | ||||
|                 im.load() | ||||
|             with Image.open(blob) as im: | ||||
|                 im.load() | ||||
| 
 | ||||
|     def test_background_from_gif(self, tmp_path): | ||||
|         with Image.open("Tests/images/chi.gif") as im: | ||||
|             original_value = im.convert("RGB").getpixel((1, 1)) | ||||
| 
 | ||||
|             # Save as AVIF | ||||
|             out_avif = str(tmp_path / "temp.avif") | ||||
|             im.save(out_avif, save_all=True) | ||||
| 
 | ||||
|         # Save as GIF | ||||
|         out_gif = str(tmp_path / "temp.gif") | ||||
|         with Image.open(out_avif) as im: | ||||
|             im.save(out_gif) | ||||
| 
 | ||||
|         with Image.open(out_gif) as reread: | ||||
|             reread_value = reread.convert("RGB").getpixel((1, 1)) | ||||
|         difference = sum( | ||||
|             [abs(original_value[i] - reread_value[i]) for i in range(0, 3)] | ||||
|         ) | ||||
|         assert difference < 5 | ||||
| 
 | ||||
|     def test_save_single_frame(self, tmp_path): | ||||
|         temp_file = str(tmp_path / "temp.avif") | ||||
|         with Image.open("Tests/images/chi.gif") as im: | ||||
|             im.save(temp_file) | ||||
|         with Image.open(temp_file) as im: | ||||
|             assert im.n_frames == 1 | ||||
| 
 | ||||
|     def test_invalid_file(self): | ||||
|         invalid_file = "Tests/images/flower.jpg" | ||||
| 
 | ||||
|         with pytest.raises(SyntaxError): | ||||
|             AvifImagePlugin.AvifImageFile(invalid_file) | ||||
| 
 | ||||
|     def test_load_transparent_rgb(self): | ||||
|         test_file = "Tests/images/avif/transparency.avif" | ||||
|         with Image.open(test_file) as im: | ||||
|             assert_image(im, "RGBA", (64, 64)) | ||||
| 
 | ||||
|             # image has 876 transparent pixels | ||||
|             assert im.getchannel("A").getcolors()[0][0] == 876 | ||||
| 
 | ||||
|     def test_save_transparent(self, tmp_path): | ||||
|         im = Image.new("RGBA", (10, 10), (0, 0, 0, 0)) | ||||
|         assert im.getcolors() == [(100, (0, 0, 0, 0))] | ||||
| 
 | ||||
|         test_file = str(tmp_path / "temp.avif") | ||||
|         im.save(test_file) | ||||
| 
 | ||||
|         # check if saved image contains same transparency | ||||
|         with Image.open(test_file) as im: | ||||
|             assert_image(im, "RGBA", (10, 10)) | ||||
|             assert im.getcolors() == [(100, (0, 0, 0, 0))] | ||||
| 
 | ||||
|     def test_save_icc_profile(self): | ||||
|         with Image.open("Tests/images/avif/icc_profile_none.avif") as im: | ||||
|             assert im.info.get("icc_profile") is None | ||||
| 
 | ||||
|             with Image.open("Tests/images/avif/icc_profile.avif") as with_icc: | ||||
|                 expected_icc = with_icc.info.get("icc_profile") | ||||
|                 assert expected_icc is not None | ||||
| 
 | ||||
|                 im = roundtrip(im, icc_profile=expected_icc) | ||||
|                 assert im.info["icc_profile"] == expected_icc | ||||
| 
 | ||||
|     def test_discard_icc_profile(self): | ||||
|         with Image.open("Tests/images/avif/icc_profile.avif") as im: | ||||
|             im = roundtrip(im, icc_profile=None) | ||||
|         assert "icc_profile" not in im.info | ||||
| 
 | ||||
|     def test_roundtrip_icc_profile(self): | ||||
|         with Image.open("Tests/images/avif/icc_profile.avif") as im: | ||||
|             expected_icc = im.info["icc_profile"] | ||||
| 
 | ||||
|             im = roundtrip(im) | ||||
|         assert im.info["icc_profile"] == expected_icc | ||||
| 
 | ||||
|     def test_roundtrip_no_icc_profile(self): | ||||
|         with Image.open("Tests/images/avif/icc_profile_none.avif") as im: | ||||
|             assert im.info.get("icc_profile") is None | ||||
| 
 | ||||
|             im = roundtrip(im) | ||||
|         assert "icc_profile" not in im.info | ||||
| 
 | ||||
|     def test_exif(self): | ||||
|         # With an EXIF chunk | ||||
|         with Image.open("Tests/images/avif/exif.avif") as im: | ||||
|             exif = im.getexif() | ||||
|         assert exif[274] == 1 | ||||
| 
 | ||||
|     def test_exif_save(self, tmp_path): | ||||
|         with Image.open("Tests/images/avif/exif.avif") as im: | ||||
|             test_file = str(tmp_path / "temp.avif") | ||||
|             im.save(test_file) | ||||
| 
 | ||||
|         with Image.open(test_file) as reloaded: | ||||
|             exif = reloaded.getexif() | ||||
|         assert exif[274] == 1 | ||||
| 
 | ||||
|     def test_exif_obj_argument(self, tmp_path): | ||||
|         exif = Image.Exif() | ||||
|         exif[274] = 1 | ||||
|         exif_data = exif.tobytes() | ||||
|         with Image.open(TEST_AVIF_FILE) as im: | ||||
|             test_file = str(tmp_path / "temp.avif") | ||||
|             im.save(test_file, exif=exif) | ||||
| 
 | ||||
|         with Image.open(test_file) as reloaded: | ||||
|             assert reloaded.info["exif"] == exif_data | ||||
| 
 | ||||
|     def test_exif_bytes_argument(self, tmp_path): | ||||
|         exif = Image.Exif() | ||||
|         exif[274] = 1 | ||||
|         exif_data = exif.tobytes() | ||||
|         with Image.open(TEST_AVIF_FILE) as im: | ||||
|             test_file = str(tmp_path / "temp.avif") | ||||
|             im.save(test_file, exif=exif_data) | ||||
| 
 | ||||
|         with Image.open(test_file) as reloaded: | ||||
|             assert reloaded.info["exif"] == exif_data | ||||
| 
 | ||||
|     def test_exif_invalid(self, tmp_path): | ||||
|         with Image.open(TEST_AVIF_FILE) as im: | ||||
|             test_file = str(tmp_path / "temp.avif") | ||||
|             with pytest.raises(ValueError): | ||||
|                 im.save(test_file, exif=b"invalid") | ||||
| 
 | ||||
|     def test_xmp(self): | ||||
|         with Image.open("Tests/images/avif/xmp_tags_orientation.avif") as im: | ||||
|             xmp = im.info.get("xmp") | ||||
|         assert_xmp_orientation(xmp, 3) | ||||
| 
 | ||||
|     def test_xmp_save(self, tmp_path): | ||||
|         with Image.open("Tests/images/avif/xmp_tags_orientation.avif") as im: | ||||
|             test_file = str(tmp_path / "temp.avif") | ||||
|             im.save(test_file) | ||||
| 
 | ||||
|         with Image.open(test_file) as reloaded: | ||||
|             xmp = reloaded.info.get("xmp") | ||||
|         assert_xmp_orientation(xmp, 3) | ||||
| 
 | ||||
|     def test_xmp_save_from_png(self, tmp_path): | ||||
|         with Image.open("Tests/images/xmp_tags_orientation.png") as im: | ||||
|             test_file = str(tmp_path / "temp.avif") | ||||
|             im.save(test_file) | ||||
| 
 | ||||
|         with Image.open(test_file) as reloaded: | ||||
|             xmp = reloaded.info.get("xmp") | ||||
|         assert_xmp_orientation(xmp, 3) | ||||
| 
 | ||||
|     def test_xmp_save_argument(self, tmp_path): | ||||
|         xmp_arg = "\n".join( | ||||
|             [ | ||||
|                 '<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>', | ||||
|                 '<x:xmpmeta xmlns:x="adobe:ns:meta/">', | ||||
|                 ' <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">', | ||||
|                 '  <rdf:Description rdf:about=""', | ||||
|                 '    xmlns:tiff="http://ns.adobe.com/tiff/1.0/"', | ||||
|                 '   tiff:Orientation="1"/>', | ||||
|                 " </rdf:RDF>", | ||||
|                 "</x:xmpmeta>", | ||||
|                 '<?xpacket end="r"?>', | ||||
|             ] | ||||
|         ) | ||||
|         with Image.open("Tests/images/avif/hopper.avif") as im: | ||||
|             test_file = str(tmp_path / "temp.avif") | ||||
|             im.save(test_file, xmp=xmp_arg) | ||||
| 
 | ||||
|         with Image.open(test_file) as reloaded: | ||||
|             xmp = reloaded.info.get("xmp") | ||||
|         assert_xmp_orientation(xmp, 1) | ||||
| 
 | ||||
|     def test_tell(self): | ||||
|         with Image.open(TEST_AVIF_FILE) as im: | ||||
|             assert im.tell() == 0 | ||||
| 
 | ||||
|     def test_seek(self): | ||||
|         with Image.open(TEST_AVIF_FILE) as im: | ||||
|             im.seek(0) | ||||
| 
 | ||||
|             with pytest.raises(EOFError): | ||||
|                 im.seek(1) | ||||
| 
 | ||||
|     @pytest.mark.parametrize("subsampling", ["4:4:4", "4:2:2", "4:0:0"]) | ||||
|     def test_encoder_subsampling(self, tmp_path, subsampling): | ||||
|         with Image.open(TEST_AVIF_FILE) as im: | ||||
|             test_file = str(tmp_path / "temp.avif") | ||||
|             im.save(test_file, subsampling=subsampling) | ||||
| 
 | ||||
|     def test_encoder_subsampling_invalid(self, tmp_path): | ||||
|         with Image.open(TEST_AVIF_FILE) as im: | ||||
|             test_file = str(tmp_path / "temp.avif") | ||||
|             with pytest.raises(ValueError): | ||||
|                 im.save(test_file, subsampling="foo") | ||||
| 
 | ||||
|     def test_encoder_range(self, tmp_path): | ||||
|         with Image.open(TEST_AVIF_FILE) as im: | ||||
|             test_file = str(tmp_path / "temp.avif") | ||||
|             im.save(test_file, range="limited") | ||||
| 
 | ||||
|     def test_encoder_range_invalid(self, tmp_path): | ||||
|         with Image.open(TEST_AVIF_FILE) as im: | ||||
|             test_file = str(tmp_path / "temp.avif") | ||||
|             with pytest.raises(ValueError): | ||||
|                 im.save(test_file, range="foo") | ||||
| 
 | ||||
|     @skip_unless_avif_encoder("aom") | ||||
|     @skip_unless_feature("avif") | ||||
|     def test_encoder_codec_param(self, tmp_path): | ||||
|         with Image.open(TEST_AVIF_FILE) as im: | ||||
|             test_file = str(tmp_path / "temp.avif") | ||||
|             im.save(test_file, codec="aom") | ||||
| 
 | ||||
|     def test_encoder_codec_invalid(self, tmp_path): | ||||
|         with Image.open(TEST_AVIF_FILE) as im: | ||||
|             test_file = str(tmp_path / "temp.avif") | ||||
|             with pytest.raises(ValueError): | ||||
|                 im.save(test_file, codec="foo") | ||||
| 
 | ||||
|     @skip_unless_avif_decoder("dav1d") | ||||
|     @skip_unless_feature("avif") | ||||
|     def test_encoder_codec_cannot_encode(self, tmp_path): | ||||
|         with Image.open(TEST_AVIF_FILE) as im: | ||||
|             test_file = str(tmp_path / "temp.avif") | ||||
|             with pytest.raises(ValueError): | ||||
|                 im.save(test_file, codec="dav1d") | ||||
| 
 | ||||
|     @skip_unless_avif_encoder("aom") | ||||
|     @skip_unless_feature("avif") | ||||
|     def test_encoder_advanced_codec_options(self): | ||||
|         with Image.open(TEST_AVIF_FILE) as im: | ||||
|             ctrl_buf = BytesIO() | ||||
|             im.save(ctrl_buf, "AVIF", codec="aom") | ||||
|             test_buf = BytesIO() | ||||
|             im.save( | ||||
|                 test_buf, | ||||
|                 "AVIF", | ||||
|                 codec="aom", | ||||
|                 advanced={ | ||||
|                     "aq-mode": "1", | ||||
|                     "enable-chroma-deltaq": "1", | ||||
|                 }, | ||||
|             ) | ||||
|             assert ctrl_buf.getvalue() != test_buf.getvalue() | ||||
| 
 | ||||
|     @skip_unless_avif_encoder("aom") | ||||
|     @skip_unless_feature("avif") | ||||
|     @pytest.mark.parametrize("val", [{"foo": "bar"}, 1234]) | ||||
|     def test_encoder_advanced_codec_options_invalid(self, tmp_path, val): | ||||
|         with Image.open(TEST_AVIF_FILE) as im: | ||||
|             test_file = str(tmp_path / "temp.avif") | ||||
|             with pytest.raises(ValueError): | ||||
|                 im.save(test_file, codec="aom", advanced=val) | ||||
| 
 | ||||
|     @skip_unless_avif_decoder("aom") | ||||
|     @skip_unless_feature("avif") | ||||
|     def test_decoder_codec_param(self): | ||||
|         AvifImagePlugin.DECODE_CODEC_CHOICE = "aom" | ||||
|         try: | ||||
|             with Image.open(TEST_AVIF_FILE) as im: | ||||
|                 assert im.size == (128, 128) | ||||
|         finally: | ||||
|             AvifImagePlugin.DECODE_CODEC_CHOICE = "auto" | ||||
| 
 | ||||
|     @skip_unless_avif_encoder("rav1e") | ||||
|     @skip_unless_feature("avif") | ||||
|     def test_decoder_codec_cannot_decode(self, tmp_path): | ||||
|         AvifImagePlugin.DECODE_CODEC_CHOICE = "rav1e" | ||||
|         try: | ||||
|             with pytest.raises(ValueError): | ||||
|                 with Image.open(TEST_AVIF_FILE): | ||||
|                     pass | ||||
|         finally: | ||||
|             AvifImagePlugin.DECODE_CODEC_CHOICE = "auto" | ||||
| 
 | ||||
|     def test_decoder_codec_invalid(self): | ||||
|         AvifImagePlugin.DECODE_CODEC_CHOICE = "foo" | ||||
|         try: | ||||
|             with pytest.raises(ValueError): | ||||
|                 with Image.open(TEST_AVIF_FILE): | ||||
|                     pass | ||||
|         finally: | ||||
|             AvifImagePlugin.DECODE_CODEC_CHOICE = "auto" | ||||
| 
 | ||||
|     @skip_unless_avif_encoder("aom") | ||||
|     @skip_unless_feature("avif") | ||||
|     def test_encoder_codec_available(self): | ||||
|         assert _avif.encoder_codec_available("aom") is True | ||||
| 
 | ||||
|     def test_encoder_codec_available_bad_params(self): | ||||
|         with pytest.raises(TypeError): | ||||
|             _avif.encoder_codec_available() | ||||
| 
 | ||||
|     @skip_unless_avif_decoder("dav1d") | ||||
|     @skip_unless_feature("avif") | ||||
|     def test_encoder_codec_available_cannot_decode(self): | ||||
|         assert _avif.encoder_codec_available("dav1d") is False | ||||
| 
 | ||||
|     def test_encoder_codec_available_invalid(self): | ||||
|         assert _avif.encoder_codec_available("foo") is False | ||||
| 
 | ||||
|     def test_encoder_quality_valueerror(self, tmp_path): | ||||
|         with Image.open("Tests/images/avif/hopper.avif") as im: | ||||
|             test_file = str(tmp_path / "temp.avif") | ||||
|             with pytest.raises(ValueError): | ||||
|                 im.save(test_file, quality="invalid") | ||||
| 
 | ||||
|     @skip_unless_avif_decoder("aom") | ||||
|     @skip_unless_feature("avif") | ||||
|     def test_decoder_codec_available(self): | ||||
|         assert _avif.decoder_codec_available("aom") is True | ||||
| 
 | ||||
|     def test_decoder_codec_available_bad_params(self): | ||||
|         with pytest.raises(TypeError): | ||||
|             _avif.decoder_codec_available() | ||||
| 
 | ||||
|     @skip_unless_avif_encoder("rav1e") | ||||
|     @skip_unless_feature("avif") | ||||
|     def test_decoder_codec_available_cannot_decode(self): | ||||
|         assert _avif.decoder_codec_available("rav1e") is False | ||||
| 
 | ||||
|     def test_decoder_codec_available_invalid(self): | ||||
|         assert _avif.decoder_codec_available("foo") is False | ||||
| 
 | ||||
|     @pytest.mark.parametrize("upsampling", ["fastest", "best", "nearest", "bilinear"]) | ||||
|     def test_decoder_upsampling(self, upsampling): | ||||
|         AvifImagePlugin.CHROMA_UPSAMPLING = upsampling | ||||
|         try: | ||||
|             with Image.open(TEST_AVIF_FILE): | ||||
|                 pass | ||||
|         finally: | ||||
|             AvifImagePlugin.CHROMA_UPSAMPLING = "auto" | ||||
| 
 | ||||
|     def test_decoder_upsampling_invalid(self): | ||||
|         AvifImagePlugin.CHROMA_UPSAMPLING = "foo" | ||||
|         try: | ||||
|             with pytest.raises(ValueError): | ||||
|                 with Image.open(TEST_AVIF_FILE): | ||||
|                     pass | ||||
|         finally: | ||||
|             AvifImagePlugin.CHROMA_UPSAMPLING = "auto" | ||||
| 
 | ||||
|     def test_p_mode_transparency(self): | ||||
|         im = Image.new("P", size=(64, 64)) | ||||
|         draw = ImageDraw.Draw(im) | ||||
|         draw.rectangle(xy=[(0, 0), (32, 32)], fill=255) | ||||
|         draw.rectangle(xy=[(32, 32), (64, 64)], fill=255) | ||||
| 
 | ||||
|         buf_png = BytesIO() | ||||
|         im.save(buf_png, format="PNG", transparency=0) | ||||
|         im_png = Image.open(buf_png) | ||||
|         buf_out = BytesIO() | ||||
|         im_png.save(buf_out, format="AVIF", quality=100) | ||||
| 
 | ||||
|         assert_image_similar(im_png.convert("RGBA"), Image.open(buf_out), 1) | ||||
| 
 | ||||
|     def test_decoder_strict_flags(self): | ||||
|         # This would fail if full avif strictFlags were enabled | ||||
|         with Image.open("Tests/images/avif/chimera-missing-pixi.avif") as im: | ||||
|             assert im.size == (480, 270) | ||||
| 
 | ||||
|     @skip_unless_avif_encoder("aom") | ||||
|     def test_aom_optimizations(self): | ||||
|         im = hopper("RGB") | ||||
|         buf = BytesIO() | ||||
|         im.save(buf, format="AVIF", codec="aom", speed=1) | ||||
| 
 | ||||
|     @skip_unless_avif_encoder("svt") | ||||
|     def test_svt_optimizations(self): | ||||
|         im = hopper("RGB") | ||||
|         buf = BytesIO() | ||||
|         im.save(buf, format="AVIF", codec="svt", speed=1) | ||||
| 
 | ||||
| 
 | ||||
| @skip_unless_feature("avif") | ||||
| class TestAvifAnimation: | ||||
|     @contextmanager | ||||
|     def star_frames(self): | ||||
|         with Image.open("Tests/images/avif/star.png") as f1: | ||||
|             with Image.open("Tests/images/avif/star90.png") as f2: | ||||
|                 with Image.open("Tests/images/avif/star180.png") as f3: | ||||
|                     with Image.open("Tests/images/avif/star270.png") as f4: | ||||
|                         yield [f1, f2, f3, f4] | ||||
| 
 | ||||
|     def test_n_frames(self): | ||||
|         """ | ||||
|         Ensure that AVIF format sets n_frames and is_animated attributes | ||||
|         correctly. | ||||
|         """ | ||||
| 
 | ||||
|         with Image.open("Tests/images/avif/hopper.avif") as im: | ||||
|             assert im.n_frames == 1 | ||||
|             assert not im.is_animated | ||||
| 
 | ||||
|         with Image.open("Tests/images/avif/star.avifs") as im: | ||||
|             assert im.n_frames == 5 | ||||
|             assert im.is_animated | ||||
| 
 | ||||
|     def test_write_animation_L(self, tmp_path): | ||||
|         """ | ||||
|         Convert an animated GIF to animated AVIF, then compare the frame | ||||
|         count, and first and last frames to ensure they're visually similar. | ||||
|         """ | ||||
| 
 | ||||
|         with Image.open("Tests/images/avif/star.gif") as orig: | ||||
|             assert orig.n_frames > 1 | ||||
| 
 | ||||
|             temp_file = str(tmp_path / "temp.avif") | ||||
|             orig.save(temp_file, save_all=True) | ||||
|             with Image.open(temp_file) as im: | ||||
|                 assert im.n_frames == orig.n_frames | ||||
| 
 | ||||
|                 # Compare first and second-to-last frames to the original animated GIF | ||||
|                 orig.load() | ||||
|                 im.load() | ||||
|                 assert_image_similar(im.convert("RGB"), orig.convert("RGB"), 25.0) | ||||
|                 orig.seek(orig.n_frames - 2) | ||||
|                 im.seek(im.n_frames - 2) | ||||
|                 orig.load() | ||||
|                 im.load() | ||||
|                 assert_image_similar(im.convert("RGB"), orig.convert("RGB"), 25.0) | ||||
| 
 | ||||
|     def test_write_animation_RGB(self, tmp_path): | ||||
|         """ | ||||
|         Write an animated AVIF from RGB frames, and ensure the frames | ||||
|         are visually similar to the originals. | ||||
|         """ | ||||
| 
 | ||||
|         def check(temp_file): | ||||
|             with Image.open(temp_file) as im: | ||||
|                 assert im.n_frames == 4 | ||||
| 
 | ||||
|                 # Compare first frame to original | ||||
|                 im.load() | ||||
|                 assert_image_similar(im, frame1.convert("RGBA"), 25.0) | ||||
| 
 | ||||
|                 # Compare second frame to original | ||||
|                 im.seek(1) | ||||
|                 im.load() | ||||
|                 assert_image_similar(im, frame2.convert("RGBA"), 25.0) | ||||
| 
 | ||||
|         with self.star_frames() as frames: | ||||
|             frame1 = frames[0] | ||||
|             frame2 = frames[1] | ||||
|             temp_file1 = str(tmp_path / "temp.avif") | ||||
|             frames[0].copy().save(temp_file1, save_all=True, append_images=frames[1:]) | ||||
|             check(temp_file1) | ||||
| 
 | ||||
|             # Tests appending using a generator | ||||
|             def imGenerator(ims): | ||||
|                 yield from ims | ||||
| 
 | ||||
|             temp_file2 = str(tmp_path / "temp_generator.avif") | ||||
|             frames[0].copy().save( | ||||
|                 temp_file2, | ||||
|                 save_all=True, | ||||
|                 append_images=imGenerator(frames[1:]), | ||||
|             ) | ||||
|             check(temp_file2) | ||||
| 
 | ||||
|     def test_sequence_dimension_mismatch_check(self, tmp_path): | ||||
|         temp_file = str(tmp_path / "temp.avif") | ||||
|         frame1 = Image.new("RGB", (100, 100)) | ||||
|         frame2 = Image.new("RGB", (150, 150)) | ||||
|         with pytest.raises(ValueError): | ||||
|             frame1.save(temp_file, save_all=True, append_images=[frame2], duration=100) | ||||
| 
 | ||||
|     def test_heif_raises_unidentified_image_error(self): | ||||
|         with pytest.raises(UnidentifiedImageError): | ||||
|             with Image.open("Tests/images/avif/rgba10.heif"): | ||||
|                 pass | ||||
| 
 | ||||
|     @pytest.mark.parametrize("alpha_premultipled", [False, True]) | ||||
|     def test_alpha_premultiplied_true(self, alpha_premultipled): | ||||
|         im = Image.new("RGBA", (10, 10), (0, 0, 0, 0)) | ||||
|         im_buf = BytesIO() | ||||
|         im.save(im_buf, "AVIF", alpha_premultiplied=alpha_premultipled) | ||||
|         im_bytes = im_buf.getvalue() | ||||
|         assert has_alpha_premultiplied(im_bytes) is alpha_premultipled | ||||
| 
 | ||||
|     def test_timestamp_and_duration(self, tmp_path): | ||||
|         """ | ||||
|         Try passing a list of durations, and make sure the encoded | ||||
|         timestamps and durations are correct. | ||||
|         """ | ||||
| 
 | ||||
|         durations = [1, 10, 20, 30, 40] | ||||
|         temp_file = str(tmp_path / "temp.avif") | ||||
|         with self.star_frames() as frames: | ||||
|             frames[0].save( | ||||
|                 temp_file, | ||||
|                 save_all=True, | ||||
|                 append_images=(frames[1:] + [frames[0]]), | ||||
|                 duration=durations, | ||||
|             ) | ||||
| 
 | ||||
|         with Image.open(temp_file) as im: | ||||
|             assert im.n_frames == 5 | ||||
|             assert im.is_animated | ||||
| 
 | ||||
|             # Check that timestamps and durations match original values specified | ||||
|             ts = 0 | ||||
|             for frame in range(im.n_frames): | ||||
|                 im.seek(frame) | ||||
|                 im.load() | ||||
|                 assert im.info["duration"] == durations[frame] | ||||
|                 assert im.info["timestamp"] == ts | ||||
|                 ts += durations[frame] | ||||
| 
 | ||||
|     def test_seeking(self, tmp_path): | ||||
|         """ | ||||
|         Create an animated AVIF file, and then try seeking through frames in | ||||
|         reverse-order, verifying the timestamps and durations are correct. | ||||
|         """ | ||||
| 
 | ||||
|         dur = 33 | ||||
|         temp_file = str(tmp_path / "temp.avif") | ||||
|         with self.star_frames() as frames: | ||||
|             frames[0].save( | ||||
|                 temp_file, | ||||
|                 save_all=True, | ||||
|                 append_images=(frames[1:] + [frames[0]]), | ||||
|                 duration=dur, | ||||
|             ) | ||||
| 
 | ||||
|         with Image.open(temp_file) as im: | ||||
|             assert im.n_frames == 5 | ||||
|             assert im.is_animated | ||||
| 
 | ||||
|             # Traverse frames in reverse, checking timestamps and durations | ||||
|             ts = dur * (im.n_frames - 1) | ||||
|             for frame in reversed(range(im.n_frames)): | ||||
|                 im.seek(frame) | ||||
|                 im.load() | ||||
|                 assert im.info["duration"] == dur | ||||
|                 assert im.info["timestamp"] == ts | ||||
|                 ts -= dur | ||||
| 
 | ||||
|     def test_seek_errors(self): | ||||
|         with Image.open("Tests/images/avif/star.avifs") as im: | ||||
|             with pytest.raises(EOFError): | ||||
|                 im.seek(-1) | ||||
| 
 | ||||
|             with pytest.raises(EOFError): | ||||
|                 im.seek(42) | ||||
| 
 | ||||
| 
 | ||||
| MAX_THREADS = os.cpu_count() or 1 | ||||
| 
 | ||||
| 
 | ||||
| @skip_unless_feature("avif") | ||||
| class TestAvifLeaks(PillowLeakTestCase): | ||||
|     mem_limit = MAX_THREADS * 3 * 1024 | ||||
|     iterations = 100 | ||||
| 
 | ||||
|     @pytest.mark.skipif( | ||||
|         is_docker_qemu(), reason="Skipping on cross-architecture containers" | ||||
|     ) | ||||
|     def test_leak_load(self): | ||||
|         with open(TEST_AVIF_FILE, "rb") as f: | ||||
|             im_data = f.read() | ||||
| 
 | ||||
|         def core(): | ||||
|             with Image.open(BytesIO(im_data)) as im: | ||||
|                 im.load() | ||||
|             gc.collect() | ||||
| 
 | ||||
|         self._test_leak(core) | ||||
							
								
								
									
										62
									
								
								depends/install_libavif.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										62
									
								
								depends/install_libavif.sh
									
									
									
									
									
										Executable file
									
								
							|  | @ -0,0 +1,62 @@ | |||
| #!/usr/bin/env bash | ||||
| set -eo pipefail | ||||
| 
 | ||||
| version=1.1.1 | ||||
| 
 | ||||
| ./download-and-extract.sh libavif-$version https://github.com/AOMediaCodec/libavif/archive/refs/tags/v$version.tar.gz | ||||
| 
 | ||||
| pushd libavif-$version | ||||
| 
 | ||||
| if uname -s | grep -q Darwin; then | ||||
|     PREFIX=$(brew --prefix) | ||||
| else | ||||
|     PREFIX=/usr | ||||
| fi | ||||
| 
 | ||||
| PKGCONFIG=${PKGCONFIG:-pkg-config} | ||||
| 
 | ||||
| LIBAVIF_CMAKE_FLAGS=() | ||||
| HAS_DECODER=0 | ||||
| HAS_ENCODER=0 | ||||
| 
 | ||||
| if $PKGCONFIG --exists dav1d; then | ||||
|     LIBAVIF_CMAKE_FLAGS+=(-DAVIF_CODEC_DAV1D=SYSTEM) | ||||
|     HAS_DECODER=1 | ||||
| fi | ||||
| 
 | ||||
| if $PKGCONFIG --exists rav1e; then | ||||
|     LIBAVIF_CMAKE_FLAGS+=(-DAVIF_CODEC_RAV1E=SYSTEM) | ||||
|     HAS_ENCODER=1 | ||||
| fi | ||||
| 
 | ||||
| if $PKGCONFIG --exists SvtAv1Enc; then | ||||
|     LIBAVIF_CMAKE_FLAGS+=(-DAVIF_CODEC_SVT=SYSTEM) | ||||
|     HAS_ENCODER=1 | ||||
| fi | ||||
| 
 | ||||
| if $PKGCONFIG --exists libgav1; then | ||||
|     LIBAVIF_CMAKE_FLAGS+=(-DAVIF_CODEC_LIBGAV1=SYSTEM) | ||||
|     HAS_DECODER=1 | ||||
| fi | ||||
| 
 | ||||
| if $PKGCONFIG --exists aom; then | ||||
|     LIBAVIF_CMAKE_FLAGS+=(-DAVIF_CODEC_AOM=SYSTEM) | ||||
|     HAS_ENCODER=1 | ||||
|     HAS_DECODER=1 | ||||
| fi | ||||
| 
 | ||||
| if [ "$HAS_ENCODER" != 1 ] || [ "$HAS_DECODER" != 1 ]; then | ||||
|     LIBAVIF_CMAKE_FLAGS+=(-DAVIF_CODEC_AOM=LOCAL) | ||||
| fi | ||||
| 
 | ||||
| cmake -G Ninja -S . -B build \ | ||||
|     -DCMAKE_INSTALL_PREFIX=$PREFIX \ | ||||
|     -DAVIF_LIBYUV=LOCAL \ | ||||
|     -DCMAKE_BUILD_TYPE=Release \ | ||||
|     -DCMAKE_INSTALL_NAME_DIR=$PREFIX/lib \ | ||||
|     -DCMAKE_MACOSX_RPATH=OFF \ | ||||
|     "${LIBAVIF_CMAKE_FLAGS[@]}" | ||||
| 
 | ||||
| sudo ninja -C build install | ||||
| 
 | ||||
| popd | ||||
|  | @ -235,7 +235,7 @@ following options are available:: | |||
| **append_images** | ||||
|     A list of images to append as additional frames. Each of the | ||||
|     images in the list can be single or multiframe images. | ||||
|     This is currently supported for GIF, PDF, PNG, TIFF, and WebP. | ||||
|     This is currently supported for GIF, PDF, PNG, TIFF, WebP, and AVIF. | ||||
| 
 | ||||
|     It is also supported for ICO and ICNS. If images are passed in of relevant | ||||
|     sizes, they will be used instead of scaling down the main image. | ||||
|  | @ -1311,6 +1311,79 @@ XBM | |||
| 
 | ||||
| Pillow reads and writes X bitmap files (mode ``1``). | ||||
| 
 | ||||
| AVIF | ||||
| ^^^^ | ||||
| 
 | ||||
| Pillow reads and writes AVIF files, including AVIF sequence images. Currently, | ||||
| it is only possible to save 8-bit AVIF images, and all AVIF images are decoded | ||||
| as 8-bit RGB(A). | ||||
| 
 | ||||
| The :py:meth:`~PIL.Image.Image.save` method supports the following options: | ||||
| 
 | ||||
| **quality** | ||||
|     Integer, 1-100, Defaults to 90. 0 gives the smallest size and poorest | ||||
|     quality, 100 the largest and best quality. The value of this setting | ||||
|     controls the ``qmin`` and ``qmax`` encoder options. | ||||
| 
 | ||||
| **qmin** / **qmax** | ||||
|     Integer, 0-63. The quality of images created by an AVIF encoder are | ||||
|     controlled by minimum and maximum quantizer values. The higher these | ||||
|     values are, the worse the quality. | ||||
| 
 | ||||
| **subsampling** | ||||
|     If present, sets the subsampling for the encoder. Defaults to ``"4:2:0``". | ||||
|     Options include: | ||||
| 
 | ||||
|     * ``"4:0:0"`` | ||||
|     * ``"4:2:0"`` | ||||
|     * ``"4:2:2"`` | ||||
|     * ``"4:4:4"`` | ||||
| 
 | ||||
| **speed** | ||||
|     Quality/speed trade-off (0=slower-better, 10=fastest). Defaults to 8. | ||||
| 
 | ||||
| **range** | ||||
|     YUV range, either "full" or "limited." Defaults to "full" | ||||
| 
 | ||||
| **codec** | ||||
|     AV1 codec to use for encoding. Possible values are "aom", "rav1e", and | ||||
|     "svt", depending on what codecs were compiled with libavif. Defaults to | ||||
|     "auto", which will choose the first available codec in the order of the | ||||
|     preceding list. | ||||
| 
 | ||||
| **tile_rows** / **tile_cols** | ||||
|     For tile encoding, the (log 2) number of tile rows and columns to use. | ||||
|     Valid values are 0-6, default 0. | ||||
| 
 | ||||
| **alpha_premultiplied** | ||||
|     Encode the image with premultiplied alpha, defaults ``False`` | ||||
| 
 | ||||
| **icc_profile** | ||||
|     The ICC Profile to include in the saved file. | ||||
| 
 | ||||
| **exif** | ||||
|     The exif data to include in the saved file. | ||||
| 
 | ||||
| **xmp** | ||||
|     The XMP data to include in the saved file. | ||||
| 
 | ||||
| Saving sequences | ||||
| ~~~~~~~~~~~~~~~~~ | ||||
| 
 | ||||
| When calling :py:meth:`~PIL.Image.Image.save` to write an AVIF file, by default | ||||
| only the first frame of a multiframe image will be saved. If the ``save_all`` | ||||
| argument is present and true, then all frames will be saved, and the following | ||||
| options will also be available. | ||||
| 
 | ||||
| **append_images** | ||||
|     A list of images to append as additional frames. Each of the | ||||
|     images in the list can be single or multiframe images. | ||||
| 
 | ||||
| **duration** | ||||
|     The display duration of each frame, in milliseconds. Pass a single | ||||
|     integer for a constant duration, or a list or tuple to set the | ||||
|     duration for each frame separately. | ||||
| 
 | ||||
| Read-only formats | ||||
| ----------------- | ||||
| 
 | ||||
|  |  | |||
|  | @ -89,6 +89,16 @@ Many of Pillow's features require external libraries: | |||
| 
 | ||||
| * **libxcb** provides X11 screengrab support. | ||||
| 
 | ||||
| 
 | ||||
| * **libavif** provides support for the AVIF format. | ||||
| 
 | ||||
|   * Pillow requires libavif version **0.8.0** or greater, which is when | ||||
|     AVIF image sequence support was added. | ||||
|   * libavif is merely an API that wraps AVIF codecs. If you are compiling | ||||
|     libavif from source, you will also need to install both an AVIF encoder | ||||
|     and decoder, such as rav1e and dav1d, or libaom, which both encodes and | ||||
|     decodes AVIF images. | ||||
| 
 | ||||
| .. tab:: Linux | ||||
| 
 | ||||
|     If you didn't build Python from source, make sure you have Python's | ||||
|  | @ -117,6 +127,12 @@ Many of Pillow's features require external libraries: | |||
|     To install libraqm, ``sudo apt-get install meson`` and then see | ||||
|     ``depends/install_raqm.sh``. | ||||
| 
 | ||||
|     Build prerequisites for libavif on Ubuntu are installed with:: | ||||
| 
 | ||||
|         sudo apt-get install cmake ninja-build nasm | ||||
| 
 | ||||
|     Then see ``depends/install_libavif.sh`` to build and install libavif. | ||||
| 
 | ||||
|     Prerequisites are installed on recent **Red Hat**, **CentOS** or **Fedora** with:: | ||||
| 
 | ||||
|         sudo dnf install libtiff-devel libjpeg-devel openjpeg2-devel zlib-devel \ | ||||
|  | @ -156,6 +172,12 @@ Many of Pillow's features require external libraries: | |||
| 
 | ||||
|     Then see ``depends/install_raqm_cmake.sh`` to install libraqm. | ||||
| 
 | ||||
|     To install libavif on macOS use Homebrew to install its build dependencies:: | ||||
| 
 | ||||
|         brew install aom dav1d rav1e | ||||
| 
 | ||||
|     Then see ``depends/install_libavif.sh`` to install libavif. | ||||
| 
 | ||||
| .. tab:: Windows | ||||
| 
 | ||||
|     We recommend you use prebuilt wheels from PyPI. | ||||
|  | @ -193,7 +215,8 @@ Many of Pillow's features require external libraries: | |||
|             mingw-w64-x86_64-libwebp \ | ||||
|             mingw-w64-x86_64-openjpeg2 \ | ||||
|             mingw-w64-x86_64-libimagequant \ | ||||
|             mingw-w64-x86_64-libraqm | ||||
|             mingw-w64-x86_64-libraqm \ | ||||
|             mingw-w64-x86_64-libavif | ||||
| 
 | ||||
|     https://www.msys2.org/docs/python/ states that setuptools >= 60 does not work with | ||||
|     MSYS2. To workaround this, before installing Pillow you must run:: | ||||
|  | @ -210,9 +233,10 @@ Many of Pillow's features require external libraries: | |||
| 
 | ||||
|     Prerequisites are installed on **FreeBSD 10 or 11** with:: | ||||
| 
 | ||||
|         sudo pkg install jpeg-turbo tiff webp lcms2 freetype2 openjpeg harfbuzz fribidi libxcb | ||||
|         sudo pkg install jpeg-turbo tiff webp lcms2 freetype2 openjpeg harfbuzz fribidi libxcb libavif | ||||
| 
 | ||||
|     Then see ``depends/install_raqm_cmake.sh`` to install libraqm. | ||||
|     See ``depends/install_raqm_cmake.sh`` to install libraqm and | ||||
|     ``depends/install_libavif.sh`` to install libavif. | ||||
| 
 | ||||
| .. tab:: Android | ||||
| 
 | ||||
|  |  | |||
|  | @ -21,6 +21,7 @@ Support for the following modules can be checked: | |||
| * ``freetype2``: FreeType font support via :py:func:`PIL.ImageFont.truetype`. | ||||
| * ``littlecms2``: LittleCMS 2 support via :py:mod:`PIL.ImageCms`. | ||||
| * ``webp``: WebP image support. | ||||
| * ``avif``: AVIF image support. | ||||
| 
 | ||||
| .. autofunction:: PIL.features.check_module | ||||
| .. autofunction:: PIL.features.version_module | ||||
|  |  | |||
|  | @ -1,6 +1,14 @@ | |||
| Plugin reference | ||||
| ================ | ||||
| 
 | ||||
| :mod:`~PIL.AvifImagePlugin` Module | ||||
| ---------------------------------- | ||||
| 
 | ||||
| .. automodule:: PIL.AvifImagePlugin | ||||
|     :members: | ||||
|     :undoc-members: | ||||
|     :show-inheritance: | ||||
| 
 | ||||
| :mod:`~PIL.BmpImagePlugin` Module | ||||
| --------------------------------- | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										17
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								setup.py
									
									
									
									
									
								
							|  | @ -305,6 +305,7 @@ class pil_build_ext(build_ext): | |||
|             "jpeg2000", | ||||
|             "imagequant", | ||||
|             "xcb", | ||||
|             "avif", | ||||
|         ] | ||||
| 
 | ||||
|         required = {"jpeg", "zlib"} | ||||
|  | @ -839,6 +840,12 @@ class pil_build_ext(build_ext): | |||
|                 if _find_library_file(self, "xcb"): | ||||
|                     feature.set("xcb", "xcb") | ||||
| 
 | ||||
|         if feature.want("avif"): | ||||
|             _dbg("Looking for avif") | ||||
|             if _find_include_file(self, "avif/avif.h"): | ||||
|                 if _find_library_file(self, "avif"): | ||||
|                     feature.set("avif", "avif") | ||||
| 
 | ||||
|         for f in feature: | ||||
|             if not feature.get(f) and feature.require(f): | ||||
|                 if f in ("jpeg", "zlib"): | ||||
|  | @ -927,6 +934,14 @@ class pil_build_ext(build_ext): | |||
|         else: | ||||
|             self._remove_extension("PIL._webp") | ||||
| 
 | ||||
|         if feature.get("avif"): | ||||
|             libs = [feature.get("avif")] | ||||
|             if sys.platform == "win32": | ||||
|                 libs.extend(["ntdll", "userenv", "ws2_32", "bcrypt"]) | ||||
|             self._update_extension("PIL._avif", libs) | ||||
|         else: | ||||
|             self._remove_extension("PIL._avif") | ||||
| 
 | ||||
|         tk_libs = ["psapi"] if sys.platform in ("win32", "cygwin") else [] | ||||
|         self._update_extension("PIL._imagingtk", tk_libs) | ||||
| 
 | ||||
|  | @ -969,6 +984,7 @@ class pil_build_ext(build_ext): | |||
|             (feature.get("lcms"), "LITTLECMS2"), | ||||
|             (feature.get("webp"), "WEBP"), | ||||
|             (feature.get("xcb"), "XCB (X protocol)"), | ||||
|             (feature.get("avif"), "LIBAVIF"), | ||||
|         ] | ||||
| 
 | ||||
|         all = 1 | ||||
|  | @ -1011,6 +1027,7 @@ ext_modules = [ | |||
|     Extension("PIL._imagingft", ["src/_imagingft.c"]), | ||||
|     Extension("PIL._imagingcms", ["src/_imagingcms.c"]), | ||||
|     Extension("PIL._webp", ["src/_webp.c"]), | ||||
|     Extension("PIL._avif", ["src/_avif.c"]), | ||||
|     Extension("PIL._imagingtk", ["src/_imagingtk.c", "src/Tk/tkImaging.c"]), | ||||
|     Extension("PIL._imagingmath", ["src/_imagingmath.c"]), | ||||
|     Extension("PIL._imagingmorph", ["src/_imagingmorph.c"]), | ||||
|  |  | |||
							
								
								
									
										282
									
								
								src/PIL/AvifImagePlugin.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										282
									
								
								src/PIL/AvifImagePlugin.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,282 @@ | |||
| from __future__ import annotations | ||||
| 
 | ||||
| from io import BytesIO | ||||
| 
 | ||||
| from . import ExifTags, Image, ImageFile | ||||
| 
 | ||||
| try: | ||||
|     from . import _avif | ||||
| 
 | ||||
|     SUPPORTED = True | ||||
| except ImportError: | ||||
|     SUPPORTED = False | ||||
| 
 | ||||
| # Decoder options as module globals, until there is a way to pass parameters | ||||
| # to Image.open (see https://github.com/python-pillow/Pillow/issues/569) | ||||
| DECODE_CODEC_CHOICE = "auto" | ||||
| CHROMA_UPSAMPLING = "auto" | ||||
| DEFAULT_MAX_THREADS = 0 | ||||
| 
 | ||||
| _VALID_AVIF_MODES = {"RGB", "RGBA"} | ||||
| 
 | ||||
| 
 | ||||
| def _accept(prefix): | ||||
|     if prefix[4:8] != b"ftyp": | ||||
|         return | ||||
|     coding_brands = (b"avif", b"avis") | ||||
|     container_brands = (b"mif1", b"msf1") | ||||
|     major_brand = prefix[8:12] | ||||
|     if major_brand in coding_brands: | ||||
|         if not SUPPORTED: | ||||
|             return ( | ||||
|                 "image file could not be identified because AVIF " | ||||
|                 "support not installed" | ||||
|             ) | ||||
|         return True | ||||
|     if major_brand in container_brands: | ||||
|         # We accept files with AVIF container brands; we can't yet know if | ||||
|         # the ftyp box has the correct compatible brands, but if it doesn't | ||||
|         # then the plugin will raise a SyntaxError which Pillow will catch | ||||
|         # before moving on to the next plugin that accepts the file. | ||||
|         # | ||||
|         # Also, because this file might not actually be an AVIF file, we | ||||
|         # don't raise an error if AVIF support isn't properly compiled. | ||||
|         return True | ||||
| 
 | ||||
| 
 | ||||
| class AvifImageFile(ImageFile.ImageFile): | ||||
|     format = "AVIF" | ||||
|     format_description = "AVIF image" | ||||
|     __loaded = -1 | ||||
|     __frame = 0 | ||||
| 
 | ||||
|     def load_seek(self, pos: int) -> None: | ||||
|         pass | ||||
| 
 | ||||
|     def _open(self): | ||||
|         if not SUPPORTED: | ||||
|             msg = ( | ||||
|                 "image file could not be identified because AVIF " | ||||
|                 "support not installed" | ||||
|             ) | ||||
|             raise SyntaxError(msg) | ||||
| 
 | ||||
|         self._decoder = _avif.AvifDecoder( | ||||
|             self.fp.read(), DECODE_CODEC_CHOICE, CHROMA_UPSAMPLING, DEFAULT_MAX_THREADS | ||||
|         ) | ||||
| 
 | ||||
|         # Get info from decoder | ||||
|         width, height, n_frames, mode, icc, exif, xmp = self._decoder.get_info() | ||||
|         self._size = width, height | ||||
|         self.n_frames = n_frames | ||||
|         self.is_animated = self.n_frames > 1 | ||||
|         self._mode = self.rawmode = mode | ||||
|         self.tile = [] | ||||
| 
 | ||||
|         if icc: | ||||
|             self.info["icc_profile"] = icc | ||||
|         if exif: | ||||
|             self.info["exif"] = exif | ||||
|         if xmp: | ||||
|             self.info["xmp"] = xmp | ||||
| 
 | ||||
|     def seek(self, frame): | ||||
|         if not self._seek_check(frame): | ||||
|             return | ||||
| 
 | ||||
|         self.__frame = frame | ||||
| 
 | ||||
|     def load(self): | ||||
|         if self.__loaded != self.__frame: | ||||
|             # We need to load the image data for this frame | ||||
|             data, timescale, tsp_in_ts, dur_in_ts = self._decoder.get_frame( | ||||
|                 self.__frame | ||||
|             ) | ||||
|             timestamp = round(1000 * (tsp_in_ts / timescale)) | ||||
|             duration = round(1000 * (dur_in_ts / timescale)) | ||||
|             self.info["timestamp"] = timestamp | ||||
|             self.info["duration"] = duration | ||||
|             self.__loaded = self.__frame | ||||
| 
 | ||||
|             # Set tile | ||||
|             if self.fp and self._exclusive_fp: | ||||
|                 self.fp.close() | ||||
|             self.fp = BytesIO(data) | ||||
|             self.tile = [("raw", (0, 0) + self.size, 0, self.rawmode)] | ||||
| 
 | ||||
|         return super().load() | ||||
| 
 | ||||
|     def tell(self): | ||||
|         return self.__frame | ||||
| 
 | ||||
| 
 | ||||
| def _save_all(im, fp, filename): | ||||
|     _save(im, fp, filename, save_all=True) | ||||
| 
 | ||||
| 
 | ||||
| def _save(im, fp, filename, save_all=False): | ||||
|     info = im.encoderinfo.copy() | ||||
|     if save_all: | ||||
|         append_images = list(info.get("append_images", [])) | ||||
|     else: | ||||
|         append_images = [] | ||||
| 
 | ||||
|     total = 0 | ||||
|     for ims in [im] + append_images: | ||||
|         total += getattr(ims, "n_frames", 1) | ||||
| 
 | ||||
|     is_single_frame = total == 1 | ||||
| 
 | ||||
|     qmin = info.get("qmin", -1) | ||||
|     qmax = info.get("qmax", -1) | ||||
|     quality = info.get("quality", 75) | ||||
|     if not isinstance(quality, int) or quality < 0 or quality > 100: | ||||
|         msg = "Invalid quality setting" | ||||
|         raise ValueError(msg) | ||||
| 
 | ||||
|     duration = info.get("duration", 0) | ||||
|     subsampling = info.get("subsampling", "4:2:0") | ||||
|     speed = info.get("speed", 6) | ||||
|     max_threads = info.get("max_threads", DEFAULT_MAX_THREADS) | ||||
|     codec = info.get("codec", "auto") | ||||
|     range_ = info.get("range", "full") | ||||
|     tile_rows_log2 = info.get("tile_rows", 0) | ||||
|     tile_cols_log2 = info.get("tile_cols", 0) | ||||
|     alpha_premultiplied = bool(info.get("alpha_premultiplied", False)) | ||||
|     autotiling = bool(info.get("autotiling", tile_rows_log2 == tile_cols_log2 == 0)) | ||||
| 
 | ||||
|     icc_profile = info.get("icc_profile", im.info.get("icc_profile")) | ||||
|     exif = info.get("exif", im.info.get("exif")) | ||||
|     if isinstance(exif, Image.Exif): | ||||
|         exif = exif.tobytes() | ||||
| 
 | ||||
|     exif_orientation = 0 | ||||
|     if exif: | ||||
|         exif_data = Image.Exif() | ||||
|         try: | ||||
|             exif_data.load(exif) | ||||
|         except SyntaxError: | ||||
|             pass | ||||
|         else: | ||||
|             orientation_tag = next( | ||||
|                 k for k, v in ExifTags.TAGS.items() if v == "Orientation" | ||||
|             ) | ||||
|             exif_orientation = exif_data.get(orientation_tag) or 0 | ||||
| 
 | ||||
|     xmp = info.get("xmp", im.info.get("xmp") or im.info.get("XML:com.adobe.xmp")) | ||||
| 
 | ||||
|     if isinstance(xmp, str): | ||||
|         xmp = xmp.encode("utf-8") | ||||
| 
 | ||||
|     advanced = info.get("advanced") | ||||
|     if isinstance(advanced, dict): | ||||
|         advanced = tuple([k, v] for (k, v) in advanced.items()) | ||||
|     if advanced is not None: | ||||
|         try: | ||||
|             advanced = tuple(advanced) | ||||
|         except TypeError: | ||||
|             invalid = True | ||||
|         else: | ||||
|             invalid = all(isinstance(v, tuple) and len(v) == 2 for v in advanced) | ||||
|         if invalid: | ||||
|             msg = ( | ||||
|                 "advanced codec options must be a dict of key-value string " | ||||
|                 "pairs or a series of key-value two-tuples" | ||||
|             ) | ||||
|             raise ValueError(msg) | ||||
|         advanced = tuple( | ||||
|             [(str(k).encode("utf-8"), str(v).encode("utf-8")) for k, v in advanced] | ||||
|         ) | ||||
| 
 | ||||
|     # Setup the AVIF encoder | ||||
|     enc = _avif.AvifEncoder( | ||||
|         im.size[0], | ||||
|         im.size[1], | ||||
|         subsampling, | ||||
|         qmin, | ||||
|         qmax, | ||||
|         quality, | ||||
|         speed, | ||||
|         max_threads, | ||||
|         codec, | ||||
|         range_, | ||||
|         tile_rows_log2, | ||||
|         tile_cols_log2, | ||||
|         alpha_premultiplied, | ||||
|         autotiling, | ||||
|         icc_profile or b"", | ||||
|         exif or b"", | ||||
|         exif_orientation, | ||||
|         xmp or b"", | ||||
|         advanced, | ||||
|     ) | ||||
| 
 | ||||
|     # Add each frame | ||||
|     frame_idx = 0 | ||||
|     frame_dur = 0 | ||||
|     cur_idx = im.tell() | ||||
|     try: | ||||
|         for ims in [im] + append_images: | ||||
|             # Get # of frames in this image | ||||
|             nfr = getattr(ims, "n_frames", 1) | ||||
| 
 | ||||
|             for idx in range(nfr): | ||||
|                 ims.seek(idx) | ||||
|                 ims.load() | ||||
| 
 | ||||
|                 # Make sure image mode is supported | ||||
|                 frame = ims | ||||
|                 rawmode = ims.mode | ||||
|                 if ims.mode not in _VALID_AVIF_MODES: | ||||
|                     alpha = ( | ||||
|                         "A" in ims.mode | ||||
|                         or "a" in ims.mode | ||||
|                         or (ims.mode == "P" and "A" in ims.im.getpalettemode()) | ||||
|                         or ( | ||||
|                             ims.mode == "P" | ||||
|                             and ims.info.get("transparency", None) is not None | ||||
|                         ) | ||||
|                     ) | ||||
|                     rawmode = "RGBA" if alpha else "RGB" | ||||
|                     frame = ims.convert(rawmode) | ||||
| 
 | ||||
|                 # Update frame duration | ||||
|                 if isinstance(duration, (list, tuple)): | ||||
|                     frame_dur = duration[frame_idx] | ||||
|                 else: | ||||
|                     frame_dur = duration | ||||
| 
 | ||||
|                 # Append the frame to the animation encoder | ||||
|                 enc.add( | ||||
|                     frame.tobytes("raw", rawmode), | ||||
|                     frame_dur, | ||||
|                     frame.size[0], | ||||
|                     frame.size[1], | ||||
|                     rawmode, | ||||
|                     is_single_frame, | ||||
|                 ) | ||||
| 
 | ||||
|                 # Update frame index | ||||
|                 frame_idx += 1 | ||||
| 
 | ||||
|                 if not save_all: | ||||
|                     break | ||||
| 
 | ||||
|     finally: | ||||
|         im.seek(cur_idx) | ||||
| 
 | ||||
|     # Get the final output from the encoder | ||||
|     data = enc.finish() | ||||
|     if data is None: | ||||
|         msg = "cannot write file as AVIF (encoder returned None)" | ||||
|         raise OSError(msg) | ||||
| 
 | ||||
|     fp.write(data) | ||||
| 
 | ||||
| 
 | ||||
| Image.register_open(AvifImageFile.format, AvifImageFile, _accept) | ||||
| if SUPPORTED: | ||||
|     Image.register_save(AvifImageFile.format, _save) | ||||
|     Image.register_save_all(AvifImageFile.format, _save_all) | ||||
|     Image.register_extensions(AvifImageFile.format, [".avif", ".avifs"]) | ||||
|     Image.register_mime(AvifImageFile.format, "image/avif") | ||||
|  | @ -1548,7 +1548,9 @@ class Image: | |||
| 
 | ||||
|         # XMP tags | ||||
|         if ExifTags.Base.Orientation not in self._exif: | ||||
|             xmp_tags = self.info.get("XML:com.adobe.xmp") | ||||
|             xmp_tags = self.info.get("XML:com.adobe.xmp") or self.info.get("xmp") | ||||
|             if isinstance(xmp_tags, bytes): | ||||
|                 xmp_tags = xmp_tags.decode("utf-8") | ||||
|             if xmp_tags: | ||||
|                 match = re.search(r'tiff:Orientation(="|>)([0-9])', xmp_tags) | ||||
|                 if match: | ||||
|  |  | |||
|  | @ -25,6 +25,7 @@ del _version | |||
| 
 | ||||
| 
 | ||||
| _plugins = [ | ||||
|     "AvifImagePlugin", | ||||
|     "BlpImagePlugin", | ||||
|     "BmpImagePlugin", | ||||
|     "BufrStubImagePlugin", | ||||
|  |  | |||
							
								
								
									
										3
									
								
								src/PIL/_avif.pyi
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								src/PIL/_avif.pyi
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,3 @@ | |||
| from typing import Any | ||||
| 
 | ||||
| def __getattr__(name: str) -> Any: ... | ||||
|  | @ -17,6 +17,7 @@ modules = { | |||
|     "freetype2": ("PIL._imagingft", "freetype2_version"), | ||||
|     "littlecms2": ("PIL._imagingcms", "littlecms_version"), | ||||
|     "webp": ("PIL._webp", "webpdecoder_version"), | ||||
|     "avif": ("PIL._avif", "libavif_version"), | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
|  | @ -287,6 +288,7 @@ def pilinfo(out: IO[str] | None = None, supported_formats: bool = True) -> None: | |||
|         ("littlecms2", "LITTLECMS2"), | ||||
|         ("webp", "WEBP"), | ||||
|         ("jpg", "JPEG"), | ||||
|         ("avif", "AVIF"), | ||||
|         ("jpg_2000", "OPENJPEG (JPEG2000)"), | ||||
|         ("zlib", "ZLIB (PNG/ZIP)"), | ||||
|         ("libtiff", "LIBTIFF"), | ||||
|  |  | |||
							
								
								
									
										1084
									
								
								src/_avif.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1084
									
								
								src/_avif.c
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										23
									
								
								wheels/dependency_licenses/DAV1D.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								wheels/dependency_licenses/DAV1D.txt
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,23 @@ | |||
| Copyright © 2018-2019, VideoLAN and dav1d authors | ||||
| All rights reserved. | ||||
| 
 | ||||
| Redistribution and use in source and binary forms, with or without | ||||
| modification, are permitted provided that the following conditions are met: | ||||
| 
 | ||||
| 1. Redistributions of source code must retain the above copyright notice, this | ||||
|    list of conditions and the following disclaimer. | ||||
| 
 | ||||
| 2. Redistributions in binary form must reproduce the above copyright notice, | ||||
|    this list of conditions and the following disclaimer in the documentation | ||||
|    and/or other materials provided with the distribution. | ||||
| 
 | ||||
| THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | ||||
| ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||||
| WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||||
| DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR | ||||
| ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||||
| (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||||
| LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||||
| ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||||
| (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||||
| SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||
							
								
								
									
										387
									
								
								wheels/dependency_licenses/LIBAVIF.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										387
									
								
								wheels/dependency_licenses/LIBAVIF.txt
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,387 @@ | |||
| Copyright 2019 Joe Drago. All rights reserved. | ||||
| 
 | ||||
| Redistribution and use in source and binary forms, with or without | ||||
| modification, are permitted provided that the following conditions are met: | ||||
| 
 | ||||
| 1. Redistributions of source code must retain the above copyright notice, this | ||||
| list of conditions and the following disclaimer. | ||||
| 
 | ||||
| 2. Redistributions in binary form must reproduce the above copyright notice, | ||||
| this list of conditions and the following disclaimer in the documentation | ||||
| and/or other materials provided with the distribution. | ||||
| 
 | ||||
| THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||||
| AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||||
| IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||||
| DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||||
| FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||||
| DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||||
| SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||||
| CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||||
| OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||||
| OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||
| 
 | ||||
| ------------------------------------------------------------------------------ | ||||
| 
 | ||||
| Files: src/obu.c | ||||
| 
 | ||||
| Copyright © 2018-2019, VideoLAN and dav1d authors | ||||
| All rights reserved. | ||||
| 
 | ||||
| Redistribution and use in source and binary forms, with or without | ||||
| modification, are permitted provided that the following conditions are met: | ||||
| 
 | ||||
| 1. Redistributions of source code must retain the above copyright notice, this | ||||
|    list of conditions and the following disclaimer. | ||||
| 
 | ||||
| 2. Redistributions in binary form must reproduce the above copyright notice, | ||||
|    this list of conditions and the following disclaimer in the documentation | ||||
|    and/or other materials provided with the distribution. | ||||
| 
 | ||||
| THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | ||||
| ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||||
| WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||||
| DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR | ||||
| ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||||
| (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||||
| LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||||
| ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||||
| (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||||
| SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||
| 
 | ||||
| ------------------------------------------------------------------------------ | ||||
| 
 | ||||
| Files: apps/shared/iccjpeg.* | ||||
| 
 | ||||
| In plain English: | ||||
| 
 | ||||
| 1. We don't promise that this software works.  (But if you find any bugs, | ||||
|    please let us know!) | ||||
| 2. You can use this software for whatever you want.  You don't have to pay us. | ||||
| 3. You may not pretend that you wrote this software.  If you use it in a | ||||
|    program, you must acknowledge somewhere in your documentation that | ||||
|    you've used the IJG code. | ||||
| 
 | ||||
| In legalese: | ||||
| 
 | ||||
| The authors make NO WARRANTY or representation, either express or implied, | ||||
| with respect to this software, its quality, accuracy, merchantability, or | ||||
| fitness for a particular purpose.  This software is provided "AS IS", and you, | ||||
| its user, assume the entire risk as to its quality and accuracy. | ||||
| 
 | ||||
| This software is copyright (C) 1991-2013, Thomas G. Lane, Guido Vollbeding. | ||||
| All Rights Reserved except as specified below. | ||||
| 
 | ||||
| Permission is hereby granted to use, copy, modify, and distribute this | ||||
| software (or portions thereof) for any purpose, without fee, subject to these | ||||
| conditions: | ||||
| (1) If any part of the source code for this software is distributed, then this | ||||
| README file must be included, with this copyright and no-warranty notice | ||||
| unaltered; and any additions, deletions, or changes to the original files | ||||
| must be clearly indicated in accompanying documentation. | ||||
| (2) If only executable code is distributed, then the accompanying | ||||
| documentation must state that "this software is based in part on the work of | ||||
| the Independent JPEG Group". | ||||
| (3) Permission for use of this software is granted only if the user accepts | ||||
| full responsibility for any undesirable consequences; the authors accept | ||||
| NO LIABILITY for damages of any kind. | ||||
| 
 | ||||
| These conditions apply to any software derived from or based on the IJG code, | ||||
| not just to the unmodified library.  If you use our work, you ought to | ||||
| acknowledge us. | ||||
| 
 | ||||
| Permission is NOT granted for the use of any IJG author's name or company name | ||||
| in advertising or publicity relating to this software or products derived from | ||||
| it.  This software may be referred to only as "the Independent JPEG Group's | ||||
| software". | ||||
| 
 | ||||
| We specifically permit and encourage the use of this software as the basis of | ||||
| commercial products, provided that all warranty or liability claims are | ||||
| assumed by the product vendor. | ||||
| 
 | ||||
| 
 | ||||
| The Unix configuration script "configure" was produced with GNU Autoconf. | ||||
| It is copyright by the Free Software Foundation but is freely distributable. | ||||
| The same holds for its supporting scripts (config.guess, config.sub, | ||||
| ltmain.sh).  Another support script, install-sh, is copyright by X Consortium | ||||
| but is also freely distributable. | ||||
| 
 | ||||
| The IJG distribution formerly included code to read and write GIF files. | ||||
| To avoid entanglement with the Unisys LZW patent, GIF reading support has | ||||
| been removed altogether, and the GIF writer has been simplified to produce | ||||
| "uncompressed GIFs".  This technique does not use the LZW algorithm; the | ||||
| resulting GIF files are larger than usual, but are readable by all standard | ||||
| GIF decoders. | ||||
| 
 | ||||
| We are required to state that | ||||
|     "The Graphics Interchange Format(c) is the Copyright property of | ||||
|     CompuServe Incorporated.  GIF(sm) is a Service Mark property of | ||||
|     CompuServe Incorporated." | ||||
| 
 | ||||
| ------------------------------------------------------------------------------ | ||||
| 
 | ||||
| Files: contrib/gdk-pixbuf/* | ||||
| 
 | ||||
| Copyright 2020 Emmanuel Gil Peyrot. All rights reserved. | ||||
| 
 | ||||
| Redistribution and use in source and binary forms, with or without | ||||
| modification, are permitted provided that the following conditions are met: | ||||
| 
 | ||||
| 1. Redistributions of source code must retain the above copyright notice, this | ||||
| list of conditions and the following disclaimer. | ||||
| 
 | ||||
| 2. Redistributions in binary form must reproduce the above copyright notice, | ||||
| this list of conditions and the following disclaimer in the documentation | ||||
| and/or other materials provided with the distribution. | ||||
| 
 | ||||
| THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||||
| AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||||
| IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||||
| DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||||
| FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||||
| DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||||
| SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||||
| CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||||
| OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||||
| OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||
| 
 | ||||
| ------------------------------------------------------------------------------ | ||||
| 
 | ||||
| Files: android_jni/gradlew* | ||||
| 
 | ||||
| 
 | ||||
|                                  Apache License | ||||
|                            Version 2.0, January 2004 | ||||
|                         http://www.apache.org/licenses/ | ||||
| 
 | ||||
|    TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION | ||||
| 
 | ||||
|    1. Definitions. | ||||
| 
 | ||||
|       "License" shall mean the terms and conditions for use, reproduction, | ||||
|       and distribution as defined by Sections 1 through 9 of this document. | ||||
| 
 | ||||
|       "Licensor" shall mean the copyright owner or entity authorized by | ||||
|       the copyright owner that is granting the License. | ||||
| 
 | ||||
|       "Legal Entity" shall mean the union of the acting entity and all | ||||
|       other entities that control, are controlled by, or are under common | ||||
|       control with that entity. For the purposes of this definition, | ||||
|       "control" means (i) the power, direct or indirect, to cause the | ||||
|       direction or management of such entity, whether by contract or | ||||
|       otherwise, or (ii) ownership of fifty percent (50%) or more of the | ||||
|       outstanding shares, or (iii) beneficial ownership of such entity. | ||||
| 
 | ||||
|       "You" (or "Your") shall mean an individual or Legal Entity | ||||
|       exercising permissions granted by this License. | ||||
| 
 | ||||
|       "Source" form shall mean the preferred form for making modifications, | ||||
|       including but not limited to software source code, documentation | ||||
|       source, and configuration files. | ||||
| 
 | ||||
|       "Object" form shall mean any form resulting from mechanical | ||||
|       transformation or translation of a Source form, including but | ||||
|       not limited to compiled object code, generated documentation, | ||||
|       and conversions to other media types. | ||||
| 
 | ||||
|       "Work" shall mean the work of authorship, whether in Source or | ||||
|       Object form, made available under the License, as indicated by a | ||||
|       copyright notice that is included in or attached to the work | ||||
|       (an example is provided in the Appendix below). | ||||
| 
 | ||||
|       "Derivative Works" shall mean any work, whether in Source or Object | ||||
|       form, that is based on (or derived from) the Work and for which the | ||||
|       editorial revisions, annotations, elaborations, or other modifications | ||||
|       represent, as a whole, an original work of authorship. For the purposes | ||||
|       of this License, Derivative Works shall not include works that remain | ||||
|       separable from, or merely link (or bind by name) to the interfaces of, | ||||
|       the Work and Derivative Works thereof. | ||||
| 
 | ||||
|       "Contribution" shall mean any work of authorship, including | ||||
|       the original version of the Work and any modifications or additions | ||||
|       to that Work or Derivative Works thereof, that is intentionally | ||||
|       submitted to Licensor for inclusion in the Work by the copyright owner | ||||
|       or by an individual or Legal Entity authorized to submit on behalf of | ||||
|       the copyright owner. For the purposes of this definition, "submitted" | ||||
|       means any form of electronic, verbal, or written communication sent | ||||
|       to the Licensor or its representatives, including but not limited to | ||||
|       communication on electronic mailing lists, source code control systems, | ||||
|       and issue tracking systems that are managed by, or on behalf of, the | ||||
|       Licensor for the purpose of discussing and improving the Work, but | ||||
|       excluding communication that is conspicuously marked or otherwise | ||||
|       designated in writing by the copyright owner as "Not a Contribution." | ||||
| 
 | ||||
|       "Contributor" shall mean Licensor and any individual or Legal Entity | ||||
|       on behalf of whom a Contribution has been received by Licensor and | ||||
|       subsequently incorporated within the Work. | ||||
| 
 | ||||
|    2. Grant of Copyright License. Subject to the terms and conditions of | ||||
|       this License, each Contributor hereby grants to You a perpetual, | ||||
|       worldwide, non-exclusive, no-charge, royalty-free, irrevocable | ||||
|       copyright license to reproduce, prepare Derivative Works of, | ||||
|       publicly display, publicly perform, sublicense, and distribute the | ||||
|       Work and such Derivative Works in Source or Object form. | ||||
| 
 | ||||
|    3. Grant of Patent License. Subject to the terms and conditions of | ||||
|       this License, each Contributor hereby grants to You a perpetual, | ||||
|       worldwide, non-exclusive, no-charge, royalty-free, irrevocable | ||||
|       (except as stated in this section) patent license to make, have made, | ||||
|       use, offer to sell, sell, import, and otherwise transfer the Work, | ||||
|       where such license applies only to those patent claims licensable | ||||
|       by such Contributor that are necessarily infringed by their | ||||
|       Contribution(s) alone or by combination of their Contribution(s) | ||||
|       with the Work to which such Contribution(s) was submitted. If You | ||||
|       institute patent litigation against any entity (including a | ||||
|       cross-claim or counterclaim in a lawsuit) alleging that the Work | ||||
|       or a Contribution incorporated within the Work constitutes direct | ||||
|       or contributory patent infringement, then any patent licenses | ||||
|       granted to You under this License for that Work shall terminate | ||||
|       as of the date such litigation is filed. | ||||
| 
 | ||||
|    4. Redistribution. You may reproduce and distribute copies of the | ||||
|       Work or Derivative Works thereof in any medium, with or without | ||||
|       modifications, and in Source or Object form, provided that You | ||||
|       meet the following conditions: | ||||
| 
 | ||||
|       (a) You must give any other recipients of the Work or | ||||
|           Derivative Works a copy of this License; and | ||||
| 
 | ||||
|       (b) You must cause any modified files to carry prominent notices | ||||
|           stating that You changed the files; and | ||||
| 
 | ||||
|       (c) You must retain, in the Source form of any Derivative Works | ||||
|           that You distribute, all copyright, patent, trademark, and | ||||
|           attribution notices from the Source form of the Work, | ||||
|           excluding those notices that do not pertain to any part of | ||||
|           the Derivative Works; and | ||||
| 
 | ||||
|       (d) If the Work includes a "NOTICE" text file as part of its | ||||
|           distribution, then any Derivative Works that You distribute must | ||||
|           include a readable copy of the attribution notices contained | ||||
|           within such NOTICE file, excluding those notices that do not | ||||
|           pertain to any part of the Derivative Works, in at least one | ||||
|           of the following places: within a NOTICE text file distributed | ||||
|           as part of the Derivative Works; within the Source form or | ||||
|           documentation, if provided along with the Derivative Works; or, | ||||
|           within a display generated by the Derivative Works, if and | ||||
|           wherever such third-party notices normally appear. The contents | ||||
|           of the NOTICE file are for informational purposes only and | ||||
|           do not modify the License. You may add Your own attribution | ||||
|           notices within Derivative Works that You distribute, alongside | ||||
|           or as an addendum to the NOTICE text from the Work, provided | ||||
|           that such additional attribution notices cannot be construed | ||||
|           as modifying the License. | ||||
| 
 | ||||
|       You may add Your own copyright statement to Your modifications and | ||||
|       may provide additional or different license terms and conditions | ||||
|       for use, reproduction, or distribution of Your modifications, or | ||||
|       for any such Derivative Works as a whole, provided Your use, | ||||
|       reproduction, and distribution of the Work otherwise complies with | ||||
|       the conditions stated in this License. | ||||
| 
 | ||||
|    5. Submission of Contributions. Unless You explicitly state otherwise, | ||||
|       any Contribution intentionally submitted for inclusion in the Work | ||||
|       by You to the Licensor shall be under the terms and conditions of | ||||
|       this License, without any additional terms or conditions. | ||||
|       Notwithstanding the above, nothing herein shall supersede or modify | ||||
|       the terms of any separate license agreement you may have executed | ||||
|       with Licensor regarding such Contributions. | ||||
| 
 | ||||
|    6. Trademarks. This License does not grant permission to use the trade | ||||
|       names, trademarks, service marks, or product names of the Licensor, | ||||
|       except as required for reasonable and customary use in describing the | ||||
|       origin of the Work and reproducing the content of the NOTICE file. | ||||
| 
 | ||||
|    7. Disclaimer of Warranty. Unless required by applicable law or | ||||
|       agreed to in writing, Licensor provides the Work (and each | ||||
|       Contributor provides its Contributions) on an "AS IS" BASIS, | ||||
|       WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or | ||||
|       implied, including, without limitation, any warranties or conditions | ||||
|       of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A | ||||
|       PARTICULAR PURPOSE. You are solely responsible for determining the | ||||
|       appropriateness of using or redistributing the Work and assume any | ||||
|       risks associated with Your exercise of permissions under this License. | ||||
| 
 | ||||
|    8. Limitation of Liability. In no event and under no legal theory, | ||||
|       whether in tort (including negligence), contract, or otherwise, | ||||
|       unless required by applicable law (such as deliberate and grossly | ||||
|       negligent acts) or agreed to in writing, shall any Contributor be | ||||
|       liable to You for damages, including any direct, indirect, special, | ||||
|       incidental, or consequential damages of any character arising as a | ||||
|       result of this License or out of the use or inability to use the | ||||
|       Work (including but not limited to damages for loss of goodwill, | ||||
|       work stoppage, computer failure or malfunction, or any and all | ||||
|       other commercial damages or losses), even if such Contributor | ||||
|       has been advised of the possibility of such damages. | ||||
| 
 | ||||
|    9. Accepting Warranty or Additional Liability. While redistributing | ||||
|       the Work or Derivative Works thereof, You may choose to offer, | ||||
|       and charge a fee for, acceptance of support, warranty, indemnity, | ||||
|       or other liability obligations and/or rights consistent with this | ||||
|       License. However, in accepting such obligations, You may act only | ||||
|       on Your own behalf and on Your sole responsibility, not on behalf | ||||
|       of any other Contributor, and only if You agree to indemnify, | ||||
|       defend, and hold each Contributor harmless for any liability | ||||
|       incurred by, or claims asserted against, such Contributor by reason | ||||
|       of your accepting any such warranty or additional liability. | ||||
| 
 | ||||
|    END OF TERMS AND CONDITIONS | ||||
| 
 | ||||
|    APPENDIX: How to apply the Apache License to your work. | ||||
| 
 | ||||
|       To apply the Apache License to your work, attach the following | ||||
|       boilerplate notice, with the fields enclosed by brackets "[]" | ||||
|       replaced with your own identifying information. (Don't include | ||||
|       the brackets!)  The text should be enclosed in the appropriate | ||||
|       comment syntax for the file format. We also recommend that a | ||||
|       file or class name and description of purpose be included on the | ||||
|       same "printed page" as the copyright notice for easier | ||||
|       identification within third-party archives. | ||||
| 
 | ||||
|    Copyright [yyyy] [name of copyright owner] | ||||
| 
 | ||||
|    Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|    you may not use this file except in compliance with the License. | ||||
|    You may obtain a copy of the License at | ||||
| 
 | ||||
|        http://www.apache.org/licenses/LICENSE-2.0 | ||||
| 
 | ||||
|    Unless required by applicable law or agreed to in writing, software | ||||
|    distributed under the License is distributed on an "AS IS" BASIS, | ||||
|    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|    See the License for the specific language governing permissions and | ||||
|    limitations under the License. | ||||
| 
 | ||||
| ------------------------------------------------------------------------------ | ||||
| 
 | ||||
| Files: third_party/libyuv/* | ||||
| 
 | ||||
| Copyright 2011 The LibYuv Project Authors. All rights reserved. | ||||
| 
 | ||||
| Redistribution and use in source and binary forms, with or without | ||||
| modification, are permitted provided that the following conditions are | ||||
| met: | ||||
| 
 | ||||
|   * Redistributions of source code must retain the above copyright | ||||
|     notice, this list of conditions and the following disclaimer. | ||||
| 
 | ||||
|   * Redistributions in binary form must reproduce the above copyright | ||||
|     notice, this list of conditions and the following disclaimer in | ||||
|     the documentation and/or other materials provided with the | ||||
|     distribution. | ||||
| 
 | ||||
|   * Neither the name of Google nor the names of its contributors may | ||||
|     be used to endorse or promote products derived from this software | ||||
|     without specific prior written permission. | ||||
| 
 | ||||
| THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | ||||
| "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | ||||
| LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | ||||
| A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | ||||
| HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | ||||
| SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | ||||
| LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | ||||
| DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | ||||
| THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||||
| (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||||
| OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||
							
								
								
									
										29
									
								
								wheels/dependency_licenses/LIBYUV.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								wheels/dependency_licenses/LIBYUV.txt
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,29 @@ | |||
| Copyright 2011 The LibYuv Project Authors. All rights reserved. | ||||
| 
 | ||||
| Redistribution and use in source and binary forms, with or without | ||||
| modification, are permitted provided that the following conditions are | ||||
| met: | ||||
| 
 | ||||
|   * Redistributions of source code must retain the above copyright | ||||
|     notice, this list of conditions and the following disclaimer. | ||||
| 
 | ||||
|   * Redistributions in binary form must reproduce the above copyright | ||||
|     notice, this list of conditions and the following disclaimer in | ||||
|     the documentation and/or other materials provided with the | ||||
|     distribution. | ||||
| 
 | ||||
|   * Neither the name of Google nor the names of its contributors may | ||||
|     be used to endorse or promote products derived from this software | ||||
|     without specific prior written permission. | ||||
| 
 | ||||
| THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | ||||
| "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | ||||
| LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | ||||
| A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | ||||
| HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | ||||
| SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | ||||
| LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | ||||
| DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | ||||
| THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||||
| (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||||
| OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||
							
								
								
									
										107
									
								
								wheels/dependency_licenses/PATENTS.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								wheels/dependency_licenses/PATENTS.txt
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,107 @@ | |||
| Alliance for Open Media Patent License 1.0 | ||||
| 
 | ||||
| 1. License Terms. | ||||
| 
 | ||||
| 1.1. Patent License. Subject to the terms and conditions of this License, each | ||||
|      Licensor, on behalf of itself and successors in interest and assigns, | ||||
|      grants Licensee a non-sublicensable, perpetual, worldwide, non-exclusive, | ||||
|      no-charge, royalty-free, irrevocable (except as expressly stated in this | ||||
|      License) patent license to its Necessary Claims to make, use, sell, offer | ||||
|      for sale, import or distribute any Implementation. | ||||
| 
 | ||||
| 1.2. Conditions. | ||||
| 
 | ||||
| 1.2.1. Availability. As a condition to the grant of rights to Licensee to make, | ||||
|        sell, offer for sale, import or distribute an Implementation under | ||||
|        Section 1.1, Licensee must make its Necessary Claims available under | ||||
|        this License, and must reproduce this License with any Implementation | ||||
|        as follows: | ||||
| 
 | ||||
|        a. For distribution in source code, by including this License in the | ||||
|           root directory of the source code with its Implementation. | ||||
| 
 | ||||
|        b. For distribution in any other form (including binary, object form, | ||||
|           and/or hardware description code (e.g., HDL, RTL, Gate Level Netlist, | ||||
|           GDSII, etc.)), by including this License in the documentation, legal | ||||
|           notices, and/or other written materials provided with the | ||||
|           Implementation. | ||||
| 
 | ||||
| 1.2.2. Additional Conditions. This license is directly from Licensor to | ||||
|        Licensee.  Licensee acknowledges as a condition of benefiting from it | ||||
|        that no rights from Licensor are received from suppliers, distributors, | ||||
|        or otherwise in connection with this License. | ||||
| 
 | ||||
| 1.3. Defensive Termination. If any Licensee, its Affiliates, or its agents | ||||
|      initiates patent litigation or files, maintains, or voluntarily | ||||
|      participates in a lawsuit against another entity or any person asserting | ||||
|      that any Implementation infringes Necessary Claims, any patent licenses | ||||
|      granted under this License directly to the Licensee are immediately | ||||
|      terminated as of the date of the initiation of action unless 1) that suit | ||||
|      was in response to a corresponding suit regarding an Implementation first | ||||
|      brought against an initiating entity, or 2) that suit was brought to | ||||
|      enforce the terms of this License (including intervention in a third-party | ||||
|      action by a Licensee). | ||||
| 
 | ||||
| 1.4. Disclaimers. The Reference Implementation and Specification are provided | ||||
|      "AS IS" and without warranty. The entire risk as to implementing or | ||||
|      otherwise using the Reference Implementation or Specification is assumed | ||||
|      by the implementer and user. Licensor expressly disclaims any warranties | ||||
|      (express, implied, or otherwise), including implied warranties of | ||||
|      merchantability, non-infringement, fitness for a particular purpose, or | ||||
|      title, related to the material. IN NO EVENT WILL LICENSOR BE LIABLE TO | ||||
|      ANY OTHER PARTY FOR LOST PROFITS OR ANY FORM OF INDIRECT, SPECIAL, | ||||
|      INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY CHARACTER FROM ANY CAUSES OF | ||||
|      ACTION OF ANY KIND WITH RESPECT TO THIS LICENSE, WHETHER BASED ON BREACH | ||||
|      OF CONTRACT, TORT (INCLUDING NEGLIGENCE), OR OTHERWISE, AND WHETHER OR | ||||
|      NOT THE OTHER PARTRY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||
| 
 | ||||
| 2. Definitions. | ||||
| 
 | ||||
| 2.1. Affiliate.  “Affiliate” means an entity that directly or indirectly | ||||
|      Controls, is Controlled by, or is under common Control of that party. | ||||
| 
 | ||||
| 2.2. Control. “Control” means direct or indirect control of more than 50% of | ||||
|      the voting power to elect directors of that corporation, or for any other | ||||
|      entity, the power to direct management of such entity. | ||||
| 
 | ||||
| 2.3. Decoder.  "Decoder" means any decoder that conforms fully with all | ||||
|      non-optional portions of the Specification. | ||||
| 
 | ||||
| 2.4. Encoder.  "Encoder" means any encoder that produces a bitstream that can | ||||
|      be decoded by a Decoder only to the extent it produces such a bitstream. | ||||
| 
 | ||||
| 2.5. Final Deliverable.  “Final Deliverable” means the final version of a | ||||
|      deliverable approved by the Alliance for Open Media as a Final | ||||
|      Deliverable. | ||||
| 
 | ||||
| 2.6. Implementation.  "Implementation" means any implementation, including the | ||||
|      Reference Implementation, that is an Encoder and/or a Decoder. An | ||||
|      Implementation also includes components of an Implementation only to the | ||||
|      extent they are used as part of an Implementation. | ||||
| 
 | ||||
| 2.7. License. “License” means this license. | ||||
| 
 | ||||
| 2.8. Licensee. “Licensee” means any person or entity who exercises patent | ||||
|      rights granted under this License. | ||||
| 
 | ||||
| 2.9. Licensor.  "Licensor" means (i) any Licensee that makes, sells, offers | ||||
|      for sale, imports or distributes any Implementation, or (ii) a person | ||||
|      or entity that has a licensing obligation to the Implementation as a | ||||
|      result of its membership and/or participation in the Alliance for Open | ||||
|      Media working group that developed the Specification. | ||||
| 
 | ||||
| 2.10. Necessary Claims.  "Necessary Claims" means all claims of patents or | ||||
|       patent applications, (a) that currently or at any time in the future, | ||||
|       are owned or controlled by the Licensor, and (b) (i) would be an | ||||
|       Essential Claim as defined by the W3C Policy as of February 5, 2004 | ||||
|       (https://www.w3.org/Consortium/Patent-Policy-20040205/#def-essential) | ||||
|       as if the Specification was a W3C Recommendation; or (ii) are infringed | ||||
|       by the Reference Implementation. | ||||
| 
 | ||||
| 2.11. Reference Implementation. “Reference Implementation” means an Encoder | ||||
|       and/or Decoder released by the Alliance for Open Media as a Final | ||||
|       Deliverable. | ||||
| 
 | ||||
| 2.12. Specification. “Specification” means the specification designated by | ||||
|       the Alliance for Open Media as a Final Deliverable for which this | ||||
|       License was issued. | ||||
							
								
								
									
										25
									
								
								wheels/dependency_licenses/RAV1E.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								wheels/dependency_licenses/RAV1E.txt
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,25 @@ | |||
| BSD 2-Clause License | ||||
| 
 | ||||
| Copyright (c) 2017-2021, the rav1e contributors | ||||
| All rights reserved. | ||||
| 
 | ||||
| Redistribution and use in source and binary forms, with or without | ||||
| modification, are permitted provided that the following conditions are met: | ||||
| 
 | ||||
| * Redistributions of source code must retain the above copyright notice, this | ||||
|   list of conditions and the following disclaimer. | ||||
| 
 | ||||
| * Redistributions in binary form must reproduce the above copyright notice, | ||||
|   this list of conditions and the following disclaimer in the documentation | ||||
|   and/or other materials provided with the distribution. | ||||
| 
 | ||||
| THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||||
| AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||||
| IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||||
| DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||||
| FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||||
| DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||||
| SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||||
| CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||||
| OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||||
| OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||
							
								
								
									
										26
									
								
								wheels/dependency_licenses/SVT-AV1.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								wheels/dependency_licenses/SVT-AV1.txt
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,26 @@ | |||
| Copyright (c) 2019, Alliance for Open Media. All rights reserved. | ||||
| 
 | ||||
| Redistribution and use in source and binary forms, with or without | ||||
| modification, are permitted provided that the following conditions | ||||
| are met: | ||||
| 
 | ||||
| 1. Redistributions of source code must retain the above copyright | ||||
|    notice, this list of conditions and the following disclaimer. | ||||
| 
 | ||||
| 2. Redistributions in binary form must reproduce the above copyright | ||||
|    notice, this list of conditions and the following disclaimer in | ||||
|    the documentation and/or other materials provided with the | ||||
|    distribution. | ||||
| 
 | ||||
| THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | ||||
| "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | ||||
| LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS | ||||
| FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE | ||||
| COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, | ||||
| INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, | ||||
| BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||||
| LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||||
| CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | ||||
| LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN | ||||
| ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | ||||
| POSSIBILITY OF SUCH DAMAGE. | ||||
							
								
								
									
										10
									
								
								winbuild/Findrav1e.cmake
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								winbuild/Findrav1e.cmake
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,10 @@ | |||
| file(TO_CMAKE_PATH "${AVIF_RAV1E_ROOT}" RAV1E_ROOT_PATH) | ||||
| add_library(rav1e::rav1e STATIC IMPORTED GLOBAL) | ||||
| set_target_properties( | ||||
|   rav1e::rav1e | ||||
|   PROPERTIES IMPORTED_LOCATION "${RAV1E_ROOT_PATH}/lib/rav1e.lib" | ||||
|              AVIF_LOCAL ON | ||||
|              INTERFACE_INCLUDE_DIRECTORIES "${RAV1E_ROOT_PATH}/inc/rav1e" | ||||
|              IMPORTED_SONAME rav1e) | ||||
| target_link_libraries(rav1e::rav1e INTERFACE ntdll.lib userenv.lib ws2_32.lib | ||||
|                                              bcrypt.lib) | ||||
|  | @ -59,6 +59,7 @@ Run ``build_prepare.py`` to configure the build:: | |||
|                             build architecture (default: same as host Python) | ||||
|       --nmake               build dependencies using NMake instead of Ninja | ||||
|       --no-imagequant       skip GPL-licensed optional dependency libimagequant | ||||
|       --no-avif             skip optional dependency libavif | ||||
|       --no-fribidi, --no-raqm | ||||
|                             skip LGPL-licensed optional dependency FriBiDi | ||||
| 
 | ||||
|  |  | |||
|  | @ -121,6 +121,9 @@ V = { | |||
|     "TIFF": "4.6.0", | ||||
|     "XZ": "5.6.3", | ||||
|     "ZLIB": "1.3.1", | ||||
|     "MESON": "1.5.1", | ||||
|     "LIBAVIF": "1.1.1", | ||||
|     "RAV1E": "0.7.1", | ||||
| } | ||||
| V["LIBPNG_DOTLESS"] = V["LIBPNG"].replace(".", "") | ||||
| V["LIBPNG_XY"] = "".join(V["LIBPNG"].split(".")[:2]) | ||||
|  | @ -397,6 +400,57 @@ DEPS: dict[str, dict[str, Any]] = { | |||
|         ], | ||||
|         "bins": [r"*.dll"], | ||||
|     }, | ||||
|     "rav1e": { | ||||
|         "url": ( | ||||
|             f"https://github.com/xiph/rav1e/releases/download/v{V['RAV1E']}/" | ||||
|             f"rav1e-{V['RAV1E']}-windows-msvc-generic.zip" | ||||
|         ), | ||||
|         "filename": f"rav1e-{V['RAV1E']}-windows-msvc-generic.zip", | ||||
|         "dir": "rav1e-windows-msvc-sdk", | ||||
|         "license": "LICENSE", | ||||
|         "build": [ | ||||
|             cmd_xcopy("include", "{inc_dir}"), | ||||
|         ], | ||||
|         "bins": [r"bin\*.dll"], | ||||
|         "libs": [r"lib\*.*"], | ||||
|     }, | ||||
|     "libavif": { | ||||
|         "url": f"https://github.com/AOMediaCodec/libavif/archive/v{V['LIBAVIF']}.zip", | ||||
|         "filename": f"libavif-{V['LIBAVIF']}.zip", | ||||
|         "dir": f"libavif-{V['LIBAVIF']}", | ||||
|         "license": "LICENSE", | ||||
|         "build": [ | ||||
|             cmd_mkdir("build.pillow"), | ||||
|             cmd_cd("build.pillow"), | ||||
|             " ".join( | ||||
|                 [ | ||||
|                     "{cmake}", | ||||
|                     "-DCMAKE_BUILD_TYPE=Release", | ||||
|                     "-DCMAKE_VERBOSE_MAKEFILE=ON", | ||||
|                     "-DCMAKE_RULE_MESSAGES:BOOL=OFF", | ||||
|                     "-DCMAKE_C_COMPILER=cl.exe", | ||||
|                     "-DCMAKE_CXX_COMPILER=cl.exe", | ||||
|                     "-DCMAKE_C_FLAGS=-nologo", | ||||
|                     "-DCMAKE_CXX_FLAGS=-nologo", | ||||
|                     "-DBUILD_SHARED_LIBS=OFF", | ||||
|                     "-DAVIF_CODEC_AOM=LOCAL", | ||||
|                     "-DAVIF_LIBYUV=LOCAL", | ||||
|                     "-DAVIF_LIBSHARPYUV=LOCAL", | ||||
|                     "-DAVIF_CODEC_RAV1E=SYSTEM", | ||||
|                     "-DAVIF_RAV1E_ROOT={build_dir}", | ||||
|                     "-DCMAKE_MODULE_PATH={winbuild_dir_cmake}", | ||||
|                     "-DAVIF_CODEC_DAV1D=LOCAL", | ||||
|                     "-DAVIF_CODEC_SVT=LOCAL", | ||||
|                     '-G "Ninja"', | ||||
|                     "..", | ||||
|                 ] | ||||
|             ), | ||||
|             "ninja -v", | ||||
|             cmd_cd(".."), | ||||
|             cmd_xcopy("include", "{inc_dir}"), | ||||
|         ], | ||||
|         "libs": [r"build.pillow\avif.lib"], | ||||
|     }, | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
|  | @ -620,12 +674,15 @@ def build_dep(name: str, prefs: dict[str, str], verbose: bool) -> str: | |||
| def build_dep_all(disabled: list[str], prefs: dict[str, str], verbose: bool) -> None: | ||||
|     lines = [r'call "{build_dir}\build_env.cmd"'] | ||||
|     gha_groups = "GITHUB_ACTIONS" in os.environ | ||||
|     scripts = ["install_meson.cmd"] | ||||
|     for dep_name in DEPS: | ||||
|         print() | ||||
|         if dep_name in disabled: | ||||
|             print(f"Skipping disabled dependency {dep_name}") | ||||
|             continue | ||||
|         script = build_dep(dep_name, prefs, verbose) | ||||
|         scripts.append(build_dep(dep_name, prefs, verbose)) | ||||
| 
 | ||||
|     for script in scripts: | ||||
|         if gha_groups: | ||||
|             lines.append(f"@echo ::group::Running {script}") | ||||
|         lines.append(rf'cmd.exe /c "{{build_dir}}\{script}"') | ||||
|  | @ -699,6 +756,11 @@ def main() -> None: | |||
|         action="store_true", | ||||
|         help="skip LGPL-licensed optional dependency FriBiDi", | ||||
|     ) | ||||
|     parser.add_argument( | ||||
|         "--no-avif", | ||||
|         action="store_true", | ||||
|         help="skip optional dependency libavif", | ||||
|     ) | ||||
|     args = parser.parse_args() | ||||
| 
 | ||||
|     arch_prefs = ARCHITECTURES[args.architecture] | ||||
|  | @ -739,12 +801,15 @@ def main() -> None: | |||
|         disabled += ["libimagequant"] | ||||
|     if args.no_fribidi: | ||||
|         disabled += ["fribidi"] | ||||
|     if args.no_avif or args.architecture != "AMD64": | ||||
|         disabled += ["rav1e", "libavif"] | ||||
| 
 | ||||
|     prefs = { | ||||
|         "architecture": args.architecture, | ||||
|         **arch_prefs, | ||||
|         # Pillow paths | ||||
|         "winbuild_dir": winbuild_dir, | ||||
|         "winbuild_dir_cmake": winbuild_dir.replace("\\", "/"), | ||||
|         # Build paths | ||||
|         "bin_dir": bin_dir, | ||||
|         "build_dir": args.build_dir, | ||||
|  | @ -766,6 +831,18 @@ def main() -> None: | |||
|     print() | ||||
| 
 | ||||
|     write_script(".gitignore", ["*"], prefs, args.verbose) | ||||
|     write_script( | ||||
|         "install_meson.cmd", | ||||
|         [ | ||||
|             r'call "{build_dir}\build_env.cmd"', | ||||
|             "@echo " + ("=" * 70), | ||||
|             f"@echo ==== {'Building meson':<60} ====", | ||||
|             "@echo " + ("=" * 70), | ||||
|             f"python -mpip install meson=={V['MESON']}", | ||||
|         ], | ||||
|         prefs, | ||||
|         args.verbose, | ||||
|     ) | ||||
|     build_env(prefs, args.verbose) | ||||
|     build_dep_all(disabled, prefs, args.verbose) | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	Block a user