diff --git a/go.mod b/go.mod new file mode 100644 index 00000000..c9a9062a --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/goatapp/bimg + +go 1.17 diff --git a/image_test.go b/image_test.go index 76e819d7..fb4e24f8 100644 --- a/image_test.go +++ b/image_test.go @@ -533,28 +533,102 @@ func TestImageTrim(t *testing.T) { } func TestImageTrimParameters(t *testing.T) { + t.Run("When trim parameters include Background", func(t *testing.T) { + if !(VipsMajorVersion >= 8 && VipsMinorVersion >= 6) { + t.Skipf("Skipping this test, libvips doesn't meet version requirement %s >= 8.6", VipsVersion) + } - if !(VipsMajorVersion >= 8 && VipsMinorVersion >= 6) { - t.Skipf("Skipping this test, libvips doesn't meet version requirement %s >= 8.6", VipsVersion) - } + i := initImage("test.png") + options := Options{ + Trim: true, + Background: Color{0.0, 0.0, 0.0}, + Threshold: 10.0, + } + buf, err := i.Process(options) + if err != nil { + t.Errorf("Cannot process the image: %#v", err) + } - i := initImage("test.png") - options := Options{ - Trim: true, - Background: Color{0.0, 0.0, 0.0}, - Threshold: 10.0, - } - buf, err := i.Process(options) - if err != nil { - t.Errorf("Cannot process the image: %#v", err) - } + err = assertSize(buf, 400, 257) + if err != nil { + t.Errorf("The image wasn't trimmed. %s", err) + } - err = assertSize(buf, 400, 257) - if err != nil { - t.Errorf("The image wasn't trimmed.") - } + Write("testdata/parameter_trim.png", buf) + }) + t.Run("When trim parameters include TrimBackground Transparent PNG", func(t *testing.T) { + if !(VipsMajorVersion >= 8 && VipsMinorVersion >= 6) { + t.Skipf("Skipping this test, libvips doesn't meet version requirement %s >= 8.6", VipsVersion) + } - Write("testdata/parameter_trim.png", buf) + i := initImage("transparent.png") + options := Options{ + Trim: true, + TrimBackground: Color{0, 0, 0}, + Threshold: 10.0, + } + buf, err := i.Process(options) + if err != nil { + t.Errorf("Cannot process the image: %#v", err) + } + + err = assertSize(buf, 247, 206) + if err != nil { + t.Errorf("The image wasn't trimmed. %s", err) + } + + Write("testdata/trim_background.png", buf) + }) + t.Run("When trim parameters include TrimBackground Background JPEG", func(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("test_1000_yeezy_additional_photo.jpeg") + options := Options{ + Trim: true, + TrimBackground: Color{255, 255, 255}, + Threshold: 20.0, + } + buf, err := i.Process(options) + if err != nil { + t.Errorf("Cannot process the image: %#v", err) + } + + err = assertSize(buf, 535, 274) + if err != nil { + t.Errorf("The image wasn't trimmed. %s", err) + } + + Write("testdata/trimmed_product_additional_photo.jpeg", buf) + }) + t.Run("When trim parameters include TrimBackground Background And Padding", func(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("test_1000_yeezy_additional_photo.jpeg") + options := Options{ + Trim: true, + TrimBackground: Color{255, 255, 255}, + Threshold: 20.0, + TrimPaddingPercent: TrimPaddingPercent{ + X: 1, + Y: 1, + }, + } + buf, err := i.Process(options) + if err != nil { + t.Errorf("Cannot process the image: %#v", err) + } + + err = assertSize(buf, 545, 288) + if err != nil { + t.Errorf("The image wasn't trimmed. %s", err) + } + + Write("testdata/trimmed_product_additional_photo_padded.jpeg", buf) + }) } func TestImageLength(t *testing.T) { diff --git a/options.go b/options.go index 9a4d5aeb..efb4de5f 100644 --- a/options.go +++ b/options.go @@ -170,6 +170,12 @@ type WatermarkImage struct { Opacity float32 } +// represents percentage of padding on an image trim extract +type TrimPaddingPercent struct { + X int + Y int +} + // GaussianBlur represents the gaussian image transformation values. type GaussianBlur struct { Sigma float64 @@ -228,6 +234,10 @@ type Options struct { Palette bool // Speed defines the AVIF encoders CPU effort. Valid values are 0-8. Speed int + // color of the background when trimming an alpha based image + TrimBackground Color + + TrimPaddingPercent TrimPaddingPercent // private fields autoRotateOnly bool diff --git a/resizer.go b/resizer.go index ef1abfc2..03e5893f 100644 --- a/resizer.go +++ b/resizer.go @@ -300,8 +300,16 @@ func extractOrEmbedImage(image *C.VipsImage, o Options) (*C.VipsImage, error) { 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, o.Background, o.Threshold) + // keeps compatibility with old APIs that only use Background + if o.TrimBackground == ColorBlack { + o.TrimBackground = o.Background + } + left, top, width, height, err := vipsTrim(image, o.TrimBackground, o.Threshold) if err == nil { + if o.TrimPaddingPercent.X > 0 || o.TrimPaddingPercent.Y > 0 { + left, top, width, height = calculatePadding(left, top, width, height, inWidth, inHeight, o.TrimPaddingPercent) + } + image, err = vipsExtract(image, left, top, width, height) } break @@ -553,6 +561,26 @@ func calculateCrop(inWidth, inHeight, outWidth, outHeight int, gravity Gravity) return left, top } +func calculatePadding(inLeft, inTop, areaWidth, areaHeight, inWidth, inHeight int, trimPaddingPercent TrimPaddingPercent) (int, int, int, int) { + top := inTop + left := inLeft + width := areaWidth + height := areaHeight + if trimPaddingPercent.X > 0 { + paddingPercent := (float64(trimPaddingPercent.X) / 100) + xBorder := math.Round(float64(areaWidth) * paddingPercent) + left = int(math.Max(0, float64(inLeft)-xBorder)) + width = int(math.Min(float64(inWidth)-float64(inLeft), float64(areaWidth)+2*xBorder)) + } + if trimPaddingPercent.Y > 0 { + paddingPercent := (float64(trimPaddingPercent.Y) / 100) + yBorder := math.Round(float64(inHeight) * paddingPercent) + top = int(math.Max(0, float64(inTop)-yBorder)) + height = int(math.Min(float64(inHeight)-float64(inTop), float64(areaHeight)+2*yBorder)) + } + return left, top, width, height +} + func calculateRotationAndFlip(image *C.VipsImage, angle Angle) (Angle, bool) { rotate := D0 flip := false diff --git a/vips.go b/vips.go index 906a9355..ee904cbb 100644 --- a/vips.go +++ b/vips.go @@ -594,7 +594,6 @@ func vipsSmartCrop(image *C.VipsImage, width, height int) (*C.VipsImage, error) } func vipsTrim(image *C.VipsImage, background Color, threshold float64) (int, int, int, int, error) { - defer C.g_object_unref(C.gpointer(image)) top := C.int(0) left := C.int(0)