Merge pull request #356 from LarsFronius/avif-support

Adds AVIF support
This commit is contained in:
Tom 2020-11-21 13:46:56 +01:00 committed by GitHub
commit 152a6b1506
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 111 additions and 12 deletions

View file

@ -11,7 +11,8 @@ env:
# - LIBVIPS=8.7.4 # - LIBVIPS=8.7.4
# - LIBVIPS=8.8.4 # - LIBVIPS=8.8.4
# - LIBVIPS=8.9.2 # - LIBVIPS=8.9.2
- LIBVIPS=8.10.0 - LIBVIPS=8.10.1
- LIBVIPS=8.10.2
matrix: matrix:
allow_failures: allow_failures:
@ -26,7 +27,7 @@ install:
- docker build -t h2non/bimg:ci --build-arg LIBVIPS_VERSION=$LIBVIPS . - docker build -t h2non/bimg:ci --build-arg LIBVIPS_VERSION=$LIBVIPS .
script: 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: # after_success:
# - goveralls -coverprofile=coverage.out -service=travis-ci # - goveralls -coverprofile=coverage.out -service=travis-ci

View file

@ -2,6 +2,7 @@ FROM golang:1.14
LABEL maintainer "tomas@aparicio.me" LABEL maintainer "tomas@aparicio.me"
ARG LIBVIPS_VERSION=8.9.2 ARG LIBVIPS_VERSION=8.9.2
ARG LIBHEIF_VERSION=1.9.1
ARG GOLANGCILINT_VERSION=1.29.0 ARG GOLANGCILINT_VERSION=1.29.0
# Installs libvips + required libraries # 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 \ 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 \ 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 \ 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 libaom-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 && \ cd /tmp && \
curl -fsSLO https://github.com/libvips/libvips/releases/download/v${LIBVIPS_VERSION}/vips-${LIBVIPS_VERSION}.tar.gz && \ curl -fsSLO https://github.com/libvips/libvips/releases/download/v${LIBVIPS_VERSION}/vips-${LIBVIPS_VERSION}.tar.gz && \
tar zvxf vips-${LIBVIPS_VERSION}.tar.gz && \ tar zvxf vips-${LIBVIPS_VERSION}.tar.gz && \
@ -32,6 +44,8 @@ RUN DEBIAN_FRONTEND=noninteractive \
make install && \ make install && \
ldconfig ldconfig
ENV LD_LIBRARY_PATH="/usr/local/lib:$LD_LIBRARY_PATH"
# Install runtime dependencies # Install runtime dependencies
# RUN DEBIAN_FRONTEND=noninteractive \ # RUN DEBIAN_FRONTEND=noninteractive \
# apt-get update && \ # apt-get update && \

View file

@ -2,7 +2,7 @@
Small [Go](http://golang.org) package for fast high-level image processing using [libvips](https://github.com/jcupitt/libvips) via C bindings, providing a simple [programmatic API](#examples). Small [Go](http://golang.org) package for fast high-level image processing using [libvips](https://github.com/jcupitt/libvips) via C bindings, providing a simple [programmatic API](#examples).
bimg was designed to be a small and efficient library supporting common [image operations](#supported-image-operations) such as crop, resize, rotate, zoom or watermark. It can read JPEG, PNG, WEBP natively, and optionally TIFF, PDF, GIF and SVG formats if `libvips@8.3+` is compiled with proper library bindings. bimg was designed to be a small and efficient library supporting common [image operations](#supported-image-operations) such as crop, resize, rotate, zoom or watermark. It can read JPEG, PNG, WEBP natively, and optionally TIFF, PDF, GIF and SVG formats if `libvips@8.3+` is compiled with proper library bindings. Lastly AVIF is supported as of `libvips@8.9+`. For AVIF support `libheif` needs to be [compiled with an applicable AVIF en-/decoder](https://github.com/strukturag/libheif#compiling).
bimg is able to output images as JPEG, PNG and WEBP formats, including transparent conversion across them. bimg is able to output images as JPEG, PNG and WEBP formats, including transparent conversion across them.
@ -53,7 +53,9 @@ If you're using `gopkg.in`, you can still rely in the `v0` without worrying abou
- C compatible compiler such as gcc 4.6+ or clang 3.0+ - C compatible compiler such as gcc 4.6+ or clang 3.0+
- Go 1.3+ - Go 1.3+
**Note**: `libvips` v8.3+ is required for GIF, PDF and SVG support. **Note**:
* `libvips` v8.3+ is required for GIF, PDF and SVG support.
* `libvips` v8.9+ is required for AVIF support. `libheif` compiled with a AVIF en-/decoder also needs to be present.
## Installation ## Installation

View file

@ -352,8 +352,8 @@ func TestImageAutoRotate(t *testing.T) {
} }
tests := []struct { tests := []struct {
file string file string
orientation int orientation int
}{ }{
{"exif/Landscape_1.jpg", 1}, {"exif/Landscape_1.jpg", 1},
{"exif/Landscape_2.jpg", 1}, {"exif/Landscape_2.jpg", 1},

View file

@ -42,6 +42,7 @@ func TestMetadata(t *testing.T) {
{"test_icc_prophoto.jpg", "jpeg", 0, false, true, "srgb"}, {"test_icc_prophoto.jpg", "jpeg", 0, false, true, "srgb"},
{"test.png", "png", 0, true, false, "srgb"}, {"test.png", "png", 0, true, false, "srgb"},
{"test.webp", "webp", 0, false, false, "srgb"}, {"test.webp", "webp", 0, false, false, "srgb"},
{"test.avif", "avif", 0, false, false, "srgb"},
} }
for _, file := range files { for _, file := range files {

View file

@ -226,6 +226,8 @@ type Options struct {
OutputICC string OutputICC string
InputICC string InputICC string
Palette bool Palette bool
// Speed defines the AVIF encoders CPU effort. Valid values are 0-8.
Speed int
// private fields // private fields
autoRotateOnly bool autoRotateOnly bool

View file

@ -31,7 +31,7 @@ func resizer(buf []byte, o Options) ([]byte, error) {
o = applyDefaults(o, imageType) o = applyDefaults(o, imageType)
// Ensure supported type // Ensure supported type
if !IsTypeSupported(o.Type) { if !IsTypeSupportedSave(o.Type) {
return nil, errors.New("Unsupported image output type") return nil, errors.New("Unsupported image output type")
} }
@ -51,7 +51,7 @@ func resizer(buf []byte, o Options) ([]byte, error) {
} }
// If JPEG or HEIF image, retrieve the buffer // 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) buf, err = getImageBuffer(image)
if err != nil { if err != nil {
return nil, err return nil, err
@ -187,6 +187,7 @@ func saveImage(image *C.VipsImage, o Options) ([]byte, error) {
StripMetadata: o.StripMetadata, StripMetadata: o.StripMetadata,
Lossless: o.Lossless, Lossless: o.Lossless,
Palette: o.Palette, Palette: o.Palette,
Speed: o.Speed,
} }
// Finally get the resultant buffer // Finally get the resultant buffer
return vipsSave(image, saveOptions) return vipsSave(image, saveOptions)

BIN
testdata/test.avif vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 253 KiB

View file

@ -30,6 +30,8 @@ const (
MAGICK MAGICK
// HEIF represents the HEIC/HEIF/HVEC image type // HEIF represents the HEIC/HEIF/HVEC image type
HEIF HEIF
// AVIF represents the AVIF image type.
AVIF
) )
var ( var (
@ -48,6 +50,7 @@ var ImageTypes = map[ImageType]string{
SVG: "svg", SVG: "svg",
MAGICK: "magick", MAGICK: "magick",
HEIF: "heif", HEIF: "heif",
AVIF: "avif",
} }
// imageMutex is used to provide thread-safe synchronization // imageMutex is used to provide thread-safe synchronization

View file

@ -22,6 +22,7 @@ func TestDeterminateImageType(t *testing.T) {
{"test.heic", HEIF}, {"test.heic", HEIF},
{"test2.heic", HEIF}, {"test2.heic", HEIF},
{"test3.heic", HEIF}, {"test3.heic", HEIF},
{"test.avif", AVIF},
} }
for _, file := range files { for _, file := range files {
@ -51,12 +52,16 @@ func TestDeterminateImageTypeName(t *testing.T) {
{"test.svg", "svg"}, {"test.svg", "svg"},
// {"test.jp2", "magick"}, // {"test.jp2", "magick"},
{"test.heic", "heif"}, {"test.heic", "heif"},
{"test.avif", "avif"},
} }
for _, file := range files { for _, file := range files {
if file.expected == "heif" && VipsMajorVersion <= 8 && VipsMinorVersion < 8 { if file.expected == "heif" && VipsMajorVersion <= 8 && VipsMinorVersion < 8 {
continue continue
} }
if file.expected == "avif" && VipsMajorVersion <= 8 && VipsMinorVersion < 9 {
continue
}
img, _ := os.Open(path.Join("testdata", file.name)) img, _ := os.Open(path.Join("testdata", file.name))
buf, _ := ioutil.ReadAll(img) buf, _ := ioutil.ReadAll(img)
@ -73,13 +78,16 @@ func TestIsTypeSupported(t *testing.T) {
types := []struct { types := []struct {
name ImageType name ImageType
}{ }{
{JPEG}, {PNG}, {WEBP}, {GIF}, {PDF}, {HEIF}, {JPEG}, {PNG}, {WEBP}, {GIF}, {PDF}, {HEIF}, {AVIF},
} }
for _, n := range types { for _, n := range types {
if n.name == HEIF && VipsMajorVersion <= 8 && VipsMinorVersion < 8 { if n.name == HEIF && VipsMajorVersion <= 8 && VipsMinorVersion < 8 {
continue continue
} }
if n.name == AVIF && VipsMajorVersion <= 8 && VipsMinorVersion < 9 {
continue
}
if IsTypeSupported(n.name) == false { if IsTypeSupported(n.name) == false {
t.Fatalf("Image type %s is not valid", ImageTypes[n.name]) t.Fatalf("Image type %s is not valid", ImageTypes[n.name])
} }
@ -97,12 +105,16 @@ func TestIsTypeNameSupported(t *testing.T) {
{"gif", true}, {"gif", true},
{"pdf", true}, {"pdf", true},
{"heif", true}, {"heif", true},
{"avif", true},
} }
for _, n := range types { for _, n := range types {
if n.name == "heif" && VipsMajorVersion <= 8 && VipsMinorVersion < 8 { if n.name == "heif" && VipsMajorVersion <= 8 && VipsMinorVersion < 8 {
continue continue
} }
if n.name == "avif" && VipsMajorVersion <= 8 && VipsMinorVersion < 9 {
continue
}
if IsTypeNameSupported(n.name) != n.expected { if IsTypeNameSupported(n.name) != n.expected {
t.Fatalf("Image type %s is not valid", n.name) t.Fatalf("Image type %s is not valid", n.name)
} }
@ -121,6 +133,9 @@ func TestIsTypeSupportedSave(t *testing.T) {
if VipsVersion >= "8.8.0" { if VipsVersion >= "8.8.0" {
types = append(types, struct{ name ImageType }{HEIF}) types = append(types, struct{ name ImageType }{HEIF})
} }
if VipsVersion >= "8.9.0" {
types = append(types, struct{ name ImageType }{AVIF})
}
for _, n := range types { for _, n := range types {
if IsTypeSupportedSave(n.name) == false { if IsTypeSupportedSave(n.name) == false {
@ -141,6 +156,7 @@ func TestIsTypeNameSupportedSave(t *testing.T) {
{"pdf", false}, {"pdf", false},
{"tiff", VipsVersion >= "8.5.0"}, {"tiff", VipsVersion >= "8.5.0"},
{"heif", VipsVersion >= "8.8.0"}, {"heif", VipsVersion >= "8.8.0"},
{"avif", VipsVersion >= "8.9.0"},
} }
for _, n := range types { for _, n := range types {

14
vips.go
View file

@ -45,6 +45,7 @@ type VipsMemoryInfo struct {
// vipsSaveOptions represents the internal option used to talk with libvips. // vipsSaveOptions represents the internal option used to talk with libvips.
type vipsSaveOptions struct { type vipsSaveOptions struct {
Speed int
Quality int Quality int
Compression int Compression int
Type ImageType Type ImageType
@ -190,6 +191,9 @@ func VipsIsTypeSupported(t ImageType) bool {
if t == HEIF { if t == HEIF {
return int(C.vips_type_find_bridge(C.HEIF)) != 0 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 return false
} }
@ -212,6 +216,9 @@ func VipsIsTypeSupportedSave(t ImageType) bool {
if t == HEIF { if t == HEIF {
return int(C.vips_type_find_save_bridge(C.HEIF)) != 0 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 return false
} }
@ -487,6 +494,7 @@ func vipsSave(image *C.VipsImage, o vipsSaveOptions) ([]byte, error) {
strip := C.int(boolToInt(o.StripMetadata)) strip := C.int(boolToInt(o.StripMetadata))
lossless := C.int(boolToInt(o.Lossless)) lossless := C.int(boolToInt(o.Lossless))
palette := C.int(boolToInt(o.Palette)) palette := C.int(boolToInt(o.Palette))
speed := C.int(o.Speed)
if o.Type != 0 && !IsTypeSupportedSave(o.Type) { if o.Type != 0 && !IsTypeSupportedSave(o.Type) {
return nil, fmt.Errorf("VIPS cannot save to %#v", ImageTypes[o.Type]) return nil, fmt.Errorf("VIPS cannot save to %#v", ImageTypes[o.Type])
@ -501,6 +509,8 @@ func vipsSave(image *C.VipsImage, o vipsSaveOptions) ([]byte, error) {
saveErr = C.vips_tiffsave_bridge(tmpImage, &ptr, &length) saveErr = C.vips_tiffsave_bridge(tmpImage, &ptr, &length)
case HEIF: case HEIF:
saveErr = C.vips_heifsave_bridge(tmpImage, &ptr, &length, strip, quality, lossless) saveErr = C.vips_heifsave_bridge(tmpImage, &ptr, &length, strip, quality, lossless)
case AVIF:
saveErr = C.vips_avifsave_bridge(tmpImage, &ptr, &length, strip, quality, lossless, speed)
default: default:
saveErr = C.vips_jpegsave_bridge(tmpImage, &ptr, &length, strip, quality, interlace) saveErr = C.vips_jpegsave_bridge(tmpImage, &ptr, &length, strip, quality, interlace)
} }
@ -735,6 +745,10 @@ func vipsImageType(buf []byte) ImageType {
// This is a HEIFS file, ftyphevc // This is a HEIFS file, ftyphevc
return HEIF 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 return UNKNOWN
} }

31
vips.h
View file

@ -34,6 +34,7 @@ enum types {
SVG, SVG,
MAGICK, MAGICK,
HEIF, HEIF,
AVIF
}; };
typedef struct { typedef struct {
@ -335,7 +336,7 @@ vips_pngsave_bridge(VipsImage *in, void **buf, size_t *len, int strip, int compr
"compression", compression, "compression", compression,
"interlace", INT_TO_GBOOLEAN(interlace), "interlace", INT_TO_GBOOLEAN(interlace),
"filter", VIPS_FOREIGN_PNG_FILTER_ALL, "filter", VIPS_FOREIGN_PNG_FILTER_ALL,
"palette", INT_TO_GBOOLEAN(palette), "palette", INT_TO_GBOOLEAN(palette),
NULL NULL
); );
#else #else
@ -367,6 +368,30 @@ vips_tiffsave_bridge(VipsImage *in, void **buf, size_t *len) {
#endif #endif
} }
int
vips_avifsave_bridge(VipsImage *in, void **buf, size_t *len, int strip, int quality, int lossless, int speed) {
#if (VIPS_MAJOR_VERSION > 8 || (VIPS_MAJOR_VERSION >= 8 && VIPS_MINOR_VERSION > 10) || (VIPS_MAJOR_VERSION >= 8 && VIPS_MINOR_VERSION >= 10 && VIPS_MICRO_VERSION >= 2))
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,
"speed", speed,
NULL
);
#elif (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 int
vips_heifsave_bridge(VipsImage *in, void **buf, size_t *len, int strip, int quality, int lossless) { 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)) #if (VIPS_MAJOR_VERSION > 8 || (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION >= 8))
@ -431,6 +456,10 @@ vips_init_image (void *buf, size_t len, int imageType, VipsImage **out) {
#if (VIPS_MAJOR_VERSION > 8 || (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION >= 8)) #if (VIPS_MAJOR_VERSION > 8 || (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION >= 8))
} else if (imageType == HEIF) { } else if (imageType == HEIF) {
code = vips_heifload_buffer(buf, len, out, "access", VIPS_ACCESS_RANDOM, NULL); code = vips_heifload_buffer(buf, len, out, "access", VIPS_ACCESS_RANDOM, NULL);
#endif
#if (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION >= 9)
} else if (imageType == AVIF) {
code = vips_heifload_buffer(buf, len, out, "access", VIPS_ACCESS_RANDOM, NULL);
#endif #endif
} }

View file

@ -58,6 +58,22 @@ func TestVipsSaveTiff(t *testing.T) {
} }
} }
func TestVipsSaveAvif(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, Speed: 8}
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) { func TestVipsRotate(t *testing.T) {
files := []struct { files := []struct {
name string name string