فهرست منبع

Add vips.Config; Move WebpPreset to vips

DarthSim 1 هفته پیش
والد
کامیت
64cdf914f9
7فایلهای تغییر یافته به همراه237 افزوده شده و 104 حذف شده
  1. 4 5
      config/config.go
  2. 0 41
      config/webp_preset.go
  3. 5 1
      init.go
  4. 2 1
      processing/processing_test.go
  5. 122 0
      vips/config.go
  6. 46 56
      vips/vips.go
  7. 58 0
      vips/webp_preset.go

+ 4 - 5
config/config.go

@@ -67,7 +67,7 @@ var (
 	AvifSpeed             int
 	JxlEffort             int
 	WebpEffort            int
-	WebpPreset            WebpPresetKind
+	WebpPreset            string
 	Quality               int
 	FormatQuality         map[imagetype.Type]int
 	StripMetadata         bool
@@ -276,7 +276,7 @@ func Reset() {
 	AvifSpeed = 8
 	JxlEffort = 4
 	WebpEffort = 4
-	WebpPreset = WebpPresetDefault
+	WebpPreset = "default"
 	Quality = 80
 	FormatQuality = map[imagetype.Type]int{
 		imagetype.WEBP: 79,
@@ -512,9 +512,8 @@ func Configure() error {
 	configurators.Int(&AvifSpeed, "IMGPROXY_AVIF_SPEED")
 	configurators.Int(&JxlEffort, "IMGPROXY_JXL_EFFORT")
 	configurators.Int(&WebpEffort, "IMGPROXY_WEBP_EFFORT")
-	if err := configurators.FromMap(&WebpPreset, "IMGPROXY_WEBP_PRESET", WebpPresets); err != nil {
-		return err
-	}
+	configurators.String(&WebpPreset, "IMGPROXY_WEBP_PRESET")
+
 	configurators.Int(&Quality, "IMGPROXY_QUALITY")
 	if err := configurators.ImageTypesQuality(FormatQuality, "IMGPROXY_FORMAT_QUALITY"); err != nil {
 		return err

+ 0 - 41
config/webp_preset.go

@@ -1,41 +0,0 @@
-package config
-
-import "fmt"
-
-type WebpPresetKind int
-
-const (
-	WebpPresetDefault WebpPresetKind = iota
-	WebpPresetPhoto
-	WebpPresetPicture
-	WebpPresetDrawing
-	WebpPresetIcon
-	WebpPresetText
-)
-
-var WebpPresets = map[string]WebpPresetKind{
-	"default": WebpPresetDefault,
-	"photo":   WebpPresetPhoto,
-	"picture": WebpPresetPicture,
-	"drawing": WebpPresetDrawing,
-	"icon":    WebpPresetIcon,
-	"text":    WebpPresetText,
-}
-
-func (wp WebpPresetKind) String() string {
-	for k, v := range WebpPresets {
-		if v == wp {
-			return k
-		}
-	}
-	return ""
-}
-
-func (wp WebpPresetKind) MarshalJSON() ([]byte, error) {
-	for k, v := range WebpPresets {
-		if v == wp {
-			return []byte(fmt.Sprintf("%q", k)), nil
-		}
-	}
-	return []byte("null"), nil
-}

+ 5 - 1
init.go

@@ -43,7 +43,11 @@ func Init() error {
 		return err
 	}
 
-	if err := vips.Init(); err != nil {
+	vipsCfg, err := vips.LoadConfigFromEnv(nil)
+	if err != nil {
+		return err
+	}
+	if err := vips.Init(vipsCfg); err != nil {
 		return err
 	}
 

+ 2 - 1
processing/processing_test.go

@@ -30,7 +30,8 @@ type ProcessingTestSuite struct {
 }
 
 func (s *ProcessingTestSuite) SetupSuite() {
-	s.Require().NoError(vips.Init())
+	vipsCfg := vips.NewDefaultConfig()
+	s.Require().NoError(vips.Init(&vipsCfg))
 
 	logger.Mute()
 

+ 122 - 0
vips/config.go

@@ -0,0 +1,122 @@
+package vips
+
+/*
+#include "vips.h"
+*/
+import "C"
+import (
+	"fmt"
+	"os"
+
+	globalConfig "github.com/imgproxy/imgproxy/v3/config"
+	"github.com/imgproxy/imgproxy/v3/ensure"
+)
+
+type Config struct {
+	// Whether to save JPEG as progressive
+	JpegProgressive bool
+
+	// Whether to save PNG as interlaced
+	PngInterlaced bool
+	// Whether to save PNG with adaptive palette
+	PngQuantize bool
+	// Number of colors for adaptive palette
+	PngQuantizationColors int
+
+	// WebP preset to use when saving WebP images
+	WebpPreset WebpPreset
+
+	// AVIF saving speed
+	AvifSpeed int
+	// WebP saving effort
+	WebpEffort int
+	// JPEG XL saving effort
+	JxlEffort int
+
+	// Whether to not apply any limits when loading PNG
+	PngUnlimited bool
+	// Whether to not apply any limits when loading JPEG
+	SvgUnlimited bool
+
+	// Whether to enable libvips memory leak check
+	LeakCheck bool
+	// Whether to enable libvips operation cache tracing
+	CacheTrace bool
+}
+
+func NewDefaultConfig() Config {
+	return Config{
+		JpegProgressive: false,
+
+		PngInterlaced:         false,
+		PngQuantize:           false,
+		PngQuantizationColors: 256,
+
+		WebpPreset: C.VIPS_FOREIGN_WEBP_PRESET_DEFAULT,
+
+		AvifSpeed:  8,
+		WebpEffort: 4,
+		JxlEffort:  4,
+
+		PngUnlimited: false,
+		SvgUnlimited: false,
+
+		LeakCheck:  false,
+		CacheTrace: false,
+	}
+}
+
+func LoadConfigFromEnv(c *Config) (*Config, error) {
+	c = ensure.Ensure(c, NewDefaultConfig)
+
+	c.JpegProgressive = globalConfig.JpegProgressive
+
+	c.PngInterlaced = globalConfig.PngInterlaced
+	c.PngQuantize = globalConfig.PngQuantize
+	c.PngQuantizationColors = globalConfig.PngQuantizationColors
+
+	if pr, ok := WebpPresets[globalConfig.WebpPreset]; ok {
+		c.WebpPreset = pr
+	} else {
+		return nil, fmt.Errorf("invalid WebP preset: %s", globalConfig.WebpPreset)
+	}
+
+	c.AvifSpeed = globalConfig.AvifSpeed
+	c.WebpEffort = globalConfig.WebpEffort
+	c.JxlEffort = globalConfig.JxlEffort
+
+	c.PngUnlimited = globalConfig.PngUnlimited
+	c.SvgUnlimited = globalConfig.SvgUnlimited
+
+	c.LeakCheck = len(os.Getenv("IMGPROXY_VIPS_LEAK_CHECK")) > 0
+	c.CacheTrace = len(os.Getenv("IMGPROXY_VIPS_CACHE_TRACE")) > 0
+
+	return c, nil
+}
+
+func (c *Config) Validate() error {
+	if c.PngQuantizationColors < 2 || c.PngQuantizationColors > 256 {
+		return fmt.Errorf(
+			"IMGPROXY_PNG_QUANTIZATION_COLORS must be between 2 and 256, got %d",
+			c.PngQuantizationColors,
+		)
+	}
+
+	if c.WebpPreset < C.VIPS_FOREIGN_WEBP_PRESET_DEFAULT || c.WebpPreset >= C.VIPS_FOREIGN_WEBP_PRESET_LAST {
+		return fmt.Errorf("invalid IMGPROXY_WEBP_PRESET: %d", c.WebpPreset)
+	}
+
+	if c.AvifSpeed < 0 || c.AvifSpeed > 9 {
+		return fmt.Errorf("IMGPROXY_AVIF_SPEED must be between 0 and 9, got %d", c.AvifSpeed)
+	}
+
+	if c.JxlEffort < 1 || c.JxlEffort > 9 {
+		return fmt.Errorf("IMGPROXY_JXL_EFFORT must be between 1 and 9, got %d", c.JxlEffort)
+	}
+
+	if c.WebpEffort < 1 || c.WebpEffort > 6 {
+		return fmt.Errorf("IMGPROXY_WEBP_EFFORT must be between 1 and 6, got %d", c.WebpEffort)
+	}
+
+	return nil
+}

+ 46 - 56
vips/vips.go

@@ -22,7 +22,6 @@ import (
 	"time"
 	"unsafe"
 
-	"github.com/imgproxy/imgproxy/v3/config"
 	"github.com/imgproxy/imgproxy/v3/ierrors"
 	"github.com/imgproxy/imgproxy/v3/imagedata"
 	"github.com/imgproxy/imgproxy/v3/imagetype"
@@ -38,20 +37,12 @@ var (
 	typeSupportSave sync.Map
 
 	gifResolutionLimit int
+
+	initOnce sync.Once
 )
 
-var vipsConf struct {
-	JpegProgressive       C.int
-	PngInterlaced         C.int
-	PngQuantize           C.int
-	PngQuantizationColors C.int
-	AvifSpeed             C.int
-	JxlEffort             C.int
-	WebpEffort            C.int
-	WebpPreset            C.VipsForeignWebpPreset
-	PngUnlimited          C.int
-	SvgUnlimited          C.int
-}
+// Global vips config. Can be set with [Init]
+var config *Config
 
 var badImageErrRe = []*regexp.Regexp{
 	regexp.MustCompile(`^(\S+)load_buffer: `),
@@ -60,13 +51,32 @@ var badImageErrRe = []*regexp.Regexp{
 	regexp.MustCompile(`^webp2vips: `),
 }
 
-func Init() error {
+func init() {
+	// Just get sure that we have some config
+	c := NewDefaultConfig()
+	config = &c
+}
+
+func Init(c *Config) error {
+	if err := c.Validate(); err != nil {
+		return err
+	}
+
+	config = c
+
 	runtime.LockOSThread()
 	defer runtime.UnlockOSThread()
 
-	if err := C.vips_initialize(); err != 0 {
-		C.vips_shutdown()
-		return newVipsError("unable to start vips!")
+	// vips_initialize must be called only once
+	var initErr error
+	initOnce.Do(func() {
+		if err := C.vips_initialize(); err != 0 {
+			C.vips_shutdown()
+			initErr = newVipsError("unable to start vips!")
+		}
+	})
+	if initErr != nil {
+		return initErr
 	}
 
 	// Disable libvips cache. Since processing pipeline is fine tuned, we won't get much profit from it.
@@ -83,41 +93,11 @@ func Init() error {
 		C.vips_concurrency_set(1)
 	}
 
-	if len(os.Getenv("IMGPROXY_VIPS_LEAK_CHECK")) > 0 {
-		C.vips_leak_set(C.gboolean(1))
-	}
-
-	if len(os.Getenv("IMGPROXY_VIPS_CACHE_TRACE")) > 0 {
-		C.vips_cache_set_trace(C.gboolean(1))
-	}
+	C.vips_leak_set(gbool(config.LeakCheck))
+	C.vips_cache_set_trace(gbool(config.CacheTrace))
 
 	gifResolutionLimit = int(C.gif_resolution_limit())
 
-	vipsConf.JpegProgressive = gbool(config.JpegProgressive)
-	vipsConf.PngInterlaced = gbool(config.PngInterlaced)
-	vipsConf.PngQuantize = gbool(config.PngQuantize)
-	vipsConf.PngQuantizationColors = C.int(config.PngQuantizationColors)
-	vipsConf.AvifSpeed = C.int(config.AvifSpeed)
-	vipsConf.JxlEffort = C.int(config.JxlEffort)
-	vipsConf.WebpEffort = C.int(config.WebpEffort)
-	vipsConf.PngUnlimited = gbool(config.PngUnlimited)
-	vipsConf.SvgUnlimited = gbool(config.SvgUnlimited)
-
-	switch config.WebpPreset {
-	case config.WebpPresetPhoto:
-		vipsConf.WebpPreset = C.VIPS_FOREIGN_WEBP_PRESET_PHOTO
-	case config.WebpPresetPicture:
-		vipsConf.WebpPreset = C.VIPS_FOREIGN_WEBP_PRESET_PICTURE
-	case config.WebpPresetDrawing:
-		vipsConf.WebpPreset = C.VIPS_FOREIGN_WEBP_PRESET_DRAWING
-	case config.WebpPresetIcon:
-		vipsConf.WebpPreset = C.VIPS_FOREIGN_WEBP_PRESET_ICON
-	case config.WebpPresetText:
-		vipsConf.WebpPreset = C.VIPS_FOREIGN_WEBP_PRESET_TEXT
-	default:
-		vipsConf.WebpPreset = C.VIPS_FOREIGN_WEBP_PRESET_DEFAULT
-	}
-
 	return nil
 }
 
@@ -334,13 +314,13 @@ func (img *Image) Load(imgdata imagedata.ImageData, shrink int, scale float64, p
 	case imagetype.JXL:
 		err = C.vips_jxlload_source_go(source, C.int(pages), &tmp)
 	case imagetype.PNG:
-		err = C.vips_pngload_source_go(source, &tmp, vipsConf.PngUnlimited)
+		err = C.vips_pngload_source_go(source, &tmp, gbool(config.PngUnlimited))
 	case imagetype.WEBP:
 		err = C.vips_webpload_source_go(source, C.double(scale), C.int(pages), &tmp)
 	case imagetype.GIF:
 		err = C.vips_gifload_source_go(source, C.int(pages), &tmp)
 	case imagetype.SVG:
-		err = C.vips_svgload_source_go(source, C.double(scale), &tmp, vipsConf.SvgUnlimited)
+		err = C.vips_svgload_source_go(source, C.double(scale), &tmp, gbool(config.SvgUnlimited))
 	case imagetype.HEIC, imagetype.AVIF:
 		err = C.vips_heifload_source_go(source, &tmp, C.int(0))
 	case imagetype.TIFF:
@@ -400,19 +380,29 @@ func (img *Image) Save(imgtype imagetype.Type, quality int) (imagedata.ImageData
 
 	switch imgtype {
 	case imagetype.JPEG:
-		err = C.vips_jpegsave_go(img.VipsImage, target, C.int(quality), vipsConf.JpegProgressive)
+		err = C.vips_jpegsave_go(img.VipsImage, target, C.int(quality), gbool(config.JpegProgressive))
 	case imagetype.JXL:
-		err = C.vips_jxlsave_go(img.VipsImage, target, C.int(quality), vipsConf.JxlEffort)
+		err = C.vips_jxlsave_go(img.VipsImage, target, C.int(quality), C.int(config.JxlEffort))
 	case imagetype.PNG:
-		err = C.vips_pngsave_go(img.VipsImage, target, vipsConf.PngInterlaced, vipsConf.PngQuantize, vipsConf.PngQuantizationColors)
+		err = C.vips_pngsave_go(
+			img.VipsImage, target,
+			gbool(config.PngInterlaced),
+			gbool(config.PngQuantize),
+			C.int(config.PngQuantizationColors),
+		)
 	case imagetype.WEBP:
-		err = C.vips_webpsave_go(img.VipsImage, target, C.int(quality), vipsConf.WebpEffort, vipsConf.WebpPreset)
+		err = C.vips_webpsave_go(
+			img.VipsImage, target,
+			C.int(quality),
+			C.int(config.WebpEffort),
+			config.WebpPreset.C(),
+		)
 	case imagetype.GIF:
 		err = C.vips_gifsave_go(img.VipsImage, target)
 	case imagetype.HEIC:
 		err = C.vips_heifsave_go(img.VipsImage, target, C.int(quality))
 	case imagetype.AVIF:
-		err = C.vips_avifsave_go(img.VipsImage, target, C.int(quality), vipsConf.AvifSpeed)
+		err = C.vips_avifsave_go(img.VipsImage, target, C.int(quality), C.int(config.AvifSpeed))
 	case imagetype.TIFF:
 		err = C.vips_tiffsave_go(img.VipsImage, target, C.int(quality))
 	case imagetype.BMP:

+ 58 - 0
vips/webp_preset.go

@@ -0,0 +1,58 @@
+package vips
+
+/*
+#include "vips.h"
+*/
+import "C"
+
+import (
+	"log/slog"
+	"strconv"
+)
+
+// WebpPreset represents WebP preset to use when saving WebP images
+type WebpPreset C.VipsForeignWebpPreset
+
+const (
+	WebpPresetDefault = C.VIPS_FOREIGN_WEBP_PRESET_DEFAULT
+	WebpPresetPhoto   = C.VIPS_FOREIGN_WEBP_PRESET_PHOTO
+	WebpPresetPicture = C.VIPS_FOREIGN_WEBP_PRESET_PICTURE
+	WebpPresetDrawing = C.VIPS_FOREIGN_WEBP_PRESET_DRAWING
+	WebpPresetIcon    = C.VIPS_FOREIGN_WEBP_PRESET_ICON
+	WebpPresetText    = C.VIPS_FOREIGN_WEBP_PRESET_TEXT
+)
+
+// WebpPresets maps string representations to WebpPreset values
+var WebpPresets = map[string]WebpPreset{
+	"default": WebpPresetDefault,
+	"photo":   WebpPresetPhoto,
+	"picture": WebpPresetPicture,
+	"drawing": WebpPresetDrawing,
+	"icon":    WebpPresetIcon,
+	"text":    WebpPresetText,
+}
+
+// C converts WebpPreset to C.VipsForeignWebpPreset
+func (wp WebpPreset) C() C.VipsForeignWebpPreset {
+	return C.VipsForeignWebpPreset(wp)
+}
+
+// String returns the string representation of the WebpPreset
+func (wp WebpPreset) String() string {
+	for k, v := range WebpPresets {
+		if v == wp {
+			return k
+		}
+	}
+	return "unknown"
+}
+
+// MarshalJSON implements the json.Marshaler interface
+func (wp WebpPreset) MarshalJSON() ([]byte, error) {
+	return strconv.AppendQuote(nil, wp.String()), nil
+}
+
+// LogValue implements the slog.LogValuer interface
+func (wp WebpPreset) LogValue() slog.Value {
+	return slog.StringValue(wp.String())
+}