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 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 5 additions & 1 deletion .ci/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ if [[ $(uname) != CYGWIN* ]]; then
sudo apt-get -qq install libfreetype6-dev liblcms2-dev python3-tk\
ghostscript libffi-dev 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
Expand Down Expand Up @@ -69,6 +70,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 @@ -10,7 +10,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 @@ -27,5 +31,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 @@ -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 \
Expand Down
14 changes: 14 additions & 0 deletions .github/workflows/test-windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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.3.1 --no-progress
echo "C:\Program Files\gs\gs10.00.0\bin" >> $env:GITHUB_PATH

Expand Down Expand Up @@ -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'
Expand Down
82 changes: 80 additions & 2 deletions .github/workflows/wheels-dependencies.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ if [ -z "$IS_MACOS" ]; then
export MB_ML_LIBC=${AUDITWHEEL_POLICY::9}
export MB_ML_VER=${AUDITWHEEL_POLICY:9}
fi
export PLAT=$CIBW_ARCHS
export PLAT="${CIBW_ARCHS:-$AUDITWHEEL_ARCH}"
source wheels/multibuild/common_utils.sh
source wheels/multibuild/library_builders.sh
if [ -z "$IS_MACOS" ]; then
Expand Down Expand Up @@ -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

if [[ -n "$IS_MACOS" ]] && [[ "$CIBW_ARCHS" == "x86_64" ]]; then
function build_openjpeg {
Expand All @@ -60,6 +62,71 @@ function build_brotli {
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
Expand All @@ -70,6 +137,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
Expand Down Expand Up @@ -132,15 +206,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

# clear bash path cache for curl
hash -d curl
fi

wrap_wheel_builder build
Expand Down
44 changes: 44 additions & 0 deletions Tests/check_avif_leaks.py
Original file line number Diff line number Diff line change
@@ -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
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():
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()
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/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