Browse Source

Trim processing option

DarthSim 5 years ago
parent
commit
ee450bec46
7 changed files with 115 additions and 2 deletions
  1. 2 0
      CHANGELOG.md
  2. 14 0
      docs/generating_the_url_advanced.md
  3. 17 2
      process.go
  4. 24 0
      processing_options.go
  5. 42 0
      vips.c
  6. 15 0
      vips.go
  7. 1 0
      vips.h

+ 2 - 0
CHANGELOG.md

@@ -1,6 +1,8 @@
 # Changelog
 
 ## [Unreleased]
+### Added
+- `trim` processing option.
 
 ## [2.8.2] - 2020-01-13
 ### Changed

+ 14 - 0
docs/generating_the_url_advanced.md

@@ -170,6 +170,20 @@ Defines an area of the image to be processed (crop before resize).
 * `width` and `height` define the size of the area. When `width` or `height` is set to `0`, imgproxy will use the full width/height of the source image.
 * `gravity` _(optional)_ accepts the same values as [gravity](#gravity) option. When `gravity` is not set, imgproxy will use the value of the [gravity](#gravity) option.
 
+#### Trim
+
+```
+trim:%threshold
+t:%threshold
+```
+
+Removes surrounding background.
+
+* `threshold` - color similarity tolerance.
+
+**Warning:** Trimming requires an image to be fully loaded into memory. This disables scale-on-load and significantly increases memory usage and processing time. Use it carefully with large images.
+**Note:** Trimming of animated images is not supported.
+
 #### Quality
 
 ```

+ 17 - 2
process.go

@@ -297,7 +297,17 @@ func applyWatermark(img *vipsImage, wmData *imageData, opts *watermarkOptions, f
 }
 
 func transformImage(ctx context.Context, img *vipsImage, data []byte, po *processingOptions, imgtype imageType) error {
-	var err error
+	var (
+		err     error
+		trimmed bool
+	)
+
+	if po.Trim.Enabled {
+		if err = img.Trim(po.Trim.Threshold); err != nil {
+			return err
+		}
+		trimmed = true
+	}
 
 	srcWidth, srcHeight, angle, flip := extractMeta(img)
 	cropWidth, cropHeight := po.Crop.Width, po.Crop.Height
@@ -317,7 +327,7 @@ func transformImage(ctx context.Context, img *vipsImage, data []byte, po *proces
 	cropGravity.X *= scale
 	cropGravity.Y *= scale
 
-	if scale != 1 && data != nil && canScaleOnLoad(imgtype, scale) {
+	if !trimmed && scale != 1 && data != nil && canScaleOnLoad(imgtype, scale) {
 		if imgtype == imageTypeWEBP || imgtype == imageTypeSVG {
 			// Do some scale-on-load
 			if err = img.Load(data, imgtype, 1, scale, 1); err != nil {
@@ -454,6 +464,11 @@ func transformImage(ctx context.Context, img *vipsImage, data []byte, po *proces
 }
 
 func transformAnimated(ctx context.Context, img *vipsImage, data []byte, po *processingOptions, imgtype imageType) error {
+	if po.Trim.Enabled {
+		logWarning("Trim is not supported for animated images")
+		po.Trim.Enabled = false
+	}
+
 	imgWidth := img.Width()
 
 	frameHeight, err := img.GetInt("page-height")

+ 24 - 0
processing_options.go

@@ -100,6 +100,11 @@ type cropOptions struct {
 	Gravity gravityOptions
 }
 
+type trimOptions struct {
+	Enabled   bool
+	Threshold float64
+}
+
 type watermarkOptions struct {
 	Enabled   bool
 	Opacity   float64
@@ -117,6 +122,7 @@ type processingOptions struct {
 	Enlarge      bool
 	Extend       extendOptions
 	Crop         cropOptions
+	Trim         trimOptions
 	Format       imageType
 	Quality      int
 	MaxBytes     int
@@ -198,6 +204,7 @@ func newProcessingOptions() *processingOptions {
 			Gravity:      gravityOptions{Type: gravityCenter},
 			Enlarge:      false,
 			Extend:       extendOptions{Enabled: false, Gravity: gravityOptions{Type: gravityCenter}},
+			Trim:         trimOptions{Enabled: false, Threshold: 10},
 			Quality:      conf.Quality,
 			MaxBytes:     0,
 			Format:       imageTypeUnknown,
@@ -545,6 +552,21 @@ func applyCropOption(po *processingOptions, args []string) error {
 	return nil
 }
 
+func applyTrimOption(po *processingOptions, args []string) error {
+	if len(args) > 1 {
+		return fmt.Errorf("Invalid crop arguments: %v", args)
+	}
+
+	if t, err := strconv.ParseFloat(args[0], 64); err == nil && t >= 0 {
+		po.Trim.Enabled = true
+		po.Trim.Threshold = t
+	} else {
+		return fmt.Errorf("Invalid trim treshold: %s", args[0])
+	}
+
+	return nil
+}
+
 func applyQualityOption(po *processingOptions, args []string) error {
 	if len(args) > 1 {
 		return fmt.Errorf("Invalid quality arguments: %v", args)
@@ -773,6 +795,8 @@ func applyProcessingOption(po *processingOptions, name string, args []string) er
 		return applyGravityOption(po, args)
 	case "crop", "c":
 		return applyCropOption(po, args)
+	case "trim", "t":
+		return applyTrimOption(po, args)
 	case "quality", "q":
 		return applyQualityOption(po, args)
 	case "max_bytes", "mb":

+ 42 - 0
vips.c

@@ -389,6 +389,48 @@ vips_extract_area_go(VipsImage *in, VipsImage **out, int left, int top, int widt
   return vips_extract_area(in, out, left, top, width, height, NULL);
 }
 
+int
+vips_trim(VipsImage *in, VipsImage **out, double threshold) {
+  VipsImage *tmp;
+
+  if (vips_image_hasalpha(in)) {
+    if (vips_flatten(in, &tmp, NULL))
+      return 1;
+  } else {
+    if (vips_copy(in, &tmp, NULL))
+      return 1;
+  }
+
+  double *bg;
+  int bgn;
+
+  if (vips_getpoint(tmp, &bg, &bgn, 0, 0, NULL)) {
+    clear_image(&tmp);
+    return 1;
+  }
+
+  VipsArrayDouble *bga = vips_array_double_new(bg, bgn);
+
+  int left, top, width, height;
+
+  if (vips_find_trim(tmp, &left, &top, &width, &height, "background", bga, "threshold", threshold, NULL)) {
+    clear_image(&tmp);
+    vips_area_unref((VipsArea *)bga);
+    g_free(bg);
+    return 1;
+  }
+
+  clear_image(&tmp);
+  vips_area_unref((VipsArea *)bga);
+  g_free(bg);
+
+  if (width == 0 || height == 0) {
+    return vips_copy(in, out, NULL);
+  }
+
+  return vips_extract_area(in, out, left, top, width, height, NULL);
+}
+
 int
 vips_replicate_go(VipsImage *in, VipsImage **out, int width, int height) {
   VipsImage *tmp;

+ 15 - 0
vips.go

@@ -356,6 +356,21 @@ func (img *vipsImage) SmartCrop(width, height int) error {
 	return nil
 }
 
+func (img *vipsImage) Trim(threshold float64) error {
+	var tmp *C.VipsImage
+
+	if err := img.CopyMemory(); err != nil {
+		return err
+	}
+
+	if C.vips_trim(img.VipsImage, &tmp, C.double(threshold)) != 0 {
+		return vipsError()
+	}
+
+	C.swap_and_clear(&img.VipsImage, tmp)
+	return nil
+}
+
 func (img *vipsImage) EnsureAlpha() error {
 	var tmp *C.VipsImage
 

+ 1 - 0
vips.h

@@ -66,6 +66,7 @@ int vips_flip_horizontal_go(VipsImage *in, VipsImage **out);
 
 int vips_extract_area_go(VipsImage *in, VipsImage **out, int left, int top, int width, int height);
 int vips_smartcrop_go(VipsImage *in, VipsImage **out, int width, int height);
+int vips_trim(VipsImage *in, VipsImage **out, double threshold);
 
 int vips_gaussblur_go(VipsImage *in, VipsImage **out, double sigma);
 int vips_sharpen_go(VipsImage *in, VipsImage **out, double sigma);