Преглед на файлове

Add gravity to extend option

DarthSim преди 5 години
родител
ревизия
05cd1286a4
променени са 6 файла, в които са добавени 88 реда и са изтрити 82 реда
  1. 1 0
      CHANGELOG.md
  2. 6 5
      docs/generating_the_url_advanced.md
  3. 35 24
      process.go
  4. 28 14
      processing_options.go
  5. 16 3
      processing_options_test.go
  6. 2 36
      vips.go

+ 1 - 0
CHANGELOG.md

@@ -8,6 +8,7 @@
 
 ### Changed
 - Docker image base is changed to Debian 10 for better stability and performance.
+- `extend` option now supports gravity.
 
 ## [2.7.0] - 2019-11-13
 ### Changed

+ 6 - 5
docs/generating_the_url_advanced.md

@@ -121,13 +121,14 @@ Default: false
 #### Extend
 
 ```
-extend:%extend
-ex:%extend
+extend:%extend:%gravity
+ex:%extend:%gravity
 ```
 
-When set to `1`, `t` or `true`, imgproxy will extend the image if it is smaller than the given size.
+* When `extend` is set to `1`, `t` or `true`, imgproxy will extend the image if it is smaller than the given size.
+* `gravity` _(optional)_ accepts the same values as [gravity](#gravity) option, except `sm`. When `gravity` is not set, imgproxy will use `ce` gravity without offsets.
 
-Default: false
+Default: `false:ce:0:0`
 
 #### Gravity
 
@@ -167,7 +168,7 @@ c:%width:%height:%gravity
 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` accepts the same values as [gravity](#gravity) option. When `gravity` is not set, imgproxy will use the value of the [gravity](#gravity) option.
+* `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.
 
 #### Quality
 

+ 35 - 24
process.go

@@ -157,40 +157,48 @@ func calcJpegShink(scale float64, imgtype imageType) int {
 	return 1
 }
 
-func calcCrop(width, height, cropWidth, cropHeight int, gravity *gravityOptions) (left, top int) {
+func calcPosition(width, height, innerWidth, innerHeight int, gravity *gravityOptions, allowOverflow bool) (left, top int) {
 	if gravity.Type == gravityFocusPoint {
 		pointX := scaleInt(width, gravity.X)
 		pointY := scaleInt(height, gravity.Y)
 
-		left = maxInt(0, minInt(pointX-cropWidth/2, width-cropWidth))
-		top = maxInt(0, minInt(pointY-cropHeight/2, height-cropHeight))
+		left = pointX - innerWidth/2
+		top = pointY - innerHeight/2
+	} else {
+		offX, offY := int(gravity.X), int(gravity.Y)
 
-		return
-	}
+		left = (width-innerWidth+1)/2 + offX
+		top = (height-innerHeight+1)/2 + offY
 
-	offX, offY := int(gravity.X), int(gravity.Y)
+		if gravity.Type == gravityNorth || gravity.Type == gravityNorthEast || gravity.Type == gravityNorthWest {
+			top = 0 + offY
+		}
 
-	left = (width-cropWidth+1)/2 + offX
-	top = (height-cropHeight+1)/2 + offY
+		if gravity.Type == gravityEast || gravity.Type == gravityNorthEast || gravity.Type == gravitySouthEast {
+			left = width - innerWidth - offX
+		}
 
-	if gravity.Type == gravityNorth || gravity.Type == gravityNorthEast || gravity.Type == gravityNorthWest {
-		top = 0 + offY
-	}
+		if gravity.Type == gravitySouth || gravity.Type == gravitySouthEast || gravity.Type == gravitySouthWest {
+			top = height - innerHeight - offY
+		}
 
-	if gravity.Type == gravityEast || gravity.Type == gravityNorthEast || gravity.Type == gravitySouthEast {
-		left = width - cropWidth - offX
+		if gravity.Type == gravityWest || gravity.Type == gravityNorthWest || gravity.Type == gravitySouthWest {
+			left = 0 + offX
+		}
 	}
 
-	if gravity.Type == gravitySouth || gravity.Type == gravitySouthEast || gravity.Type == gravitySouthWest {
-		top = height - cropHeight - offY
-	}
+	var minX, maxX, minY, maxY int
 
-	if gravity.Type == gravityWest || gravity.Type == gravityNorthWest || gravity.Type == gravitySouthWest {
-		left = 0 + offX
+	if allowOverflow {
+		minX, maxX = -innerWidth+1, width-1
+		minY, maxY = -innerHeight+1, height-1
+	} else {
+		minX, maxX = 0, width-innerWidth
+		minY, maxY = 0, height-innerHeight
 	}
 
-	left = maxInt(0, minInt(left, width-cropWidth))
-	top = maxInt(0, minInt(top, height-cropHeight))
+	left = maxInt(minX, minInt(left, maxX))
+	top = maxInt(minY, minInt(top, maxY))
 
 	return
 }
@@ -221,7 +229,7 @@ func cropImage(img *vipsImage, cropWidth, cropHeight int, gravity *gravityOption
 		return img.CopyMemory()
 	}
 
-	left, top := calcCrop(imgWidth, imgHeight, cropWidth, cropHeight, gravity)
+	left, top := calcPosition(imgWidth, imgHeight, cropWidth, cropHeight, gravity, false)
 	return img.Crop(left, top, cropWidth, cropHeight)
 }
 
@@ -253,7 +261,9 @@ func prepareWatermark(wm *vipsImage, wmData *imageData, opts *watermarkOptions,
 		return wm.Replicate(imgWidth, imgHeight)
 	}
 
-	return wm.Embed(opts.Gravity, imgWidth, imgHeight, opts.OffsetX, opts.OffsetY, rgbColor{0, 0, 0})
+	left, top := calcPosition(imgWidth, imgWidth, wm.Width(), wm.Height(), &opts.Gravity, true)
+
+	return wm.Embed(imgWidth, imgHeight, left, top, rgbColor{0, 0, 0})
 }
 
 func applyWatermark(img *vipsImage, wmData *imageData, opts *watermarkOptions, framesCount int) error {
@@ -440,8 +450,9 @@ func transformImage(ctx context.Context, img *vipsImage, data []byte, po *proces
 		}
 	}
 
-	if po.Extend && (po.Width > img.Width() || po.Height > img.Height()) {
-		if err = img.Embed(gravityCenter, po.Width, po.Height, 0, 0, po.Background); err != nil {
+	if po.Extend.Enabled && (po.Width > img.Width() || po.Height > img.Height()) {
+		offX, offY := calcPosition(po.Width, po.Height, img.Width(), img.Height(), &po.Extend.Gravity, false)
+		if err = img.Embed(po.Width, po.Height, offX, offY, po.Background); err != nil {
 			return err
 		}
 	}

+ 28 - 14
processing_options.go

@@ -89,6 +89,11 @@ type gravityOptions struct {
 	X, Y float64
 }
 
+type extendOptions struct {
+	Enabled bool
+	Gravity gravityOptions
+}
+
 type cropOptions struct {
 	Width   int
 	Height  int
@@ -99,9 +104,7 @@ type watermarkOptions struct {
 	Enabled   bool
 	Opacity   float64
 	Replicate bool
-	Gravity   gravityType
-	OffsetX   int
-	OffsetY   int
+	Gravity   gravityOptions
 	Scale     float64
 }
 
@@ -112,7 +115,7 @@ type processingOptions struct {
 	Dpr          float64
 	Gravity      gravityOptions
 	Enlarge      bool
-	Extend       bool
+	Extend       extendOptions
 	Crop         cropOptions
 	Format       imageType
 	Quality      int
@@ -194,6 +197,7 @@ func newProcessingOptions() *processingOptions {
 			Height:       0,
 			Gravity:      gravityOptions{Type: gravityCenter},
 			Enlarge:      false,
+			Extend:       extendOptions{Enabled: false, Gravity: gravityOptions{Type: gravityCenter}},
 			Quality:      conf.Quality,
 			MaxBytes:     0,
 			Format:       imageTypeUnknown,
@@ -201,7 +205,7 @@ func newProcessingOptions() *processingOptions {
 			Blur:         0,
 			Sharpen:      0,
 			Dpr:          1,
-			Watermark:    watermarkOptions{Opacity: 1, Replicate: false, Gravity: gravityCenter},
+			Watermark:    watermarkOptions{Opacity: 1, Replicate: false, Gravity: gravityOptions{Type: gravityCenter}},
 		}
 	})
 
@@ -416,17 +420,27 @@ func applyEnlargeOption(po *processingOptions, args []string) error {
 }
 
 func applyExtendOption(po *processingOptions, args []string) error {
-	if len(args) > 1 {
+	if len(args) > 4 {
 		return fmt.Errorf("Invalid extend arguments: %v", args)
 	}
 
-	po.Extend = parseBoolOption(args[0])
+	po.Extend.Enabled = parseBoolOption(args[0])
+
+	if len(args) > 1 {
+		if err := parseGravity(&po.Extend.Gravity, args[1:]); err != nil {
+			return err
+		}
+
+		if po.Extend.Gravity.Type == gravitySmart {
+			return errors.New("extend doesn't support smart gravity")
+		}
+	}
 
 	return nil
 }
 
 func applySizeOption(po *processingOptions, args []string) (err error) {
-	if len(args) > 4 {
+	if len(args) > 7 {
 		return fmt.Errorf("Invalid size arguments: %v", args)
 	}
 
@@ -448,8 +462,8 @@ func applySizeOption(po *processingOptions, args []string) (err error) {
 		}
 	}
 
-	if len(args) == 4 && len(args[3]) > 0 {
-		if err = applyExtendOption(po, args[3:4]); err != nil {
+	if len(args) >= 4 && len(args[3]) > 0 {
+		if err = applyExtendOption(po, args[3:]); err != nil {
 			return
 		}
 	}
@@ -472,7 +486,7 @@ func applyResizingTypeOption(po *processingOptions, args []string) error {
 }
 
 func applyResizeOption(po *processingOptions, args []string) error {
-	if len(args) > 5 {
+	if len(args) > 8 {
 		return fmt.Errorf("Invalid resize arguments: %v", args)
 	}
 
@@ -664,7 +678,7 @@ func applyWatermarkOption(po *processingOptions, args []string) error {
 		if args[1] == "re" {
 			po.Watermark.Replicate = true
 		} else if g, ok := gravityTypes[args[1]]; ok && g != gravityFocusPoint && g != gravitySmart {
-			po.Watermark.Gravity = g
+			po.Watermark.Gravity.Type = g
 		} else {
 			return fmt.Errorf("Invalid watermark position: %s", args[1])
 		}
@@ -672,7 +686,7 @@ func applyWatermarkOption(po *processingOptions, args []string) error {
 
 	if len(args) > 2 && len(args[2]) > 0 {
 		if x, err := strconv.Atoi(args[2]); err == nil {
-			po.Watermark.OffsetX = x
+			po.Watermark.Gravity.X = float64(x)
 		} else {
 			return fmt.Errorf("Invalid watermark X offset: %s", args[2])
 		}
@@ -680,7 +694,7 @@ func applyWatermarkOption(po *processingOptions, args []string) error {
 
 	if len(args) > 3 && len(args[3]) > 0 {
 		if y, err := strconv.Atoi(args[3]); err == nil {
-			po.Watermark.OffsetY = y
+			po.Watermark.Gravity.Y = float64(y)
 		} else {
 			return fmt.Errorf("Invalid watermark Y offset: %s", args[3])
 		}

+ 16 - 3
processing_options_test.go

@@ -196,6 +196,19 @@ func (s *ProcessingOptionsTestSuite) TestParsePathAdvancedEnlarge() {
 	assert.True(s.T(), po.Enlarge)
 }
 
+func (s *ProcessingOptionsTestSuite) TestParsePathAdvancedExtend() {
+	req := s.getRequest("http://example.com/unsafe/extend:1:so:10:20/plain/http://images.dev/lorem/ipsum.jpg")
+	ctx, err := parsePath(context.Background(), req)
+
+	require.Nil(s.T(), err)
+
+	po := getProcessingOptions(ctx)
+	assert.Equal(s.T(), true, po.Extend.Enabled)
+	assert.Equal(s.T(), gravitySouth, po.Extend.Gravity.Type)
+	assert.Equal(s.T(), 10.0, po.Extend.Gravity.X)
+	assert.Equal(s.T(), 20.0, po.Extend.Gravity.Y)
+}
+
 func (s *ProcessingOptionsTestSuite) TestParsePathAdvancedGravity() {
 	req := s.getRequest("http://example.com/unsafe/gravity:soea/plain/http://images.dev/lorem/ipsum.jpg")
 	ctx, err := parsePath(context.Background(), req)
@@ -300,9 +313,9 @@ func (s *ProcessingOptionsTestSuite) TestParsePathAdvancedWatermark() {
 
 	po := getProcessingOptions(ctx)
 	assert.True(s.T(), po.Watermark.Enabled)
-	assert.Equal(s.T(), gravitySouthEast, po.Watermark.Gravity)
-	assert.Equal(s.T(), 10, po.Watermark.OffsetX)
-	assert.Equal(s.T(), 20, po.Watermark.OffsetY)
+	assert.Equal(s.T(), gravitySouthEast, po.Watermark.Gravity.Type)
+	assert.Equal(s.T(), 10.0, po.Watermark.Gravity.X)
+	assert.Equal(s.T(), 20.0, po.Watermark.Gravity.Y)
 	assert.Equal(s.T(), 0.6, po.Watermark.Scale)
 }
 

+ 2 - 36
vips.go

@@ -489,41 +489,7 @@ func (img *vipsImage) Replicate(width, height int) error {
 	return nil
 }
 
-func (img *vipsImage) Embed(gravity gravityType, width, height int, offX, offY int, bg rgbColor) error {
-	wmWidth := img.Width()
-	wmHeight := img.Height()
-
-	left := (width-wmWidth+1)/2 + offX
-	top := (height-wmHeight+1)/2 + offY
-
-	if gravity == gravityNorth || gravity == gravityNorthEast || gravity == gravityNorthWest {
-		top = offY
-	}
-
-	if gravity == gravityEast || gravity == gravityNorthEast || gravity == gravitySouthEast {
-		left = width - wmWidth - offX
-	}
-
-	if gravity == gravitySouth || gravity == gravitySouthEast || gravity == gravitySouthWest {
-		top = height - wmHeight - offY
-	}
-
-	if gravity == gravityWest || gravity == gravityNorthWest || gravity == gravitySouthWest {
-		left = offX
-	}
-
-	if left > width {
-		left = width - wmWidth
-	} else if left < -wmWidth {
-		left = 0
-	}
-
-	if top > height {
-		top = height - wmHeight
-	} else if top < -wmHeight {
-		top = 0
-	}
-
+func (img *vipsImage) Embed(width, height int, offX, offY int, bg rgbColor) error {
 	if err := img.RgbColourspace(); err != nil {
 		return err
 	}
@@ -536,7 +502,7 @@ func (img *vipsImage) Embed(gravity gravityType, width, height int, offX, offY i
 	}
 
 	var tmp *C.VipsImage
-	if C.vips_embed_go(img.VipsImage, &tmp, C.int(left), C.int(top), C.int(width), C.int(height), &bgc[0], C.int(len(bgc))) != 0 {
+	if C.vips_embed_go(img.VipsImage, &tmp, C.int(offX), C.int(offY), C.int(width), C.int(height), &bgc[0], C.int(len(bgc))) != 0 {
 		return vipsError()
 	}
 	C.swap_and_clear(&img.VipsImage, tmp)