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:%gravity
|
gravity:%gravity_type:%x_offset:%y_offset
|
||||||
g:%gravity
|
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);
|
* `gravity_type` - specifies the gravity type. Available values:
|
||||||
* `so`: south (bottom edge);
|
* `no`: north (top edge);
|
||||||
* `ea`: east (right edge);
|
* `so`: south (bottom edge);
|
||||||
* `we`: west (left edge);
|
* `ea`: east (right edge);
|
||||||
* `noea`: north-east (top-right corner);
|
* `we`: west (left edge);
|
||||||
* `nowe`: north-west (top-left corner);
|
* `noea`: north-east (top-right corner);
|
||||||
* `soea`: south-east (bottom-right corner);
|
* `nowe`: north-west (top-left corner);
|
||||||
* `sowe`: south-west (bottom-left corner);
|
* `soea`: south-east (bottom-right corner);
|
||||||
* `ce`: center;
|
* `sowe`: south-west (bottom-left corner);
|
||||||
* `sm`: smart. `libvips` detects the most "interesting" section of the image and considers it as the center of the resulting image;
|
* `ce`: center.
|
||||||
* `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`.
|
* `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
|
##### Crop
|
||||||
|
|
||||||
|
56
process.go
56
process.go
@ -115,25 +115,30 @@ func calcCrop(width, height, cropWidth, cropHeight int, gravity *gravityOptions)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
left = (width - cropWidth + 1) / 2
|
offX, offY := int(gravity.X), int(gravity.Y)
|
||||||
top = (height - cropHeight + 1) / 2
|
|
||||||
|
left = (width-cropWidth+1)/2 + offX
|
||||||
|
top = (height-cropHeight+1)/2 + offY
|
||||||
|
|
||||||
if gravity.Type == gravityNorth || gravity.Type == gravityNorthEast || gravity.Type == gravityNorthWest {
|
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 {
|
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 {
|
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 {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -176,14 +181,28 @@ func cropImage(img *vipsImage, cropWidth, cropHeight int, gravity *gravityOption
|
|||||||
return img.Crop(left, top, cropWidth, cropHeight)
|
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 {
|
func transformImage(ctx context.Context, img *vipsImage, data []byte, po *processingOptions, imgtype imageType) error {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
srcWidth, srcHeight, angle, flip := extractMeta(img)
|
srcWidth, srcHeight, angle, flip := extractMeta(img)
|
||||||
|
|
||||||
widthToScale, heightToScale := srcWidth, srcHeight
|
|
||||||
cropWidth, cropHeight := po.Crop.Width, po.Crop.Height
|
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 {
|
if cropWidth > 0 {
|
||||||
widthToScale = minInt(cropWidth, srcWidth)
|
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)
|
scale := calcScale(widthToScale, heightToScale, po, imgtype)
|
||||||
|
|
||||||
cropWidth = roundToInt(float64(cropWidth) * scale)
|
cropWidth = scaleSize(cropWidth, scale)
|
||||||
cropHeight = roundToInt(float64(cropHeight) * scale)
|
cropHeight = scaleSize(cropHeight, scale)
|
||||||
|
cropGravity.X = cropGravity.X * scale
|
||||||
|
cropGravity.Y = cropGravity.Y * scale
|
||||||
|
|
||||||
if scale != 1 && data != nil && canScaleOnLoad(imgtype, scale) {
|
if scale != 1 && data != nil && canScaleOnLoad(imgtype, scale) {
|
||||||
if imgtype == imageTypeWEBP || imgtype == imageTypeSVG {
|
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
|
// Update scale after scale-on-load
|
||||||
newWidth, newHeight, _, _ := extractMeta(img)
|
newWidth, newHeight, _, _ := extractMeta(img)
|
||||||
|
|
||||||
widthToScale = roundToInt(float64(widthToScale) * float64(newWidth) / float64(srcWidth))
|
widthToScale = scaleSize(widthToScale, float64(newWidth)/float64(srcWidth))
|
||||||
heightToScale = roundToInt(float64(heightToScale) * float64(newHeight) / float64(srcHeight))
|
heightToScale = scaleSize(heightToScale, float64(newHeight)/float64(srcHeight))
|
||||||
|
|
||||||
scale = calcScale(widthToScale, heightToScale, po, imgtype)
|
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)
|
dprWidth := roundToInt(float64(po.Width) * po.Dpr)
|
||||||
dprHeight := roundToInt(float64(po.Height) * 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 cropGravity.Type == po.Gravity.Type && cropGravity.Type != gravityFocusPoint {
|
||||||
if cropWidth == 0 {
|
if cropWidth == 0 {
|
||||||
cropWidth = dprWidth
|
cropWidth = dprWidth
|
||||||
@ -287,7 +303,13 @@ func transformImage(ctx context.Context, img *vipsImage, data []byte, po *proces
|
|||||||
cropHeight = minInt(cropHeight, dprHeight)
|
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
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -52,11 +52,6 @@ var gravityTypes = map[string]gravityType{
|
|||||||
"fp": gravityFocusPoint,
|
"fp": gravityFocusPoint,
|
||||||
}
|
}
|
||||||
|
|
||||||
type gravityOptions struct {
|
|
||||||
Type gravityType
|
|
||||||
X, Y float64
|
|
||||||
}
|
|
||||||
|
|
||||||
type resizeType int
|
type resizeType int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -80,6 +75,11 @@ const (
|
|||||||
hexColorShortFormat = "%1x%1x%1x"
|
hexColorShortFormat = "%1x%1x%1x"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type gravityOptions struct {
|
||||||
|
Type gravityType
|
||||||
|
X, Y float64
|
||||||
|
}
|
||||||
|
|
||||||
type cropOptions struct {
|
type cropOptions struct {
|
||||||
Width int
|
Width int
|
||||||
Height int
|
Height int
|
||||||
@ -258,31 +258,47 @@ func parseDimension(d *int, name, arg string) error {
|
|||||||
return nil
|
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 {
|
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 {
|
if t, ok := gravityTypes[args[0]]; ok {
|
||||||
g.Type = t
|
g.Type = t
|
||||||
} else {
|
} else {
|
||||||
return fmt.Errorf("Invalid gravity: %s", args[0])
|
return fmt.Errorf("Invalid gravity: %s", args[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
if g.Type == gravityFocusPoint {
|
if g.Type == gravitySmart && nArgs > 1 {
|
||||||
if len(args) != 3 {
|
return fmt.Errorf("Invalid gravity arguments: %v", args)
|
||||||
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
|
g.X = x
|
||||||
} else {
|
} else {
|
||||||
return fmt.Errorf("Invalid gravity X: %s", args[1])
|
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
|
g.Y = y
|
||||||
} else {
|
} else {
|
||||||
return fmt.Errorf("Invalid gravity Y: %s", args[2])
|
return fmt.Errorf("Invalid gravity Y: %s", args[2])
|
||||||
}
|
}
|
||||||
} else if len(args) > 1 {
|
|
||||||
return fmt.Errorf("Invalid gravity arguments: %v", args)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
Loading…
Reference in New Issue
Block a user