mirror of
https://github.com/imgproxy/imgproxy.git
synced 2025-01-18 11:12:10 +02:00
Crop between scale-on-load and scale
This commit is contained in:
parent
1ab981ef11
commit
7a968d5fed
@ -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
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user