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:
parent
2937bf84f6
commit
b9cc9df4b6
4 changed files with 37 additions and 8 deletions
21
data.go
21
data.go
|
@ -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)
|
||||||
|
|
12
data_test.go
12
data_test.go
|
@ -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 {
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue