1
0
mirror of https://github.com/imgproxy/imgproxy.git synced 2025-04-07 06:50:09 +02:00

Add AVIF support

This commit is contained in:
DarthSim 2020-09-16 16:15:01 +06:00
parent bdbb496c18
commit 1bd789df4e
9 changed files with 239 additions and 199 deletions

View File

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

View File

@ -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>"

View File

@ -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\"",
}

View File

@ -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
imagemeta/heif.go Normal file
View File

@ -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)
}

View File

@ -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
vips.c
View File

@ -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

View File

@ -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:

3
vips.h
View File

@ -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);