From f1ffe66d1a2eb53b387fb58fdc8b301ab44593c2 Mon Sep 17 00:00:00 2001 From: Angelo Girardi Date: Tue, 12 Oct 2021 17:36:53 -0700 Subject: [PATCH 1/4] added trimColor and trim padding options --- go.mod | 3 +++ options.go | 10 ++++++++++ resizer.go | 27 ++++++++++++++++++++++++++- 3 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 go.mod 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/options.go b/options.go index 9a4d5aeb..84d84295 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 // private fields autoRotateOnly bool diff --git a/resizer.go b/resizer.go index ef1abfc2..43cfd4c6 100644 --- a/resizer.go +++ b/resizer.go @@ -300,8 +300,15 @@ 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) + background := o.Background + if o.TrimBackground != ColorBlack && o.Background != ColorBlack { + background = o.TrimBackground + } + left, top, width, height, err := vipsTrim(image, background, 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 +560,24 @@ func calculateCrop(inWidth, inHeight, outWidth, outHeight int, gravity Gravity) return left, top } +func calculatePadding(inLeft, inTop, areaWidth, areaHeight, inWidth, inHeight int, trimPaddingPercent TrimPaddingPercent) (left int, top int, width int, height int) { + top = inTop + left = inLeft + width = areaWidth + height = areaHeight + if trimPaddingPercent.X > 0 { + xBorder := math.Round(float64(inWidth) * float64(trimPaddingPercent.X/100)) + left = int(math.Max(0, float64(inLeft)-xBorder)) + width = int(math.Min(float64(inWidth)-float64(inLeft), float64(areaWidth)+2*xBorder)) + } + if trimPaddingPercent.Y > 0 { + yBorder := math.Round(float64(inHeight) * float64(trimPaddingPercent.X/100)) + top = int(math.Max(0, float64(inTop)-yBorder)) + height = int(math.Min(float64(inHeight)-float64(inTop), float64(areaHeight)+2*yBorder)) + } + return +} + func calculateRotationAndFlip(image *C.VipsImage, angle Angle) (Angle, bool) { rotate := D0 flip := false From 4799c5aaf24c16819a33f7c17c21d837eeb60f85 Mon Sep 17 00:00:00 2001 From: Angelo Girardi Date: Tue, 12 Oct 2021 17:59:40 -0700 Subject: [PATCH 2/4] fix resizer bug --- resizer.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resizer.go b/resizer.go index 43cfd4c6..582ab3bf 100644 --- a/resizer.go +++ b/resizer.go @@ -301,7 +301,7 @@ func extractOrEmbedImage(image *C.VipsImage, o Options) (*C.VipsImage, error) { break case o.Trim: background := o.Background - if o.TrimBackground != ColorBlack && o.Background != ColorBlack { + if o.TrimBackground != ColorBlack { background = o.TrimBackground } left, top, width, height, err := vipsTrim(image, background, o.Threshold) From e60e407211a069c201c60fe27ea53c9f2dc1c237 Mon Sep 17 00:00:00 2001 From: Angelo Girardi Date: Tue, 12 Oct 2021 18:05:10 -0700 Subject: [PATCH 3/4] fix bug with named returns --- resizer.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resizer.go b/resizer.go index 582ab3bf..63229c01 100644 --- a/resizer.go +++ b/resizer.go @@ -575,7 +575,7 @@ func calculatePadding(inLeft, inTop, areaWidth, areaHeight, inWidth, inHeight in top = int(math.Max(0, float64(inTop)-yBorder)) height = int(math.Min(float64(inHeight)-float64(inTop), float64(areaHeight)+2*yBorder)) } - return + return left, top, width, height } func calculateRotationAndFlip(image *C.VipsImage, angle Angle) (Angle, bool) { From d9f56e1ed6ba54203a87ef4f8ef43c243ae9e948 Mon Sep 17 00:00:00 2001 From: Angelo Girardi Date: Wed, 13 Oct 2021 17:05:16 -0700 Subject: [PATCH 4/4] added image padding background support --- image_test.go | 110 +++++++++++++++++++++++++++++++++++++++++--------- options.go | 2 +- resizer.go | 25 +++++++----- vips.go | 1 - 4 files changed, 107 insertions(+), 31 deletions(-) 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 84d84295..efb4de5f 100644 --- a/options.go +++ b/options.go @@ -237,7 +237,7 @@ type Options struct { // color of the background when trimming an alpha based image TrimBackground Color - TrimPaddingPercent + TrimPaddingPercent TrimPaddingPercent // private fields autoRotateOnly bool diff --git a/resizer.go b/resizer.go index 63229c01..03e5893f 100644 --- a/resizer.go +++ b/resizer.go @@ -300,15 +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: - background := o.Background - if o.TrimBackground != ColorBlack { - background = o.TrimBackground + // keeps compatibility with old APIs that only use Background + if o.TrimBackground == ColorBlack { + o.TrimBackground = o.Background } - left, top, width, height, err := vipsTrim(image, background, o.Threshold) + 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 @@ -560,18 +561,20 @@ func calculateCrop(inWidth, inHeight, outWidth, outHeight int, gravity Gravity) return left, top } -func calculatePadding(inLeft, inTop, areaWidth, areaHeight, inWidth, inHeight int, trimPaddingPercent TrimPaddingPercent) (left int, top int, width int, height int) { - top = inTop - left = inLeft - width = areaWidth - height = areaHeight +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 { - xBorder := math.Round(float64(inWidth) * float64(trimPaddingPercent.X/100)) + 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 { - yBorder := math.Round(float64(inHeight) * float64(trimPaddingPercent.X/100)) + 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)) } 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)