Browse Source

Add AVIF support

DarthSim 4 years ago
parent
commit
1bd789df4e
9 changed files with 239 additions and 199 deletions
  1. 2 0
      CHANGELOG.md
  2. 1 1
      docker/Dockerfile
  3. 4 0
      image_type.go
  4. 0 195
      imagemeta/heic.go
  5. 212 0
      imagemeta/heif.go
  6. 1 1
      process.go
  7. 14 0
      vips.c
  8. 3 1
      vips.go
  9. 2 1
      vips.h

+ 2 - 0
CHANGELOG.md

@@ -1,6 +1,8 @@
 # Changelog
 
 ## [Unreleased]
+### Added
+- AVIF support.
 
 ## [2.15.0] - 2020-09-03
 ### Added

+ 1 - 1
docker/Dockerfile

@@ -1,4 +1,4 @@
-ARG BASE_IMAGE_VERSION="v1.0.4"
+ARG BASE_IMAGE_VERSION="v1.2.0"
 
 FROM darthsim/imgproxy-base:${BASE_IMAGE_VERSION}
 LABEL maintainer="Sergey Alexandrovich <darthsim@gmail.com>"

+ 4 - 0
image_type.go

@@ -24,6 +24,7 @@ const (
 	imageTypeICO     = imageType(C.ICO)
 	imageTypeSVG     = imageType(C.SVG)
 	imageTypeHEIC    = imageType(C.HEIC)
+	imageTypeAVIF    = imageType(C.AVIF)
 	imageTypeBMP     = imageType(C.BMP)
 	imageTypeTIFF    = imageType(C.TIFF)
 
@@ -40,6 +41,7 @@ var (
 		"ico":  imageTypeICO,
 		"svg":  imageTypeSVG,
 		"heic": imageTypeHEIC,
+		"avif": imageTypeAVIF,
 		"bmp":  imageTypeBMP,
 		"tiff": imageTypeTIFF,
 	}
@@ -52,6 +54,7 @@ var (
 		imageTypeICO:  "image/x-icon",
 		imageTypeSVG:  "image/svg+xml",
 		imageTypeHEIC: "image/heif",
+		imageTypeAVIF: "image/avif",
 		imageTypeBMP:  "image/bmp",
 		imageTypeTIFF: "image/tiff",
 	}
@@ -64,6 +67,7 @@ var (
 		imageTypeICO:  "inline; filename=\"%s.ico\"",
 		imageTypeSVG:  "inline; filename=\"%s.svg\"",
 		imageTypeHEIC: "inline; filename=\"%s.heic\"",
+		imageTypeAVIF: "inline; filename=\"%s.avif\"",
 		imageTypeBMP:  "inline; filename=\"%s.bmp\"",
 		imageTypeTIFF: "inline; filename=\"%s.tiff\"",
 	}

+ 0 - 195
imagemeta/heic.go

@@ -1,195 +0,0 @@
-package imagemeta
-
-import (
-	"bytes"
-	"encoding/binary"
-	"errors"
-	"fmt"
-	"io"
-)
-
-const heicBoxHeaderSize = int64(8)
-
-var heicBrand = []byte("heic")
-var heicPict = []byte("pict")
-
-type heicDimensionsData struct {
-	Width, Height int64
-}
-
-func (d *heicDimensionsData) IsFilled() bool {
-	return d.Width > 0 && d.Height > 0
-}
-
-func heicReadBoxHeader(r io.Reader) (boxType string, boxDataSize int64, err error) {
-	b := make([]byte, heicBoxHeaderSize)
-	_, err = io.ReadFull(r, b)
-	if err != nil {
-		return
-	}
-
-	boxDataSize = int64(binary.BigEndian.Uint32(b[0:4])) - heicBoxHeaderSize
-	boxType = string(b[4:8])
-
-	return
-}
-
-func heicReadBoxData(r io.Reader, boxDataSize int64) (b []byte, err error) {
-	b = make([]byte, boxDataSize)
-	_, err = io.ReadFull(r, b)
-	return
-}
-
-func heicReadFtyp(r io.Reader, boxDataSize int64) error {
-	if boxDataSize < 8 {
-		return errors.New("Invalid ftyp data")
-	}
-
-	data, err := heicReadBoxData(r, boxDataSize)
-	if err != nil {
-		return err
-	}
-
-	if bytes.Equal(data[0:4], heicBrand) {
-		return nil
-	}
-
-	if boxDataSize >= 12 {
-		for i := int64(8); i < boxDataSize; i += 4 {
-			if bytes.Equal(data[i:i+4], heicBrand) {
-				return nil
-			}
-		}
-	}
-
-	return errors.New("Image is not compatible with heic")
-}
-
-func heicReadMeta(d *heicDimensionsData, r io.Reader, boxDataSize int64) error {
-	if boxDataSize < 4 {
-		return errors.New("Invalid meta data")
-	}
-
-	if _, err := io.ReadFull(r, make([]byte, 4)); err != nil {
-		return err
-	}
-
-	if boxDataSize > 4 {
-		if err := heicReadBoxes(d, io.LimitReader(r, boxDataSize-4)); err != nil && err != io.EOF {
-			return err
-		}
-	}
-
-	return nil
-}
-
-func heicReadHldr(r io.Reader, boxDataSize int64) error {
-	if boxDataSize < 12 {
-		return errors.New("Invalid hdlr data")
-	}
-
-	data, err := heicReadBoxData(r, boxDataSize)
-	if err != nil {
-		return err
-	}
-
-	if !bytes.Equal(data[8:12], heicPict) {
-		return fmt.Errorf("Invalid handler. Expected: pict, actual: %s", data[8:12])
-	}
-
-	return nil
-}
-
-func heicReadIspe(r io.Reader, boxDataSize int64) (w, h int64, err error) {
-	if boxDataSize < 12 {
-		return 0, 0, errors.New("Invalid ispe data")
-	}
-
-	data, err := heicReadBoxData(r, boxDataSize)
-	if err != nil {
-		return 0, 0, err
-	}
-
-	w = int64(binary.BigEndian.Uint32(data[4:8]))
-	h = int64(binary.BigEndian.Uint32(data[8:12]))
-
-	return
-}
-
-func heicReadBoxes(d *heicDimensionsData, r io.Reader) error {
-	for {
-		boxType, boxDataSize, err := heicReadBoxHeader(r)
-
-		if err != nil {
-			return err
-		}
-
-		if boxDataSize < 0 {
-			return errors.New("Invalid box data")
-		}
-
-		// log.Printf("Box type: %s; Box data size: %d", boxType, boxDataSize)
-
-		switch boxType {
-		case "ftyp":
-			if err := heicReadFtyp(r, boxDataSize); err != nil {
-				return err
-			}
-		case "meta":
-			if err := heicReadMeta(d, r, boxDataSize); err != nil {
-				return err
-			}
-			if !d.IsFilled() {
-				return errors.New("Dimensions data wasn't found in meta box")
-			}
-			return nil
-		case "hdlr":
-			if err := heicReadHldr(r, boxDataSize); err != nil {
-				return nil
-			}
-		case "iprp", "ipco":
-			if err := heicReadBoxes(d, io.LimitReader(r, boxDataSize)); err != nil && err != io.EOF {
-				return err
-			}
-		case "ispe":
-			w, h, err := heicReadIspe(r, boxDataSize)
-			if err != nil {
-				return err
-			}
-			if w > d.Width || h > d.Height {
-				d.Width, d.Height = w, h
-			}
-		case "mdat":
-			return errors.New("mdat box occurred before meta box")
-		default:
-			if _, err := heicReadBoxData(r, boxDataSize); err != nil {
-				return err
-			}
-		}
-	}
-}
-
-func DecodeHeicMeta(r io.Reader) (Meta, error) {
-	d := new(heicDimensionsData)
-
-	if err := heicReadBoxes(d, r); err != nil && !d.IsFilled() {
-		return nil, err
-	}
-
-	return &meta{
-		format: "heic",
-		width:  int(d.Width),
-		height: int(d.Height),
-	}, nil
-}
-
-func init() {
-	RegisterFormat("????ftypheic", DecodeHeicMeta)
-	RegisterFormat("????ftypheix", DecodeHeicMeta)
-	RegisterFormat("????ftyphevc", DecodeHeicMeta)
-	RegisterFormat("????ftypheim", DecodeHeicMeta)
-	RegisterFormat("????ftypheis", DecodeHeicMeta)
-	RegisterFormat("????ftyphevm", DecodeHeicMeta)
-	RegisterFormat("????ftyphevs", DecodeHeicMeta)
-	RegisterFormat("????ftypmif1", DecodeHeicMeta)
-}

+ 212 - 0
imagemeta/heif.go

@@ -0,0 +1,212 @@
+package imagemeta
+
+import (
+	"bytes"
+	"encoding/binary"
+	"errors"
+	"fmt"
+	"io"
+)
+
+const heifBoxHeaderSize = int64(8)
+
+var heicBrand = []byte("heic")
+var avifBrand = []byte("avif")
+var heifPict = []byte("pict")
+
+type heifData struct {
+	Format        string
+	Width, Height int64
+}
+
+func (d *heifData) IsFilled() bool {
+	return len(d.Format) > 0 && d.Width > 0 && d.Height > 0
+}
+
+func heifReadBoxHeader(r io.Reader) (boxType string, boxDataSize int64, err error) {
+	b := make([]byte, heifBoxHeaderSize)
+	_, err = io.ReadFull(r, b)
+	if err != nil {
+		return
+	}
+
+	boxDataSize = int64(binary.BigEndian.Uint32(b[0:4])) - heifBoxHeaderSize
+	boxType = string(b[4:8])
+
+	return
+}
+
+func heifReadBoxData(r io.Reader, boxDataSize int64) (b []byte, err error) {
+	b = make([]byte, boxDataSize)
+	_, err = io.ReadFull(r, b)
+	return
+}
+
+func heifAssignFormat(d *heifData, brand []byte) bool {
+	if bytes.Equal(brand, heicBrand) {
+		d.Format = "heic"
+		return true
+	}
+
+	if bytes.Equal(brand, avifBrand) {
+		d.Format = "avif"
+		return true
+	}
+
+	return false
+}
+
+func heifReadFtyp(d *heifData, r io.Reader, boxDataSize int64) error {
+	if boxDataSize < 8 {
+		return errors.New("Invalid ftyp data")
+	}
+
+	data, err := heifReadBoxData(r, boxDataSize)
+	if err != nil {
+		return err
+	}
+
+	if heifAssignFormat(d, data[0:4]) {
+		return nil
+	}
+
+	if boxDataSize >= 12 {
+		for i := int64(8); i < boxDataSize; i += 4 {
+			if heifAssignFormat(d, data[i:i+4]) {
+				return nil
+			}
+		}
+	}
+
+	return errors.New("Image is not compatible with heic/avif")
+}
+
+func heifReadMeta(d *heifData, r io.Reader, boxDataSize int64) error {
+	if boxDataSize < 4 {
+		return errors.New("Invalid meta data")
+	}
+
+	if _, err := io.ReadFull(r, make([]byte, 4)); err != nil {
+		return err
+	}
+
+	if boxDataSize > 4 {
+		if err := heifReadBoxes(d, io.LimitReader(r, boxDataSize-4)); err != nil && err != io.EOF {
+			return err
+		}
+	}
+
+	return nil
+}
+
+func heifReadHldr(r io.Reader, boxDataSize int64) error {
+	if boxDataSize < 12 {
+		return errors.New("Invalid hdlr data")
+	}
+
+	data, err := heifReadBoxData(r, boxDataSize)
+	if err != nil {
+		return err
+	}
+
+	if !bytes.Equal(data[8:12], heifPict) {
+		return fmt.Errorf("Invalid handler. Expected: pict, actual: %s", data[8:12])
+	}
+
+	return nil
+}
+
+func heifReadIspe(r io.Reader, boxDataSize int64) (w, h int64, err error) {
+	if boxDataSize < 12 {
+		return 0, 0, errors.New("Invalid ispe data")
+	}
+
+	data, err := heifReadBoxData(r, boxDataSize)
+	if err != nil {
+		return 0, 0, err
+	}
+
+	w = int64(binary.BigEndian.Uint32(data[4:8]))
+	h = int64(binary.BigEndian.Uint32(data[8:12]))
+
+	return
+}
+
+func heifReadBoxes(d *heifData, r io.Reader) error {
+	for {
+		boxType, boxDataSize, err := heifReadBoxHeader(r)
+
+		if err != nil {
+			return err
+		}
+
+		if boxDataSize < 0 {
+			return errors.New("Invalid box data")
+		}
+
+		// log.Printf("Box type: %s; Box data size: %d", boxType, boxDataSize)
+
+		switch boxType {
+		case "ftyp":
+			if err := heifReadFtyp(d, r, boxDataSize); err != nil {
+				return err
+			}
+		case "meta":
+			if err := heifReadMeta(d, r, boxDataSize); err != nil {
+				return err
+			}
+			if !d.IsFilled() {
+				return errors.New("Dimensions data wasn't found in meta box")
+			}
+			return nil
+		case "hdlr":
+			if err := heifReadHldr(r, boxDataSize); err != nil {
+				return nil
+			}
+		case "iprp", "ipco":
+			if err := heifReadBoxes(d, io.LimitReader(r, boxDataSize)); err != nil && err != io.EOF {
+				return err
+			}
+		case "ispe":
+			w, h, err := heifReadIspe(r, boxDataSize)
+			if err != nil {
+				return err
+			}
+			if w > d.Width || h > d.Height {
+				d.Width, d.Height = w, h
+			}
+		case "mdat":
+			return errors.New("mdat box occurred before meta box")
+		default:
+			if _, err := heifReadBoxData(r, boxDataSize); err != nil {
+				return err
+			}
+		}
+	}
+}
+
+func DecodeHeifMeta(r io.Reader) (Meta, error) {
+	d := new(heifData)
+
+	if err := heifReadBoxes(d, r); err != nil && !d.IsFilled() {
+		return nil, err
+	}
+
+	return &meta{
+		format: d.Format,
+		width:  int(d.Width),
+		height: int(d.Height),
+	}, nil
+}
+
+func init() {
+	RegisterFormat("????ftypheic", DecodeHeifMeta)
+	RegisterFormat("????ftypheix", DecodeHeifMeta)
+	RegisterFormat("????ftyphevc", DecodeHeifMeta)
+	RegisterFormat("????ftypheim", DecodeHeifMeta)
+	RegisterFormat("????ftypheis", DecodeHeifMeta)
+	RegisterFormat("????ftyphevm", DecodeHeifMeta)
+	RegisterFormat("????ftyphevs", DecodeHeifMeta)
+	RegisterFormat("????ftypmif1", DecodeHeifMeta)
+	RegisterFormat("????ftypavif", DecodeHeifMeta)
+}

+ 1 - 1
process.go

@@ -138,7 +138,7 @@ func canScaleOnLoad(imgtype imageType, scale float64) bool {
 
 func canFitToBytes(imgtype imageType) bool {
 	switch imgtype {
-	case imageTypeJPEG, imageTypeWEBP, imageTypeHEIC, imageTypeTIFF:
+	case imageTypeJPEG, imageTypeWEBP, imageTypeAVIF, imageTypeTIFF:
 		return true
 	default:
 		return false

+ 14 - 0
vips.c

@@ -85,6 +85,8 @@ vips_type_find_load_go(int imgtype) {
     return vips_type_find("VipsOperation", "svgload_buffer");
   case (HEIC):
     return vips_type_find("VipsOperation", "heifload_buffer");
+  case (AVIF):
+    return vips_type_find("VipsOperation", "heifload_buffer");
   case (BMP):
     return vips_type_find("VipsOperation", "magickload_buffer");
   case (TIFF):
@@ -105,6 +107,8 @@ vips_type_find_save_go(int imgtype) {
     return vips_type_find("VipsOperation", "webpsave_buffer");
   case (GIF):
     return vips_type_find("VipsOperation", "magicksave_buffer");
+  case (AVIF):
+    return vips_type_find("VipsOperation", "heifsave_buffer");
   case (ICO):
     return vips_type_find("VipsOperation", "pngsave_buffer");
   case (BMP):
@@ -600,6 +604,16 @@ vips_tiffsave_go(VipsImage *in, void **buf, size_t *len, int quality) {
 #endif
 }
 
+int
+vips_avifsave_go(VipsImage *in, void **buf, size_t *len, int quality) {
+#if VIPS_SUPPORT_HEIF
+  return vips_heifsave_buffer(in, buf, len, "Q", quality, "compression", VIPS_FOREIGN_HEIF_COMPRESSION_AV1, NULL);
+#else
+  vips_error("vips_avifsave_go", "Saving AVIF is not supported (libvips 8.6+ reuired)");
+  return 1;
+#endif
+}
+
 int
 vips_bmpsave_go(VipsImage *in, void **buf, size_t *len) {
 #if VIPS_SUPPORT_MAGICK

+ 3 - 1
vips.go

@@ -164,7 +164,7 @@ func (img *vipsImage) Load(data []byte, imgtype imageType, shrink int, scale flo
 		err = C.vips_gifload_go(unsafe.Pointer(&data[0]), C.size_t(len(data)), C.int(pages), &tmp)
 	case imageTypeSVG:
 		err = C.vips_svgload_go(unsafe.Pointer(&data[0]), C.size_t(len(data)), C.double(scale), &tmp)
-	case imageTypeHEIC:
+	case imageTypeHEIC, imageTypeAVIF:
 		err = C.vips_heifload_go(unsafe.Pointer(&data[0]), C.size_t(len(data)), &tmp)
 	case imageTypeBMP:
 		err = C.vips_bmpload_go(unsafe.Pointer(&data[0]), C.size_t(len(data)), &tmp)
@@ -205,6 +205,8 @@ func (img *vipsImage) Save(imgtype imageType, quality int, stripMeta bool) ([]by
 		err = C.vips_webpsave_go(img.VipsImage, &ptr, &imgsize, C.int(quality), gbool(stripMeta))
 	case imageTypeGIF:
 		err = C.vips_gifsave_go(img.VipsImage, &ptr, &imgsize)
+	case imageTypeAVIF:
+		err = C.vips_avifsave_go(img.VipsImage, &ptr, &imgsize, C.int(quality))
 	case imageTypeBMP:
 		err = C.vips_bmpsave_go(img.VipsImage, &ptr, &imgsize)
 	case imageTypeTIFF:

+ 2 - 1
vips.h

@@ -13,6 +13,7 @@ enum ImgproxyImageTypes {
   ICO,
   SVG,
   HEIC,
+  AVIF,
   BMP,
   TIFF
 };
@@ -90,7 +91,7 @@ int vips_jpegsave_go(VipsImage *in, void **buf, size_t *len, int quality, int in
 int vips_pngsave_go(VipsImage *in, void **buf, size_t *len, int interlace, int quantize, int colors);
 int vips_webpsave_go(VipsImage *in, void **buf, size_t *len, int quality, gboolean strip);
 int vips_gifsave_go(VipsImage *in, void **buf, size_t *len);
-int vips_icosave_go(VipsImage *in, void **buf, size_t *len);
+int vips_avifsave_go(VipsImage *in, void **buf, size_t *len, int quality);
 int vips_bmpsave_go(VipsImage *in, void **buf, size_t *len);
 int vips_tiffsave_go(VipsImage *in, void **buf, size_t *len, int quality);