diff --git a/fixtures/northern_cardinal_bird.jpg b/fixtures/northern_cardinal_bird.jpg new file mode 100644 index 0000000..1bf5385 Binary files /dev/null and b/fixtures/northern_cardinal_bird.jpg differ diff --git a/image.go b/image.go index 14e4045..6f925fb 100644 --- a/image.go +++ b/image.go @@ -41,6 +41,17 @@ func (i *Image) ResizeAndCrop(width, height int) ([]byte, error) { return i.Process(options) } +// SmartCrop produces a thumbnail aiming at focus on the interesting part. +func (i *Image) SmartCrop(width, height int) ([]byte, error) { + options := Options{ + Width: width, + Height: height, + Crop: true, + SmartCrop: true, + } + return i.Process(options) +} + // Extract area from the by X/Y axis in the current image. func (i *Image) Extract(top, left, width, height int) ([]byte, error) { options := Options{ diff --git a/image_test.go b/image_test.go index f5199e7..5428957 100644 --- a/image_test.go +++ b/image_test.go @@ -455,6 +455,23 @@ func TestFluentInterface(t *testing.T) { Write("fixtures/test_image_fluent_out.png", image.Image()) } +func TestImageSmartCrop(t *testing.T) { + if VipsMajorVersion >= 8 && VipsMinorVersion > 4 { + i := initImage("northern_cardinal_bird.jpg") + buf, err := i.SmartCrop(300, 300) + if err != nil { + t.Errorf("Cannot process the image: %#v", err) + } + + err = assertSize(buf, 300, 300) + if err != nil { + t.Error(err) + } + + Write("fixtures/test_smart_crop.jpg", buf) + } +} + func initImage(file string) *Image { buf, _ := imageBuf(file) return NewImage(buf) diff --git a/options.go b/options.go index 0826fa7..b8d8a08 100644 --- a/options.go +++ b/options.go @@ -193,6 +193,7 @@ type Options struct { Compression int Zoom int Crop bool + SmartCrop bool Enlarge bool Embed bool Flip bool diff --git a/resize.go b/resize.go index 6a1016c..c05a6b8 100644 --- a/resize.go +++ b/resize.go @@ -249,6 +249,9 @@ func extractOrEmbedImage(image *C.VipsImage, o Options) (*C.VipsImage, error) { inHeight := int(image.Ysize) switch { + case o.SmartCrop: + image, err = vipsSmartCrop(image, o.Width, o.Height) + break case o.Crop: width := int(math.Min(float64(inWidth), float64(o.Width))) height := int(math.Min(float64(inHeight), float64(o.Height))) diff --git a/vips.go b/vips.go index d826bbf..55eab8e 100644 --- a/vips.go +++ b/vips.go @@ -456,6 +456,22 @@ func vipsExtract(image *C.VipsImage, left, top, width, height int) (*C.VipsImage return buf, nil } +func vipsSmartCrop(image *C.VipsImage, width, height int) (*C.VipsImage, error) { + var buf *C.VipsImage + defer C.g_object_unref(C.gpointer(image)) + + if width > MaxSize || height > MaxSize { + return nil, errors.New("Maximum image size exceeded") + } + + err := C.vips_smartcrop_bridge(image, &buf, C.int(width), C.int(height)) + if err != 0 { + return nil, catchVipsError() + } + + return buf, nil +} + func vipsShrinkJpeg(buf []byte, input *C.VipsImage, shrink int) (*C.VipsImage, error) { var image *C.VipsImage var ptr = unsafe.Pointer(&buf[0]) diff --git a/vips.h b/vips.h index febb516..a8fd6d5 100644 --- a/vips.h +++ b/vips.h @@ -303,9 +303,7 @@ vips_webpsave_bridge(VipsImage *in, void **buf, size_t *len, int strip, int qual int vips_tiffsave_bridge(VipsImage *in, void **buf, size_t *len) { #if (VIPS_MAJOR_VERSION >= 8 && VIPS_MINOR_VERSION >= 5) - return vips_tiffsave_buffer(in, buf, len, - NULL - ); + return vips_tiffsave_buffer(in, buf, len, NULL); #else return 0; #endif @@ -572,3 +570,12 @@ vips_watermark_image(VipsImage *in, VipsImage *sub, VipsImage **out, WatermarkIm g_object_unref(base); return 0; } + +int +vips_smartcrop_bridge(VipsImage *in, VipsImage **out, int width, int height) { +#if (VIPS_MAJOR_VERSION >= 8 && VIPS_MINOR_VERSION >= 5) + return vips_smartcrop(in, out, width, height, NULL); +#else + return 0; +#endif +}