Explorar el Código

HEIC images support

DarthSim hace 6 años
padre
commit
86c883f04b
Se han modificado 6 ficheros con 239 adiciones y 1 borrados
  1. 194 0
      heic.go
  2. 10 0
      process.go
  3. 2 0
      processing_options.go
  4. 2 0
      server.go
  5. 27 0
      vips.c
  6. 4 1
      vips.h

+ 194 - 0
heic.go

@@ -0,0 +1,194 @@
+package main
+
+import (
+	"bytes"
+	"encoding/binary"
+	"errors"
+	"fmt"
+	"image"
+	"image/color"
+	"io"
+)
+
+const heifBoxHeaderSize = int64(8)
+
+var heicBrand = []byte("heic")
+var heicPict = []byte("pict")
+
+type heifDimensionsData struct {
+	Width, Height int64
+}
+
+func (d *heifDimensionsData) IsFilled() bool {
+	return d.Width > 0 && d.Height > 0
+}
+
+func heifReadBoxHeader(r io.Reader) (boxType string, boxDataSize int64, err error) {
+	b := make([]byte, heifBoxHeaderSize)
+	_, err = r.Read(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 = r.Read(b)
+	return
+}
+
+func heifReadFtyp(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 bytes.Compare(data[0:4], heicBrand) == 0 {
+		return nil
+	}
+
+	if boxDataSize >= 12 {
+		for i := int64(8); i < boxDataSize; i += 4 {
+			if bytes.Compare(data[i:i+4], heicBrand) == 0 {
+				return nil
+			}
+		}
+	}
+
+	return errors.New("Image is not compatible with heic")
+}
+
+func heifReadMeta(d *heifDimensionsData, r io.Reader, boxDataSize int64) error {
+	if boxDataSize < 4 {
+		return errors.New("Invalid meta data")
+	}
+
+	if _, err := r.Read(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.Compare(data[8:12], heicPict) != 0 {
+		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 *heifDimensionsData, 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(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 wan'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 heifDecodeConfig(r io.Reader) (image.Config, error) {
+	d := new(heifDimensionsData)
+
+	if err := heifReadBoxes(d, r); err != nil && !d.IsFilled() {
+		return image.Config{}, err
+	}
+
+	return image.Config{
+		ColorModel: color.NRGBAModel,
+		Width:      int(d.Width),
+		Height:     int(d.Height),
+	}, nil
+}
+
+func heifDecode(r io.Reader) (image.Image, error) {
+	return image.NewRGBA(image.Rect(0, 0, 1, 1)), nil
+}
+
+func init() {
+	image.RegisterFormat("heic", "????ftyp", heifDecode, heifDecodeConfig)
+}

+ 10 - 0
process.go

@@ -83,6 +83,9 @@ func initVips() {
 	if int(C.vips_type_find_load_go(C.int(imageTypeSVG))) != 0 {
 		vipsTypeSupportLoad[imageTypeSVG] = true
 	}
+	if int(C.vips_type_find_load_go(C.int(imageTypeHEIC))) != 0 {
+		vipsTypeSupportLoad[imageTypeHEIC] = true
+	}
 
 	// we load ICO with github.com/mat/besticon/ico and send decoded data to vips
 	vipsTypeSupportLoad[imageTypeICO] = true
@@ -102,6 +105,9 @@ func initVips() {
 	if int(C.vips_type_find_save_go(C.int(imageTypeICO))) != 0 {
 		vipsTypeSupportSave[imageTypeICO] = true
 	}
+	if int(C.vips_type_find_save_go(C.int(imageTypeHEIC))) != 0 {
+		vipsTypeSupportSave[imageTypeHEIC] = true
+	}
 
 	if conf.JpegProgressive {
 		cConf.JpegProgressive = C.int(1)
@@ -688,6 +694,8 @@ func vipsLoadImage(data []byte, imgtype imageType, shrink int, scale float64, pa
 		}
 
 		img = C.vips_image_new_from_memory_copy(unsafe.Pointer(&rawData[0]), C.size_t(width*height*4), C.int(width), C.int(height), 4, C.VIPS_FORMAT_UCHAR)
+	case imageTypeHEIC:
+		err = C.vips_heifload_go(unsafe.Pointer(&data[0]), C.size_t(len(data)), &img)
 	}
 	if err != 0 {
 		return nil, vipsError()
@@ -718,6 +726,8 @@ func vipsSaveImage(img *C.VipsImage, imgtype imageType, quality int) ([]byte, co
 		err = C.vips_gifsave_go(img, &ptr, &imgsize)
 	case imageTypeICO:
 		err = C.vips_icosave_go(img, &ptr, &imgsize)
+	case imageTypeHEIC:
+		err = C.vips_heifsave_go(img, &ptr, &imgsize, C.int(quality))
 	}
 	if err != 0 {
 		C.g_free_go(&ptr)

+ 2 - 0
processing_options.go

@@ -30,6 +30,7 @@ const (
 	imageTypeGIF     = imageType(C.GIF)
 	imageTypeICO     = imageType(C.ICO)
 	imageTypeSVG     = imageType(C.SVG)
+	imageTypeHEIC    = imageType(C.HEIC)
 )
 
 type processingHeaders struct {
@@ -47,6 +48,7 @@ var imageTypes = map[string]imageType{
 	"gif":  imageTypeGIF,
 	"ico":  imageTypeICO,
 	"svg":  imageTypeSVG,
+	"heic": imageTypeHEIC,
 }
 
 type gravityType int

+ 2 - 0
server.go

@@ -26,6 +26,7 @@ var (
 		imageTypeWEBP: "image/webp",
 		imageTypeGIF:  "image/gif",
 		imageTypeICO:  "image/x-icon",
+		imageTypeHEIC: "image/heif",
 	}
 
 	contentDispositionsFmt = map[imageType]string{
@@ -34,6 +35,7 @@ var (
 		imageTypeWEBP: "inline; filename=\"%s.webp\"",
 		imageTypeGIF:  "inline; filename=\"%s.gif\"",
 		imageTypeICO:  "inline; filename=\"%s.ico\"",
+		imageTypeHEIC: "inline; filename=\"%s.heic\"",
 	}
 
 	imgproxyIsRunningMsg = []byte("imgproxy is running")

+ 27 - 0
vips.c

@@ -25,6 +25,9 @@
 #define VIPS_SUPPORT_WEBP_ANIMATION \
   (VIPS_MAJOR_VERSION > 8 || (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION >= 8))
 
+#define VIPS_SUPPORT_HEIF \
+  (VIPS_MAJOR_VERSION > 8 || (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION >= 8))
+
 #define VIPS_SUPPORT_BUILTIN_ICC \
   (VIPS_MAJOR_VERSION > 8 || (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION >= 8))
 
@@ -71,6 +74,8 @@ vips_type_find_load_go(int imgtype) {
     return vips_type_find("VipsOperation", "gifload_buffer");
   case (SVG):
     return vips_type_find("VipsOperation", "svgload_buffer");
+  case (HEIC):
+    return vips_type_find("VipsOperation", "heifload_buffer");
   }
   return 0;
 }
@@ -89,6 +94,8 @@ vips_type_find_save_go(int imgtype) {
     return vips_type_find("VipsOperation", "magicksave_buffer");
   case (ICO):
     return vips_type_find("VipsOperation", "magicksave_buffer");
+  case (HEIC):
+    return vips_type_find("VipsOperation", "heifsave_buffer");
   }
 
   return 0;
@@ -144,6 +151,16 @@ vips_svgload_go(void *buf, size_t len, double scale, VipsImage **out) {
   #endif
 }
 
+int
+vips_heifload_go(void *buf, size_t len, VipsImage **out) {
+#if VIPS_SUPPORT_HEIF
+  return vips_heifload_buffer(buf, len, out, "access", VIPS_ACCESS_SEQUENTIAL, "autorotate", 1, NULL);
+#else
+  vips_error("vips_heifload_go", "Loading HEIF is not supported");
+  return 1;
+#endif
+}
+
 int
 vips_get_exif_orientation(VipsImage *image) {
   const char *orientation;
@@ -549,6 +566,16 @@ vips_icosave_go(VipsImage *in, void **buf, size_t *len) {
 #endif
 }
 
+int
+vips_heifsave_go(VipsImage *in, void **buf, size_t *len, int quality) {
+#if VIPS_SUPPORT_HEIF
+  return vips_heifsave_buffer(in, buf, len, "Q", quality, NULL);
+#else
+  vips_error("vips_heifsave_go", "Saving HEIF is not supported");
+  return 1;
+#endif
+}
+
 void
 vips_cleanup() {
   vips_error_clear();

+ 4 - 1
vips.h

@@ -10,7 +10,8 @@ enum ImgproxyImageTypes {
   WEBP,
   GIF,
   ICO,
-  SVG
+  SVG,
+  HEIC
 };
 
 int vips_initialize();
@@ -28,6 +29,7 @@ int vips_pngload_go(void *buf, size_t len, VipsImage **out);
 int vips_webpload_go(void *buf, size_t len, double scale, int pages, VipsImage **out);
 int vips_gifload_go(void *buf, size_t len, int pages, VipsImage **out);
 int vips_svgload_go(void *buf, size_t len, double scale, VipsImage **out);
+int vips_heifload_go(void *buf, size_t len, VipsImage **out);
 
 int vips_get_exif_orientation(VipsImage *image);
 void vips_strip_meta(VipsImage *image);
@@ -80,5 +82,6 @@ int vips_pngsave_go(VipsImage *in, void **buf, size_t *len, int interlace, int q
 int vips_webpsave_go(VipsImage *in, void **buf, size_t *len, int quality);
 int vips_gifsave_go(VipsImage *in, void **buf, size_t *len);
 int vips_icosave_go(VipsImage *in, void **buf, size_t *len);
+int vips_heifsave_go(VipsImage *in, void **buf, size_t *len, int quality);
 
 void vips_cleanup();