From b9cc9df4b65727be5a43bfed1648b9f804160900 Mon Sep 17 00:00:00 2001 From: Will Norris Date: Thu, 1 Jun 2017 07:51:14 -0700 Subject: [PATCH] add support for specifying output image format For now, the options are "jpeg" and "png". Gif is a little harder to support because of the way we use the image/gif package to handle animated gifs. I have also have trouble imagining someone wanting to use gif over png. But if the need really exists, we can address it when it comes up. Fixes #89 --- data.go | 21 +++++++++++++++++++-- data_test.go | 12 +++++++----- imageproxy.go | 5 ++++- transform.go | 7 +++++++ 4 files changed, 37 insertions(+), 8 deletions(-) diff --git a/data.go b/data.go index 6eae282..2e2c226 100644 --- a/data.go +++ b/data.go @@ -28,6 +28,8 @@ const ( optFit = "fit" optFlipVertical = "fv" optFlipHorizontal = "fh" + optFormatJPEG = "jpeg" + optFormatPNG = "png" optRotatePrefix = "r" optQualityPrefix = "q" optSignaturePrefix = "s" @@ -71,6 +73,9 @@ type Options struct { // Allow image to scale beyond its original dimensions. This value // will always be overwritten by the value of Proxy.ScaleUp. ScaleUp bool + + // Desired image format. Valid values are "jpeg", "png". + Format string } func (o Options) String() string { @@ -97,14 +102,18 @@ func (o Options) String() string { if o.ScaleUp { fmt.Fprintf(buf, ",%s", optScaleUp) } + if o.Format != "" { + fmt.Fprintf(buf, ",%s", o.Format) + } return buf.String() } // transform returns whether o includes transformation options. Some fields // are not transform related at all (like Signature), and others only apply in -// the presence of other fields (like Fit and Quality). +// the presence of other fields (like Fit and Quality). A non-empty Format +// value is assumed to involve a transformation. func (o Options) transform() bool { - return o.Width != 0 || o.Height != 0 || o.Rotate != 0 || o.FlipHorizontal || o.FlipVertical + return o.Width != 0 || o.Height != 0 || o.Rotate != 0 || o.FlipHorizontal || o.FlipVertical || o.Format != "" } // ParseOptions parses str as a list of comma separated transformation options. @@ -153,6 +162,11 @@ func (o Options) transform() bool { // The "q{qualityPercentage}" option can be used to specify the quality of the // output file (JPEG only). If not specified, the default value of "95" is used. // +// Format +// +// The "jpeg" and "png" options can be used to specify the desired image format +// of the proxied image. +// // Signature // // The "s{signature}" option specifies an optional base64 encoded HMAC used to @@ -174,6 +188,7 @@ func (o Options) transform() bool { // 100,r90 - 100 pixels square, rotated 90 degrees // 100,fv,fh - 100 pixels square, flipped horizontal and vertical // 200x,q80 - 200 pixels wide, proportional height, 80% quality +// 200x,png - 200 pixels wide, converted to PNG format func ParseOptions(str string) Options { var options Options @@ -189,6 +204,8 @@ func ParseOptions(str string) Options { options.FlipHorizontal = true case opt == optScaleUp: // this option is intentionally not documented above options.ScaleUp = true + case opt == optFormatJPEG, opt == optFormatPNG: + options.Format = opt case strings.HasPrefix(opt, optRotatePrefix): value := strings.TrimPrefix(opt, optRotatePrefix) options.Rotate, _ = strconv.Atoi(value) diff --git a/data_test.go b/data_test.go index 2f19ef2..5f79321 100644 --- a/data_test.go +++ b/data_test.go @@ -31,12 +31,12 @@ func TestOptions_String(t *testing.T) { "0x0", }, { - Options{1, 2, true, 90, true, true, 80, "", false}, + Options{1, 2, true, 90, true, true, 80, "", false, ""}, "1x2,fit,r90,fv,fh,q80", }, { - Options{0.15, 1.3, false, 45, false, false, 95, "c0ffee", false}, - "0.15x1.3,r45,q95,sc0ffee", + Options{0.15, 1.3, false, 45, false, false, 95, "c0ffee", false, "png"}, + "0.15x1.3,r45,q95,sc0ffee,png", }, } @@ -72,6 +72,7 @@ func TestParseOptions(t *testing.T) { {"r90", Options{Rotate: 90}}, {"fv", Options{FlipVertical: true}}, {"fh", Options{FlipHorizontal: true}}, + {"jpeg", Options{Format: "jpeg"}}, // duplicate flags (last one wins) {"1x2,3x4", Options{Width: 3, Height: 4}}, @@ -79,13 +80,14 @@ func TestParseOptions(t *testing.T) { {"1x2,0x3", Options{Width: 0, Height: 3}}, {"1x,x2", Options{Width: 1, Height: 2}}, {"r90,r270", Options{Rotate: 270}}, + {"jpeg,png", Options{Format: "png"}}, // mix of valid and invalid flags {"FOO,1,BAR,r90,BAZ", Options{Width: 1, Height: 1, Rotate: 90}}, // all flags, in different orders - {"q70,1x2,fit,r90,fv,fh,sc0ffee", Options{1, 2, true, 90, true, true, 70, "c0ffee", false}}, - {"r90,fh,sc0ffee,q90,1x2,fv,fit", Options{1, 2, true, 90, true, true, 90, "c0ffee", false}}, + {"q70,1x2,fit,r90,fv,fh,sc0ffee,png", Options{1, 2, true, 90, true, true, 70, "c0ffee", false, "png"}}, + {"r90,fh,sc0ffee,png,q90,1x2,fv,fit", Options{1, 2, true, 90, true, true, 90, "c0ffee", false, "png"}}, } for _, tt := range tests { diff --git a/imageproxy.go b/imageproxy.go index 4afc67f..5c0db49 100644 --- a/imageproxy.go +++ b/imageproxy.go @@ -306,7 +306,10 @@ func (t *TransformingTransport) RoundTrip(req *http.Request) (*http.Response, er // replay response with transformed image and updated content length buf := new(bytes.Buffer) fmt.Fprintf(buf, "%s %s\n", resp.Proto, resp.Status) - resp.Header.WriteSubset(buf, map[string]bool{"Content-Length": true}) + resp.Header.WriteSubset(buf, map[string]bool{ + "Content-Length": true, + "Content-Type": opt.Format != "", + }) fmt.Fprintf(buf, "Content-Length: %d\n\n", len(img)) buf.Write(img) diff --git a/transform.go b/transform.go index 4536c61..4c91913 100644 --- a/transform.go +++ b/transform.go @@ -16,6 +16,7 @@ package imageproxy import ( "bytes" + "fmt" "image" _ "image/gif" // register gif format "image/jpeg" @@ -46,6 +47,10 @@ func Transform(img []byte, opt Options) ([]byte, error) { return nil, err } + if opt.Format != "" { + format = opt.Format + } + // transform and encode image buf := new(bytes.Buffer) switch format { @@ -74,6 +79,8 @@ func Transform(img []byte, opt Options) ([]byte, error) { if err != nil { return nil, err } + default: + return nil, fmt.Errorf("unsupported format: %v", format) } return buf.Bytes(), nil