Skip to content
Permalink
Browse files

Add gravity to extend option

  • Loading branch information
DarthSim committed Dec 25, 2019
1 parent 7c7ac56 commit 05cd1286a427afeebe26cc8141bfc9928df25a6c
Showing with 88 additions and 82 deletions.
  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
@@ -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
@@ -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

@@ -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
}
}
@@ -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,14 +197,15 @@ 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,
Background: rgbColor{255, 255, 255},
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,23 +678,23 @@ 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])
}
}

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])
}
}

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])
}
@@ -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)
}

38 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)

0 comments on commit 05cd128

Please sign in to comment.
You can’t perform that action at this time.