Pārlūkot izejas kodu

Crop between scale-on-load and scale

DarthSim 3 gadi atpakaļ
vecāks
revīzija
7a968d5fed

+ 3 - 0
CHANGELOG.md

@@ -5,6 +5,9 @@
 - (pro) Add `video_meta` to the `/info` response.
 - Add 1/2/4-bit BMP support.
 
+### Change
+- Optimized `crop`.
+
 ### Fix
 - Fix Datadog support.
 

+ 8 - 0
imath/imath.go

@@ -38,3 +38,11 @@ func Scale(a int, scale float64) int {
 
 	return Round(float64(a) * scale)
 }
+
+func Shrink(a int, shrink float64) int {
+	if a == 0 {
+		return 0
+	}
+
+	return Round(float64(a) / shrink)
+}

+ 158 - 0
options/gravity_options.go

@@ -0,0 +1,158 @@
+package options
+
+import (
+	"fmt"
+)
+
+type GravityType int
+
+const (
+	GravityUnknown GravityType = iota
+	GravityCenter
+	GravityNorth
+	GravityEast
+	GravitySouth
+	GravityWest
+	GravityNorthWest
+	GravityNorthEast
+	GravitySouthWest
+	GravitySouthEast
+	GravitySmart
+	GravityFocusPoint
+)
+
+var gravityTypes = map[string]GravityType{
+	"ce":   GravityCenter,
+	"no":   GravityNorth,
+	"ea":   GravityEast,
+	"so":   GravitySouth,
+	"we":   GravityWest,
+	"nowe": GravityNorthWest,
+	"noea": GravityNorthEast,
+	"sowe": GravitySouthWest,
+	"soea": GravitySouthEast,
+	"sm":   GravitySmart,
+	"fp":   GravityFocusPoint,
+}
+
+var gravityTypesRotationMap = map[int]map[GravityType]GravityType{
+	90: {
+		GravityNorth:     GravityWest,
+		GravityEast:      GravityNorth,
+		GravitySouth:     GravityEast,
+		GravityWest:      GravitySouth,
+		GravityNorthWest: GravitySouthWest,
+		GravityNorthEast: GravityNorthWest,
+		GravitySouthWest: GravitySouthEast,
+		GravitySouthEast: GravityNorthEast,
+	},
+	180: {
+		GravityNorth:     GravitySouth,
+		GravityEast:      GravityWest,
+		GravitySouth:     GravityNorth,
+		GravityWest:      GravityEast,
+		GravityNorthWest: GravitySouthEast,
+		GravityNorthEast: GravitySouthWest,
+		GravitySouthWest: GravityNorthEast,
+		GravitySouthEast: GravityNorthWest,
+	},
+	270: {
+		GravityNorth:     GravityEast,
+		GravityEast:      GravitySouth,
+		GravitySouth:     GravityWest,
+		GravityWest:      GravityNorth,
+		GravityNorthWest: GravityNorthEast,
+		GravityNorthEast: GravitySouthEast,
+		GravitySouthWest: GravityNorthWest,
+		GravitySouthEast: GravitySouthWest,
+	},
+}
+
+var gravityTypesFlipMap = map[GravityType]GravityType{
+	GravityEast:      GravityWest,
+	GravityWest:      GravityEast,
+	GravityNorthWest: GravityNorthEast,
+	GravityNorthEast: GravityNorthWest,
+	GravitySouthWest: GravitySouthEast,
+	GravitySouthEast: GravitySouthWest,
+}
+
+func (gt GravityType) String() string {
+	for k, v := range gravityTypes {
+		if v == gt {
+			return k
+		}
+	}
+	return ""
+}
+
+func (gt GravityType) MarshalJSON() ([]byte, error) {
+	for k, v := range gravityTypes {
+		if v == gt {
+			return []byte(fmt.Sprintf("%q", k)), nil
+		}
+	}
+	return []byte("null"), nil
+}
+
+type GravityOptions struct {
+	Type GravityType
+	X, Y float64
+}
+
+func (g *GravityOptions) RotateAndFlip(angle int, flip bool) {
+	angle %= 360
+
+	if flip {
+		if gt, ok := gravityTypesFlipMap[g.Type]; ok {
+			g.Type = gt
+		}
+
+		switch g.Type {
+		case GravityCenter, GravityNorth, GravitySouth:
+			g.X = -g.X
+		case GravityFocusPoint:
+			g.X = 1.0 - g.X
+		}
+	}
+
+	if angle > 0 {
+		if rotMap := gravityTypesRotationMap[angle]; rotMap != nil {
+			if gt, ok := rotMap[g.Type]; ok {
+				g.Type = gt
+			}
+
+			switch angle {
+			case 90:
+				switch g.Type {
+				case GravityCenter, GravityEast, GravityWest:
+					g.X, g.Y = g.Y, -g.X
+				case GravityFocusPoint:
+					g.X, g.Y = g.Y, 1.0-g.X
+				default:
+					g.X, g.Y = g.Y, g.X
+				}
+			case 180:
+				switch g.Type {
+				case GravityCenter:
+					g.X, g.Y = -g.X, -g.Y
+				case GravityNorth, GravitySouth:
+					g.X = -g.X
+				case GravityEast, GravityWest:
+					g.Y = -g.Y
+				case GravityFocusPoint:
+					g.X, g.Y = 1.0-g.X, 1.0-g.Y
+				}
+			case 270:
+				switch g.Type {
+				case GravityCenter, GravityNorth, GravitySouth:
+					g.X, g.Y = -g.Y, g.X
+				case GravityFocusPoint:
+					g.X, g.Y = 1.0-g.Y, g.X
+				default:
+					g.X, g.Y = g.Y, g.X
+				}
+			}
+		}
+	}
+}

+ 0 - 52
options/gravity_type.go

@@ -1,52 +0,0 @@
-package options
-
-import "fmt"
-
-type GravityType int
-
-const (
-	GravityUnknown GravityType = iota
-	GravityCenter
-	GravityNorth
-	GravityEast
-	GravitySouth
-	GravityWest
-	GravityNorthWest
-	GravityNorthEast
-	GravitySouthWest
-	GravitySouthEast
-	GravitySmart
-	GravityFocusPoint
-)
-
-var gravityTypes = map[string]GravityType{
-	"ce":   GravityCenter,
-	"no":   GravityNorth,
-	"ea":   GravityEast,
-	"so":   GravitySouth,
-	"we":   GravityWest,
-	"nowe": GravityNorthWest,
-	"noea": GravityNorthEast,
-	"sowe": GravitySouthWest,
-	"soea": GravitySouthEast,
-	"sm":   GravitySmart,
-	"fp":   GravityFocusPoint,
-}
-
-func (gt GravityType) String() string {
-	for k, v := range gravityTypes {
-		if v == gt {
-			return k
-		}
-	}
-	return ""
-}
-
-func (gt GravityType) MarshalJSON() ([]byte, error) {
-	for k, v := range gravityTypes {
-		if v == gt {
-			return []byte(fmt.Sprintf("%q", k)), nil
-		}
-	}
-	return []byte("null"), nil
-}

+ 1 - 10
options/processing_options.go

@@ -23,11 +23,6 @@ const maxClientHintDPR = 8
 
 var errExpiredURL = errors.New("Expired URL")
 
-type GravityOptions struct {
-	Type GravityType
-	X, Y float64
-}
-
 type ExtendOptions struct {
 	Enabled bool
 	Gravity GravityOptions
@@ -211,11 +206,7 @@ func parseBoolOption(str string) bool {
 }
 
 func isGravityOffcetValid(gravity GravityType, offset float64) bool {
-	if gravity == GravityCenter {
-		return true
-	}
-
-	return offset >= 0 && (gravity != GravityFocusPoint || offset <= 1)
+	return gravity != GravityFocusPoint || (offset >= 0 && offset <= 1)
 }
 
 func parseGravity(g *GravityOptions, args []string) error {

+ 12 - 2
processing/crop.go

@@ -38,10 +38,20 @@ func cropImage(img *vips.Image, cropWidth, cropHeight int, gravity *options.Grav
 }
 
 func crop(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptions, imgdata *imagedata.ImageData) error {
-	if err := cropImage(img, pctx.cropWidth, pctx.cropHeight, &pctx.cropGravity); err != nil {
-		return err
+	width, height := pctx.cropWidth, pctx.cropHeight
+
+	opts := pctx.cropGravity
+	opts.RotateAndFlip(pctx.angle, pctx.flip)
+	opts.RotateAndFlip(po.Rotate, false)
+
+	if (pctx.angle+po.Rotate)%180 == 90 {
+		width, height = height, width
 	}
 
+	return cropImage(img, width, height, &opts)
+}
+
+func cropToResult(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptions, imgdata *imagedata.ImageData) error {
 	// Crop image to the result size
 	resultWidth := imath.Scale(po.Width, po.Dpr)
 	resultHeight := imath.Scale(po.Height, po.Dpr)

+ 0 - 11
processing/prepare.go

@@ -161,16 +161,5 @@ func prepare(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptio
 
 	pctx.wscale, pctx.hscale = calcScale(widthToScale, heightToScale, po, pctx.imgtype)
 
-	if pctx.cropWidth > 0 {
-		pctx.cropWidth = imath.Max(1, imath.Scale(pctx.cropWidth, pctx.wscale))
-	}
-	if pctx.cropHeight > 0 {
-		pctx.cropHeight = imath.Max(1, imath.Scale(pctx.cropHeight, pctx.hscale))
-	}
-	if pctx.cropGravity.Type != options.GravityFocusPoint {
-		pctx.cropGravity.X *= pctx.wscale
-		pctx.cropGravity.Y *= pctx.hscale
-	}
-
 	return nil
 }

+ 2 - 1
processing/processing.go

@@ -23,9 +23,10 @@ var mainPipeline = pipeline{
 	prepare,
 	scaleOnLoad,
 	importColorProfile,
+	crop,
 	scale,
 	rotateAndFlip,
-	crop,
+	cropToResult,
 	fixWebpSize,
 	applyFilters,
 	extend,

+ 16 - 2
processing/scale_on_load.go

@@ -58,15 +58,29 @@ func scaleOnLoad(pctx *pipelineContext, img *vips.Image, po *options.ProcessingO
 	// Update scales after scale-on-load
 	newWidth, newHeight, _, _ := extractMeta(img, po.Rotate, po.AutoRotate)
 
-	pctx.wscale = float64(pctx.srcWidth) * pctx.wscale / float64(newWidth)
+	wpreshrink := float64(pctx.srcWidth) / float64(newWidth)
+	hpreshrink := float64(pctx.srcHeight) / float64(newHeight)
+
+	pctx.wscale = wpreshrink * pctx.wscale
 	if newWidth == imath.Scale(newWidth, pctx.wscale) {
 		pctx.wscale = 1.0
 	}
 
-	pctx.hscale = float64(pctx.srcHeight) * pctx.hscale / float64(newHeight)
+	pctx.hscale = hpreshrink * pctx.hscale
 	if newHeight == imath.Scale(newHeight, pctx.hscale) {
 		pctx.hscale = 1.0
 	}
 
+	if pctx.cropWidth > 0 {
+		pctx.cropWidth = imath.Max(1, imath.Shrink(pctx.cropWidth, wpreshrink))
+	}
+	if pctx.cropHeight > 0 {
+		pctx.cropHeight = imath.Max(1, imath.Shrink(pctx.cropHeight, hpreshrink))
+	}
+	if pctx.cropGravity.Type != options.GravityFocusPoint {
+		pctx.cropGravity.X /= wpreshrink
+		pctx.cropGravity.Y /= hpreshrink
+	}
+
 	return nil
 }