From 546e8159b05a88a05f577b231da28c5687f91d82 Mon Sep 17 00:00:00 2001 From: Viktor Sokolov Date: Thu, 18 Sep 2025 10:29:42 +0200 Subject: [PATCH] Processor instance --- config.go | 13 +- handlers/processing/handler.go | 2 + handlers/processing/request_methods.go | 2 +- imgproxy.go | 13 ++ init.go | 6 - processing/apply_filters.go | 2 +- processing/colorspace_to_processing.go | 4 +- processing/colorspace_to_result.go | 2 +- processing/{pipeline => }/config.go | 28 +++- processing/crop.go | 4 +- processing/extend.go | 4 +- processing/fix_size.go | 2 +- processing/flatten.go | 2 +- processing/padding.go | 2 +- processing/pipeline.go | 44 +----- processing/prepare.go | 211 +++++++++++++------------ processing/processing.go | 127 +++++---------- processing/processing_test.go | 39 +++-- processing/processor.go | 23 +++ processing/rotate_and_flip.go | 14 +- processing/scale.go | 2 +- processing/scale_on_load.go | 12 +- processing/strip_metadata.go | 18 +-- processing/trim.go | 5 +- processing/vector_guard_scale.go | 3 +- processing/watermark.go | 48 +++--- 26 files changed, 314 insertions(+), 318 deletions(-) rename processing/{pipeline => }/config.go (60%) create mode 100644 processing/processor.go diff --git a/config.go b/config.go index 5d3d50e0..d09c7ecd 100644 --- a/config.go +++ b/config.go @@ -7,6 +7,7 @@ import ( processinghandler "github.com/imgproxy/imgproxy/v3/handlers/processing" streamhandler "github.com/imgproxy/imgproxy/v3/handlers/stream" "github.com/imgproxy/imgproxy/v3/options" + "github.com/imgproxy/imgproxy/v3/processing" "github.com/imgproxy/imgproxy/v3/security" "github.com/imgproxy/imgproxy/v3/server" "github.com/imgproxy/imgproxy/v3/workers" @@ -27,6 +28,7 @@ type Config struct { Handlers HandlerConfigs Server server.Config Security security.Config + Processing processing.Config Options options.Config } @@ -41,9 +43,10 @@ func NewDefaultConfig() Config { Processing: processinghandler.NewDefaultConfig(), Stream: streamhandler.NewDefaultConfig(), }, - Server: server.NewDefaultConfig(), - Security: security.NewDefaultConfig(), - Options: options.NewDefaultConfig(), + Server: server.NewDefaultConfig(), + Security: security.NewDefaultConfig(), + Processing: processing.NewDefaultConfig(), + Options: options.NewDefaultConfig(), } } @@ -89,5 +92,9 @@ func LoadConfigFromEnv(c *Config) (*Config, error) { return nil, err } + if _, err = processing.LoadConfigFromEnv(&c.Processing); err != nil { + return nil, err + } + return c, nil } diff --git a/handlers/processing/handler.go b/handlers/processing/handler.go index e7482d7c..42f677e0 100644 --- a/handlers/processing/handler.go +++ b/handlers/processing/handler.go @@ -14,6 +14,7 @@ import ( "github.com/imgproxy/imgproxy/v3/monitoring" "github.com/imgproxy/imgproxy/v3/monitoring/stats" "github.com/imgproxy/imgproxy/v3/options" + "github.com/imgproxy/imgproxy/v3/processing" "github.com/imgproxy/imgproxy/v3/security" "github.com/imgproxy/imgproxy/v3/server" "github.com/imgproxy/imgproxy/v3/workers" @@ -27,6 +28,7 @@ type HandlerContext interface { ImageDataFactory() *imagedata.Factory Security() *security.Checker OptionsFactory() *options.Factory + Processor() *processing.Processor } // Handler handles image processing requests diff --git a/handlers/processing/request_methods.go b/handlers/processing/request_methods.go index dd315396..1a941ab2 100644 --- a/handlers/processing/request_methods.go +++ b/handlers/processing/request_methods.go @@ -157,7 +157,7 @@ func (r *request) getFallbackImage( // processImage calls actual image processing func (r *request) processImage(ctx context.Context, originData imagedata.ImageData) (*processing.Result, error) { 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 diff --git a/imgproxy.go b/imgproxy.go index cb366dbb..774306f3 100644 --- a/imgproxy.go +++ b/imgproxy.go @@ -15,6 +15,7 @@ import ( "github.com/imgproxy/imgproxy/v3/memory" "github.com/imgproxy/imgproxy/v3/monitoring/prometheus" "github.com/imgproxy/imgproxy/v3/options" + "github.com/imgproxy/imgproxy/v3/processing" "github.com/imgproxy/imgproxy/v3/security" "github.com/imgproxy/imgproxy/v3/server" "github.com/imgproxy/imgproxy/v3/workers" @@ -31,6 +32,7 @@ type ImgproxyHandlers struct { Landing *landinghandler.Handler Processing *processinghandler.Handler Stream *streamhandler.Handler + Processor *processing.Processor } // Imgproxy holds all the components needed for imgproxy to function @@ -43,6 +45,7 @@ type Imgproxy struct { handlers ImgproxyHandlers security *security.Checker optionsFactory *options.Factory + processor *processing.Processor config *Config } @@ -80,6 +83,11 @@ func New(ctx context.Context, config *Config) (*Imgproxy, error) { return nil, err } + processor, err := processing.New(&config.Processing, watermarkImage) + if err != nil { + return nil, err + } + imgproxy := &Imgproxy{ workers: workers, fallbackImage: fallbackImage, @@ -89,6 +97,7 @@ func New(ctx context.Context, config *Config) (*Imgproxy, error) { config: config, security: security, optionsFactory: processingOptionsFactory, + processor: processor, } imgproxy.handlers.Health = healthhandler.New() @@ -211,3 +220,7 @@ func (i *Imgproxy) Security() *security.Checker { func (i *Imgproxy) OptionsFactory() *options.Factory { return i.optionsFactory } + +func (i *Imgproxy) Processor() *processing.Processor { + return i.processor +} diff --git a/init.go b/init.go index 26c4fce6..d0bf5144 100644 --- a/init.go +++ b/init.go @@ -10,7 +10,6 @@ import ( "github.com/imgproxy/imgproxy/v3/gliblog" "github.com/imgproxy/imgproxy/v3/logger" "github.com/imgproxy/imgproxy/v3/monitoring" - "github.com/imgproxy/imgproxy/v3/processing" "github.com/imgproxy/imgproxy/v3/vips" "go.uber.org/automaxprocs/maxprocs" ) @@ -47,11 +46,6 @@ func Init() error { errorreport.Init() - if err := processing.ValidatePreferredFormats(); err != nil { - vips.Shutdown() - return err - } - return nil } diff --git a/processing/apply_filters.go b/processing/apply_filters.go index 5fc2b97e..d6443f71 100644 --- a/processing/apply_filters.go +++ b/processing/apply_filters.go @@ -1,6 +1,6 @@ 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 { return nil } diff --git a/processing/colorspace_to_processing.go b/processing/colorspace_to_processing.go index 495915e7..719229fb 100644 --- a/processing/colorspace_to_processing.go +++ b/processing/colorspace_to_processing.go @@ -1,6 +1,6 @@ package processing -func colorspaceToProcessing(c *Context) error { +func (p *Processor) colorspaceToProcessing(c *Context) error { if c.Img.ColourProfileImported() { return nil } @@ -9,7 +9,7 @@ func colorspaceToProcessing(c *Context) error { 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() { // The image is linear. If we keep its ICC, we'll get wrong colors after diff --git a/processing/colorspace_to_result.go b/processing/colorspace_to_result.go index 37eafe18..3feb9af6 100644 --- a/processing/colorspace_to_result.go +++ b/processing/colorspace_to_result.go @@ -1,6 +1,6 @@ package processing -func colorspaceToResult(c *Context) error { +func (p *Processor) colorspaceToResult(c *Context) error { keepProfile := !c.PO.StripColorProfile && c.PO.Format.SupportsColourProfile() if c.Img.IsLinear() { diff --git a/processing/pipeline/config.go b/processing/config.go similarity index 60% rename from processing/pipeline/config.go rename to processing/config.go index 1a87f791..ea4dab2a 100644 --- a/processing/pipeline/config.go +++ b/processing/config.go @@ -1,14 +1,18 @@ -package pipeline +package processing import ( "errors" "github.com/imgproxy/imgproxy/v3/config" "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. type Config struct { + PreferredFormats []imagetype.Type WatermarkOpacity float64 DisableShrinkOnLoad bool UseLinearColorspace bool @@ -18,6 +22,11 @@ type Config struct { func NewDefaultConfig() Config { return Config{ 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.DisableShrinkOnLoad = config.DisableShrinkOnLoad c.UseLinearColorspace = config.UseLinearColorspace + c.PreferredFormats = config.PreferredFormats 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") } + 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 } diff --git a/processing/crop.go b/processing/crop.go index 3b818904..97842697 100644 --- a/processing/crop.go +++ b/processing/crop.go @@ -31,7 +31,7 @@ func cropImage(img *vips.Image, cropWidth, cropHeight int, gravity *options.Grav 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 opts := c.CropGravity @@ -46,6 +46,6 @@ func crop(c *Context) error { 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) } diff --git a/processing/extend.go b/processing/extend.go index 11bd8492..5cdd8819 100644 --- a/processing/extend.go +++ b/processing/extend.go @@ -24,7 +24,7 @@ func extendImage(img *vips.Image, width, height int, gravity *options.GravityOpt return img.Embed(width, height, offX, offY) } -func extend(c *Context) error { +func (p *Processor) extend(c *Context) error { if !c.PO.Extend.Enabled { return nil } @@ -33,7 +33,7 @@ func extend(c *Context) error { 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 { return nil } diff --git a/processing/fix_size.go b/processing/fix_size.go index 854e751e..2622c249 100644 --- a/processing/fix_size.go +++ b/processing/fix_size.go @@ -88,7 +88,7 @@ func fixIcoSize(img *vips.Image) error { return nil } -func fixSize(c *Context) error { +func (p *Processor) fixSize(c *Context) error { switch c.PO.Format { case imagetype.WEBP: return fixWebpSize(c.Img) diff --git a/processing/flatten.go b/processing/flatten.go index eca5febe..e0ed4dbe 100644 --- a/processing/flatten.go +++ b/processing/flatten.go @@ -1,6 +1,6 @@ package processing -func flatten(c *Context) error { +func (p *Processor) flatten(c *Context) error { if !c.PO.Flatten && c.PO.Format.SupportsAlpha() { return nil } diff --git a/processing/padding.go b/processing/padding.go index 4b0b0cf3..fbf844c3 100644 --- a/processing/padding.go +++ b/processing/padding.go @@ -4,7 +4,7 @@ import ( "github.com/imgproxy/imgproxy/v3/imath" ) -func padding(c *Context) error { +func (p *Processor) padding(c *Context) error { if !c.PO.Padding.Enabled { return nil } diff --git a/processing/pipeline.go b/processing/pipeline.go index 988e4128..140cba12 100644 --- a/processing/pipeline.go +++ b/processing/pipeline.go @@ -6,21 +6,14 @@ import ( "github.com/imgproxy/imgproxy/v3/auximageprovider" "github.com/imgproxy/imgproxy/v3/imagedata" "github.com/imgproxy/imgproxy/v3/options" - "github.com/imgproxy/imgproxy/v3/processing/pipeline" "github.com/imgproxy/imgproxy/v3/server" "github.com/imgproxy/imgproxy/v3/vips" ) -// NOTE: this will be called pipeline.Context in the separate package type Context struct { - // The runner that runs this pipeline - runner *Runner - + // The context to check for timeouts and cancellations Ctx context.Context - // Global processing configuration which could be used by individual steps - Config *pipeline.Config - // VIPS image Img *vips.Image @@ -80,33 +73,18 @@ type Context struct { ExtendAspectRatioHeight int } -// NOTE: same, pipeline.Step, pipeline.Pipeline, pipeline.Runner -type Step func(ctx *Context) error +type Step func(c *Context) error 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 -func (f *Runner) Run( - p Pipeline, +func (p Pipeline) Run( ctx context.Context, img *vips.Image, po *options.ProcessingOptions, imgdata imagedata.ImageData, ) 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 { if err := step(&pctx); err != nil { @@ -123,17 +101,14 @@ func (f *Runner) Run( return nil } -func (r *Runner) newContext( +func (p Pipeline) newContext( ctx context.Context, img *vips.Image, po *options.ProcessingOptions, imgdata imagedata.ImageData, ) Context { pctx := Context{ - runner: r, - Ctx: ctx, - Config: r.config, Img: img, PO: po, ImgData: imgdata, @@ -144,8 +119,7 @@ func (r *Runner) newContext( DprScale: 1.0, VectorBaseScale: 1.0, - CropGravity: po.Crop.Gravity, - WatermarkProvider: r.watermark, + CropGravity: po.Crop.Gravity, } if pctx.CropGravity.Type == options.GravityUnknown { @@ -154,7 +128,3 @@ func (r *Runner) newContext( return pctx } - -func (c *Context) Runner() *Runner { - return c.runner -} diff --git a/processing/prepare.go b/processing/prepare.go index aeee0a64..e2122103 100644 --- a/processing/prepare.go +++ b/processing/prepare.go @@ -8,29 +8,12 @@ import ( "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() height := img.Height() - angle := 0 - 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 - } - } + angle, flip := c.angleFlip(img, autoRotate) if (angle+baseAngle)%180 != 0 { width, height = height, width @@ -39,7 +22,39 @@ func extractMeta(img *vips.Image, baseAngle int, useOrientation bool) (int, int, 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 { case crop == 0.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 srcW, srcH := float64(width), float64(height) dstW, dstH := float64(po.Width), float64(po.Height) - if po.Width == 0 { - dstW = srcW - } - - if dstW == srcW { - wshrink = 1 - } else { - wshrink = srcW / dstW - } - - if po.Height == 0 { - dstH = srcH - } - - if dstH == srcH { - hshrink = 1 - } else { - hshrink = srcH / dstH - } + dstW, wshrink = calcShrink(po.Width, srcW, dstW) + dstH, hshrink = calcShrink(po.Height, srcH, dstH) if wshrink != 1 || hshrink != 1 { rt := po.ResizingType @@ -107,22 +119,22 @@ func (pctx *Context) calcScale(width, height int, po *options.ProcessingOptions) wshrink /= po.ZoomWidth 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) if minShrink < 1 { wshrink /= minShrink hshrink /= minShrink if !po.Extend.Enabled { - pctx.DprScale /= minShrink + c.DprScale /= minShrink } } // The minimum of wshrink and hshrink is the maximum dprScale value // 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 { @@ -139,8 +151,8 @@ func (pctx *Context) calcScale(width, height int, po *options.ProcessingOptions) } } - wshrink /= pctx.DprScale - hshrink /= pctx.DprScale + wshrink /= c.DprScale + hshrink /= c.DprScale if wshrink > srcW { wshrink = srcW @@ -150,110 +162,113 @@ func (pctx *Context) calcScale(width, height int, po *options.ProcessingOptions) hshrink = srcH } - pctx.WScale = 1.0 / wshrink - pctx.HScale = 1.0 / hshrink + c.WScale = 1.0 / wshrink + c.HScale = 1.0 / hshrink } -func (pctx *Context) calcSizes(widthToScale, heightToScale int, po *options.ProcessingOptions) { - pctx.TargetWidth = imath.Scale(po.Width, pctx.DprScale*po.ZoomWidth) - pctx.TargetHeight = imath.Scale(po.Height, pctx.DprScale*po.ZoomHeight) +func (c *Context) calcSizes(widthToScale, heightToScale int, po *options.ProcessingOptions) { + c.TargetWidth = imath.Scale(po.Width, c.DprScale*po.ZoomWidth) + c.TargetHeight = imath.Scale(po.Height, c.DprScale*po.ZoomHeight) - pctx.ScaledWidth = imath.Scale(widthToScale, pctx.WScale) - pctx.ScaledHeight = imath.Scale(heightToScale, pctx.HScale) + c.ScaledWidth = imath.Scale(widthToScale, c.WScale) + c.ScaledHeight = imath.Scale(heightToScale, c.HScale) if po.ResizingType == options.ResizeFillDown && !po.Enlarge { - diffW := float64(pctx.TargetWidth) / float64(pctx.ScaledWidth) - diffH := float64(pctx.TargetHeight) / float64(pctx.ScaledHeight) + diffW := float64(c.TargetWidth) / float64(c.ScaledWidth) + diffH := float64(c.TargetHeight) / float64(c.ScaledHeight) switch { case diffW > diffH && diffW > 1.0: - pctx.ResultCropHeight = imath.Scale(pctx.ScaledWidth, float64(pctx.TargetHeight)/float64(pctx.TargetWidth)) - pctx.ResultCropWidth = pctx.ScaledWidth + c.ResultCropHeight = imath.Scale(c.ScaledWidth, float64(c.TargetHeight)/float64(c.TargetWidth)) + c.ResultCropWidth = c.ScaledWidth case diffH > diffW && diffH > 1.0: - pctx.ResultCropWidth = imath.Scale(pctx.ScaledHeight, float64(pctx.TargetWidth)/float64(pctx.TargetHeight)) - pctx.ResultCropHeight = pctx.ScaledHeight + c.ResultCropWidth = imath.Scale(c.ScaledHeight, float64(c.TargetWidth)/float64(c.TargetHeight)) + c.ResultCropHeight = c.ScaledHeight default: - pctx.ResultCropWidth = pctx.TargetWidth - pctx.ResultCropHeight = pctx.TargetHeight + c.ResultCropWidth = c.TargetWidth + c.ResultCropHeight = c.TargetHeight } } else { - pctx.ResultCropWidth = pctx.TargetWidth - pctx.ResultCropHeight = pctx.TargetHeight + c.ResultCropWidth = c.TargetWidth + c.ResultCropHeight = c.TargetHeight } - if po.ExtendAspectRatio.Enabled && pctx.TargetWidth > 0 && pctx.TargetHeight > 0 { - outWidth := imath.MinNonZero(pctx.ScaledWidth, pctx.ResultCropWidth) - outHeight := imath.MinNonZero(pctx.ScaledHeight, pctx.ResultCropHeight) + if po.ExtendAspectRatio.Enabled && c.TargetWidth > 0 && c.TargetHeight > 0 { + outWidth := imath.MinNonZero(c.ScaledWidth, c.ResultCropWidth) + outHeight := imath.MinNonZero(c.ScaledHeight, c.ResultCropHeight) - diffW := float64(pctx.TargetWidth) / float64(outWidth) - diffH := float64(pctx.TargetHeight) / float64(outHeight) + diffW := float64(c.TargetWidth) / float64(outWidth) + diffH := float64(c.TargetHeight) / float64(outHeight) switch { case diffH > diffW: - pctx.ExtendAspectRatioHeight = imath.Scale(outWidth, float64(pctx.TargetHeight)/float64(pctx.TargetWidth)) - pctx.ExtendAspectRatioWidth = outWidth + c.ExtendAspectRatioHeight = imath.Scale(outWidth, float64(c.TargetHeight)/float64(c.TargetWidth)) + c.ExtendAspectRatioWidth = outWidth case diffW > diffH: - pctx.ExtendAspectRatioWidth = imath.Scale(outHeight, float64(pctx.TargetWidth)/float64(pctx.TargetHeight)) - pctx.ExtendAspectRatioHeight = outHeight + c.ExtendAspectRatioWidth = imath.Scale(outHeight, float64(c.TargetWidth)/float64(c.TargetHeight)) + 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 if maxresultDim <= 0 { return } - outWidth := imath.MinNonZero(pctx.ScaledWidth, pctx.ResultCropWidth) - outHeight := imath.MinNonZero(pctx.ScaledHeight, pctx.ResultCropHeight) + outWidth := imath.MinNonZero(c.ScaledWidth, c.ResultCropWidth) + outHeight := imath.MinNonZero(c.ScaledHeight, c.ResultCropHeight) if po.Extend.Enabled { - outWidth = max(outWidth, pctx.TargetWidth) - outHeight = max(outHeight, pctx.TargetHeight) + outWidth = max(outWidth, c.TargetWidth) + outHeight = max(outHeight, c.TargetHeight) } else if po.ExtendAspectRatio.Enabled { - outWidth = max(outWidth, pctx.ExtendAspectRatioWidth) - outHeight = max(outHeight, pctx.ExtendAspectRatioHeight) + outWidth = max(outWidth, c.ExtendAspectRatioWidth) + outHeight = max(outHeight, c.ExtendAspectRatioHeight) } if po.Padding.Enabled { - outWidth += imath.ScaleToEven(po.Padding.Left, pctx.DprScale) + imath.ScaleToEven(po.Padding.Right, pctx.DprScale) - outHeight += imath.ScaleToEven(po.Padding.Top, pctx.DprScale) + imath.ScaleToEven(po.Padding.Bottom, pctx.DprScale) + outWidth += imath.ScaleToEven(po.Padding.Left, c.DprScale) + imath.ScaleToEven(po.Padding.Right, c.DprScale) + outHeight += imath.ScaleToEven(po.Padding.Top, c.DprScale) + imath.ScaleToEven(po.Padding.Bottom, c.DprScale) } if maxresultDim > 0 && (outWidth > maxresultDim || outHeight > maxresultDim) { downScale := float64(maxresultDim) / float64(max(outWidth, outHeight)) - pctx.WScale *= downScale - pctx.HScale *= downScale + c.WScale *= downScale + c.HScale *= downScale // Prevent scaling below 1px - if minWScale := 1.0 / float64(widthToScale); pctx.WScale < minWScale { - pctx.WScale = minWScale + if minWScale := 1.0 / float64(widthToScale); c.WScale < minWScale { + c.WScale = minWScale } - if minHScale := 1.0 / float64(heightToScale); pctx.HScale < minHScale { - pctx.HScale = minHScale + if minHScale := 1.0 / float64(heightToScale); c.HScale < minHScale { + c.HScale = minHScale } - pctx.DprScale *= downScale + c.DprScale *= downScale // 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. -// This can't be done in advance because some steps like trimming and rasterization could -// happen before this step. -func prepare(c *Context) error { - c.SrcWidth, c.SrcHeight, c.Angle, c.Flip = extractMeta(c.Img, c.PO.Rotate, c.PO.AutoRotate) +// Prepare calculates context image parameters based on the current image size. +// Some steps (like trim) must call this function when finished. +func (c *Context) CalcParams() { + if c.ImgData == nil { + return + } - c.CropWidth = calcCropSize(c.SrcWidth, c.PO.Crop.Width) - c.CropHeight = calcCropSize(c.SrcHeight, c.PO.Crop.Height) + c.SrcWidth, c.SrcHeight, c.Angle, c.Flip = c.ExtractGeometry(c.Img, c.PO.Rotate, c.PO.AutoRotate) + + 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) heightToScale := imath.MinNonZero(c.CropHeight, c.SrcHeight) @@ -261,6 +276,4 @@ func prepare(c *Context) error { c.calcScale(widthToScale, heightToScale, c.PO) c.calcSizes(widthToScale, heightToScale, c.PO) c.limitScale(widthToScale, heightToScale, c.PO) - - return nil } diff --git a/processing/processing.go b/processing/processing.go index 4b3db1f0..2662594c 100644 --- a/processing/processing.go +++ b/processing/processing.go @@ -2,7 +2,6 @@ package processing import ( "context" - "errors" "runtime" "slices" @@ -13,59 +12,41 @@ import ( "github.com/imgproxy/imgproxy/v3/imagedata" "github.com/imgproxy/imgproxy/v3/imagetype" "github.com/imgproxy/imgproxy/v3/options" - "github.com/imgproxy/imgproxy/v3/processing/pipeline" "github.com/imgproxy/imgproxy/v3/security" "github.com/imgproxy/imgproxy/v3/server" "github.com/imgproxy/imgproxy/v3/svg" "github.com/imgproxy/imgproxy/v3/vips" ) -// The main processing pipeline (without finalization). -// Applied to non-animated images and individual frames of animated images. -var mainPipeline = Pipeline{ - vectorGuardScale, - trim, - prepare, - scaleOnLoad, - colorspaceToProcessing, - crop, - scale, - rotateAndFlip, - cropToResult, - applyFilters, - extend, - extendAspectRatio, - padding, - fixSize, - flatten, - watermark, +// mainPipeline constructs the main image processing pipeline. +// This pipeline is applied to each image frame. +func (p *Processor) mainPipeline() Pipeline { + return Pipeline{ + p.vectorGuardScale, + p.trim, + p.scaleOnLoad, + p.colorspaceToProcessing, + p.crop, + p.scale, + p.rotateAndFlip, + p.cropToResult, + p.applyFilters, + p.extend, + p.extendAspectRatio, + p.padding, + p.fixSize, + p.flatten, + p.watermark, + } } -// The finalization pipeline. -// Applied right before saving the image. -var finalizePipeline = Pipeline{ - colorspaceToResult, - 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) - } +// finalizePipeline constructs the finalization pipeline. +// This pipeline is applied before saving the image. +func (p *Processor) finalizePipeline() Pipeline { + return Pipeline{ + p.colorspaceToResult, + p.stripMetadata, } - - if len(filtered) == 0 { - return errors.New("no supported preferred formats specified") - } - - config.PreferredFormats = filtered - - return nil } // 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. // // The provided processing options may be modified during processing. -func ProcessImage( +func (p *Processor) ProcessImage( ctx context.Context, imgdata imagedata.ImageData, po *options.ProcessingOptions, @@ -139,19 +120,12 @@ func ProcessImage( } // 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 } - // 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) - 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 } @@ -389,41 +363,25 @@ func findPreferredFormat(animated, expectTransparency bool) imagetype.Type { return config.PreferredFormats[0] } -func transformImage( +func (p *Processor) transformImage( ctx context.Context, img *vips.Image, po *options.ProcessingOptions, imgdata imagedata.ImageData, asAnimated bool, - watermark auximageprovider.Provider, ) error { if asAnimated { - return transformAnimated(ctx, img, po, watermark) + return p.transformAnimated(ctx, img, po) } - // NOTE: THIS IS TEMPORARY - runner, err := tmpNewRunner(watermark) - if err != nil { - return err - } - // NOTE: END TEMPORARY BLOCK - - return runner.Run(mainPipeline, ctx, img, po, imgdata) + return p.mainPipeline().Run(ctx, img, po, imgdata) } -func transformAnimated( +func (p *Processor) transformAnimated( ctx context.Context, img *vips.Image, po *options.ProcessingOptions, - watermark auximageprovider.Provider, ) error { - // NOTE: THIS IS TEMPORARY - runner, rerr := tmpNewRunner(watermark) - if rerr != nil { - return rerr - } - // NOTE: END TEMPORARY BLOCK - if po.Trim.Enabled { log.Warning("Trim is not supported for animated images") po.Trim.Enabled = false @@ -477,7 +435,7 @@ func transformAnimated( // Transform the frame using the main pipeline. // We don't provide imgdata here to prevent scale-on-load. // 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 } @@ -501,7 +459,7 @@ func transformAnimated( // Apply watermark to all frames at once if it was requested. // 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. // `imgproxy-dpr-scale` is set by the pipeline. dprScale, derr := img.GetDoubleDefault("imgproxy-dpr-scale", 1.0) @@ -509,7 +467,7 @@ func transformAnimated( 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 } } @@ -565,16 +523,3 @@ func saveImage( // Otherwise, just save the image with the specified quality. 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 -} diff --git a/processing/processing_test.go b/processing/processing_test.go index c36ebb30..fd1d1938 100644 --- a/processing/processing_test.go +++ b/processing/processing_test.go @@ -29,6 +29,8 @@ type ProcessingTestSuite struct { security testutil.LazyObj[*security.Checker] poConfig testutil.LazyObj[*options.Config] po testutil.LazyObj[*options.Factory] + config testutil.LazyObj[*Config] + processor testutil.LazyObj[*Processor] } func (s *ProcessingTestSuite) SetupSuite() { @@ -68,6 +70,15 @@ func (s *ProcessingTestSuite) SetupSuite() { s.po, _ = testutil.NewLazySuiteObj(s, func() (*options.Factory, error) { 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 { @@ -116,7 +127,7 @@ func (s *ProcessingTestSuite) TestResizeToFit() { po.Width = tc.width 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().NotNil(result) @@ -155,7 +166,7 @@ func (s *ProcessingTestSuite) TestResizeToFitEnlarge() { po.Width = tc.width 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().NotNil(result) @@ -199,7 +210,7 @@ func (s *ProcessingTestSuite) TestResizeToFitExtend() { po.Width = tc.width 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().NotNil(result) @@ -243,7 +254,7 @@ func (s *ProcessingTestSuite) TestResizeToFitExtendAR() { po.Width = tc.width 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().NotNil(result) @@ -287,7 +298,7 @@ func (s *ProcessingTestSuite) TestResizeToFill() { po.Width = tc.width 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().NotNil(result) @@ -326,7 +337,7 @@ func (s *ProcessingTestSuite) TestResizeToFillEnlarge() { po.Width = tc.width 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().NotNil(result) @@ -372,7 +383,7 @@ func (s *ProcessingTestSuite) TestResizeToFillExtend() { po.Width = tc.width 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().NotNil(result) @@ -418,7 +429,7 @@ func (s *ProcessingTestSuite) TestResizeToFillExtendAR() { po.Width = tc.width 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().NotNil(result) @@ -456,7 +467,7 @@ func (s *ProcessingTestSuite) TestResizeToFillDown() { po.Width = tc.width 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().NotNil(result) @@ -495,7 +506,7 @@ func (s *ProcessingTestSuite) TestResizeToFillDownEnlarge() { po.Width = tc.width 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().NotNil(result) @@ -541,7 +552,7 @@ func (s *ProcessingTestSuite) TestResizeToFillDownExtend() { po.Width = tc.width 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().NotNil(result) @@ -585,7 +596,7 @@ func (s *ProcessingTestSuite) TestResizeToFillDownExtendAR() { po.Width = tc.width 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().NotNil(result) @@ -1014,7 +1025,7 @@ func (s *ProcessingTestSuite) TestResultSizeLimit() { po.Rotate = tc.rotate 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().NotNil(result) @@ -1029,7 +1040,7 @@ func (s *ProcessingTestSuite) TestImageResolutionTooLarge() { po.SecurityOptions.MaxSrcResolution = 1 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().Equal(422, ierrors.Wrap(err, 0).StatusCode()) diff --git a/processing/processor.go b/processing/processor.go new file mode 100644 index 00000000..dbba5be3 --- /dev/null +++ b/processing/processor.go @@ -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 +} diff --git a/processing/rotate_and_flip.go b/processing/rotate_and_flip.go index 6cafbbf4..6040f4d8 100644 --- a/processing/rotate_and_flip.go +++ b/processing/rotate_and_flip.go @@ -1,23 +1,23 @@ package processing -func rotateAndFlip(ctx *Context) error { - if ctx.Angle%360 == 0 && ctx.PO.Rotate%360 == 0 && !ctx.Flip { +func (p *Processor) rotateAndFlip(c *Context) error { + if c.Angle%360 == 0 && c.PO.Rotate%360 == 0 && !c.Flip { return nil } - if err := ctx.Img.CopyMemory(); err != nil { + if err := c.Img.CopyMemory(); err != nil { return err } - if err := ctx.Img.Rotate(ctx.Angle); err != nil { + if err := c.Img.Rotate(c.Angle); err != nil { return err } - if ctx.Flip { - if err := ctx.Img.Flip(); err != nil { + if c.Flip { + if err := c.Img.Flip(); err != nil { return err } } - return ctx.Img.Rotate(ctx.PO.Rotate) + return c.Img.Rotate(c.PO.Rotate) } diff --git a/processing/scale.go b/processing/scale.go index 86100e7d..395ca026 100644 --- a/processing/scale.go +++ b/processing/scale.go @@ -1,6 +1,6 @@ package processing -func scale(c *Context) error { +func (p *Processor) scale(c *Context) error { if c.WScale == 1 && c.HScale == 1 { return nil } diff --git a/processing/scale_on_load.go b/processing/scale_on_load.go index 4cead206..f04bc354 100644 --- a/processing/scale_on_load.go +++ b/processing/scale_on_load.go @@ -12,7 +12,7 @@ import ( "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 { return false } @@ -21,7 +21,7 @@ func canScaleOnLoad(c *Context, imgdata imagedata.ImageData, scale float64) bool return true } - if c.Config.DisableShrinkOnLoad || scale >= 1 { + if p.config.DisableShrinkOnLoad || scale >= 1 { return false } @@ -44,7 +44,7 @@ func calcJpegShink(shrink float64) int { 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)) hshrink := float64(c.SrcHeight) / float64(imath.Scale(c.SrcHeight, c.HScale)) preshrink := math.Min(wshrink, hshrink) @@ -55,7 +55,7 @@ func scaleOnLoad(c *Context) error { prescale *= c.VectorBaseScale } - if !canScaleOnLoad(c, c.ImgData, prescale) { + if !p.canScaleOnLoad(c, c.ImgData, prescale) { return nil } @@ -71,7 +71,7 @@ func scaleOnLoad(c *Context) error { } 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 { return nil @@ -91,7 +91,7 @@ func scaleOnLoad(c *Context) error { 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 diff --git a/processing/strip_metadata.go b/processing/strip_metadata.go index de8bed96..f2fa8492 100644 --- a/processing/strip_metadata.go +++ b/processing/strip_metadata.go @@ -103,29 +103,29 @@ func stripXMP(img *vips.Image) []byte { return xmpData } -func stripMetadata(ctx *Context) error { - if !ctx.PO.StripMetadata { +func (p *Processor) stripMetadata(c *Context) error { + if !c.PO.StripMetadata { return nil } var ps3Data, xmpData []byte - if ctx.PO.KeepCopyright { - ps3Data = stripPS3(ctx.Img) - xmpData = stripXMP(ctx.Img) + if c.PO.KeepCopyright { + ps3Data = stripPS3(c.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 } - if ctx.PO.KeepCopyright { + if c.PO.KeepCopyright { if len(ps3Data) > 0 { - ctx.Img.SetBlob("iptc-data", ps3Data) + c.Img.SetBlob("iptc-data", ps3Data) } if len(xmpData) > 0 { - ctx.Img.SetBlob("xmp-data", xmpData) + c.Img.SetBlob("xmp-data", xmpData) } } diff --git a/processing/trim.go b/processing/trim.go index 6af6cf89..59cfb9bb 100644 --- a/processing/trim.go +++ b/processing/trim.go @@ -1,12 +1,12 @@ package processing -func trim(c *Context) error { +func (p *Processor) trim(c *Context) error { if !c.PO.Trim.Enabled { return nil } // We need to import color profile before trim - if err := colorspaceToProcessing(c); err != nil { + if err := p.colorspaceToProcessing(c); err != nil { return err } @@ -18,6 +18,7 @@ func trim(c *Context) error { } c.ImgData = nil + c.CalcParams() return nil } diff --git a/processing/vector_guard_scale.go b/processing/vector_guard_scale.go index a6c10fed..d912ee61 100644 --- a/processing/vector_guard_scale.go +++ b/processing/vector_guard_scale.go @@ -6,7 +6,7 @@ import ( // vectorGuardScale checks if the image is a vector format and downscales it // 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() { return nil } @@ -19,6 +19,7 @@ func vectorGuardScale(c *Context) error { return err } } + c.CalcParams() return nil } diff --git a/processing/watermark.go b/processing/watermark.go index 3dce2dab..c9c7135f 100644 --- a/processing/watermark.go +++ b/processing/watermark.go @@ -4,7 +4,6 @@ import ( "context" "math" - "github.com/imgproxy/imgproxy/v3/auximageprovider" "github.com/imgproxy/imgproxy/v3/config" "github.com/imgproxy/imgproxy/v3/imagedata" "github.com/imgproxy/imgproxy/v3/imath" @@ -12,25 +11,20 @@ import ( "github.com/imgproxy/imgproxy/v3/vips" ) -var watermarkPipeline = Pipeline{ - vectorGuardScale, - prepare, - scaleOnLoad, - colorspaceToProcessing, - scale, - rotateAndFlip, - padding, +// watermarkPipeline constructs the watermark processing pipeline. +// This pipeline is applied to the watermark image. +func (p *Processor) watermarkPipeline() Pipeline { + return Pipeline{ + p.vectorGuardScale, + p.scaleOnLoad, + p.colorspaceToProcessing, + p.scale, + p.rotateAndFlip, + p.padding, + } } -func prepareWatermark( - ctx context.Context, - runner *Runner, - wm *vips.Image, - wmData imagedata.ImageData, - po *options.ProcessingOptions, - imgWidth, imgHeight int, - offsetScale float64, -) error { +func (p *Processor) prepareWatermark(wm *vips.Image, wmData imagedata.ImageData, po *options.ProcessingOptions, imgWidth, imgHeight int, offsetScale float64, framesCount int) error { if err := wm.Load(wmData, 1, 1.0, 1); err != nil { return err } @@ -70,7 +64,7 @@ func prepareWatermark( 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 } @@ -90,20 +84,18 @@ func prepareWatermark( return wm.StripAll() } -func applyWatermark( +func (p *Processor) applyWatermark( ctx context.Context, - runner *Runner, img *vips.Image, - watermark auximageprovider.Provider, po *options.ProcessingOptions, offsetScale float64, framesCount int, ) error { - if watermark == nil { + if p.watermarkProvider == nil { return nil } - wmData, _, err := watermark.Get(ctx, po) + wmData, _, err := p.watermarkProvider.Get(ctx, po) if err != nil { return err } @@ -121,9 +113,7 @@ func applyWatermark( height := img.Height() frameHeight := height / framesCount - if err := prepareWatermark( - ctx, runner, wm, wmData, po, width, frameHeight, offsetScale, - ); err != nil { + if err := p.prepareWatermark(wm, wmData, po, width, frameHeight, offsetScale, framesCount); err != nil { return err } @@ -197,10 +187,10 @@ func applyWatermark( return nil } -func watermark(c *Context) error { +func (p *Processor) watermark(c *Context) error { if !c.PO.Watermark.Enabled || c.WatermarkProvider == 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) }