IMG-49: Get rid of global watermark image and fetcher (#1515)

* Get rid of global watermark image

* imagedata.Factory, removed global Fetcher
This commit is contained in:
Victor Sokolov
2025-09-05 16:22:57 +02:00
committed by GitHub
parent 6f11d950fb
commit 37a6ea2bf7
20 changed files with 270 additions and 197 deletions

View File

@@ -3,6 +3,7 @@ package processing
import (
"context"
"github.com/imgproxy/imgproxy/v3/auximageprovider"
"github.com/imgproxy/imgproxy/v3/imagedata"
"github.com/imgproxy/imgproxy/v3/imagetype"
"github.com/imgproxy/imgproxy/v3/options"
@@ -15,6 +16,9 @@ type pipelineContext struct {
imgtype imagetype.Type
// The watermark image provider, if any watermarking is to be done.
watermarkProvider auximageprovider.Provider
trimmed bool
srcWidth int
@@ -67,7 +71,13 @@ type pipelineContext struct {
type pipelineStep func(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptions, imgdata imagedata.ImageData) error
type pipeline []pipelineStep
func (p pipeline) Run(ctx context.Context, img *vips.Image, po *options.ProcessingOptions, imgdata imagedata.ImageData) error {
func (p pipeline) Run(
ctx context.Context,
img *vips.Image,
po *options.ProcessingOptions,
imgdata imagedata.ImageData,
watermark auximageprovider.Provider,
) error {
pctx := pipelineContext{
ctx: ctx,
@@ -77,7 +87,8 @@ func (p pipeline) Run(ctx context.Context, img *vips.Image, po *options.Processi
dprScale: 1.0,
vectorBaseScale: 1.0,
cropGravity: po.Crop.Gravity,
cropGravity: po.Crop.Gravity,
watermarkProvider: watermark,
}
if pctx.cropGravity.Type == options.GravityUnknown {

View File

@@ -8,6 +8,7 @@ import (
log "github.com/sirupsen/logrus"
"github.com/imgproxy/imgproxy/v3/auximageprovider"
"github.com/imgproxy/imgproxy/v3/config"
"github.com/imgproxy/imgproxy/v3/imagedata"
"github.com/imgproxy/imgproxy/v3/imagetype"
@@ -83,6 +84,8 @@ func ProcessImage(
ctx context.Context,
imgdata imagedata.ImageData,
po *options.ProcessingOptions,
watermarkProvider auximageprovider.Provider,
idf *imagedata.Factory,
) (*Result, error) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
@@ -136,12 +139,12 @@ func ProcessImage(
}
// Transform the image (resize, crop, etc)
if err = transformImage(ctx, img, po, imgdata, animated); err != nil {
if err = transformImage(ctx, img, po, imgdata, animated, watermarkProvider); err != nil {
return nil, err
}
// Finalize the image (colorspace conversion, metadata stripping, etc)
if err = finalizePipeline.Run(ctx, img, po, imgdata); err != nil {
if err = finalizePipeline.Run(ctx, img, po, imgdata, watermarkProvider); err != nil {
return nil, err
}
@@ -385,18 +388,20 @@ func transformImage(
po *options.ProcessingOptions,
imgdata imagedata.ImageData,
asAnimated bool,
watermark auximageprovider.Provider,
) error {
if asAnimated {
return transformAnimated(ctx, img, po)
return transformAnimated(ctx, img, po, watermark)
}
return mainPipeline.Run(ctx, img, po, imgdata)
return mainPipeline.Run(ctx, img, po, imgdata, watermark)
}
func transformAnimated(
ctx context.Context,
img *vips.Image,
po *options.ProcessingOptions,
watermark auximageprovider.Provider,
) error {
if po.Trim.Enabled {
log.Warning("Trim is not supported for animated images")
@@ -450,7 +455,8 @@ func transformAnimated(
// Transform the frame using the main pipeline.
// We don't provide imgdata here to prevent scale-on-load.
if err = mainPipeline.Run(ctx, frame, po, nil); err != nil {
// 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 {
return err
}
@@ -474,7 +480,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 && imagedata.Watermark != nil {
if watermarkEnabled && watermark != 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)
@@ -483,7 +489,7 @@ func transformAnimated(
}
if err = applyWatermark(
img, imagedata.Watermark, &po.Watermark, dprScale, framesCount,
img, watermark, &po.Watermark, dprScale, framesCount,
); err != nil {
return err
}

View File

@@ -12,14 +12,17 @@ import (
"github.com/stretchr/testify/suite"
"github.com/imgproxy/imgproxy/v3/config"
"github.com/imgproxy/imgproxy/v3/fetcher"
"github.com/imgproxy/imgproxy/v3/ierrors"
"github.com/imgproxy/imgproxy/v3/imagedata"
"github.com/imgproxy/imgproxy/v3/options"
"github.com/imgproxy/imgproxy/v3/transport"
"github.com/imgproxy/imgproxy/v3/vips"
)
type ProcessingTestSuite struct {
suite.Suite
idf *imagedata.Factory
}
func (s *ProcessingTestSuite) SetupSuite() {
@@ -30,10 +33,19 @@ func (s *ProcessingTestSuite) SetupSuite() {
config.MaxAnimationFrames = 100
config.MaxAnimationFrameResolution = 10 * 1024 * 1024
s.Require().NoError(imagedata.Init())
s.Require().NoError(vips.Init())
logrus.SetOutput(io.Discard)
trc := transport.NewDefaultConfig()
tr, err := transport.New(trc)
s.Require().NoError(err)
fc := fetcher.NewDefaultConfig()
f, err := fetcher.New(tr, fc)
s.Require().NoError(err)
s.idf = imagedata.NewFactory(f)
}
func (s *ProcessingTestSuite) openFile(name string) imagedata.ImageData {
@@ -41,7 +53,7 @@ func (s *ProcessingTestSuite) openFile(name string) imagedata.ImageData {
s.Require().NoError(err)
path := filepath.Join(wd, "..", "testdata", name)
imagedata, err := imagedata.NewFromPath(path)
imagedata, err := s.idf.NewFromPath(path)
s.Require().NoError(err)
return imagedata
@@ -82,7 +94,7 @@ func (s *ProcessingTestSuite) TestResizeToFit() {
po.Width = tc.width
po.Height = tc.height
result, err := ProcessImage(context.Background(), imgdata, po)
result, err := ProcessImage(context.Background(), imgdata, po, nil, s.idf)
s.Require().NoError(err)
s.Require().NotNil(result)
@@ -121,7 +133,7 @@ func (s *ProcessingTestSuite) TestResizeToFitEnlarge() {
po.Width = tc.width
po.Height = tc.height
result, err := ProcessImage(context.Background(), imgdata, po)
result, err := ProcessImage(context.Background(), imgdata, po, nil, s.idf)
s.Require().NoError(err)
s.Require().NotNil(result)
@@ -165,7 +177,7 @@ func (s *ProcessingTestSuite) TestResizeToFitExtend() {
po.Width = tc.width
po.Height = tc.height
result, err := ProcessImage(context.Background(), imgdata, po)
result, err := ProcessImage(context.Background(), imgdata, po, nil, s.idf)
s.Require().NoError(err)
s.Require().NotNil(result)
@@ -209,7 +221,7 @@ func (s *ProcessingTestSuite) TestResizeToFitExtendAR() {
po.Width = tc.width
po.Height = tc.height
result, err := ProcessImage(context.Background(), imgdata, po)
result, err := ProcessImage(context.Background(), imgdata, po, nil, s.idf)
s.Require().NoError(err)
s.Require().NotNil(result)
@@ -247,7 +259,7 @@ func (s *ProcessingTestSuite) TestResizeToFill() {
po.Width = tc.width
po.Height = tc.height
result, err := ProcessImage(context.Background(), imgdata, po)
result, err := ProcessImage(context.Background(), imgdata, po, nil, s.idf)
s.Require().NoError(err)
s.Require().NotNil(result)
@@ -286,7 +298,7 @@ func (s *ProcessingTestSuite) TestResizeToFillEnlarge() {
po.Width = tc.width
po.Height = tc.height
result, err := ProcessImage(context.Background(), imgdata, po)
result, err := ProcessImage(context.Background(), imgdata, po, nil, s.idf)
s.Require().NoError(err)
s.Require().NotNil(result)
@@ -332,7 +344,7 @@ func (s *ProcessingTestSuite) TestResizeToFillExtend() {
po.Width = tc.width
po.Height = tc.height
result, err := ProcessImage(context.Background(), imgdata, po)
result, err := ProcessImage(context.Background(), imgdata, po, nil, s.idf)
s.Require().NoError(err)
s.Require().NotNil(result)
@@ -378,7 +390,7 @@ func (s *ProcessingTestSuite) TestResizeToFillExtendAR() {
po.Width = tc.width
po.Height = tc.height
result, err := ProcessImage(context.Background(), imgdata, po)
result, err := ProcessImage(context.Background(), imgdata, po, nil, s.idf)
s.Require().NoError(err)
s.Require().NotNil(result)
@@ -416,7 +428,7 @@ func (s *ProcessingTestSuite) TestResizeToFillDown() {
po.Width = tc.width
po.Height = tc.height
result, err := ProcessImage(context.Background(), imgdata, po)
result, err := ProcessImage(context.Background(), imgdata, po, nil, s.idf)
s.Require().NoError(err)
s.Require().NotNil(result)
@@ -455,7 +467,7 @@ func (s *ProcessingTestSuite) TestResizeToFillDownEnlarge() {
po.Width = tc.width
po.Height = tc.height
result, err := ProcessImage(context.Background(), imgdata, po)
result, err := ProcessImage(context.Background(), imgdata, po, nil, s.idf)
s.Require().NoError(err)
s.Require().NotNil(result)
@@ -501,7 +513,7 @@ func (s *ProcessingTestSuite) TestResizeToFillDownExtend() {
po.Width = tc.width
po.Height = tc.height
result, err := ProcessImage(context.Background(), imgdata, po)
result, err := ProcessImage(context.Background(), imgdata, po, nil, s.idf)
s.Require().NoError(err)
s.Require().NotNil(result)
@@ -545,7 +557,7 @@ func (s *ProcessingTestSuite) TestResizeToFillDownExtendAR() {
po.Width = tc.width
po.Height = tc.height
result, err := ProcessImage(context.Background(), imgdata, po)
result, err := ProcessImage(context.Background(), imgdata, po, nil, s.idf)
s.Require().NoError(err)
s.Require().NotNil(result)
@@ -974,7 +986,7 @@ func (s *ProcessingTestSuite) TestResultSizeLimit() {
po.Rotate = tc.rotate
po.Padding = tc.padding
result, err := ProcessImage(context.Background(), imgdata, po)
result, err := ProcessImage(context.Background(), imgdata, po, nil, s.idf)
s.Require().NoError(err)
s.Require().NotNil(result)
@@ -989,7 +1001,7 @@ func (s *ProcessingTestSuite) TestImageResolutionTooLarge() {
po.SecurityOptions.MaxSrcResolution = 1
imgdata := s.openFile("test2.jpg")
_, err := ProcessImage(context.Background(), imgdata, po)
_, err := ProcessImage(context.Background(), imgdata, po, nil, s.idf)
s.Require().Error(err)
s.Require().Equal(422, ierrors.Wrap(err, 0).StatusCode())

View File

@@ -4,6 +4,7 @@ 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"
@@ -59,17 +60,14 @@ 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); err != nil {
if err := watermarkPipeline.Run(context.Background(), wm, po, wmData, nil); err != nil {
return err
}
if opts.ShouldReplicate() || framesCount > 1 {
// We need to copy image if we're going to replicate.
// Replication requires image to be read several times, and this requires
// random access to pixels
if err := wm.CopyMemory(); err != nil {
return err
}
// We need to copy the image to ensure that it is in memory since we will
// close it after watermark processing is done.
if err := wm.CopyMemory(); err != nil {
return err
}
if opts.ShouldReplicate() {
@@ -82,7 +80,20 @@ func prepareWatermark(wm *vips.Image, wmData imagedata.ImageData, opts *options.
return wm.StripAll()
}
func applyWatermark(img *vips.Image, wmData imagedata.ImageData, opts *options.WatermarkOptions, offsetScale float64, framesCount int) error {
func applyWatermark(img *vips.Image, watermark auximageprovider.Provider, opts *options.WatermarkOptions, offsetScale float64, framesCount int) error {
if watermark == nil {
return nil
}
wmData, _, err := watermark.Get(context.Background(), nil)
if err != nil {
return err
}
if wmData == nil {
return nil
}
defer wmData.Close()
wm := new(vips.Image)
defer wm.Clear()
@@ -164,9 +175,9 @@ func applyWatermark(img *vips.Image, wmData imagedata.ImageData, opts *options.W
}
func watermark(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptions, imgdata imagedata.ImageData) error {
if !po.Watermark.Enabled || imagedata.Watermark == nil {
if !po.Watermark.Enabled || pctx.watermarkProvider == nil {
return nil
}
return applyWatermark(img, imagedata.Watermark, &po.Watermark, pctx.dprScale, 1)
return applyWatermark(img, pctx.watermarkProvider, &po.Watermark, pctx.dprScale, 1)
}