diff --git a/config/config.go b/config/config.go index 832745ab..81f60562 100644 --- a/config/config.go +++ b/config/config.go @@ -7,7 +7,7 @@ import ( "github.com/spf13/viper" "github.com/thoas/picfit/constants" - "github.com/thoas/picfit/engine" + engineconfig "github.com/thoas/picfit/engine/config" "github.com/thoas/picfit/kvstore" "github.com/thoas/picfit/logger" "github.com/thoas/picfit/storage" @@ -46,7 +46,7 @@ type Sentry struct { // Config is a struct to load configuration flags type Config struct { Debug bool - Engine *engine.Config + Engine *engineconfig.Config Sentry *Sentry SecretKey string `mapstructure:"secret_key"` Shard *Shard @@ -63,10 +63,15 @@ type Config struct { // DefaultConfig returns a default config instance func DefaultConfig() *Config { return &Config{ - Engine: &engine.Config{ - DefaultFormat: DefaultFormat, - Quality: DefaultQuality, - Format: "", + Engine: &engineconfig.Config{ + DefaultFormat: DefaultFormat, + Quality: DefaultQuality, + JpegQuality: DefaultQuality, + WebpQuality: DefaultQuality, + PngCompression: engineconfig.DefaultPngCompression, + MaxBufferSize: engineconfig.DefaultMaxBufferSize, + ImageBufferSize: engineconfig.DefaultImageBufferSize, + Format: "", }, Options: &Options{ EnableDelete: false, @@ -95,6 +100,7 @@ func load(content string, isPath bool) (*Config, error) { viper.SetDefault("shard", defaultConfig.Shard) viper.SetDefault("port", defaultConfig.Port) viper.SetDefault("kvstore", defaultConfig.KVStore) + viper.SetDefault("engine", defaultConfig.Engine) viper.SetEnvPrefix("picfit") var err error @@ -120,10 +126,6 @@ func load(content string, isPath bool) (*Config, error) { return nil, err } - if config.Engine == nil { - config.Engine = defaultConfig.Engine - } - return config, nil } diff --git a/engine/backend/goimage.go b/engine/backend/goimage.go index 36bd1290..75013d7c 100644 --- a/engine/backend/goimage.go +++ b/engine/backend/goimage.go @@ -20,7 +20,7 @@ import ( "golang.org/x/image/tiff" ) -type GoImageEngine struct{} +type GoImage struct{} type ImageTransformation func(img image.Image) *image.NRGBA @@ -68,11 +68,11 @@ func imageToPaletted(img image.Image) *image.Paletted { return pm } -func (e *GoImageEngine) String() string { +func (e *GoImage) String() string { return "goimage" } -func (e *GoImageEngine) TransformGIF(img *imagefile.ImageFile, options *Options, trans Transformation) ([]byte, error) { +func (e *GoImage) TransformGIF(img *imagefile.ImageFile, options *Options, trans Transformation) ([]byte, error) { first, err := gif.Decode(bytes.NewReader(img.Source)) if err != nil { @@ -126,7 +126,7 @@ func (e *GoImageEngine) TransformGIF(img *imagefile.ImageFile, options *Options, return buf.Bytes(), nil } -func (e *GoImageEngine) Resize(img *imagefile.ImageFile, options *Options) ([]byte, error) { +func (e *GoImage) Resize(img *imagefile.ImageFile, options *Options) ([]byte, error) { if options.Format == imaging.GIF { content, err := e.TransformGIF(img, options, imaging.Resize) @@ -146,15 +146,15 @@ func (e *GoImageEngine) Resize(img *imagefile.ImageFile, options *Options) ([]by return e.transform(image, options, imaging.Resize) } -func (e *GoImageEngine) transform(img image.Image, options *Options, trans Transformation) ([]byte, error) { +func (e *GoImage) transform(img image.Image, options *Options, trans Transformation) ([]byte, error) { return e.ToBytes(scale(img, options, trans), options.Format, options.Quality) } -func (e *GoImageEngine) Source(img *imagefile.ImageFile) (image.Image, error) { +func (e *GoImage) Source(img *imagefile.ImageFile) (image.Image, error) { return decode(bytes.NewReader(img.Source)) } -func (e *GoImageEngine) Rotate(img *imagefile.ImageFile, options *Options) ([]byte, error) { +func (e *GoImage) Rotate(img *imagefile.ImageFile, options *Options) ([]byte, error) { image, err := e.Source(img) if err != nil { @@ -172,7 +172,7 @@ func (e *GoImageEngine) Rotate(img *imagefile.ImageFile, options *Options) ([]by return e.ToBytes(transform(image), options.Format, options.Quality) } -func (e *GoImageEngine) Flip(img *imagefile.ImageFile, options *Options) ([]byte, error) { +func (e *GoImage) Flip(img *imagefile.ImageFile, options *Options) ([]byte, error) { image, err := e.Source(img) if err != nil { @@ -190,7 +190,7 @@ func (e *GoImageEngine) Flip(img *imagefile.ImageFile, options *Options) ([]byte return e.ToBytes(transform(image), options.Format, options.Quality) } -func (e *GoImageEngine) Thumbnail(img *imagefile.ImageFile, options *Options) ([]byte, error) { +func (e *GoImage) Thumbnail(img *imagefile.ImageFile, options *Options) ([]byte, error) { if options.Format == imaging.GIF { content, err := e.TransformGIF(img, options, imaging.Thumbnail) @@ -210,7 +210,7 @@ func (e *GoImageEngine) Thumbnail(img *imagefile.ImageFile, options *Options) ([ return e.transform(image, options, imaging.Thumbnail) } -func (e *GoImageEngine) Fit(img *imagefile.ImageFile, options *Options) ([]byte, error) { +func (e *GoImage) Fit(img *imagefile.ImageFile, options *Options) ([]byte, error) { if options.Format == imaging.GIF { content, err := e.TransformGIF(img, options, imaging.Thumbnail) @@ -230,7 +230,7 @@ func (e *GoImageEngine) Fit(img *imagefile.ImageFile, options *Options) ([]byte, return e.transform(image, options, imaging.Fit) } -func (e *GoImageEngine) ToBytes(img image.Image, format imaging.Format, quality int) ([]byte, error) { +func (e *GoImage) ToBytes(img image.Image, format imaging.Format, quality int) ([]byte, error) { buf := &bytes.Buffer{} var err error diff --git a/engine/backend/lilliput.go b/engine/backend/lilliput.go index 30ede796..7cd960e9 100644 --- a/engine/backend/lilliput.go +++ b/engine/backend/lilliput.go @@ -7,63 +7,97 @@ import ( "github.com/discordapp/lilliput" "github.com/pkg/errors" + "github.com/thoas/picfit/engine/config" imagefile "github.com/thoas/picfit/image" ) -type LilliputEngine struct { - MaxBufferSize int +type Lilliput struct { + MaxBufferSize int + ImageBufferSize int + EncodeOptions map[int]int } -func NewLilliputEngine(maxBufferSize int) *LilliputEngine { - if maxBufferSize > 0 { - return &LilliputEngine{MaxBufferSize: maxBufferSize} +func NewLilliput(cfg config.Config) *Lilliput { + maxBufferSize := config.DefaultMaxBufferSize + if cfg.MaxBufferSize != 0 { + maxBufferSize = cfg.MaxBufferSize } - return &LilliputEngine{MaxBufferSize: 8192} + + imageBufferSize := config.DefaultImageBufferSize + if cfg.ImageBufferSize != 0 { + imageBufferSize = cfg.ImageBufferSize + } + + jpegQuality := config.DefaultQuality + if cfg.JpegQuality != 0 { + jpegQuality = cfg.JpegQuality + } + + webpQuality := config.DefaultQuality + if cfg.WebpQuality != 0 { + webpQuality = cfg.WebpQuality + } + + pngCompression := config.DefaultPngCompression + if cfg.PngCompression != 0 { + pngCompression = cfg.PngCompression + } + + return &Lilliput{ + MaxBufferSize: maxBufferSize, + ImageBufferSize: imageBufferSize, + EncodeOptions: map[int]int{ + lilliput.JpegQuality: jpegQuality, + lilliput.PngCompression: pngCompression, + lilliput.WebpQuality: webpQuality, + }} } // Resize resizes the image to the specified width and height and // returns the transformed image. If one of width or height is 0, // the image aspect ratio is preserved. -func (e *LilliputEngine) Resize(img *imagefile.ImageFile, options *Options) ([]byte, error) { +func (e *Lilliput) Resize(img *imagefile.ImageFile, options *Options) ([]byte, error) { opts := &lilliput.ImageOptions{ FileType: img.FilenameExt(), Width: options.Width, Height: options.Height, NormalizeOrientation: true, ResizeMethod: lilliput.ImageOpsResize, + EncodeOptions: e.EncodeOptions, } return e.transform(img, opts, options.Upscale) } -func (e *LilliputEngine) Rotate(img *imagefile.ImageFile, options *Options) ([]byte, error) { +func (e *Lilliput) Rotate(img *imagefile.ImageFile, options *Options) ([]byte, error) { return nil, MethodNotImplementedError } -func (e *LilliputEngine) Flip(img *imagefile.ImageFile, options *Options) ([]byte, error) { +func (e *Lilliput) Flip(img *imagefile.ImageFile, options *Options) ([]byte, error) { return nil, MethodNotImplementedError } // Thumbnail scales the image up or down using the specified resample filter, crops it // to the specified width and hight and returns the transformed image. -func (e *LilliputEngine) Thumbnail(img *imagefile.ImageFile, options *Options) ([]byte, error) { +func (e *Lilliput) Thumbnail(img *imagefile.ImageFile, options *Options) ([]byte, error) { opts := &lilliput.ImageOptions{ FileType: img.FilenameExt(), Width: options.Width, Height: options.Height, NormalizeOrientation: true, // Lilliput ImageOpsFit is a thumbnail operation - ResizeMethod: lilliput.ImageOpsFit, + ResizeMethod: lilliput.ImageOpsFit, + EncodeOptions: e.EncodeOptions, } return e.transform(img, opts, options.Upscale) } -func (e *LilliputEngine) Fit(img *imagefile.ImageFile, options *Options) ([]byte, error) { +func (e *Lilliput) Fit(img *imagefile.ImageFile, options *Options) ([]byte, error) { return nil, MethodNotImplementedError } -func (e *LilliputEngine) transform(img *imagefile.ImageFile, options *lilliput.ImageOptions, upscale bool) ([]byte, error) { +func (e *Lilliput) transform(img *imagefile.ImageFile, options *lilliput.ImageOptions, upscale bool) ([]byte, error) { decoder, err := lilliput.NewDecoder(img.Source) if err != nil { return nil, errors.WithStack(err) @@ -109,11 +143,11 @@ func (e *LilliputEngine) transform(img *imagefile.ImageFile, options *lilliput.I ops := lilliput.NewImageOps(e.MaxBufferSize) defer ops.Close() - outputImg := make([]byte, 50*1024*1024) + outputImg := make([]byte, e.ImageBufferSize) return ops.Transform(decoder, options, outputImg) } -func (e *LilliputEngine) String() string { +func (e *Lilliput) String() string { return "lilliput" } diff --git a/engine/config.go b/engine/config.go deleted file mode 100644 index 0dd76ac3..00000000 --- a/engine/config.go +++ /dev/null @@ -1,10 +0,0 @@ -package engine - -// Config is the engine config -type Config struct { - Backends []string `mapstructure:"backends"` - DefaultFormat string `mapstructure:"default_format"` - Format string `mapstructure:"format"` - Quality int `mapstructure:"quality"` - MaxBufferSize int `mapstructure:"max_buffer_size"` -} diff --git a/engine/config/config.go b/engine/config/config.go new file mode 100644 index 00000000..dce7a483 --- /dev/null +++ b/engine/config/config.go @@ -0,0 +1,14 @@ +package config + +// Config is the engine config +type Config struct { + Backends []string `mapstructure:"backends"` + DefaultFormat string `mapstructure:"default_format"` + Format string `mapstructure:"format"` + Quality int `mapstructure:"quality"` + MaxBufferSize int `mapstructure:"max_buffer_size"` + ImageBufferSize int `mapstructure:"image_buffer_size"` + JpegQuality int `mapstructure:"jpeg_quality"` + PngCompression int `mapstructure:"png_compression"` + WebpQuality int `mapstructure:"webp_quality"` +} diff --git a/engine/config/constants.go b/engine/config/constants.go new file mode 100644 index 00000000..96e7dd52 --- /dev/null +++ b/engine/config/constants.go @@ -0,0 +1,13 @@ +package config + +// DefaultQuality is the default quality +const DefaultQuality = 85 + +// DefaultPngCompression is the default compression for png. +const DefaultPngCompression = 0 + +// DefaultMaxBufferSize is the maximum size of buffer for lilliput +const DefaultMaxBufferSize = 8192 + +// DefaultImageBufferSize is the default image buffer size for lilliput +const DefaultImageBufferSize = 50 * 1024 * 1024 diff --git a/engine/engine.go b/engine/engine.go index a5387d6f..3f77cad5 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -9,6 +9,7 @@ import ( "github.com/imdario/mergo" "github.com/thoas/picfit/engine/backend" + "github.com/thoas/picfit/engine/config" "github.com/thoas/picfit/image" ) @@ -41,18 +42,18 @@ const ( ) // New initializes an Engine -func New(cfg Config) *Engine { +func New(cfg config.Config) *Engine { var b []backend.Backend for i := range cfg.Backends { if cfg.Backends[i] == lilliputEngineType { - b = append(b, backend.NewLilliputEngine(cfg.MaxBufferSize)) + b = append(b, backend.NewLilliput(cfg)) } else if cfg.Backends[i] == goEngineType { - b = append(b, &backend.GoImageEngine{}) + b = append(b, &backend.GoImage{}) } } if len(b) == 0 { - b = append(b, &backend.GoImageEngine{}) + b = append(b, &backend.GoImage{}) } return &Engine{