Skip to content

Commit

Permalink
Skip building libavif on 32-bit Windows (#16)
Browse files Browse the repository at this point in the history
* Corrected comment

* Reduced difference

* Generate rotated images

* Build rav1e

* Skip building libavif on 32-bit

* Fixed building libavif on oss-fuzz

* Removed unnecessary converts

---------

Co-authored-by: Andrew Murray <[email protected]>
  • Loading branch information
radarhere and radarhere authored Jan 21, 2025
1 parent ce6bf21 commit 4b29af4
Show file tree
Hide file tree
Showing 6 changed files with 48 additions and 80 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test-windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ jobs:
run: "& winbuild\\build\\build_dep_libpng.cmd"

- name: Build dependencies / libavif
if: steps.build-cache.outputs.cache-hit != 'true'
if: steps.build-cache.outputs.cache-hit != 'true' && matrix.architecture == 'x64'
run: "& winbuild\\build\\build_dep_libavif.cmd"

# for FreeType WOFF2 font support
Expand Down
53 changes: 12 additions & 41 deletions .github/workflows/wheels-dependencies.sh
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ 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,50 +100,22 @@ function build_harfbuzz {
function build_libavif {
if [ -e libavif-stamp ]; then return; fi

if [[ "$PLAT" == "aarch64" ]]; then
# 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
if [[ "$MB_ML_VER" == 2014 ]]; then
yum install -y perl-IPC-Cmd
fi
fi

rav1e=LOCAL
fi

python3 -m pip install meson ninja

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

# For rav1e
curl https://sh.rustup.rs -sSf | sh -s -- -y
. "$HOME/.cargo/env"
if [ -z "$IS_ALPINE" ] && [ -z "$SANITIZER" ] && [ -z "$IS_MACOS" ]; then
yum install -y perl
if [[ "$MB_ML_VER" == 2014 ]]; then
yum install -y perl-IPC-Cmd
fi
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 \
Expand All @@ -154,7 +125,7 @@ EOF
-DBUILD_SHARED_LIBS=OFF \
-DAVIF_LIBSHARPYUV=LOCAL \
-DAVIF_LIBYUV=LOCAL \
-DAVIF_CODEC_RAV1E=$rav1e \
-DAVIF_CODEC_RAV1E=LOCAL \
-DAVIF_CODEC_AOM=LOCAL \
-DAVIF_CODEC_DAV1D=LOCAL \
-DAVIF_CODEC_SVT=LOCAL \
Expand Down
Binary file removed Tests/images/avif/star180.png
Binary file not shown.
Binary file removed Tests/images/avif/star270.png
Binary file not shown.
Binary file removed Tests/images/avif/star90.png
Binary file not shown.
73 changes: 35 additions & 38 deletions Tests/test_file_avif.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ def test_background_from_gif(self, tmp_path: Path) -> None:
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(3)])
assert difference < 5
assert difference <= 3

def test_save_single_frame(self, tmp_path: Path) -> None:
temp_file = str(tmp_path / "temp.avif")
Expand Down Expand Up @@ -255,10 +255,10 @@ def test_save_transparent(self, tmp_path: Path) -> None:

def test_save_icc_profile(self) -> None:
with Image.open("Tests/images/avif/icc_profile_none.avif") as im:
assert im.info.get("icc_profile") is None
assert "icc_profile" not in im.info

with Image.open("Tests/images/avif/icc_profile.avif") as with_icc:
expected_icc = with_icc.info.get("icc_profile")
expected_icc = with_icc.info["icc_profile"]
assert expected_icc is not None

im = roundtrip(im, icc_profile=expected_icc)
Expand All @@ -278,7 +278,7 @@ def test_roundtrip_icc_profile(self) -> None:

def test_roundtrip_no_icc_profile(self) -> None:
with Image.open("Tests/images/avif/icc_profile_none.avif") as im:
assert im.info.get("icc_profile") is None
assert "icc_profile" not in im.info

im = roundtrip(im)
assert "icc_profile" not in im.info
Expand Down Expand Up @@ -470,14 +470,14 @@ def test_encoder_advanced_codec_options(

@skip_unless_avif_encoder("aom")
@skip_unless_feature("avif")
@pytest.mark.parametrize("val", [{"foo": "bar"}, 1234])
@pytest.mark.parametrize("advanced", [{"foo": "bar"}, 1234])
def test_encoder_advanced_codec_options_invalid(
self, tmp_path: Path, val: dict[str, str] | int
self, tmp_path: Path, advanced: dict[str, str] | int
) -> None:
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)
im.save(test_file, codec="aom", advanced=advanced)

@skip_unless_avif_decoder("aom")
@skip_unless_feature("avif")
Expand Down Expand Up @@ -545,48 +545,43 @@ def test_decoder_codec_available_cannot_decode(self) -> None:
def test_decoder_codec_available_invalid(self) -> None:
assert _avif.decoder_codec_available("foo") is False

def test_p_mode_transparency(self) -> None:
def test_p_mode_transparency(self, tmp_path: Path) -> None:
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)
out_png = str(tmp_path / "temp.png")
im.save(out_png, transparency=0)
with Image.open(out_png) as im_png:
out_avif = str(tmp_path / "temp.avif")
im_png.save(out_avif, quality=100)

with Image.open(buf_out) as expected:
assert_image_similar(im_png.convert("RGBA"), expected, 0.17)
with Image.open(out_avif) as expected:
assert_image_similar(im_png.convert("RGBA"), expected, 0.17)

def test_decoder_strict_flags(self) -> None:
# 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) -> None:
im = hopper("RGB")
buf = BytesIO()
im.save(buf, format="AVIF", codec="aom", speed=1)
def test_aom_optimizations(self, tmp_path: Path) -> None:
test_file = str(tmp_path / "temp.avif")
hopper().save(test_file, codec="aom", speed=1)

@skip_unless_avif_encoder("svt")
def test_svt_optimizations(self) -> None:
im = hopper("RGB")
buf = BytesIO()
im.save(buf, format="AVIF", codec="svt", speed=1)
def test_svt_optimizations(self, tmp_path: Path) -> None:
test_file = str(tmp_path / "temp.avif")
hopper().save(test_file, codec="svt", speed=1)


@skip_unless_feature("avif")
class TestAvifAnimation:
@contextmanager
def star_frames(self) -> Generator[list[ImageFile.ImageFile], None, None]:
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]
with Image.open("Tests/images/avif/star.png") as f:
yield [f, f.rotate(90), f.rotate(180), f.rotate(270)]

def test_n_frames(self) -> None:
"""
Expand All @@ -602,10 +597,10 @@ def test_n_frames(self) -> None:
assert im.n_frames == 5
assert im.is_animated

def test_write_animation_L(self, tmp_path: Path) -> None:
def test_write_animation_P(self, tmp_path: Path) -> None:
"""
Convert an animated GIF to animated AVIF, then compare the frame
count, and first and last frames to ensure they're visually similar.
count, and first and second-to-last frames to ensure they're visually similar.
"""

with Image.open("Tests/images/avif/star.gif") as orig:
Expand All @@ -616,15 +611,17 @@ def test_write_animation_L(self, tmp_path: Path) -> None:
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
assert_image_similar(im.convert("RGB"), orig.convert("RGB"), 2.25)
# Compare first frame in P mode to frame from original GIF
assert_image_similar(im, orig.convert("RGBA"), 2)

# Compare second-to-last frame in RGBA mode to frame from original GIF
orig.seek(orig.n_frames - 2)
im.seek(im.n_frames - 2)
assert_image_similar(im.convert("RGB"), orig.convert("RGB"), 2.54)
assert_image_similar(im, orig, 2.54)

def test_write_animation_RGB(self, tmp_path: Path) -> None:
def test_write_animation_RGBA(self, tmp_path: Path) -> None:
"""
Write an animated AVIF from RGB frames, and ensure the frames
Write an animated AVIF from RGBA frames, and ensure the frames
are visually similar to the originals.
"""

Expand All @@ -633,11 +630,11 @@ def check(temp_file: str) -> None:
assert im.n_frames == 4

# Compare first frame to original
assert_image_similar(im, frame1.convert("RGBA"), 2.7)
assert_image_similar(im, frame1, 2.7)

# Compare second frame to original
im.seek(1)
assert_image_similar(im, frame2.convert("RGBA"), 4.1)
assert_image_similar(im, frame2, 4.1)

with self.star_frames() as frames:
frame1 = frames[0]
Expand All @@ -646,7 +643,7 @@ def check(temp_file: str) -> None:
frames[0].copy().save(temp_file1, save_all=True, append_images=frames[1:])
check(temp_file1)

# Tests appending using a generator
# Test appending using a generator
def imGenerator(
ims: list[ImageFile.ImageFile],
) -> Generator[ImageFile.ImageFile, None, None]:
Expand Down

0 comments on commit 4b29af4

Please sign in to comment.