Add IMGPROXY_MAX_RESULT_DIMENSION config and max_result_dimension processing option

This commit is contained in:
DarthSim
2025-06-06 19:28:41 +03:00
parent 810cad5d26
commit a5a587eb39
6 changed files with 502 additions and 0 deletions

View File

@@ -2,6 +2,7 @@
## [Unreleased]
### Added
- Add [IMGPROXY_MAX_RESULT_DIMENSION](https://docs.imgproxy.net/latest/configuration/options#IMGPROXY_MAX_RESULT_DIMENSION) config and [max_result_dimension](https://docs.imgproxy.net/latest/usage/processing#max-result-dimension) processing option.
- Add `imgproxy.source_image_origin` attribute to New Relic, DataDog, and OpenTelemetry traces.
- Add `imgproxy.source_image_url` and `imgproxy.source_image_origin` attributes to `downloading_image` spans in New Relic, DataDog, and OpenTelemetry traces.
- Add `imgproxy.processing_options` attribute to `processing_image` spans in New Relic, DataDog, and OpenTelemetry traces.

View File

@@ -47,6 +47,7 @@ var (
MaxRedirects int
PngUnlimited bool
SvgUnlimited bool
MaxResultDimension int
AllowSecurityOptions bool
JpegProgressive bool
@@ -252,6 +253,7 @@ func Reset() {
MaxRedirects = 10
PngUnlimited = false
SvgUnlimited = false
MaxResultDimension = 0
AllowSecurityOptions = false
JpegProgressive = false
@@ -483,6 +485,8 @@ func Configure() error {
configurators.Bool(&PngUnlimited, "IMGPROXY_PNG_UNLIMITED")
configurators.Bool(&SvgUnlimited, "IMGPROXY_SVG_UNLIMITED")
configurators.Int(&MaxResultDimension, "IMGPROXY_MAX_RESULT_DIMENSION")
configurators.Bool(&AllowSecurityOptions, "IMGPROXY_ALLOW_SECURITY_OPTIONS")
configurators.Bool(&JpegProgressive, "IMGPROXY_JPEG_PROGRESSIVE")

View File

@@ -966,6 +966,24 @@ func applyMaxAnimationFrameResolutionOption(po *ProcessingOptions, args []string
return nil
}
func applyMaxResultDimensionOption(po *ProcessingOptions, args []string) error {
if err := security.IsSecurityOptionsAllowed(); err != nil {
return err
}
if len(args) > 1 {
return newOptionArgumentError("Invalid max_result_dimension arguments: %v", args)
}
if x, err := strconv.Atoi(args[0]); err == nil {
po.SecurityOptions.MaxResultDimension = x
} else {
return newOptionArgumentError("Invalid max_result_dimension: %s", args[0])
}
return nil
}
func applyURLOption(po *ProcessingOptions, name string, args []string, usedPresets ...string) error {
switch name {
case "resize", "rs":
@@ -1056,6 +1074,8 @@ func applyURLOption(po *ProcessingOptions, name string, args []string, usedPrese
return applyMaxAnimationFramesOption(po, args)
case "max_animation_frame_resolution", "mafr":
return applyMaxAnimationFrameResolutionOption(po, args)
case "max_result_dimension", "mrd":
return applyMaxResultDimensionOption(po, args)
}
return newUnknownOptionError("processing", name)

View File

@@ -204,6 +204,50 @@ func (pctx *pipelineContext) calcSizes(widthToScale, heightToScale int, po *opti
}
}
func (pctx *pipelineContext) 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)
if po.Extend.Enabled {
outWidth = imath.Max(outWidth, pctx.targetWidth)
outHeight = imath.Max(outHeight, pctx.targetHeight)
} else if po.ExtendAspectRatio.Enabled {
outWidth = imath.Max(outWidth, pctx.extendAspectRatioWidth)
outHeight = imath.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)
}
if maxresultDim > 0 && (outWidth > maxresultDim || outHeight > maxresultDim) {
downScale := float64(maxresultDim) / float64(imath.Max(outWidth, outHeight))
pctx.wscale *= downScale
pctx.hscale *= downScale
// Prevent scaling below 1px
if minWScale := 1.0 / float64(widthToScale); pctx.wscale < minWScale {
pctx.wscale = minWScale
}
if minHScale := 1.0 / float64(heightToScale); pctx.hscale < minHScale {
pctx.hscale = minHScale
}
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 {
@@ -233,5 +277,7 @@ func prepare(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptio
pctx.calcSizes(widthToScale, heightToScale, po)
pctx.limitScale(widthToScale, heightToScale, po)
return nil
}

View File

@@ -560,6 +560,435 @@ func (s *ProcessingTestSuite) TestResizeToFillDownExtendAR() {
}
}
func (s *ProcessingTestSuite) TestResultSizeLimit() {
imgdata := s.openFile("test2.jpg")
po := options.NewProcessingOptions()
testCases := []struct {
limit int
width int
height int
resizingType options.ResizeType
enlarge bool
extend bool
extendAR bool
padding options.PaddingOptions
rotate int
outWidth int
outHeight int
}{
{
limit: 1000,
width: 100,
height: 100,
resizingType: options.ResizeFit,
outWidth: 100,
outHeight: 50,
},
{
limit: 50,
width: 100,
height: 100,
resizingType: options.ResizeFit,
outWidth: 50,
outHeight: 25,
},
{
limit: 50,
width: 0,
height: 0,
resizingType: options.ResizeFit,
outWidth: 50,
outHeight: 25,
},
{
limit: 100,
width: 0,
height: 100,
resizingType: options.ResizeFit,
outWidth: 100,
outHeight: 50,
},
{
limit: 50,
width: 150,
height: 0,
resizingType: options.ResizeFit,
outWidth: 50,
outHeight: 25,
},
{
limit: 100,
width: 1000,
height: 1000,
resizingType: options.ResizeFit,
outWidth: 100,
outHeight: 50,
},
{
limit: 100,
width: 1000,
height: 1000,
resizingType: options.ResizeFit,
enlarge: true,
outWidth: 100,
outHeight: 50,
},
{
limit: 100,
width: 1000,
height: 2000,
resizingType: options.ResizeFit,
extend: true,
outWidth: 50,
outHeight: 100,
},
{
limit: 100,
width: 1000,
height: 2000,
resizingType: options.ResizeFit,
extendAR: true,
outWidth: 50,
outHeight: 100,
},
{
limit: 100,
width: 100,
height: 150,
resizingType: options.ResizeFit,
rotate: 90,
outWidth: 50,
outHeight: 100,
},
{
limit: 100,
width: 0,
height: 0,
resizingType: options.ResizeFit,
rotate: 90,
outWidth: 50,
outHeight: 100,
},
{
limit: 200,
width: 100,
height: 100,
resizingType: options.ResizeFit,
padding: options.PaddingOptions{
Enabled: true,
Top: 100,
Right: 200,
Bottom: 300,
Left: 400,
},
outWidth: 200,
outHeight: 129,
},
{
limit: 1000,
width: 100,
height: 100,
resizingType: options.ResizeFill,
outWidth: 100,
outHeight: 100,
},
{
limit: 50,
width: 100,
height: 100,
resizingType: options.ResizeFill,
outWidth: 50,
outHeight: 50,
},
{
limit: 50,
width: 1000,
height: 50,
resizingType: options.ResizeFill,
outWidth: 50,
outHeight: 13,
},
{
limit: 50,
width: 100,
height: 1000,
resizingType: options.ResizeFill,
outWidth: 50,
outHeight: 50,
},
{
limit: 50,
width: 0,
height: 0,
resizingType: options.ResizeFill,
outWidth: 50,
outHeight: 25,
},
{
limit: 100,
width: 0,
height: 100,
resizingType: options.ResizeFill,
outWidth: 100,
outHeight: 50,
},
{
limit: 50,
width: 150,
height: 0,
resizingType: options.ResizeFill,
outWidth: 50,
outHeight: 25,
},
{
limit: 100,
width: 1000,
height: 1000,
resizingType: options.ResizeFill,
outWidth: 100,
outHeight: 50,
},
{
limit: 100,
width: 1000,
height: 1000,
resizingType: options.ResizeFill,
enlarge: true,
outWidth: 100,
outHeight: 100,
},
{
limit: 100,
width: 1000,
height: 2000,
resizingType: options.ResizeFill,
extend: true,
outWidth: 50,
outHeight: 100,
},
{
limit: 100,
width: 1000,
height: 2000,
resizingType: options.ResizeFill,
extendAR: true,
outWidth: 50,
outHeight: 100,
},
{
limit: 100,
width: 100,
height: 150,
resizingType: options.ResizeFill,
rotate: 90,
outWidth: 67,
outHeight: 100,
},
{
limit: 100,
width: 0,
height: 0,
resizingType: options.ResizeFill,
rotate: 90,
outWidth: 50,
outHeight: 100,
},
{
limit: 200,
width: 100,
height: 100,
resizingType: options.ResizeFill,
padding: options.PaddingOptions{
Enabled: true,
Top: 100,
Right: 200,
Bottom: 300,
Left: 400,
},
outWidth: 200,
outHeight: 144,
},
{
limit: 1000,
width: 100,
height: 100,
resizingType: options.ResizeFillDown,
outWidth: 100,
outHeight: 100,
},
{
limit: 50,
width: 100,
height: 100,
resizingType: options.ResizeFillDown,
outWidth: 50,
outHeight: 50,
},
{
limit: 50,
width: 1000,
height: 50,
resizingType: options.ResizeFillDown,
outWidth: 50,
outHeight: 3,
},
{
limit: 50,
width: 100,
height: 1000,
resizingType: options.ResizeFillDown,
outWidth: 5,
outHeight: 50,
},
{
limit: 50,
width: 0,
height: 0,
resizingType: options.ResizeFillDown,
outWidth: 50,
outHeight: 25,
},
{
limit: 100,
width: 0,
height: 100,
resizingType: options.ResizeFillDown,
outWidth: 100,
outHeight: 50,
},
{
limit: 50,
width: 150,
height: 0,
resizingType: options.ResizeFillDown,
outWidth: 50,
outHeight: 25,
},
{
limit: 100,
width: 1000,
height: 1000,
resizingType: options.ResizeFillDown,
outWidth: 100,
outHeight: 100,
},
{
limit: 100,
width: 1000,
height: 1000,
resizingType: options.ResizeFillDown,
enlarge: true,
outWidth: 100,
outHeight: 100,
},
{
limit: 100,
width: 1000,
height: 2000,
resizingType: options.ResizeFillDown,
extend: true,
outWidth: 50,
outHeight: 100,
},
{
limit: 100,
width: 1000,
height: 2000,
resizingType: options.ResizeFillDown,
extendAR: true,
outWidth: 50,
outHeight: 100,
},
{
limit: 100,
width: 1000,
height: 1500,
resizingType: options.ResizeFillDown,
rotate: 90,
outWidth: 67,
outHeight: 100,
},
{
limit: 100,
width: 0,
height: 0,
resizingType: options.ResizeFillDown,
rotate: 90,
outWidth: 50,
outHeight: 100,
},
{
limit: 200,
width: 100,
height: 100,
resizingType: options.ResizeFillDown,
padding: options.PaddingOptions{
Enabled: true,
Top: 100,
Right: 200,
Bottom: 300,
Left: 400,
},
outWidth: 200,
outHeight: 144,
},
{
limit: 200,
width: 1000,
height: 1000,
resizingType: options.ResizeFillDown,
padding: options.PaddingOptions{
Enabled: true,
Top: 100,
Right: 200,
Bottom: 300,
Left: 400,
},
outWidth: 200,
outHeight: 144,
},
}
for _, tc := range testCases {
name := fmt.Sprintf("%s_%dx%d_limit_%d", tc.resizingType, tc.width, tc.height, tc.limit)
if tc.enlarge {
name += "_enlarge"
}
if tc.extend {
name += "_extend"
}
if tc.extendAR {
name += "_extendAR"
}
if tc.rotate != 0 {
name += fmt.Sprintf("_rot_%d", tc.rotate)
}
if tc.padding.Enabled {
name += fmt.Sprintf("_padding_%dx%dx%dx%d", tc.padding.Top, tc.padding.Right, tc.padding.Bottom, tc.padding.Left)
}
s.Run(name, func() {
po.SecurityOptions.MaxResultDimension = tc.limit
po.Width = tc.width
po.Height = tc.height
po.ResizingType = tc.resizingType
po.Enlarge = tc.enlarge
po.Extend.Enabled = tc.extend
po.ExtendAspectRatio.Enabled = tc.extendAR
po.Rotate = tc.rotate
po.Padding = tc.padding
outImgdata, err := ProcessImage(context.Background(), imgdata, po)
s.Require().NoError(err)
s.Require().NotNil(outImgdata)
s.checkSize(outImgdata, tc.outWidth, tc.outHeight)
})
}
}
func TestProcessing(t *testing.T) {
suite.Run(t, new(ProcessingTestSuite))
}

View File

@@ -9,6 +9,7 @@ type Options struct {
MaxSrcFileSize int
MaxAnimationFrames int
MaxAnimationFrameResolution int
MaxResultDimension int
}
func DefaultOptions() Options {
@@ -17,6 +18,7 @@ func DefaultOptions() Options {
MaxSrcFileSize: config.MaxSrcFileSize,
MaxAnimationFrames: config.MaxAnimationFrames,
MaxAnimationFrameResolution: config.MaxAnimationFrameResolution,
MaxResultDimension: config.MaxResultDimension,
}
}