Ver Fonte

pipeline context changes, Runner

Viktor Sokolov há 3 semanas atrás
pai
commit
833f2bb082

+ 6 - 12
processing/apply_filters.go

@@ -1,27 +1,21 @@
 package processing
 
-import (
-	"github.com/imgproxy/imgproxy/v3/imagedata"
-	"github.com/imgproxy/imgproxy/v3/options"
-	"github.com/imgproxy/imgproxy/v3/vips"
-)
-
-func applyFilters(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptions, imgdata imagedata.ImageData) error {
-	if po.Blur == 0 && po.Sharpen == 0 && po.Pixelate <= 1 {
+func applyFilters(с *Context) error {
+	if с.PO.Blur == 0 && с.PO.Sharpen == 0 && с.PO.Pixelate <= 1 {
 		return nil
 	}
 
-	if err := img.CopyMemory(); err != nil {
+	if err := с.Img.CopyMemory(); err != nil {
 		return err
 	}
 
-	if err := img.RgbColourspace(); err != nil {
+	if err := с.Img.RgbColourspace(); err != nil {
 		return err
 	}
 
-	if err := img.ApplyFilters(po.Blur, po.Sharpen, po.Pixelate); err != nil {
+	if err := с.Img.ApplyFilters(с.PO.Blur, с.PO.Sharpen, с.PO.Pixelate); err != nil {
 		return err
 	}
 
-	return img.CopyMemory()
+	return с.Img.CopyMemory()
 }

+ 11 - 18
processing/colorspace_to_processing.go

@@ -1,42 +1,35 @@
 package processing
 
-import (
-	"github.com/imgproxy/imgproxy/v3/config"
-	"github.com/imgproxy/imgproxy/v3/imagedata"
-	"github.com/imgproxy/imgproxy/v3/options"
-	"github.com/imgproxy/imgproxy/v3/vips"
-)
-
-func colorspaceToProcessing(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptions, imgdata imagedata.ImageData) error {
-	if img.ColourProfileImported() {
+func colorspaceToProcessing(c *Context) error {
+	if c.Img.ColourProfileImported() {
 		return nil
 	}
 
-	if err := img.Rad2Float(); err != nil {
+	if err := c.Img.Rad2Float(); err != nil {
 		return err
 	}
 
-	convertToLinear := config.UseLinearColorspace && (pctx.wscale != 1 || pctx.hscale != 1)
+	convertToLinear := c.Config.UseLinearColorspace && (c.WScale != 1 || c.HScale != 1)
 
-	if img.IsLinear() {
+	if c.Img.IsLinear() {
 		// The image is linear. If we keep its ICC, we'll get wrong colors after
 		// converting it to sRGB
-		img.RemoveColourProfile()
+		c.Img.RemoveColourProfile()
 	} else {
 		// vips 8.15+ tends to lose the colour profile during some color conversions.
 		// We need to backup the colour profile before the conversion and restore it later.
-		img.BackupColourProfile()
+		c.Img.BackupColourProfile()
 
-		if convertToLinear || !img.IsRGB() {
-			if err := img.ImportColourProfile(); err != nil {
+		if convertToLinear || !c.Img.IsRGB() {
+			if err := c.Img.ImportColourProfile(); err != nil {
 				return err
 			}
 		}
 	}
 
 	if convertToLinear {
-		return img.LinearColourspace()
+		return c.Img.LinearColourspace()
 	}
 
-	return img.RgbColourspace()
+	return c.Img.RgbColourspace()
 }

+ 10 - 16
processing/colorspace_to_result.go

@@ -1,48 +1,42 @@
 package processing
 
-import (
-	"github.com/imgproxy/imgproxy/v3/imagedata"
-	"github.com/imgproxy/imgproxy/v3/options"
-	"github.com/imgproxy/imgproxy/v3/vips"
-)
+func colorspaceToResult(c *Context) error {
+	keepProfile := !c.PO.StripColorProfile && c.PO.Format.SupportsColourProfile()
 
-func colorspaceToResult(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptions, imgdata imagedata.ImageData) error {
-	keepProfile := !po.StripColorProfile && po.Format.SupportsColourProfile()
-
-	if img.IsLinear() {
-		if err := img.RgbColourspace(); err != nil {
+	if c.Img.IsLinear() {
+		if err := c.Img.RgbColourspace(); err != nil {
 			return err
 		}
 	}
 
 	// vips 8.15+ tends to lose the colour profile during some color conversions.
 	// We probably have a backup of the colour profile, so we need to restore it.
-	img.RestoreColourProfile()
+	c.Img.RestoreColourProfile()
 
-	if img.ColourProfileImported() {
+	if c.Img.ColourProfileImported() {
 		if keepProfile {
 			// We imported ICC profile and want to keep it,
 			// so we need to export it
-			if err := img.ExportColourProfile(); err != nil {
+			if err := c.Img.ExportColourProfile(); err != nil {
 				return err
 			}
 		} else {
 			// We imported ICC profile but don't want to keep it,
 			// so we need to export image to sRGB for maximum compatibility
-			if err := img.ExportColourProfileToSRGB(); err != nil {
+			if err := c.Img.ExportColourProfileToSRGB(); err != nil {
 				return err
 			}
 		}
 	} else if !keepProfile {
 		// We don't import ICC profile and don't want to keep it,
 		// so we need to transform it to sRGB for maximum compatibility
-		if err := img.TransformColourProfileToSRGB(); err != nil {
+		if err := c.Img.TransformColourProfileToSRGB(); err != nil {
 			return err
 		}
 	}
 
 	if !keepProfile {
-		return img.RemoveColourProfile()
+		return c.Img.RemoveColourProfile()
 	}
 
 	return nil

+ 9 - 10
processing/crop.go

@@ -1,7 +1,6 @@
 package processing
 
 import (
-	"github.com/imgproxy/imgproxy/v3/imagedata"
 	"github.com/imgproxy/imgproxy/v3/imath"
 	"github.com/imgproxy/imgproxy/v3/options"
 	"github.com/imgproxy/imgproxy/v3/vips"
@@ -32,21 +31,21 @@ func cropImage(img *vips.Image, cropWidth, cropHeight int, gravity *options.Grav
 	return img.Crop(left, top, cropWidth, cropHeight)
 }
 
-func crop(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptions, imgdata imagedata.ImageData) error {
-	width, height := pctx.cropWidth, pctx.cropHeight
+func crop(c *Context) error {
+	width, height := c.CropWidth, c.CropHeight
 
-	opts := pctx.cropGravity
-	opts.RotateAndFlip(pctx.angle, pctx.flip)
-	opts.RotateAndFlip(po.Rotate, false)
+	opts := c.CropGravity
+	opts.RotateAndFlip(c.Angle, c.Flip)
+	opts.RotateAndFlip(c.PO.Rotate, false)
 
-	if (pctx.angle+po.Rotate)%180 == 90 {
+	if (c.Angle+c.PO.Rotate)%180 == 90 {
 		width, height = height, width
 	}
 
 	// Since we crop before scaling, we shouldn't consider DPR
-	return cropImage(img, width, height, &opts, 1.0)
+	return cropImage(c.Img, width, height, &opts, 1.0)
 }
 
-func cropToResult(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptions, imgdata imagedata.ImageData) error {
-	return cropImage(img, pctx.resultCropWidth, pctx.resultCropHeight, &po.Gravity, pctx.dprScale)
+func cropToResult(c *Context) error {
+	return cropImage(c.Img, c.ResultCropWidth, c.ResultCropHeight, &c.PO.Gravity, c.DprScale)
 }

+ 8 - 9
processing/extend.go

@@ -1,7 +1,6 @@
 package processing
 
 import (
-	"github.com/imgproxy/imgproxy/v3/imagedata"
 	"github.com/imgproxy/imgproxy/v3/options"
 	"github.com/imgproxy/imgproxy/v3/vips"
 )
@@ -25,24 +24,24 @@ func extendImage(img *vips.Image, width, height int, gravity *options.GravityOpt
 	return img.Embed(width, height, offX, offY)
 }
 
-func extend(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptions, imgdata imagedata.ImageData) error {
-	if !po.Extend.Enabled {
+func extend(c *Context) error {
+	if !c.PO.Extend.Enabled {
 		return nil
 	}
 
-	width, height := pctx.targetWidth, pctx.targetHeight
-	return extendImage(img, width, height, &po.Extend.Gravity, pctx.dprScale)
+	width, height := c.TargetWidth, c.TargetHeight
+	return extendImage(c.Img, width, height, &c.PO.Extend.Gravity, c.DprScale)
 }
 
-func extendAspectRatio(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptions, imgdata imagedata.ImageData) error {
-	if !po.ExtendAspectRatio.Enabled {
+func extendAspectRatio(c *Context) error {
+	if !c.PO.ExtendAspectRatio.Enabled {
 		return nil
 	}
 
-	width, height := pctx.extendAspectRatioWidth, pctx.extendAspectRatioHeight
+	width, height := c.ExtendAspectRatioWidth, c.ExtendAspectRatioHeight
 	if width == 0 || height == 0 {
 		return nil
 	}
 
-	return extendImage(img, width, height, &po.ExtendAspectRatio.Gravity, pctx.dprScale)
+	return extendImage(c.Img, width, height, &c.PO.ExtendAspectRatio.Gravity, c.DprScale)
 }

+ 6 - 8
processing/fix_size.go

@@ -3,9 +3,7 @@ package processing
 import (
 	"math"
 
-	"github.com/imgproxy/imgproxy/v3/imagedata"
 	"github.com/imgproxy/imgproxy/v3/imagetype"
-	"github.com/imgproxy/imgproxy/v3/options"
 	"github.com/imgproxy/imgproxy/v3/vips"
 	log "github.com/sirupsen/logrus"
 )
@@ -90,16 +88,16 @@ func fixIcoSize(img *vips.Image) error {
 	return nil
 }
 
-func fixSize(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptions, imgdata imagedata.ImageData) error {
-	switch po.Format {
+func fixSize(c *Context) error {
+	switch c.PO.Format {
 	case imagetype.WEBP:
-		return fixWebpSize(img)
+		return fixWebpSize(c.Img)
 	case imagetype.AVIF, imagetype.HEIC:
-		return fixHeifSize(img)
+		return fixHeifSize(c.Img)
 	case imagetype.GIF:
-		return fixGifSize(img)
+		return fixGifSize(c.Img)
 	case imagetype.ICO:
-		return fixIcoSize(img)
+		return fixIcoSize(c.Img)
 	}
 
 	return nil

+ 3 - 9
processing/flatten.go

@@ -1,15 +1,9 @@
 package processing
 
-import (
-	"github.com/imgproxy/imgproxy/v3/imagedata"
-	"github.com/imgproxy/imgproxy/v3/options"
-	"github.com/imgproxy/imgproxy/v3/vips"
-)
-
-func flatten(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptions, imgdata imagedata.ImageData) error {
-	if !po.Flatten && po.Format.SupportsAlpha() {
+func flatten(c *Context) error {
+	if !c.PO.Flatten && c.PO.Format.SupportsAlpha() {
 		return nil
 	}
 
-	return img.Flatten(po.Background)
+	return c.Img.Flatten(c.PO.Background)
 }

+ 9 - 12
processing/padding.go

@@ -1,25 +1,22 @@
 package processing
 
 import (
-	"github.com/imgproxy/imgproxy/v3/imagedata"
 	"github.com/imgproxy/imgproxy/v3/imath"
-	"github.com/imgproxy/imgproxy/v3/options"
-	"github.com/imgproxy/imgproxy/v3/vips"
 )
 
-func padding(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptions, imgdata imagedata.ImageData) error {
-	if !po.Padding.Enabled {
+func padding(c *Context) error {
+	if !c.PO.Padding.Enabled {
 		return nil
 	}
 
-	paddingTop := imath.ScaleToEven(po.Padding.Top, pctx.dprScale)
-	paddingRight := imath.ScaleToEven(po.Padding.Right, pctx.dprScale)
-	paddingBottom := imath.ScaleToEven(po.Padding.Bottom, pctx.dprScale)
-	paddingLeft := imath.ScaleToEven(po.Padding.Left, pctx.dprScale)
+	paddingTop := imath.ScaleToEven(c.PO.Padding.Top, c.DprScale)
+	paddingRight := imath.ScaleToEven(c.PO.Padding.Right, c.DprScale)
+	paddingBottom := imath.ScaleToEven(c.PO.Padding.Bottom, c.DprScale)
+	paddingLeft := imath.ScaleToEven(c.PO.Padding.Left, c.DprScale)
 
-	return img.Embed(
-		img.Width()+paddingLeft+paddingRight,
-		img.Height()+paddingTop+paddingBottom,
+	return c.Img.Embed(
+		c.Img.Width()+paddingLeft+paddingRight,
+		c.Img.Height()+paddingTop+paddingBottom,
 		paddingLeft,
 		paddingTop,
 	)

+ 88 - 48
processing/pipeline.go

@@ -5,98 +5,108 @@ import (
 
 	"github.com/imgproxy/imgproxy/v3/auximageprovider"
 	"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/server"
 	"github.com/imgproxy/imgproxy/v3/vips"
 )
 
-type pipelineContext struct {
-	ctx context.Context
+// NOTE: this will be called pipeline.Context in the separate package
+type Context struct {
+	Сtx context.Context
 
-	imgtype imagetype.Type
+	// Global processing configuration which could be used by individual steps
+	Config *pipeline.Config
 
-	// The watermark image provider, if any watermarking is to be done.
-	watermarkProvider auximageprovider.Provider
+	// VIPS image
+	Img *vips.Image
+
+	// Processing options this pipeline runs with
+	PO *options.ProcessingOptions
 
-	trimmed bool
+	// Source image data
+	ImgData imagedata.ImageData
+
+	// The watermark image provider, if any watermarking is to be done.
+	WatermarkProvider auximageprovider.Provider
 
-	srcWidth  int
-	srcHeight int
-	angle     int
-	flip      bool
+	SrcWidth  int
+	SrcHeight int
+	Angle     int
+	Flip      bool
 
-	cropWidth   int
-	cropHeight  int
-	cropGravity options.GravityOptions
+	CropWidth   int
+	CropHeight  int
+	CropGravity options.GravityOptions
 
-	wscale float64
-	hscale float64
+	WScale float64
+	HScale float64
 
-	dprScale float64
+	DprScale float64
 
 	// The base scale factor for vector images.
 	// It is used to downscale the input vector image to the maximum allowed resolution
-	vectorBaseScale float64
+	VectorBaseScale float64
 
 	// The width we aim to get.
 	// Based on the requested width scaled according to processing options.
 	// Can be 0 if width is not specified in the processing options.
-	targetWidth int
+	TargetWidth int
 	// The height we aim to get.
 	// Based on the requested height scaled according to processing options.
 	// Can be 0 if height is not specified in the processing options.
-	targetHeight int
+	TargetHeight int
 
 	// The width of the image after cropping, scaling and rotating
-	scaledWidth int
+	ScaledWidth int
 	// The height of the image after cropping, scaling and rotating
-	scaledHeight int
+	ScaledHeight int
 
 	// The width of the result crop according to the resizing type
-	resultCropWidth int
+	ResultCropWidth int
 	// The height of the result crop according to the resizing type
-	resultCropHeight int
+	ResultCropHeight int
 
 	// The width of the image extended to the requested aspect ratio.
 	// Can be 0 if any of the dimensions is not specified in the processing options
 	// or if the image already has the requested aspect ratio.
-	extendAspectRatioWidth int
+	ExtendAspectRatioWidth int
 	// The width of the image extended to the requested aspect ratio.
 	// Can be 0 if any of the dimensions is not specified in the processing options
 	// or if the image already has the requested aspect ratio.
-	extendAspectRatioHeight int
+	ExtendAspectRatioHeight int
 }
 
-type pipelineStep func(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptions, imgdata imagedata.ImageData) error
-type pipeline []pipelineStep
+// NOTE: same, pipeline.Step, pipeline.Pipeline, pipeline.Runner
+type Step func(ctx *Context) error
+type Pipeline []Step
 
-func (p pipeline) Run(
+// 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,
 	ctx context.Context,
 	img *vips.Image,
 	po *options.ProcessingOptions,
 	imgdata imagedata.ImageData,
-	watermark auximageprovider.Provider,
 ) error {
-	pctx := pipelineContext{
-		ctx: ctx,
-
-		wscale: 1.0,
-		hscale: 1.0,
-
-		dprScale:        1.0,
-		vectorBaseScale: 1.0,
-
-		cropGravity:       po.Crop.Gravity,
-		watermarkProvider: watermark,
-	}
-
-	if pctx.cropGravity.Type == options.GravityUnknown {
-		pctx.cropGravity = po.Gravity
-	}
+	pctx := f.newContext(ctx, img, po, imgdata)
 
 	for _, step := range p {
-		if err := step(&pctx, img, po, imgdata); err != nil {
+		if err := step(&pctx); err != nil {
 			return err
 		}
 
@@ -105,7 +115,37 @@ func (p pipeline) Run(
 		}
 	}
 
-	img.SetDouble("imgproxy-dpr-scale", pctx.dprScale)
+	img.SetDouble("imgproxy-dpr-scale", pctx.DprScale)
 
 	return nil
 }
+
+func (r *Runner) newContext(
+	ctx context.Context,
+	img *vips.Image,
+	po *options.ProcessingOptions,
+	imgdata imagedata.ImageData,
+) Context {
+	pctx := Context{
+		Сtx:     ctx,
+		Config:  r.config,
+		Img:     img,
+		PO:      po,
+		ImgData: imgdata,
+
+		WScale: 1.0,
+		HScale: 1.0,
+
+		DprScale:        1.0,
+		VectorBaseScale: 1.0,
+
+		CropGravity:       po.Crop.Gravity,
+		WatermarkProvider: r.watermark,
+	}
+
+	if pctx.CropGravity.Type == options.GravityUnknown {
+		pctx.CropGravity = po.Gravity
+	}
+
+	return pctx
+}

+ 44 - 0
processing/pipeline/config.go

@@ -0,0 +1,44 @@
+package pipeline
+
+import (
+	"errors"
+
+	"github.com/imgproxy/imgproxy/v3/config"
+	"github.com/imgproxy/imgproxy/v3/ensure"
+)
+
+// Config holds pipeline-related configuration.
+type Config struct {
+	WatermarkOpacity    float64
+	DisableShrinkOnLoad bool
+	UseLinearColorspace bool
+}
+
+// NewConfig creates a new Config instance with the given parameters.
+func NewDefaultConfig() Config {
+	return Config{
+		WatermarkOpacity: 1,
+	}
+}
+
+// NewConfig creates a new Config instance with the given parameters.
+func LoadConfigFromEnv(c *Config) (*Config, error) {
+	c = ensure.Ensure(c, NewDefaultConfig)
+
+	c.WatermarkOpacity = config.WatermarkOpacity
+	c.DisableShrinkOnLoad = config.DisableShrinkOnLoad
+	c.UseLinearColorspace = config.UseLinearColorspace
+
+	return c, nil
+}
+
+// Validate checks if the configuration is valid
+func (c *Config) Validate() error {
+	if c.WatermarkOpacity <= 0 {
+		return errors.New("watermark opacity should be greater than 0")
+	} else if c.WatermarkOpacity > 1 {
+		return errors.New("watermark opacity should be less than or equal to 1")
+	}
+
+	return nil
+}

+ 61 - 65
processing/prepare.go

@@ -3,8 +3,6 @@ package processing
 import (
 	"math"
 
-	"github.com/imgproxy/imgproxy/v3/imagedata"
-	"github.com/imgproxy/imgproxy/v3/imagetype"
 	"github.com/imgproxy/imgproxy/v3/imath"
 	"github.com/imgproxy/imgproxy/v3/options"
 	"github.com/imgproxy/imgproxy/v3/vips"
@@ -52,7 +50,7 @@ func calcCropSize(orig int, crop float64) int {
 	}
 }
 
-func (pctx *pipelineContext) calcScale(width, height int, po *options.ProcessingOptions) {
+func (pctx *Context) calcScale(width, height int, po *options.ProcessingOptions) {
 	var wshrink, hshrink float64
 
 	srcW, srcH := float64(width), float64(height)
@@ -109,22 +107,22 @@ func (pctx *pipelineContext) calcScale(width, height int, po *options.Processing
 	wshrink /= po.ZoomWidth
 	hshrink /= po.ZoomHeight
 
-	pctx.dprScale = po.Dpr
+	pctx.DprScale = po.Dpr
 
-	if !po.Enlarge && !pctx.imgtype.IsVector() {
+	if !po.Enlarge && pctx.ImgData != nil && !pctx.ImgData.Format().IsVector() {
 		minShrink := math.Min(wshrink, hshrink)
 		if minShrink < 1 {
 			wshrink /= minShrink
 			hshrink /= minShrink
 
 			if !po.Extend.Enabled {
-				pctx.dprScale /= minShrink
+				pctx.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))
+		pctx.DprScale = math.Min(pctx.DprScale, math.Min(wshrink, hshrink))
 	}
 
 	if po.MinWidth > 0 {
@@ -141,8 +139,8 @@ func (pctx *pipelineContext) calcScale(width, height int, po *options.Processing
 		}
 	}
 
-	wshrink /= pctx.dprScale
-	hshrink /= pctx.dprScale
+	wshrink /= pctx.DprScale
+	hshrink /= pctx.DprScale
 
 	if wshrink > srcW {
 		wshrink = srcW
@@ -152,119 +150,117 @@ func (pctx *pipelineContext) calcScale(width, height int, po *options.Processing
 		hshrink = srcH
 	}
 
-	pctx.wscale = 1.0 / wshrink
-	pctx.hscale = 1.0 / hshrink
+	pctx.WScale = 1.0 / wshrink
+	pctx.HScale = 1.0 / hshrink
 }
 
-func (pctx *pipelineContext) 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 (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)
 
-	pctx.scaledWidth = imath.Scale(widthToScale, pctx.wscale)
-	pctx.scaledHeight = imath.Scale(heightToScale, pctx.hscale)
+	pctx.ScaledWidth = imath.Scale(widthToScale, pctx.WScale)
+	pctx.ScaledHeight = imath.Scale(heightToScale, pctx.HScale)
 
 	if po.ResizingType == options.ResizeFillDown && !po.Enlarge {
-		diffW := float64(pctx.targetWidth) / float64(pctx.scaledWidth)
-		diffH := float64(pctx.targetHeight) / float64(pctx.scaledHeight)
+		diffW := float64(pctx.TargetWidth) / float64(pctx.ScaledWidth)
+		diffH := float64(pctx.TargetHeight) / float64(pctx.ScaledHeight)
 
 		switch {
 		case diffW > diffH && diffW > 1.0:
-			pctx.resultCropHeight = imath.Scale(pctx.scaledWidth, float64(pctx.targetHeight)/float64(pctx.targetWidth))
-			pctx.resultCropWidth = pctx.scaledWidth
+			pctx.ResultCropHeight = imath.Scale(pctx.ScaledWidth, float64(pctx.TargetHeight)/float64(pctx.TargetWidth))
+			pctx.ResultCropWidth = pctx.ScaledWidth
 
 		case diffH > diffW && diffH > 1.0:
-			pctx.resultCropWidth = imath.Scale(pctx.scaledHeight, float64(pctx.targetWidth)/float64(pctx.targetHeight))
-			pctx.resultCropHeight = pctx.scaledHeight
+			pctx.ResultCropWidth = imath.Scale(pctx.ScaledHeight, float64(pctx.TargetWidth)/float64(pctx.TargetHeight))
+			pctx.ResultCropHeight = pctx.ScaledHeight
 
 		default:
-			pctx.resultCropWidth = pctx.targetWidth
-			pctx.resultCropHeight = pctx.targetHeight
+			pctx.ResultCropWidth = pctx.TargetWidth
+			pctx.ResultCropHeight = pctx.TargetHeight
 		}
 	} else {
-		pctx.resultCropWidth = pctx.targetWidth
-		pctx.resultCropHeight = pctx.targetHeight
+		pctx.ResultCropWidth = pctx.TargetWidth
+		pctx.ResultCropHeight = pctx.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 && pctx.TargetWidth > 0 && pctx.TargetHeight > 0 {
+		outWidth := imath.MinNonZero(pctx.ScaledWidth, pctx.ResultCropWidth)
+		outHeight := imath.MinNonZero(pctx.ScaledHeight, pctx.ResultCropHeight)
 
-		diffW := float64(pctx.targetWidth) / float64(outWidth)
-		diffH := float64(pctx.targetHeight) / float64(outHeight)
+		diffW := float64(pctx.TargetWidth) / float64(outWidth)
+		diffH := float64(pctx.TargetHeight) / float64(outHeight)
 
 		switch {
 		case diffH > diffW:
-			pctx.extendAspectRatioHeight = imath.Scale(outWidth, float64(pctx.targetHeight)/float64(pctx.targetWidth))
-			pctx.extendAspectRatioWidth = outWidth
+			pctx.ExtendAspectRatioHeight = imath.Scale(outWidth, float64(pctx.TargetHeight)/float64(pctx.TargetWidth))
+			pctx.ExtendAspectRatioWidth = outWidth
 
 		case diffW > diffH:
-			pctx.extendAspectRatioWidth = imath.Scale(outHeight, float64(pctx.targetWidth)/float64(pctx.targetHeight))
-			pctx.extendAspectRatioHeight = outHeight
+			pctx.ExtendAspectRatioWidth = imath.Scale(outHeight, float64(pctx.TargetWidth)/float64(pctx.TargetHeight))
+			pctx.ExtendAspectRatioHeight = outHeight
 		}
 	}
 }
 
-func (pctx *pipelineContext) limitScale(widthToScale, heightToScale int, po *options.ProcessingOptions) {
+func (pctx *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(pctx.ScaledWidth, pctx.ResultCropWidth)
+	outHeight := imath.MinNonZero(pctx.ScaledHeight, pctx.ResultCropHeight)
 
 	if po.Extend.Enabled {
-		outWidth = max(outWidth, pctx.targetWidth)
-		outHeight = max(outHeight, pctx.targetHeight)
+		outWidth = max(outWidth, pctx.TargetWidth)
+		outHeight = max(outHeight, pctx.TargetHeight)
 	} else if po.ExtendAspectRatio.Enabled {
-		outWidth = max(outWidth, pctx.extendAspectRatioWidth)
-		outHeight = max(outHeight, pctx.extendAspectRatioHeight)
+		outWidth = max(outWidth, pctx.ExtendAspectRatioWidth)
+		outHeight = max(outHeight, pctx.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, pctx.DprScale) + imath.ScaleToEven(po.Padding.Right, pctx.DprScale)
+		outHeight += imath.ScaleToEven(po.Padding.Top, pctx.DprScale) + imath.ScaleToEven(po.Padding.Bottom, pctx.DprScale)
 	}
 
 	if maxresultDim > 0 && (outWidth > maxresultDim || outHeight > maxresultDim) {
 		downScale := float64(maxresultDim) / float64(max(outWidth, outHeight))
 
-		pctx.wscale *= downScale
-		pctx.hscale *= downScale
+		pctx.WScale *= downScale
+		pctx.HScale *= downScale
 
 		// Prevent scaling below 1px
-		if minWScale := 1.0 / float64(widthToScale); pctx.wscale < minWScale {
-			pctx.wscale = minWScale
+		if minWScale := 1.0 / float64(widthToScale); pctx.WScale < minWScale {
+			pctx.WScale = minWScale
 		}
-		if minHScale := 1.0 / float64(heightToScale); pctx.hscale < minHScale {
-			pctx.hscale = minHScale
+		if minHScale := 1.0 / float64(heightToScale); pctx.HScale < minHScale {
+			pctx.HScale = minHScale
 		}
 
-		pctx.dprScale *= downScale
+		pctx.DprScale *= downScale
 
 		// Recalculate the sizes after changing the scales
 		pctx.calcSizes(widthToScale, heightToScale, po)
 	}
 }
 
-func prepare(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptions, imgdata imagedata.ImageData) error {
-	pctx.imgtype = imagetype.Unknown
-	if imgdata != nil {
-		pctx.imgtype = imgdata.Format()
-	}
-
-	pctx.srcWidth, pctx.srcHeight, pctx.angle, pctx.flip = extractMeta(img, po.Rotate, po.AutoRotate)
+// 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)
 
-	pctx.cropWidth = calcCropSize(pctx.srcWidth, po.Crop.Width)
-	pctx.cropHeight = calcCropSize(pctx.srcHeight, po.Crop.Height)
+	c.CropWidth = calcCropSize(c.SrcWidth, c.PO.Crop.Width)
+	c.CropHeight = calcCropSize(c.SrcHeight, c.PO.Crop.Height)
 
-	widthToScale := imath.MinNonZero(pctx.cropWidth, pctx.srcWidth)
-	heightToScale := imath.MinNonZero(pctx.cropHeight, pctx.srcHeight)
+	widthToScale := imath.MinNonZero(c.CropWidth, c.SrcWidth)
+	heightToScale := imath.MinNonZero(c.CropHeight, c.SrcHeight)
 
-	pctx.calcScale(widthToScale, heightToScale, po)
-	pctx.calcSizes(widthToScale, heightToScale, po)
-	pctx.limitScale(widthToScale, heightToScale, po)
+	c.calcScale(widthToScale, heightToScale, c.PO)
+	c.calcSizes(widthToScale, heightToScale, c.PO)
+	c.limitScale(widthToScale, heightToScale, c.PO)
 
 	return nil
 }

+ 41 - 6
processing/processing.go

@@ -13,6 +13,7 @@ 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"
@@ -21,7 +22,7 @@ import (
 
 // The main processing pipeline (without finalization).
 // Applied to non-animated images and individual frames of animated images.
-var mainPipeline = pipeline{
+var mainPipeline = Pipeline{
 	vectorGuardScale,
 	trim,
 	prepare,
@@ -42,7 +43,7 @@ var mainPipeline = pipeline{
 
 // The finalization pipeline.
 // Applied right before saving the image.
-var finalizePipeline = pipeline{
+var finalizePipeline = Pipeline{
 	colorspaceToResult,
 	stripMetadata,
 }
@@ -142,8 +143,15 @@ func ProcessImage(
 		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 = finalizePipeline.Run(ctx, img, po, imgdata, watermarkProvider); err != nil {
+	if err = runner.Run(finalizePipeline, ctx, img, po, imgdata); err != nil {
 		return nil, err
 	}
 
@@ -393,7 +401,14 @@ func transformImage(
 		return transformAnimated(ctx, img, po, watermark)
 	}
 
-	return mainPipeline.Run(ctx, img, po, imgdata, watermark)
+	// 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)
 }
 
 func transformAnimated(
@@ -452,10 +467,17 @@ func transformAnimated(
 
 		frames = append(frames, frame)
 
+		// NOTE: THIS IS TEMPORARY
+		runner, rerr := tmpNewRunner(watermark)
+		if rerr != nil {
+			return rerr
+		}
+		// NOTE: END TEMPORARY BLOCK
+
 		// Transform the frame using the main pipeline.
 		// We don't provide imgdata here to prevent scale-on-load.
-		// Let's skip passing watermark here since in would be applied later to all frames at once.
-		if err = mainPipeline.Run(ctx, frame, po, nil, nil); err != nil {
+		// Watermarking is disabled for individual frames (see above)
+		if err = runner.Run(mainPipeline, ctx, frame, po, nil); err != nil {
 			return err
 		}
 
@@ -543,3 +565,16 @@ 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
+}

+ 7 - 13
processing/rotate_and_flip.go

@@ -1,29 +1,23 @@
 package processing
 
-import (
-	"github.com/imgproxy/imgproxy/v3/imagedata"
-	"github.com/imgproxy/imgproxy/v3/options"
-	"github.com/imgproxy/imgproxy/v3/vips"
-)
-
-func rotateAndFlip(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptions, imgdata imagedata.ImageData) error {
-	if pctx.angle%360 == 0 && po.Rotate%360 == 0 && !pctx.flip {
+func rotateAndFlip(ctx *Context) error {
+	if ctx.Angle%360 == 0 && ctx.PO.Rotate%360 == 0 && !ctx.Flip {
 		return nil
 	}
 
-	if err := img.CopyMemory(); err != nil {
+	if err := ctx.Img.CopyMemory(); err != nil {
 		return err
 	}
 
-	if err := img.Rotate(pctx.angle); err != nil {
+	if err := ctx.Img.Rotate(ctx.Angle); err != nil {
 		return err
 	}
 
-	if pctx.flip {
-		if err := img.Flip(); err != nil {
+	if ctx.Flip {
+		if err := ctx.Img.Flip(); err != nil {
 			return err
 		}
 	}
 
-	return img.Rotate(po.Rotate)
+	return ctx.Img.Rotate(ctx.PO.Rotate)
 }

+ 5 - 11
processing/scale.go

@@ -1,20 +1,14 @@
 package processing
 
-import (
-	"github.com/imgproxy/imgproxy/v3/imagedata"
-	"github.com/imgproxy/imgproxy/v3/options"
-	"github.com/imgproxy/imgproxy/v3/vips"
-)
-
-func scale(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptions, imgdata imagedata.ImageData) error {
-	if pctx.wscale == 1 && pctx.hscale == 1 {
+func scale(c *Context) error {
+	if c.WScale == 1 && c.HScale == 1 {
 		return nil
 	}
 
-	wscale, hscale := pctx.wscale, pctx.hscale
-	if (pctx.angle+po.Rotate)%180 == 90 {
+	wscale, hscale := c.WScale, c.HScale
+	if (c.Angle+c.PO.Rotate)%180 == 90 {
 		wscale, hscale = hscale, wscale
 	}
 
-	return img.Resize(wscale, hscale)
+	return c.Img.Resize(wscale, hscale)
 }

+ 36 - 37
processing/scale_on_load.go

@@ -5,7 +5,6 @@ import (
 
 	log "github.com/sirupsen/logrus"
 
-	"github.com/imgproxy/imgproxy/v3/config"
 	"github.com/imgproxy/imgproxy/v3/imagedata"
 	"github.com/imgproxy/imgproxy/v3/imagetype"
 	"github.com/imgproxy/imgproxy/v3/imath"
@@ -13,8 +12,8 @@ import (
 	"github.com/imgproxy/imgproxy/v3/vips"
 )
 
-func canScaleOnLoad(pctx *pipelineContext, imgdata imagedata.ImageData, scale float64) bool {
-	if imgdata == nil || pctx.trimmed || scale == 1 {
+func canScaleOnLoad(c *Context, imgdata imagedata.ImageData, scale float64) bool {
+	if imgdata == nil || scale == 1 {
 		return false
 	}
 
@@ -22,7 +21,7 @@ func canScaleOnLoad(pctx *pipelineContext, imgdata imagedata.ImageData, scale fl
 		return true
 	}
 
-	if config.DisableShrinkOnLoad || scale >= 1 {
+	if c.Config.DisableShrinkOnLoad || scale >= 1 {
 		return false
 	}
 
@@ -45,86 +44,86 @@ func calcJpegShink(shrink float64) int {
 	return 1
 }
 
-func scaleOnLoad(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptions, imgdata imagedata.ImageData) error {
-	wshrink := float64(pctx.srcWidth) / float64(imath.Scale(pctx.srcWidth, pctx.wscale))
-	hshrink := float64(pctx.srcHeight) / float64(imath.Scale(pctx.srcHeight, pctx.hscale))
+func 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)
 	prescale := 1.0 / preshrink
 
-	if imgdata != nil && imgdata.Format().IsVector() {
+	if c.ImgData != nil && c.ImgData.Format().IsVector() {
 		// For vector images, apply the vector base scale
-		prescale *= pctx.vectorBaseScale
+		prescale *= c.VectorBaseScale
 	}
 
-	if !canScaleOnLoad(pctx, imgdata, prescale) {
+	if !canScaleOnLoad(c, c.ImgData, prescale) {
 		return nil
 	}
 
 	var newWidth, newHeight int
 
-	if imgdata.Format().SupportsThumbnail() {
+	if c.ImgData.Format().SupportsThumbnail() {
 		thumbnail := new(vips.Image)
 		defer thumbnail.Clear()
 
-		if err := thumbnail.LoadThumbnail(imgdata); err != nil {
+		if err := thumbnail.LoadThumbnail(c.ImgData); err != nil {
 			log.Debugf("Can't load thumbnail: %s", err)
 			return nil
 		}
 
 		angle, flip := 0, false
-		newWidth, newHeight, angle, flip = extractMeta(thumbnail, po.Rotate, po.AutoRotate)
+		newWidth, newHeight, angle, flip = extractMeta(thumbnail, c.PO.Rotate, c.PO.AutoRotate)
 
-		if newWidth >= pctx.srcWidth || float64(newWidth)/float64(pctx.srcWidth) < prescale {
+		if newWidth >= c.SrcWidth || float64(newWidth)/float64(c.SrcWidth) < prescale {
 			return nil
 		}
 
-		img.Swap(thumbnail)
-		pctx.angle = angle
-		pctx.flip = flip
+		c.Img.Swap(thumbnail)
+		c.Angle = angle
+		c.Flip = flip
 	} else {
 		jpegShrink := calcJpegShink(preshrink)
 
-		if pctx.imgtype == imagetype.JPEG && jpegShrink == 1 {
+		if c.ImgData.Format() == imagetype.JPEG && jpegShrink == 1 {
 			return nil
 		}
 
-		if err := img.Load(imgdata, jpegShrink, prescale, 1); err != nil {
+		if err := c.Img.Load(c.ImgData, jpegShrink, prescale, 1); err != nil {
 			return err
 		}
 
-		newWidth, newHeight, _, _ = extractMeta(img, po.Rotate, po.AutoRotate)
+		newWidth, newHeight, _, _ = extractMeta(c.Img, c.PO.Rotate, c.PO.AutoRotate)
 	}
 
 	// Update scales after scale-on-load
-	wpreshrink := float64(pctx.srcWidth) / float64(newWidth)
-	hpreshrink := float64(pctx.srcHeight) / float64(newHeight)
+	wpreshrink := float64(c.SrcWidth) / float64(newWidth)
+	hpreshrink := float64(c.SrcHeight) / float64(newHeight)
 
-	pctx.wscale = wpreshrink * pctx.wscale
-	if newWidth == imath.Scale(newWidth, pctx.wscale) {
-		pctx.wscale = 1.0
+	c.WScale = wpreshrink * c.WScale
+	if newWidth == imath.Scale(newWidth, c.WScale) {
+		c.WScale = 1.0
 	}
 
-	pctx.hscale = hpreshrink * pctx.hscale
-	if newHeight == imath.Scale(newHeight, pctx.hscale) {
-		pctx.hscale = 1.0
+	c.HScale = hpreshrink * c.HScale
+	if newHeight == imath.Scale(newHeight, c.HScale) {
+		c.HScale = 1.0
 	}
 
 	// We should crop before scaling, but we scaled the image on load,
 	// so we need to adjust crop options
-	if pctx.cropWidth > 0 {
-		pctx.cropWidth = max(1, imath.Shrink(pctx.cropWidth, wpreshrink))
+	if c.CropWidth > 0 {
+		c.CropWidth = max(1, imath.Shrink(c.CropWidth, wpreshrink))
 	}
-	if pctx.cropHeight > 0 {
-		pctx.cropHeight = max(1, imath.Shrink(pctx.cropHeight, hpreshrink))
+	if c.CropHeight > 0 {
+		c.CropHeight = max(1, imath.Shrink(c.CropHeight, hpreshrink))
 	}
-	if pctx.cropGravity.Type != options.GravityFocusPoint {
+	if c.CropGravity.Type != options.GravityFocusPoint {
 		// Adjust only when crop gravity offsets are absolute
-		if math.Abs(pctx.cropGravity.X) >= 1.0 {
+		if math.Abs(c.CropGravity.X) >= 1.0 {
 			// Round offsets to prevent turning absolute offsets to relative (ex: 1.0 => 0.5)
-			pctx.cropGravity.X = math.RoundToEven(pctx.cropGravity.X / wpreshrink)
+			c.CropGravity.X = math.RoundToEven(c.CropGravity.X / wpreshrink)
 		}
-		if math.Abs(pctx.cropGravity.Y) >= 1.0 {
-			pctx.cropGravity.Y = math.RoundToEven(pctx.cropGravity.Y / hpreshrink)
+		if math.Abs(c.CropGravity.Y) >= 1.0 {
+			c.CropGravity.Y = math.RoundToEven(c.CropGravity.Y / hpreshrink)
 		}
 	}
 

+ 9 - 11
processing/strip_metadata.go

@@ -5,10 +5,8 @@ import (
 
 	"github.com/trimmer-io/go-xmp/xmp"
 
-	"github.com/imgproxy/imgproxy/v3/imagedata"
 	"github.com/imgproxy/imgproxy/v3/imagemeta/iptc"
 	"github.com/imgproxy/imgproxy/v3/imagemeta/photoshop"
-	"github.com/imgproxy/imgproxy/v3/options"
 	"github.com/imgproxy/imgproxy/v3/vips"
 )
 
@@ -105,29 +103,29 @@ func stripXMP(img *vips.Image) []byte {
 	return xmpData
 }
 
-func stripMetadata(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptions, imgdata imagedata.ImageData) error {
-	if !po.StripMetadata {
+func stripMetadata(ctx *Context) error {
+	if !ctx.PO.StripMetadata {
 		return nil
 	}
 
 	var ps3Data, xmpData []byte
 
-	if po.KeepCopyright {
-		ps3Data = stripPS3(img)
-		xmpData = stripXMP(img)
+	if ctx.PO.KeepCopyright {
+		ps3Data = stripPS3(ctx.Img)
+		xmpData = stripXMP(ctx.Img)
 	}
 
-	if err := img.Strip(po.KeepCopyright); err != nil {
+	if err := ctx.Img.Strip(ctx.PO.KeepCopyright); err != nil {
 		return err
 	}
 
-	if po.KeepCopyright {
+	if ctx.PO.KeepCopyright {
 		if len(ps3Data) > 0 {
-			img.SetBlob("iptc-data", ps3Data)
+			ctx.Img.SetBlob("iptc-data", ps3Data)
 		}
 
 		if len(xmpData) > 0 {
-			img.SetBlob("xmp-data", xmpData)
+			ctx.Img.SetBlob("xmp-data", xmpData)
 		}
 	}
 

+ 6 - 12
processing/trim.go

@@ -1,29 +1,23 @@
 package processing
 
-import (
-	"github.com/imgproxy/imgproxy/v3/imagedata"
-	"github.com/imgproxy/imgproxy/v3/options"
-	"github.com/imgproxy/imgproxy/v3/vips"
-)
-
-func trim(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptions, imgdata imagedata.ImageData) error {
-	if !po.Trim.Enabled {
+func trim(c *Context) error {
+	if !c.PO.Trim.Enabled {
 		return nil
 	}
 
 	// We need to import color profile before trim
-	if err := colorspaceToProcessing(pctx, img, po, imgdata); err != nil {
+	if err := colorspaceToProcessing(c); err != nil {
 		return err
 	}
 
-	if err := img.Trim(po.Trim.Threshold, po.Trim.Smart, po.Trim.Color, po.Trim.EqualHor, po.Trim.EqualVer); err != nil {
+	if err := c.Img.Trim(c.PO.Trim.Threshold, c.PO.Trim.Smart, c.PO.Trim.Color, c.PO.Trim.EqualHor, c.PO.Trim.EqualVer); err != nil {
 		return err
 	}
-	if err := img.CopyMemory(); err != nil {
+	if err := c.Img.CopyMemory(); err != nil {
 		return err
 	}
 
-	pctx.trimmed = true
+	c.ImgData = nil
 
 	return nil
 }

+ 6 - 10
processing/vector_guard_scale.go

@@ -2,24 +2,20 @@ package processing
 
 import (
 	"math"
-
-	"github.com/imgproxy/imgproxy/v3/imagedata"
-	"github.com/imgproxy/imgproxy/v3/options"
-	"github.com/imgproxy/imgproxy/v3/vips"
 )
 
 // vectorGuardScale checks if the image is a vector format and downscales it
 // to the maximum allowed resolution if necessary
-func vectorGuardScale(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptions, imgdata imagedata.ImageData) error {
-	if imgdata == nil || !imgdata.Format().IsVector() {
+func vectorGuardScale(c *Context) error {
+	if c.ImgData == nil || !c.ImgData.Format().IsVector() {
 		return nil
 	}
 
-	if resolution := img.Width() * img.Height(); resolution > po.SecurityOptions.MaxSrcResolution {
-		scale := math.Sqrt(float64(po.SecurityOptions.MaxSrcResolution) / float64(resolution))
-		pctx.vectorBaseScale = scale
+	if resolution := c.Img.Width() * c.Img.Height(); resolution > c.PO.SecurityOptions.MaxSrcResolution {
+		scale := math.Sqrt(float64(c.PO.SecurityOptions.MaxSrcResolution) / float64(resolution))
+		c.VectorBaseScale = scale
 
-		if err := img.Load(imgdata, 1, scale, 1); err != nil {
+		if err := c.Img.Load(c.ImgData, 1, scale, 1); err != nil {
 			return err
 		}
 	}

+ 13 - 10
processing/watermark.go

@@ -12,7 +12,7 @@ import (
 	"github.com/imgproxy/imgproxy/v3/vips"
 )
 
-var watermarkPipeline = pipeline{
+var watermarkPipeline = Pipeline{
 	vectorGuardScale,
 	prepare,
 	scaleOnLoad,
@@ -62,7 +62,14 @@ func prepareWatermark(wm *vips.Image, wmData imagedata.ImageData, po *options.Pr
 		wmPo.Padding.Bottom = offY - wmPo.Padding.Top
 	}
 
-	if err := watermarkPipeline.Run(context.Background(), wm, wmPo, wmData, nil); err != nil {
+	// NOTE: THIS IS TEMPORARY
+	runner, err := tmpNewRunner(nil) // watermark will present in runner
+	if err != nil {
+		return err
+	}
+	// NOTE: END TEMPORARY BLOCK
+
+	if err := runner.Run(watermarkPipeline, context.Background(), wm, wmPo, wmData); err != nil {
 		return err
 	}
 
@@ -126,6 +133,7 @@ func applyWatermark(
 		return err
 	}
 
+	// TODO: Use runner config
 	opacity := opts.Opacity * config.WatermarkOpacity
 
 	// If we replicated the watermark and need to apply it to an animated image,
@@ -185,15 +193,10 @@ func applyWatermark(
 	return nil
 }
 
-func watermark(
-	pctx *pipelineContext,
-	img *vips.Image,
-	po *options.ProcessingOptions,
-	imgdata imagedata.ImageData,
-) error {
-	if !po.Watermark.Enabled || pctx.watermarkProvider == nil {
+func watermark(c *Context) error {
+	if !c.PO.Watermark.Enabled || c.WatermarkProvider == nil {
 		return nil
 	}
 
-	return applyWatermark(pctx.ctx, img, pctx.watermarkProvider, po, pctx.dprScale, 1)
+	return applyWatermark(c.Сtx, c.Img, c.WatermarkProvider, c.PO, c.DprScale, 1)
 }