Browse Source

Crop offsets

DarthSim 6 years ago
parent
commit
e894afec13
3 changed files with 88 additions and 45 deletions
  1. 20 15
      docs/generating_the_url_advanced.md
  2. 39 17
      process.go
  3. 29 13
      processing_options.go

+ 20 - 15
docs/generating_the_url_advanced.md

@@ -120,25 +120,30 @@ Default: `0`
 ##### Gravity
 
 ```
-gravity:%gravity
-g:%gravity
+gravity:%gravity_type:%x_offset:%y_offset
+g:%gravity_type:%x_offset:%y_offset
 ```
 
-When imgproxy needs to cut some parts of the image, it is guided by the gravity. The following values are supported:
+When imgproxy needs to cut some parts of the image, it is guided by the gravity.
 
-* `no`: north (top edge);
-* `so`: south (bottom edge);
-* `ea`: east (right edge);
-* `we`: west (left edge);
-* `noea`: north-east (top-right corner);
-* `nowe`: north-west (top-left corner);
-* `soea`: south-east (bottom-right corner);
-* `sowe`: south-west (bottom-left corner);
-* `ce`: center;
-* `sm`: smart. `libvips` detects the most "interesting" section of the image and considers it as the center of the resulting image;
-* `fp:%x:%y`: focus point. `x` and `y` are floating point numbers between 0 and 1 that define the coordinates of the center of the resulting image. Treat 0 and 1 as right/left for `x` and top/bottom for `y`.
+* `gravity_type` - specifies the gravity type. Available values:
+  * `no`: north (top edge);
+  * `so`: south (bottom edge);
+  * `ea`: east (right edge);
+  * `we`: west (left edge);
+  * `noea`: north-east (top-right corner);
+  * `nowe`: north-west (top-left corner);
+  * `soea`: south-east (bottom-right corner);
+  * `sowe`: south-west (bottom-left corner);
+  * `ce`: center.
+* `x_offset`, `y_offset` - (optional) specify gravity offset by X and Y axes.
+
+Default: `ce:0:0`
+
+###### Special gravities:
 
-Default: `ce`
+* `gravity:sm` - smart gravity. `libvips` detects the most "interesting" section of the image and considers it as the center of the resulting image. Offsets are not applicable here;
+* `gravity:fp:%x:%y` - focus point gravity. `x` and `y` are floating point numbers between 0 and 1 that define the coordinates of the center of the resulting image. Treat 0 and 1 as right/left for `x` and top/bottom for `y`.
 
 ##### Crop
 

+ 39 - 17
process.go

@@ -115,25 +115,30 @@ func calcCrop(width, height, cropWidth, cropHeight int, gravity *gravityOptions)
 		return
 	}
 
-	left = (width - cropWidth + 1) / 2
-	top = (height - cropHeight + 1) / 2
+	offX, offY := int(gravity.X), int(gravity.Y)
+
+	left = (width-cropWidth+1)/2 + offX
+	top = (height-cropHeight+1)/2 + offY
 
 	if gravity.Type == gravityNorth || gravity.Type == gravityNorthEast || gravity.Type == gravityNorthWest {
-		top = 0
+		top = 0 + offY
 	}
 
 	if gravity.Type == gravityEast || gravity.Type == gravityNorthEast || gravity.Type == gravitySouthEast {
-		left = width - cropWidth
+		left = width - cropWidth - offX
 	}
 
 	if gravity.Type == gravitySouth || gravity.Type == gravitySouthEast || gravity.Type == gravitySouthWest {
-		top = height - cropHeight
+		top = height - cropHeight - offY
 	}
 
 	if gravity.Type == gravityWest || gravity.Type == gravityNorthWest || gravity.Type == gravitySouthWest {
-		left = 0
+		left = 0 + offX
 	}
 
+	left = maxInt(0, minInt(left, width-cropWidth))
+	top = maxInt(0, minInt(top, height-cropHeight))
+
 	return
 }
 
@@ -176,14 +181,28 @@ func cropImage(img *vipsImage, cropWidth, cropHeight int, gravity *gravityOption
 	return img.Crop(left, top, cropWidth, cropHeight)
 }
 
+func scaleSize(size int, scale float64) int {
+	if size == 0 {
+		return 0
+	}
+
+	return roundToInt(float64(size) * scale)
+}
+
 func transformImage(ctx context.Context, img *vipsImage, data []byte, po *processingOptions, imgtype imageType) error {
 	var err error
 
 	srcWidth, srcHeight, angle, flip := extractMeta(img)
 
-	widthToScale, heightToScale := srcWidth, srcHeight
 	cropWidth, cropHeight := po.Crop.Width, po.Crop.Height
 
+	cropGravity := po.Crop.Gravity
+	if cropGravity.Type == gravityUnknown {
+		cropGravity = po.Gravity
+	}
+
+	widthToScale, heightToScale := srcWidth, srcHeight
+
 	if cropWidth > 0 {
 		widthToScale = minInt(cropWidth, srcWidth)
 	}
@@ -193,8 +212,10 @@ func transformImage(ctx context.Context, img *vipsImage, data []byte, po *proces
 
 	scale := calcScale(widthToScale, heightToScale, po, imgtype)
 
-	cropWidth = roundToInt(float64(cropWidth) * scale)
-	cropHeight = roundToInt(float64(cropHeight) * scale)
+	cropWidth = scaleSize(cropWidth, scale)
+	cropHeight = scaleSize(cropHeight, scale)
+	cropGravity.X = cropGravity.X * scale
+	cropGravity.Y = cropGravity.Y * scale
 
 	if scale != 1 && data != nil && canScaleOnLoad(imgtype, scale) {
 		if imgtype == imageTypeWEBP || imgtype == imageTypeSVG {
@@ -214,8 +235,8 @@ func transformImage(ctx context.Context, img *vipsImage, data []byte, po *proces
 		// Update scale after scale-on-load
 		newWidth, newHeight, _, _ := extractMeta(img)
 
-		widthToScale = roundToInt(float64(widthToScale) * float64(newWidth) / float64(srcWidth))
-		heightToScale = roundToInt(float64(heightToScale) * float64(newHeight) / float64(srcHeight))
+		widthToScale = scaleSize(widthToScale, float64(newWidth)/float64(srcWidth))
+		heightToScale = scaleSize(heightToScale, float64(newHeight)/float64(srcHeight))
 
 		scale = calcScale(widthToScale, heightToScale, po, imgtype)
 	}
@@ -269,11 +290,6 @@ func transformImage(ctx context.Context, img *vipsImage, data []byte, po *proces
 	dprWidth := roundToInt(float64(po.Width) * po.Dpr)
 	dprHeight := roundToInt(float64(po.Height) * po.Dpr)
 
-	cropGravity := po.Crop.Gravity
-	if cropGravity.Type == gravityUnknown {
-		cropGravity = po.Gravity
-	}
-
 	if cropGravity.Type == po.Gravity.Type && cropGravity.Type != gravityFocusPoint {
 		if cropWidth == 0 {
 			cropWidth = dprWidth
@@ -287,7 +303,13 @@ func transformImage(ctx context.Context, img *vipsImage, data []byte, po *proces
 			cropHeight = minInt(cropHeight, dprHeight)
 		}
 
-		if err = cropImage(img, cropWidth, cropHeight, &cropGravity); err != nil {
+		sumGravity := gravityOptions{
+			Type: cropGravity.Type,
+			X:    cropGravity.X + po.Gravity.X,
+			Y:    cropGravity.Y + po.Gravity.Y,
+		}
+
+		if err = cropImage(img, cropWidth, cropHeight, &sumGravity); err != nil {
 			return err
 		}
 	} else {

+ 29 - 13
processing_options.go

@@ -52,11 +52,6 @@ var gravityTypes = map[string]gravityType{
 	"fp":   gravityFocusPoint,
 }
 
-type gravityOptions struct {
-	Type gravityType
-	X, Y float64
-}
-
 type resizeType int
 
 const (
@@ -80,6 +75,11 @@ const (
 	hexColorShortFormat = "%1x%1x%1x"
 )
 
+type gravityOptions struct {
+	Type gravityType
+	X, Y float64
+}
+
 type cropOptions struct {
 	Width   int
 	Height  int
@@ -258,31 +258,47 @@ func parseDimension(d *int, name, arg string) error {
 	return nil
 }
 
+func isGravityOffcetValid(gravity gravityType, offset float64) bool {
+	if gravity == gravityCenter {
+		return true
+	}
+
+	return offset >= 0 && (gravity != gravityFocusPoint || offset <= 1)
+}
+
 func parseGravity(g *gravityOptions, args []string) error {
+	nArgs := len(args)
+
+	if nArgs > 3 {
+		return fmt.Errorf("Invalid gravity arguments: %v", args)
+	}
+
 	if t, ok := gravityTypes[args[0]]; ok {
 		g.Type = t
 	} else {
 		return fmt.Errorf("Invalid gravity: %s", args[0])
 	}
 
-	if g.Type == gravityFocusPoint {
-		if len(args) != 3 {
-			return fmt.Errorf("Invalid gravity arguments: %v", args)
-		}
+	if g.Type == gravitySmart && nArgs > 1 {
+		return fmt.Errorf("Invalid gravity arguments: %v", args)
+	} else if g.Type == gravityFocusPoint && nArgs != 3 {
+		return fmt.Errorf("Invalid gravity arguments: %v", args)
+	}
 
-		if x, err := strconv.ParseFloat(args[1], 64); err == nil && x >= 0 && x <= 1 {
+	if nArgs > 1 {
+		if x, err := strconv.ParseFloat(args[1], 64); err == nil && isGravityOffcetValid(g.Type, x) {
 			g.X = x
 		} else {
 			return fmt.Errorf("Invalid gravity X: %s", args[1])
 		}
+	}
 
-		if y, err := strconv.ParseFloat(args[2], 64); err == nil && y >= 0 && y <= 1 {
+	if nArgs > 2 {
+		if y, err := strconv.ParseFloat(args[2], 64); err == nil && isGravityOffcetValid(g.Type, y) {
 			g.Y = y
 		} else {
 			return fmt.Errorf("Invalid gravity Y: %s", args[2])
 		}
-	} else if len(args) > 1 {
-		return fmt.Errorf("Invalid gravity arguments: %v", args)
 	}
 
 	return nil