Selaa lähdekoodia

Extract Vips stuff into vips.go and CString stuff into c_strings.go

DarthSim 6 vuotta sitten
vanhempi
commit
fa224981af
3 muutettua tiedostoa jossa 763 lisäystä ja 711 poistoa
  1. 16 0
      c_strings.go
  2. 70 711
      process.go
  3. 677 0
      vips.go

+ 16 - 0
c_strings.go

@@ -0,0 +1,16 @@
+package main
+
+import "C"
+
+var cStringsCache = make(map[string]*C.char)
+
+func cachedCString(str string) *C.char {
+	if cstr, ok := cStringsCache[str]; ok {
+		return cstr
+	}
+
+	cstr := C.CString(str)
+	cStringsCache[str] = cstr
+
+	return cstr
+}

+ 70 - 711
process.go

@@ -1,185 +1,33 @@
 package main
 package main
 
 
-/*
-#cgo pkg-config: vips
-#cgo LDFLAGS: -s -w
-#cgo CFLAGS: -O3
-#include "vips.h"
-*/
-import "C"
-
 import (
 import (
 	"context"
 	"context"
-	"errors"
 	"math"
 	"math"
-	"os"
 	"runtime"
 	"runtime"
-	"time"
-	"unsafe"
 
 
 	"golang.org/x/sync/errgroup"
 	"golang.org/x/sync/errgroup"
 )
 )
 
 
-var (
-	vipsSupportSmartcrop bool
-	vipsTypeSupportLoad  = make(map[imageType]bool)
-	vipsTypeSupportSave  = make(map[imageType]bool)
-
-	watermark *C.VipsImage
-
-	errSmartCropNotSupported = errors.New("Smart crop is not supported by used version of libvips")
-)
-
-type cConfig struct {
-	JpegProgressive       C.int
-	PngInterlaced         C.int
-	PngQuantize           C.int
-	PngQuantizationColors C.int
-	WatermarkOpacity      C.double
-}
-
-var cConf cConfig
-
-var cstrings = make(map[string]*C.char)
-
-func initVips() {
-	runtime.LockOSThread()
-	defer runtime.UnlockOSThread()
-
-	if err := C.vips_initialize(); err != 0 {
-		C.vips_shutdown()
-		logFatal("unable to start vips!")
-	}
-
-	// Disable libvips cache. Since processing pipeline is fine tuned, we won't get much profit from it.
-	// Enabled cache can cause SIGSEGV on Musl-based systems like Alpine.
-	C.vips_cache_set_max_mem(0)
-	C.vips_cache_set_max(0)
-
-	C.vips_concurrency_set(1)
-
-	if len(os.Getenv("IMGPROXY_VIPS_LEAK_CHECK")) > 0 {
-		C.vips_leak_set(C.gboolean(1))
-	}
-
-	if len(os.Getenv("IMGPROXY_VIPS_CACHE_TRACE")) > 0 {
-		C.vips_cache_set_trace(C.gboolean(1))
-	}
-
-	vipsSupportSmartcrop = C.vips_support_smartcrop() == 1
-
-	if int(C.vips_type_find_load_go(C.int(imageTypeJPEG))) != 0 {
-		vipsTypeSupportLoad[imageTypeJPEG] = true
-	}
-	if int(C.vips_type_find_load_go(C.int(imageTypePNG))) != 0 {
-		vipsTypeSupportLoad[imageTypePNG] = true
-	}
-	if int(C.vips_type_find_load_go(C.int(imageTypeWEBP))) != 0 {
-		vipsTypeSupportLoad[imageTypeWEBP] = true
-	}
-	if int(C.vips_type_find_load_go(C.int(imageTypeGIF))) != 0 {
-		vipsTypeSupportLoad[imageTypeGIF] = true
-	}
-	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
+func extractMeta(img *vipsImage) (int, int, int, bool) {
+	width := img.Width()
+	height := img.Height()
 
 
-	if int(C.vips_type_find_save_go(C.int(imageTypeJPEG))) != 0 {
-		vipsTypeSupportSave[imageTypeJPEG] = true
-	}
-	if int(C.vips_type_find_save_go(C.int(imageTypePNG))) != 0 {
-		vipsTypeSupportSave[imageTypePNG] = true
-	}
-	if int(C.vips_type_find_save_go(C.int(imageTypeWEBP))) != 0 {
-		vipsTypeSupportSave[imageTypeWEBP] = true
-	}
-	if int(C.vips_type_find_save_go(C.int(imageTypeGIF))) != 0 {
-		vipsTypeSupportSave[imageTypeGIF] = true
-	}
-	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)
-	}
-
-	if conf.PngInterlaced {
-		cConf.PngInterlaced = C.int(1)
-	}
-
-	if conf.PngQuantize {
-		cConf.PngQuantize = C.int(1)
-	}
-
-	cConf.PngQuantizationColors = C.int(conf.PngQuantizationColors)
-
-	cConf.WatermarkOpacity = C.double(conf.WatermarkOpacity)
-
-	if err := vipsPrepareWatermark(); err != nil {
-		logFatal(err.Error())
-	}
-
-	collectVipsMetrics()
-}
-
-func shutdownVips() {
-	C.clear_image(&watermark)
-	C.vips_shutdown()
-}
-
-func collectVipsMetrics() {
-	if prometheusEnabled {
-		go func() {
-			for range time.Tick(5 * time.Second) {
-				prometheusVipsMemory.Set(float64(C.vips_tracked_get_mem()))
-				prometheusVipsMaxMemory.Set(float64(C.vips_tracked_get_mem_highwater()))
-				prometheusVipsAllocs.Set(float64(C.vips_tracked_get_allocs()))
-			}
-		}()
-	}
-}
-
-func cachedCString(str string) *C.char {
-	if cstr, ok := cstrings[str]; ok {
-		return cstr
-	}
-
-	cstr := C.CString(str)
-	cstrings[str] = cstr
-
-	return cstr
-}
-
-func extractMeta(img *C.VipsImage) (int, int, int, bool) {
-	width := int(img.Xsize)
-	height := int(img.Ysize)
-
-	angle := C.VIPS_ANGLE_D0
+	angle := vipsAngleD0
 	flip := false
 	flip := false
 
 
-	orientation := C.vips_get_exif_orientation(img)
+	orientation := img.Orientation()
 
 
 	if orientation >= 5 && orientation <= 8 {
 	if orientation >= 5 && orientation <= 8 {
 		width, height = height, width
 		width, height = height, width
 	}
 	}
 	if orientation == 3 || orientation == 4 {
 	if orientation == 3 || orientation == 4 {
-		angle = C.VIPS_ANGLE_D180
+		angle = vipsAngleD180
 	}
 	}
 	if orientation == 5 || orientation == 6 {
 	if orientation == 5 || orientation == 6 {
-		angle = C.VIPS_ANGLE_D90
+		angle = vipsAngleD90
 	}
 	}
 	if orientation == 7 || orientation == 8 {
 	if orientation == 7 || orientation == 8 {
-		angle = C.VIPS_ANGLE_D270
+		angle = vipsAngleD270
 	}
 	}
 	if orientation == 2 || orientation == 4 || orientation == 5 || orientation == 7 {
 	if orientation == 2 || orientation == 4 || orientation == 5 || orientation == 7 {
 		flip = true
 		flip = true
@@ -296,79 +144,75 @@ func calcCrop(width, height, cropWidth, cropHeight int, gravity *gravityOptions)
 	return
 	return
 }
 }
 
 
-func transformImage(ctx context.Context, img **C.VipsImage, data []byte, po *processingOptions, imgtype imageType) error {
+func transformImage(ctx context.Context, img *vipsImage, data []byte, po *processingOptions, imgtype imageType) error {
 	var err error
 	var err error
 
 
-	imgWidth, imgHeight, angle, flip := extractMeta(*img)
+	imgWidth, imgHeight, angle, flip := extractMeta(img)
 
 
-	hasAlpha := vipsImageHasAlpha(*img)
+	hasAlpha := img.HasAlpha()
 
 
 	scale := calcScale(imgWidth, imgHeight, po, imgtype)
 	scale := calcScale(imgWidth, imgHeight, po, imgtype)
 
 
 	if scale != 1 && data != nil && canScaleOnLoad(imgtype, scale) {
 	if scale != 1 && data != nil && canScaleOnLoad(imgtype, scale) {
 		if imgtype == imageTypeWEBP || imgtype == imageTypeSVG {
 		if imgtype == imageTypeWEBP || imgtype == imageTypeSVG {
 			// Do some scale-on-load
 			// Do some scale-on-load
-			if tmp, err := vipsLoadImage(data, imgtype, 1, scale, 1); err == nil {
-				C.swap_and_clear(img, tmp)
-			} else {
+			if err := img.Load(data, imgtype, 1, scale, 1); err != nil {
 				return err
 				return err
 			}
 			}
 		} else if imgtype == imageTypeJPEG {
 		} else if imgtype == imageTypeJPEG {
 			// Do some shrink-on-load
 			// Do some shrink-on-load
 			if shrink := calcJpegShink(scale, imgtype); shrink != 1 {
 			if shrink := calcJpegShink(scale, imgtype); shrink != 1 {
-				if tmp, err := vipsLoadImage(data, imgtype, shrink, 1.0, 1); err == nil {
-					C.swap_and_clear(img, tmp)
-				} else {
+				if err := img.Load(data, imgtype, shrink, 1.0, 1); err != nil {
 					return err
 					return err
 				}
 				}
 			}
 			}
 		}
 		}
 
 
 		// Update actual image size ans scale after scale-on-load
 		// Update actual image size ans scale after scale-on-load
-		imgWidth, imgHeight, _, _ = extractMeta(*img)
+		imgWidth, imgHeight, _, _ = extractMeta(img)
 		scale = calcScale(imgWidth, imgHeight, po, imgtype)
 		scale = calcScale(imgWidth, imgHeight, po, imgtype)
 	}
 	}
 
 
-	if err = vipsRad2Float(img); err != nil {
+	if err = img.Rad2Float(); err != nil {
 		return err
 		return err
 	}
 	}
 
 
 	convertToLinear := conf.UseLinearColorspace && (scale != 1 || po.Dpr != 1)
 	convertToLinear := conf.UseLinearColorspace && (scale != 1 || po.Dpr != 1)
 
 
 	if convertToLinear {
 	if convertToLinear {
-		if err = vipsImportColourProfile(img, true); err != nil {
+		if err = img.ImportColourProfile(true); err != nil {
 			return err
 			return err
 		}
 		}
 
 
-		if err = vipsLinearColourspace(img); err != nil {
+		if err = img.LinearColourspace(); err != nil {
 			return err
 			return err
 		}
 		}
 	}
 	}
 
 
 	if scale != 1 {
 	if scale != 1 {
-		if err = vipsResize(img, scale, hasAlpha); err != nil {
+		if err = img.Resize(scale, hasAlpha); err != nil {
 			return err
 			return err
 		}
 		}
 	}
 	}
 
 
 	// Update actual image size after resize
 	// Update actual image size after resize
-	imgWidth, imgHeight, _, _ = extractMeta(*img)
+	imgWidth, imgHeight, _, _ = extractMeta(img)
 
 
 	checkTimeout(ctx)
 	checkTimeout(ctx)
 
 
-	if angle != C.VIPS_ANGLE_D0 || flip {
-		if err = vipsImageCopyMemory(img); err != nil {
+	if angle != vipsAngleD0 || flip {
+		if err = img.CopyMemory(); err != nil {
 			return err
 			return err
 		}
 		}
 
 
-		if angle != C.VIPS_ANGLE_D0 {
-			if err = vipsRotate(img, angle); err != nil {
+		if angle != vipsAngleD0 {
+			if err = img.Rotate(angle); err != nil {
 				return err
 				return err
 			}
 			}
 		}
 		}
 
 
 		if flip {
 		if flip {
-			if err = vipsFlip(img); err != nil {
+			if err = img.Flip(); err != nil {
 				return err
 				return err
 			}
 			}
 		}
 		}
@@ -397,20 +241,20 @@ func transformImage(ctx context.Context, img **C.VipsImage, data []byte, po *pro
 
 
 	if cropW < imgWidth || cropH < imgHeight {
 	if cropW < imgWidth || cropH < imgHeight {
 		if po.Gravity.Type == gravitySmart {
 		if po.Gravity.Type == gravitySmart {
-			if err = vipsImageCopyMemory(img); err != nil {
+			if err = img.CopyMemory(); err != nil {
 				return err
 				return err
 			}
 			}
-			if err = vipsSmartCrop(img, cropW, cropH); err != nil {
+			if err = img.SmartCrop(cropW, cropH); err != nil {
 				return err
 				return err
 			}
 			}
 			// Applying additional modifications after smart crop causes SIGSEGV on Alpine
 			// Applying additional modifications after smart crop causes SIGSEGV on Alpine
 			// so we have to copy memory after it
 			// so we have to copy memory after it
-			if err = vipsImageCopyMemory(img); err != nil {
+			if err = img.CopyMemory(); err != nil {
 				return err
 				return err
 			}
 			}
 		} else {
 		} else {
 			left, top := calcCrop(imgWidth, imgHeight, cropW, cropH, &po.Gravity)
 			left, top := calcCrop(imgWidth, imgHeight, cropW, cropH, &po.Gravity)
-			if err = vipsCrop(img, left, top, cropW, cropH); err != nil {
+			if err = img.Crop(left, top, cropW, cropH); err != nil {
 				return err
 				return err
 			}
 			}
 		}
 		}
@@ -420,50 +264,50 @@ func transformImage(ctx context.Context, img **C.VipsImage, data []byte, po *pro
 
 
 	if po.Enlarge && po.Resize == resizeCrop && po.Dpr > 1 {
 	if po.Enlarge && po.Resize == resizeCrop && po.Dpr > 1 {
 		// We didn't enlarge the image before, because is wasn't optimal. Now it's time to do it
 		// We didn't enlarge the image before, because is wasn't optimal. Now it's time to do it
-		if err = vipsResize(img, po.Dpr, hasAlpha); err != nil {
+		if err = img.Resize(po.Dpr, hasAlpha); err != nil {
 			return err
 			return err
 		}
 		}
-		if err = vipsImageCopyMemory(img); err != nil {
+		if err = img.CopyMemory(); err != nil {
 			return err
 			return err
 		}
 		}
 	}
 	}
 
 
 	if convertToLinear {
 	if convertToLinear {
-		if err = vipsFixColourspace(img); err != nil {
+		if err = img.FixColourspace(); err != nil {
 			return err
 			return err
 		}
 		}
 	} else {
 	} else {
-		if err = vipsImportColourProfile(img, false); err != nil {
+		if err = img.ImportColourProfile(false); err != nil {
 			return err
 			return err
 		}
 		}
 	}
 	}
 
 
-	if po.Expand && (po.Width > int((*img).Xsize) || po.Height > int((*img).Ysize)) {
-		if err = vipsEnsureAlpha(img); err != nil {
+	if po.Expand && (po.Width > img.Width() || po.Height > img.Height()) {
+		if err = img.EnsureAlpha(); err != nil {
 			return err
 			return err
 		}
 		}
 
 
 		hasAlpha = true
 		hasAlpha = true
 
 
-		if err = vipsEmbed(img, gravityCenter, C.int(po.Width), C.int(po.Height), 0, 0); err != nil {
+		if err = img.Embed(gravityCenter, po.Width, po.Height, 0, 0); err != nil {
 			return err
 			return err
 		}
 		}
 	}
 	}
 
 
 	if hasAlpha && (po.Flatten || po.Format == imageTypeJPEG) {
 	if hasAlpha && (po.Flatten || po.Format == imageTypeJPEG) {
-		if err = vipsFlatten(img, po.Background); err != nil {
+		if err = img.Flatten(po.Background); err != nil {
 			return err
 			return err
 		}
 		}
 	}
 	}
 
 
 	if po.Blur > 0 {
 	if po.Blur > 0 {
-		if err = vipsBlur(img, po.Blur); err != nil {
+		if err = img.Blur(po.Blur); err != nil {
 			return err
 			return err
 		}
 		}
 	}
 	}
 
 
 	if po.Sharpen > 0 {
 	if po.Sharpen > 0 {
-		if err = vipsSharpen(img, po.Sharpen); err != nil {
+		if err = img.Sharpen(po.Sharpen); err != nil {
 			return err
 			return err
 		}
 		}
 	}
 	}
@@ -471,23 +315,23 @@ func transformImage(ctx context.Context, img **C.VipsImage, data []byte, po *pro
 	checkTimeout(ctx)
 	checkTimeout(ctx)
 
 
 	if po.Watermark.Enabled {
 	if po.Watermark.Enabled {
-		if err = vipsApplyWatermark(img, &po.Watermark); err != nil {
+		if err = img.ApplyWatermark(&po.Watermark); err != nil {
 			return err
 			return err
 		}
 		}
 	}
 	}
 
 
-	return vipsFixColourspace(img)
+	return img.FixColourspace()
 }
 }
 
 
-func transformAnimated(ctx context.Context, img **C.VipsImage, data []byte, po *processingOptions, imgtype imageType) error {
-	imgWidth := int((*img).Xsize)
+func transformAnimated(ctx context.Context, img *vipsImage, data []byte, po *processingOptions, imgtype imageType) error {
+	imgWidth := img.Width()
 
 
-	frameHeight, err := vipsGetInt(*img, "page-height")
+	frameHeight, err := img.GetInt("page-height")
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
 
 
-	framesCount := minInt(int((*img).Ysize)/frameHeight, conf.MaxGifFrames)
+	framesCount := minInt(img.Height()/frameHeight, conf.MaxGifFrames)
 
 
 	// Double check dimensions because animated image has many frames
 	// Double check dimensions because animated image has many frames
 	if err := checkDimensions(imgWidth, frameHeight*framesCount); err != nil {
 	if err := checkDimensions(imgWidth, frameHeight*framesCount); err != nil {
@@ -495,40 +339,38 @@ func transformAnimated(ctx context.Context, img **C.VipsImage, data []byte, po *
 	}
 	}
 
 
 	// Vips 8.8+ supports n-pages and doesn't load the whole animated image on header access
 	// Vips 8.8+ supports n-pages and doesn't load the whole animated image on header access
-	if nPages, _ := vipsGetInt(*img, "n-pages"); nPages > 0 {
+	if nPages, _ := img.GetInt("n-pages"); nPages > 0 {
 		scale := calcScale(imgWidth, frameHeight, po, imgtype)
 		scale := calcScale(imgWidth, frameHeight, po, imgtype)
 
 
 		if nPages > framesCount || canScaleOnLoad(imgtype, scale) {
 		if nPages > framesCount || canScaleOnLoad(imgtype, scale) {
 			// Do some scale-on-load
 			// Do some scale-on-load
-			if tmp, err := vipsLoadImage(data, imgtype, 1, scale, framesCount); err == nil {
-				C.swap_and_clear(img, tmp)
-			} else {
+			if err := img.Load(data, imgtype, 1, scale, framesCount); err != nil {
 				return err
 				return err
 			}
 			}
 		}
 		}
 
 
-		imgWidth = int((*img).Xsize)
+		imgWidth = img.Width()
 
 
-		frameHeight, err = vipsGetInt(*img, "page-height")
+		frameHeight, err = img.GetInt("page-height")
 		if err != nil {
 		if err != nil {
 			return err
 			return err
 		}
 		}
 	}
 	}
 
 
-	delay, err := vipsGetInt(*img, "gif-delay")
+	delay, err := img.GetInt("gif-delay")
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
 
 
-	loop, err := vipsGetInt(*img, "gif-loop")
+	loop, err := img.GetInt("gif-loop")
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
 
 
-	frames := make([]*C.VipsImage, framesCount)
+	frames := make([]*vipsImage, framesCount)
 	defer func() {
 	defer func() {
 		for _, frame := range frames {
 		for _, frame := range frames {
-			C.clear_image(&frame)
+			frame.Clear()
 		}
 		}
 	}()
 	}()
 
 
@@ -537,13 +379,13 @@ func transformAnimated(ctx context.Context, img **C.VipsImage, data []byte, po *
 	for i := 0; i < framesCount; i++ {
 	for i := 0; i < framesCount; i++ {
 		ind := i
 		ind := i
 		errg.Go(func() error {
 		errg.Go(func() error {
-			var frame *C.VipsImage
+			frame := new(vipsImage)
 
 
-			if err := vipsExtract(*img, &frame, 0, ind*frameHeight, imgWidth, frameHeight); err != nil {
+			if err := img.Extract(frame, 0, ind*frameHeight, imgWidth, frameHeight); err != nil {
 				return err
 				return err
 			}
 			}
 
 
-			if err := transformImage(ctx, &frame, nil, po, imgtype); err != nil {
+			if err := transformImage(ctx, frame, nil, po, imgtype); err != nil {
 				return err
 				return err
 			}
 			}
 
 
@@ -559,14 +401,14 @@ func transformAnimated(ctx context.Context, img **C.VipsImage, data []byte, po *
 
 
 	checkTimeout(ctx)
 	checkTimeout(ctx)
 
 
-	if err := vipsArrayjoin(frames, img); err != nil {
+	if err := img.Arrayjoin(frames); err != nil {
 		return err
 		return err
 	}
 	}
 
 
-	vipsSetInt(*img, "page-height", int(frames[0].Ysize))
-	vipsSetInt(*img, "gif-delay", delay)
-	vipsSetInt(*img, "gif-loop", loop)
-	vipsSetInt(*img, "n-pages", framesCount)
+	img.SetInt("page-height", frames[0].Height())
+	img.SetInt("gif-delay", delay)
+	img.SetInt("gif-loop", loop)
+	img.SetInt("n-pages", framesCount)
 
 
 	return nil
 	return nil
 }
 }
@@ -584,7 +426,7 @@ func processImage(ctx context.Context) ([]byte, context.CancelFunc, error) {
 		defer startPrometheusDuration(prometheusProcessingDuration)()
 		defer startPrometheusDuration(prometheusProcessingDuration)()
 	}
 	}
 
 
-	defer C.vips_cleanup()
+	defer vipsCleanup()
 
 
 	po := getProcessingOptions(ctx)
 	po := getProcessingOptions(ctx)
 	data := getImageData(ctx).Bytes()
 	data := getImageData(ctx).Bytes()
@@ -609,18 +451,19 @@ func processImage(ctx context.Context) ([]byte, context.CancelFunc, error) {
 		pages = -1
 		pages = -1
 	}
 	}
 
 
-	img, err := vipsLoadImage(data, imgtype, 1, 1.0, pages)
-	if err != nil {
+	img := new(vipsImage)
+	defer img.Clear()
+
+	if err := img.Load(data, imgtype, 1, 1.0, pages); err != nil {
 		return nil, func() {}, err
 		return nil, func() {}, err
 	}
 	}
-	defer C.clear_image(&img)
 
 
-	if animationSupport && vipsIsAnimated(img) {
-		if err := transformAnimated(ctx, &img, data, po, imgtype); err != nil {
+	if animationSupport && img.IsAnimated() {
+		if err := transformAnimated(ctx, img, data, po, imgtype); err != nil {
 			return nil, func() {}, err
 			return nil, func() {}, err
 		}
 		}
 	} else {
 	} else {
-		if err := transformImage(ctx, &img, data, po, imgtype); err != nil {
+		if err := transformImage(ctx, img, data, po, imgtype); err != nil {
 			return nil, func() {}, err
 			return nil, func() {}, err
 		}
 		}
 	}
 	}
@@ -628,495 +471,11 @@ func processImage(ctx context.Context) ([]byte, context.CancelFunc, error) {
 	checkTimeout(ctx)
 	checkTimeout(ctx)
 
 
 	if po.Format == imageTypeGIF {
 	if po.Format == imageTypeGIF {
-		if err := vipsCastUchar(&img); err != nil {
+		if err := img.CastUchar(); err != nil {
 			return nil, func() {}, err
 			return nil, func() {}, err
 		}
 		}
 		checkTimeout(ctx)
 		checkTimeout(ctx)
 	}
 	}
 
 
-	return vipsSaveImage(img, po.Format, po.Quality)
-}
-
-func vipsPrepareWatermark() error {
-	data, imgtype, cancel, err := watermarkData()
-	defer cancel()
-
-	if err != nil {
-		return err
-	}
-
-	if data == nil {
-		return nil
-	}
-
-	watermark, err = vipsLoadImage(data, imgtype, 1, 1.0, 1)
-	if err != nil {
-		return err
-	}
-
-	var tmp *C.VipsImage
-
-	if C.vips_apply_opacity(watermark, &tmp, C.double(conf.WatermarkOpacity)) != 0 {
-		return vipsError()
-	}
-	C.swap_and_clear(&watermark, tmp)
-
-	if tmp = C.vips_image_copy_memory(watermark); tmp == nil {
-		return vipsError()
-	}
-	C.swap_and_clear(&watermark, tmp)
-
-	return nil
-}
-
-func vipsLoadImage(data []byte, imgtype imageType, shrink int, scale float64, pages int) (*C.VipsImage, error) {
-	var img *C.VipsImage
-
-	err := C.int(0)
-
-	switch imgtype {
-	case imageTypeJPEG:
-		err = C.vips_jpegload_go(unsafe.Pointer(&data[0]), C.size_t(len(data)), C.int(shrink), &img)
-	case imageTypePNG:
-		err = C.vips_pngload_go(unsafe.Pointer(&data[0]), C.size_t(len(data)), &img)
-	case imageTypeWEBP:
-		err = C.vips_webpload_go(unsafe.Pointer(&data[0]), C.size_t(len(data)), C.double(scale), C.int(pages), &img)
-	case imageTypeGIF:
-		err = C.vips_gifload_go(unsafe.Pointer(&data[0]), C.size_t(len(data)), C.int(pages), &img)
-	case imageTypeSVG:
-		err = C.vips_svgload_go(unsafe.Pointer(&data[0]), C.size_t(len(data)), C.double(scale), &img)
-	case imageTypeICO:
-		rawData, width, height, icoErr := icoData(data)
-		if icoErr != nil {
-			return nil, icoErr
-		}
-
-		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()
-	}
-
-	return img, nil
-}
-
-func vipsSaveImage(img *C.VipsImage, imgtype imageType, quality int) ([]byte, context.CancelFunc, error) {
-	var ptr unsafe.Pointer
-
-	cancel := func() {
-		C.g_free_go(&ptr)
-	}
-
-	err := C.int(0)
-
-	imgsize := C.size_t(0)
-
-	switch imgtype {
-	case imageTypeJPEG:
-		err = C.vips_jpegsave_go(img, &ptr, &imgsize, C.int(quality), cConf.JpegProgressive)
-	case imageTypePNG:
-		err = C.vips_pngsave_go(img, &ptr, &imgsize, cConf.PngInterlaced, cConf.PngQuantize, cConf.PngQuantizationColors)
-	case imageTypeWEBP:
-		err = C.vips_webpsave_go(img, &ptr, &imgsize, C.int(quality))
-	case imageTypeGIF:
-		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)
-		return nil, cancel, vipsError()
-	}
-
-	const maxBufSize = ^uint32(0)
-
-	b := (*[maxBufSize]byte)(ptr)[:int(imgsize):int(imgsize)]
-
-	return b, cancel, nil
-}
-
-func vipsArrayjoin(in []*C.VipsImage, out **C.VipsImage) error {
-	var tmp *C.VipsImage
-
-	if C.vips_arrayjoin_go(&in[0], &tmp, C.int(len(in))) != 0 {
-		return vipsError()
-	}
-
-	C.swap_and_clear(out, tmp)
-	return nil
-}
-
-func vipsSupportAnimation(imgtype imageType) bool {
-	return imgtype == imageTypeGIF ||
-		(imgtype == imageTypeWEBP && C.vips_support_webp_animation() != 0)
-}
-
-func vipsIsAnimated(img *C.VipsImage) bool {
-	return C.vips_is_animated(img) > 0
-}
-
-func vipsImageHasAlpha(img *C.VipsImage) bool {
-	return C.vips_image_hasalpha_go(img) > 0
-}
-
-func vipsGetInt(img *C.VipsImage, name string) (int, error) {
-	var i C.int
-
-	if C.vips_image_get_int(img, cachedCString(name), &i) != 0 {
-		return 0, vipsError()
-	}
-	return int(i), nil
-}
-
-func vipsSetInt(img *C.VipsImage, name string, value int) {
-	C.vips_image_set_int(img, cachedCString(name), C.int(value))
-}
-
-func vipsCastUchar(img **C.VipsImage) error {
-	var tmp *C.VipsImage
-
-	if C.vips_image_get_format(*img) != C.VIPS_FORMAT_UCHAR {
-		if C.vips_cast_go(*img, &tmp, C.VIPS_FORMAT_UCHAR) != 0 {
-			return vipsError()
-		}
-		C.swap_and_clear(img, tmp)
-	}
-
-	return nil
-}
-
-func vipsRad2Float(img **C.VipsImage) error {
-	var tmp *C.VipsImage
-
-	if C.vips_image_get_coding(*img) == C.VIPS_CODING_RAD {
-		if C.vips_rad2float_go(*img, &tmp) != 0 {
-			return vipsError()
-		}
-		C.swap_and_clear(img, tmp)
-	}
-
-	return nil
-}
-
-func vipsResize(img **C.VipsImage, scale float64, hasAlpa bool) error {
-	var tmp *C.VipsImage
-
-	if hasAlpa {
-		if C.vips_resize_with_premultiply(*img, &tmp, C.double(scale)) != 0 {
-			return vipsError()
-		}
-	} else {
-		if C.vips_resize_go(*img, &tmp, C.double(scale)) != 0 {
-			return vipsError()
-		}
-	}
-
-	C.swap_and_clear(img, tmp)
-
-	return nil
-}
-
-func vipsRotate(img **C.VipsImage, angle int) error {
-	var tmp *C.VipsImage
-
-	if C.vips_rot_go(*img, &tmp, C.VipsAngle(angle)) != 0 {
-		return vipsError()
-	}
-
-	C.swap_and_clear(img, tmp)
-	return nil
-}
-
-func vipsFlip(img **C.VipsImage) error {
-	var tmp *C.VipsImage
-
-	if C.vips_flip_horizontal_go(*img, &tmp) != 0 {
-		return vipsError()
-	}
-
-	C.swap_and_clear(img, tmp)
-	return nil
-}
-
-func vipsCrop(img **C.VipsImage, left, top, width, height int) error {
-	var tmp *C.VipsImage
-
-	if C.vips_extract_area_go(*img, &tmp, C.int(left), C.int(top), C.int(width), C.int(height)) != 0 {
-		return vipsError()
-	}
-
-	C.swap_and_clear(img, tmp)
-	return nil
-}
-
-func vipsExtract(in *C.VipsImage, out **C.VipsImage, left, top, width, height int) error {
-	if C.vips_extract_area_go(in, out, C.int(left), C.int(top), C.int(width), C.int(height)) != 0 {
-		return vipsError()
-	}
-	return nil
-}
-
-func vipsSmartCrop(img **C.VipsImage, width, height int) error {
-	var tmp *C.VipsImage
-
-	if C.vips_smartcrop_go(*img, &tmp, C.int(width), C.int(height)) != 0 {
-		return vipsError()
-	}
-
-	C.swap_and_clear(img, tmp)
-	return nil
-}
-
-func vipsEnsureAlpha(img **C.VipsImage) error {
-	var tmp *C.VipsImage
-
-	if C.vips_ensure_alpha(*img, &tmp) != 0 {
-		return vipsError()
-	}
-
-	C.swap_and_clear(img, tmp)
-	return nil
-}
-
-func vipsFlatten(img **C.VipsImage, bg rgbColor) error {
-	var tmp *C.VipsImage
-
-	if C.vips_flatten_go(*img, &tmp, C.double(bg.R), C.double(bg.G), C.double(bg.B)) != 0 {
-		return vipsError()
-	}
-	C.swap_and_clear(img, tmp)
-
-	return nil
-}
-
-func vipsBlur(img **C.VipsImage, sigma float32) error {
-	var tmp *C.VipsImage
-
-	if C.vips_gaussblur_go(*img, &tmp, C.double(sigma)) != 0 {
-		return vipsError()
-	}
-
-	C.swap_and_clear(img, tmp)
-	return nil
-}
-
-func vipsSharpen(img **C.VipsImage, sigma float32) error {
-	var tmp *C.VipsImage
-
-	if C.vips_sharpen_go(*img, &tmp, C.double(sigma)) != 0 {
-		return vipsError()
-	}
-
-	C.swap_and_clear(img, tmp)
-	return nil
-}
-
-func vipsImportColourProfile(img **C.VipsImage, evenSRGB bool) error {
-	var tmp *C.VipsImage
-
-	if (*img).Coding != C.VIPS_CODING_NONE {
-		return nil
-	}
-
-	if (*img).BandFmt != C.VIPS_FORMAT_UCHAR && (*img).BandFmt != C.VIPS_FORMAT_USHORT {
-		return nil
-	}
-
-	profile := (*C.char)(nil)
-
-	if (*img).Type == C.VIPS_INTERPRETATION_sRGB {
-		// No embedded profile for sRGB, ignore
-		if C.vips_has_embedded_icc(*img) == 0 {
-			return nil
-		}
-
-		// Don't import sRGB IEC61966 2.1 unless evenSRGB
-		if !evenSRGB && C.vips_icc_is_srgb_iec61966(*img) != 0 {
-			return nil
-		}
-	} else if (*img).Type == C.VIPS_INTERPRETATION_CMYK && C.vips_has_embedded_icc(*img) == 0 {
-		if C.vips_support_builtin_icc() != 0 {
-			profile = cachedCString("cmyk")
-		} else {
-			p, err := cmykProfilePath()
-			if err != nil {
-				return err
-			}
-			profile = cachedCString(p)
-		}
-	}
-
-	if C.vips_icc_import_go(*img, &tmp, profile) == 0 {
-		C.swap_and_clear(img, tmp)
-	} else {
-		logWarning("Can't import ICC profile: %s", vipsError())
-	}
-
-	return nil
-}
-
-func vipsLinearColourspace(img **C.VipsImage) error {
-	var tmp *C.VipsImage
-
-	if C.vips_image_guess_interpretation(*img) != C.VIPS_INTERPRETATION_scRGB {
-		if C.vips_colourspace_go(*img, &tmp, C.VIPS_INTERPRETATION_scRGB) != 0 {
-			return vipsError()
-		}
-		C.swap_and_clear(img, tmp)
-	}
-
-	return nil
-}
-
-func vipsFixColourspace(img **C.VipsImage) error {
-	var tmp *C.VipsImage
-
-	if C.vips_image_guess_interpretation(*img) != C.VIPS_INTERPRETATION_sRGB {
-		if C.vips_colourspace_go(*img, &tmp, C.VIPS_INTERPRETATION_sRGB) != 0 {
-			return vipsError()
-		}
-		C.swap_and_clear(img, tmp)
-	}
-
-	return nil
-}
-
-func vipsImageCopyMemory(img **C.VipsImage) error {
-	var tmp *C.VipsImage
-	if tmp = C.vips_image_copy_memory(*img); tmp == nil {
-		return vipsError()
-	}
-	C.swap_and_clear(img, tmp)
-	return nil
-}
-
-func vipsReplicate(img **C.VipsImage, width, height C.int) error {
-	var tmp *C.VipsImage
-
-	if C.vips_replicate_go(*img, &tmp, width, height) != 0 {
-		return vipsError()
-	}
-	C.swap_and_clear(img, tmp)
-
-	return nil
-}
-
-func vipsEmbed(img **C.VipsImage, gravity gravityType, width, height C.int, offX, offY C.int) error {
-	wmWidth := (*img).Xsize
-	wmHeight := (*img).Ysize
-
-	left := (width-wmWidth+1)/2 + offX
-	top := (height-wmHeight+1)/2 + offY
-
-	if gravity == gravityNorth || gravity == gravityNorthEast || gravity == gravityNorthWest {
-		top = offY
-	}
-
-	if gravity == gravityEast || gravity == gravityNorthEast || gravity == gravitySouthEast {
-		left = width - wmWidth - offX
-	}
-
-	if gravity == gravitySouth || gravity == gravitySouthEast || gravity == gravitySouthWest {
-		top = height - wmHeight - offY
-	}
-
-	if gravity == gravityWest || gravity == gravityNorthWest || gravity == gravitySouthWest {
-		left = offX
-	}
-
-	if left > width {
-		left = width - wmWidth
-	} else if left < -wmWidth {
-		left = 0
-	}
-
-	if top > height {
-		top = height - wmHeight
-	} else if top < -wmHeight {
-		top = 0
-	}
-
-	var tmp *C.VipsImage
-	if C.vips_embed_go(*img, &tmp, left, top, width, height) != 0 {
-		return vipsError()
-	}
-	C.swap_and_clear(img, tmp)
-
-	return nil
-}
-
-func vipsResizeWatermark(width, height int) (wm *C.VipsImage, err error) {
-	wmW := float64(watermark.Xsize)
-	wmH := float64(watermark.Ysize)
-
-	wr := float64(width) / wmW
-	hr := float64(height) / wmH
-
-	scale := math.Min(wr, hr)
-
-	if wmW*scale < 1 {
-		scale = 1 / wmW
-	}
-
-	if wmH*scale < 1 {
-		scale = 1 / wmH
-	}
-
-	if C.vips_resize_with_premultiply(watermark, &wm, C.double(scale)) != 0 {
-		err = vipsError()
-	}
-
-	return
-}
-
-func vipsApplyWatermark(img **C.VipsImage, opts *watermarkOptions) error {
-	if watermark == nil {
-		return nil
-	}
-
-	var wm, tmp *C.VipsImage
-	defer C.clear_image(&wm)
-
-	var err error
-
-	imgW := (*img).Xsize
-	imgH := (*img).Ysize
-
-	if opts.Scale == 0 {
-		if C.vips_copy_go(watermark, &wm) != 0 {
-			return vipsError()
-		}
-	} else {
-		wmW := maxInt(int(float64(imgW)*opts.Scale), 1)
-		wmH := maxInt(int(float64(imgH)*opts.Scale), 1)
-
-		if wm, err = vipsResizeWatermark(wmW, wmH); err != nil {
-			return err
-		}
-	}
-
-	if opts.Replicate {
-		if err = vipsReplicate(&wm, imgW, imgH); err != nil {
-			return err
-		}
-	} else {
-		if err = vipsEmbed(&wm, opts.Gravity, imgW, imgH, C.int(opts.OffsetX), C.int(opts.OffsetY)); err != nil {
-			return err
-		}
-	}
-
-	if C.vips_apply_watermark(*img, wm, &tmp, C.double(opts.Opacity)) != 0 {
-		return vipsError()
-	}
-	C.swap_and_clear(img, tmp)
-
-	return nil
-}
-
-func vipsError() error {
-	return newUnexpectedError(C.GoString(C.vips_error_buffer()), 1)
+	return img.Save(po.Format, po.Quality)
 }
 }

+ 677 - 0
vips.go

@@ -0,0 +1,677 @@
+package main
+
+/*
+#cgo pkg-config: vips
+#cgo LDFLAGS: -s -w
+#cgo CFLAGS: -O3
+#include "vips.h"
+*/
+import "C"
+import (
+	"context"
+	"errors"
+	"math"
+	"os"
+	"runtime"
+	"time"
+	"unsafe"
+)
+
+type vipsImage struct {
+	VipsImage *C.VipsImage
+}
+
+var (
+	vipsSupportSmartcrop bool
+	vipsTypeSupportLoad  = make(map[imageType]bool)
+	vipsTypeSupportSave  = make(map[imageType]bool)
+
+	watermark *vipsImage
+
+	errSmartCropNotSupported = errors.New("Smart crop is not supported by used version of libvips")
+)
+
+var vipsConf struct {
+	JpegProgressive       C.int
+	PngInterlaced         C.int
+	PngQuantize           C.int
+	PngQuantizationColors C.int
+	WatermarkOpacity      C.double
+}
+
+const (
+	vipsAngleD0   = C.VIPS_ANGLE_D0
+	vipsAngleD90  = C.VIPS_ANGLE_D90
+	vipsAngleD180 = C.VIPS_ANGLE_D180
+	vipsAngleD270 = C.VIPS_ANGLE_D270
+)
+
+func initVips() {
+	runtime.LockOSThread()
+	defer runtime.UnlockOSThread()
+
+	if err := C.vips_initialize(); err != 0 {
+		C.vips_shutdown()
+		logFatal("unable to start vips!")
+	}
+
+	// Disable libvips cache. Since processing pipeline is fine tuned, we won't get much profit from it.
+	// Enabled cache can cause SIGSEGV on Musl-based systems like Alpine.
+	C.vips_cache_set_max_mem(0)
+	C.vips_cache_set_max(0)
+
+	C.vips_concurrency_set(1)
+
+	if len(os.Getenv("IMGPROXY_VIPS_LEAK_CHECK")) > 0 {
+		C.vips_leak_set(C.gboolean(1))
+	}
+
+	if len(os.Getenv("IMGPROXY_VIPS_CACHE_TRACE")) > 0 {
+		C.vips_cache_set_trace(C.gboolean(1))
+	}
+
+	vipsSupportSmartcrop = C.vips_support_smartcrop() == 1
+
+	if int(C.vips_type_find_load_go(C.int(imageTypeJPEG))) != 0 {
+		vipsTypeSupportLoad[imageTypeJPEG] = true
+	}
+	if int(C.vips_type_find_load_go(C.int(imageTypePNG))) != 0 {
+		vipsTypeSupportLoad[imageTypePNG] = true
+	}
+	if int(C.vips_type_find_load_go(C.int(imageTypeWEBP))) != 0 {
+		vipsTypeSupportLoad[imageTypeWEBP] = true
+	}
+	if int(C.vips_type_find_load_go(C.int(imageTypeGIF))) != 0 {
+		vipsTypeSupportLoad[imageTypeGIF] = true
+	}
+	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
+
+	if int(C.vips_type_find_save_go(C.int(imageTypeJPEG))) != 0 {
+		vipsTypeSupportSave[imageTypeJPEG] = true
+	}
+	if int(C.vips_type_find_save_go(C.int(imageTypePNG))) != 0 {
+		vipsTypeSupportSave[imageTypePNG] = true
+	}
+	if int(C.vips_type_find_save_go(C.int(imageTypeWEBP))) != 0 {
+		vipsTypeSupportSave[imageTypeWEBP] = true
+	}
+	if int(C.vips_type_find_save_go(C.int(imageTypeGIF))) != 0 {
+		vipsTypeSupportSave[imageTypeGIF] = true
+	}
+	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 {
+		vipsConf.JpegProgressive = C.int(1)
+	}
+
+	if conf.PngInterlaced {
+		vipsConf.PngInterlaced = C.int(1)
+	}
+
+	if conf.PngQuantize {
+		vipsConf.PngQuantize = C.int(1)
+	}
+
+	vipsConf.PngQuantizationColors = C.int(conf.PngQuantizationColors)
+
+	vipsConf.WatermarkOpacity = C.double(conf.WatermarkOpacity)
+
+	if err := vipsPrepareWatermark(); err != nil {
+		logFatal(err.Error())
+	}
+
+	vipsCollectMetrics()
+}
+
+func shutdownVips() {
+	if watermark != nil {
+		watermark.Clear()
+	}
+
+	C.vips_shutdown()
+}
+
+func vipsCollectMetrics() {
+	if prometheusEnabled {
+		go func() {
+			for range time.Tick(5 * time.Second) {
+				prometheusVipsMemory.Set(float64(C.vips_tracked_get_mem()))
+				prometheusVipsMaxMemory.Set(float64(C.vips_tracked_get_mem_highwater()))
+				prometheusVipsAllocs.Set(float64(C.vips_tracked_get_allocs()))
+			}
+		}()
+	}
+}
+
+func vipsCleanup() {
+	C.vips_cleanup()
+}
+
+func vipsError() error {
+	return newUnexpectedError(C.GoString(C.vips_error_buffer()), 1)
+}
+
+func vipsPrepareWatermark() error {
+	data, imgtype, cancel, err := watermarkData()
+	defer cancel()
+
+	if err != nil {
+		return err
+	}
+
+	if data == nil {
+		return nil
+	}
+
+	watermark = new(vipsImage)
+
+	if err = watermark.Load(data, imgtype, 1, 1.0, 1); err != nil {
+		return err
+	}
+
+	var tmp *C.VipsImage
+
+	if C.vips_apply_opacity(watermark.VipsImage, &tmp, C.double(conf.WatermarkOpacity)) != 0 {
+		return vipsError()
+	}
+	C.swap_and_clear(&watermark.VipsImage, tmp)
+
+	if err = watermark.CopyMemory(); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func vipsResizeWatermark(width, height int) (wm *vipsImage, err error) {
+	wmW := float64(watermark.VipsImage.Xsize)
+	wmH := float64(watermark.VipsImage.Ysize)
+
+	wr := float64(width) / wmW
+	hr := float64(height) / wmH
+
+	scale := math.Min(wr, hr)
+
+	if wmW*scale < 1 {
+		scale = 1 / wmW
+	}
+
+	if wmH*scale < 1 {
+		scale = 1 / wmH
+	}
+
+	wm = new(vipsImage)
+
+	if C.vips_resize_with_premultiply(watermark.VipsImage, &wm.VipsImage, C.double(scale)) != 0 {
+		err = vipsError()
+	}
+
+	return
+}
+
+func (img *vipsImage) Width() int {
+	return int(img.VipsImage.Xsize)
+}
+
+func (img *vipsImage) Height() int {
+	return int(img.VipsImage.Ysize)
+}
+
+func (img *vipsImage) Load(data []byte, imgtype imageType, shrink int, scale float64, pages int) error {
+	var tmp *C.VipsImage
+
+	err := C.int(0)
+
+	switch imgtype {
+	case imageTypeJPEG:
+		err = C.vips_jpegload_go(unsafe.Pointer(&data[0]), C.size_t(len(data)), C.int(shrink), &tmp)
+	case imageTypePNG:
+		err = C.vips_pngload_go(unsafe.Pointer(&data[0]), C.size_t(len(data)), &tmp)
+	case imageTypeWEBP:
+		err = C.vips_webpload_go(unsafe.Pointer(&data[0]), C.size_t(len(data)), C.double(scale), C.int(pages), &tmp)
+	case imageTypeGIF:
+		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 imageTypeICO:
+		rawData, width, height, icoErr := icoData(data)
+		if icoErr != nil {
+			return icoErr
+		}
+
+		tmp = 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)), &tmp)
+	}
+	if err != 0 {
+		return vipsError()
+	}
+
+	C.swap_and_clear(&img.VipsImage, tmp)
+
+	return nil
+}
+
+func (img *vipsImage) Save(imgtype imageType, quality int) ([]byte, context.CancelFunc, error) {
+	var ptr unsafe.Pointer
+
+	cancel := func() {
+		C.g_free_go(&ptr)
+	}
+
+	err := C.int(0)
+
+	imgsize := C.size_t(0)
+
+	switch imgtype {
+	case imageTypeJPEG:
+		err = C.vips_jpegsave_go(img.VipsImage, &ptr, &imgsize, C.int(quality), vipsConf.JpegProgressive)
+	case imageTypePNG:
+		err = C.vips_pngsave_go(img.VipsImage, &ptr, &imgsize, vipsConf.PngInterlaced, vipsConf.PngQuantize, vipsConf.PngQuantizationColors)
+	case imageTypeWEBP:
+		err = C.vips_webpsave_go(img.VipsImage, &ptr, &imgsize, C.int(quality))
+	case imageTypeGIF:
+		err = C.vips_gifsave_go(img.VipsImage, &ptr, &imgsize)
+	case imageTypeICO:
+		err = C.vips_icosave_go(img.VipsImage, &ptr, &imgsize)
+	case imageTypeHEIC:
+		err = C.vips_heifsave_go(img.VipsImage, &ptr, &imgsize, C.int(quality))
+	}
+	if err != 0 {
+		C.g_free_go(&ptr)
+		return nil, cancel, vipsError()
+	}
+
+	const maxBufSize = ^uint32(0)
+
+	b := (*[maxBufSize]byte)(ptr)[:int(imgsize):int(imgsize)]
+
+	return b, cancel, nil
+}
+
+func (img *vipsImage) Clear() {
+	if img.VipsImage != nil {
+		C.clear_image(&img.VipsImage)
+	}
+}
+
+func (img *vipsImage) Arrayjoin(in []*vipsImage) error {
+	var tmp *C.VipsImage
+
+	arr := make([]*C.VipsImage, len(in))
+	for i, im := range in {
+		arr[i] = im.VipsImage
+	}
+
+	if C.vips_arrayjoin_go(&arr[0], &tmp, C.int(len(arr))) != 0 {
+		return vipsError()
+	}
+
+	C.swap_and_clear(&img.VipsImage, tmp)
+	return nil
+}
+
+func vipsSupportAnimation(imgtype imageType) bool {
+	return imgtype == imageTypeGIF ||
+		(imgtype == imageTypeWEBP && C.vips_support_webp_animation() != 0)
+}
+
+func (img *vipsImage) IsAnimated() bool {
+	return C.vips_is_animated(img.VipsImage) > 0
+}
+
+func (img *vipsImage) HasAlpha() bool {
+	return C.vips_image_hasalpha_go(img.VipsImage) > 0
+}
+
+func (img *vipsImage) GetInt(name string) (int, error) {
+	var i C.int
+
+	if C.vips_image_get_int(img.VipsImage, cachedCString(name), &i) != 0 {
+		return 0, vipsError()
+	}
+	return int(i), nil
+}
+
+func (img *vipsImage) SetInt(name string, value int) {
+	C.vips_image_set_int(img.VipsImage, cachedCString(name), C.int(value))
+}
+
+func (img *vipsImage) CastUchar() error {
+	var tmp *C.VipsImage
+
+	if C.vips_image_get_format(img.VipsImage) != C.VIPS_FORMAT_UCHAR {
+		if C.vips_cast_go(img.VipsImage, &tmp, C.VIPS_FORMAT_UCHAR) != 0 {
+			return vipsError()
+		}
+		C.swap_and_clear(&img.VipsImage, tmp)
+	}
+
+	return nil
+}
+
+func (img *vipsImage) Rad2Float() error {
+	var tmp *C.VipsImage
+
+	if C.vips_image_get_coding(img.VipsImage) == C.VIPS_CODING_RAD {
+		if C.vips_rad2float_go(img.VipsImage, &tmp) != 0 {
+			return vipsError()
+		}
+		C.swap_and_clear(&img.VipsImage, tmp)
+	}
+
+	return nil
+}
+
+func (img *vipsImage) Resize(scale float64, hasAlpa bool) error {
+	var tmp *C.VipsImage
+
+	if hasAlpa {
+		if C.vips_resize_with_premultiply(img.VipsImage, &tmp, C.double(scale)) != 0 {
+			return vipsError()
+		}
+	} else {
+		if C.vips_resize_go(img.VipsImage, &tmp, C.double(scale)) != 0 {
+			return vipsError()
+		}
+	}
+
+	C.swap_and_clear(&img.VipsImage, tmp)
+
+	return nil
+}
+
+func (img *vipsImage) Orientation() C.int {
+	return C.vips_get_exif_orientation(img.VipsImage)
+}
+
+func (img *vipsImage) Rotate(angle int) error {
+	var tmp *C.VipsImage
+
+	if C.vips_rot_go(img.VipsImage, &tmp, C.VipsAngle(angle)) != 0 {
+		return vipsError()
+	}
+
+	C.swap_and_clear(&img.VipsImage, tmp)
+	return nil
+}
+
+func (img *vipsImage) Flip() error {
+	var tmp *C.VipsImage
+
+	if C.vips_flip_horizontal_go(img.VipsImage, &tmp) != 0 {
+		return vipsError()
+	}
+
+	C.swap_and_clear(&img.VipsImage, tmp)
+	return nil
+}
+
+func (img *vipsImage) Crop(left, top, width, height int) error {
+	var tmp *C.VipsImage
+
+	if C.vips_extract_area_go(img.VipsImage, &tmp, C.int(left), C.int(top), C.int(width), C.int(height)) != 0 {
+		return vipsError()
+	}
+
+	C.swap_and_clear(&img.VipsImage, tmp)
+	return nil
+}
+
+func (img *vipsImage) Extract(out *vipsImage, left, top, width, height int) error {
+	if C.vips_extract_area_go(img.VipsImage, &out.VipsImage, C.int(left), C.int(top), C.int(width), C.int(height)) != 0 {
+		return vipsError()
+	}
+	return nil
+}
+
+func (img *vipsImage) SmartCrop(width, height int) error {
+	var tmp *C.VipsImage
+
+	if C.vips_smartcrop_go(img.VipsImage, &tmp, C.int(width), C.int(height)) != 0 {
+		return vipsError()
+	}
+
+	C.swap_and_clear(&img.VipsImage, tmp)
+	return nil
+}
+
+func (img *vipsImage) EnsureAlpha() error {
+	var tmp *C.VipsImage
+
+	if C.vips_ensure_alpha(img.VipsImage, &tmp) != 0 {
+		return vipsError()
+	}
+
+	C.swap_and_clear(&img.VipsImage, tmp)
+	return nil
+}
+
+func (img *vipsImage) Flatten(bg rgbColor) error {
+	var tmp *C.VipsImage
+
+	if C.vips_flatten_go(img.VipsImage, &tmp, C.double(bg.R), C.double(bg.G), C.double(bg.B)) != 0 {
+		return vipsError()
+	}
+	C.swap_and_clear(&img.VipsImage, tmp)
+
+	return nil
+}
+
+func (img *vipsImage) Blur(sigma float32) error {
+	var tmp *C.VipsImage
+
+	if C.vips_gaussblur_go(img.VipsImage, &tmp, C.double(sigma)) != 0 {
+		return vipsError()
+	}
+
+	C.swap_and_clear(&img.VipsImage, tmp)
+	return nil
+}
+
+func (img *vipsImage) Sharpen(sigma float32) error {
+	var tmp *C.VipsImage
+
+	if C.vips_sharpen_go(img.VipsImage, &tmp, C.double(sigma)) != 0 {
+		return vipsError()
+	}
+
+	C.swap_and_clear(&img.VipsImage, tmp)
+	return nil
+}
+
+func (img *vipsImage) ImportColourProfile(evenSRGB bool) error {
+	var tmp *C.VipsImage
+
+	if img.VipsImage.Coding != C.VIPS_CODING_NONE {
+		return nil
+	}
+
+	if img.VipsImage.BandFmt != C.VIPS_FORMAT_UCHAR && img.VipsImage.BandFmt != C.VIPS_FORMAT_USHORT {
+		return nil
+	}
+
+	profile := (*C.char)(nil)
+
+	if img.VipsImage.Type == C.VIPS_INTERPRETATION_sRGB {
+		// No embedded profile for sRGB, ignore
+		if C.vips_has_embedded_icc(img.VipsImage) == 0 {
+			return nil
+		}
+
+		// Don't import sRGB IEC61966 2.1 unless evenSRGB
+		if !evenSRGB && C.vips_icc_is_srgb_iec61966(img.VipsImage) != 0 {
+			return nil
+		}
+	} else if img.VipsImage.Type == C.VIPS_INTERPRETATION_CMYK && C.vips_has_embedded_icc(img.VipsImage) == 0 {
+		if C.vips_support_builtin_icc() != 0 {
+			profile = cachedCString("cmyk")
+		} else {
+			p, err := cmykProfilePath()
+			if err != nil {
+				return err
+			}
+			profile = cachedCString(p)
+		}
+	}
+
+	if C.vips_icc_import_go(img.VipsImage, &tmp, profile) == 0 {
+		C.swap_and_clear(&img.VipsImage, tmp)
+	} else {
+		logWarning("Can't import ICC profile: %s", vipsError())
+	}
+
+	return nil
+}
+
+func (img *vipsImage) LinearColourspace() error {
+	if C.vips_image_guess_interpretation(img.VipsImage) != C.VIPS_INTERPRETATION_scRGB {
+		var tmp *C.VipsImage
+
+		if C.vips_colourspace_go(img.VipsImage, &tmp, C.VIPS_INTERPRETATION_scRGB) != 0 {
+			return vipsError()
+		}
+		C.swap_and_clear(&img.VipsImage, tmp)
+	}
+
+	return nil
+}
+
+func (img *vipsImage) FixColourspace() error {
+	if C.vips_image_guess_interpretation(img.VipsImage) != C.VIPS_INTERPRETATION_sRGB {
+		var tmp *C.VipsImage
+
+		if C.vips_colourspace_go(img.VipsImage, &tmp, C.VIPS_INTERPRETATION_sRGB) != 0 {
+			return vipsError()
+		}
+		C.swap_and_clear(&img.VipsImage, tmp)
+	}
+
+	return nil
+}
+
+func (img *vipsImage) CopyMemory() error {
+	var tmp *C.VipsImage
+	if tmp = C.vips_image_copy_memory(img.VipsImage); tmp == nil {
+		return vipsError()
+	}
+	C.swap_and_clear(&img.VipsImage, tmp)
+	return nil
+}
+
+func (img *vipsImage) Replicate(width, height int) error {
+	var tmp *C.VipsImage
+
+	if C.vips_replicate_go(img.VipsImage, &tmp, C.int(width), C.int(height)) != 0 {
+		return vipsError()
+	}
+	C.swap_and_clear(&img.VipsImage, tmp)
+
+	return nil
+}
+
+func (img *vipsImage) Embed(gravity gravityType, width, height int, offX, offY int) error {
+	wmWidth := img.Width()
+	wmHeight := img.Height()
+
+	left := (width-wmWidth+1)/2 + offX
+	top := (height-wmHeight+1)/2 + offY
+
+	if gravity == gravityNorth || gravity == gravityNorthEast || gravity == gravityNorthWest {
+		top = offY
+	}
+
+	if gravity == gravityEast || gravity == gravityNorthEast || gravity == gravitySouthEast {
+		left = width - wmWidth - offX
+	}
+
+	if gravity == gravitySouth || gravity == gravitySouthEast || gravity == gravitySouthWest {
+		top = height - wmHeight - offY
+	}
+
+	if gravity == gravityWest || gravity == gravityNorthWest || gravity == gravitySouthWest {
+		left = offX
+	}
+
+	if left > width {
+		left = width - wmWidth
+	} else if left < -wmWidth {
+		left = 0
+	}
+
+	if top > height {
+		top = height - wmHeight
+	} else if top < -wmHeight {
+		top = 0
+	}
+
+	var tmp *C.VipsImage
+	if C.vips_embed_go(img.VipsImage, &tmp, C.int(left), C.int(top), C.int(width), C.int(height)) != 0 {
+		return vipsError()
+	}
+	C.swap_and_clear(&img.VipsImage, tmp)
+
+	return nil
+}
+
+func (img *vipsImage) ApplyWatermark(opts *watermarkOptions) error {
+	if watermark == nil {
+		return nil
+	}
+
+	var (
+		wm  *vipsImage
+		tmp *C.VipsImage
+	)
+	defer wm.Clear()
+
+	var err error
+
+	imgW := img.Width()
+	imgH := img.Height()
+
+	if opts.Scale == 0 {
+		wm = new(vipsImage)
+
+		if C.vips_copy_go(watermark.VipsImage, &wm.VipsImage) != 0 {
+			return vipsError()
+		}
+	} else {
+		wmW := maxInt(int(float64(imgW)*opts.Scale), 1)
+		wmH := maxInt(int(float64(imgH)*opts.Scale), 1)
+
+		if wm, err = vipsResizeWatermark(wmW, wmH); err != nil {
+			return err
+		}
+	}
+
+	if opts.Replicate {
+		if err = wm.Replicate(imgW, imgH); err != nil {
+			return err
+		}
+	} else {
+		if err = wm.Embed(opts.Gravity, imgW, imgH, opts.OffsetX, opts.OffsetY); err != nil {
+			return err
+		}
+	}
+
+	if C.vips_apply_watermark(img.VipsImage, wm.VipsImage, &tmp, C.double(opts.Opacity)) != 0 {
+		return vipsError()
+	}
+	C.swap_and_clear(&img.VipsImage, tmp)
+
+	return nil
+}