mirror of
https://github.com/imgproxy/imgproxy.git
synced 2024-11-29 08:22:11 +02:00
Optimal and correct DPR support
This commit is contained in:
parent
8b5f620ac3
commit
c3852d683d
165
process.go
165
process.go
@ -134,19 +134,26 @@ func extractMeta(img *C.VipsImage) (int, int, int, bool) {
|
||||
return width, height, angle, flip
|
||||
}
|
||||
|
||||
func needToScale(width, height int, po *processingOptions) bool {
|
||||
return ((po.Width != 0 && po.Width != width) || (po.Height != 0 && po.Height != height)) &&
|
||||
(po.Resize == resizeFill || po.Resize == resizeFit)
|
||||
func calcScale(width, height int, po *processingOptions) float64 {
|
||||
// If we're going only to crop, we need only to scale down to DPR.
|
||||
// Scaling up while cropping is not optimal on this stage, we'll do it later if needed.
|
||||
if po.Resize == resizeCrop {
|
||||
if po.Dpr < 1 {
|
||||
return po.Dpr
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
func calcScale(width, height int, po *processingOptions) float64 {
|
||||
var scale float64
|
||||
|
||||
srcW, srcH := float64(width), float64(height)
|
||||
|
||||
if (po.Width == 0 || po.Width == width) && (po.Height == 0 || po.Height == height) {
|
||||
scale = 1
|
||||
} else {
|
||||
wr := float64(po.Width) / srcW
|
||||
hr := float64(po.Height) / srcH
|
||||
|
||||
var scale float64
|
||||
|
||||
if po.Width == 0 {
|
||||
scale = hr
|
||||
} else if po.Height == 0 {
|
||||
@ -156,6 +163,13 @@ func calcScale(width, height int, po *processingOptions) float64 {
|
||||
} else {
|
||||
scale = math.Max(wr, hr)
|
||||
}
|
||||
}
|
||||
|
||||
scale = scale * po.Dpr
|
||||
|
||||
if !po.Enlarge && scale > 1 {
|
||||
return 1
|
||||
}
|
||||
|
||||
if srcW*scale < 1 {
|
||||
scale = 1 / srcW
|
||||
@ -188,80 +202,42 @@ func calcShink(scale float64, imgtype imageType) int {
|
||||
return 1
|
||||
}
|
||||
|
||||
func calcCrop(width, height int, po *processingOptions) (left, top int) {
|
||||
if po.Gravity.Type == gravityFocusPoint {
|
||||
pointX := int(float64(width) * po.Gravity.X)
|
||||
pointY := int(float64(height) * po.Gravity.Y)
|
||||
func calcCrop(width, height, cropWidth, cropHeight int, gravity *gravityOptions) (left, top int) {
|
||||
if gravity.Type == gravityFocusPoint {
|
||||
pointX := int(float64(width) * gravity.X)
|
||||
pointY := int(float64(height) * gravity.Y)
|
||||
|
||||
left = maxInt(0, minInt(pointX-po.Width/2, width-po.Width))
|
||||
top = maxInt(0, minInt(pointY-po.Height/2, height-po.Height))
|
||||
left = maxInt(0, minInt(pointX-cropWidth/2, width-cropWidth))
|
||||
top = maxInt(0, minInt(pointY-cropHeight/2, height-cropHeight))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
left = (width - po.Width + 1) / 2
|
||||
top = (height - po.Height + 1) / 2
|
||||
left = (width - cropWidth + 1) / 2
|
||||
top = (height - cropHeight + 1) / 2
|
||||
|
||||
if po.Gravity.Type == gravityNorth || po.Gravity.Type == gravityNorthEast || po.Gravity.Type == gravityNorthWest {
|
||||
if gravity.Type == gravityNorth || gravity.Type == gravityNorthEast || gravity.Type == gravityNorthWest {
|
||||
top = 0
|
||||
}
|
||||
|
||||
if po.Gravity.Type == gravityEast || po.Gravity.Type == gravityNorthEast || po.Gravity.Type == gravitySouthEast {
|
||||
left = width - po.Width
|
||||
if gravity.Type == gravityEast || gravity.Type == gravityNorthEast || gravity.Type == gravitySouthEast {
|
||||
left = width - cropWidth
|
||||
}
|
||||
|
||||
if po.Gravity.Type == gravitySouth || po.Gravity.Type == gravitySouthEast || po.Gravity.Type == gravitySouthWest {
|
||||
top = height - po.Height
|
||||
if gravity.Type == gravitySouth || gravity.Type == gravitySouthEast || gravity.Type == gravitySouthWest {
|
||||
top = height - cropHeight
|
||||
}
|
||||
|
||||
if po.Gravity.Type == gravityWest || po.Gravity.Type == gravityNorthWest || po.Gravity.Type == gravitySouthWest {
|
||||
if gravity.Type == gravityWest || gravity.Type == gravityNorthWest || gravity.Type == gravitySouthWest {
|
||||
left = 0
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func transformImage(ctx context.Context, img **C.struct__VipsImage, data []byte, po *processingOptions, imgtype imageType) error {
|
||||
func resizeImage(img **C.struct__VipsImage, scale float64, hasAlpha bool) error {
|
||||
var err error
|
||||
|
||||
imgWidth, imgHeight, angle, flip := extractMeta(*img)
|
||||
if po.Dpr != 1 {
|
||||
if po.Width != 0 {
|
||||
po.Width = int(float32(po.Width) * po.Dpr)
|
||||
}
|
||||
if po.Height != 0 {
|
||||
po.Height = int(float32(po.Height) * po.Dpr)
|
||||
}
|
||||
}
|
||||
// Ensure we won't crop out of bounds
|
||||
if !po.Enlarge || po.Resize == resizeCrop {
|
||||
if imgWidth < po.Width {
|
||||
po.Width = imgWidth
|
||||
}
|
||||
|
||||
if imgHeight < po.Height {
|
||||
po.Height = imgHeight
|
||||
}
|
||||
}
|
||||
|
||||
hasAlpha := vipsImageHasAlpha(*img)
|
||||
|
||||
if needToScale(imgWidth, imgHeight, po) {
|
||||
scale := calcScale(imgWidth, imgHeight, po)
|
||||
|
||||
// Do some shrink-on-load
|
||||
if scale < 1.0 && data != nil {
|
||||
if shrink := calcShink(scale, imgtype); shrink != 1 {
|
||||
scale = scale * float64(shrink)
|
||||
|
||||
if tmp, err := vipsLoadImage(data, imgtype, shrink, false); err == nil {
|
||||
C.swap_and_clear(img, tmp)
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
premultiplied := false
|
||||
var bandFormat C.VipsBandFormat
|
||||
|
||||
@ -276,14 +252,42 @@ func transformImage(ctx context.Context, img **C.struct__VipsImage, data []byte,
|
||||
return err
|
||||
}
|
||||
|
||||
// Update actual image size after resize
|
||||
imgWidth, imgHeight, _, _ = extractMeta(*img)
|
||||
|
||||
if premultiplied {
|
||||
if err = vipsUnpremultiply(img, bandFormat); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func transformImage(ctx context.Context, img **C.struct__VipsImage, data []byte, po *processingOptions, imgtype imageType) error {
|
||||
var err error
|
||||
|
||||
imgWidth, imgHeight, angle, flip := extractMeta(*img)
|
||||
|
||||
hasAlpha := vipsImageHasAlpha(*img)
|
||||
|
||||
if scale := calcScale(imgWidth, imgHeight, po); scale != 1 {
|
||||
// Do some shrink-on-load
|
||||
if scale < 1.0 && data != nil {
|
||||
if shrink := calcShink(scale, imgtype); shrink != 1 {
|
||||
scale = scale * float64(shrink)
|
||||
|
||||
if tmp, err := vipsLoadImage(data, imgtype, shrink, false); err == nil {
|
||||
C.swap_and_clear(img, tmp)
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err = resizeImage(img, scale, hasAlpha); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Update actual image size after resize
|
||||
imgWidth, imgHeight, _, _ = extractMeta(*img)
|
||||
}
|
||||
|
||||
if err = vipsImportColourProfile(img); err != nil {
|
||||
@ -312,20 +316,31 @@ func transformImage(ctx context.Context, img **C.struct__VipsImage, data []byte,
|
||||
|
||||
checkTimeout(ctx)
|
||||
|
||||
if po.Width == 0 {
|
||||
po.Width = imgWidth
|
||||
cropW, cropH := po.Width, po.Height
|
||||
|
||||
if po.Dpr < 1 || (po.Dpr > 1 && po.Resize != resizeCrop) {
|
||||
cropW = int(float64(cropW) * po.Dpr)
|
||||
cropH = int(float64(cropH) * po.Dpr)
|
||||
}
|
||||
|
||||
if po.Height == 0 {
|
||||
po.Height = imgHeight
|
||||
if cropW == 0 {
|
||||
cropW = imgWidth
|
||||
} else {
|
||||
cropW = minInt(cropW, imgWidth)
|
||||
}
|
||||
|
||||
if po.Width < imgWidth || po.Height < imgHeight {
|
||||
if cropH == 0 {
|
||||
cropH = imgHeight
|
||||
} else {
|
||||
cropH = minInt(cropH, imgHeight)
|
||||
}
|
||||
|
||||
if cropW < imgWidth || cropH < imgHeight {
|
||||
if po.Gravity.Type == gravitySmart {
|
||||
if err = vipsImageCopyMemory(img); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = vipsSmartCrop(img, po.Width, po.Height); err != nil {
|
||||
if err = vipsSmartCrop(img, cropW, cropH); err != nil {
|
||||
return err
|
||||
}
|
||||
// Applying additional modifications after smart crop causes SIGSEGV on Alpine
|
||||
@ -334,8 +349,8 @@ func transformImage(ctx context.Context, img **C.struct__VipsImage, data []byte,
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
left, top := calcCrop(imgWidth, imgHeight, po)
|
||||
if err = vipsCrop(img, left, top, po.Width, po.Height); err != nil {
|
||||
left, top := calcCrop(imgWidth, imgHeight, cropW, cropH, &po.Gravity)
|
||||
if err = vipsCrop(img, left, top, cropW, cropH); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -343,6 +358,16 @@ func transformImage(ctx context.Context, img **C.struct__VipsImage, data []byte,
|
||||
checkTimeout(ctx)
|
||||
}
|
||||
|
||||
if po.Enlarge && po.Resize == resizeCrop && po.Dpr > 1 {
|
||||
// We didn't enlarge the image before, because is wasn't optimal. Now it's time to do it
|
||||
if err = resizeImage(img, po.Dpr, hasAlpha); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = vipsImageCopyMemory(img); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if hasAlpha && (po.Flatten || po.Format == imageTypeJPEG) {
|
||||
if err = vipsFlatten(img, po.Background); err != nil {
|
||||
return err
|
||||
|
@ -117,13 +117,13 @@ type processingOptions struct {
|
||||
Resize resizeType
|
||||
Width int
|
||||
Height int
|
||||
Dpr float64
|
||||
Gravity gravityOptions
|
||||
Enlarge bool
|
||||
Format imageType
|
||||
Quality int
|
||||
Flatten bool
|
||||
Background color
|
||||
Dpr float32
|
||||
Blur float32
|
||||
Sharpen float32
|
||||
|
||||
@ -140,6 +140,7 @@ const (
|
||||
imageURLCtxKey = ctxKey("imageUrl")
|
||||
processingOptionsCtxKey = ctxKey("processingOptions")
|
||||
urlTokenPlain = "plain"
|
||||
maxClientHintDPR = 8
|
||||
)
|
||||
|
||||
var (
|
||||
@ -374,6 +375,20 @@ func applyResizeOption(po *processingOptions, args []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func applyDprOption(po *processingOptions, args []string) error {
|
||||
if len(args) > 1 {
|
||||
return fmt.Errorf("Invalid dpr arguments: %v", args)
|
||||
}
|
||||
|
||||
if d, err := strconv.ParseFloat(args[0], 64); err == nil || (d > 0 && d != 1) {
|
||||
po.Dpr = d
|
||||
} else {
|
||||
return fmt.Errorf("Invalid dpr: %s", args[0])
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func applyGravityOption(po *processingOptions, args []string) error {
|
||||
if g, ok := gravityTypes[args[0]]; ok {
|
||||
po.Gravity.Type = g
|
||||
@ -472,19 +487,6 @@ func applyBlurOption(po *processingOptions, args []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func applyDprOption(po *processingOptions, args []string) error {
|
||||
if len(args) > 1 {
|
||||
return fmt.Errorf("Invalid dpr arguments: %v", args)
|
||||
}
|
||||
|
||||
if d, err := strconv.ParseFloat(args[0], 32); err == nil || (d > 0 && d != 1) {
|
||||
po.Dpr = float32(d)
|
||||
} else {
|
||||
return fmt.Errorf("Invalid dpr: %s", args[0])
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
func applySharpenOption(po *processingOptions, args []string) error {
|
||||
if len(args) > 1 {
|
||||
return fmt.Errorf("Invalid sharpen arguments: %v", args)
|
||||
@ -631,6 +633,10 @@ func applyProcessingOption(po *processingOptions, name string, args []string) er
|
||||
if err := applyEnlargeOption(po, args); err != nil {
|
||||
return err
|
||||
}
|
||||
case "dpr":
|
||||
if err := applyDprOption(po, args); err != nil {
|
||||
return err
|
||||
}
|
||||
case "gravity", "g":
|
||||
if err := applyGravityOption(po, args); err != nil {
|
||||
return err
|
||||
@ -663,10 +669,6 @@ func applyProcessingOption(po *processingOptions, name string, args []string) er
|
||||
if err := applyCacheBusterOption(po, args); err != nil {
|
||||
return err
|
||||
}
|
||||
case "dpr":
|
||||
if err := applyDprOption(po, args); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("Unknown processing option: %s", name)
|
||||
}
|
||||
@ -743,8 +745,8 @@ func defaultProcessingOptions(headers *processingHeaders) (*processingOptions, e
|
||||
}
|
||||
}
|
||||
if conf.EnableClientHints && len(headers.DPR) > 0 {
|
||||
if dpr, err := strconv.ParseFloat(headers.DPR, 32); err == nil || (dpr > 0 && dpr <= 8) {
|
||||
po.Dpr = float32(dpr)
|
||||
if dpr, err := strconv.ParseFloat(headers.DPR, 64); err == nil || (dpr > 0 && dpr <= maxClientHintDPR) {
|
||||
po.Dpr = dpr
|
||||
}
|
||||
}
|
||||
if _, ok := conf.Presets["default"]; ok {
|
||||
|
Loading…
Reference in New Issue
Block a user