Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add AVIF plugin (decoder + encoder using libavif) #5201

Open
wants to merge 31 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
3878b58
Add AVIF plugin (using libavif)
fdintino Jan 3, 2021
e2add24
Added type hints (#2)
radarhere Oct 16, 2024
e5494a2
Fix PLAT envvar in cibuildwheel container
fdintino Oct 16, 2024
8b8bbba
Update Tests/check_avif_leaks.py
fdintino Oct 18, 2024
58ef692
Simplified code (#3)
radarhere Oct 19, 2024
d6a0a15
Merge branch 'main' into libavif-plugin
radarhere Nov 8, 2024
50b993a
Set default max threads in Python (#4)
radarhere Nov 11, 2024
671e3c8
Removed unused upsampling setting (#5)
radarhere Nov 12, 2024
658cdf3
Merge branch 'main' into libavif-plugin
radarhere Nov 18, 2024
7225cb9
Merge branch 'main' into libavif-plugin
radarhere Nov 24, 2024
3730bf2
Merge branch 'main' into libavif-plugin
radarhere Nov 30, 2024
c40bcbf
Improved error handling
radarhere Dec 2, 2024
d76ae2f
Do not ignore SyntaxError when saving EXIF data (#8)
radarhere Dec 7, 2024
9ad8311
Allow libavif to install rav1e, except on manylinux2014 and aarch64 (#7)
radarhere Dec 7, 2024
de4c6c1
Removed ld64 flag (#6)
radarhere Dec 7, 2024
524d802
fix: set exif orientation from irot/imir when decoding AVIF
fdintino Dec 9, 2024
7b73d77
Merge branch 'main' into libavif-plugin
radarhere Dec 13, 2024
a56acd8
Removed unittest mock (#10)
radarhere Dec 13, 2024
f5dc957
Use cmds_cmake (#9)
radarhere Dec 13, 2024
8d77678
chore(docs): update quality and speed with correct defaults
fdintino Dec 13, 2024
4eaa6b7
Merge branch 'main' into libavif-plugin
radarhere Dec 14, 2024
bdb24f9
Removed `_avif.HAVE_AVIF` and `_avif.VERSION` (#11)
radarhere Dec 15, 2024
ddc8e7e
Use "rav1e" if available as default ("auto") avif encoder
fdintino Dec 15, 2024
b585f9e
Simplified EXIF code (#12)
radarhere Dec 15, 2024
da2e18d
Revert "Use "rav1e" if available as default ("auto") avif encoder"
fdintino Dec 17, 2024
9b6e575
Merge branch 'main' into libavif-plugin
radarhere Dec 18, 2024
3a9a3ab
Reduced epsilons (#13)
radarhere Dec 24, 2024
9328932
Removed avifEncOptions (#14)
radarhere Jan 3, 2025
be02830
Merge branch 'main' into libavif-plugin
radarhere Jan 3, 2025
4135664
Merge branch 'main' into libavif-plugin
radarhere Jan 4, 2025
29c158d
Merge branch 'main' into libavif-plugin
radarhere Jan 8, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .ci/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ 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 nasm
fi

python3 -m pip install --upgrade pip
Expand Down Expand Up @@ -62,6 +62,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
Expand Down
9 changes: 8 additions & 1 deletion .github/workflows/macos-install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 change: 1 addition & 0 deletions .github/workflows/test-mingw.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,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 \
Expand Down
6 changes: 5 additions & 1 deletion .github/workflows/test-windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ jobs:
matrix:
python-version: ["pypy3.10", "3.9", "3.10", "3.11", "3.12", "3.13"]

timeout-minutes: 30
timeout-minutes: 45

name: Python ${{ matrix.python-version }}

Expand Down Expand Up @@ -139,6 +139,10 @@ jobs:
if: steps.build-cache.outputs.cache-hit != 'true'
run: "& winbuild\\build\\build_dep_libpng.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'
Expand Down
71 changes: 70 additions & 1 deletion .github/workflows/wheels-dependencies.sh
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ else
MB_ML_LIBC=${AUDITWHEEL_POLICY::9}
MB_ML_VER=${AUDITWHEEL_POLICY:9}
fi
PLAT=$CIBW_ARCHS
PLAT="${CIBW_ARCHS:-$AUDITWHEEL_ARCH}"

# Define custom utilities
source wheels/multibuild/common_utils.sh
Expand Down Expand Up @@ -55,6 +55,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_pkg_config {
if [ -e pkg-config-stamp ]; then return; fi
Expand Down Expand Up @@ -101,13 +103,80 @@ function build_harfbuzz {
touch harfbuzz-stamp
}

function build_libavif {
if [ -e libavif-stamp ]; then return; fi

if [[ "$MB_ML_VER" == 2014 ]] || [[ "$PLAT" == "aarch64" ]]; then
# Once Amazon 2 is EOL on 30 June 2025, manylinux2014 will no longer be needed
# Once GitHub Actions supports aarch64 without emulation, this will no longer needed as building will be faster
if [[ "$PLAT" == "aarch64" ]]; then
suffix="aarch64"
else
suffix="generic"
fi

curl -sLo - \
https://github.com/xiph/rav1e/releases/download/v$RAV1E_VERSION/librav1e-$RAV1E_VERSION-linux-$suffix.tar.gz \
| tar -C $BUILD_PREFIX -zxf -

# 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

rav1e=SYSTEM
else
curl https://sh.rustup.rs -sSf | sh -s -- -y
. "$HOME/.cargo/env"

if [ -z "$IS_ALPINE" ] && [ -z "$IS_MACOS" ]; then
yum install -y perl
fi

rav1e=LOCAL
fi

python3 -m pip install meson ninja

if [[ "$PLAT" == "x86_64" ]]; then
build_simple nasm 2.16.03 https://www.nasm.us/pub/nasm/releasebuilds/2.16.03
fi

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_LIBDIR=$BUILD_PREFIX/lib \
-DCMAKE_BUILD_TYPE=Release \
-DBUILD_SHARED_LIBS=OFF \
-DAVIF_LIBSHARPYUV=LOCAL \
-DAVIF_LIBYUV=LOCAL \
-DAVIF_CODEC_RAV1E=$rav1e \
-DAVIF_CODEC_AOM=LOCAL \
-DAVIF_CODEC_DAV1D=LOCAL \
-DAVIF_CODEC_SVT=LOCAL \
-DENABLE_NASM=ON \
-DCMAKE_MODULE_PATH=/tmp/cmake/Modules \
. \
&& make install)
touch libavif-stamp
}

function build {
build_xz
if [ -z "$IS_ALPINE" ] && [ -z "$IS_MACOS" ]; then
yum remove -y zlib-devel
fi
build_zlib_ng

build_libavif

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
Expand Down
43 changes: 43 additions & 0 deletions Tests/check_avif_leaks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
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() -> None:
from resource import RLIMIT_AS, RLIMIT_STACK, setrlimit
Copy link

@mara004 mara004 Dec 5, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are you importing this inside a function, and that twice (l. 22, l. 32)? I'd rather move the import to the top...

Copy link
Author

@fdintino fdintino Dec 6, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good question. This was copied from the leak test for jpeg2000 here. I suspect that was done because those imports will fail on windows (judging by the skip pytest mark above it). But those could be handled at the top of the file with an except ImportError


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() -> None:
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):
test_output = BytesIO()
with Image.open(test_file) as im:
im.save(test_output, "AVIF")
test_output.seek(0)
test_output.read()
9 changes: 8 additions & 1 deletion Tests/check_wheel.py
Original file line number Diff line number Diff line change
@@ -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:
Expand All @@ -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


Expand Down
Binary file added Tests/images/avif/chimera-missing-pixi.avif
Binary file not shown.
Binary file added Tests/images/avif/exif.avif
Binary file not shown.
Binary file added Tests/images/avif/hopper.avif
Binary file not shown.
Binary file added Tests/images/avif/hopper_avif_write.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Tests/images/avif/icc_profile.avif
Binary file not shown.
Binary file added Tests/images/avif/icc_profile_none.avif
Binary file not shown.
Binary file added Tests/images/avif/rgba10.heif
Binary file not shown.
Binary file added Tests/images/avif/rot0mir0.avif
Binary file not shown.
Binary file added Tests/images/avif/rot0mir1.avif
Binary file not shown.
Binary file added Tests/images/avif/rot1mir0.avif
Binary file not shown.
Binary file added Tests/images/avif/rot1mir1.avif
Binary file not shown.
Binary file added Tests/images/avif/rot2mir0.avif
Binary file not shown.
Binary file added Tests/images/avif/rot2mir1.avif
Binary file not shown.
Binary file added Tests/images/avif/rot3mir0.avif
Binary file not shown.
Binary file added Tests/images/avif/rot3mir1.avif
Binary file not shown.
Binary file added Tests/images/avif/star.avifs
Binary file not shown.
Binary file added Tests/images/avif/star.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Tests/images/avif/star.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Tests/images/avif/star180.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Tests/images/avif/star270.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Tests/images/avif/star90.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Tests/images/avif/transparency.avif
Binary file not shown.
Binary file added Tests/images/avif/xmp_tags_orientation.avif
Binary file not shown.
Loading
Loading