From 810cad5d263e0db6f92b869b7a9cc94ae2a440cf Mon Sep 17 00:00:00 2001 From: DarthSim Date: Thu, 5 Jun 2025 20:19:20 +0300 Subject: [PATCH] Calculate all sizes at the start of processing pipeline --- processing/crop.go | 20 +- processing/extend.go | 62 ++-- processing/pipeline.go | 28 ++ processing/prepare.go | 85 ++++- processing/processing_test.go | 565 ++++++++++++++++++++++++++++++++++ processing/result_size.go | 13 - testdata/test2.jpg | Bin 0 -> 649 bytes 7 files changed, 686 insertions(+), 87 deletions(-) create mode 100644 processing/processing_test.go delete mode 100644 processing/result_size.go create mode 100644 testdata/test2.jpg diff --git a/processing/crop.go b/processing/crop.go index 8934a6e4..74c82fe6 100644 --- a/processing/crop.go +++ b/processing/crop.go @@ -48,23 +48,5 @@ func crop(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptions, } func cropToResult(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptions, imgdata *imagedata.ImageData) error { - // Crop image to the result size - resultWidth, resultHeight := resultSize(po, pctx.dprScale) - - if po.ResizingType == options.ResizeFillDown { - diffW := float64(resultWidth) / float64(img.Width()) - diffH := float64(resultHeight) / float64(img.Height()) - - switch { - case diffW > diffH && diffW > 1.0: - resultHeight = imath.Scale(img.Width(), float64(resultHeight)/float64(resultWidth)) - resultWidth = img.Width() - - case diffH > diffW && diffH > 1.0: - resultWidth = imath.Scale(img.Height(), float64(resultWidth)/float64(resultHeight)) - resultHeight = img.Height() - } - } - - return cropImage(img, resultWidth, resultHeight, &po.Gravity, pctx.dprScale) + return cropImage(img, pctx.resultCropWidth, pctx.resultCropHeight, &po.Gravity, pctx.dprScale) } diff --git a/processing/extend.go b/processing/extend.go index c3ff1e77..484e0394 100644 --- a/processing/extend.go +++ b/processing/extend.go @@ -2,61 +2,47 @@ 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 extendImage(img *vips.Image, resultWidth, resultHeight int, opts *options.ExtendOptions, offsetScale float64, extendAr bool) error { +func extendImage(img *vips.Image, width, height int, gravity *options.GravityOptions, offsetScale float64) error { imgWidth := img.Width() imgHeight := img.Height() - if !opts.Enabled || (resultWidth <= imgWidth && resultHeight <= imgHeight) { + if width <= imgWidth && height <= imgHeight { return nil } - if resultWidth <= 0 { - if extendAr { - return nil - } - resultWidth = imgWidth + if width <= 0 { + width = imgWidth } - if resultHeight <= 0 { - if extendAr { - return nil - } - resultHeight = imgHeight + if height <= 0 { + height = imgHeight } - if extendAr && resultWidth > imgWidth && resultHeight > imgHeight { - diffW := float64(resultWidth) / float64(imgWidth) - diffH := float64(resultHeight) / float64(imgHeight) - - switch { - case diffH > diffW: - resultHeight = imath.Scale(imgWidth, float64(resultHeight)/float64(resultWidth)) - resultWidth = imgWidth - - case diffW > diffH: - resultWidth = imath.Scale(imgHeight, float64(resultWidth)/float64(resultHeight)) - resultHeight = imgHeight - - default: - // The image has the requested arpect ratio - return nil - } - } - - offX, offY := calcPosition(resultWidth, resultHeight, imgWidth, imgHeight, &opts.Gravity, offsetScale, false) - return img.Embed(resultWidth, resultHeight, offX, offY) + offX, offY := calcPosition(width, height, imgWidth, imgHeight, gravity, offsetScale, false) + return img.Embed(width, height, offX, offY) } func extend(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptions, imgdata *imagedata.ImageData) error { - resultWidth, resultHeight := resultSize(po, pctx.dprScale) - return extendImage(img, resultWidth, resultHeight, &po.Extend, pctx.dprScale, false) + if !po.Extend.Enabled { + return nil + } + + width, height := pctx.targetWidth, pctx.targetHeight + return extendImage(img, width, height, &po.Extend.Gravity, pctx.dprScale) } func extendAspectRatio(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptions, imgdata *imagedata.ImageData) error { - resultWidth, resultHeight := resultSize(po, pctx.dprScale) - return extendImage(img, resultWidth, resultHeight, &po.ExtendAspectRatio, pctx.dprScale, true) + if !po.ExtendAspectRatio.Enabled { + return nil + } + + width, height := pctx.extendAspectRatioWidth, pctx.extendAspectRatioHeight + if width == 0 || height == 0 { + return nil + } + + return extendImage(img, width, height, &po.ExtendAspectRatio.Gravity, pctx.dprScale) } diff --git a/processing/pipeline.go b/processing/pipeline.go index 64612413..12c63b65 100644 --- a/processing/pipeline.go +++ b/processing/pipeline.go @@ -30,6 +30,34 @@ type pipelineContext struct { hscale float64 dprScale 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 + // 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 + + // The width of the image after cropping, scaling and rotating + scaledWidth int + // The height of the image after cropping, scaling and rotating + scaledHeight int + + // The width of the result crop according to the resizing type + resultCropWidth int + // The height of the result crop according to the resizing type + 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 + // 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 } type pipelineStep func(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptions, imgdata *imagedata.ImageData) error diff --git a/processing/prepare.go b/processing/prepare.go index 0442aa2a..3299493a 100644 --- a/processing/prepare.go +++ b/processing/prepare.go @@ -41,7 +41,18 @@ func extractMeta(img *vips.Image, baseAngle int, useOrientation bool) (int, int, return width, height, angle, flip } -func calcScale(width, height int, po *options.ProcessingOptions, imgtype imagetype.Type) (float64, float64, float64) { +func calcCropSize(orig int, crop float64) int { + switch { + case crop == 0.0: + return 0 + case crop >= 1.0: + return int(crop) + default: + return imath.Max(1, imath.Scale(orig, crop)) + } +} + +func (pctx *pipelineContext) calcScale(width, height int, po *options.ProcessingOptions) { var wshrink, hshrink float64 srcW, srcH := float64(width), float64(height) @@ -98,22 +109,22 @@ func calcScale(width, height int, po *options.ProcessingOptions, imgtype imagety wshrink /= po.ZoomWidth hshrink /= po.ZoomHeight - dprScale := po.Dpr + pctx.dprScale = po.Dpr - if !po.Enlarge && imgtype != imagetype.SVG { + if !po.Enlarge && !pctx.imgtype.IsVector() { minShrink := math.Min(wshrink, hshrink) if minShrink < 1 { wshrink /= minShrink hshrink /= minShrink if !po.Extend.Enabled { - dprScale /= minShrink + pctx.dprScale /= minShrink } } // The minimum of wshrink and hshrink is the maximum dprScale value // that can be used without enlarging the image. - dprScale = math.Min(dprScale, math.Min(wshrink, hshrink)) + pctx.dprScale = math.Min(pctx.dprScale, math.Min(wshrink, hshrink)) } if po.MinWidth > 0 { @@ -130,8 +141,8 @@ func calcScale(width, height int, po *options.ProcessingOptions, imgtype imagety } } - wshrink /= dprScale - hshrink /= dprScale + wshrink /= pctx.dprScale + hshrink /= pctx.dprScale if wshrink > srcW { wshrink = srcW @@ -141,17 +152,55 @@ func calcScale(width, height int, po *options.ProcessingOptions, imgtype imagety hshrink = srcH } - return 1.0 / wshrink, 1.0 / hshrink, dprScale + pctx.wscale = 1.0 / wshrink + pctx.hscale = 1.0 / hshrink } -func calcCropSize(orig int, crop float64) int { - switch { - case crop == 0.0: - return 0 - case crop >= 1.0: - return int(crop) - default: - return imath.Max(1, imath.Scale(orig, crop)) +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) + + 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) + + switch { + case diffW > diffH && diffW > 1.0: + 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 + + default: + pctx.resultCropWidth = pctx.targetWidth + pctx.resultCropHeight = pctx.targetHeight + } + } else { + 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) + + 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 + + case diffW > diffH: + pctx.extendAspectRatioWidth = imath.Scale(outHeight, float64(pctx.targetWidth)/float64(pctx.targetHeight)) + pctx.extendAspectRatioHeight = outHeight + } } } @@ -169,7 +218,7 @@ func prepare(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptio widthToScale := imath.MinNonZero(pctx.cropWidth, pctx.srcWidth) heightToScale := imath.MinNonZero(pctx.cropHeight, pctx.srcHeight) - pctx.wscale, pctx.hscale, pctx.dprScale = calcScale(widthToScale, heightToScale, po, pctx.imgtype) + pctx.calcScale(widthToScale, heightToScale, po) // The size of a vector image is not checked during download, yet it can be very large. // So we should scale it down to the maximum allowed resolution @@ -182,5 +231,7 @@ func prepare(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptio } } + pctx.calcSizes(widthToScale, heightToScale, po) + return nil } diff --git a/processing/processing_test.go b/processing/processing_test.go new file mode 100644 index 00000000..ec621db3 --- /dev/null +++ b/processing/processing_test.go @@ -0,0 +1,565 @@ +package processing + +import ( + "context" + "fmt" + "io" + "os" + "path/filepath" + "testing" + + "github.com/sirupsen/logrus" + "github.com/stretchr/testify/suite" + + "github.com/imgproxy/imgproxy/v3/config" + "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 +} + +func (s *ProcessingTestSuite) SetupSuite() { + config.Reset() + + s.Require().NoError(imagedata.Init()) + s.Require().NoError(vips.Init()) + + logrus.SetOutput(io.Discard) +} + +func (s *ProcessingTestSuite) openFile(name string) *imagedata.ImageData { + secopts := security.Options{ + MaxSrcResolution: 10 * 1024 * 1024, + MaxSrcFileSize: 10 * 1024 * 1024, + MaxAnimationFrames: 100, + MaxAnimationFrameResolution: 10 * 1024 * 1024, + } + + wd, err := os.Getwd() + s.Require().NoError(err) + path := filepath.Join(wd, "..", "testdata", name) + + imagedata, err := imagedata.FromFile(path, "test image", secopts) + s.Require().NoError(err) + + return imagedata +} + +func (s *ProcessingTestSuite) checkSize(imgdata *imagedata.ImageData, width, height int) { + img := new(vips.Image) + err := img.Load(imgdata, 1, 1, 1) + s.Require().NoError(err) + defer img.Clear() + + s.Require().Equal(width, img.Width(), "Width mismatch") + s.Require().Equal(height, img.Height(), "Height mismatch") +} + +func (s *ProcessingTestSuite) TestResizeToFit() { + imgdata := s.openFile("test2.jpg") + + po := options.NewProcessingOptions() + po.ResizingType = options.ResizeFit + + testCases := []struct { + width int + height int + outWidth int + outHeight int + }{ + {width: 50, height: 50, outWidth: 50, outHeight: 25}, + {width: 50, height: 20, outWidth: 40, outHeight: 20}, + {width: 20, height: 50, outWidth: 20, outHeight: 10}, + {width: 300, height: 300, outWidth: 200, outHeight: 100}, + {width: 300, height: 50, outWidth: 100, outHeight: 50}, + {width: 100, height: 300, outWidth: 100, outHeight: 50}, + {width: 0, height: 50, outWidth: 100, outHeight: 50}, + {width: 50, height: 0, outWidth: 50, outHeight: 25}, + {width: 0, height: 200, outWidth: 200, outHeight: 100}, + {width: 300, height: 0, outWidth: 200, outHeight: 100}, + } + + for _, tc := range testCases { + s.Run(fmt.Sprintf("%dx%d", tc.width, tc.height), func() { + po.Width = tc.width + po.Height = tc.height + + outImgdata, err := ProcessImage(context.Background(), imgdata, po) + s.Require().NoError(err) + s.Require().NotNil(outImgdata) + + s.checkSize(outImgdata, tc.outWidth, tc.outHeight) + }) + } +} + +func (s *ProcessingTestSuite) TestResizeToFitEnlarge() { + imgdata := s.openFile("test2.jpg") + + po := options.NewProcessingOptions() + po.ResizingType = options.ResizeFit + po.Enlarge = true + + testCases := []struct { + width int + height int + outWidth int + outHeight int + }{ + {width: 50, height: 50, outWidth: 50, outHeight: 25}, + {width: 50, height: 20, outWidth: 40, outHeight: 20}, + {width: 20, height: 50, outWidth: 20, outHeight: 10}, + {width: 300, height: 300, outWidth: 300, outHeight: 150}, + {width: 300, height: 125, outWidth: 250, outHeight: 125}, + {width: 250, height: 300, outWidth: 250, outHeight: 125}, + {width: 0, height: 50, outWidth: 100, outHeight: 50}, + {width: 50, height: 0, outWidth: 50, outHeight: 25}, + {width: 0, height: 200, outWidth: 400, outHeight: 200}, + {width: 300, height: 0, outWidth: 300, outHeight: 150}, + } + + for _, tc := range testCases { + s.Run(fmt.Sprintf("%dx%d", tc.width, tc.height), func() { + po.Width = tc.width + po.Height = tc.height + + outImgdata, err := ProcessImage(context.Background(), imgdata, po) + s.Require().NoError(err) + s.Require().NotNil(outImgdata) + + s.checkSize(outImgdata, tc.outWidth, tc.outHeight) + }) + } +} + +func (s *ProcessingTestSuite) TestResizeToFitExtend() { + imgdata := s.openFile("test2.jpg") + + po := options.NewProcessingOptions() + po.ResizingType = options.ResizeFit + po.Extend = options.ExtendOptions{ + Enabled: true, + Gravity: options.GravityOptions{ + Type: options.GravityCenter, + }, + } + + testCases := []struct { + width int + height int + outWidth int + outHeight int + }{ + {width: 50, height: 50, outWidth: 50, outHeight: 50}, + {width: 50, height: 20, outWidth: 50, outHeight: 20}, + {width: 20, height: 50, outWidth: 20, outHeight: 50}, + {width: 300, height: 300, outWidth: 300, outHeight: 300}, + {width: 300, height: 125, outWidth: 300, outHeight: 125}, + {width: 250, height: 300, outWidth: 250, outHeight: 300}, + {width: 0, height: 50, outWidth: 100, outHeight: 50}, + {width: 50, height: 0, outWidth: 50, outHeight: 25}, + {width: 0, height: 200, outWidth: 200, outHeight: 200}, + {width: 300, height: 0, outWidth: 300, outHeight: 100}, + } + + for _, tc := range testCases { + s.Run(fmt.Sprintf("%dx%d", tc.width, tc.height), func() { + po.Width = tc.width + po.Height = tc.height + + outImgdata, err := ProcessImage(context.Background(), imgdata, po) + s.Require().NoError(err) + s.Require().NotNil(outImgdata) + + s.checkSize(outImgdata, tc.outWidth, tc.outHeight) + }) + } +} + +func (s *ProcessingTestSuite) TestResizeToFitExtendAR() { + imgdata := s.openFile("test2.jpg") + + po := options.NewProcessingOptions() + po.ResizingType = options.ResizeFit + po.ExtendAspectRatio = options.ExtendOptions{ + Enabled: true, + Gravity: options.GravityOptions{ + Type: options.GravityCenter, + }, + } + + testCases := []struct { + width int + height int + outWidth int + outHeight int + }{ + {width: 50, height: 50, outWidth: 50, outHeight: 50}, + {width: 50, height: 20, outWidth: 50, outHeight: 20}, + {width: 20, height: 50, outWidth: 20, outHeight: 50}, + {width: 300, height: 300, outWidth: 200, outHeight: 200}, + {width: 300, height: 125, outWidth: 240, outHeight: 100}, + {width: 250, height: 500, outWidth: 200, outHeight: 400}, + {width: 0, height: 50, outWidth: 100, outHeight: 50}, + {width: 50, height: 0, outWidth: 50, outHeight: 25}, + {width: 0, height: 200, outWidth: 200, outHeight: 100}, + {width: 300, height: 0, outWidth: 200, outHeight: 100}, + } + + for _, tc := range testCases { + s.Run(fmt.Sprintf("%dx%d", tc.width, tc.height), func() { + po.Width = tc.width + po.Height = tc.height + + outImgdata, err := ProcessImage(context.Background(), imgdata, po) + s.Require().NoError(err) + s.Require().NotNil(outImgdata) + + s.checkSize(outImgdata, tc.outWidth, tc.outHeight) + }) + } +} + +func (s *ProcessingTestSuite) TestResizeToFill() { + imgdata := s.openFile("test2.jpg") + + po := options.NewProcessingOptions() + po.ResizingType = options.ResizeFill + + testCases := []struct { + width int + height int + outWidth int + outHeight int + }{ + {width: 50, height: 50, outWidth: 50, outHeight: 50}, + {width: 50, height: 20, outWidth: 50, outHeight: 20}, + {width: 20, height: 50, outWidth: 20, outHeight: 50}, + {width: 300, height: 300, outWidth: 200, outHeight: 100}, + {width: 300, height: 50, outWidth: 200, outHeight: 50}, + {width: 100, height: 300, outWidth: 100, outHeight: 100}, + {width: 0, height: 50, outWidth: 100, outHeight: 50}, + {width: 50, height: 0, outWidth: 50, outHeight: 25}, + {width: 0, height: 200, outWidth: 200, outHeight: 100}, + {width: 300, height: 0, outWidth: 200, outHeight: 100}, + } + + for _, tc := range testCases { + s.Run(fmt.Sprintf("%dx%d", tc.width, tc.height), func() { + po.Width = tc.width + po.Height = tc.height + + outImgdata, err := ProcessImage(context.Background(), imgdata, po) + s.Require().NoError(err) + s.Require().NotNil(outImgdata) + + s.checkSize(outImgdata, tc.outWidth, tc.outHeight) + }) + } +} + +func (s *ProcessingTestSuite) TestResizeToFillEnlarge() { + imgdata := s.openFile("test2.jpg") + + po := options.NewProcessingOptions() + po.ResizingType = options.ResizeFill + po.Enlarge = true + + testCases := []struct { + width int + height int + outWidth int + outHeight int + }{ + {width: 50, height: 50, outWidth: 50, outHeight: 50}, + {width: 50, height: 20, outWidth: 50, outHeight: 20}, + {width: 20, height: 50, outWidth: 20, outHeight: 50}, + {width: 300, height: 300, outWidth: 300, outHeight: 300}, + {width: 300, height: 125, outWidth: 300, outHeight: 125}, + {width: 250, height: 300, outWidth: 250, outHeight: 300}, + {width: 0, height: 50, outWidth: 100, outHeight: 50}, + {width: 50, height: 0, outWidth: 50, outHeight: 25}, + {width: 0, height: 200, outWidth: 400, outHeight: 200}, + {width: 300, height: 0, outWidth: 300, outHeight: 150}, + } + + for _, tc := range testCases { + s.Run(fmt.Sprintf("%dx%d", tc.width, tc.height), func() { + po.Width = tc.width + po.Height = tc.height + + outImgdata, err := ProcessImage(context.Background(), imgdata, po) + s.Require().NoError(err) + s.Require().NotNil(outImgdata) + + s.checkSize(outImgdata, tc.outWidth, tc.outHeight) + }) + } +} + +func (s *ProcessingTestSuite) TestResizeToFillExtend() { + imgdata := s.openFile("test2.jpg") + + po := options.NewProcessingOptions() + po.ResizingType = options.ResizeFill + po.Extend = options.ExtendOptions{ + Enabled: true, + Gravity: options.GravityOptions{ + Type: options.GravityCenter, + }, + } + + testCases := []struct { + width int + height int + outWidth int + outHeight int + }{ + {width: 50, height: 50, outWidth: 50, outHeight: 50}, + {width: 50, height: 20, outWidth: 50, outHeight: 20}, + {width: 20, height: 50, outWidth: 20, outHeight: 50}, + {width: 300, height: 300, outWidth: 300, outHeight: 300}, + {width: 300, height: 125, outWidth: 300, outHeight: 125}, + {width: 250, height: 300, outWidth: 250, outHeight: 300}, + {width: 300, height: 50, outWidth: 300, outHeight: 50}, + {width: 100, height: 300, outWidth: 100, outHeight: 300}, + {width: 0, height: 50, outWidth: 100, outHeight: 50}, + {width: 50, height: 0, outWidth: 50, outHeight: 25}, + {width: 0, height: 200, outWidth: 200, outHeight: 200}, + {width: 300, height: 0, outWidth: 300, outHeight: 100}, + } + + for _, tc := range testCases { + s.Run(fmt.Sprintf("%dx%d", tc.width, tc.height), func() { + po.Width = tc.width + po.Height = tc.height + + outImgdata, err := ProcessImage(context.Background(), imgdata, po) + s.Require().NoError(err) + s.Require().NotNil(outImgdata) + + s.checkSize(outImgdata, tc.outWidth, tc.outHeight) + }) + } +} + +func (s *ProcessingTestSuite) TestResizeToFillExtendAR() { + imgdata := s.openFile("test2.jpg") + + po := options.NewProcessingOptions() + po.ResizingType = options.ResizeFill + po.ExtendAspectRatio = options.ExtendOptions{ + Enabled: true, + Gravity: options.GravityOptions{ + Type: options.GravityCenter, + }, + } + + testCases := []struct { + width int + height int + outWidth int + outHeight int + }{ + {width: 50, height: 50, outWidth: 50, outHeight: 50}, + {width: 50, height: 20, outWidth: 50, outHeight: 20}, + {width: 20, height: 50, outWidth: 20, outHeight: 50}, + {width: 300, height: 300, outWidth: 200, outHeight: 200}, + {width: 300, height: 125, outWidth: 240, outHeight: 100}, + {width: 250, height: 500, outWidth: 200, outHeight: 400}, + {width: 300, height: 50, outWidth: 300, outHeight: 50}, + {width: 100, height: 300, outWidth: 100, outHeight: 300}, + {width: 0, height: 50, outWidth: 100, outHeight: 50}, + {width: 50, height: 0, outWidth: 50, outHeight: 25}, + {width: 0, height: 200, outWidth: 200, outHeight: 100}, + {width: 300, height: 0, outWidth: 200, outHeight: 100}, + } + + for _, tc := range testCases { + s.Run(fmt.Sprintf("%dx%d", tc.width, tc.height), func() { + po.Width = tc.width + po.Height = tc.height + + outImgdata, err := ProcessImage(context.Background(), imgdata, po) + s.Require().NoError(err) + s.Require().NotNil(outImgdata) + + s.checkSize(outImgdata, tc.outWidth, tc.outHeight) + }) + } +} + +func (s *ProcessingTestSuite) TestResizeToFillDown() { + imgdata := s.openFile("test2.jpg") + + po := options.NewProcessingOptions() + po.ResizingType = options.ResizeFillDown + + testCases := []struct { + width int + height int + outWidth int + outHeight int + }{ + {width: 50, height: 50, outWidth: 50, outHeight: 50}, + {width: 50, height: 20, outWidth: 50, outHeight: 20}, + {width: 20, height: 50, outWidth: 20, outHeight: 50}, + {width: 300, height: 300, outWidth: 100, outHeight: 100}, + {width: 300, height: 125, outWidth: 200, outHeight: 83}, + {width: 250, height: 300, outWidth: 83, outHeight: 100}, + {width: 0, height: 50, outWidth: 100, outHeight: 50}, + {width: 50, height: 0, outWidth: 50, outHeight: 25}, + {width: 0, height: 200, outWidth: 200, outHeight: 100}, + {width: 300, height: 0, outWidth: 200, outHeight: 100}, + } + + for _, tc := range testCases { + s.Run(fmt.Sprintf("%dx%d", tc.width, tc.height), func() { + po.Width = tc.width + po.Height = tc.height + + outImgdata, err := ProcessImage(context.Background(), imgdata, po) + s.Require().NoError(err) + s.Require().NotNil(outImgdata) + + s.checkSize(outImgdata, tc.outWidth, tc.outHeight) + }) + } +} + +func (s *ProcessingTestSuite) TestResizeToFillDownEnlarge() { + imgdata := s.openFile("test2.jpg") + + po := options.NewProcessingOptions() + po.ResizingType = options.ResizeFillDown + po.Enlarge = true + + testCases := []struct { + width int + height int + outWidth int + outHeight int + }{ + {width: 50, height: 50, outWidth: 50, outHeight: 50}, + {width: 50, height: 20, outWidth: 50, outHeight: 20}, + {width: 20, height: 50, outWidth: 20, outHeight: 50}, + {width: 300, height: 300, outWidth: 300, outHeight: 300}, + {width: 300, height: 125, outWidth: 300, outHeight: 125}, + {width: 250, height: 300, outWidth: 250, outHeight: 300}, + {width: 0, height: 50, outWidth: 100, outHeight: 50}, + {width: 50, height: 0, outWidth: 50, outHeight: 25}, + {width: 0, height: 200, outWidth: 400, outHeight: 200}, + {width: 300, height: 0, outWidth: 300, outHeight: 150}, + } + + for _, tc := range testCases { + s.Run(fmt.Sprintf("%dx%d", tc.width, tc.height), func() { + po.Width = tc.width + po.Height = tc.height + + outImgdata, err := ProcessImage(context.Background(), imgdata, po) + s.Require().NoError(err) + s.Require().NotNil(outImgdata) + + s.checkSize(outImgdata, tc.outWidth, tc.outHeight) + }) + } +} + +func (s *ProcessingTestSuite) TestResizeToFillDownExtend() { + imgdata := s.openFile("test2.jpg") + + po := options.NewProcessingOptions() + po.ResizingType = options.ResizeFillDown + po.Extend = options.ExtendOptions{ + Enabled: true, + Gravity: options.GravityOptions{ + Type: options.GravityCenter, + }, + } + + testCases := []struct { + width int + height int + outWidth int + outHeight int + }{ + {width: 50, height: 50, outWidth: 50, outHeight: 50}, + {width: 50, height: 20, outWidth: 50, outHeight: 20}, + {width: 20, height: 50, outWidth: 20, outHeight: 50}, + {width: 300, height: 300, outWidth: 300, outHeight: 300}, + {width: 300, height: 125, outWidth: 300, outHeight: 125}, + {width: 250, height: 300, outWidth: 250, outHeight: 300}, + {width: 300, height: 50, outWidth: 300, outHeight: 50}, + {width: 100, height: 300, outWidth: 100, outHeight: 300}, + {width: 0, height: 50, outWidth: 100, outHeight: 50}, + {width: 50, height: 0, outWidth: 50, outHeight: 25}, + {width: 0, height: 200, outWidth: 200, outHeight: 200}, + {width: 300, height: 0, outWidth: 300, outHeight: 100}, + } + + for _, tc := range testCases { + s.Run(fmt.Sprintf("%dx%d", tc.width, tc.height), func() { + po.Width = tc.width + po.Height = tc.height + + outImgdata, err := ProcessImage(context.Background(), imgdata, po) + s.Require().NoError(err) + s.Require().NotNil(outImgdata) + + s.checkSize(outImgdata, tc.outWidth, tc.outHeight) + }) + } +} + +func (s *ProcessingTestSuite) TestResizeToFillDownExtendAR() { + imgdata := s.openFile("test2.jpg") + + po := options.NewProcessingOptions() + po.ResizingType = options.ResizeFillDown + po.ExtendAspectRatio = options.ExtendOptions{ + Enabled: true, + Gravity: options.GravityOptions{ + Type: options.GravityCenter, + }, + } + + testCases := []struct { + width int + height int + outWidth int + outHeight int + }{ + {width: 50, height: 50, outWidth: 50, outHeight: 50}, + {width: 50, height: 20, outWidth: 50, outHeight: 20}, + {width: 20, height: 50, outWidth: 20, outHeight: 50}, + {width: 300, height: 300, outWidth: 100, outHeight: 100}, + {width: 300, height: 125, outWidth: 200, outHeight: 83}, + {width: 250, height: 300, outWidth: 83, outHeight: 100}, + {width: 0, height: 50, outWidth: 100, outHeight: 50}, + {width: 50, height: 0, outWidth: 50, outHeight: 25}, + {width: 0, height: 200, outWidth: 200, outHeight: 100}, + {width: 300, height: 0, outWidth: 200, outHeight: 100}, + } + + for _, tc := range testCases { + s.Run(fmt.Sprintf("%dx%d", tc.width, tc.height), func() { + po.Width = tc.width + po.Height = tc.height + + 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)) +} diff --git a/processing/result_size.go b/processing/result_size.go deleted file mode 100644 index 18de1069..00000000 --- a/processing/result_size.go +++ /dev/null @@ -1,13 +0,0 @@ -package processing - -import ( - "github.com/imgproxy/imgproxy/v3/imath" - "github.com/imgproxy/imgproxy/v3/options" -) - -func resultSize(po *options.ProcessingOptions, dprScale float64) (int, int) { - resultWidth := imath.Scale(po.Width, dprScale*po.ZoomWidth) - resultHeight := imath.Scale(po.Height, dprScale*po.ZoomHeight) - - return resultWidth, resultHeight -} diff --git a/testdata/test2.jpg b/testdata/test2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d788093c76cdcbac68c4ca6e5b24fa05f3d8b549 GIT binary patch literal 649 zcmcgpyGq4C5IwV#-DE?q$y#cof+!Xu+K7!;!NMZq57`(oJ}N&z zzd%?wdqZwwdJnrZbIzQ7vG&z|;^gS=>JrFvYygPBv;gTUa&wy&+j@`I!Cn_G)`)*9 zU@dF2lk6A9r<3xwyqT2OOFNre*gZVX16A7*LJ`3YudRA5^RW}`H