Optimal and correct DPR support

This commit is contained in:
DarthSim
2018-11-15 17:19:27 +06:00
parent 8b5f620ac3
commit c3852d683d
2 changed files with 124 additions and 97 deletions

View File

@@ -134,19 +134,26 @@ func extractMeta(img *C.VipsImage) (int, int, int, bool) {
return width, height, angle, flip return width, height, angle, flip
} }
func needToScale(width, height int, po *processingOptions) bool { func calcScale(width, height int, po *processingOptions) float64 {
return ((po.Width != 0 && po.Width != width) || (po.Height != 0 && po.Height != height)) && // If we're going only to crop, we need only to scale down to DPR.
(po.Resize == resizeFill || po.Resize == resizeFit) // 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) 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 wr := float64(po.Width) / srcW
hr := float64(po.Height) / srcH hr := float64(po.Height) / srcH
var scale float64
if po.Width == 0 { if po.Width == 0 {
scale = hr scale = hr
} else if po.Height == 0 { } else if po.Height == 0 {
@@ -156,6 +163,13 @@ func calcScale(width, height int, po *processingOptions) float64 {
} else { } else {
scale = math.Max(wr, hr) scale = math.Max(wr, hr)
} }
}
scale = scale * po.Dpr
if !po.Enlarge && scale > 1 {
return 1
}
if srcW*scale < 1 { if srcW*scale < 1 {
scale = 1 / srcW scale = 1 / srcW
@@ -188,80 +202,42 @@ func calcShink(scale float64, imgtype imageType) int {
return 1 return 1
} }
func calcCrop(width, height int, po *processingOptions) (left, top int) { func calcCrop(width, height, cropWidth, cropHeight int, gravity *gravityOptions) (left, top int) {
if po.Gravity.Type == gravityFocusPoint { if gravity.Type == gravityFocusPoint {
pointX := int(float64(width) * po.Gravity.X) pointX := int(float64(width) * gravity.X)
pointY := int(float64(height) * po.Gravity.Y) pointY := int(float64(height) * gravity.Y)
left = maxInt(0, minInt(pointX-po.Width/2, width-po.Width)) left = maxInt(0, minInt(pointX-cropWidth/2, width-cropWidth))
top = maxInt(0, minInt(pointY-po.Height/2, height-po.Height)) top = maxInt(0, minInt(pointY-cropHeight/2, height-cropHeight))
return return
} }
left = (width - po.Width + 1) / 2 left = (width - cropWidth + 1) / 2
top = (height - po.Height + 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 top = 0
} }
if po.Gravity.Type == gravityEast || po.Gravity.Type == gravityNorthEast || po.Gravity.Type == gravitySouthEast { if gravity.Type == gravityEast || gravity.Type == gravityNorthEast || gravity.Type == gravitySouthEast {
left = width - po.Width left = width - cropWidth
} }
if po.Gravity.Type == gravitySouth || po.Gravity.Type == gravitySouthEast || po.Gravity.Type == gravitySouthWest { if gravity.Type == gravitySouth || gravity.Type == gravitySouthEast || gravity.Type == gravitySouthWest {
top = height - po.Height 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 left = 0
} }
return 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 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 premultiplied := false
var bandFormat C.VipsBandFormat var bandFormat C.VipsBandFormat
@@ -276,14 +252,42 @@ func transformImage(ctx context.Context, img **C.struct__VipsImage, data []byte,
return err return err
} }
// Update actual image size after resize
imgWidth, imgHeight, _, _ = extractMeta(*img)
if premultiplied { if premultiplied {
if err = vipsUnpremultiply(img, bandFormat); err != nil { if err = vipsUnpremultiply(img, bandFormat); err != nil {
return err 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 { if err = vipsImportColourProfile(img); err != nil {
@@ -312,20 +316,31 @@ func transformImage(ctx context.Context, img **C.struct__VipsImage, data []byte,
checkTimeout(ctx) checkTimeout(ctx)
if po.Width == 0 { cropW, cropH := po.Width, po.Height
po.Width = imgWidth
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 { if cropW == 0 {
po.Height = imgHeight 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 po.Gravity.Type == gravitySmart {
if err = vipsImageCopyMemory(img); err != nil { if err = vipsImageCopyMemory(img); err != nil {
return err return err
} }
if err = vipsSmartCrop(img, po.Width, po.Height); err != nil { if err = vipsSmartCrop(img, cropW, cropH); err != nil {
return err return err
} }
// Applying additional modifications after smart crop causes SIGSEGV on Alpine // 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 return err
} }
} else { } else {
left, top := calcCrop(imgWidth, imgHeight, po) left, top := calcCrop(imgWidth, imgHeight, cropW, cropH, &po.Gravity)
if err = vipsCrop(img, left, top, po.Width, po.Height); err != nil { if err = vipsCrop(img, left, top, cropW, cropH); err != nil {
return err return err
} }
} }
@@ -343,6 +358,16 @@ func transformImage(ctx context.Context, img **C.struct__VipsImage, data []byte,
checkTimeout(ctx) 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 hasAlpha && (po.Flatten || po.Format == imageTypeJPEG) {
if err = vipsFlatten(img, po.Background); err != nil { if err = vipsFlatten(img, po.Background); err != nil {
return err return err

View File

@@ -117,13 +117,13 @@ type processingOptions struct {
Resize resizeType Resize resizeType
Width int Width int
Height int Height int
Dpr float64
Gravity gravityOptions Gravity gravityOptions
Enlarge bool Enlarge bool
Format imageType Format imageType
Quality int Quality int
Flatten bool Flatten bool
Background color Background color
Dpr float32
Blur float32 Blur float32
Sharpen float32 Sharpen float32
@@ -140,6 +140,7 @@ const (
imageURLCtxKey = ctxKey("imageUrl") imageURLCtxKey = ctxKey("imageUrl")
processingOptionsCtxKey = ctxKey("processingOptions") processingOptionsCtxKey = ctxKey("processingOptions")
urlTokenPlain = "plain" urlTokenPlain = "plain"
maxClientHintDPR = 8
) )
var ( var (
@@ -374,6 +375,20 @@ func applyResizeOption(po *processingOptions, args []string) error {
return nil 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 { func applyGravityOption(po *processingOptions, args []string) error {
if g, ok := gravityTypes[args[0]]; ok { if g, ok := gravityTypes[args[0]]; ok {
po.Gravity.Type = g po.Gravity.Type = g
@@ -472,19 +487,6 @@ func applyBlurOption(po *processingOptions, args []string) error {
return nil 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 { func applySharpenOption(po *processingOptions, args []string) error {
if len(args) > 1 { if len(args) > 1 {
return fmt.Errorf("Invalid sharpen arguments: %v", args) 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 { if err := applyEnlargeOption(po, args); err != nil {
return err return err
} }
case "dpr":
if err := applyDprOption(po, args); err != nil {
return err
}
case "gravity", "g": case "gravity", "g":
if err := applyGravityOption(po, args); err != nil { if err := applyGravityOption(po, args); err != nil {
return err return err
@@ -663,10 +669,6 @@ func applyProcessingOption(po *processingOptions, name string, args []string) er
if err := applyCacheBusterOption(po, args); err != nil { if err := applyCacheBusterOption(po, args); err != nil {
return err return err
} }
case "dpr":
if err := applyDprOption(po, args); err != nil {
return err
}
default: default:
return fmt.Errorf("Unknown processing option: %s", name) 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 conf.EnableClientHints && len(headers.DPR) > 0 {
if dpr, err := strconv.ParseFloat(headers.DPR, 32); err == nil || (dpr > 0 && dpr <= 8) { if dpr, err := strconv.ParseFloat(headers.DPR, 64); err == nil || (dpr > 0 && dpr <= maxClientHintDPR) {
po.Dpr = float32(dpr) po.Dpr = dpr
} }
} }
if _, ok := conf.Presets["default"]; ok { if _, ok := conf.Presets["default"]; ok {