1
0
mirror of https://github.com/imgproxy/imgproxy.git synced 2025-11-25 22:41:43 +02:00

Fix the way the dpr processing option affects offsets and paddings

This commit is contained in:
DarthSim
2023-04-16 20:20:44 +03:00
parent ee0cb9feda
commit 2c28252966
14 changed files with 101 additions and 44 deletions

View File

@@ -7,6 +7,7 @@
### Fix ### Fix
- Fix detection of dead HTTP/2 connections. - Fix detection of dead HTTP/2 connections.
- Fix the way the `dpr` processing option affects offsets and paddings.
### Remove ### Remove
- Remove suport for `Viewport-Width` client hint. - Remove suport for `Viewport-Width` client hint.

View File

@@ -135,6 +135,8 @@ When set, imgproxy will multiply the image dimensions according to these factors
Can be combined with `width` and `height` options. In this case, imgproxy calculates scale factors for the provided size and then multiplies it with the provided zoom factors. Can be combined with `width` and `height` options. In this case, imgproxy calculates scale factors for the provided size and then multiplies it with the provided zoom factors.
**📝 Note:** Unlike the `dpr` option, the `zoom` option doesn't affect gravities offsets, watermark offsets, and paddings.
**📝 Note:** Unlike [dpr](#dpr), `zoom` doesn't set the `Content-DPR` header in the response. **📝 Note:** Unlike [dpr](#dpr), `zoom` doesn't set the `Content-DPR` header in the response.
Default: `1` Default: `1`
@@ -147,6 +149,8 @@ dpr:%dpr
When set, imgproxy will multiply the image dimensions according to this factor for HiDPI (Retina) devices. The value must be greater than 0. When set, imgproxy will multiply the image dimensions according to this factor for HiDPI (Retina) devices. The value must be greater than 0.
**📝 Note:** The `dpr` option affects gravities offsets, watermark offsets, and paddings to make the resulting image structures with and without the `dpr` option applied match.
**📝 Note:** `dpr` also sets the `Content-DPR` header in the response so the browser can correctly render the image. **📝 Note:** `dpr` also sets the `Content-DPR` header in the response so the browser can correctly render the image.
Default: `1` Default: `1`

View File

@@ -31,6 +31,10 @@ func Round(a float64) int {
return int(math.Round(a)) return int(math.Round(a))
} }
func RoundToEven(a float64) int {
return int(math.RoundToEven(a))
}
func Scale(a int, scale float64) int { func Scale(a int, scale float64) int {
if a == 0 { if a == 0 {
return 0 return 0
@@ -39,6 +43,14 @@ func Scale(a int, scale float64) int {
return Round(float64(a) * scale) return Round(float64(a) * scale)
} }
func ScaleToEven(a int, scale float64) int {
if a == 0 {
return 0
}
return RoundToEven(float64(a) * scale)
}
func Shrink(a int, shrink float64) int { func Shrink(a int, shrink float64) int {
if a == 0 { if a == 0 {
return 0 return 0

View File

@@ -5,7 +5,7 @@ import (
"github.com/imgproxy/imgproxy/v3/options" "github.com/imgproxy/imgproxy/v3/options"
) )
func calcPosition(width, height, innerWidth, innerHeight int, gravity *options.GravityOptions, allowOverflow bool) (left, top int) { func calcPosition(width, height, innerWidth, innerHeight int, gravity *options.GravityOptions, dpr float64, allowOverflow bool) (left, top int) {
if gravity.Type == options.GravityFocusPoint { if gravity.Type == options.GravityFocusPoint {
pointX := imath.Scale(width, gravity.X) pointX := imath.Scale(width, gravity.X)
pointY := imath.Scale(height, gravity.Y) pointY := imath.Scale(height, gravity.Y)
@@ -13,7 +13,7 @@ func calcPosition(width, height, innerWidth, innerHeight int, gravity *options.G
left = pointX - innerWidth/2 left = pointX - innerWidth/2
top = pointY - innerHeight/2 top = pointY - innerHeight/2
} else { } else {
offX, offY := int(gravity.X), int(gravity.Y) offX, offY := int(gravity.X*dpr), int(gravity.Y*dpr)
left = (width-innerWidth+1)/2 + offX left = (width-innerWidth+1)/2 + offX
top = (height-innerHeight+1)/2 + offY top = (height-innerHeight+1)/2 + offY

View File

@@ -7,7 +7,7 @@ import (
"github.com/imgproxy/imgproxy/v3/vips" "github.com/imgproxy/imgproxy/v3/vips"
) )
func cropImage(img *vips.Image, cropWidth, cropHeight int, gravity *options.GravityOptions) error { func cropImage(img *vips.Image, cropWidth, cropHeight int, gravity *options.GravityOptions, offsetScale float64) error {
if cropWidth == 0 && cropHeight == 0 { if cropWidth == 0 && cropHeight == 0 {
return nil return nil
} }
@@ -28,7 +28,7 @@ func cropImage(img *vips.Image, cropWidth, cropHeight int, gravity *options.Grav
return img.SmartCrop(cropWidth, cropHeight) return img.SmartCrop(cropWidth, cropHeight)
} }
left, top := calcPosition(imgWidth, imgHeight, cropWidth, cropHeight, gravity, false) left, top := calcPosition(imgWidth, imgHeight, cropWidth, cropHeight, gravity, offsetScale, false)
return img.Crop(left, top, cropWidth, cropHeight) return img.Crop(left, top, cropWidth, cropHeight)
} }
@@ -43,12 +43,13 @@ func crop(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptions,
width, height = height, width width, height = height, width
} }
return cropImage(img, width, height, &opts) // Since we crop before scaling, we shouldn't consider DPR
return cropImage(img, width, height, &opts, 1.0)
} }
func cropToResult(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptions, imgdata *imagedata.ImageData) error { func cropToResult(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptions, imgdata *imagedata.ImageData) error {
// Crop image to the result size // Crop image to the result size
resultWidth, resultHeight := resultSize(po) resultWidth, resultHeight := resultSize(po, pctx.dprScale)
if po.ResizingType == options.ResizeFillDown { if po.ResizingType == options.ResizeFillDown {
diffW := float64(resultWidth) / float64(img.Width()) diffW := float64(resultWidth) / float64(img.Width())
@@ -65,5 +66,5 @@ func cropToResult(pctx *pipelineContext, img *vips.Image, po *options.Processing
} }
} }
return cropImage(img, resultWidth, resultHeight, &po.Gravity) return cropImage(img, resultWidth, resultHeight, &po.Gravity, pctx.dprScale)
} }

View File

@@ -7,7 +7,7 @@ import (
"github.com/imgproxy/imgproxy/v3/vips" "github.com/imgproxy/imgproxy/v3/vips"
) )
func extendImage(img *vips.Image, resultWidth, resultHeight int, opts *options.ExtendOptions, extendAr bool) error { func extendImage(img *vips.Image, resultWidth, resultHeight int, opts *options.ExtendOptions, offsetScale float64, extendAr bool) error {
if !opts.Enabled || (resultWidth <= img.Width() && resultHeight <= img.Height()) { if !opts.Enabled || (resultWidth <= img.Width() && resultHeight <= img.Height()) {
return nil return nil
} }
@@ -31,16 +31,16 @@ func extendImage(img *vips.Image, resultWidth, resultHeight int, opts *options.E
} }
} }
offX, offY := calcPosition(resultWidth, resultHeight, img.Width(), img.Height(), &opts.Gravity, false) offX, offY := calcPosition(resultWidth, resultHeight, img.Width(), img.Height(), &opts.Gravity, offsetScale, false)
return img.Embed(resultWidth, resultHeight, offX, offY) return img.Embed(resultWidth, resultHeight, offX, offY)
} }
func extend(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptions, imgdata *imagedata.ImageData) error { func extend(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptions, imgdata *imagedata.ImageData) error {
resultWidth, resultHeight := resultSize(po) resultWidth, resultHeight := resultSize(po, pctx.dprScale)
return extendImage(img, resultWidth, resultHeight, &po.Extend, false) return extendImage(img, resultWidth, resultHeight, &po.Extend, pctx.dprScale, false)
} }
func extendAspectRatio(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptions, imgdata *imagedata.ImageData) error { func extendAspectRatio(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptions, imgdata *imagedata.ImageData) error {
resultWidth, resultHeight := resultSize(po) resultWidth, resultHeight := resultSize(po, pctx.dprScale)
return extendImage(img, resultWidth, resultHeight, &po.ExtendAspectRatio, true) return extendImage(img, resultWidth, resultHeight, &po.ExtendAspectRatio, pctx.dprScale, true)
} }

View File

@@ -12,10 +12,10 @@ func padding(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptio
return nil return nil
} }
paddingTop := imath.Scale(po.Padding.Top, po.Dpr) paddingTop := imath.Scale(po.Padding.Top, pctx.dprScale)
paddingRight := imath.Scale(po.Padding.Right, po.Dpr) paddingRight := imath.Scale(po.Padding.Right, pctx.dprScale)
paddingBottom := imath.Scale(po.Padding.Bottom, po.Dpr) paddingBottom := imath.Scale(po.Padding.Bottom, pctx.dprScale)
paddingLeft := imath.Scale(po.Padding.Left, po.Dpr) paddingLeft := imath.Scale(po.Padding.Left, pctx.dprScale)
return img.Embed( return img.Embed(
img.Width()+paddingLeft+paddingRight, img.Width()+paddingLeft+paddingRight,

View File

@@ -29,6 +29,8 @@ type pipelineContext struct {
wscale float64 wscale float64
hscale float64 hscale float64
dprScale float64
iccImported bool iccImported bool
} }
@@ -59,5 +61,7 @@ func (p pipeline) Run(ctx context.Context, img *vips.Image, po *options.Processi
} }
} }
img.SetDouble("imgproxy-dpr-scale", pctx.dprScale)
return nil return nil
} }

View File

@@ -41,7 +41,7 @@ func extractMeta(img *vips.Image, baseAngle int, useOrientation bool) (int, int,
return width, height, angle, flip return width, height, angle, flip
} }
func calcScale(width, height int, po *options.ProcessingOptions, imgtype imagetype.Type) (float64, float64) { func calcScale(width, height int, po *options.ProcessingOptions, imgtype imagetype.Type) (float64, float64, float64) {
var wshrink, hshrink float64 var wshrink, hshrink float64
srcW, srcH := float64(width), float64(height) srcW, srcH := float64(width), float64(height)
@@ -67,9 +67,6 @@ func calcScale(width, height int, po *options.ProcessingOptions, imgtype imagety
hshrink = srcH / dstH hshrink = srcH / dstH
} }
wshrink /= po.Dpr
hshrink /= po.Dpr
if wshrink != 1 || hshrink != 1 { if wshrink != 1 || hshrink != 1 {
rt := po.ResizingType rt := po.ResizingType
@@ -101,17 +98,24 @@ func calcScale(width, height int, po *options.ProcessingOptions, imgtype imagety
wshrink /= po.ZoomWidth wshrink /= po.ZoomWidth
hshrink /= po.ZoomHeight hshrink /= po.ZoomHeight
dprScale := po.Dpr
if !po.Enlarge && imgtype != imagetype.SVG { if !po.Enlarge && imgtype != imagetype.SVG {
if wshrink < 1 { minShrink := math.Min(wshrink, hshrink)
hshrink /= wshrink if minShrink < 1 {
wshrink = 1 wshrink /= minShrink
} hshrink /= minShrink
if hshrink < 1 {
wshrink /= hshrink if !po.Extend.Enabled {
hshrink = 1 dprScale /= minShrink
}
} }
dprScale = math.Min(dprScale, math.Min(wshrink, hshrink))
} }
wshrink /= dprScale
hshrink /= dprScale
if po.MinWidth > 0 { if po.MinWidth > 0 {
if minShrink := srcW / float64(po.MinWidth); minShrink < wshrink { if minShrink := srcW / float64(po.MinWidth); minShrink < wshrink {
hshrink /= wshrink / minShrink hshrink /= wshrink / minShrink
@@ -134,7 +138,7 @@ func calcScale(width, height int, po *options.ProcessingOptions, imgtype imagety
hshrink = srcH hshrink = srcH
} }
return 1.0 / wshrink, 1.0 / hshrink return 1.0 / wshrink, 1.0 / hshrink, dprScale
} }
func calcCropSize(orig int, crop float64) int { func calcCropSize(orig int, crop float64) int {
@@ -162,7 +166,7 @@ func prepare(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptio
widthToScale := imath.MinNonZero(pctx.cropWidth, pctx.srcWidth) widthToScale := imath.MinNonZero(pctx.cropWidth, pctx.srcWidth)
heightToScale := imath.MinNonZero(pctx.cropHeight, pctx.srcHeight) heightToScale := imath.MinNonZero(pctx.cropHeight, pctx.srcHeight)
pctx.wscale, pctx.hscale = calcScale(widthToScale, heightToScale, po, pctx.imgtype) pctx.wscale, pctx.hscale, pctx.dprScale = calcScale(widthToScale, heightToScale, po, pctx.imgtype)
return nil return nil
} }

View File

@@ -174,7 +174,12 @@ func transformAnimated(ctx context.Context, img *vips.Image, po *options.Process
} }
if watermarkEnabled && imagedata.Watermark != nil { if watermarkEnabled && imagedata.Watermark != nil {
if err = applyWatermark(img, imagedata.Watermark, &po.Watermark, framesCount); err != nil { dprScale, derr := img.GetDoubleDefault("imgproxy-dpr-scale", 1.0)
if derr != nil {
dprScale = 1.0
}
if err = applyWatermark(img, imagedata.Watermark, &po.Watermark, dprScale, framesCount); err != nil {
return err return err
} }
} }

View File

@@ -5,9 +5,9 @@ import (
"github.com/imgproxy/imgproxy/v3/options" "github.com/imgproxy/imgproxy/v3/options"
) )
func resultSize(po *options.ProcessingOptions) (int, int) { func resultSize(po *options.ProcessingOptions, dprScale float64) (int, int) {
resultWidth := imath.Scale(po.Width, po.Dpr*po.ZoomWidth) resultWidth := imath.Scale(po.Width, dprScale*po.ZoomWidth)
resultHeight := imath.Scale(po.Height, po.Dpr*po.ZoomHeight) resultHeight := imath.Scale(po.Height, dprScale*po.ZoomHeight)
return resultWidth, resultHeight return resultWidth, resultHeight
} }

View File

@@ -2,6 +2,7 @@ package processing
import ( import (
"context" "context"
"math"
"github.com/imgproxy/imgproxy/v3/config" "github.com/imgproxy/imgproxy/v3/config"
"github.com/imgproxy/imgproxy/v3/imagedata" "github.com/imgproxy/imgproxy/v3/imagedata"
@@ -19,7 +20,7 @@ var watermarkPipeline = pipeline{
padding, padding,
} }
func prepareWatermark(wm *vips.Image, wmData *imagedata.ImageData, opts *options.WatermarkOptions, imgWidth, imgHeight, framesCount int) error { func prepareWatermark(wm *vips.Image, wmData *imagedata.ImageData, opts *options.WatermarkOptions, imgWidth, imgHeight int, offsetScale float64, framesCount int) error {
if err := wm.Load(wmData, 1, 1.0, 1); err != nil { if err := wm.Load(wmData, 1, 1.0, 1); err != nil {
return err return err
} }
@@ -36,11 +37,14 @@ func prepareWatermark(wm *vips.Image, wmData *imagedata.ImageData, opts *options
} }
if opts.Replicate { if opts.Replicate {
offX := int(math.RoundToEven(opts.Gravity.X * offsetScale))
offY := int(math.RoundToEven(opts.Gravity.Y * offsetScale))
po.Padding.Enabled = true po.Padding.Enabled = true
po.Padding.Left = int(opts.Gravity.X / 2) po.Padding.Left = offX / 2
po.Padding.Right = int(opts.Gravity.X) - po.Padding.Left po.Padding.Right = offX - po.Padding.Left
po.Padding.Top = int(opts.Gravity.Y / 2) po.Padding.Top = offY / 2
po.Padding.Bottom = int(opts.Gravity.Y) - po.Padding.Top po.Padding.Bottom = offY - po.Padding.Top
} }
if err := watermarkPipeline.Run(context.Background(), wm, po, wmData); err != nil { if err := watermarkPipeline.Run(context.Background(), wm, po, wmData); err != nil {
@@ -61,7 +65,7 @@ func prepareWatermark(wm *vips.Image, wmData *imagedata.ImageData, opts *options
return err return err
} }
} else { } else {
left, top := calcPosition(imgWidth, imgHeight, wm.Width(), wm.Height(), &opts.Gravity, true) left, top := calcPosition(imgWidth, imgHeight, wm.Width(), wm.Height(), &opts.Gravity, offsetScale, true)
if err := wm.Embed(imgWidth, imgHeight, left, top); err != nil { if err := wm.Embed(imgWidth, imgHeight, left, top); err != nil {
return err return err
} }
@@ -76,7 +80,7 @@ func prepareWatermark(wm *vips.Image, wmData *imagedata.ImageData, opts *options
return nil return nil
} }
func applyWatermark(img *vips.Image, wmData *imagedata.ImageData, opts *options.WatermarkOptions, framesCount int) error { func applyWatermark(img *vips.Image, wmData *imagedata.ImageData, opts *options.WatermarkOptions, offsetScale float64, framesCount int) error {
if err := img.RgbColourspace(); err != nil { if err := img.RgbColourspace(); err != nil {
return err return err
} }
@@ -87,7 +91,7 @@ func applyWatermark(img *vips.Image, wmData *imagedata.ImageData, opts *options.
width := img.Width() width := img.Width()
height := img.Height() height := img.Height()
if err := prepareWatermark(wm, wmData, opts, width, height/framesCount, framesCount); err != nil { if err := prepareWatermark(wm, wmData, opts, width, height/framesCount, offsetScale, framesCount); err != nil {
return err return err
} }
@@ -101,5 +105,5 @@ func watermark(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOpt
return nil return nil
} }
return applyWatermark(img, imagedata.Watermark, &po.Watermark, 1) return applyWatermark(img, imagedata.Watermark, &po.Watermark, pctx.dprScale, 1)
} }

View File

@@ -615,7 +615,8 @@ vips_strip(VipsImage *in, VipsImage **out, int keep_exif_copyright) {
(strcmp(name, "yres") == 0) || (strcmp(name, "yres") == 0) ||
(strcmp(name, "vips-loader") == 0) || (strcmp(name, "vips-loader") == 0) ||
(strcmp(name, "background") == 0) || (strcmp(name, "background") == 0) ||
(strcmp(name, "vips-sequential") == 0) (strcmp(name, "vips-sequential") == 0) ||
(strcmp(name, "imgproxy-dpr-scale") == 0)
) continue; ) continue;
if (keep_exif_copyright) { if (keep_exif_copyright) {

View File

@@ -434,6 +434,23 @@ func (img *Image) GetIntSliceDefault(name string, def []int) ([]int, error) {
return img.GetIntSlice(name) return img.GetIntSlice(name)
} }
func (img *Image) GetDouble(name string) (float64, error) {
var d C.double
if C.vips_image_get_double(img.VipsImage, cachedCString(name), &d) != 0 {
return 0, Error()
}
return float64(d), nil
}
func (img *Image) GetDoubleDefault(name string, def float64) (float64, error) {
if C.vips_image_get_typeof(img.VipsImage, cachedCString(name)) == 0 {
return def, nil
}
return img.GetDouble(name)
}
func (img *Image) GetBlob(name string) ([]byte, error) { func (img *Image) GetBlob(name string) ([]byte, error) {
var ( var (
tmp unsafe.Pointer tmp unsafe.Pointer
@@ -458,6 +475,10 @@ func (img *Image) SetIntSlice(name string, value []int) {
C.vips_image_set_array_int_go(img.VipsImage, cachedCString(name), &in[0], C.int(len(value))) C.vips_image_set_array_int_go(img.VipsImage, cachedCString(name), &in[0], C.int(len(value)))
} }
func (img *Image) SetDouble(name string, value float64) {
C.vips_image_set_double(img.VipsImage, cachedCString(name), C.double(value))
}
func (img *Image) SetBlob(name string, value []byte) { func (img *Image) SetBlob(name string, value []byte) {
defer runtime.KeepAlive(value) defer runtime.KeepAlive(value)
C.vips_image_set_blob_copy(img.VipsImage, cachedCString(name), unsafe.Pointer(&value[0]), C.size_t(len(value))) C.vips_image_set_blob_copy(img.VipsImage, cachedCString(name), unsafe.Pointer(&value[0]), C.size_t(len(value)))