Browse Source

Crop processing oprion; Resize type `crop` is deprecated

DarthSim 6 years ago
parent
commit
dbad387eef
2 changed files with 163 additions and 91 deletions
  1. 94 58
      process.go
  2. 69 33
      processing_options.go

+ 94 - 58
process.go

@@ -37,15 +37,6 @@ func extractMeta(img *vipsImage) (int, int, int, bool) {
 }
 
 func calcScale(width, height int, po *processingOptions, imgtype imageType) float64 {
-	// If we're going only to crop, we need only to scale down to DPR.
-	// Scaling up while cropping is not optimal on this stage, we'll do it later if needed.
-	if po.Resize == resizeCrop {
-		if po.Dpr < 1 {
-			return po.Dpr
-		}
-		return 1
-	}
-
 	var scale float64
 
 	srcW, srcH := float64(width), float64(height)
@@ -144,14 +135,64 @@ func calcCrop(width, height, cropWidth, cropHeight int, gravity *gravityOptions)
 	return
 }
 
+func cropImage(img *vipsImage, cropWidth, cropHeight int, gravity *gravityOptions) error {
+	if cropWidth == 0 && cropHeight == 0 {
+		return nil
+	}
+
+	imgWidth, imgHeight := img.Width(), img.Height()
+
+	if cropWidth == 0 {
+		cropWidth = imgWidth
+	} else {
+		cropWidth = minInt(cropWidth, imgWidth)
+	}
+
+	if cropHeight == 0 {
+		cropHeight = imgHeight
+	} else {
+		cropHeight = minInt(cropHeight, imgHeight)
+	}
+
+	if cropWidth < imgWidth || cropHeight < imgHeight {
+		if gravity.Type == gravitySmart {
+			if err := img.CopyMemory(); err != nil {
+				return err
+			}
+			if err := img.SmartCrop(cropWidth, cropHeight); err != nil {
+				return err
+			}
+			// Applying additional modifications after smart crop causes SIGSEGV on Alpine
+			// so we have to copy memory after it
+			return img.CopyMemory()
+		} else {
+			left, top := calcCrop(imgWidth, imgHeight, cropWidth, cropHeight, gravity)
+			return img.Crop(left, top, cropWidth, cropHeight)
+		}
+	}
+
+	return nil
+}
+
 func transformImage(ctx context.Context, img *vipsImage, data []byte, po *processingOptions, imgtype imageType) error {
 	var err error
 
-	imgWidth, imgHeight, angle, flip := extractMeta(img)
+	srcWidth, srcHeight, angle, flip := extractMeta(img)
 
-	hasAlpha := img.HasAlpha()
+	widthToScale, heightToScale := srcWidth, srcHeight
+	cropWidth, cropHeight := po.Crop.Width, po.Crop.Height
+
+	if cropWidth > 0 {
+		widthToScale = minInt(cropWidth, srcWidth)
+	}
+	if cropHeight > 0 {
+		heightToScale = minInt(cropHeight, srcHeight)
+	}
+
+	scale := calcScale(widthToScale, heightToScale, po, imgtype)
 
-	scale := calcScale(imgWidth, imgHeight, po, imgtype)
+	cropWidth = int(float64(cropWidth) * scale)
+	cropHeight = int(float64(cropHeight) * scale)
 
 	if scale != 1 && data != nil && canScaleOnLoad(imgtype, scale) {
 		if imgtype == imageTypeWEBP || imgtype == imageTypeSVG {
@@ -168,9 +209,13 @@ func transformImage(ctx context.Context, img *vipsImage, data []byte, po *proces
 			}
 		}
 
-		// Update actual image size ans scale after scale-on-load
-		imgWidth, imgHeight, _, _ = extractMeta(img)
-		scale = calcScale(imgWidth, imgHeight, po, imgtype)
+		// Update scale after scale-on-load
+		newWidth, newHeight, _, _ := extractMeta(img)
+
+		widthToScale = int(float64(widthToScale) * float64(newWidth) / float64(srcWidth))
+		heightToScale = int(float64(heightToScale) * float64(newHeight) / float64(srcHeight))
+
+		scale = calcScale(widthToScale, heightToScale, po, imgtype)
 	}
 
 	if err = img.Rad2Float(); err != nil {
@@ -189,15 +234,14 @@ func transformImage(ctx context.Context, img *vipsImage, data []byte, po *proces
 		}
 	}
 
+	hasAlpha := img.HasAlpha()
+
 	if scale != 1 {
 		if err = img.Resize(scale, hasAlpha); err != nil {
 			return err
 		}
 	}
 
-	// Update actual image size after resize
-	imgWidth, imgHeight, _, _ = extractMeta(img)
-
 	checkTimeout(ctx)
 
 	if angle != vipsAngleD0 || flip {
@@ -220,58 +264,41 @@ func transformImage(ctx context.Context, img *vipsImage, data []byte, po *proces
 
 	checkTimeout(ctx)
 
-	cropW, cropH := po.Width, po.Height
-
-	if po.Dpr < 1 || (po.Dpr > 1 && po.Resize != resizeCrop) {
-		cropW = int(float64(cropW) * po.Dpr)
-		cropH = int(float64(cropH) * po.Dpr)
-	}
+	dprWidth := int(float64(po.Width) * po.Dpr)
+	dprHeight := int(float64(po.Height) * po.Dpr)
 
-	if cropW == 0 {
-		cropW = imgWidth
-	} else {
-		cropW = minInt(cropW, imgWidth)
-	}
-
-	if cropH == 0 {
-		cropH = imgHeight
-	} else {
-		cropH = minInt(cropH, imgHeight)
+	cropGravity := po.Crop.Gravity
+	if cropGravity.Type == gravityUnknown {
+		cropGravity = po.Gravity
 	}
 
-	if cropW < imgWidth || cropH < imgHeight {
-		if po.Gravity.Type == gravitySmart {
-			if err = img.CopyMemory(); err != nil {
-				return err
-			}
-			if err = img.SmartCrop(cropW, cropH); err != nil {
-				return err
-			}
-			// Applying additional modifications after smart crop causes SIGSEGV on Alpine
-			// so we have to copy memory after it
-			if err = img.CopyMemory(); err != nil {
-				return err
-			}
-		} else {
-			left, top := calcCrop(imgWidth, imgHeight, cropW, cropH, &po.Gravity)
-			if err = img.Crop(left, top, cropW, cropH); err != nil {
-				return err
-			}
+	if cropGravity.Type == po.Gravity.Type && cropGravity.Type != gravityFocusPoint {
+		if cropWidth == 0 {
+			cropWidth = dprWidth
+		} else if dprWidth > 0 {
+			cropWidth = minInt(cropWidth, dprWidth)
 		}
 
-		checkTimeout(ctx)
-	}
+		if cropHeight == 0 {
+			cropHeight = dprHeight
+		} else if dprHeight > 0 {
+			cropHeight = minInt(cropHeight, dprHeight)
+		}
 
-	if po.Enlarge && po.Resize == resizeCrop && po.Dpr > 1 {
-		// We didn't enlarge the image before, because is wasn't optimal. Now it's time to do it
-		if err = img.Resize(po.Dpr, hasAlpha); err != nil {
+		if err = cropImage(img, cropWidth, cropHeight, &cropGravity); err != nil {
 			return err
 		}
-		if err = img.CopyMemory(); err != nil {
+	} else {
+		if err = cropImage(img, cropWidth, cropHeight, &cropGravity); err != nil {
+			return err
+		}
+		if err = cropImage(img, dprWidth, dprHeight, &po.Gravity); err != nil {
 			return err
 		}
 	}
 
+	checkTimeout(ctx)
+
 	if convertToLinear {
 		if err = img.FixColourspace(); err != nil {
 			return err
@@ -444,6 +471,15 @@ func processImage(ctx context.Context) ([]byte, context.CancelFunc, error) {
 		}
 	}
 
+	if po.Resize == resizeCrop {
+		logWarning("`crop` resizing type is deprecated and will be removed in future versions. Use `crop` processing option instead")
+
+		po.Crop.Width, po.Crop.Height = po.Width, po.Height
+
+		po.Resize = resizeFit
+		po.Width, po.Height = 0, 0
+	}
+
 	animationSupport := conf.MaxGifFrames > 1 && vipsSupportAnimation(imgtype) && vipsSupportAnimation(po.Format)
 
 	pages := 1

+ 69 - 33
processing_options.go

@@ -24,7 +24,8 @@ type processingHeaders struct {
 type gravityType int
 
 const (
-	gravityCenter gravityType = iota
+	gravityUnknown gravityType = iota
+	gravityCenter
 	gravityNorth
 	gravityEast
 	gravitySouth
@@ -79,6 +80,12 @@ const (
 	hexColorShortFormat = "%1x%1x%1x"
 )
 
+type cropOptions struct {
+	Width   int
+	Height  int
+	Gravity gravityOptions
+}
+
 type watermarkOptions struct {
 	Enabled   bool
 	Opacity   float64
@@ -97,6 +104,7 @@ type processingOptions struct {
 	Gravity    gravityOptions
 	Enlarge    bool
 	Expand     bool
+	Crop       cropOptions
 	Format     imageType
 	Quality    int
 	Flatten    bool
@@ -240,32 +248,60 @@ func decodeURL(parts []string) (string, string, error) {
 	return decodeBase64URL(parts)
 }
 
-func applyWidthOption(po *processingOptions, args []string) error {
-	if len(args) > 1 {
-		return fmt.Errorf("Invalid width arguments: %v", args)
+func parseDimension(d *int, name, arg string) error {
+	if v, err := strconv.Atoi(arg); err == nil && v >= 0 {
+		*d = v
+	} else {
+		return fmt.Errorf("Invalid %s: %s", name, arg)
 	}
 
-	if w, err := strconv.Atoi(args[0]); err == nil && w >= 0 {
-		po.Width = w
+	return nil
+}
+
+func parseGravity(g *gravityOptions, args []string) error {
+	if t, ok := gravityTypes[args[0]]; ok {
+		g.Type = t
 	} else {
-		return fmt.Errorf("Invalid width: %s", args[0])
+		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 x, err := strconv.ParseFloat(args[1], 64); err == nil && x >= 0 && x <= 1 {
+			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 {
+			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
 }
 
-func applyHeightOption(po *processingOptions, args []string) error {
+func applyWidthOption(po *processingOptions, args []string) error {
 	if len(args) > 1 {
-		return fmt.Errorf("Invalid height arguments: %v", args)
+		return fmt.Errorf("Invalid width arguments: %v", args)
 	}
 
-	if h, err := strconv.Atoi(args[0]); err == nil && h >= 0 {
-		po.Height = h
-	} else {
-		return fmt.Errorf("Invalid height: %s", args[0])
+	return parseDimension(&po.Width, "width", args[0])
+}
+
+func applyHeightOption(po *processingOptions, args []string) error {
+	if len(args) > 1 {
+		return fmt.Errorf("Invalid height arguments: %v", args)
 	}
 
-	return nil
+	return parseDimension(&po.Height, "height", args[0])
 }
 
 func applyEnlargeOption(po *processingOptions, args []string) error {
@@ -369,30 +405,26 @@ func applyDprOption(po *processingOptions, args []string) error {
 }
 
 func applyGravityOption(po *processingOptions, args []string) error {
-	if g, ok := gravityTypes[args[0]]; ok {
-		po.Gravity.Type = g
-	} else {
-		return fmt.Errorf("Invalid gravity: %s", args[0])
+	return parseGravity(&po.Gravity, args)
+}
+
+func applyCropOption(po *processingOptions, args []string) error {
+	if len(args) > 5 {
+		return fmt.Errorf("Invalid crop arguments: %v", args)
 	}
 
-	if po.Gravity.Type == gravityFocusPoint {
-		if len(args) != 3 {
-			return fmt.Errorf("Invalid gravity arguments: %v", args)
-		}
+	if err := parseDimension(&po.Crop.Width, "crop width", args[0]); err != nil {
+		return err
+	}
 
-		if x, err := strconv.ParseFloat(args[1], 64); err == nil && x >= 0 && x <= 1 {
-			po.Gravity.X = x
-		} else {
-			return fmt.Errorf("Invalid gravity X: %s", args[1])
+	if len(args) > 1 {
+		if err := parseDimension(&po.Crop.Height, "crop height", args[1]); err != nil {
+			return err
 		}
+	}
 
-		if y, err := strconv.ParseFloat(args[2], 64); err == nil && y >= 0 && y <= 1 {
-			po.Gravity.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)
+	if len(args) > 2 {
+		return parseGravity(&po.Crop.Gravity, args[2:])
 	}
 
 	return nil
@@ -625,6 +657,10 @@ func applyProcessingOption(po *processingOptions, name string, args []string) er
 		if err := applyGravityOption(po, args); err != nil {
 			return err
 		}
+	case "crop", "c":
+		if err := applyCropOption(po, args); err != nil {
+			return err
+		}
 	case "quality", "q":
 		if err := applyQualityOption(po, args); err != nil {
 			return err