mirror of
				https://github.com/imgproxy/imgproxy.git
				synced 2025-10-30 23:08:02 +02:00 
			
		
		
		
	Crop between scale-on-load and scale
This commit is contained in:
		| @@ -5,6 +5,9 @@ | ||||
| - (pro) Add `video_meta` to the `/info` response. | ||||
| - Add 1/2/4-bit BMP support. | ||||
|  | ||||
| ### Change | ||||
| - Optimized `crop`. | ||||
|  | ||||
| ### Fix | ||||
| - Fix Datadog support. | ||||
|  | ||||
|   | ||||
| @@ -38,3 +38,11 @@ func Scale(a int, scale float64) int { | ||||
|  | ||||
| 	return Round(float64(a) * scale) | ||||
| } | ||||
|  | ||||
| func Shrink(a int, shrink float64) int { | ||||
| 	if a == 0 { | ||||
| 		return 0 | ||||
| 	} | ||||
|  | ||||
| 	return Round(float64(a) / shrink) | ||||
| } | ||||
|   | ||||
							
								
								
									
										158
									
								
								options/gravity_options.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										158
									
								
								options/gravity_options.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,158 @@ | ||||
| package options | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| ) | ||||
|  | ||||
| type GravityType int | ||||
|  | ||||
| const ( | ||||
| 	GravityUnknown GravityType = iota | ||||
| 	GravityCenter | ||||
| 	GravityNorth | ||||
| 	GravityEast | ||||
| 	GravitySouth | ||||
| 	GravityWest | ||||
| 	GravityNorthWest | ||||
| 	GravityNorthEast | ||||
| 	GravitySouthWest | ||||
| 	GravitySouthEast | ||||
| 	GravitySmart | ||||
| 	GravityFocusPoint | ||||
| ) | ||||
|  | ||||
| var gravityTypes = map[string]GravityType{ | ||||
| 	"ce":   GravityCenter, | ||||
| 	"no":   GravityNorth, | ||||
| 	"ea":   GravityEast, | ||||
| 	"so":   GravitySouth, | ||||
| 	"we":   GravityWest, | ||||
| 	"nowe": GravityNorthWest, | ||||
| 	"noea": GravityNorthEast, | ||||
| 	"sowe": GravitySouthWest, | ||||
| 	"soea": GravitySouthEast, | ||||
| 	"sm":   GravitySmart, | ||||
| 	"fp":   GravityFocusPoint, | ||||
| } | ||||
|  | ||||
| var gravityTypesRotationMap = map[int]map[GravityType]GravityType{ | ||||
| 	90: { | ||||
| 		GravityNorth:     GravityWest, | ||||
| 		GravityEast:      GravityNorth, | ||||
| 		GravitySouth:     GravityEast, | ||||
| 		GravityWest:      GravitySouth, | ||||
| 		GravityNorthWest: GravitySouthWest, | ||||
| 		GravityNorthEast: GravityNorthWest, | ||||
| 		GravitySouthWest: GravitySouthEast, | ||||
| 		GravitySouthEast: GravityNorthEast, | ||||
| 	}, | ||||
| 	180: { | ||||
| 		GravityNorth:     GravitySouth, | ||||
| 		GravityEast:      GravityWest, | ||||
| 		GravitySouth:     GravityNorth, | ||||
| 		GravityWest:      GravityEast, | ||||
| 		GravityNorthWest: GravitySouthEast, | ||||
| 		GravityNorthEast: GravitySouthWest, | ||||
| 		GravitySouthWest: GravityNorthEast, | ||||
| 		GravitySouthEast: GravityNorthWest, | ||||
| 	}, | ||||
| 	270: { | ||||
| 		GravityNorth:     GravityEast, | ||||
| 		GravityEast:      GravitySouth, | ||||
| 		GravitySouth:     GravityWest, | ||||
| 		GravityWest:      GravityNorth, | ||||
| 		GravityNorthWest: GravityNorthEast, | ||||
| 		GravityNorthEast: GravitySouthEast, | ||||
| 		GravitySouthWest: GravityNorthWest, | ||||
| 		GravitySouthEast: GravitySouthWest, | ||||
| 	}, | ||||
| } | ||||
|  | ||||
| var gravityTypesFlipMap = map[GravityType]GravityType{ | ||||
| 	GravityEast:      GravityWest, | ||||
| 	GravityWest:      GravityEast, | ||||
| 	GravityNorthWest: GravityNorthEast, | ||||
| 	GravityNorthEast: GravityNorthWest, | ||||
| 	GravitySouthWest: GravitySouthEast, | ||||
| 	GravitySouthEast: GravitySouthWest, | ||||
| } | ||||
|  | ||||
| func (gt GravityType) String() string { | ||||
| 	for k, v := range gravityTypes { | ||||
| 		if v == gt { | ||||
| 			return k | ||||
| 		} | ||||
| 	} | ||||
| 	return "" | ||||
| } | ||||
|  | ||||
| func (gt GravityType) MarshalJSON() ([]byte, error) { | ||||
| 	for k, v := range gravityTypes { | ||||
| 		if v == gt { | ||||
| 			return []byte(fmt.Sprintf("%q", k)), nil | ||||
| 		} | ||||
| 	} | ||||
| 	return []byte("null"), nil | ||||
| } | ||||
|  | ||||
| type GravityOptions struct { | ||||
| 	Type GravityType | ||||
| 	X, Y float64 | ||||
| } | ||||
|  | ||||
| func (g *GravityOptions) RotateAndFlip(angle int, flip bool) { | ||||
| 	angle %= 360 | ||||
|  | ||||
| 	if flip { | ||||
| 		if gt, ok := gravityTypesFlipMap[g.Type]; ok { | ||||
| 			g.Type = gt | ||||
| 		} | ||||
|  | ||||
| 		switch g.Type { | ||||
| 		case GravityCenter, GravityNorth, GravitySouth: | ||||
| 			g.X = -g.X | ||||
| 		case GravityFocusPoint: | ||||
| 			g.X = 1.0 - g.X | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if angle > 0 { | ||||
| 		if rotMap := gravityTypesRotationMap[angle]; rotMap != nil { | ||||
| 			if gt, ok := rotMap[g.Type]; ok { | ||||
| 				g.Type = gt | ||||
| 			} | ||||
|  | ||||
| 			switch angle { | ||||
| 			case 90: | ||||
| 				switch g.Type { | ||||
| 				case GravityCenter, GravityEast, GravityWest: | ||||
| 					g.X, g.Y = g.Y, -g.X | ||||
| 				case GravityFocusPoint: | ||||
| 					g.X, g.Y = g.Y, 1.0-g.X | ||||
| 				default: | ||||
| 					g.X, g.Y = g.Y, g.X | ||||
| 				} | ||||
| 			case 180: | ||||
| 				switch g.Type { | ||||
| 				case GravityCenter: | ||||
| 					g.X, g.Y = -g.X, -g.Y | ||||
| 				case GravityNorth, GravitySouth: | ||||
| 					g.X = -g.X | ||||
| 				case GravityEast, GravityWest: | ||||
| 					g.Y = -g.Y | ||||
| 				case GravityFocusPoint: | ||||
| 					g.X, g.Y = 1.0-g.X, 1.0-g.Y | ||||
| 				} | ||||
| 			case 270: | ||||
| 				switch g.Type { | ||||
| 				case GravityCenter, GravityNorth, GravitySouth: | ||||
| 					g.X, g.Y = -g.Y, g.X | ||||
| 				case GravityFocusPoint: | ||||
| 					g.X, g.Y = 1.0-g.Y, g.X | ||||
| 				default: | ||||
| 					g.X, g.Y = g.Y, g.X | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @@ -1,52 +0,0 @@ | ||||
| package options | ||||
|  | ||||
| import "fmt" | ||||
|  | ||||
| type GravityType int | ||||
|  | ||||
| const ( | ||||
| 	GravityUnknown GravityType = iota | ||||
| 	GravityCenter | ||||
| 	GravityNorth | ||||
| 	GravityEast | ||||
| 	GravitySouth | ||||
| 	GravityWest | ||||
| 	GravityNorthWest | ||||
| 	GravityNorthEast | ||||
| 	GravitySouthWest | ||||
| 	GravitySouthEast | ||||
| 	GravitySmart | ||||
| 	GravityFocusPoint | ||||
| ) | ||||
|  | ||||
| var gravityTypes = map[string]GravityType{ | ||||
| 	"ce":   GravityCenter, | ||||
| 	"no":   GravityNorth, | ||||
| 	"ea":   GravityEast, | ||||
| 	"so":   GravitySouth, | ||||
| 	"we":   GravityWest, | ||||
| 	"nowe": GravityNorthWest, | ||||
| 	"noea": GravityNorthEast, | ||||
| 	"sowe": GravitySouthWest, | ||||
| 	"soea": GravitySouthEast, | ||||
| 	"sm":   GravitySmart, | ||||
| 	"fp":   GravityFocusPoint, | ||||
| } | ||||
|  | ||||
| func (gt GravityType) String() string { | ||||
| 	for k, v := range gravityTypes { | ||||
| 		if v == gt { | ||||
| 			return k | ||||
| 		} | ||||
| 	} | ||||
| 	return "" | ||||
| } | ||||
|  | ||||
| func (gt GravityType) MarshalJSON() ([]byte, error) { | ||||
| 	for k, v := range gravityTypes { | ||||
| 		if v == gt { | ||||
| 			return []byte(fmt.Sprintf("%q", k)), nil | ||||
| 		} | ||||
| 	} | ||||
| 	return []byte("null"), nil | ||||
| } | ||||
| @@ -23,11 +23,6 @@ const maxClientHintDPR = 8 | ||||
|  | ||||
| var errExpiredURL = errors.New("Expired URL") | ||||
|  | ||||
| type GravityOptions struct { | ||||
| 	Type GravityType | ||||
| 	X, Y float64 | ||||
| } | ||||
|  | ||||
| type ExtendOptions struct { | ||||
| 	Enabled bool | ||||
| 	Gravity GravityOptions | ||||
| @@ -211,11 +206,7 @@ func parseBoolOption(str string) bool { | ||||
| } | ||||
|  | ||||
| func isGravityOffcetValid(gravity GravityType, offset float64) bool { | ||||
| 	if gravity == GravityCenter { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	return offset >= 0 && (gravity != GravityFocusPoint || offset <= 1) | ||||
| 	return gravity != GravityFocusPoint || (offset >= 0 && offset <= 1) | ||||
| } | ||||
|  | ||||
| func parseGravity(g *GravityOptions, args []string) error { | ||||
|   | ||||
| @@ -38,10 +38,20 @@ func cropImage(img *vips.Image, cropWidth, cropHeight int, gravity *options.Grav | ||||
| } | ||||
|  | ||||
| func crop(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptions, imgdata *imagedata.ImageData) error { | ||||
| 	if err := cropImage(img, pctx.cropWidth, pctx.cropHeight, &pctx.cropGravity); err != nil { | ||||
| 		return err | ||||
| 	width, height := pctx.cropWidth, pctx.cropHeight | ||||
|  | ||||
| 	opts := pctx.cropGravity | ||||
| 	opts.RotateAndFlip(pctx.angle, pctx.flip) | ||||
| 	opts.RotateAndFlip(po.Rotate, false) | ||||
|  | ||||
| 	if (pctx.angle+po.Rotate)%180 == 90 { | ||||
| 		width, height = height, width | ||||
| 	} | ||||
|  | ||||
| 	return cropImage(img, width, height, &opts) | ||||
| } | ||||
|  | ||||
| func cropToResult(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptions, imgdata *imagedata.ImageData) error { | ||||
| 	// Crop image to the result size | ||||
| 	resultWidth := imath.Scale(po.Width, po.Dpr) | ||||
| 	resultHeight := imath.Scale(po.Height, po.Dpr) | ||||
|   | ||||
| @@ -161,16 +161,5 @@ func prepare(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptio | ||||
|  | ||||
| 	pctx.wscale, pctx.hscale = calcScale(widthToScale, heightToScale, po, pctx.imgtype) | ||||
|  | ||||
| 	if pctx.cropWidth > 0 { | ||||
| 		pctx.cropWidth = imath.Max(1, imath.Scale(pctx.cropWidth, pctx.wscale)) | ||||
| 	} | ||||
| 	if pctx.cropHeight > 0 { | ||||
| 		pctx.cropHeight = imath.Max(1, imath.Scale(pctx.cropHeight, pctx.hscale)) | ||||
| 	} | ||||
| 	if pctx.cropGravity.Type != options.GravityFocusPoint { | ||||
| 		pctx.cropGravity.X *= pctx.wscale | ||||
| 		pctx.cropGravity.Y *= pctx.hscale | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|   | ||||
| @@ -23,9 +23,10 @@ var mainPipeline = pipeline{ | ||||
| 	prepare, | ||||
| 	scaleOnLoad, | ||||
| 	importColorProfile, | ||||
| 	crop, | ||||
| 	scale, | ||||
| 	rotateAndFlip, | ||||
| 	crop, | ||||
| 	cropToResult, | ||||
| 	fixWebpSize, | ||||
| 	applyFilters, | ||||
| 	extend, | ||||
|   | ||||
| @@ -58,15 +58,29 @@ func scaleOnLoad(pctx *pipelineContext, img *vips.Image, po *options.ProcessingO | ||||
| 	// Update scales after scale-on-load | ||||
| 	newWidth, newHeight, _, _ := extractMeta(img, po.Rotate, po.AutoRotate) | ||||
|  | ||||
| 	pctx.wscale = float64(pctx.srcWidth) * pctx.wscale / float64(newWidth) | ||||
| 	wpreshrink := float64(pctx.srcWidth) / float64(newWidth) | ||||
| 	hpreshrink := float64(pctx.srcHeight) / float64(newHeight) | ||||
|  | ||||
| 	pctx.wscale = wpreshrink * pctx.wscale | ||||
| 	if newWidth == imath.Scale(newWidth, pctx.wscale) { | ||||
| 		pctx.wscale = 1.0 | ||||
| 	} | ||||
|  | ||||
| 	pctx.hscale = float64(pctx.srcHeight) * pctx.hscale / float64(newHeight) | ||||
| 	pctx.hscale = hpreshrink * pctx.hscale | ||||
| 	if newHeight == imath.Scale(newHeight, pctx.hscale) { | ||||
| 		pctx.hscale = 1.0 | ||||
| 	} | ||||
|  | ||||
| 	if pctx.cropWidth > 0 { | ||||
| 		pctx.cropWidth = imath.Max(1, imath.Shrink(pctx.cropWidth, wpreshrink)) | ||||
| 	} | ||||
| 	if pctx.cropHeight > 0 { | ||||
| 		pctx.cropHeight = imath.Max(1, imath.Shrink(pctx.cropHeight, hpreshrink)) | ||||
| 	} | ||||
| 	if pctx.cropGravity.Type != options.GravityFocusPoint { | ||||
| 		pctx.cropGravity.X /= wpreshrink | ||||
| 		pctx.cropGravity.Y /= hpreshrink | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user