From 1bd789df4e09ca9b45ee368b4d830e8d2d7735ee Mon Sep 17 00:00:00 2001 From: DarthSim Date: Wed, 16 Sep 2020 16:15:01 +0600 Subject: [PATCH] Add AVIF support --- CHANGELOG.md | 2 + docker/Dockerfile | 2 +- image_type.go | 4 + imagemeta/heic.go | 195 ------------------------------------------ imagemeta/heif.go | 212 ++++++++++++++++++++++++++++++++++++++++++++++ process.go | 2 +- vips.c | 14 +++ vips.go | 4 +- vips.h | 3 +- 9 files changed, 239 insertions(+), 199 deletions(-) delete mode 100644 imagemeta/heic.go create mode 100644 imagemeta/heif.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e124943..78832736 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ # Changelog ## [Unreleased] +### Added +- AVIF support. ## [2.15.0] - 2020-09-03 ### Added diff --git a/docker/Dockerfile b/docker/Dockerfile index b9b38fc9..26e2a885 100644 --- a/docker/Dockerfile +++ b/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 " diff --git a/image_type.go b/image_type.go index c5ba5610..18a1a7f1 100644 --- a/image_type.go +++ b/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\"", } diff --git a/imagemeta/heic.go b/imagemeta/heic.go deleted file mode 100644 index 80e3f789..00000000 --- a/imagemeta/heic.go +++ /dev/null @@ -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) -} diff --git a/imagemeta/heif.go b/imagemeta/heif.go new file mode 100644 index 00000000..ce4ef313 --- /dev/null +++ b/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) +} diff --git a/process.go b/process.go index d9eeb936..95844036 100644 --- a/process.go +++ b/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 diff --git a/vips.c b/vips.c index b378bc0a..f56471ae 100644 --- a/vips.c +++ b/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 diff --git a/vips.go b/vips.go index a1c85767..34d13448 100644 --- a/vips.go +++ b/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: diff --git a/vips.h b/vips.h index 5aa51204..c4553d95 100644 --- a/vips.h +++ b/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);