ProcessingOptionsFactory in processing and watermark

This commit is contained in:
Viktor Sokolov
2025-09-15 17:03:48 +02:00
parent 308f3b9c32
commit 4698023504
7 changed files with 68 additions and 116 deletions

View File

@@ -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 processing.ProcessImage(ctx, originData, r.po, r.WatermarkImage(), r.ProcessingOptionsFactory())
}
// writeDebugHeaders writes debug headers (X-Origin-*, X-Result-*) to the response

View File

@@ -80,6 +80,8 @@ func (f *Factory) NewProcessingOptions() *ProcessingOptions {
defaultQuality: f.config.Quality,
}
po.defaultOptions = &po
po.FormatQuality = make(map[imagetype.Type]int, len(f.config.FormatQuality))
maps.Copy(po.FormatQuality, f.config.FormatQuality)

View File

@@ -2,7 +2,6 @@ package options
import (
"encoding/base64"
"fmt"
"net/http"
"slices"
"strconv"
@@ -11,7 +10,6 @@ import (
log "github.com/sirupsen/logrus"
"github.com/imgproxy/imgproxy/v3/config"
"github.com/imgproxy/imgproxy/v3/ierrors"
"github.com/imgproxy/imgproxy/v3/imagetype"
"github.com/imgproxy/imgproxy/v3/imath"
@@ -118,77 +116,7 @@ type ProcessingOptions struct {
SecurityOptions security.Options
defaultQuality int
}
func NewProcessingOptions() *ProcessingOptions {
// NOTE: This is temporary hack until ProcessingOptions does not have Factory
securityCfg, err := security.LoadConfigFromEnv(nil)
if err != nil {
fmt.Println(err)
}
// NOTE: This is a temporary workaround for logrus bug that deadlocks
// if log is used within another log (issue 1448)
if len(securityCfg.Salts) == 0 {
securityCfg.Salts = [][]byte{[]byte("logrusbugworkaround")}
}
if len(securityCfg.Keys) == 0 {
securityCfg.Keys = [][]byte{[]byte("logrusbugworkaround")}
}
// END OF WORKAROUND
security, err := security.New(securityCfg)
if err != nil {
fmt.Println(err)
}
securityOptions := security.NewOptions()
// NOTE: This is temporary hack until ProcessingOptions does not have Factory
po := ProcessingOptions{
ResizingType: ResizeFit,
Width: 0,
Height: 0,
ZoomWidth: 1,
ZoomHeight: 1,
Gravity: GravityOptions{Type: GravityCenter},
Enlarge: false,
Extend: ExtendOptions{Enabled: false, Gravity: GravityOptions{Type: GravityCenter}},
ExtendAspectRatio: ExtendOptions{Enabled: false, Gravity: GravityOptions{Type: GravityCenter}},
Padding: PaddingOptions{Enabled: false},
Trim: TrimOptions{Enabled: false, Threshold: 10, Smart: true},
Rotate: 0,
Quality: 0,
MaxBytes: 0,
Format: imagetype.Unknown,
Background: vips.Color{R: 255, G: 255, B: 255},
Blur: 0,
Sharpen: 0,
Dpr: 1,
Watermark: WatermarkOptions{Opacity: 1, Position: GravityOptions{Type: GravityCenter}},
StripMetadata: config.StripMetadata,
KeepCopyright: config.KeepCopyright,
StripColorProfile: config.StripColorProfile,
AutoRotate: config.AutoRotate,
EnforceThumbnail: config.EnforceThumbnail,
ReturnAttachment: config.ReturnAttachment,
SkipProcessingFormats: append([]imagetype.Type(nil), config.SkipProcessingFormats...),
UsedPresets: make([]string, 0, len(config.Presets)),
SecurityOptions: securityOptions,
// Basically, we need this to update ETag when `IMGPROXY_QUALITY` is changed
defaultQuality: config.Quality,
}
po.FormatQuality = make(map[imagetype.Type]int, len(config.FormatQuality))
for k, v := range config.FormatQuality {
po.FormatQuality[k] = v
}
return &po
defaultOptions *ProcessingOptions
}
func (po *ProcessingOptions) GetQuality() int {
@@ -206,7 +134,7 @@ func (po *ProcessingOptions) GetQuality() int {
}
func (po *ProcessingOptions) Diff() structdiff.Entries {
return structdiff.Diff(NewProcessingOptions(), po)
return structdiff.Diff(po.defaultOptions, po)
}
func (po *ProcessingOptions) String() string {

View File

@@ -19,6 +19,8 @@ type pipelineContext struct {
// The watermark image provider, if any watermarking is to be done.
watermarkProvider auximageprovider.Provider
processingOptionsFactory *options.Factory
trimmed bool
srcWidth int
@@ -77,6 +79,7 @@ func (p pipeline) Run(
po *options.ProcessingOptions,
imgdata imagedata.ImageData,
watermark auximageprovider.Provider,
processingOptionsFactory *options.Factory,
) error {
pctx := pipelineContext{
ctx: ctx,
@@ -89,6 +92,7 @@ func (p pipeline) Run(
cropGravity: po.Crop.Gravity,
watermarkProvider: watermark,
processingOptionsFactory: processingOptionsFactory,
}
if pctx.cropGravity.Type == options.GravityUnknown {

View File

@@ -85,6 +85,7 @@ func ProcessImage(
imgdata imagedata.ImageData,
po *options.ProcessingOptions,
watermarkProvider auximageprovider.Provider,
processingOptionsFactory *options.Factory,
) (*Result, error) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
@@ -138,12 +139,12 @@ func ProcessImage(
}
// Transform the image (resize, crop, etc)
if err = transformImage(ctx, img, po, imgdata, animated, watermarkProvider); err != nil {
if err = transformImage(ctx, img, po, imgdata, animated, watermarkProvider, processingOptionsFactory); err != nil {
return nil, err
}
// Finalize the image (colorspace conversion, metadata stripping, etc)
if err = finalizePipeline.Run(ctx, img, po, imgdata, watermarkProvider); err != nil {
if err = finalizePipeline.Run(ctx, img, po, imgdata, watermarkProvider, processingOptionsFactory); err != nil {
return nil, err
}
@@ -388,12 +389,13 @@ func transformImage(
imgdata imagedata.ImageData,
asAnimated bool,
watermark auximageprovider.Provider,
processingOptionsFactory *options.Factory,
) error {
if asAnimated {
return transformAnimated(ctx, img, po, watermark)
return transformAnimated(ctx, img, po, watermark, processingOptionsFactory)
}
return mainPipeline.Run(ctx, img, po, imgdata, watermark)
return mainPipeline.Run(ctx, img, po, imgdata, watermark, processingOptionsFactory)
}
func transformAnimated(
@@ -401,6 +403,7 @@ func transformAnimated(
img *vips.Image,
po *options.ProcessingOptions,
watermark auximageprovider.Provider,
processingOptionsFactory *options.Factory,
) error {
if po.Trim.Enabled {
log.Warning("Trim is not supported for animated images")
@@ -455,7 +458,7 @@ func transformAnimated(
// 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 {
if err = mainPipeline.Run(ctx, frame, po, nil, nil, processingOptionsFactory); err != nil {
return err
}
@@ -487,7 +490,7 @@ func transformAnimated(
dprScale = 1.0
}
if err = applyWatermark(ctx, img, watermark, po, dprScale, framesCount); err != nil {
if err = applyWatermark(ctx, img, watermark, processingOptionsFactory, po, dprScale, framesCount); err != nil {
return err
}
}

View File

@@ -16,12 +16,14 @@ import (
"github.com/imgproxy/imgproxy/v3/ierrors"
"github.com/imgproxy/imgproxy/v3/imagedata"
"github.com/imgproxy/imgproxy/v3/options"
"github.com/imgproxy/imgproxy/v3/security"
"github.com/imgproxy/imgproxy/v3/vips"
)
type ProcessingTestSuite struct {
suite.Suite
idf *imagedata.Factory
pof *options.Factory
}
func (s *ProcessingTestSuite) SetupSuite() {
@@ -41,6 +43,18 @@ func (s *ProcessingTestSuite) SetupSuite() {
s.Require().NoError(err)
s.idf = imagedata.NewFactory(f)
scfg, err := security.LoadConfigFromEnv(nil)
s.Require().NoError(err)
security, err := security.New(scfg)
s.Require().NoError(err)
cfg, err := options.LoadConfigFromEnv(nil)
s.Require().NoError(err)
s.pof, err = options.NewFactory(cfg, security)
s.Require().NoError(err)
}
func (s *ProcessingTestSuite) openFile(name string) imagedata.ImageData {
@@ -63,7 +77,7 @@ func (s *ProcessingTestSuite) checkSize(r *Result, width, height int) {
func (s *ProcessingTestSuite) TestResizeToFit() {
imgdata := s.openFile("test2.jpg")
po := options.NewProcessingOptions()
po := s.pof.NewProcessingOptions()
po.ResizingType = options.ResizeFit
testCases := []struct {
@@ -89,7 +103,7 @@ func (s *ProcessingTestSuite) TestResizeToFit() {
po.Width = tc.width
po.Height = tc.height
result, err := ProcessImage(context.Background(), imgdata, po, nil)
result, err := ProcessImage(context.Background(), imgdata, po, nil, s.pof)
s.Require().NoError(err)
s.Require().NotNil(result)
@@ -101,7 +115,7 @@ func (s *ProcessingTestSuite) TestResizeToFit() {
func (s *ProcessingTestSuite) TestResizeToFitEnlarge() {
imgdata := s.openFile("test2.jpg")
po := options.NewProcessingOptions()
po := s.pof.NewProcessingOptions()
po.ResizingType = options.ResizeFit
po.Enlarge = true
@@ -128,7 +142,7 @@ func (s *ProcessingTestSuite) TestResizeToFitEnlarge() {
po.Width = tc.width
po.Height = tc.height
result, err := ProcessImage(context.Background(), imgdata, po, nil)
result, err := ProcessImage(context.Background(), imgdata, po, nil, s.pof)
s.Require().NoError(err)
s.Require().NotNil(result)
@@ -140,7 +154,7 @@ func (s *ProcessingTestSuite) TestResizeToFitEnlarge() {
func (s *ProcessingTestSuite) TestResizeToFitExtend() {
imgdata := s.openFile("test2.jpg")
po := options.NewProcessingOptions()
po := s.pof.NewProcessingOptions()
po.ResizingType = options.ResizeFit
po.Extend = options.ExtendOptions{
Enabled: true,
@@ -172,7 +186,7 @@ func (s *ProcessingTestSuite) TestResizeToFitExtend() {
po.Width = tc.width
po.Height = tc.height
result, err := ProcessImage(context.Background(), imgdata, po, nil)
result, err := ProcessImage(context.Background(), imgdata, po, nil, s.pof)
s.Require().NoError(err)
s.Require().NotNil(result)
@@ -184,7 +198,7 @@ func (s *ProcessingTestSuite) TestResizeToFitExtend() {
func (s *ProcessingTestSuite) TestResizeToFitExtendAR() {
imgdata := s.openFile("test2.jpg")
po := options.NewProcessingOptions()
po := s.pof.NewProcessingOptions()
po.ResizingType = options.ResizeFit
po.ExtendAspectRatio = options.ExtendOptions{
Enabled: true,
@@ -216,7 +230,7 @@ func (s *ProcessingTestSuite) TestResizeToFitExtendAR() {
po.Width = tc.width
po.Height = tc.height
result, err := ProcessImage(context.Background(), imgdata, po, nil)
result, err := ProcessImage(context.Background(), imgdata, po, nil, s.pof)
s.Require().NoError(err)
s.Require().NotNil(result)
@@ -228,7 +242,7 @@ func (s *ProcessingTestSuite) TestResizeToFitExtendAR() {
func (s *ProcessingTestSuite) TestResizeToFill() {
imgdata := s.openFile("test2.jpg")
po := options.NewProcessingOptions()
po := s.pof.NewProcessingOptions()
po.ResizingType = options.ResizeFill
testCases := []struct {
@@ -254,7 +268,7 @@ func (s *ProcessingTestSuite) TestResizeToFill() {
po.Width = tc.width
po.Height = tc.height
result, err := ProcessImage(context.Background(), imgdata, po, nil)
result, err := ProcessImage(context.Background(), imgdata, po, nil, s.pof)
s.Require().NoError(err)
s.Require().NotNil(result)
@@ -266,7 +280,7 @@ func (s *ProcessingTestSuite) TestResizeToFill() {
func (s *ProcessingTestSuite) TestResizeToFillEnlarge() {
imgdata := s.openFile("test2.jpg")
po := options.NewProcessingOptions()
po := s.pof.NewProcessingOptions()
po.ResizingType = options.ResizeFill
po.Enlarge = true
@@ -293,7 +307,7 @@ func (s *ProcessingTestSuite) TestResizeToFillEnlarge() {
po.Width = tc.width
po.Height = tc.height
result, err := ProcessImage(context.Background(), imgdata, po, nil)
result, err := ProcessImage(context.Background(), imgdata, po, nil, s.pof)
s.Require().NoError(err)
s.Require().NotNil(result)
@@ -305,7 +319,7 @@ func (s *ProcessingTestSuite) TestResizeToFillEnlarge() {
func (s *ProcessingTestSuite) TestResizeToFillExtend() {
imgdata := s.openFile("test2.jpg")
po := options.NewProcessingOptions()
po := s.pof.NewProcessingOptions()
po.ResizingType = options.ResizeFill
po.Extend = options.ExtendOptions{
Enabled: true,
@@ -339,7 +353,7 @@ func (s *ProcessingTestSuite) TestResizeToFillExtend() {
po.Width = tc.width
po.Height = tc.height
result, err := ProcessImage(context.Background(), imgdata, po, nil)
result, err := ProcessImage(context.Background(), imgdata, po, nil, s.pof)
s.Require().NoError(err)
s.Require().NotNil(result)
@@ -351,7 +365,7 @@ func (s *ProcessingTestSuite) TestResizeToFillExtend() {
func (s *ProcessingTestSuite) TestResizeToFillExtendAR() {
imgdata := s.openFile("test2.jpg")
po := options.NewProcessingOptions()
po := s.pof.NewProcessingOptions()
po.ResizingType = options.ResizeFill
po.ExtendAspectRatio = options.ExtendOptions{
Enabled: true,
@@ -385,7 +399,7 @@ func (s *ProcessingTestSuite) TestResizeToFillExtendAR() {
po.Width = tc.width
po.Height = tc.height
result, err := ProcessImage(context.Background(), imgdata, po, nil)
result, err := ProcessImage(context.Background(), imgdata, po, nil, s.pof)
s.Require().NoError(err)
s.Require().NotNil(result)
@@ -397,7 +411,7 @@ func (s *ProcessingTestSuite) TestResizeToFillExtendAR() {
func (s *ProcessingTestSuite) TestResizeToFillDown() {
imgdata := s.openFile("test2.jpg")
po := options.NewProcessingOptions()
po := s.pof.NewProcessingOptions()
po.ResizingType = options.ResizeFillDown
testCases := []struct {
@@ -423,7 +437,7 @@ func (s *ProcessingTestSuite) TestResizeToFillDown() {
po.Width = tc.width
po.Height = tc.height
result, err := ProcessImage(context.Background(), imgdata, po, nil)
result, err := ProcessImage(context.Background(), imgdata, po, nil, s.pof)
s.Require().NoError(err)
s.Require().NotNil(result)
@@ -435,7 +449,7 @@ func (s *ProcessingTestSuite) TestResizeToFillDown() {
func (s *ProcessingTestSuite) TestResizeToFillDownEnlarge() {
imgdata := s.openFile("test2.jpg")
po := options.NewProcessingOptions()
po := s.pof.NewProcessingOptions()
po.ResizingType = options.ResizeFillDown
po.Enlarge = true
@@ -462,7 +476,7 @@ func (s *ProcessingTestSuite) TestResizeToFillDownEnlarge() {
po.Width = tc.width
po.Height = tc.height
result, err := ProcessImage(context.Background(), imgdata, po, nil)
result, err := ProcessImage(context.Background(), imgdata, po, nil, s.pof)
s.Require().NoError(err)
s.Require().NotNil(result)
@@ -474,7 +488,7 @@ func (s *ProcessingTestSuite) TestResizeToFillDownEnlarge() {
func (s *ProcessingTestSuite) TestResizeToFillDownExtend() {
imgdata := s.openFile("test2.jpg")
po := options.NewProcessingOptions()
po := s.pof.NewProcessingOptions()
po.ResizingType = options.ResizeFillDown
po.Extend = options.ExtendOptions{
Enabled: true,
@@ -508,7 +522,7 @@ func (s *ProcessingTestSuite) TestResizeToFillDownExtend() {
po.Width = tc.width
po.Height = tc.height
result, err := ProcessImage(context.Background(), imgdata, po, nil)
result, err := ProcessImage(context.Background(), imgdata, po, nil, s.pof)
s.Require().NoError(err)
s.Require().NotNil(result)
@@ -520,7 +534,7 @@ func (s *ProcessingTestSuite) TestResizeToFillDownExtend() {
func (s *ProcessingTestSuite) TestResizeToFillDownExtendAR() {
imgdata := s.openFile("test2.jpg")
po := options.NewProcessingOptions()
po := s.pof.NewProcessingOptions()
po.ResizingType = options.ResizeFillDown
po.ExtendAspectRatio = options.ExtendOptions{
Enabled: true,
@@ -552,7 +566,7 @@ func (s *ProcessingTestSuite) TestResizeToFillDownExtendAR() {
po.Width = tc.width
po.Height = tc.height
result, err := ProcessImage(context.Background(), imgdata, po, nil)
result, err := ProcessImage(context.Background(), imgdata, po, nil, s.pof)
s.Require().NoError(err)
s.Require().NotNil(result)
@@ -564,7 +578,7 @@ func (s *ProcessingTestSuite) TestResizeToFillDownExtendAR() {
func (s *ProcessingTestSuite) TestResultSizeLimit() {
imgdata := s.openFile("test2.jpg")
po := options.NewProcessingOptions()
po := s.pof.NewProcessingOptions()
testCases := []struct {
limit int
@@ -981,7 +995,7 @@ func (s *ProcessingTestSuite) TestResultSizeLimit() {
po.Rotate = tc.rotate
po.Padding = tc.padding
result, err := ProcessImage(context.Background(), imgdata, po, nil)
result, err := ProcessImage(context.Background(), imgdata, po, nil, s.pof)
s.Require().NoError(err)
s.Require().NotNil(result)
@@ -992,11 +1006,11 @@ func (s *ProcessingTestSuite) TestResultSizeLimit() {
}
func (s *ProcessingTestSuite) TestImageResolutionTooLarge() {
po := options.NewProcessingOptions()
po := s.pof.NewProcessingOptions()
po.SecurityOptions.MaxSrcResolution = 1
imgdata := s.openFile("test2.jpg")
_, err := ProcessImage(context.Background(), imgdata, po, nil)
_, err := ProcessImage(context.Background(), imgdata, po, nil, s.pof)
s.Require().Error(err)
s.Require().Equal(422, ierrors.Wrap(err, 0).StatusCode())

View File

@@ -22,12 +22,12 @@ var watermarkPipeline = pipeline{
padding,
}
func prepareWatermark(wm *vips.Image, wmData imagedata.ImageData, opts *options.WatermarkOptions, imgWidth, imgHeight int, offsetScale float64, framesCount int) error {
func prepareWatermark(wm *vips.Image, wmData imagedata.ImageData, opts *options.WatermarkOptions, pof *options.Factory, imgWidth, imgHeight int, offsetScale float64, framesCount int) error {
if err := wm.Load(wmData, 1, 1.0, 1); err != nil {
return err
}
po := options.NewProcessingOptions()
po := pof.NewProcessingOptions()
po.ResizingType = options.ResizeFit
po.Dpr = 1
po.Enlarge = true
@@ -60,7 +60,7 @@ func prepareWatermark(wm *vips.Image, wmData imagedata.ImageData, opts *options.
po.Padding.Bottom = offY - po.Padding.Top
}
if err := watermarkPipeline.Run(context.Background(), wm, po, wmData, nil); err != nil {
if err := watermarkPipeline.Run(context.Background(), wm, po, wmData, nil, pof); err != nil {
return err
}
@@ -84,6 +84,7 @@ func applyWatermark(
ctx context.Context,
img *vips.Image,
watermark auximageprovider.Provider,
pof *options.Factory,
po *options.ProcessingOptions,
offsetScale float64,
framesCount int,
@@ -110,7 +111,7 @@ func applyWatermark(
height := img.Height()
frameHeight := height / framesCount
if err := prepareWatermark(wm, wmData, &opts, width, frameHeight, offsetScale, framesCount); err != nil {
if err := prepareWatermark(wm, wmData, &opts, pof, width, frameHeight, offsetScale, framesCount); err != nil {
return err
}
@@ -193,5 +194,5 @@ func watermark(
return nil
}
return applyWatermark(pctx.ctx, img, pctx.watermarkProvider, po, pctx.dprScale, 1)
return applyWatermark(pctx.ctx, img, pctx.watermarkProvider, pctx.processingOptionsFactory, po, pctx.dprScale, 1)
}