diff --git a/README.md b/README.md index 6c687605..17f8037d 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ If you're using `gopkg.in`, you can still rely in the `v0` without worrying abou - Resize - Enlarge -- Crop (including smart crop support) +- Crop (including smart crop support, libvips 8.5+) - Rotate (with auto-rotate based on EXIF orientation) - Flip (with auto-flip based on EXIF metadata) - Flop @@ -47,6 +47,7 @@ If you're using `gopkg.in`, you can still rely in the `v0` without worrying abou - Custom output color space (RGB, grayscale...) - Format conversion (with additional quality/compression settings) - EXIF metadata (size, alpha channel, profile, orientation...) +- Trim (libvips 8.6+) ## Prerequisites diff --git a/fixtures/transparent_trim.png b/fixtures/transparent_trim.png new file mode 100644 index 00000000..80878106 Binary files /dev/null and b/fixtures/transparent_trim.png differ diff --git a/image.go b/image.go index 7cff0791..093d5a5a 100644 --- a/image.go +++ b/image.go @@ -178,6 +178,13 @@ func (i *Image) Colourspace(c Interpretation) ([]byte, error) { return i.Process(options) } +// Trim removes the background from the picture. It can result in a 0x0 output +// if the image is all background. +func (i *Image) Trim() ([]byte, error) { + options := Options{Trim: true} + return i.Process(options) +} + // Process processes the image based on the given transformation options, // talking with libvips bindings accordingly and returning the resultant // image buffer. diff --git a/image_test.go b/image_test.go index aff3d3d3..e7c431b2 100644 --- a/image_test.go +++ b/image_test.go @@ -457,8 +457,8 @@ func TestFluentInterface(t *testing.T) { func TestImageSmartCrop(t *testing.T) { - if !(VipsMajorVersion >= 8 && VipsMinorVersion > 4) { - t.Skipf("Skipping this test, libvips doesn't meet version requirement %s > 8.4", VipsVersion) + if !(VipsMajorVersion >= 8 && VipsMinorVersion >= 5) { + t.Skipf("Skipping this test, libvips doesn't meet version requirement %s >= 8.5", VipsVersion) } i := initImage("northern_cardinal_bird.jpg") @@ -475,6 +475,26 @@ func TestImageSmartCrop(t *testing.T) { Write("fixtures/test_smart_crop.jpg", buf) } +func TestImageTrim(t *testing.T) { + + if !(VipsMajorVersion >= 8 && VipsMinorVersion >= 6) { + t.Skipf("Skipping this test, libvips doesn't meet version requirement %s >= 8.6", VipsVersion) + } + + i := initImage("transparent.png") + buf, err := i.Trim() + if err != nil { + t.Errorf("Cannot process the image: %#v", err) + } + + err = assertSize(buf, 250, 208) + if err != nil { + t.Errorf("The image wasn't trimmed.") + } + + Write("fixtures/transparent_trim.png", buf) +} + func TestImageLength(t *testing.T) { i := initImage("test.jpg") diff --git a/options.go b/options.go index f155f5e3..609de606 100644 --- a/options.go +++ b/options.go @@ -205,6 +205,7 @@ type Options struct { NoProfile bool Interlace bool StripMetadata bool + Trim bool Extend Extend Rotate Angle Background Color diff --git a/resizer.go b/resizer.go index 8e7e9c45..eab4d041 100644 --- a/resizer.go +++ b/resizer.go @@ -175,7 +175,8 @@ func normalizeOperation(o *Options, inWidth, inHeight int) { func shouldTransformImage(o Options, inWidth, inHeight int) bool { return o.Force || (o.Width > 0 && o.Width != inWidth) || - (o.Height > 0 && o.Height != inHeight) || o.AreaWidth > 0 || o.AreaHeight > 0 + (o.Height > 0 && o.Height != inHeight) || o.AreaWidth > 0 || o.AreaHeight > 0 || + o.Trim } func shouldApplyEffects(o Options) bool { @@ -268,7 +269,12 @@ func extractOrEmbedImage(image *C.VipsImage, o Options) (*C.VipsImage, error) { left, top := (o.Width-inWidth)/2, (o.Height-inHeight)/2 image, err = vipsEmbed(image, left, top, o.Width, o.Height, o.Extend, o.Background) break - + case o.Trim: + left, top, width, height, err := vipsTrim(image) + if err == nil { + image, err = vipsExtract(image, left, top, width, height) + } + break case o.Top != 0 || o.Left != 0 || o.AreaWidth != 0 || o.AreaHeight != 0: if o.AreaWidth == 0 { o.AreaHeight = o.Width diff --git a/vips.go b/vips.go index 5843df22..1cc27743 100644 --- a/vips.go +++ b/vips.go @@ -503,6 +503,22 @@ func vipsSmartCrop(image *C.VipsImage, width, height int) (*C.VipsImage, error) return buf, nil } +func vipsTrim(image *C.VipsImage) (int, int, int, int, error) { + defer C.g_object_unref(C.gpointer(image)) + + top := C.int(0) + left := C.int(0) + width := C.int(0) + height := C.int(0) + + err := C.vips_find_trim_bridge(image, &top, &left, &width, &height) + if err != 0 { + return 0, 0, 0, 0, catchVipsError() + } + + return int(top), int(left), int(width), int(height), 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 30e68478..f7277519 100644 --- a/vips.h +++ b/vips.h @@ -539,3 +539,11 @@ vips_smartcrop_bridge(VipsImage *in, VipsImage **out, int width, int height) { return 0; #endif } + +int vips_find_trim_bridge(VipsImage *in, int *top, int *left, int *width, int *height) { +#if (VIPS_MAJOR_VERSION >= 8 && VIPS_MINOR_VERSION >= 6) + return vips_find_trim(in, top, left, width, height, NULL); +#else + return 0; +#endif +}