mirror of
https://github.com/imgproxy/imgproxy.git
synced 2025-01-03 10:43:58 +02:00
Crop offsets
This commit is contained in:
parent
4167ade55a
commit
e894afec13
@ -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`
|
||||
Default: `ce:0:0`
|
||||
|
||||
###### Special gravities:
|
||||
|
||||
* `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
|
||||
|
||||
|
56
process.go
56
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 {
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user