diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0c11b9db..b10b237c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -3,21 +3,11 @@ name: Test # to-do: # - add a macos test with brew etc. # - build with clang/asan/etc. and run the fuzz tests -# - libvips work with 1.6.1 in ubuntu-20.04, but fails with 1.10, strangely -# - paste this back when it's working -# - name: Add libheif PPA -# run: | -# sudo add-apt-repository ppa:strukturag/libde265 -# sudo add-apt-repository ppa:strukturag/libheif -# you may also need to add libaom-dev, libde265-dev, libx265-dev to get -# this to work, see https://github.com/strukturag/libheif/issues/404 on: - push: - branches: [ master ] - pull_request: - branches: [ master ] - workflow_dispatch: + - push + - pull_request + - workflow_dispatch # manually triggered workflow jobs: build: @@ -33,6 +23,11 @@ jobs: run: sudo apt-get update -qq -o Acquire::Retries=3 + - name: Add libheif PPA + run: | + sudo add-apt-repository ppa:strukturag/libde265 + sudo add-apt-repository ppa:strukturag/libheif + - name: Install platform dependencies env: DEBIAN_FRONTEND: noninteractive @@ -72,15 +67,15 @@ jobs: --disable-deprecated - name: Build libvips - run: make V=0 + run: make V=0 -j$(nproc) - name: Check libvips run: make V=0 check - name: Install libvips run: | - sudo make V=0 install - sudo ldconfig + sudo make V=0 install + sudo ldconfig - name: Install pyvips run: pip3 install pyvips pytest diff --git a/.travis.yml b/.travis.yml index 3d2a11f2..bebdeff5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,7 +9,7 @@ addons: apt: update: true sources: &common_sources - # add support for HEIF files + # add support for AVIF files - sourceline: 'ppa:strukturag/libheif' - sourceline: 'ppa:strukturag/libde265' packages: &common_packages @@ -41,26 +41,30 @@ addons: - libopenslide-dev - libffi-dev homebrew: + update: true packages: - ccache - - gtk-doc - - gobject-introspection - - fftw - - libexif - - libjpeg-turbo - - webp - - imagemagick - cfitsio - - gsl - - libmatio - - orc - - little-cms2 - - poppler - - librsvg - - openexr - - pango + - fftw + - giflib + - glib + - gobject-introspection + - gtk-doc + - libexif - libgsf + - libheif + - libjpeg-turbo + - libmatio + - librsvg + - libspng + - libtiff + - little-cms2 + - openexr - openslide + - orc + - pango + - poppler + - webp jobs: allow_failures: @@ -70,7 +74,7 @@ jobs: - os: linux dist: bionic compiler: gcc - name: "Ubuntu 18.04 / GCC 10" + name: "Linux x64 (Ubuntu 18.04) - GCC 10" addons: apt: sources: @@ -92,7 +96,7 @@ jobs: - os: linux dist: bionic compiler: clang - name: "Ubuntu 18.04 / Clang 10 with ASan and UBSan" + name: "Linux x64 (Ubuntu 18.04) - Clang 10 with ASan and UBSan" addons: apt: sources: @@ -127,16 +131,16 @@ jobs: cache: ccache - os: osx osx_image: xcode11 - name: "macOS 10.14.6 / GCC 9" + name: "macOS (10.14.6) - Xcode 11" env: - JPEG=/usr/local/opt/jpeg-turbo - JOBS="`sysctl -n hw.ncpu`" - - WITH_MAGICK=yes + - WITH_MAGICK=no - PATH="/usr/local/opt/ccache/libexec:$PATH" - PKG_CONFIG_PATH="/usr/local/opt/jpeg-turbo/lib/pkgconfig:/usr/local/opt/libxml2/lib/pkgconfig:$PKG_CONFIG_PATH" - HOMEBREW_NO_AUTO_UPDATE=1 - - CC="gcc-9" - - CXX="g++-9" + - CC="clang" + - CXX="clang++" cache: ccache install: diff --git a/README.md b/README.md index 911ef254..b7b9023f 100644 --- a/README.md +++ b/README.md @@ -23,9 +23,9 @@ statistics and others. It supports a large range of [numeric types](http://libvips.github.io/libvips/API/current/VipsImage.html#VipsBandFormat), from 8-bit int to 128-bit complex. Images can have any number of bands. It supports a good range of image formats, including JPEG, TIFF, PNG, -WebP, HEIC, FITS, Matlab, OpenEXR, PDF, SVG, HDR, PPM / PGM / PFM, CSV, -GIF, Analyze, NIfTI, DeepZoom, and OpenSlide. It can also load images via -ImageMagick or GraphicsMagick, letting it work with formats like DICOM. +WebP, HEIC, AVIF, FITS, Matlab, OpenEXR, PDF, SVG, HDR, PPM / PGM / PFM, +CSV, GIF, Analyze, NIfTI, DeepZoom, and OpenSlide. It can also load images +via ImageMagick or GraphicsMagick, letting it work with formats like DICOM. It comes with bindings for [C](http://libvips.github.io/libvips/API/current/using-from-c.html), @@ -279,7 +279,7 @@ files: Aperio, Hamamatsu, Leica, MIRAX, Sakura, Trestle, and Ventana. ### libheif -If available, libvips can load and save HEIC images. +If available, libvips can load and save HEIC and AVIF images. # Contributors diff --git a/test/test-suite/helpers/helpers.py b/test/test-suite/helpers/helpers.py index 585a7f0c..c062d436 100644 --- a/test/test-suite/helpers/helpers.py +++ b/test/test-suite/helpers/helpers.py @@ -41,7 +41,7 @@ DICOM_FILE = os.path.join(IMAGES, "dicom_test_image.dcm") BMP_FILE = os.path.join(IMAGES, "MARBLES.BMP") NIFTI_FILE = os.path.join(IMAGES, "avg152T1_LR_nifti.nii.gz") ICO_FILE = os.path.join(IMAGES, "favicon.ico") -HEIC_FILE = os.path.join(IMAGES, "heic-orientation-6.heic") +AVIF_FILE = os.path.join(IMAGES, "avif-orientation-6.avif") MOSAIC_FILES = [os.path.join(IMAGES, "cd1.1.jpg"), os.path.join(IMAGES, "cd1.2.jpg"), os.path.join(IMAGES, "cd2.1.jpg"), os.path.join(IMAGES, "cd2.2.jpg"), os.path.join(IMAGES, "cd3.1.jpg"), os.path.join(IMAGES, "cd3.2.jpg"), diff --git a/test/test-suite/images/Example1.heic b/test/test-suite/images/Example1.heic deleted file mode 100644 index dfa3d5f0..00000000 Binary files a/test/test-suite/images/Example1.heic and /dev/null differ diff --git a/test/test-suite/images/avif-orientation-6.avif b/test/test-suite/images/avif-orientation-6.avif new file mode 100644 index 00000000..bd99b941 Binary files /dev/null and b/test/test-suite/images/avif-orientation-6.avif differ diff --git a/test/test-suite/images/heic-orientation-6.heic b/test/test-suite/images/heic-orientation-6.heic deleted file mode 100644 index b5c44cc3..00000000 Binary files a/test/test-suite/images/heic-orientation-6.heic and /dev/null differ diff --git a/test/test-suite/test_create.py b/test/test-suite/test_create.py index 6dd9c35c..2dbf64ce 100644 --- a/test/test-suite/test_create.py +++ b/test/test-suite/test_create.py @@ -72,6 +72,8 @@ class TestCreate: assert im.max() == 255.0 assert im.min() == 0.0 + @pytest.mark.skipif(pyvips.type_find("VipsOperation", "fwfft") == 0, + reason="no FFTW, skipping test") def test_fractsurf(self): im = pyvips.Image.fractsurf(100, 90, 2.5) assert im.width == 100 diff --git a/test/test-suite/test_foreign.py b/test/test-suite/test_foreign.py index 273f46fc..b6b64bf2 100644 --- a/test/test-suite/test_foreign.py +++ b/test/test-suite/test_foreign.py @@ -11,7 +11,7 @@ from helpers import \ JPEG_FILE, SRGB_FILE, MATLAB_FILE, PNG_FILE, TIF_FILE, OME_FILE, \ ANALYZE_FILE, GIF_FILE, WEBP_FILE, EXR_FILE, FITS_FILE, OPENSLIDE_FILE, \ PDF_FILE, SVG_FILE, SVGZ_FILE, SVG_GZ_FILE, GIF_ANIM_FILE, DICOM_FILE, \ - BMP_FILE, NIFTI_FILE, ICO_FILE, HEIC_FILE, TRUNCATED_FILE, \ + BMP_FILE, NIFTI_FILE, ICO_FILE, AVIF_FILE, TRUNCATED_FILE, \ GIF_ANIM_EXPECTED_PNG_FILE, GIF_ANIM_DISPOSE_BACKGROUND_FILE, \ GIF_ANIM_DISPOSE_BACKGROUND_EXPECTED_PNG_FILE, \ GIF_ANIM_DISPOSE_PREVIOUS_FILE, \ @@ -75,7 +75,7 @@ class TestForeign: max_diff = (im - x).abs().max() assert max_diff == 0 - def save_load_file(self, format, options, im, thresh): + def save_load_file(self, format, options, im, max_diff=0): # yuk! # but we can't set format parameters for pyvips.Image.new_temp_file() filename = temp_filename(self.tempdir, format) @@ -86,8 +86,7 @@ class TestForeign: assert im.width == x.width assert im.height == x.height assert im.bands == x.bands - max_diff = (im - x).abs().max() - assert max_diff <= thresh + assert (im - x).abs().max() <= max_diff x = None def save_load_buffer(self, saver, loader, im, max_diff=0, **kwargs): @@ -115,7 +114,7 @@ class TestForeign: assert (im - x).abs().max() <= max_diff def test_vips(self): - self.save_load_file(".v", "", self.colour, 0) + self.save_load_file(".v", "", self.colour) # check we can save and restore metadata filename = temp_filename(self.tempdir, ".v") @@ -321,8 +320,8 @@ class TestForeign: self.save_load_buffer("pngsave_buffer", "pngload_buffer", self.colour) self.save_load("%s.png", self.mono) self.save_load("%s.png", self.colour) - self.save_load_file(".png", "[interlace]", self.colour, 0) - self.save_load_file(".png", "[interlace]", self.mono, 0) + self.save_load_file(".png", "[interlace]", self.colour) + self.save_load_file(".png", "[interlace]", self.mono) # size of a regular mono PNG len_mono = len(self.mono.write_to_buffer(".png")) @@ -391,30 +390,30 @@ class TestForeign: self.save_load("%s.tif", self.cmyk) self.save_load("%s.tif", self.onebit) - self.save_load_file(".tif", "[bitdepth=1]", self.onebit, 0) - self.save_load_file(".tif", "[miniswhite]", self.onebit, 0) - self.save_load_file(".tif", "[bitdepth=1,miniswhite]", self.onebit, 0) + self.save_load_file(".tif", "[bitdepth=1]", self.onebit) + self.save_load_file(".tif", "[miniswhite]", self.onebit) + self.save_load_file(".tif", "[bitdepth=1,miniswhite]", self.onebit) self.save_load_file(".tif", "[profile={0}]".format(SRGB_FILE), - self.colour, 0) - self.save_load_file(".tif", "[tile]", self.colour, 0) - self.save_load_file(".tif", "[tile,pyramid]", self.colour, 0) - self.save_load_file(".tif", "[tile,pyramid,subifd]", self.colour, 0) + self.colour) + self.save_load_file(".tif", "[tile]", self.colour) + self.save_load_file(".tif", "[tile,pyramid]", self.colour) + self.save_load_file(".tif", "[tile,pyramid,subifd]", self.colour) self.save_load_file(".tif", "[tile,pyramid,compression=jpeg]", self.colour, 80) self.save_load_file(".tif", "[tile,pyramid,subifd,compression=jpeg]", self.colour, 80) - self.save_load_file(".tif", "[bigtiff]", self.colour, 0) + self.save_load_file(".tif", "[bigtiff]", self.colour) self.save_load_file(".tif", "[compression=jpeg]", self.colour, 80) self.save_load_file(".tif", "[tile,tile-width=256]", self.colour, 10) im = pyvips.Image.new_from_file(TIF2_FILE) - self.save_load_file(".tif", "[bitdepth=2]", im, 0) + self.save_load_file(".tif", "[bitdepth=2]", im) im = pyvips.Image.new_from_file(TIF4_FILE) - self.save_load_file(".tif", "[bitdepth=4]", im, 0) + self.save_load_file(".tif", "[bitdepth=4]", im) filename = temp_filename(self.tempdir, '.tif') x = pyvips.Image.new_from_file(TIF_FILE) @@ -1072,37 +1071,42 @@ class TestForeign: def test_heifload(self): def heif_valid(im): a = im(10, 10) - # different versions of HEIC decode have slightly different + # different versions of libheif decode have slightly different # rounding assert_almost_equal_objects(a, [197.0, 181.0, 158.0], threshold=2) assert im.width == 3024 assert im.height == 4032 assert im.bands == 3 - self.file_loader("heifload", HEIC_FILE, heif_valid) - self.buffer_loader("heifload_buffer", HEIC_FILE, heif_valid) + self.file_loader("heifload", AVIF_FILE, heif_valid) + self.buffer_loader("heifload_buffer", AVIF_FILE, heif_valid) @skip_if_no("heifsave") def test_heifsave(self): self.save_load_buffer("heifsave_buffer", "heifload_buffer", - self.colour, 80) - self.save_load("%s.heic", self.colour) + self.colour, 80, compression="av1") + # TODO: perhaps we should automatically set the compression to + # av1 when we save to *.avif? + #self.save_load("%s.avif", self.colour) + self.save_load_file(".avif", "[compression=av1]", + self.colour, 80) - # test lossless mode - im = pyvips.Image.new_from_file(HEIC_FILE) - buf = im.heifsave_buffer(lossless=True) - im2 = pyvips.Image.new_from_buffer(buf, "") + # uncomment to test lossless mode, will take a while + #im = pyvips.Image.new_from_file(AVIF_FILE) + #buf = im.heifsave_buffer(lossless=True, compression="av1") + #im2 = pyvips.Image.new_from_buffer(buf, "") # not in fact quite lossless - assert abs(im.avg() - im2.avg()) < 3 + #assert abs(im.avg() - im2.avg()) < 3 - # higher Q should mean a bigger buffer - b1 = im.heifsave_buffer(Q=10) - b2 = im.heifsave_buffer(Q=90) + # higher Q should mean a bigger buffer, needs libheif >= v1.8.0, + # see: https://github.com/libvips/libvips/issues/1757 + b1 = self.mono.heifsave_buffer(Q=10, compression="av1") + b2 = self.mono.heifsave_buffer(Q=90, compression="av1") assert len(b2) > len(b1) # try saving an image with an ICC profile and reading it back # not all libheif have profile support, so put it in an if - buf = self.colour.heifsave_buffer() + buf = self.colour.heifsave_buffer(Q=10, compression="av1") im = pyvips.Image.new_from_buffer(buf, "") p1 = self.colour.get("icc-profile-data") if im.get_typeof("icc-profile-data") != 0: @@ -1113,18 +1117,16 @@ class TestForeign: # the exif test will need us to be able to walk the header, # we can't just check exif-data - # libheif 1.1 (on ubuntu 18.04, current LTS) does not support exif - # write, so this test is commented out - # test that exif changes change the output of heifsave # first make sure we have exif support - #z = pyvips.Image.new_from_file(JPEG_FILE) - #if z.get_typeof("exif-ifd0-Orientation") != 0: - # x = self.colour.copy() - # x.set("exif-ifd0-Make", "banana") - # buf = x.heifsave_buffer() - # y = pyvips.Image.new_from_buffer(buf, "") - # assert y.get("exif-ifd0-Make").split(" ")[0] == "banana" + z = pyvips.Image.new_from_file(AVIF_FILE) + if z.get_typeof("exif-ifd0-Make") != 0: + x = z.copy() + x.set("exif-ifd0-Make", "banana") + buf = x.heifsave_buffer(Q=10, compression="av1") + y = pyvips.Image.new_from_buffer(buf, "") + assert y.get("exif-ifd0-Make").split(" ")[0] == "banana" + if __name__ == '__main__': pytest.main() diff --git a/test/test-suite/test_resample.py b/test/test-suite/test_resample.py index afd21422..1756b581 100644 --- a/test/test-suite/test_resample.py +++ b/test/test-suite/test_resample.py @@ -2,7 +2,7 @@ import pytest import pyvips -from helpers import JPEG_FILE, OME_FILE, HEIC_FILE, TIF_FILE, all_formats, have +from helpers import JPEG_FILE, OME_FILE, AVIF_FILE, TIF_FILE, all_formats, have # Run a function expecting a complex image on a two-band image @@ -195,8 +195,8 @@ class TestResample: if have("heifload"): # this image is orientation 6 ... thumbnail should flip it - im = pyvips.Image.new_from_file(HEIC_FILE) - thumb = pyvips.Image.thumbnail(HEIC_FILE, 100) + im = pyvips.Image.new_from_file(AVIF_FILE) + thumb = pyvips.Image.thumbnail(AVIF_FILE, 100) # thumb should be portrait assert thumb.width < thumb.height