mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-02-05 22:20:54 +03:00
Skip building libavif on 32-bit Windows (#16)
* 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 <radarhere@users.noreply.github.com>
This commit is contained in:
parent
ce6bf21f15
commit
4b29af49fd
2
.github/workflows/test-windows.yml
vendored
2
.github/workflows/test-windows.yml
vendored
|
@ -146,7 +146,7 @@ jobs:
|
||||||
run: "& winbuild\\build\\build_dep_libpng.cmd"
|
run: "& winbuild\\build\\build_dep_libpng.cmd"
|
||||||
|
|
||||||
- name: Build dependencies / libavif
|
- 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"
|
run: "& winbuild\\build\\build_dep_libavif.cmd"
|
||||||
|
|
||||||
# for FreeType WOFF2 font support
|
# for FreeType WOFF2 font support
|
||||||
|
|
53
.github/workflows/wheels-dependencies.sh
vendored
53
.github/workflows/wheels-dependencies.sh
vendored
|
@ -51,7 +51,6 @@ BZIP2_VERSION=1.0.8
|
||||||
LIBXCB_VERSION=1.17.0
|
LIBXCB_VERSION=1.17.0
|
||||||
BROTLI_VERSION=1.1.0
|
BROTLI_VERSION=1.1.0
|
||||||
LIBAVIF_VERSION=1.1.1
|
LIBAVIF_VERSION=1.1.1
|
||||||
RAV1E_VERSION=0.7.1
|
|
||||||
|
|
||||||
function build_pkg_config {
|
function build_pkg_config {
|
||||||
if [ -e pkg-config-stamp ]; then return; fi
|
if [ -e pkg-config-stamp ]; then return; fi
|
||||||
|
@ -101,50 +100,22 @@ function build_harfbuzz {
|
||||||
function build_libavif {
|
function build_libavif {
|
||||||
if [ -e libavif-stamp ]; then return; fi
|
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
|
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
|
build_simple nasm 2.16.03 https://www.nasm.us/pub/nasm/releasebuilds/2.16.03
|
||||||
fi
|
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)
|
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 \
|
(cd $out_dir \
|
||||||
&& cmake \
|
&& cmake \
|
||||||
|
@ -154,7 +125,7 @@ EOF
|
||||||
-DBUILD_SHARED_LIBS=OFF \
|
-DBUILD_SHARED_LIBS=OFF \
|
||||||
-DAVIF_LIBSHARPYUV=LOCAL \
|
-DAVIF_LIBSHARPYUV=LOCAL \
|
||||||
-DAVIF_LIBYUV=LOCAL \
|
-DAVIF_LIBYUV=LOCAL \
|
||||||
-DAVIF_CODEC_RAV1E=$rav1e \
|
-DAVIF_CODEC_RAV1E=LOCAL \
|
||||||
-DAVIF_CODEC_AOM=LOCAL \
|
-DAVIF_CODEC_AOM=LOCAL \
|
||||||
-DAVIF_CODEC_DAV1D=LOCAL \
|
-DAVIF_CODEC_DAV1D=LOCAL \
|
||||||
-DAVIF_CODEC_SVT=LOCAL \
|
-DAVIF_CODEC_SVT=LOCAL \
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 9.0 KiB |
Binary file not shown.
Before Width: | Height: | Size: 9.2 KiB |
Binary file not shown.
Before Width: | Height: | Size: 9.1 KiB |
|
@ -218,7 +218,7 @@ class TestFileAvif:
|
||||||
with Image.open(out_gif) as reread:
|
with Image.open(out_gif) as reread:
|
||||||
reread_value = reread.convert("RGB").getpixel((1, 1))
|
reread_value = reread.convert("RGB").getpixel((1, 1))
|
||||||
difference = sum([abs(original_value[i] - reread_value[i]) for i in range(3)])
|
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:
|
def test_save_single_frame(self, tmp_path: Path) -> None:
|
||||||
temp_file = str(tmp_path / "temp.avif")
|
temp_file = str(tmp_path / "temp.avif")
|
||||||
|
@ -255,10 +255,10 @@ class TestFileAvif:
|
||||||
|
|
||||||
def test_save_icc_profile(self) -> None:
|
def test_save_icc_profile(self) -> None:
|
||||||
with Image.open("Tests/images/avif/icc_profile_none.avif") as im:
|
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:
|
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
|
assert expected_icc is not None
|
||||||
|
|
||||||
im = roundtrip(im, icc_profile=expected_icc)
|
im = roundtrip(im, icc_profile=expected_icc)
|
||||||
|
@ -278,7 +278,7 @@ class TestFileAvif:
|
||||||
|
|
||||||
def test_roundtrip_no_icc_profile(self) -> None:
|
def test_roundtrip_no_icc_profile(self) -> None:
|
||||||
with Image.open("Tests/images/avif/icc_profile_none.avif") as im:
|
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)
|
im = roundtrip(im)
|
||||||
assert "icc_profile" not in im.info
|
assert "icc_profile" not in im.info
|
||||||
|
@ -470,14 +470,14 @@ class TestFileAvif:
|
||||||
|
|
||||||
@skip_unless_avif_encoder("aom")
|
@skip_unless_avif_encoder("aom")
|
||||||
@skip_unless_feature("avif")
|
@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(
|
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:
|
) -> None:
|
||||||
with Image.open(TEST_AVIF_FILE) as im:
|
with Image.open(TEST_AVIF_FILE) as im:
|
||||||
test_file = str(tmp_path / "temp.avif")
|
test_file = str(tmp_path / "temp.avif")
|
||||||
with pytest.raises(ValueError):
|
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_avif_decoder("aom")
|
||||||
@skip_unless_feature("avif")
|
@skip_unless_feature("avif")
|
||||||
|
@ -545,20 +545,20 @@ class TestFileAvif:
|
||||||
def test_decoder_codec_available_invalid(self) -> None:
|
def test_decoder_codec_available_invalid(self) -> None:
|
||||||
assert _avif.decoder_codec_available("foo") is False
|
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))
|
im = Image.new("P", size=(64, 64))
|
||||||
draw = ImageDraw.Draw(im)
|
draw = ImageDraw.Draw(im)
|
||||||
draw.rectangle(xy=[(0, 0), (32, 32)], fill=255)
|
draw.rectangle(xy=[(0, 0), (32, 32)], fill=255)
|
||||||
draw.rectangle(xy=[(32, 32), (64, 64)], fill=255)
|
draw.rectangle(xy=[(32, 32), (64, 64)], fill=255)
|
||||||
|
|
||||||
buf_png = BytesIO()
|
out_png = str(tmp_path / "temp.png")
|
||||||
im.save(buf_png, format="PNG", transparency=0)
|
im.save(out_png, transparency=0)
|
||||||
im_png = Image.open(buf_png)
|
with Image.open(out_png) as im_png:
|
||||||
buf_out = BytesIO()
|
out_avif = str(tmp_path / "temp.avif")
|
||||||
im_png.save(buf_out, format="AVIF", quality=100)
|
im_png.save(out_avif, quality=100)
|
||||||
|
|
||||||
with Image.open(buf_out) as expected:
|
with Image.open(out_avif) as expected:
|
||||||
assert_image_similar(im_png.convert("RGBA"), expected, 0.17)
|
assert_image_similar(im_png.convert("RGBA"), expected, 0.17)
|
||||||
|
|
||||||
def test_decoder_strict_flags(self) -> None:
|
def test_decoder_strict_flags(self) -> None:
|
||||||
# This would fail if full avif strictFlags were enabled
|
# This would fail if full avif strictFlags were enabled
|
||||||
|
@ -566,27 +566,22 @@ class TestFileAvif:
|
||||||
assert im.size == (480, 270)
|
assert im.size == (480, 270)
|
||||||
|
|
||||||
@skip_unless_avif_encoder("aom")
|
@skip_unless_avif_encoder("aom")
|
||||||
def test_aom_optimizations(self) -> None:
|
def test_aom_optimizations(self, tmp_path: Path) -> None:
|
||||||
im = hopper("RGB")
|
test_file = str(tmp_path / "temp.avif")
|
||||||
buf = BytesIO()
|
hopper().save(test_file, codec="aom", speed=1)
|
||||||
im.save(buf, format="AVIF", codec="aom", speed=1)
|
|
||||||
|
|
||||||
@skip_unless_avif_encoder("svt")
|
@skip_unless_avif_encoder("svt")
|
||||||
def test_svt_optimizations(self) -> None:
|
def test_svt_optimizations(self, tmp_path: Path) -> None:
|
||||||
im = hopper("RGB")
|
test_file = str(tmp_path / "temp.avif")
|
||||||
buf = BytesIO()
|
hopper().save(test_file, codec="svt", speed=1)
|
||||||
im.save(buf, format="AVIF", codec="svt", speed=1)
|
|
||||||
|
|
||||||
|
|
||||||
@skip_unless_feature("avif")
|
@skip_unless_feature("avif")
|
||||||
class TestAvifAnimation:
|
class TestAvifAnimation:
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def star_frames(self) -> Generator[list[ImageFile.ImageFile], None, None]:
|
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/star.png") as f:
|
||||||
with Image.open("Tests/images/avif/star90.png") as f2:
|
yield [f, f.rotate(90), f.rotate(180), f.rotate(270)]
|
||||||
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) -> None:
|
def test_n_frames(self) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -602,10 +597,10 @@ class TestAvifAnimation:
|
||||||
assert im.n_frames == 5
|
assert im.n_frames == 5
|
||||||
assert im.is_animated
|
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
|
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:
|
with Image.open("Tests/images/avif/star.gif") as orig:
|
||||||
|
@ -616,15 +611,17 @@ class TestAvifAnimation:
|
||||||
with Image.open(temp_file) as im:
|
with Image.open(temp_file) as im:
|
||||||
assert im.n_frames == orig.n_frames
|
assert im.n_frames == orig.n_frames
|
||||||
|
|
||||||
# Compare first and second-to-last frames to the original animated GIF
|
# Compare first frame in P mode to frame from original GIF
|
||||||
assert_image_similar(im.convert("RGB"), orig.convert("RGB"), 2.25)
|
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)
|
orig.seek(orig.n_frames - 2)
|
||||||
im.seek(im.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.
|
are visually similar to the originals.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -633,11 +630,11 @@ class TestAvifAnimation:
|
||||||
assert im.n_frames == 4
|
assert im.n_frames == 4
|
||||||
|
|
||||||
# Compare first frame to original
|
# 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
|
# Compare second frame to original
|
||||||
im.seek(1)
|
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:
|
with self.star_frames() as frames:
|
||||||
frame1 = frames[0]
|
frame1 = frames[0]
|
||||||
|
@ -646,7 +643,7 @@ class TestAvifAnimation:
|
||||||
frames[0].copy().save(temp_file1, save_all=True, append_images=frames[1:])
|
frames[0].copy().save(temp_file1, save_all=True, append_images=frames[1:])
|
||||||
check(temp_file1)
|
check(temp_file1)
|
||||||
|
|
||||||
# Tests appending using a generator
|
# Test appending using a generator
|
||||||
def imGenerator(
|
def imGenerator(
|
||||||
ims: list[ImageFile.ImageFile],
|
ims: list[ImageFile.ImageFile],
|
||||||
) -> Generator[ImageFile.ImageFile, None, None]:
|
) -> Generator[ImageFile.ImageFile, None, None]:
|
||||||
|
|
Loading…
Reference in New Issue
Block a user