瀏覽代碼

IMGPROXY_PREFERRED_FORMATS config

DarthSim 3 年之前
父節點
當前提交
f368ed13e9
共有 6 個文件被更改,包括 82 次插入8 次删除
  1. 1 0
      CHANGELOG.md
  2. 19 0
      config/config.go
  3. 3 3
      config/configurators/configurators.go
  4. 16 0
      docs/configuration.md
  5. 6 0
      main.go
  6. 37 5
      processing/processing.go

+ 1 - 0
CHANGELOG.md

@@ -5,6 +5,7 @@
 - Add support of 16-bit BMP.
 - Add `IMGPROXY_NEW_RELIC_LABELS` config.
 - Add support of JPEG files with differential Huffman coding or arithmetic coding.
+- Add `IMGPROXY_PREFERRED_FORMATS` config.
 
 ### Fix
 - Fix trimming of CMYK images.

+ 19 - 0
config/config.go

@@ -59,6 +59,8 @@ var (
 	EnforceAvif         bool
 	EnableClientHints   bool
 
+	PreferredFormats []imagetype.Type
+
 	SkipProcessingFormats []imagetype.Type
 
 	UseLinearColorspace bool
@@ -220,6 +222,15 @@ func Reset() {
 	EnforceAvif = false
 	EnableClientHints = false
 
+	PreferredFormats = []imagetype.Type{
+		imagetype.JPEG,
+		imagetype.PNG,
+		imagetype.GIF,
+		imagetype.WEBP,
+		imagetype.AVIF,
+		imagetype.ICO,
+	}
+
 	SkipProcessingFormats = make([]imagetype.Type, 0)
 
 	UseLinearColorspace = false
@@ -376,6 +387,10 @@ func Configure() error {
 
 	configurators.String(&HealthCheckPath, "IMGPROXY_HEALTH_CHECK_PATH")
 
+	if err := configurators.ImageTypes(&PreferredFormats, "IMGPROXY_PREFERRED_FORMATS"); err != nil {
+		return err
+	}
+
 	if err := configurators.ImageTypes(&SkipProcessingFormats, "IMGPROXY_SKIP_PROCESSING_FORMATS"); err != nil {
 		return err
 	}
@@ -557,6 +572,10 @@ func Configure() error {
 		return fmt.Errorf("Quality can't be greater than 100, now - %d\n", Quality)
 	}
 
+	if len(PreferredFormats) == 0 {
+		return fmt.Errorf("At least one preferred format should be specified")
+	}
+
 	if IgnoreSslVerification {
 		log.Warning("Ignoring SSL verification is very unsafe")
 	}

+ 3 - 3
config/configurators/configurators.go

@@ -103,17 +103,17 @@ func Bool(b *bool, name string) {
 }
 
 func ImageTypes(it *[]imagetype.Type, name string) error {
-	*it = []imagetype.Type{}
-
 	if env := os.Getenv(name); len(env) > 0 {
 		parts := strings.Split(env, ",")
 
+		*it = make([]imagetype.Type, 0, len(parts))
+
 		for _, p := range parts {
 			pt := strings.TrimSpace(p)
 			if t, ok := imagetype.Types[pt]; ok {
 				*it = append(*it, t)
 			} else {
-				return fmt.Errorf("Unknown image format to skip: %s", pt)
+				return fmt.Errorf("Unknown image format: %s", pt)
 			}
 		}
 	}

+ 16 - 0
docs/configuration.md

@@ -249,6 +249,22 @@ You can set up a fallback image that will be used in case imgproxy is unable to
 * `IMGPROXY_FALLBACK_IMAGE_TTL`: a duration (in seconds) sent via the `Expires` and `Cache-Control: max-age` HTTP headers when a fallback image was used. When blank or `0`, the value from `IMGPROXY_TTL` is used.
 * `IMGPROXY_FALLBACK_IMAGES_CACHE_SIZE`: ![pro](/assets/pro.svg) the size of custom fallback images cache. When set to `0`, the fallback image cache is disabled. 256 fallback images are cached by default.
 
+## Preferred formats
+
+When the resulting image format is not explicitly specified in the imgproxy URL via the extension or the `format` processing option, imgproxy will choose one of the preferred formats as the resultant:
+
+* `IMGPROXY_PREFERRED_FORMATS`: a list of preferred formats, comma divided. Default: `jpeg,png,gif,webp,avif,ico`
+
+imgproxy is guided by the following rules when choosing the resulting format:
+
+1. If the preferred formats list contains the source image format, it will be used as the resultant
+2. If the resulting image is animated, the resulting image format should support animations
+3. If the resulting image contains transparency, the resulting image format should support transparency
+4. imgproxy chooses the first preferred format that meets those requirements
+5. If none of the preferred formats meet the requirements, the first preferred format is used as the resultant
+
+**📝Note:** When AVIF/WebP support detection is enabled and the browser supports AVIF/WebP, it may be used as the resultant format even if the preferred formats list doesn't contain it.
+
 ## Skip processing
 
 You can configure imgproxy to skip processing of some formats:

+ 6 - 0
main.go

@@ -20,6 +20,7 @@ import (
 	"github.com/imgproxy/imgproxy/v3/metrics"
 	"github.com/imgproxy/imgproxy/v3/metrics/prometheus"
 	"github.com/imgproxy/imgproxy/v3/options"
+	"github.com/imgproxy/imgproxy/v3/processing"
 	"github.com/imgproxy/imgproxy/v3/version"
 	"github.com/imgproxy/imgproxy/v3/vips"
 )
@@ -51,6 +52,11 @@ func initialize() error {
 		return err
 	}
 
+	if err := processing.ValidatePreferredFormats(); err != nil {
+		vips.Shutdown()
+		return err
+	}
+
 	if err := options.ParsePresets(config.Presets); err != nil {
 		vips.Shutdown()
 		return err

+ 37 - 5
processing/processing.go

@@ -37,9 +37,40 @@ var mainPipeline = pipeline{
 	finalize,
 }
 
-func imageTypeGoodForWeb(imgtype imagetype.Type) bool {
-	return imgtype != imagetype.TIFF &&
-		imgtype != imagetype.BMP
+func isImageTypePreferred(imgtype imagetype.Type) bool {
+	for _, t := range config.PreferredFormats {
+		if imgtype == t {
+			return true
+		}
+	}
+
+	return false
+}
+
+func findBestFormat(srcType imagetype.Type, animated, expectAlpha bool) imagetype.Type {
+	for _, t := range config.PreferredFormats {
+		if animated && !t.SupportsAnimation() {
+			continue
+		}
+
+		if expectAlpha && !t.SupportsAlpha() {
+			continue
+		}
+
+		return t
+	}
+
+	return config.PreferredFormats[0]
+}
+
+func ValidatePreferredFormats() error {
+	for _, t := range config.PreferredFormats {
+		if !vips.SupportsSave(t) {
+			return fmt.Errorf("%s can't be a preferred format as it's saving is not supported", t)
+		}
+	}
+
+	return nil
 }
 
 func canFitToBytes(imgtype imagetype.Type) bool {
@@ -223,6 +254,7 @@ func ProcessImage(ctx context.Context, imgdata *imagedata.ImageData, po *options
 	originWidth, originHeight := getImageSize(img)
 
 	animated := img.IsAnimated()
+	expectAlpha := !po.Flatten && (img.HasAlpha() || po.Padding.Enabled || po.Extend.Enabled)
 
 	switch {
 	case po.Format == imagetype.Unknown:
@@ -231,10 +263,10 @@ func ProcessImage(ctx context.Context, imgdata *imagedata.ImageData, po *options
 			po.Format = imagetype.AVIF
 		case po.PreferWebP:
 			po.Format = imagetype.WEBP
-		case vips.SupportsSave(imgdata.Type) && imageTypeGoodForWeb(imgdata.Type):
+		case isImageTypePreferred(imgdata.Type):
 			po.Format = imgdata.Type
 		default:
-			po.Format = imagetype.JPEG
+			po.Format = findBestFormat(imgdata.Type, animated, expectAlpha)
 		}
 	case po.EnforceAvif && !animated:
 		po.Format = imagetype.AVIF