1
0
mirror of https://github.com/imgproxy/imgproxy.git synced 2025-07-02 22:46:50 +02:00

Add gravity to extend option

This commit is contained in:
DarthSim
2019-12-25 16:50:02 +06:00
parent 7c7ac56a48
commit 05cd1286a4
6 changed files with 92 additions and 86 deletions

View File

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

View File

@ -121,13 +121,14 @@ Default: false
#### Extend #### Extend
``` ```
extend:%extend extend:%extend:%gravity
ex:%extend 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 #### Gravity
@ -167,7 +168,7 @@ c:%width:%height:%gravity
Defines an area of the image to be processed (crop before resize). 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. * `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 #### Quality

View File

@ -157,40 +157,48 @@ func calcJpegShink(scale float64, imgtype imageType) int {
return 1 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 { if gravity.Type == gravityFocusPoint {
pointX := scaleInt(width, gravity.X) pointX := scaleInt(width, gravity.X)
pointY := scaleInt(height, gravity.Y) pointY := scaleInt(height, gravity.Y)
left = maxInt(0, minInt(pointX-cropWidth/2, width-cropWidth)) left = pointX - innerWidth/2
top = maxInt(0, minInt(pointY-cropHeight/2, height-cropHeight)) 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
if gravity.Type == gravityNorth || gravity.Type == gravityNorthEast || gravity.Type == gravityNorthWest {
top = 0 + offY
}
if gravity.Type == gravityEast || gravity.Type == gravityNorthEast || gravity.Type == gravitySouthEast {
left = width - innerWidth - offX
}
if gravity.Type == gravitySouth || gravity.Type == gravitySouthEast || gravity.Type == gravitySouthWest {
top = height - innerHeight - offY
}
if gravity.Type == gravityWest || gravity.Type == gravityNorthWest || gravity.Type == gravitySouthWest {
left = 0 + offX
}
} }
offX, offY := int(gravity.X), int(gravity.Y) var minX, maxX, minY, maxY int
left = (width-cropWidth+1)/2 + offX if allowOverflow {
top = (height-cropHeight+1)/2 + offY minX, maxX = -innerWidth+1, width-1
minY, maxY = -innerHeight+1, height-1
if gravity.Type == gravityNorth || gravity.Type == gravityNorthEast || gravity.Type == gravityNorthWest { } else {
top = 0 + offY minX, maxX = 0, width-innerWidth
minY, maxY = 0, height-innerHeight
} }
if gravity.Type == gravityEast || gravity.Type == gravityNorthEast || gravity.Type == gravitySouthEast { left = maxInt(minX, minInt(left, maxX))
left = width - cropWidth - offX top = maxInt(minY, minInt(top, maxY))
}
if gravity.Type == gravitySouth || gravity.Type == gravitySouthEast || gravity.Type == gravitySouthWest {
top = height - cropHeight - offY
}
if gravity.Type == gravityWest || gravity.Type == gravityNorthWest || gravity.Type == gravitySouthWest {
left = 0 + offX
}
left = maxInt(0, minInt(left, width-cropWidth))
top = maxInt(0, minInt(top, height-cropHeight))
return return
} }
@ -221,7 +229,7 @@ func cropImage(img *vipsImage, cropWidth, cropHeight int, gravity *gravityOption
return img.CopyMemory() 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) 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.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 { 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 po.Extend.Enabled && (po.Width > img.Width() || po.Height > img.Height()) {
if err = img.Embed(gravityCenter, po.Width, po.Height, 0, 0, po.Background); err != nil { 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 return err
} }
} }

View File

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

View File

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

38
vips.go
View File

@ -489,41 +489,7 @@ func (img *vipsImage) Replicate(width, height int) error {
return nil return nil
} }
func (img *vipsImage) Embed(gravity gravityType, width, height int, offX, offY int, bg rgbColor) error { func (img *vipsImage) Embed(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
}
if err := img.RgbColourspace(); err != nil { if err := img.RgbColourspace(); err != nil {
return err return err
} }
@ -536,7 +502,7 @@ func (img *vipsImage) Embed(gravity gravityType, width, height int, offX, offY i
} }
var tmp *C.VipsImage 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() return vipsError()
} }
C.swap_and_clear(&img.VipsImage, tmp) C.swap_and_clear(&img.VipsImage, tmp)