From 5a1d1eb113f6629c7cd44a3f7676f1cc5fdf3956 Mon Sep 17 00:00:00 2001 From: Paul van Santen Date: Thu, 26 Mar 2020 17:50:34 +0100 Subject: [PATCH 1/3] Use vips_thumbnail_image() to resize --- resizer.go | 30 +++--------------------------- vips.go | 20 +++++++++++++++++++- vips.h | 16 ++++++++++++++++ 3 files changed, 38 insertions(+), 28 deletions(-) diff --git a/resizer.go b/resizer.go index d3bed897..e8b385e6 100644 --- a/resizer.go +++ b/resizer.go @@ -34,20 +34,6 @@ func resizer(buf []byte, o Options) ([]byte, error) { return nil, errors.New("Unsupported image output type") } - // Auto rotate image based on EXIF orientation header - image, rotated, err := rotateAndFlipImage(image, o) - if err != nil { - return nil, err - } - - // If JPEG or HEIF image, retrieve the buffer - if rotated && (imageType == JPEG || imageType == HEIF) && !o.NoAutoRotate { - buf, err = getImageBuffer(image) - if err != nil { - return nil, err - } - } - inWidth := int(image.Xsize) inHeight := int(image.Ysize) @@ -71,19 +57,9 @@ func resizer(buf []byte, o Options) ([]byte, error) { } } - // Try to use libjpeg/libwebp shrink-on-load - supportsShrinkOnLoad := imageType == WEBP && VipsMajorVersion >= 8 && VipsMinorVersion >= 3 - supportsShrinkOnLoad = supportsShrinkOnLoad || imageType == JPEG - if supportsShrinkOnLoad && shrink >= 2 { - tmpImage, factor, err := shrinkOnLoad(buf, image, imageType, factor, shrink) - if err != nil { - return nil, err - } - - image = tmpImage - factor = math.Max(factor, 1.0) - shrink = int(math.Floor(factor)) - residual = float64(shrink) / factor + image, err = vipsThumbnail(image, inWidth, inHeight, o.NoAutoRotate, o.Crop) + if err != nil { + return nil, err } // Zoom image, if necessary diff --git a/vips.go b/vips.go index 6a775700..56180f32 100644 --- a/vips.go +++ b/vips.go @@ -343,6 +343,24 @@ func vipsInterpretation(image *C.VipsImage) Interpretation { return Interpretation(C.vips_image_guess_interpretation_bridge(image)) } +func vipsThumbnail(image *C.VipsImage, width, height int, noRotate, crop bool) (*C.VipsImage, error) { + var outImage *C.VipsImage + + noRotateParam := C.int(boolToInt(noRotate)) + cropParam := C.int(boolToInt(crop)) + + err := C.vips_thumbnail_bridge(image, &outImage, + C.int(width), C.int(height), noRotateParam, cropParam, + ) + if int(err) != 0 { + return nil, catchVipsError() + } + C.g_object_unref(C.gpointer(image)) + image = outImage + + return image, nil +} + func vipsFlattenBackground(image *C.VipsImage, background Color) (*C.VipsImage, error) { var outImage *C.VipsImage @@ -733,4 +751,4 @@ func vipsGamma(image *C.VipsImage, Gamma float64) (*C.VipsImage, error) { return nil, catchVipsError() } return out, nil -} \ No newline at end of file +} diff --git a/vips.h b/vips.h index 676943da..a33661f4 100644 --- a/vips.h +++ b/vips.h @@ -354,6 +354,22 @@ vips_is_16bit (VipsInterpretation interpretation) { return interpretation == VIPS_INTERPRETATION_RGB16 || interpretation == VIPS_INTERPRETATION_GREY16; } +int vips_thumbnail_bridge(VipsImage *in, VipsImage **out, int width, int height, int no_rotate, int crop) { + if (crop) { + return vips_thumbnail_image(in, out, width, + "height", height, + "no_rotate", INT_TO_GBOOLEAN(no_rotate), + "crop", VIPS_INTERESTING_CENTRE, + NULL + ); + } + return vips_thumbnail_image(in, out, width, + "height", height, + "no_rotate", INT_TO_GBOOLEAN(no_rotate), + NULL + ); +} + int vips_flatten_background_brigde(VipsImage *in, VipsImage **out, double r, double g, double b) { if (vips_is_16bit(in->Type)) { From 760d9f9f1999ab7dc9ae3d1a74c33dc3b525e926 Mon Sep 17 00:00:00 2001 From: Paul van Santen Date: Fri, 27 Mar 2020 10:22:24 +0100 Subject: [PATCH 2/3] Restore resizer() and build a parallel thumbnail function using vips_thumbnail_buffer() --- image.go | 15 +++++++++++++++ resizer.go | 38 +++++++++++++++++++++++++++++++++++--- vips.go | 38 ++++++++++++++++++++------------------ vips.h | 7 ++++--- 4 files changed, 74 insertions(+), 24 deletions(-) diff --git a/image.go b/image.go index 488582a9..cad2ebfc 100644 --- a/image.go +++ b/image.go @@ -129,6 +129,21 @@ func (i *Image) Thumbnail(pixels int) ([]byte, error) { return i.Process(options) } +func (i *Image) ThumbnailOptions(o Options) ([]byte, error) { + vipsImage, err := vipsThumbnail(i.buffer, o.Width, o.Height, o.NoAutoRotate, o.Crop) + if err != nil { + return nil, err + } + + image, err := saveImage(vipsImage, o) + if err != nil { + return nil, err + } + + i.buffer = image + return image, nil +} + // Watermark adds text as watermark on the given image. func (i *Image) Watermark(w Watermark) ([]byte, error) { options := Options{Watermark: w} diff --git a/resizer.go b/resizer.go index e8b385e6..af9e0d52 100644 --- a/resizer.go +++ b/resizer.go @@ -34,6 +34,20 @@ func resizer(buf []byte, o Options) ([]byte, error) { return nil, errors.New("Unsupported image output type") } + // Auto rotate image based on EXIF orientation header + image, rotated, err := rotateAndFlipImage(image, o) + if err != nil { + return nil, err + } + + // If JPEG or HEIF image, retrieve the buffer + if rotated && (imageType == JPEG || imageType == HEIF) && !o.NoAutoRotate { + buf, err = getImageBuffer(image) + if err != nil { + return nil, err + } + } + inWidth := int(image.Xsize) inHeight := int(image.Ysize) @@ -57,9 +71,19 @@ func resizer(buf []byte, o Options) ([]byte, error) { } } - image, err = vipsThumbnail(image, inWidth, inHeight, o.NoAutoRotate, o.Crop) - if err != nil { - return nil, err + // Try to use libjpeg/libwebp shrink-on-load + supportsShrinkOnLoad := imageType == WEBP && VipsMajorVersion >= 8 && VipsMinorVersion >= 3 + supportsShrinkOnLoad = supportsShrinkOnLoad || imageType == JPEG + if supportsShrinkOnLoad && shrink >= 2 { + tmpImage, factor, err := shrinkOnLoad(buf, image, imageType, factor, shrink) + if err != nil { + return nil, err + } + + image = tmpImage + factor = math.Max(factor, 1.0) + shrink = int(math.Floor(factor)) + residual = float64(shrink) / factor } // Zoom image, if necessary @@ -124,6 +148,14 @@ func loadImage(buf []byte) (*C.VipsImage, ImageType, error) { return image, imageType, nil } +func imageThumbnail(buf []byte, width, height int, noRotate, crop bool) (*C.VipsImage, error) { + if len(buf) == 0 { + return nil, errors.New("Image buffer is empty") + } + + return vipsThumbnail(buf, width, height, noRotate, crop) +} + func applyDefaults(o Options, imageType ImageType) Options { if o.Quality == 0 { o.Quality = Quality diff --git a/vips.go b/vips.go index 56180f32..fe2fbc33 100644 --- a/vips.go +++ b/vips.go @@ -317,6 +317,26 @@ func vipsRead(buf []byte) (*C.VipsImage, ImageType, error) { return image, imageType, nil } +func vipsThumbnail(buf []byte, width, height int, noAutoRotate, crop bool) (*C.VipsImage, error) { + var image *C.VipsImage + + noRotateParam := C.int(boolToInt(noAutoRotate)) + cropParam := C.int(boolToInt(crop)) + + length := C.size_t(len(buf)) + imageBuf := unsafe.Pointer(&buf[0]) + + err := C.vips_thumbnail_bridge(imageBuf, length, &image, + C.int(width), C.int(height), noRotateParam, cropParam, + ) + if int(err) != 0 { + return nil, catchVipsError() + } + C.g_object_unref(C.gpointer(image)) + + return image, nil +} + func vipsColourspaceIsSupportedBuffer(buf []byte) (bool, error) { image, _, err := vipsRead(buf) if err != nil { @@ -343,24 +363,6 @@ func vipsInterpretation(image *C.VipsImage) Interpretation { return Interpretation(C.vips_image_guess_interpretation_bridge(image)) } -func vipsThumbnail(image *C.VipsImage, width, height int, noRotate, crop bool) (*C.VipsImage, error) { - var outImage *C.VipsImage - - noRotateParam := C.int(boolToInt(noRotate)) - cropParam := C.int(boolToInt(crop)) - - err := C.vips_thumbnail_bridge(image, &outImage, - C.int(width), C.int(height), noRotateParam, cropParam, - ) - if int(err) != 0 { - return nil, catchVipsError() - } - C.g_object_unref(C.gpointer(image)) - image = outImage - - return image, nil -} - func vipsFlattenBackground(image *C.VipsImage, background Color) (*C.VipsImage, error) { var outImage *C.VipsImage diff --git a/vips.h b/vips.h index a33661f4..21e17e49 100644 --- a/vips.h +++ b/vips.h @@ -354,16 +354,17 @@ vips_is_16bit (VipsInterpretation interpretation) { return interpretation == VIPS_INTERPRETATION_RGB16 || interpretation == VIPS_INTERPRETATION_GREY16; } -int vips_thumbnail_bridge(VipsImage *in, VipsImage **out, int width, int height, int no_rotate, int crop) { +int +vips_thumbnail_bridge(void *buf, size_t len, VipsImage **out, int width, int height, int no_rotate, int crop) { if (crop) { - return vips_thumbnail_image(in, out, width, + return vips_thumbnail_buffer(buf, len, out, width, "height", height, "no_rotate", INT_TO_GBOOLEAN(no_rotate), "crop", VIPS_INTERESTING_CENTRE, NULL ); } - return vips_thumbnail_image(in, out, width, + return vips_thumbnail_buffer(buf, len, out, width, "height", height, "no_rotate", INT_TO_GBOOLEAN(no_rotate), NULL From 8ce56806ddbf2a43f5af33326172ed32e52bee34 Mon Sep 17 00:00:00 2001 From: Paul van Santen Date: Fri, 27 Mar 2020 11:44:02 +0100 Subject: [PATCH 3/3] Add test, fix ThumbnailOptions() --- image.go | 2 +- image_test.go | 37 +++++++++++++++++++++++++++++++++++++ vips.go | 1 - 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/image.go b/image.go index cad2ebfc..dd7053db 100644 --- a/image.go +++ b/image.go @@ -130,7 +130,7 @@ func (i *Image) Thumbnail(pixels int) ([]byte, error) { } func (i *Image) ThumbnailOptions(o Options) ([]byte, error) { - vipsImage, err := vipsThumbnail(i.buffer, o.Width, o.Height, o.NoAutoRotate, o.Crop) + vipsImage, err := imageThumbnail(i.buffer, o.Width, o.Height, o.NoAutoRotate, o.Crop) if err != nil { return nil, err } diff --git a/image_test.go b/image_test.go index 5af0431d..39ebf2ac 100644 --- a/image_test.go +++ b/image_test.go @@ -212,6 +212,43 @@ func TestImageThumbnail(t *testing.T) { Write("testdata/test_thumbnail_out.jpg", buf) } +func TestImageThumbnailOptions(t *testing.T) { + buf, err := initImage("test.jpg").ThumbnailOptions(Options{ + Height: 100, + Width: 100, + Quality: 50, + Type: JPEG, + }) + if err != nil { + t.Errorf("Cannot process the image: %s", err) + } + + err = assertSize(buf, 100, 62) + if err != nil { + t.Error(err) + } + + Write("testdata/test_thumbnail_options_out.jpg", buf) + + buf, err = initImage("test.jpg").ThumbnailOptions(Options{ + Height: 100, + Width: 100, + Quality: 50, + Type: JPEG, + Crop: true, + }) + if err != nil { + t.Errorf("Cannot process the image: %s", err) + } + + err = assertSize(buf, 100, 100) + if err != nil { + t.Error(err) + } + + Write("testdata/test_thumbnail_options_crop_out.jpg", buf) +} + func TestImageWatermark(t *testing.T) { image := initImage("test.jpg") _, err := image.Crop(800, 600, GravityNorth) diff --git a/vips.go b/vips.go index fe2fbc33..21ec11a7 100644 --- a/vips.go +++ b/vips.go @@ -332,7 +332,6 @@ func vipsThumbnail(buf []byte, width, height int, noAutoRotate, crop bool) (*C.V if int(err) != 0 { return nil, catchVipsError() } - C.g_object_unref(C.gpointer(image)) return image, nil }