mirror of
https://github.com/imgproxy/imgproxy.git
synced 2025-09-28 20:43:54 +02:00
Processor instance
This commit is contained in:
@@ -7,6 +7,7 @@ import (
|
|||||||
processinghandler "github.com/imgproxy/imgproxy/v3/handlers/processing"
|
processinghandler "github.com/imgproxy/imgproxy/v3/handlers/processing"
|
||||||
streamhandler "github.com/imgproxy/imgproxy/v3/handlers/stream"
|
streamhandler "github.com/imgproxy/imgproxy/v3/handlers/stream"
|
||||||
"github.com/imgproxy/imgproxy/v3/options"
|
"github.com/imgproxy/imgproxy/v3/options"
|
||||||
|
"github.com/imgproxy/imgproxy/v3/processing"
|
||||||
"github.com/imgproxy/imgproxy/v3/security"
|
"github.com/imgproxy/imgproxy/v3/security"
|
||||||
"github.com/imgproxy/imgproxy/v3/server"
|
"github.com/imgproxy/imgproxy/v3/server"
|
||||||
"github.com/imgproxy/imgproxy/v3/workers"
|
"github.com/imgproxy/imgproxy/v3/workers"
|
||||||
@@ -27,6 +28,7 @@ type Config struct {
|
|||||||
Handlers HandlerConfigs
|
Handlers HandlerConfigs
|
||||||
Server server.Config
|
Server server.Config
|
||||||
Security security.Config
|
Security security.Config
|
||||||
|
Processing processing.Config
|
||||||
Options options.Config
|
Options options.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,6 +45,7 @@ func NewDefaultConfig() Config {
|
|||||||
},
|
},
|
||||||
Server: server.NewDefaultConfig(),
|
Server: server.NewDefaultConfig(),
|
||||||
Security: security.NewDefaultConfig(),
|
Security: security.NewDefaultConfig(),
|
||||||
|
Processing: processing.NewDefaultConfig(),
|
||||||
Options: options.NewDefaultConfig(),
|
Options: options.NewDefaultConfig(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -89,5 +92,9 @@ func LoadConfigFromEnv(c *Config) (*Config, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if _, err = processing.LoadConfigFromEnv(&c.Processing); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
@@ -14,6 +14,7 @@ import (
|
|||||||
"github.com/imgproxy/imgproxy/v3/monitoring"
|
"github.com/imgproxy/imgproxy/v3/monitoring"
|
||||||
"github.com/imgproxy/imgproxy/v3/monitoring/stats"
|
"github.com/imgproxy/imgproxy/v3/monitoring/stats"
|
||||||
"github.com/imgproxy/imgproxy/v3/options"
|
"github.com/imgproxy/imgproxy/v3/options"
|
||||||
|
"github.com/imgproxy/imgproxy/v3/processing"
|
||||||
"github.com/imgproxy/imgproxy/v3/security"
|
"github.com/imgproxy/imgproxy/v3/security"
|
||||||
"github.com/imgproxy/imgproxy/v3/server"
|
"github.com/imgproxy/imgproxy/v3/server"
|
||||||
"github.com/imgproxy/imgproxy/v3/workers"
|
"github.com/imgproxy/imgproxy/v3/workers"
|
||||||
@@ -27,6 +28,7 @@ type HandlerContext interface {
|
|||||||
ImageDataFactory() *imagedata.Factory
|
ImageDataFactory() *imagedata.Factory
|
||||||
Security() *security.Checker
|
Security() *security.Checker
|
||||||
OptionsFactory() *options.Factory
|
OptionsFactory() *options.Factory
|
||||||
|
Processor() *processing.Processor
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handler handles image processing requests
|
// Handler handles image processing requests
|
||||||
|
@@ -157,7 +157,7 @@ func (r *request) getFallbackImage(
|
|||||||
// processImage calls actual image processing
|
// processImage calls actual image processing
|
||||||
func (r *request) processImage(ctx context.Context, originData imagedata.ImageData) (*processing.Result, error) {
|
func (r *request) processImage(ctx context.Context, originData imagedata.ImageData) (*processing.Result, error) {
|
||||||
defer monitoring.StartProcessingSegment(ctx, r.monitoringMeta.Filter(monitoring.MetaProcessingOptions))()
|
defer monitoring.StartProcessingSegment(ctx, r.monitoringMeta.Filter(monitoring.MetaProcessingOptions))()
|
||||||
return processing.ProcessImage(ctx, originData, r.po, r.WatermarkImage())
|
return r.Processor().ProcessImage(ctx, originData, r.po, r.WatermarkImage())
|
||||||
}
|
}
|
||||||
|
|
||||||
// writeDebugHeaders writes debug headers (X-Origin-*, X-Result-*) to the response
|
// writeDebugHeaders writes debug headers (X-Origin-*, X-Result-*) to the response
|
||||||
|
13
imgproxy.go
13
imgproxy.go
@@ -15,6 +15,7 @@ import (
|
|||||||
"github.com/imgproxy/imgproxy/v3/memory"
|
"github.com/imgproxy/imgproxy/v3/memory"
|
||||||
"github.com/imgproxy/imgproxy/v3/monitoring/prometheus"
|
"github.com/imgproxy/imgproxy/v3/monitoring/prometheus"
|
||||||
"github.com/imgproxy/imgproxy/v3/options"
|
"github.com/imgproxy/imgproxy/v3/options"
|
||||||
|
"github.com/imgproxy/imgproxy/v3/processing"
|
||||||
"github.com/imgproxy/imgproxy/v3/security"
|
"github.com/imgproxy/imgproxy/v3/security"
|
||||||
"github.com/imgproxy/imgproxy/v3/server"
|
"github.com/imgproxy/imgproxy/v3/server"
|
||||||
"github.com/imgproxy/imgproxy/v3/workers"
|
"github.com/imgproxy/imgproxy/v3/workers"
|
||||||
@@ -31,6 +32,7 @@ type ImgproxyHandlers struct {
|
|||||||
Landing *landinghandler.Handler
|
Landing *landinghandler.Handler
|
||||||
Processing *processinghandler.Handler
|
Processing *processinghandler.Handler
|
||||||
Stream *streamhandler.Handler
|
Stream *streamhandler.Handler
|
||||||
|
Processor *processing.Processor
|
||||||
}
|
}
|
||||||
|
|
||||||
// Imgproxy holds all the components needed for imgproxy to function
|
// Imgproxy holds all the components needed for imgproxy to function
|
||||||
@@ -43,6 +45,7 @@ type Imgproxy struct {
|
|||||||
handlers ImgproxyHandlers
|
handlers ImgproxyHandlers
|
||||||
security *security.Checker
|
security *security.Checker
|
||||||
optionsFactory *options.Factory
|
optionsFactory *options.Factory
|
||||||
|
processor *processing.Processor
|
||||||
config *Config
|
config *Config
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,6 +83,11 @@ func New(ctx context.Context, config *Config) (*Imgproxy, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
processor, err := processing.New(&config.Processing, watermarkImage)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
imgproxy := &Imgproxy{
|
imgproxy := &Imgproxy{
|
||||||
workers: workers,
|
workers: workers,
|
||||||
fallbackImage: fallbackImage,
|
fallbackImage: fallbackImage,
|
||||||
@@ -89,6 +97,7 @@ func New(ctx context.Context, config *Config) (*Imgproxy, error) {
|
|||||||
config: config,
|
config: config,
|
||||||
security: security,
|
security: security,
|
||||||
optionsFactory: processingOptionsFactory,
|
optionsFactory: processingOptionsFactory,
|
||||||
|
processor: processor,
|
||||||
}
|
}
|
||||||
|
|
||||||
imgproxy.handlers.Health = healthhandler.New()
|
imgproxy.handlers.Health = healthhandler.New()
|
||||||
@@ -211,3 +220,7 @@ func (i *Imgproxy) Security() *security.Checker {
|
|||||||
func (i *Imgproxy) OptionsFactory() *options.Factory {
|
func (i *Imgproxy) OptionsFactory() *options.Factory {
|
||||||
return i.optionsFactory
|
return i.optionsFactory
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i *Imgproxy) Processor() *processing.Processor {
|
||||||
|
return i.processor
|
||||||
|
}
|
||||||
|
6
init.go
6
init.go
@@ -10,7 +10,6 @@ import (
|
|||||||
"github.com/imgproxy/imgproxy/v3/gliblog"
|
"github.com/imgproxy/imgproxy/v3/gliblog"
|
||||||
"github.com/imgproxy/imgproxy/v3/logger"
|
"github.com/imgproxy/imgproxy/v3/logger"
|
||||||
"github.com/imgproxy/imgproxy/v3/monitoring"
|
"github.com/imgproxy/imgproxy/v3/monitoring"
|
||||||
"github.com/imgproxy/imgproxy/v3/processing"
|
|
||||||
"github.com/imgproxy/imgproxy/v3/vips"
|
"github.com/imgproxy/imgproxy/v3/vips"
|
||||||
"go.uber.org/automaxprocs/maxprocs"
|
"go.uber.org/automaxprocs/maxprocs"
|
||||||
)
|
)
|
||||||
@@ -47,11 +46,6 @@ func Init() error {
|
|||||||
|
|
||||||
errorreport.Init()
|
errorreport.Init()
|
||||||
|
|
||||||
if err := processing.ValidatePreferredFormats(); err != nil {
|
|
||||||
vips.Shutdown()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
package processing
|
package processing
|
||||||
|
|
||||||
func applyFilters(c *Context) error {
|
func (p *Processor) applyFilters(c *Context) error {
|
||||||
if c.PO.Blur == 0 && c.PO.Sharpen == 0 && c.PO.Pixelate <= 1 {
|
if c.PO.Blur == 0 && c.PO.Sharpen == 0 && c.PO.Pixelate <= 1 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
package processing
|
package processing
|
||||||
|
|
||||||
func colorspaceToProcessing(c *Context) error {
|
func (p *Processor) colorspaceToProcessing(c *Context) error {
|
||||||
if c.Img.ColourProfileImported() {
|
if c.Img.ColourProfileImported() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -9,7 +9,7 @@ func colorspaceToProcessing(c *Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
convertToLinear := c.Config.UseLinearColorspace && (c.WScale != 1 || c.HScale != 1)
|
convertToLinear := p.config.UseLinearColorspace && (c.WScale != 1 || c.HScale != 1)
|
||||||
|
|
||||||
if c.Img.IsLinear() {
|
if c.Img.IsLinear() {
|
||||||
// The image is linear. If we keep its ICC, we'll get wrong colors after
|
// The image is linear. If we keep its ICC, we'll get wrong colors after
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
package processing
|
package processing
|
||||||
|
|
||||||
func colorspaceToResult(c *Context) error {
|
func (p *Processor) colorspaceToResult(c *Context) error {
|
||||||
keepProfile := !c.PO.StripColorProfile && c.PO.Format.SupportsColourProfile()
|
keepProfile := !c.PO.StripColorProfile && c.PO.Format.SupportsColourProfile()
|
||||||
|
|
||||||
if c.Img.IsLinear() {
|
if c.Img.IsLinear() {
|
||||||
|
@@ -1,14 +1,18 @@
|
|||||||
package pipeline
|
package processing
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"github.com/imgproxy/imgproxy/v3/config"
|
"github.com/imgproxy/imgproxy/v3/config"
|
||||||
"github.com/imgproxy/imgproxy/v3/ensure"
|
"github.com/imgproxy/imgproxy/v3/ensure"
|
||||||
|
"github.com/imgproxy/imgproxy/v3/imagetype"
|
||||||
|
"github.com/imgproxy/imgproxy/v3/vips"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Config holds pipeline-related configuration.
|
// Config holds pipeline-related configuration.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
|
PreferredFormats []imagetype.Type
|
||||||
WatermarkOpacity float64
|
WatermarkOpacity float64
|
||||||
DisableShrinkOnLoad bool
|
DisableShrinkOnLoad bool
|
||||||
UseLinearColorspace bool
|
UseLinearColorspace bool
|
||||||
@@ -18,6 +22,11 @@ type Config struct {
|
|||||||
func NewDefaultConfig() Config {
|
func NewDefaultConfig() Config {
|
||||||
return Config{
|
return Config{
|
||||||
WatermarkOpacity: 1,
|
WatermarkOpacity: 1,
|
||||||
|
PreferredFormats: []imagetype.Type{
|
||||||
|
imagetype.JPEG,
|
||||||
|
imagetype.PNG,
|
||||||
|
imagetype.GIF,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,6 +37,7 @@ func LoadConfigFromEnv(c *Config) (*Config, error) {
|
|||||||
c.WatermarkOpacity = config.WatermarkOpacity
|
c.WatermarkOpacity = config.WatermarkOpacity
|
||||||
c.DisableShrinkOnLoad = config.DisableShrinkOnLoad
|
c.DisableShrinkOnLoad = config.DisableShrinkOnLoad
|
||||||
c.UseLinearColorspace = config.UseLinearColorspace
|
c.UseLinearColorspace = config.UseLinearColorspace
|
||||||
|
c.PreferredFormats = config.PreferredFormats
|
||||||
|
|
||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
@@ -40,5 +50,21 @@ func (c *Config) Validate() error {
|
|||||||
return errors.New("watermark opacity should be less than or equal to 1")
|
return errors.New("watermark opacity should be less than or equal to 1")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
filtered := c.PreferredFormats[:0]
|
||||||
|
|
||||||
|
for _, t := range c.PreferredFormats {
|
||||||
|
if !vips.SupportsSave(t) {
|
||||||
|
log.Warnf("%s can't be a preferred format as it's saving is not supported", t)
|
||||||
|
} else {
|
||||||
|
filtered = append(filtered, t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(filtered) == 0 {
|
||||||
|
return errors.New("no supported preferred formats specified")
|
||||||
|
}
|
||||||
|
|
||||||
|
c.PreferredFormats = filtered
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
@@ -31,7 +31,7 @@ func cropImage(img *vips.Image, cropWidth, cropHeight int, gravity *options.Grav
|
|||||||
return img.Crop(left, top, cropWidth, cropHeight)
|
return img.Crop(left, top, cropWidth, cropHeight)
|
||||||
}
|
}
|
||||||
|
|
||||||
func crop(c *Context) error {
|
func (p *Processor) crop(c *Context) error {
|
||||||
width, height := c.CropWidth, c.CropHeight
|
width, height := c.CropWidth, c.CropHeight
|
||||||
|
|
||||||
opts := c.CropGravity
|
opts := c.CropGravity
|
||||||
@@ -46,6 +46,6 @@ func crop(c *Context) error {
|
|||||||
return cropImage(c.Img, width, height, &opts, 1.0)
|
return cropImage(c.Img, width, height, &opts, 1.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func cropToResult(c *Context) error {
|
func (p *Processor) cropToResult(c *Context) error {
|
||||||
return cropImage(c.Img, c.ResultCropWidth, c.ResultCropHeight, &c.PO.Gravity, c.DprScale)
|
return cropImage(c.Img, c.ResultCropWidth, c.ResultCropHeight, &c.PO.Gravity, c.DprScale)
|
||||||
}
|
}
|
||||||
|
@@ -24,7 +24,7 @@ func extendImage(img *vips.Image, width, height int, gravity *options.GravityOpt
|
|||||||
return img.Embed(width, height, offX, offY)
|
return img.Embed(width, height, offX, offY)
|
||||||
}
|
}
|
||||||
|
|
||||||
func extend(c *Context) error {
|
func (p *Processor) extend(c *Context) error {
|
||||||
if !c.PO.Extend.Enabled {
|
if !c.PO.Extend.Enabled {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -33,7 +33,7 @@ func extend(c *Context) error {
|
|||||||
return extendImage(c.Img, width, height, &c.PO.Extend.Gravity, c.DprScale)
|
return extendImage(c.Img, width, height, &c.PO.Extend.Gravity, c.DprScale)
|
||||||
}
|
}
|
||||||
|
|
||||||
func extendAspectRatio(c *Context) error {
|
func (p *Processor) extendAspectRatio(c *Context) error {
|
||||||
if !c.PO.ExtendAspectRatio.Enabled {
|
if !c.PO.ExtendAspectRatio.Enabled {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@@ -88,7 +88,7 @@ func fixIcoSize(img *vips.Image) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func fixSize(c *Context) error {
|
func (p *Processor) fixSize(c *Context) error {
|
||||||
switch c.PO.Format {
|
switch c.PO.Format {
|
||||||
case imagetype.WEBP:
|
case imagetype.WEBP:
|
||||||
return fixWebpSize(c.Img)
|
return fixWebpSize(c.Img)
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
package processing
|
package processing
|
||||||
|
|
||||||
func flatten(c *Context) error {
|
func (p *Processor) flatten(c *Context) error {
|
||||||
if !c.PO.Flatten && c.PO.Format.SupportsAlpha() {
|
if !c.PO.Flatten && c.PO.Format.SupportsAlpha() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@@ -4,7 +4,7 @@ import (
|
|||||||
"github.com/imgproxy/imgproxy/v3/imath"
|
"github.com/imgproxy/imgproxy/v3/imath"
|
||||||
)
|
)
|
||||||
|
|
||||||
func padding(c *Context) error {
|
func (p *Processor) padding(c *Context) error {
|
||||||
if !c.PO.Padding.Enabled {
|
if !c.PO.Padding.Enabled {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@@ -6,21 +6,14 @@ import (
|
|||||||
"github.com/imgproxy/imgproxy/v3/auximageprovider"
|
"github.com/imgproxy/imgproxy/v3/auximageprovider"
|
||||||
"github.com/imgproxy/imgproxy/v3/imagedata"
|
"github.com/imgproxy/imgproxy/v3/imagedata"
|
||||||
"github.com/imgproxy/imgproxy/v3/options"
|
"github.com/imgproxy/imgproxy/v3/options"
|
||||||
"github.com/imgproxy/imgproxy/v3/processing/pipeline"
|
|
||||||
"github.com/imgproxy/imgproxy/v3/server"
|
"github.com/imgproxy/imgproxy/v3/server"
|
||||||
"github.com/imgproxy/imgproxy/v3/vips"
|
"github.com/imgproxy/imgproxy/v3/vips"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NOTE: this will be called pipeline.Context in the separate package
|
|
||||||
type Context struct {
|
type Context struct {
|
||||||
// The runner that runs this pipeline
|
// The context to check for timeouts and cancellations
|
||||||
runner *Runner
|
|
||||||
|
|
||||||
Ctx context.Context
|
Ctx context.Context
|
||||||
|
|
||||||
// Global processing configuration which could be used by individual steps
|
|
||||||
Config *pipeline.Config
|
|
||||||
|
|
||||||
// VIPS image
|
// VIPS image
|
||||||
Img *vips.Image
|
Img *vips.Image
|
||||||
|
|
||||||
@@ -80,33 +73,18 @@ type Context struct {
|
|||||||
ExtendAspectRatioHeight int
|
ExtendAspectRatioHeight int
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: same, pipeline.Step, pipeline.Pipeline, pipeline.Runner
|
type Step func(c *Context) error
|
||||||
type Step func(ctx *Context) error
|
|
||||||
type Pipeline []Step
|
type Pipeline []Step
|
||||||
|
|
||||||
// Runner is responsible for running a processing pipeline
|
|
||||||
type Runner struct {
|
|
||||||
config *pipeline.Config
|
|
||||||
watermark auximageprovider.Provider
|
|
||||||
}
|
|
||||||
|
|
||||||
// New creates a new Runner instance with the given configuration and watermark provider
|
|
||||||
func New(config *pipeline.Config, watermark auximageprovider.Provider) *Runner {
|
|
||||||
return &Runner{
|
|
||||||
config: config,
|
|
||||||
watermark: watermark,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run runs the given pipeline with the given parameters
|
// Run runs the given pipeline with the given parameters
|
||||||
func (f *Runner) Run(
|
func (p Pipeline) Run(
|
||||||
p Pipeline,
|
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
img *vips.Image,
|
img *vips.Image,
|
||||||
po *options.ProcessingOptions,
|
po *options.ProcessingOptions,
|
||||||
imgdata imagedata.ImageData,
|
imgdata imagedata.ImageData,
|
||||||
) error {
|
) error {
|
||||||
pctx := f.newContext(ctx, img, po, imgdata)
|
pctx := p.newContext(ctx, img, po, imgdata)
|
||||||
|
pctx.CalcParams() // calc initial params if not done before
|
||||||
|
|
||||||
for _, step := range p {
|
for _, step := range p {
|
||||||
if err := step(&pctx); err != nil {
|
if err := step(&pctx); err != nil {
|
||||||
@@ -123,17 +101,14 @@ func (f *Runner) Run(
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Runner) newContext(
|
func (p Pipeline) newContext(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
img *vips.Image,
|
img *vips.Image,
|
||||||
po *options.ProcessingOptions,
|
po *options.ProcessingOptions,
|
||||||
imgdata imagedata.ImageData,
|
imgdata imagedata.ImageData,
|
||||||
) Context {
|
) Context {
|
||||||
pctx := Context{
|
pctx := Context{
|
||||||
runner: r,
|
|
||||||
|
|
||||||
Ctx: ctx,
|
Ctx: ctx,
|
||||||
Config: r.config,
|
|
||||||
Img: img,
|
Img: img,
|
||||||
PO: po,
|
PO: po,
|
||||||
ImgData: imgdata,
|
ImgData: imgdata,
|
||||||
@@ -145,7 +120,6 @@ func (r *Runner) newContext(
|
|||||||
VectorBaseScale: 1.0,
|
VectorBaseScale: 1.0,
|
||||||
|
|
||||||
CropGravity: po.Crop.Gravity,
|
CropGravity: po.Crop.Gravity,
|
||||||
WatermarkProvider: r.watermark,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if pctx.CropGravity.Type == options.GravityUnknown {
|
if pctx.CropGravity.Type == options.GravityUnknown {
|
||||||
@@ -154,7 +128,3 @@ func (r *Runner) newContext(
|
|||||||
|
|
||||||
return pctx
|
return pctx
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) Runner() *Runner {
|
|
||||||
return c.runner
|
|
||||||
}
|
|
||||||
|
@@ -8,29 +8,12 @@ import (
|
|||||||
"github.com/imgproxy/imgproxy/v3/vips"
|
"github.com/imgproxy/imgproxy/v3/vips"
|
||||||
)
|
)
|
||||||
|
|
||||||
func extractMeta(img *vips.Image, baseAngle int, useOrientation bool) (int, int, int, bool) {
|
// ExtractGeometry extracts image width, height, orientation angle and flip flag from the image metadata.
|
||||||
|
func (c *Context) ExtractGeometry(img *vips.Image, baseAngle int, autoRotate bool) (int, int, int, bool) {
|
||||||
width := img.Width()
|
width := img.Width()
|
||||||
height := img.Height()
|
height := img.Height()
|
||||||
|
|
||||||
angle := 0
|
angle, flip := c.angleFlip(img, autoRotate)
|
||||||
flip := false
|
|
||||||
|
|
||||||
if useOrientation {
|
|
||||||
orientation := img.Orientation()
|
|
||||||
|
|
||||||
if orientation == 3 || orientation == 4 {
|
|
||||||
angle = 180
|
|
||||||
}
|
|
||||||
if orientation == 5 || orientation == 6 {
|
|
||||||
angle = 90
|
|
||||||
}
|
|
||||||
if orientation == 7 || orientation == 8 {
|
|
||||||
angle = 270
|
|
||||||
}
|
|
||||||
if orientation == 2 || orientation == 4 || orientation == 5 || orientation == 7 {
|
|
||||||
flip = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (angle+baseAngle)%180 != 0 {
|
if (angle+baseAngle)%180 != 0 {
|
||||||
width, height = height, width
|
width, height = height, width
|
||||||
@@ -39,7 +22,39 @@ func extractMeta(img *vips.Image, baseAngle int, useOrientation bool) (int, int,
|
|||||||
return width, height, angle, flip
|
return width, height, angle, flip
|
||||||
}
|
}
|
||||||
|
|
||||||
func calcCropSize(orig int, crop float64) int {
|
// angleFlip returns the orientation angle and flip flag based on the image metadata
|
||||||
|
// and po.AutoRotate flag.
|
||||||
|
func (c *Context) angleFlip(img *vips.Image, autoRotate bool) (int, bool) {
|
||||||
|
if !autoRotate {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
angle := 0
|
||||||
|
flip := false
|
||||||
|
|
||||||
|
orientation := img.Orientation()
|
||||||
|
|
||||||
|
if orientation == 3 || orientation == 4 {
|
||||||
|
angle = 180
|
||||||
|
}
|
||||||
|
|
||||||
|
if orientation == 5 || orientation == 6 {
|
||||||
|
angle = 90
|
||||||
|
}
|
||||||
|
|
||||||
|
if orientation == 7 || orientation == 8 {
|
||||||
|
angle = 270
|
||||||
|
}
|
||||||
|
|
||||||
|
if orientation == 2 || orientation == 4 || orientation == 5 || orientation == 7 {
|
||||||
|
flip = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return angle, flip
|
||||||
|
}
|
||||||
|
|
||||||
|
// CalcCropSize calculates the crop size based on the original size and crop scale.
|
||||||
|
func (c *Context) CalcCropSize(orig int, crop float64) int {
|
||||||
switch {
|
switch {
|
||||||
case crop == 0.0:
|
case crop == 0.0:
|
||||||
return 0
|
return 0
|
||||||
@@ -50,31 +65,28 @@ func calcCropSize(orig int, crop float64) int {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pctx *Context) calcScale(width, height int, po *options.ProcessingOptions) {
|
// calcShrink calculates the destination size and shrink factor
|
||||||
|
func calcShrink(value int, src, dst float64) (float64, float64) {
|
||||||
|
if value == 0 {
|
||||||
|
dst = src
|
||||||
|
}
|
||||||
|
|
||||||
|
shrink := 1.0
|
||||||
|
if dst != src {
|
||||||
|
shrink = src / dst
|
||||||
|
}
|
||||||
|
|
||||||
|
return dst, shrink
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Context) calcScale(width, height int, po *options.ProcessingOptions) {
|
||||||
var wshrink, hshrink float64
|
var wshrink, hshrink float64
|
||||||
|
|
||||||
srcW, srcH := float64(width), float64(height)
|
srcW, srcH := float64(width), float64(height)
|
||||||
dstW, dstH := float64(po.Width), float64(po.Height)
|
dstW, dstH := float64(po.Width), float64(po.Height)
|
||||||
|
|
||||||
if po.Width == 0 {
|
dstW, wshrink = calcShrink(po.Width, srcW, dstW)
|
||||||
dstW = srcW
|
dstH, hshrink = calcShrink(po.Height, srcH, dstH)
|
||||||
}
|
|
||||||
|
|
||||||
if dstW == srcW {
|
|
||||||
wshrink = 1
|
|
||||||
} else {
|
|
||||||
wshrink = srcW / dstW
|
|
||||||
}
|
|
||||||
|
|
||||||
if po.Height == 0 {
|
|
||||||
dstH = srcH
|
|
||||||
}
|
|
||||||
|
|
||||||
if dstH == srcH {
|
|
||||||
hshrink = 1
|
|
||||||
} else {
|
|
||||||
hshrink = srcH / dstH
|
|
||||||
}
|
|
||||||
|
|
||||||
if wshrink != 1 || hshrink != 1 {
|
if wshrink != 1 || hshrink != 1 {
|
||||||
rt := po.ResizingType
|
rt := po.ResizingType
|
||||||
@@ -107,22 +119,22 @@ func (pctx *Context) calcScale(width, height int, po *options.ProcessingOptions)
|
|||||||
wshrink /= po.ZoomWidth
|
wshrink /= po.ZoomWidth
|
||||||
hshrink /= po.ZoomHeight
|
hshrink /= po.ZoomHeight
|
||||||
|
|
||||||
pctx.DprScale = po.Dpr
|
c.DprScale = po.Dpr
|
||||||
|
|
||||||
if !po.Enlarge && pctx.ImgData != nil && !pctx.ImgData.Format().IsVector() {
|
if !po.Enlarge && c.ImgData != nil && !c.ImgData.Format().IsVector() {
|
||||||
minShrink := math.Min(wshrink, hshrink)
|
minShrink := math.Min(wshrink, hshrink)
|
||||||
if minShrink < 1 {
|
if minShrink < 1 {
|
||||||
wshrink /= minShrink
|
wshrink /= minShrink
|
||||||
hshrink /= minShrink
|
hshrink /= minShrink
|
||||||
|
|
||||||
if !po.Extend.Enabled {
|
if !po.Extend.Enabled {
|
||||||
pctx.DprScale /= minShrink
|
c.DprScale /= minShrink
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The minimum of wshrink and hshrink is the maximum dprScale value
|
// The minimum of wshrink and hshrink is the maximum dprScale value
|
||||||
// that can be used without enlarging the image.
|
// that can be used without enlarging the image.
|
||||||
pctx.DprScale = math.Min(pctx.DprScale, math.Min(wshrink, hshrink))
|
c.DprScale = math.Min(c.DprScale, math.Min(wshrink, hshrink))
|
||||||
}
|
}
|
||||||
|
|
||||||
if po.MinWidth > 0 {
|
if po.MinWidth > 0 {
|
||||||
@@ -139,8 +151,8 @@ func (pctx *Context) calcScale(width, height int, po *options.ProcessingOptions)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
wshrink /= pctx.DprScale
|
wshrink /= c.DprScale
|
||||||
hshrink /= pctx.DprScale
|
hshrink /= c.DprScale
|
||||||
|
|
||||||
if wshrink > srcW {
|
if wshrink > srcW {
|
||||||
wshrink = srcW
|
wshrink = srcW
|
||||||
@@ -150,110 +162,113 @@ func (pctx *Context) calcScale(width, height int, po *options.ProcessingOptions)
|
|||||||
hshrink = srcH
|
hshrink = srcH
|
||||||
}
|
}
|
||||||
|
|
||||||
pctx.WScale = 1.0 / wshrink
|
c.WScale = 1.0 / wshrink
|
||||||
pctx.HScale = 1.0 / hshrink
|
c.HScale = 1.0 / hshrink
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pctx *Context) calcSizes(widthToScale, heightToScale int, po *options.ProcessingOptions) {
|
func (c *Context) calcSizes(widthToScale, heightToScale int, po *options.ProcessingOptions) {
|
||||||
pctx.TargetWidth = imath.Scale(po.Width, pctx.DprScale*po.ZoomWidth)
|
c.TargetWidth = imath.Scale(po.Width, c.DprScale*po.ZoomWidth)
|
||||||
pctx.TargetHeight = imath.Scale(po.Height, pctx.DprScale*po.ZoomHeight)
|
c.TargetHeight = imath.Scale(po.Height, c.DprScale*po.ZoomHeight)
|
||||||
|
|
||||||
pctx.ScaledWidth = imath.Scale(widthToScale, pctx.WScale)
|
c.ScaledWidth = imath.Scale(widthToScale, c.WScale)
|
||||||
pctx.ScaledHeight = imath.Scale(heightToScale, pctx.HScale)
|
c.ScaledHeight = imath.Scale(heightToScale, c.HScale)
|
||||||
|
|
||||||
if po.ResizingType == options.ResizeFillDown && !po.Enlarge {
|
if po.ResizingType == options.ResizeFillDown && !po.Enlarge {
|
||||||
diffW := float64(pctx.TargetWidth) / float64(pctx.ScaledWidth)
|
diffW := float64(c.TargetWidth) / float64(c.ScaledWidth)
|
||||||
diffH := float64(pctx.TargetHeight) / float64(pctx.ScaledHeight)
|
diffH := float64(c.TargetHeight) / float64(c.ScaledHeight)
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case diffW > diffH && diffW > 1.0:
|
case diffW > diffH && diffW > 1.0:
|
||||||
pctx.ResultCropHeight = imath.Scale(pctx.ScaledWidth, float64(pctx.TargetHeight)/float64(pctx.TargetWidth))
|
c.ResultCropHeight = imath.Scale(c.ScaledWidth, float64(c.TargetHeight)/float64(c.TargetWidth))
|
||||||
pctx.ResultCropWidth = pctx.ScaledWidth
|
c.ResultCropWidth = c.ScaledWidth
|
||||||
|
|
||||||
case diffH > diffW && diffH > 1.0:
|
case diffH > diffW && diffH > 1.0:
|
||||||
pctx.ResultCropWidth = imath.Scale(pctx.ScaledHeight, float64(pctx.TargetWidth)/float64(pctx.TargetHeight))
|
c.ResultCropWidth = imath.Scale(c.ScaledHeight, float64(c.TargetWidth)/float64(c.TargetHeight))
|
||||||
pctx.ResultCropHeight = pctx.ScaledHeight
|
c.ResultCropHeight = c.ScaledHeight
|
||||||
|
|
||||||
default:
|
default:
|
||||||
pctx.ResultCropWidth = pctx.TargetWidth
|
c.ResultCropWidth = c.TargetWidth
|
||||||
pctx.ResultCropHeight = pctx.TargetHeight
|
c.ResultCropHeight = c.TargetHeight
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
pctx.ResultCropWidth = pctx.TargetWidth
|
c.ResultCropWidth = c.TargetWidth
|
||||||
pctx.ResultCropHeight = pctx.TargetHeight
|
c.ResultCropHeight = c.TargetHeight
|
||||||
}
|
}
|
||||||
|
|
||||||
if po.ExtendAspectRatio.Enabled && pctx.TargetWidth > 0 && pctx.TargetHeight > 0 {
|
if po.ExtendAspectRatio.Enabled && c.TargetWidth > 0 && c.TargetHeight > 0 {
|
||||||
outWidth := imath.MinNonZero(pctx.ScaledWidth, pctx.ResultCropWidth)
|
outWidth := imath.MinNonZero(c.ScaledWidth, c.ResultCropWidth)
|
||||||
outHeight := imath.MinNonZero(pctx.ScaledHeight, pctx.ResultCropHeight)
|
outHeight := imath.MinNonZero(c.ScaledHeight, c.ResultCropHeight)
|
||||||
|
|
||||||
diffW := float64(pctx.TargetWidth) / float64(outWidth)
|
diffW := float64(c.TargetWidth) / float64(outWidth)
|
||||||
diffH := float64(pctx.TargetHeight) / float64(outHeight)
|
diffH := float64(c.TargetHeight) / float64(outHeight)
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case diffH > diffW:
|
case diffH > diffW:
|
||||||
pctx.ExtendAspectRatioHeight = imath.Scale(outWidth, float64(pctx.TargetHeight)/float64(pctx.TargetWidth))
|
c.ExtendAspectRatioHeight = imath.Scale(outWidth, float64(c.TargetHeight)/float64(c.TargetWidth))
|
||||||
pctx.ExtendAspectRatioWidth = outWidth
|
c.ExtendAspectRatioWidth = outWidth
|
||||||
|
|
||||||
case diffW > diffH:
|
case diffW > diffH:
|
||||||
pctx.ExtendAspectRatioWidth = imath.Scale(outHeight, float64(pctx.TargetWidth)/float64(pctx.TargetHeight))
|
c.ExtendAspectRatioWidth = imath.Scale(outHeight, float64(c.TargetWidth)/float64(c.TargetHeight))
|
||||||
pctx.ExtendAspectRatioHeight = outHeight
|
c.ExtendAspectRatioHeight = outHeight
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pctx *Context) limitScale(widthToScale, heightToScale int, po *options.ProcessingOptions) {
|
func (c *Context) limitScale(widthToScale, heightToScale int, po *options.ProcessingOptions) {
|
||||||
maxresultDim := po.SecurityOptions.MaxResultDimension
|
maxresultDim := po.SecurityOptions.MaxResultDimension
|
||||||
|
|
||||||
if maxresultDim <= 0 {
|
if maxresultDim <= 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
outWidth := imath.MinNonZero(pctx.ScaledWidth, pctx.ResultCropWidth)
|
outWidth := imath.MinNonZero(c.ScaledWidth, c.ResultCropWidth)
|
||||||
outHeight := imath.MinNonZero(pctx.ScaledHeight, pctx.ResultCropHeight)
|
outHeight := imath.MinNonZero(c.ScaledHeight, c.ResultCropHeight)
|
||||||
|
|
||||||
if po.Extend.Enabled {
|
if po.Extend.Enabled {
|
||||||
outWidth = max(outWidth, pctx.TargetWidth)
|
outWidth = max(outWidth, c.TargetWidth)
|
||||||
outHeight = max(outHeight, pctx.TargetHeight)
|
outHeight = max(outHeight, c.TargetHeight)
|
||||||
} else if po.ExtendAspectRatio.Enabled {
|
} else if po.ExtendAspectRatio.Enabled {
|
||||||
outWidth = max(outWidth, pctx.ExtendAspectRatioWidth)
|
outWidth = max(outWidth, c.ExtendAspectRatioWidth)
|
||||||
outHeight = max(outHeight, pctx.ExtendAspectRatioHeight)
|
outHeight = max(outHeight, c.ExtendAspectRatioHeight)
|
||||||
}
|
}
|
||||||
|
|
||||||
if po.Padding.Enabled {
|
if po.Padding.Enabled {
|
||||||
outWidth += imath.ScaleToEven(po.Padding.Left, pctx.DprScale) + imath.ScaleToEven(po.Padding.Right, pctx.DprScale)
|
outWidth += imath.ScaleToEven(po.Padding.Left, c.DprScale) + imath.ScaleToEven(po.Padding.Right, c.DprScale)
|
||||||
outHeight += imath.ScaleToEven(po.Padding.Top, pctx.DprScale) + imath.ScaleToEven(po.Padding.Bottom, pctx.DprScale)
|
outHeight += imath.ScaleToEven(po.Padding.Top, c.DprScale) + imath.ScaleToEven(po.Padding.Bottom, c.DprScale)
|
||||||
}
|
}
|
||||||
|
|
||||||
if maxresultDim > 0 && (outWidth > maxresultDim || outHeight > maxresultDim) {
|
if maxresultDim > 0 && (outWidth > maxresultDim || outHeight > maxresultDim) {
|
||||||
downScale := float64(maxresultDim) / float64(max(outWidth, outHeight))
|
downScale := float64(maxresultDim) / float64(max(outWidth, outHeight))
|
||||||
|
|
||||||
pctx.WScale *= downScale
|
c.WScale *= downScale
|
||||||
pctx.HScale *= downScale
|
c.HScale *= downScale
|
||||||
|
|
||||||
// Prevent scaling below 1px
|
// Prevent scaling below 1px
|
||||||
if minWScale := 1.0 / float64(widthToScale); pctx.WScale < minWScale {
|
if minWScale := 1.0 / float64(widthToScale); c.WScale < minWScale {
|
||||||
pctx.WScale = minWScale
|
c.WScale = minWScale
|
||||||
}
|
}
|
||||||
if minHScale := 1.0 / float64(heightToScale); pctx.HScale < minHScale {
|
if minHScale := 1.0 / float64(heightToScale); c.HScale < minHScale {
|
||||||
pctx.HScale = minHScale
|
c.HScale = minHScale
|
||||||
}
|
}
|
||||||
|
|
||||||
pctx.DprScale *= downScale
|
c.DprScale *= downScale
|
||||||
|
|
||||||
// Recalculate the sizes after changing the scales
|
// Recalculate the sizes after changing the scales
|
||||||
pctx.calcSizes(widthToScale, heightToScale, po)
|
c.calcSizes(widthToScale, heightToScale, po)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// prepare extracts image metadata and calculates scaling factors and target sizes.
|
// Prepare calculates context image parameters based on the current image size.
|
||||||
// This can't be done in advance because some steps like trimming and rasterization could
|
// Some steps (like trim) must call this function when finished.
|
||||||
// happen before this step.
|
func (c *Context) CalcParams() {
|
||||||
func prepare(c *Context) error {
|
if c.ImgData == nil {
|
||||||
c.SrcWidth, c.SrcHeight, c.Angle, c.Flip = extractMeta(c.Img, c.PO.Rotate, c.PO.AutoRotate)
|
return
|
||||||
|
}
|
||||||
|
|
||||||
c.CropWidth = calcCropSize(c.SrcWidth, c.PO.Crop.Width)
|
c.SrcWidth, c.SrcHeight, c.Angle, c.Flip = c.ExtractGeometry(c.Img, c.PO.Rotate, c.PO.AutoRotate)
|
||||||
c.CropHeight = calcCropSize(c.SrcHeight, c.PO.Crop.Height)
|
|
||||||
|
c.CropWidth = c.CalcCropSize(c.SrcWidth, c.PO.Crop.Width)
|
||||||
|
c.CropHeight = c.CalcCropSize(c.SrcHeight, c.PO.Crop.Height)
|
||||||
|
|
||||||
widthToScale := imath.MinNonZero(c.CropWidth, c.SrcWidth)
|
widthToScale := imath.MinNonZero(c.CropWidth, c.SrcWidth)
|
||||||
heightToScale := imath.MinNonZero(c.CropHeight, c.SrcHeight)
|
heightToScale := imath.MinNonZero(c.CropHeight, c.SrcHeight)
|
||||||
@@ -261,6 +276,4 @@ func prepare(c *Context) error {
|
|||||||
c.calcScale(widthToScale, heightToScale, c.PO)
|
c.calcScale(widthToScale, heightToScale, c.PO)
|
||||||
c.calcSizes(widthToScale, heightToScale, c.PO)
|
c.calcSizes(widthToScale, heightToScale, c.PO)
|
||||||
c.limitScale(widthToScale, heightToScale, c.PO)
|
c.limitScale(widthToScale, heightToScale, c.PO)
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
@@ -2,7 +2,6 @@ package processing
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
"slices"
|
"slices"
|
||||||
|
|
||||||
@@ -13,59 +12,41 @@ import (
|
|||||||
"github.com/imgproxy/imgproxy/v3/imagedata"
|
"github.com/imgproxy/imgproxy/v3/imagedata"
|
||||||
"github.com/imgproxy/imgproxy/v3/imagetype"
|
"github.com/imgproxy/imgproxy/v3/imagetype"
|
||||||
"github.com/imgproxy/imgproxy/v3/options"
|
"github.com/imgproxy/imgproxy/v3/options"
|
||||||
"github.com/imgproxy/imgproxy/v3/processing/pipeline"
|
|
||||||
"github.com/imgproxy/imgproxy/v3/security"
|
"github.com/imgproxy/imgproxy/v3/security"
|
||||||
"github.com/imgproxy/imgproxy/v3/server"
|
"github.com/imgproxy/imgproxy/v3/server"
|
||||||
"github.com/imgproxy/imgproxy/v3/svg"
|
"github.com/imgproxy/imgproxy/v3/svg"
|
||||||
"github.com/imgproxy/imgproxy/v3/vips"
|
"github.com/imgproxy/imgproxy/v3/vips"
|
||||||
)
|
)
|
||||||
|
|
||||||
// The main processing pipeline (without finalization).
|
// mainPipeline constructs the main image processing pipeline.
|
||||||
// Applied to non-animated images and individual frames of animated images.
|
// This pipeline is applied to each image frame.
|
||||||
var mainPipeline = Pipeline{
|
func (p *Processor) mainPipeline() Pipeline {
|
||||||
vectorGuardScale,
|
return Pipeline{
|
||||||
trim,
|
p.vectorGuardScale,
|
||||||
prepare,
|
p.trim,
|
||||||
scaleOnLoad,
|
p.scaleOnLoad,
|
||||||
colorspaceToProcessing,
|
p.colorspaceToProcessing,
|
||||||
crop,
|
p.crop,
|
||||||
scale,
|
p.scale,
|
||||||
rotateAndFlip,
|
p.rotateAndFlip,
|
||||||
cropToResult,
|
p.cropToResult,
|
||||||
applyFilters,
|
p.applyFilters,
|
||||||
extend,
|
p.extend,
|
||||||
extendAspectRatio,
|
p.extendAspectRatio,
|
||||||
padding,
|
p.padding,
|
||||||
fixSize,
|
p.fixSize,
|
||||||
flatten,
|
p.flatten,
|
||||||
watermark,
|
p.watermark,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The finalization pipeline.
|
// finalizePipeline constructs the finalization pipeline.
|
||||||
// Applied right before saving the image.
|
// This pipeline is applied before saving the image.
|
||||||
var finalizePipeline = Pipeline{
|
func (p *Processor) finalizePipeline() Pipeline {
|
||||||
colorspaceToResult,
|
return Pipeline{
|
||||||
stripMetadata,
|
p.colorspaceToResult,
|
||||||
}
|
p.stripMetadata,
|
||||||
|
|
||||||
func ValidatePreferredFormats() error {
|
|
||||||
filtered := config.PreferredFormats[:0]
|
|
||||||
|
|
||||||
for _, t := range config.PreferredFormats {
|
|
||||||
if !vips.SupportsSave(t) {
|
|
||||||
log.Warnf("%s can't be a preferred format as it's saving is not supported", t)
|
|
||||||
} else {
|
|
||||||
filtered = append(filtered, t)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if len(filtered) == 0 {
|
|
||||||
return errors.New("no supported preferred formats specified")
|
|
||||||
}
|
|
||||||
|
|
||||||
config.PreferredFormats = filtered
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Result holds the result of image processing.
|
// Result holds the result of image processing.
|
||||||
@@ -81,7 +62,7 @@ type Result struct {
|
|||||||
// and returns a [Result] that includes the processed image data and dimensions.
|
// and returns a [Result] that includes the processed image data and dimensions.
|
||||||
//
|
//
|
||||||
// The provided processing options may be modified during processing.
|
// The provided processing options may be modified during processing.
|
||||||
func ProcessImage(
|
func (p *Processor) ProcessImage(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
imgdata imagedata.ImageData,
|
imgdata imagedata.ImageData,
|
||||||
po *options.ProcessingOptions,
|
po *options.ProcessingOptions,
|
||||||
@@ -139,19 +120,12 @@ func ProcessImage(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Transform the image (resize, crop, etc)
|
// Transform the image (resize, crop, etc)
|
||||||
if err = transformImage(ctx, img, po, imgdata, animated, watermarkProvider); err != nil {
|
if err = p.transformImage(ctx, img, po, imgdata, animated); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: THIS IS TEMPORARY
|
|
||||||
runner, err := tmpNewRunner(watermarkProvider)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// NOTE: END TEMPORARY BLOCK
|
|
||||||
|
|
||||||
// Finalize the image (colorspace conversion, metadata stripping, etc)
|
// Finalize the image (colorspace conversion, metadata stripping, etc)
|
||||||
if err = runner.Run(finalizePipeline, ctx, img, po, imgdata); err != nil {
|
if err = p.finalizePipeline().Run(ctx, img, po, imgdata); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -389,41 +363,25 @@ func findPreferredFormat(animated, expectTransparency bool) imagetype.Type {
|
|||||||
return config.PreferredFormats[0]
|
return config.PreferredFormats[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
func transformImage(
|
func (p *Processor) transformImage(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
img *vips.Image,
|
img *vips.Image,
|
||||||
po *options.ProcessingOptions,
|
po *options.ProcessingOptions,
|
||||||
imgdata imagedata.ImageData,
|
imgdata imagedata.ImageData,
|
||||||
asAnimated bool,
|
asAnimated bool,
|
||||||
watermark auximageprovider.Provider,
|
|
||||||
) error {
|
) error {
|
||||||
if asAnimated {
|
if asAnimated {
|
||||||
return transformAnimated(ctx, img, po, watermark)
|
return p.transformAnimated(ctx, img, po)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: THIS IS TEMPORARY
|
return p.mainPipeline().Run(ctx, img, po, imgdata)
|
||||||
runner, err := tmpNewRunner(watermark)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// NOTE: END TEMPORARY BLOCK
|
|
||||||
|
|
||||||
return runner.Run(mainPipeline, ctx, img, po, imgdata)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func transformAnimated(
|
func (p *Processor) transformAnimated(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
img *vips.Image,
|
img *vips.Image,
|
||||||
po *options.ProcessingOptions,
|
po *options.ProcessingOptions,
|
||||||
watermark auximageprovider.Provider,
|
|
||||||
) error {
|
) error {
|
||||||
// NOTE: THIS IS TEMPORARY
|
|
||||||
runner, rerr := tmpNewRunner(watermark)
|
|
||||||
if rerr != nil {
|
|
||||||
return rerr
|
|
||||||
}
|
|
||||||
// NOTE: END TEMPORARY BLOCK
|
|
||||||
|
|
||||||
if po.Trim.Enabled {
|
if po.Trim.Enabled {
|
||||||
log.Warning("Trim is not supported for animated images")
|
log.Warning("Trim is not supported for animated images")
|
||||||
po.Trim.Enabled = false
|
po.Trim.Enabled = false
|
||||||
@@ -477,7 +435,7 @@ func transformAnimated(
|
|||||||
// Transform the frame using the main pipeline.
|
// Transform the frame using the main pipeline.
|
||||||
// We don't provide imgdata here to prevent scale-on-load.
|
// We don't provide imgdata here to prevent scale-on-load.
|
||||||
// Watermarking is disabled for individual frames (see above)
|
// Watermarking is disabled for individual frames (see above)
|
||||||
if err = runner.Run(mainPipeline, ctx, frame, po, nil); err != nil {
|
if err = p.mainPipeline().Run(ctx, frame, po, nil); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -501,7 +459,7 @@ func transformAnimated(
|
|||||||
|
|
||||||
// Apply watermark to all frames at once if it was requested.
|
// Apply watermark to all frames at once if it was requested.
|
||||||
// This is much more efficient than applying watermark to individual frames.
|
// This is much more efficient than applying watermark to individual frames.
|
||||||
if watermarkEnabled && watermark != nil {
|
if watermarkEnabled && p.watermarkProvider != nil {
|
||||||
// Get DPR scale to apply watermark correctly on HiDPI images.
|
// Get DPR scale to apply watermark correctly on HiDPI images.
|
||||||
// `imgproxy-dpr-scale` is set by the pipeline.
|
// `imgproxy-dpr-scale` is set by the pipeline.
|
||||||
dprScale, derr := img.GetDoubleDefault("imgproxy-dpr-scale", 1.0)
|
dprScale, derr := img.GetDoubleDefault("imgproxy-dpr-scale", 1.0)
|
||||||
@@ -509,7 +467,7 @@ func transformAnimated(
|
|||||||
dprScale = 1.0
|
dprScale = 1.0
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = applyWatermark(ctx, runner, img, watermark, po, dprScale, framesCount); err != nil {
|
if err = p.applyWatermark(ctx, img, po, dprScale, framesCount); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -565,16 +523,3 @@ func saveImage(
|
|||||||
// Otherwise, just save the image with the specified quality.
|
// Otherwise, just save the image with the specified quality.
|
||||||
return img.Save(po.Format, po.GetQuality())
|
return img.Save(po.Format, po.GetQuality())
|
||||||
}
|
}
|
||||||
|
|
||||||
func tmpNewRunner(watermarkProvider auximageprovider.Provider) (*Runner, error) {
|
|
||||||
// NOTE: THIS IS TEMPORARY
|
|
||||||
config, err := pipeline.LoadConfigFromEnv(nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
runner := New(config, watermarkProvider)
|
|
||||||
|
|
||||||
return runner, nil
|
|
||||||
// NOTE: END TEMPORARY BLOCK
|
|
||||||
}
|
|
||||||
|
@@ -29,6 +29,8 @@ type ProcessingTestSuite struct {
|
|||||||
security testutil.LazyObj[*security.Checker]
|
security testutil.LazyObj[*security.Checker]
|
||||||
poConfig testutil.LazyObj[*options.Config]
|
poConfig testutil.LazyObj[*options.Config]
|
||||||
po testutil.LazyObj[*options.Factory]
|
po testutil.LazyObj[*options.Factory]
|
||||||
|
config testutil.LazyObj[*Config]
|
||||||
|
processor testutil.LazyObj[*Processor]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ProcessingTestSuite) SetupSuite() {
|
func (s *ProcessingTestSuite) SetupSuite() {
|
||||||
@@ -68,6 +70,15 @@ func (s *ProcessingTestSuite) SetupSuite() {
|
|||||||
s.po, _ = testutil.NewLazySuiteObj(s, func() (*options.Factory, error) {
|
s.po, _ = testutil.NewLazySuiteObj(s, func() (*options.Factory, error) {
|
||||||
return options.NewFactory(s.poConfig(), s.security())
|
return options.NewFactory(s.poConfig(), s.security())
|
||||||
})
|
})
|
||||||
|
|
||||||
|
s.config, _ = testutil.NewLazySuiteObj(s, func() (*Config, error) {
|
||||||
|
c := NewDefaultConfig()
|
||||||
|
return &c, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
s.processor, _ = testutil.NewLazySuiteObj(s, func() (*Processor, error) {
|
||||||
|
return New(s.config(), nil)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ProcessingTestSuite) openFile(name string) imagedata.ImageData {
|
func (s *ProcessingTestSuite) openFile(name string) imagedata.ImageData {
|
||||||
@@ -116,7 +127,7 @@ func (s *ProcessingTestSuite) TestResizeToFit() {
|
|||||||
po.Width = tc.width
|
po.Width = tc.width
|
||||||
po.Height = tc.height
|
po.Height = tc.height
|
||||||
|
|
||||||
result, err := ProcessImage(context.Background(), imgdata, po, nil)
|
result, err := s.processor().ProcessImage(context.Background(), imgdata, po, nil)
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
s.Require().NotNil(result)
|
s.Require().NotNil(result)
|
||||||
|
|
||||||
@@ -155,7 +166,7 @@ func (s *ProcessingTestSuite) TestResizeToFitEnlarge() {
|
|||||||
po.Width = tc.width
|
po.Width = tc.width
|
||||||
po.Height = tc.height
|
po.Height = tc.height
|
||||||
|
|
||||||
result, err := ProcessImage(context.Background(), imgdata, po, nil)
|
result, err := s.processor().ProcessImage(context.Background(), imgdata, po, nil)
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
s.Require().NotNil(result)
|
s.Require().NotNil(result)
|
||||||
|
|
||||||
@@ -199,7 +210,7 @@ func (s *ProcessingTestSuite) TestResizeToFitExtend() {
|
|||||||
po.Width = tc.width
|
po.Width = tc.width
|
||||||
po.Height = tc.height
|
po.Height = tc.height
|
||||||
|
|
||||||
result, err := ProcessImage(context.Background(), imgdata, po, nil)
|
result, err := s.processor().ProcessImage(context.Background(), imgdata, po, nil)
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
s.Require().NotNil(result)
|
s.Require().NotNil(result)
|
||||||
|
|
||||||
@@ -243,7 +254,7 @@ func (s *ProcessingTestSuite) TestResizeToFitExtendAR() {
|
|||||||
po.Width = tc.width
|
po.Width = tc.width
|
||||||
po.Height = tc.height
|
po.Height = tc.height
|
||||||
|
|
||||||
result, err := ProcessImage(context.Background(), imgdata, po, nil)
|
result, err := s.processor().ProcessImage(context.Background(), imgdata, po, nil)
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
s.Require().NotNil(result)
|
s.Require().NotNil(result)
|
||||||
|
|
||||||
@@ -287,7 +298,7 @@ func (s *ProcessingTestSuite) TestResizeToFill() {
|
|||||||
po.Width = tc.width
|
po.Width = tc.width
|
||||||
po.Height = tc.height
|
po.Height = tc.height
|
||||||
|
|
||||||
result, err := ProcessImage(context.Background(), imgdata, po, nil)
|
result, err := s.processor().ProcessImage(context.Background(), imgdata, po, nil)
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
s.Require().NotNil(result)
|
s.Require().NotNil(result)
|
||||||
|
|
||||||
@@ -326,7 +337,7 @@ func (s *ProcessingTestSuite) TestResizeToFillEnlarge() {
|
|||||||
po.Width = tc.width
|
po.Width = tc.width
|
||||||
po.Height = tc.height
|
po.Height = tc.height
|
||||||
|
|
||||||
result, err := ProcessImage(context.Background(), imgdata, po, nil)
|
result, err := s.processor().ProcessImage(context.Background(), imgdata, po, nil)
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
s.Require().NotNil(result)
|
s.Require().NotNil(result)
|
||||||
|
|
||||||
@@ -372,7 +383,7 @@ func (s *ProcessingTestSuite) TestResizeToFillExtend() {
|
|||||||
po.Width = tc.width
|
po.Width = tc.width
|
||||||
po.Height = tc.height
|
po.Height = tc.height
|
||||||
|
|
||||||
result, err := ProcessImage(context.Background(), imgdata, po, nil)
|
result, err := s.processor().ProcessImage(context.Background(), imgdata, po, nil)
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
s.Require().NotNil(result)
|
s.Require().NotNil(result)
|
||||||
|
|
||||||
@@ -418,7 +429,7 @@ func (s *ProcessingTestSuite) TestResizeToFillExtendAR() {
|
|||||||
po.Width = tc.width
|
po.Width = tc.width
|
||||||
po.Height = tc.height
|
po.Height = tc.height
|
||||||
|
|
||||||
result, err := ProcessImage(context.Background(), imgdata, po, nil)
|
result, err := s.processor().ProcessImage(context.Background(), imgdata, po, nil)
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
s.Require().NotNil(result)
|
s.Require().NotNil(result)
|
||||||
|
|
||||||
@@ -456,7 +467,7 @@ func (s *ProcessingTestSuite) TestResizeToFillDown() {
|
|||||||
po.Width = tc.width
|
po.Width = tc.width
|
||||||
po.Height = tc.height
|
po.Height = tc.height
|
||||||
|
|
||||||
result, err := ProcessImage(context.Background(), imgdata, po, nil)
|
result, err := s.processor().ProcessImage(context.Background(), imgdata, po, nil)
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
s.Require().NotNil(result)
|
s.Require().NotNil(result)
|
||||||
|
|
||||||
@@ -495,7 +506,7 @@ func (s *ProcessingTestSuite) TestResizeToFillDownEnlarge() {
|
|||||||
po.Width = tc.width
|
po.Width = tc.width
|
||||||
po.Height = tc.height
|
po.Height = tc.height
|
||||||
|
|
||||||
result, err := ProcessImage(context.Background(), imgdata, po, nil)
|
result, err := s.processor().ProcessImage(context.Background(), imgdata, po, nil)
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
s.Require().NotNil(result)
|
s.Require().NotNil(result)
|
||||||
|
|
||||||
@@ -541,7 +552,7 @@ func (s *ProcessingTestSuite) TestResizeToFillDownExtend() {
|
|||||||
po.Width = tc.width
|
po.Width = tc.width
|
||||||
po.Height = tc.height
|
po.Height = tc.height
|
||||||
|
|
||||||
result, err := ProcessImage(context.Background(), imgdata, po, nil)
|
result, err := s.processor().ProcessImage(context.Background(), imgdata, po, nil)
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
s.Require().NotNil(result)
|
s.Require().NotNil(result)
|
||||||
|
|
||||||
@@ -585,7 +596,7 @@ func (s *ProcessingTestSuite) TestResizeToFillDownExtendAR() {
|
|||||||
po.Width = tc.width
|
po.Width = tc.width
|
||||||
po.Height = tc.height
|
po.Height = tc.height
|
||||||
|
|
||||||
result, err := ProcessImage(context.Background(), imgdata, po, nil)
|
result, err := s.processor().ProcessImage(context.Background(), imgdata, po, nil)
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
s.Require().NotNil(result)
|
s.Require().NotNil(result)
|
||||||
|
|
||||||
@@ -1014,7 +1025,7 @@ func (s *ProcessingTestSuite) TestResultSizeLimit() {
|
|||||||
po.Rotate = tc.rotate
|
po.Rotate = tc.rotate
|
||||||
po.Padding = tc.padding
|
po.Padding = tc.padding
|
||||||
|
|
||||||
result, err := ProcessImage(context.Background(), imgdata, po, nil)
|
result, err := s.processor().ProcessImage(context.Background(), imgdata, po, nil)
|
||||||
|
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
s.Require().NotNil(result)
|
s.Require().NotNil(result)
|
||||||
@@ -1029,7 +1040,7 @@ func (s *ProcessingTestSuite) TestImageResolutionTooLarge() {
|
|||||||
po.SecurityOptions.MaxSrcResolution = 1
|
po.SecurityOptions.MaxSrcResolution = 1
|
||||||
|
|
||||||
imgdata := s.openFile("test2.jpg")
|
imgdata := s.openFile("test2.jpg")
|
||||||
_, err := ProcessImage(context.Background(), imgdata, po, nil)
|
_, err := s.processor().ProcessImage(context.Background(), imgdata, po, nil)
|
||||||
|
|
||||||
s.Require().Error(err)
|
s.Require().Error(err)
|
||||||
s.Require().Equal(422, ierrors.Wrap(err, 0).StatusCode())
|
s.Require().Equal(422, ierrors.Wrap(err, 0).StatusCode())
|
||||||
|
23
processing/processor.go
Normal file
23
processing/processor.go
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
package processing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/imgproxy/imgproxy/v3/auximageprovider"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Processor is responsible for processing images according to the given configuration.
|
||||||
|
type Processor struct {
|
||||||
|
config *Config
|
||||||
|
watermarkProvider auximageprovider.Provider
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a new Processor instance with the given configuration and watermark provider
|
||||||
|
func New(config *Config, watermark auximageprovider.Provider) (*Processor, error) {
|
||||||
|
if err := config.Validate(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Processor{
|
||||||
|
config: config,
|
||||||
|
watermarkProvider: watermark,
|
||||||
|
}, nil
|
||||||
|
}
|
@@ -1,23 +1,23 @@
|
|||||||
package processing
|
package processing
|
||||||
|
|
||||||
func rotateAndFlip(ctx *Context) error {
|
func (p *Processor) rotateAndFlip(c *Context) error {
|
||||||
if ctx.Angle%360 == 0 && ctx.PO.Rotate%360 == 0 && !ctx.Flip {
|
if c.Angle%360 == 0 && c.PO.Rotate%360 == 0 && !c.Flip {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := ctx.Img.CopyMemory(); err != nil {
|
if err := c.Img.CopyMemory(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := ctx.Img.Rotate(ctx.Angle); err != nil {
|
if err := c.Img.Rotate(c.Angle); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.Flip {
|
if c.Flip {
|
||||||
if err := ctx.Img.Flip(); err != nil {
|
if err := c.Img.Flip(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ctx.Img.Rotate(ctx.PO.Rotate)
|
return c.Img.Rotate(c.PO.Rotate)
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
package processing
|
package processing
|
||||||
|
|
||||||
func scale(c *Context) error {
|
func (p *Processor) scale(c *Context) error {
|
||||||
if c.WScale == 1 && c.HScale == 1 {
|
if c.WScale == 1 && c.HScale == 1 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@@ -12,7 +12,7 @@ import (
|
|||||||
"github.com/imgproxy/imgproxy/v3/vips"
|
"github.com/imgproxy/imgproxy/v3/vips"
|
||||||
)
|
)
|
||||||
|
|
||||||
func canScaleOnLoad(c *Context, imgdata imagedata.ImageData, scale float64) bool {
|
func (p *Processor) canScaleOnLoad(c *Context, imgdata imagedata.ImageData, scale float64) bool {
|
||||||
if imgdata == nil || scale == 1 {
|
if imgdata == nil || scale == 1 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -21,7 +21,7 @@ func canScaleOnLoad(c *Context, imgdata imagedata.ImageData, scale float64) bool
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.Config.DisableShrinkOnLoad || scale >= 1 {
|
if p.config.DisableShrinkOnLoad || scale >= 1 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,7 +44,7 @@ func calcJpegShink(shrink float64) int {
|
|||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
func scaleOnLoad(c *Context) error {
|
func (p *Processor) scaleOnLoad(c *Context) error {
|
||||||
wshrink := float64(c.SrcWidth) / float64(imath.Scale(c.SrcWidth, c.WScale))
|
wshrink := float64(c.SrcWidth) / float64(imath.Scale(c.SrcWidth, c.WScale))
|
||||||
hshrink := float64(c.SrcHeight) / float64(imath.Scale(c.SrcHeight, c.HScale))
|
hshrink := float64(c.SrcHeight) / float64(imath.Scale(c.SrcHeight, c.HScale))
|
||||||
preshrink := math.Min(wshrink, hshrink)
|
preshrink := math.Min(wshrink, hshrink)
|
||||||
@@ -55,7 +55,7 @@ func scaleOnLoad(c *Context) error {
|
|||||||
prescale *= c.VectorBaseScale
|
prescale *= c.VectorBaseScale
|
||||||
}
|
}
|
||||||
|
|
||||||
if !canScaleOnLoad(c, c.ImgData, prescale) {
|
if !p.canScaleOnLoad(c, c.ImgData, prescale) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,7 +71,7 @@ func scaleOnLoad(c *Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
angle, flip := 0, false
|
angle, flip := 0, false
|
||||||
newWidth, newHeight, angle, flip = extractMeta(thumbnail, c.PO.Rotate, c.PO.AutoRotate)
|
newWidth, newHeight, angle, flip = c.ExtractGeometry(thumbnail, c.PO.Rotate, c.PO.AutoRotate)
|
||||||
|
|
||||||
if newWidth >= c.SrcWidth || float64(newWidth)/float64(c.SrcWidth) < prescale {
|
if newWidth >= c.SrcWidth || float64(newWidth)/float64(c.SrcWidth) < prescale {
|
||||||
return nil
|
return nil
|
||||||
@@ -91,7 +91,7 @@ func scaleOnLoad(c *Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
newWidth, newHeight, _, _ = extractMeta(c.Img, c.PO.Rotate, c.PO.AutoRotate)
|
newWidth, newHeight, _, _ = c.ExtractGeometry(c.Img, c.PO.Rotate, c.PO.AutoRotate)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update scales after scale-on-load
|
// Update scales after scale-on-load
|
||||||
|
@@ -103,29 +103,29 @@ func stripXMP(img *vips.Image) []byte {
|
|||||||
return xmpData
|
return xmpData
|
||||||
}
|
}
|
||||||
|
|
||||||
func stripMetadata(ctx *Context) error {
|
func (p *Processor) stripMetadata(c *Context) error {
|
||||||
if !ctx.PO.StripMetadata {
|
if !c.PO.StripMetadata {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var ps3Data, xmpData []byte
|
var ps3Data, xmpData []byte
|
||||||
|
|
||||||
if ctx.PO.KeepCopyright {
|
if c.PO.KeepCopyright {
|
||||||
ps3Data = stripPS3(ctx.Img)
|
ps3Data = stripPS3(c.Img)
|
||||||
xmpData = stripXMP(ctx.Img)
|
xmpData = stripXMP(c.Img)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := ctx.Img.Strip(ctx.PO.KeepCopyright); err != nil {
|
if err := c.Img.Strip(c.PO.KeepCopyright); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.PO.KeepCopyright {
|
if c.PO.KeepCopyright {
|
||||||
if len(ps3Data) > 0 {
|
if len(ps3Data) > 0 {
|
||||||
ctx.Img.SetBlob("iptc-data", ps3Data)
|
c.Img.SetBlob("iptc-data", ps3Data)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(xmpData) > 0 {
|
if len(xmpData) > 0 {
|
||||||
ctx.Img.SetBlob("xmp-data", xmpData)
|
c.Img.SetBlob("xmp-data", xmpData)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,12 +1,12 @@
|
|||||||
package processing
|
package processing
|
||||||
|
|
||||||
func trim(c *Context) error {
|
func (p *Processor) trim(c *Context) error {
|
||||||
if !c.PO.Trim.Enabled {
|
if !c.PO.Trim.Enabled {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// We need to import color profile before trim
|
// We need to import color profile before trim
|
||||||
if err := colorspaceToProcessing(c); err != nil {
|
if err := p.colorspaceToProcessing(c); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -18,6 +18,7 @@ func trim(c *Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
c.ImgData = nil
|
c.ImgData = nil
|
||||||
|
c.CalcParams()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@@ -6,7 +6,7 @@ import (
|
|||||||
|
|
||||||
// vectorGuardScale checks if the image is a vector format and downscales it
|
// vectorGuardScale checks if the image is a vector format and downscales it
|
||||||
// to the maximum allowed resolution if necessary
|
// to the maximum allowed resolution if necessary
|
||||||
func vectorGuardScale(c *Context) error {
|
func (p *Processor) vectorGuardScale(c *Context) error {
|
||||||
if c.ImgData == nil || !c.ImgData.Format().IsVector() {
|
if c.ImgData == nil || !c.ImgData.Format().IsVector() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -19,6 +19,7 @@ func vectorGuardScale(c *Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
c.CalcParams()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@@ -4,7 +4,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
"github.com/imgproxy/imgproxy/v3/auximageprovider"
|
|
||||||
"github.com/imgproxy/imgproxy/v3/config"
|
"github.com/imgproxy/imgproxy/v3/config"
|
||||||
"github.com/imgproxy/imgproxy/v3/imagedata"
|
"github.com/imgproxy/imgproxy/v3/imagedata"
|
||||||
"github.com/imgproxy/imgproxy/v3/imath"
|
"github.com/imgproxy/imgproxy/v3/imath"
|
||||||
@@ -12,25 +11,20 @@ import (
|
|||||||
"github.com/imgproxy/imgproxy/v3/vips"
|
"github.com/imgproxy/imgproxy/v3/vips"
|
||||||
)
|
)
|
||||||
|
|
||||||
var watermarkPipeline = Pipeline{
|
// watermarkPipeline constructs the watermark processing pipeline.
|
||||||
vectorGuardScale,
|
// This pipeline is applied to the watermark image.
|
||||||
prepare,
|
func (p *Processor) watermarkPipeline() Pipeline {
|
||||||
scaleOnLoad,
|
return Pipeline{
|
||||||
colorspaceToProcessing,
|
p.vectorGuardScale,
|
||||||
scale,
|
p.scaleOnLoad,
|
||||||
rotateAndFlip,
|
p.colorspaceToProcessing,
|
||||||
padding,
|
p.scale,
|
||||||
|
p.rotateAndFlip,
|
||||||
|
p.padding,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func prepareWatermark(
|
func (p *Processor) prepareWatermark(wm *vips.Image, wmData imagedata.ImageData, po *options.ProcessingOptions, imgWidth, imgHeight int, offsetScale float64, framesCount int) error {
|
||||||
ctx context.Context,
|
|
||||||
runner *Runner,
|
|
||||||
wm *vips.Image,
|
|
||||||
wmData imagedata.ImageData,
|
|
||||||
po *options.ProcessingOptions,
|
|
||||||
imgWidth, imgHeight int,
|
|
||||||
offsetScale float64,
|
|
||||||
) error {
|
|
||||||
if err := wm.Load(wmData, 1, 1.0, 1); err != nil {
|
if err := wm.Load(wmData, 1, 1.0, 1); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -70,7 +64,7 @@ func prepareWatermark(
|
|||||||
wmPo.Padding.Bottom = offY - wmPo.Padding.Top
|
wmPo.Padding.Bottom = offY - wmPo.Padding.Top
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := runner.Run(watermarkPipeline, ctx, wm, wmPo, wmData); err != nil {
|
if err := p.watermarkPipeline().Run(context.Background(), wm, wmPo, wmData); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,20 +84,18 @@ func prepareWatermark(
|
|||||||
return wm.StripAll()
|
return wm.StripAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyWatermark(
|
func (p *Processor) applyWatermark(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
runner *Runner,
|
|
||||||
img *vips.Image,
|
img *vips.Image,
|
||||||
watermark auximageprovider.Provider,
|
|
||||||
po *options.ProcessingOptions,
|
po *options.ProcessingOptions,
|
||||||
offsetScale float64,
|
offsetScale float64,
|
||||||
framesCount int,
|
framesCount int,
|
||||||
) error {
|
) error {
|
||||||
if watermark == nil {
|
if p.watermarkProvider == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
wmData, _, err := watermark.Get(ctx, po)
|
wmData, _, err := p.watermarkProvider.Get(ctx, po)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -121,9 +113,7 @@ func applyWatermark(
|
|||||||
height := img.Height()
|
height := img.Height()
|
||||||
frameHeight := height / framesCount
|
frameHeight := height / framesCount
|
||||||
|
|
||||||
if err := prepareWatermark(
|
if err := p.prepareWatermark(wm, wmData, po, width, frameHeight, offsetScale, framesCount); err != nil {
|
||||||
ctx, runner, wm, wmData, po, width, frameHeight, offsetScale,
|
|
||||||
); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,10 +187,10 @@ func applyWatermark(
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func watermark(c *Context) error {
|
func (p *Processor) watermark(c *Context) error {
|
||||||
if !c.PO.Watermark.Enabled || c.WatermarkProvider == nil {
|
if !c.PO.Watermark.Enabled || c.WatermarkProvider == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return applyWatermark(c.Ctx, c.Runner(), c.Img, c.WatermarkProvider, c.PO, c.DprScale, 1)
|
return p.applyWatermark(c.Ctx, c.Img, c.PO, c.DprScale, 1)
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user