0
Fork 0
mirror of https://github.com/willnorris/imageproxy.git synced 2024-12-16 21:56:43 -05:00

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
This commit is contained in:
Will Norris 2017-06-01 07:51:14 -07:00
parent 2937bf84f6
commit b9cc9df4b6
4 changed files with 37 additions and 8 deletions

21
data.go
View file

@ -28,6 +28,8 @@ const (
optFit = "fit" optFit = "fit"
optFlipVertical = "fv" optFlipVertical = "fv"
optFlipHorizontal = "fh" optFlipHorizontal = "fh"
optFormatJPEG = "jpeg"
optFormatPNG = "png"
optRotatePrefix = "r" optRotatePrefix = "r"
optQualityPrefix = "q" optQualityPrefix = "q"
optSignaturePrefix = "s" optSignaturePrefix = "s"
@ -71,6 +73,9 @@ type Options struct {
// Allow image to scale beyond its original dimensions. This value // Allow image to scale beyond its original dimensions. This value
// will always be overwritten by the value of Proxy.ScaleUp. // will always be overwritten by the value of Proxy.ScaleUp.
ScaleUp bool ScaleUp bool
// Desired image format. Valid values are "jpeg", "png".
Format string
} }
func (o Options) String() string { func (o Options) String() string {
@ -97,14 +102,18 @@ func (o Options) String() string {
if o.ScaleUp { if o.ScaleUp {
fmt.Fprintf(buf, ",%s", optScaleUp) fmt.Fprintf(buf, ",%s", optScaleUp)
} }
if o.Format != "" {
fmt.Fprintf(buf, ",%s", o.Format)
}
return buf.String() return buf.String()
} }
// transform returns whether o includes transformation options. Some fields // transform returns whether o includes transformation options. Some fields
// are not transform related at all (like Signature), and others only apply in // 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 { 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. // 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 // 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. // 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 // Signature
// //
// The "s{signature}" option specifies an optional base64 encoded HMAC used to // 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,r90 - 100 pixels square, rotated 90 degrees
// 100,fv,fh - 100 pixels square, flipped horizontal and vertical // 100,fv,fh - 100 pixels square, flipped horizontal and vertical
// 200x,q80 - 200 pixels wide, proportional height, 80% quality // 200x,q80 - 200 pixels wide, proportional height, 80% quality
// 200x,png - 200 pixels wide, converted to PNG format
func ParseOptions(str string) Options { func ParseOptions(str string) Options {
var options Options var options Options
@ -189,6 +204,8 @@ func ParseOptions(str string) Options {
options.FlipHorizontal = true options.FlipHorizontal = true
case opt == optScaleUp: // this option is intentionally not documented above case opt == optScaleUp: // this option is intentionally not documented above
options.ScaleUp = true options.ScaleUp = true
case opt == optFormatJPEG, opt == optFormatPNG:
options.Format = opt
case strings.HasPrefix(opt, optRotatePrefix): case strings.HasPrefix(opt, optRotatePrefix):
value := strings.TrimPrefix(opt, optRotatePrefix) value := strings.TrimPrefix(opt, optRotatePrefix)
options.Rotate, _ = strconv.Atoi(value) options.Rotate, _ = strconv.Atoi(value)

View file

@ -31,12 +31,12 @@ func TestOptions_String(t *testing.T) {
"0x0", "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", "1x2,fit,r90,fv,fh,q80",
}, },
{ {
Options{0.15, 1.3, false, 45, false, false, 95, "c0ffee", false}, Options{0.15, 1.3, false, 45, false, false, 95, "c0ffee", false, "png"},
"0.15x1.3,r45,q95,sc0ffee", "0.15x1.3,r45,q95,sc0ffee,png",
}, },
} }
@ -72,6 +72,7 @@ func TestParseOptions(t *testing.T) {
{"r90", Options{Rotate: 90}}, {"r90", Options{Rotate: 90}},
{"fv", Options{FlipVertical: true}}, {"fv", Options{FlipVertical: true}},
{"fh", Options{FlipHorizontal: true}}, {"fh", Options{FlipHorizontal: true}},
{"jpeg", Options{Format: "jpeg"}},
// duplicate flags (last one wins) // duplicate flags (last one wins)
{"1x2,3x4", Options{Width: 3, Height: 4}}, {"1x2,3x4", Options{Width: 3, Height: 4}},
@ -79,13 +80,14 @@ func TestParseOptions(t *testing.T) {
{"1x2,0x3", Options{Width: 0, Height: 3}}, {"1x2,0x3", Options{Width: 0, Height: 3}},
{"1x,x2", Options{Width: 1, Height: 2}}, {"1x,x2", Options{Width: 1, Height: 2}},
{"r90,r270", Options{Rotate: 270}}, {"r90,r270", Options{Rotate: 270}},
{"jpeg,png", Options{Format: "png"}},
// mix of valid and invalid flags // mix of valid and invalid flags
{"FOO,1,BAR,r90,BAZ", Options{Width: 1, Height: 1, Rotate: 90}}, {"FOO,1,BAR,r90,BAZ", Options{Width: 1, Height: 1, Rotate: 90}},
// all flags, in different orders // all flags, in different orders
{"q70,1x2,fit,r90,fv,fh,sc0ffee", Options{1, 2, true, 90, true, true, 70, "c0ffee", false}}, {"q70,1x2,fit,r90,fv,fh,sc0ffee,png", Options{1, 2, true, 90, true, true, 70, "c0ffee", false, "png"}},
{"r90,fh,sc0ffee,q90,1x2,fv,fit", Options{1, 2, true, 90, true, true, 90, "c0ffee", false}}, {"r90,fh,sc0ffee,png,q90,1x2,fv,fit", Options{1, 2, true, 90, true, true, 90, "c0ffee", false, "png"}},
} }
for _, tt := range tests { for _, tt := range tests {

View file

@ -306,7 +306,10 @@ func (t *TransformingTransport) RoundTrip(req *http.Request) (*http.Response, er
// replay response with transformed image and updated content length // replay response with transformed image and updated content length
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
fmt.Fprintf(buf, "%s %s\n", resp.Proto, resp.Status) 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)) fmt.Fprintf(buf, "Content-Length: %d\n\n", len(img))
buf.Write(img) buf.Write(img)

View file

@ -16,6 +16,7 @@ package imageproxy
import ( import (
"bytes" "bytes"
"fmt"
"image" "image"
_ "image/gif" // register gif format _ "image/gif" // register gif format
"image/jpeg" "image/jpeg"
@ -46,6 +47,10 @@ func Transform(img []byte, opt Options) ([]byte, error) {
return nil, err return nil, err
} }
if opt.Format != "" {
format = opt.Format
}
// transform and encode image // transform and encode image
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
switch format { switch format {
@ -74,6 +79,8 @@ func Transform(img []byte, opt Options) ([]byte, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
default:
return nil, fmt.Errorf("unsupported format: %v", format)
} }
return buf.Bytes(), nil return buf.Bytes(), nil