Merge branch 'master' of github.com:libvips/libvips

This commit is contained in:
John Cupitt 2021-01-12 18:21:03 +00:00
commit 22f3b44d1e
10 changed files with 90 additions and 85 deletions

View File

@ -3,19 +3,11 @@ name: Test
# to-do: # to-do:
# - add a macos test with brew etc. # - add a macos test with brew etc.
# - build with clang/asan/etc. and run the fuzz tests # - build with clang/asan/etc. and run the fuzz tests
# - libvips work with 1.6.1 in ubuntu-20.04, but 1.10 has a bug in heic write
# 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
on: on:
push: - push
branches: [ master ] - pull_request
pull_request: - workflow_dispatch # manually triggered workflow
branches: [ master ]
workflow_dispatch:
jobs: jobs:
build: build:
@ -31,6 +23,11 @@ jobs:
run: run:
sudo apt-get update -qq -o Acquire::Retries=3 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 - name: Install platform dependencies
env: env:
DEBIAN_FRONTEND: noninteractive DEBIAN_FRONTEND: noninteractive
@ -70,15 +67,15 @@ jobs:
--disable-deprecated --disable-deprecated
- name: Build libvips - name: Build libvips
run: make V=0 run: make V=0 -j$(nproc)
- name: Check libvips - name: Check libvips
run: make V=0 check run: make V=0 check
- name: Install libvips - name: Install libvips
run: | run: |
sudo make V=0 install sudo make V=0 install
sudo ldconfig sudo ldconfig
- name: Install pyvips - name: Install pyvips
run: pip3 install pyvips pytest run: pip3 install pyvips pytest

View File

@ -9,7 +9,7 @@ addons:
apt: apt:
update: true update: true
sources: &common_sources sources: &common_sources
# add support for HEIF files # add support for AVIF files
- sourceline: 'ppa:strukturag/libheif' - sourceline: 'ppa:strukturag/libheif'
- sourceline: 'ppa:strukturag/libde265' - sourceline: 'ppa:strukturag/libde265'
packages: &common_packages packages: &common_packages
@ -41,26 +41,30 @@ addons:
- libopenslide-dev - libopenslide-dev
- libffi-dev - libffi-dev
homebrew: homebrew:
update: true
packages: packages:
- ccache - ccache
- gtk-doc
- gobject-introspection
- fftw
- libexif
- libjpeg-turbo
- webp
- imagemagick
- cfitsio - cfitsio
- gsl - fftw
- libmatio - giflib
- orc - glib
- little-cms2 - gobject-introspection
- poppler - gtk-doc
- librsvg - libexif
- openexr
- pango
- libgsf - libgsf
- libheif
- libjpeg-turbo
- libmatio
- librsvg
- libspng
- libtiff
- little-cms2
- openexr
- openslide - openslide
- orc
- pango
- poppler
- webp
jobs: jobs:
allow_failures: allow_failures:
@ -70,7 +74,7 @@ jobs:
- os: linux - os: linux
dist: bionic dist: bionic
compiler: gcc compiler: gcc
name: "Ubuntu 18.04 / GCC 10" name: "Linux x64 (Ubuntu 18.04) - GCC 10"
addons: addons:
apt: apt:
sources: sources:
@ -92,7 +96,7 @@ jobs:
- os: linux - os: linux
dist: bionic dist: bionic
compiler: clang 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: addons:
apt: apt:
sources: sources:
@ -127,16 +131,16 @@ jobs:
cache: ccache cache: ccache
- os: osx - os: osx
osx_image: xcode11 osx_image: xcode11
name: "macOS 10.14.6 / GCC 9" name: "macOS (10.14.6) - Xcode 11"
env: env:
- JPEG=/usr/local/opt/jpeg-turbo - JPEG=/usr/local/opt/jpeg-turbo
- JOBS="`sysctl -n hw.ncpu`" - JOBS="`sysctl -n hw.ncpu`"
- WITH_MAGICK=yes - WITH_MAGICK=no
- PATH="/usr/local/opt/ccache/libexec:$PATH" - 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" - PKG_CONFIG_PATH="/usr/local/opt/jpeg-turbo/lib/pkgconfig:/usr/local/opt/libxml2/lib/pkgconfig:$PKG_CONFIG_PATH"
- HOMEBREW_NO_AUTO_UPDATE=1 - HOMEBREW_NO_AUTO_UPDATE=1
- CC="gcc-9" - CC="clang"
- CXX="g++-9" - CXX="clang++"
cache: ccache cache: ccache
install: install:

View File

@ -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), 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. 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, It supports a good range of image formats, including JPEG, TIFF, PNG,
WebP, HEIC, FITS, Matlab, OpenEXR, PDF, SVG, HDR, PPM / PGM / PFM, CSV, WebP, HEIC, AVIF, FITS, Matlab, OpenEXR, PDF, SVG, HDR, PPM / PGM / PFM,
GIF, Analyze, NIfTI, DeepZoom, and OpenSlide. It can also load images via CSV, GIF, Analyze, NIfTI, DeepZoom, and OpenSlide. It can also load images
ImageMagick or GraphicsMagick, letting it work with formats like DICOM. via ImageMagick or GraphicsMagick, letting it work with formats like DICOM.
It comes with bindings for It comes with bindings for
[C](http://libvips.github.io/libvips/API/current/using-from-c.html), [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 ### libheif
If available, libvips can load and save HEIC images. If available, libvips can load and save HEIC and AVIF images.
# Contributors # Contributors

View File

@ -41,7 +41,7 @@ DICOM_FILE = os.path.join(IMAGES, "dicom_test_image.dcm")
BMP_FILE = os.path.join(IMAGES, "MARBLES.BMP") BMP_FILE = os.path.join(IMAGES, "MARBLES.BMP")
NIFTI_FILE = os.path.join(IMAGES, "avg152T1_LR_nifti.nii.gz") NIFTI_FILE = os.path.join(IMAGES, "avg152T1_LR_nifti.nii.gz")
ICO_FILE = os.path.join(IMAGES, "favicon.ico") 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"), 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, "cd2.1.jpg"), os.path.join(IMAGES, "cd2.2.jpg"),
os.path.join(IMAGES, "cd3.1.jpg"), os.path.join(IMAGES, "cd3.2.jpg"), os.path.join(IMAGES, "cd3.1.jpg"), os.path.join(IMAGES, "cd3.2.jpg"),

Binary file not shown.

Binary file not shown.

View File

@ -72,6 +72,8 @@ class TestCreate:
assert im.max() == 255.0 assert im.max() == 255.0
assert im.min() == 0.0 assert im.min() == 0.0
@pytest.mark.skipif(pyvips.type_find("VipsOperation", "fwfft") == 0,
reason="no FFTW, skipping test")
def test_fractsurf(self): def test_fractsurf(self):
im = pyvips.Image.fractsurf(100, 90, 2.5) im = pyvips.Image.fractsurf(100, 90, 2.5)
assert im.width == 100 assert im.width == 100

View File

@ -11,7 +11,7 @@ from helpers import \
JPEG_FILE, SRGB_FILE, MATLAB_FILE, PNG_FILE, TIF_FILE, OME_FILE, \ JPEG_FILE, SRGB_FILE, MATLAB_FILE, PNG_FILE, TIF_FILE, OME_FILE, \
ANALYZE_FILE, GIF_FILE, WEBP_FILE, EXR_FILE, FITS_FILE, OPENSLIDE_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, \ 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_EXPECTED_PNG_FILE, GIF_ANIM_DISPOSE_BACKGROUND_FILE, \
GIF_ANIM_DISPOSE_BACKGROUND_EXPECTED_PNG_FILE, \ GIF_ANIM_DISPOSE_BACKGROUND_EXPECTED_PNG_FILE, \
GIF_ANIM_DISPOSE_PREVIOUS_FILE, \ GIF_ANIM_DISPOSE_PREVIOUS_FILE, \
@ -75,7 +75,7 @@ class TestForeign:
max_diff = (im - x).abs().max() max_diff = (im - x).abs().max()
assert max_diff == 0 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! # yuk!
# but we can't set format parameters for pyvips.Image.new_temp_file() # but we can't set format parameters for pyvips.Image.new_temp_file()
filename = temp_filename(self.tempdir, format) filename = temp_filename(self.tempdir, format)
@ -86,8 +86,7 @@ class TestForeign:
assert im.width == x.width assert im.width == x.width
assert im.height == x.height assert im.height == x.height
assert im.bands == x.bands assert im.bands == x.bands
max_diff = (im - x).abs().max() assert (im - x).abs().max() <= max_diff
assert max_diff <= thresh
x = None x = None
def save_load_buffer(self, saver, loader, im, max_diff=0, **kwargs): 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 assert (im - x).abs().max() <= max_diff
def test_vips(self): 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 # check we can save and restore metadata
filename = temp_filename(self.tempdir, ".v") 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_buffer("pngsave_buffer", "pngload_buffer", self.colour)
self.save_load("%s.png", self.mono) self.save_load("%s.png", self.mono)
self.save_load("%s.png", self.colour) self.save_load("%s.png", self.colour)
self.save_load_file(".png", "[interlace]", self.colour, 0) self.save_load_file(".png", "[interlace]", self.colour)
self.save_load_file(".png", "[interlace]", self.mono, 0) self.save_load_file(".png", "[interlace]", self.mono)
# size of a regular mono PNG # size of a regular mono PNG
len_mono = len(self.mono.write_to_buffer(".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.cmyk)
self.save_load("%s.tif", self.onebit) self.save_load("%s.tif", self.onebit)
self.save_load_file(".tif", "[bitdepth=1]", self.onebit, 0) self.save_load_file(".tif", "[bitdepth=1]", self.onebit)
self.save_load_file(".tif", "[miniswhite]", self.onebit, 0) self.save_load_file(".tif", "[miniswhite]", self.onebit)
self.save_load_file(".tif", "[bitdepth=1,miniswhite]", self.onebit, 0) self.save_load_file(".tif", "[bitdepth=1,miniswhite]", self.onebit)
self.save_load_file(".tif", self.save_load_file(".tif",
"[profile={0}]".format(SRGB_FILE), "[profile={0}]".format(SRGB_FILE),
self.colour, 0) self.colour)
self.save_load_file(".tif", "[tile]", self.colour, 0) self.save_load_file(".tif", "[tile]", self.colour)
self.save_load_file(".tif", "[tile,pyramid]", self.colour, 0) self.save_load_file(".tif", "[tile,pyramid]", self.colour)
self.save_load_file(".tif", "[tile,pyramid,subifd]", self.colour, 0) self.save_load_file(".tif", "[tile,pyramid,subifd]", self.colour)
self.save_load_file(".tif", self.save_load_file(".tif",
"[tile,pyramid,compression=jpeg]", self.colour, 80) "[tile,pyramid,compression=jpeg]", self.colour, 80)
self.save_load_file(".tif", self.save_load_file(".tif",
"[tile,pyramid,subifd,compression=jpeg]", "[tile,pyramid,subifd,compression=jpeg]",
self.colour, 80) 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", "[compression=jpeg]", self.colour, 80)
self.save_load_file(".tif", self.save_load_file(".tif",
"[tile,tile-width=256]", self.colour, 10) "[tile,tile-width=256]", self.colour, 10)
im = pyvips.Image.new_from_file(TIF2_FILE) 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) 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') filename = temp_filename(self.tempdir, '.tif')
x = pyvips.Image.new_from_file(TIF_FILE) x = pyvips.Image.new_from_file(TIF_FILE)
@ -1072,37 +1071,42 @@ class TestForeign:
def test_heifload(self): def test_heifload(self):
def heif_valid(im): def heif_valid(im):
a = im(10, 10) a = im(10, 10)
# different versions of HEIC decode have slightly different # different versions of libheif decode have slightly different
# rounding # rounding
assert_almost_equal_objects(a, [197.0, 181.0, 158.0], threshold=2) assert_almost_equal_objects(a, [197.0, 181.0, 158.0], threshold=2)
assert im.width == 3024 assert im.width == 3024
assert im.height == 4032 assert im.height == 4032
assert im.bands == 3 assert im.bands == 3
self.file_loader("heifload", HEIC_FILE, heif_valid) self.file_loader("heifload", AVIF_FILE, heif_valid)
self.buffer_loader("heifload_buffer", HEIC_FILE, heif_valid) self.buffer_loader("heifload_buffer", AVIF_FILE, heif_valid)
@skip_if_no("heifsave") @skip_if_no("heifsave")
def test_heifsave(self): def test_heifsave(self):
self.save_load_buffer("heifsave_buffer", "heifload_buffer", self.save_load_buffer("heifsave_buffer", "heifload_buffer",
self.colour, 80) self.colour, 80, compression="av1")
self.save_load("%s.heic", self.colour) # 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 # uncomment to test lossless mode, will take a while
im = pyvips.Image.new_from_file(HEIC_FILE) #im = pyvips.Image.new_from_file(AVIF_FILE)
buf = im.heifsave_buffer(lossless=True) #buf = im.heifsave_buffer(lossless=True, compression="av1")
im2 = pyvips.Image.new_from_buffer(buf, "") #im2 = pyvips.Image.new_from_buffer(buf, "")
# not in fact quite lossless # 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 # higher Q should mean a bigger buffer, needs libheif >= v1.8.0,
b1 = im.heifsave_buffer(Q=10) # see: https://github.com/libvips/libvips/issues/1757
b2 = im.heifsave_buffer(Q=90) b1 = self.mono.heifsave_buffer(Q=10, compression="av1")
b2 = self.mono.heifsave_buffer(Q=90, compression="av1")
assert len(b2) > len(b1) assert len(b2) > len(b1)
# try saving an image with an ICC profile and reading it back # try saving an image with an ICC profile and reading it back
# not all libheif have profile support, so put it in an if # 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, "") im = pyvips.Image.new_from_buffer(buf, "")
p1 = self.colour.get("icc-profile-data") p1 = self.colour.get("icc-profile-data")
if im.get_typeof("icc-profile-data") != 0: 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, # the exif test will need us to be able to walk the header,
# we can't just check exif-data # 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 # test that exif changes change the output of heifsave
# first make sure we have exif support # first make sure we have exif support
#z = pyvips.Image.new_from_file(JPEG_FILE) z = pyvips.Image.new_from_file(AVIF_FILE)
#if z.get_typeof("exif-ifd0-Orientation") != 0: if z.get_typeof("exif-ifd0-Make") != 0:
# x = self.colour.copy() x = z.copy()
# x.set("exif-ifd0-Make", "banana") x.set("exif-ifd0-Make", "banana")
# buf = x.heifsave_buffer() buf = x.heifsave_buffer(Q=10, compression="av1")
# y = pyvips.Image.new_from_buffer(buf, "") y = pyvips.Image.new_from_buffer(buf, "")
# assert y.get("exif-ifd0-Make").split(" ")[0] == "banana" assert y.get("exif-ifd0-Make").split(" ")[0] == "banana"
if __name__ == '__main__': if __name__ == '__main__':
pytest.main() pytest.main()

View File

@ -2,7 +2,7 @@
import pytest import pytest
import pyvips 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 # Run a function expecting a complex image on a two-band image
@ -195,8 +195,8 @@ class TestResample:
if have("heifload"): if have("heifload"):
# this image is orientation 6 ... thumbnail should flip it # this image is orientation 6 ... thumbnail should flip it
im = pyvips.Image.new_from_file(HEIC_FILE) im = pyvips.Image.new_from_file(AVIF_FILE)
thumb = pyvips.Image.thumbnail(HEIC_FILE, 100) thumb = pyvips.Image.thumbnail(AVIF_FILE, 100)
# thumb should be portrait # thumb should be portrait
assert thumb.width < thumb.height assert thumb.width < thumb.height