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:
@ -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
|
||||
|
||||
|
67
process.go
67
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
|
||||
|
||||
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
|
||||
top = (height-cropHeight+1)/2 + offY
|
||||
|
||||
if gravity.Type == gravityNorth || gravity.Type == gravityNorthEast || gravity.Type == gravityNorthWest {
|
||||
top = 0 + offY
|
||||
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
|
||||
}
|
||||
|
||||
if gravity.Type == gravityEast || gravity.Type == gravityNorthEast || gravity.Type == gravitySouthEast {
|
||||
left = width - cropWidth - offX
|
||||
}
|
||||
|
||||
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))
|
||||
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,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])
|
||||
}
|
||||
|
@ -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
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)
|
||||
|
Reference in New Issue
Block a user