From 83c14048e0a5b44e7a6cd60eb0ef0ce1ddf6b000 Mon Sep 17 00:00:00 2001 From: Lars Fronius Date: Sat, 10 Oct 2020 10:59:35 +0200 Subject: [PATCH] Adds AVIF support * This adds a new type AVIF to the supported type list if libvips >= 8.9.0 * Calls libheif through libvips with the AV1 compression set to save AVIF images. --- .travis.yml | 4 ++-- Dockerfile | 18 ++++++++++++++++-- image_test.go | 4 ++-- resizer.go | 2 +- type.go | 3 +++ type_test.go | 18 +++++++++++++++++- vips.go | 12 ++++++++++++ vips.h | 18 +++++++++++++++++- vips_test.go | 16 ++++++++++++++++ 9 files changed, 86 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index 781864c..4391782 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,7 +11,7 @@ env: # - LIBVIPS=8.7.4 # - LIBVIPS=8.8.4 # - LIBVIPS=8.9.2 - - LIBVIPS=8.10.0 + - LIBVIPS=8.10.1 matrix: allow_failures: @@ -26,7 +26,7 @@ install: - docker build -t h2non/bimg:ci --build-arg LIBVIPS_VERSION=$LIBVIPS . script: - - docker run h2non/bimg:ci sh -c 'export LD_LIBRARY_PATH=/vips/lib:$LD_LIBRARY_PATH; export PKG_CONFIG_PATH=/vips/lib/pkgconfig:/usr/local/lib/pkgconfig:/usr/lib/pkgconfig:/usr/X11/lib/pkgconfig; go vet . && golint . && go test -v -race -covermode=atomic -coverprofile=coverage.out' + - docker run h2non/bimg:ci sh -c 'export LD_LIBRARY_PATH=/vips/lib:/usr/local/lib:$LD_LIBRARY_PATH; export PKG_CONFIG_PATH=/vips/lib/pkgconfig:/usr/local/lib/pkgconfig:/usr/lib/pkgconfig:/usr/X11/lib/pkgconfig; go vet . && golint . && go test -v -race -covermode=atomic -coverprofile=coverage.out' # after_success: # - goveralls -coverprofile=coverage.out -service=travis-ci diff --git a/Dockerfile b/Dockerfile index 29ee6a3..b358868 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,6 +2,7 @@ FROM golang:1.14 LABEL maintainer "tomas@aparicio.me" ARG LIBVIPS_VERSION=8.9.2 +ARG LIBHEIF_VERSION=1.9.1 ARG GOLANGCILINT_VERSION=1.29.0 # Installs libvips + required libraries @@ -13,7 +14,18 @@ RUN DEBIAN_FRONTEND=noninteractive \ gobject-introspection gtk-doc-tools libglib2.0-dev libjpeg62-turbo-dev libpng-dev \ libwebp-dev libtiff5-dev libgif-dev libexif-dev libxml2-dev libpoppler-glib-dev \ swig libmagickwand-dev libpango1.0-dev libmatio-dev libopenslide-dev libcfitsio-dev \ - libgsf-1-dev fftw3-dev liborc-0.4-dev librsvg2-dev libimagequant-dev libheif-dev && \ + libgsf-1-dev fftw3-dev liborc-0.4-dev librsvg2-dev libimagequant-dev && \ + cd /tmp && \ + curl -fsSLO https://github.com/strukturag/libheif/releases/download/v${LIBHEIF_VERSION}/libheif-${LIBHEIF_VERSION}.tar.gz && \ + tar zvxf libheif-${LIBHEIF_VERSION}.tar.gz && \ + cd /tmp/libheif-${LIBHEIF_VERSION} && \ + ./configure --prefix=/vips && \ + make && \ + make install && \ + echo '/vips/lib' > /etc/ld.so.conf.d/vips.conf && \ + ldconfig -v && \ + export LD_LIBRARY_PATH="/vips/lib:$LD_LIBRARY_PATH" && \ + export PKG_CONFIG_PATH="/vips/lib/pkgconfig:$PKG_CONFIG_PATH" && \ cd /tmp && \ curl -fsSLO https://github.com/libvips/libvips/releases/download/v${LIBVIPS_VERSION}/vips-${LIBVIPS_VERSION}.tar.gz && \ tar zvxf vips-${LIBVIPS_VERSION}.tar.gz && \ @@ -32,6 +44,8 @@ RUN DEBIAN_FRONTEND=noninteractive \ make install && \ ldconfig +ENV LD_LIBRARY_PATH="/usr/local/lib:$LD_LIBRARY_PATH" + # Install runtime dependencies # RUN DEBIAN_FRONTEND=noninteractive \ # apt-get update && \ @@ -62,4 +76,4 @@ COPY . . # apt-get clean && \ # rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* -CMD [ "/bin/bash" ] \ No newline at end of file +CMD [ "/bin/bash" ] diff --git a/image_test.go b/image_test.go index dbe7f3e..76e819d 100644 --- a/image_test.go +++ b/image_test.go @@ -352,8 +352,8 @@ func TestImageAutoRotate(t *testing.T) { } tests := []struct { - file string - orientation int + file string + orientation int }{ {"exif/Landscape_1.jpg", 1}, {"exif/Landscape_2.jpg", 1}, diff --git a/resizer.go b/resizer.go index 427cada..9c3d8b6 100644 --- a/resizer.go +++ b/resizer.go @@ -51,7 +51,7 @@ func resizer(buf []byte, o Options) ([]byte, error) { } // If JPEG or HEIF image, retrieve the buffer - if rotated && (imageType == JPEG || imageType == HEIF) && !o.NoAutoRotate { + if rotated && (imageType == JPEG || imageType == HEIF || imageType == AVIF) && !o.NoAutoRotate { buf, err = getImageBuffer(image) if err != nil { return nil, err diff --git a/type.go b/type.go index 603dddf..7b027fd 100644 --- a/type.go +++ b/type.go @@ -30,6 +30,8 @@ const ( MAGICK // HEIF represents the HEIC/HEIF/HVEC image type HEIF + // AVIF represents the AVIF image type. + AVIF ) var ( @@ -48,6 +50,7 @@ var ImageTypes = map[ImageType]string{ SVG: "svg", MAGICK: "magick", HEIF: "heif", + AVIF: "avif", } // imageMutex is used to provide thread-safe synchronization diff --git a/type_test.go b/type_test.go index 7c4b1fa..7154da2 100644 --- a/type_test.go +++ b/type_test.go @@ -22,6 +22,7 @@ func TestDeterminateImageType(t *testing.T) { {"test.heic", HEIF}, {"test2.heic", HEIF}, {"test3.heic", HEIF}, + {"test.avif", AVIF}, } for _, file := range files { @@ -51,12 +52,16 @@ func TestDeterminateImageTypeName(t *testing.T) { {"test.svg", "svg"}, // {"test.jp2", "magick"}, {"test.heic", "heif"}, + {"test.avif", "avif"}, } for _, file := range files { if file.expected == "heif" && VipsMajorVersion <= 8 && VipsMinorVersion < 8 { continue } + if file.expected == "avif" && VipsMajorVersion <= 8 && VipsMinorVersion < 9 { + continue + } img, _ := os.Open(path.Join("testdata", file.name)) buf, _ := ioutil.ReadAll(img) @@ -73,13 +78,16 @@ func TestIsTypeSupported(t *testing.T) { types := []struct { name ImageType }{ - {JPEG}, {PNG}, {WEBP}, {GIF}, {PDF}, {HEIF}, + {JPEG}, {PNG}, {WEBP}, {GIF}, {PDF}, {HEIF}, {AVIF}, } for _, n := range types { if n.name == HEIF && VipsMajorVersion <= 8 && VipsMinorVersion < 8 { continue } + if n.name == AVIF && VipsMajorVersion <= 8 && VipsMinorVersion < 9 { + continue + } if IsTypeSupported(n.name) == false { t.Fatalf("Image type %s is not valid", ImageTypes[n.name]) } @@ -97,12 +105,16 @@ func TestIsTypeNameSupported(t *testing.T) { {"gif", true}, {"pdf", true}, {"heif", true}, + {"avif", true}, } for _, n := range types { if n.name == "heif" && VipsMajorVersion <= 8 && VipsMinorVersion < 8 { continue } + if n.name == "avif" && VipsMajorVersion <= 8 && VipsMinorVersion < 9 { + continue + } if IsTypeNameSupported(n.name) != n.expected { t.Fatalf("Image type %s is not valid", n.name) } @@ -121,6 +133,9 @@ func TestIsTypeSupportedSave(t *testing.T) { if VipsVersion >= "8.8.0" { types = append(types, struct{ name ImageType }{HEIF}) } + if VipsVersion >= "8.9.0" { + types = append(types, struct{ name ImageType }{AVIF}) + } for _, n := range types { if IsTypeSupportedSave(n.name) == false { @@ -141,6 +156,7 @@ func TestIsTypeNameSupportedSave(t *testing.T) { {"pdf", false}, {"tiff", VipsVersion >= "8.5.0"}, {"heif", VipsVersion >= "8.8.0"}, + {"avif", VipsVersion >= "8.9.0"}, } for _, n := range types { diff --git a/vips.go b/vips.go index 7fa4606..a7a6536 100644 --- a/vips.go +++ b/vips.go @@ -190,6 +190,9 @@ func VipsIsTypeSupported(t ImageType) bool { if t == HEIF { return int(C.vips_type_find_bridge(C.HEIF)) != 0 } + if t == AVIF { + return int(C.vips_type_find_bridge(C.HEIF)) != 0 + } return false } @@ -212,6 +215,9 @@ func VipsIsTypeSupportedSave(t ImageType) bool { if t == HEIF { return int(C.vips_type_find_save_bridge(C.HEIF)) != 0 } + if t == AVIF { + return int(C.vips_type_find_save_bridge(C.HEIF)) != 0 + } return false } @@ -501,6 +507,8 @@ func vipsSave(image *C.VipsImage, o vipsSaveOptions) ([]byte, error) { saveErr = C.vips_tiffsave_bridge(tmpImage, &ptr, &length) case HEIF: saveErr = C.vips_heifsave_bridge(tmpImage, &ptr, &length, strip, quality, lossless) + case AVIF: + saveErr = C.vips_avifsave_bridge(tmpImage, &ptr, &length, strip, quality, lossless) default: saveErr = C.vips_jpegsave_bridge(tmpImage, &ptr, &length, strip, quality, interlace) } @@ -735,6 +743,10 @@ func vipsImageType(buf []byte) ImageType { // This is a HEIFS file, ftyphevc return HEIF } + if IsTypeSupported(HEIF) && buf[4] == 0x66 && buf[5] == 0x74 && buf[6] == 0x79 && buf[7] == 0x70 && + buf[8] == 0x61 && buf[9] == 0x76 && buf[10] == 0x69 && buf[11] == 0x66 { + return AVIF + } return UNKNOWN } diff --git a/vips.h b/vips.h index 64d772f..a7d0c4f 100644 --- a/vips.h +++ b/vips.h @@ -34,6 +34,7 @@ enum types { SVG, MAGICK, HEIF, + AVIF }; typedef struct { @@ -335,7 +336,7 @@ vips_pngsave_bridge(VipsImage *in, void **buf, size_t *len, int strip, int compr "compression", compression, "interlace", INT_TO_GBOOLEAN(interlace), "filter", VIPS_FOREIGN_PNG_FILTER_ALL, - "palette", INT_TO_GBOOLEAN(palette), + "palette", INT_TO_GBOOLEAN(palette), NULL ); #else @@ -367,6 +368,21 @@ vips_tiffsave_bridge(VipsImage *in, void **buf, size_t *len) { #endif } +int +vips_avifsave_bridge(VipsImage *in, void **buf, size_t *len, int strip, int quality, int lossless) { +#if (VIPS_MAJOR_VERSION > 8 || (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION >= 9)) + return vips_heifsave_buffer(in, buf, len, + "strip", INT_TO_GBOOLEAN(strip), + "Q", quality, + "lossless", INT_TO_GBOOLEAN(lossless), + "compression", VIPS_FOREIGN_HEIF_COMPRESSION_AV1, + NULL + ); +#else + return 0; +#endif +} + int vips_heifsave_bridge(VipsImage *in, void **buf, size_t *len, int strip, int quality, int lossless) { #if (VIPS_MAJOR_VERSION > 8 || (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION >= 8)) diff --git a/vips_test.go b/vips_test.go index e3092bd..a792054 100644 --- a/vips_test.go +++ b/vips_test.go @@ -58,6 +58,22 @@ func TestVipsSaveTiff(t *testing.T) { } } +func TestVipsSafeAvif(t *testing.T) { + if !IsTypeSupportedSave(AVIF) { + t.Skipf("Format %#v is not supported", ImageTypes[AVIF]) + } + image, _, _ := vipsRead(readImage("test.jpg")) + options := vipsSaveOptions{Quality: 95, Type: AVIF} + buf, err := vipsSave(image, options) + if err != nil { + t.Fatalf("Error saving image type %v: %v", ImageTypes[AVIF], err) + } + + if len(buf) == 0 { + t.Fatalf("Empty saved '%v' image", ImageTypes[AVIF]) + } +} + func TestVipsRotate(t *testing.T) { files := []struct { name string