Sfoglia il codice sorgente

Implement Max Bytes Filter (#275)

* Implement Max Bytes Filter

* Update according to code review comments

* Refactor according to code review comments
Dmitry Zuev 5 anni fa
parent
commit
d74c402066
3 ha cambiato i file con 70 aggiunte e 0 eliminazioni
  1. 13 0
      docs/generating_the_url_advanced.md
  2. 39 0
      process.go
  3. 18 0
      processing_options.go

+ 13 - 0
docs/generating_the_url_advanced.md

@@ -169,6 +169,19 @@ Redefines quality of the resulting image, percentage.
 
 Default: value from the environment variable.
 
+#### Max Bytes
+
+```
+max_bytes:%max_bytes
+mb:%max_bytes
+```
+
+This filter automatically degrades the quality of the image until the image is under the specified amount of bytes.
+
+*Warning: this filter processes image multiple times to achieve specified image size*
+
+Default: 0
+
 #### Background
 
 ```

+ 39 - 0
process.go

@@ -133,6 +133,15 @@ func canScaleOnLoad(imgtype imageType, scale float64) bool {
 	return imgtype == imageTypeJPEG || imgtype == imageTypeWEBP
 }
 
+func canFitToBytes(imgtype imageType) bool {
+	switch imgtype {
+	case imageTypeJPEG, imageTypeWEBP, imageTypeHEIC, imageTypeTIFF:
+		return true
+	default:
+		return false
+	}
+}
+
 func calcJpegShink(scale float64, imgtype imageType) int {
 	shrink := int(1.0 / scale)
 
@@ -682,5 +691,35 @@ func processImage(ctx context.Context) ([]byte, context.CancelFunc, error) {
 		checkTimeout(ctx)
 	}
 
+	if po.MaxBytes > 0 && canFitToBytes(po.Format) {
+		return processToFitBytes(po, img)
+	}
+
 	return img.Save(po.Format, po.Quality)
 }
+
+func processToFitBytes(po *processingOptions, img *vipsImage) ([]byte, context.CancelFunc, error) {
+	var diff float64
+	quality := po.Quality
+
+	img.CopyMemory()
+
+	for {
+		result, cancel, err := img.Save(po.Format, quality)
+		if len(result) <= po.MaxBytes || quality <= 10 || err != nil {
+			return result, cancel, err
+		}
+		cancel()
+
+		delta := float64(len(result)) / float64(po.MaxBytes)
+		switch {
+		case delta > 3:
+			diff = 0.25
+		case delta > 1.5:
+			diff = 0.5
+		default:
+			diff = 0.75
+		}
+		quality = int(float64(quality) * diff)
+	}
+}

+ 18 - 0
processing_options.go

@@ -116,6 +116,7 @@ type processingOptions struct {
 	Crop         cropOptions
 	Format       imageType
 	Quality      int
+	MaxBytes     int
 	Flatten      bool
 	Background   rgbColor
 	Blur         float32
@@ -193,6 +194,7 @@ func newProcessingOptions() *processingOptions {
 			Gravity:      gravityOptions{Type: gravityCenter},
 			Enlarge:      false,
 			Quality:      conf.Quality,
+			MaxBytes:     0,
 			Format:       imageTypeUnknown,
 			Background:   rgbColor{255, 255, 255},
 			Blur:         0,
@@ -542,6 +544,20 @@ func applyQualityOption(po *processingOptions, args []string) error {
 	return nil
 }
 
+func applyMaxBytesOption(po *processingOptions, args []string) error {
+	if len(args) > 1 {
+		return fmt.Errorf("Invalid max_bytes arguments: %v", args)
+	}
+
+	if max, err := strconv.Atoi(args[0]); err == nil && max >= 0 {
+		po.MaxBytes = max
+	} else {
+		return fmt.Errorf("Invalid max_bytes: %s", args[0])
+	}
+
+	return nil
+}
+
 func applyBackgroundOption(po *processingOptions, args []string) error {
 	switch len(args) {
 	case 1:
@@ -744,6 +760,8 @@ func applyProcessingOption(po *processingOptions, name string, args []string) er
 		return applyCropOption(po, args)
 	case "quality", "q":
 		return applyQualityOption(po, args)
+	case "max_bytes", "mb":
+		return applyMaxBytesOption(po, args)
 	case "background", "bg":
 		return applyBackgroundOption(po, args)
 	case "blur", "bl":