From e894afec13521d2b565c45b81a5fc95769f3f5b4 Mon Sep 17 00:00:00 2001 From: DarthSim Date: Thu, 20 Jun 2019 18:49:25 +0600 Subject: [PATCH] Crop offsets --- docs/generating_the_url_advanced.md | 35 ++++++++++-------- process.go | 56 ++++++++++++++++++++--------- processing_options.go | 42 +++++++++++++++------- 3 files changed, 88 insertions(+), 45 deletions(-) diff --git a/docs/generating_the_url_advanced.md b/docs/generating_the_url_advanced.md index 81ddb6b1..dd245b39 100644 --- a/docs/generating_the_url_advanced.md +++ b/docs/generating_the_url_advanced.md @@ -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 diff --git a/process.go b/process.go index a3407011..870eee11 100644 --- a/process.go +++ b/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 { diff --git a/processing_options.go b/processing_options.go index d2594bb0..1ea8366d 100644 --- a/processing_options.go +++ b/processing_options.go @@ -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