mirror of
				https://github.com/imgproxy/imgproxy.git
				synced 2025-10-30 23:08:02 +02:00 
			
		
		
		
	Calculate all sizes at the start of processing pipeline
This commit is contained in:
		| @@ -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) | ||||
| } | ||||
|   | ||||
| @@ -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) | ||||
| } | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
| } | ||||
|   | ||||
							
								
								
									
										565
									
								
								processing/processing_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										565
									
								
								processing/processing_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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)) | ||||
| } | ||||
| @@ -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 | ||||
| } | ||||
							
								
								
									
										
											BIN
										
									
								
								testdata/test2.jpg
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								testdata/test2.jpg
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 649 B | 
		Reference in New Issue
	
	Block a user