瀏覽代碼

adds dpr transformation (#104)

* adds dpr transformation.

* adds dpr transformation.

* fix conflict.
Marius Cristea 6 年之前
父節點
當前提交
8b5f620ac3
共有 5 個文件被更改,包括 79 次插入4 次删除
  1. 2 2
      docs/configuration.md
  2. 10 0
      docs/generating_the_url_advanced.md
  3. 9 2
      process.go
  4. 26 0
      processing_options.go
  5. 32 0
      processing_options_test.go

+ 2 - 2
docs/configuration.md

@@ -73,11 +73,11 @@ When WebP support detection is enabled, please take care to configure your CDN o
 
 ## Client Hints support
 
-imgproxy can use the `Width` or `Viewport-Width` HTTP header to determine the width of the image container using Client Hints when the width argument is ommited.
+imgproxy can use the `Width`, `Viewport-Width` or `DPR` HTTP header to determine the width of the image container using Client Hints when the width/dpr argument is ommited. 
 
 * `IMGPROXY_ENABLE_CLIENT_HINTS`: enables Client Hints support when the width is ommited for automatic responsive images . Read [here](https://developers.google.com/web/updates/2015/09/automating-resource-selection-with-client-hints) details about Client Hints.
 
-**Warning**: Headers cannot be signed. This means that an attacker can bypass your CDN cache by changing the `Width` or `Viewport-Width` HTTP headers. Have this in mind when configuring your production caching setup.
+**Warning**: Headers cannot be signed. This means that an attacker can bypass your CDN cache by changing the `Width`, `Viewport-Width` or `DPR` HTTP headers. Have this in mind when configuring your production caching setup.
 
 ### Watermark
 

+ 10 - 0
docs/generating_the_url_advanced.md

@@ -158,6 +158,16 @@ When set, imgproxy will apply the gaussian blur filter to the resulting image. `
 
 Default: disabled
 
+##### Dpr
+
+```
+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.
+
+Default: `1`
+
 ##### Sharpen
 
 ```

+ 9 - 2
process.go

@@ -223,9 +223,16 @@ func calcCrop(width, height int, po *processingOptions) (left, top int) {
 
 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)
-
+	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 {

+ 26 - 0
processing_options.go

@@ -34,6 +34,7 @@ type processingHeaders struct {
 	Accept        string
 	Width         string
 	ViewportWidth string
+	DPR           string
 }
 
 var imageTypes = map[string]imageType{
@@ -122,6 +123,7 @@ type processingOptions struct {
 	Quality    int
 	Flatten    bool
 	Background color
+	Dpr        float32
 	Blur       float32
 	Sharpen    float32
 
@@ -470,6 +472,19 @@ 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)
@@ -648,6 +663,10 @@ 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)
 	}
@@ -705,6 +724,7 @@ func defaultProcessingOptions(headers *processingHeaders) (*processingOptions, e
 		Background:  color{255, 255, 255},
 		Blur:        0,
 		Sharpen:     0,
+		Dpr:         1,
 		Watermark:   watermarkOptions{Opacity: 1, Replicate: false, Gravity: gravityCenter},
 		UsedPresets: make([]string, 0, len(conf.Presets)),
 	}
@@ -722,6 +742,11 @@ func defaultProcessingOptions(headers *processingHeaders) (*processingOptions, e
 			po.Width = w
 		}
 	}
+	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 _, ok := conf.Presets["default"]; ok {
 		err = applyPresetOption(&po, []string{"default"})
 	}
@@ -817,6 +842,7 @@ func parsePath(ctx context.Context, r *http.Request) (context.Context, error) {
 		Accept:        r.Header.Get("Accept"),
 		Width:         r.Header.Get("Width"),
 		ViewportWidth: r.Header.Get("Viewport-Width"),
+		DPR:           r.Header.Get("DPR"),
 	}
 
 	var imageURL string

+ 32 - 0
processing_options_test.go

@@ -307,7 +307,15 @@ func (s *ProcessingOptionsTestSuite) TestParsePathAdvancedSharpen() {
 	po := getProcessingOptions(ctx)
 	assert.Equal(s.T(), float32(0.2), po.Sharpen)
 }
+func (s *ProcessingOptionsTestSuite) TestParsePathAdvancedDpr() {
+	req := s.getRequest("http://example.com/unsafe/dpr:2/plain/http://images.dev/lorem/ipsum.jpg")
+	ctx, err := parsePath(context.Background(), req)
+
+	require.Nil(s.T(), err)
 
+	po := getProcessingOptions(ctx)
+	assert.Equal(s.T(), float32(2), po.Dpr)
+}
 func (s *ProcessingOptionsTestSuite) TestParsePathAdvancedWatermark() {
 	req := s.getRequest("http://example.com/unsafe/watermark:0.5:soea:10:20:0.6/plain/http://images.dev/lorem/ipsum.jpg")
 	ctx, err := parsePath(context.Background(), req)
@@ -500,6 +508,30 @@ func (s *ProcessingOptionsTestSuite) TestParsePathViewportWidthHeaderRedefine()
 	assert.Equal(s.T(), 150, po.Width)
 }
 
+func (s *ProcessingOptionsTestSuite) TestParsePathDprHeader() {
+	conf.EnableClientHints = true
+
+	req := s.getRequest("http://example.com/unsafe/plain/http://images.dev/lorem/ipsum.jpg@png")
+	req.Header.Set("DPR", "2")
+	ctx, err := parsePath(context.Background(), req)
+
+	require.Nil(s.T(), err)
+
+	po := getProcessingOptions(ctx)
+	assert.Equal(s.T(), float32(2), po.Dpr)
+}
+
+func (s *ProcessingOptionsTestSuite) TestParsePathDprHeaderDisabled() {
+	req := s.getRequest("http://example.com/unsafe/plain/http://images.dev/lorem/ipsum.jpg@png")
+	req.Header.Set("DPR", "2")
+	ctx, err := parsePath(context.Background(), req)
+
+	require.Nil(s.T(), err)
+
+	po := getProcessingOptions(ctx)
+	assert.Equal(s.T(), float32(1), po.Dpr)
+}
+
 func (s *ProcessingOptionsTestSuite) TestParsePathSigned() {
 	conf.Key = []byte("test-key")
 	conf.Salt = []byte("test-salt")