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

Skip building libavif on 32-bit Windows #16

Merged
merged 7 commits into from
Jan 21, 2025
Merged
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
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
Loading