0
Fork 0
mirror of https://github.com/willnorris/imageproxy.git synced 2024-12-30 22:34:18 -05:00

add docs and cleanup data code

- simplify ParseOptions
- add lots of docs to ParseOptions and NewRequest
- cleanup some data tests

No behavioral changes.
This commit is contained in:
Will Norris 2014-11-23 15:34:39 -08:00
parent ea67c79ffe
commit 54ddf21df2
2 changed files with 107 additions and 65 deletions

144
data.go
View file

@ -33,17 +33,18 @@ func (e URLError) Error() string {
return fmt.Sprintf("malformed URL %q: %s", e.URL, e.Message)
}
// Options specifies transformations that can be performed on a
// requested image.
// Options specifies transformations to be performed on the requested image.
type Options struct {
Width float64 // requested width, in pixels
Height float64 // requested height, in pixels
// See ParseOptions for interpretation of Width and Height values
Width float64
Height float64
// If true, resize the image to fit in the specified dimensions. Image
// will not be cropped, and aspect ratio will be maintained.
Fit bool
// Rotate image the specified degrees counter-clockwise. Valid values are 90, 180, 270.
// Rotate image the specified degrees counter-clockwise. Valid values
// are 90, 180, 270.
Rotate int
FlipVertical bool
@ -70,64 +71,109 @@ func (o Options) String() string {
return buf.String()
}
// ParseOptions parses str as a list of comma separated transformation options.
// The following options can be specified in any order:
//
// Size and Cropping
//
// The size option takes the general form "{width}x{height}", where width and
// height are numbers. Integer values greater than 1 are interpreted as exact
// pixel values. Floats between 0 and 1 are interpreted as percentages of the
// original image size. If either value is omitted or set to 0, it will be
// automatically set to preserve the aspect ratio based on the other dimension.
// If a single number is provided (with no "x" separator), it will be used for
// both height and width.
//
// Depending on the size options specified, an image may be cropped to fit the
// requested size. In all cases, the original aspect ratio of the image will be
// preserved; imageproxy will never stretch the original image.
//
// When no explicit crop mode is specified, the following rules are followed:
//
// - If both width and height values are specified, the image will be scaled to
// fill the space, cropping if necessary to fit the exact dimension.
//
// - If only one of the width or height values is specified, the image will be
// resized to fit the specified dimension, scaling the other dimension as
// needed to maintain the aspect ratio.
//
// If the "fit" option is specified together with a width and height value, the
// image will be resized to fit within a containing box of the specified size.
// As always, the original aspect ratio will be preserved. Specifying the "fit"
// option with only one of either width or height does the same thing as if
// "fit" had not been specified.
//
// Rotation and Flips
//
// The "r{degrees}" option will rotate the image the specified number of
// degrees, counter-clockwise. Valid degrees values are 90, 180, and 270.
//
// The "fv" option will flip the image vertically. The "fh" option will flip
// the image horizontally. Images are flipped after being rotated.
//
// Examples
//
// 0x0 - no resizing
// 200x - 200 pixels wide, proportional height
// 0.15x - 15% original width, proportional height
// x100 - 100 pixels tall, proportional width
// 100x150 - 100 by 150 pixels, cropping as needed
// 100 - 100 pixels square, cropping as needed
// 150,fit - scale to fit 150 pixels square, no cropping
// 100,r90 - 100 pixels square, rotated 90 degrees
// 100,fv,fh - 100 pixels square, flipped horizontal and vertical
func ParseOptions(str string) Options {
o := Options{}
options := Options{}
parts := strings.Split(str, ",")
for _, part := range parts {
if part == "fit" {
o.Fit = true
continue
for _, opt := range strings.Split(str, ",") {
switch {
case opt == "fit":
options.Fit = true
case opt == "fv":
options.FlipVertical = true
case opt == "fh":
options.FlipHorizontal = true
case len(opt) > 2 && opt[:1] == "r":
options.Rotate, _ = strconv.Atoi(opt[1:])
case strings.ContainsRune(opt, 'x'):
size := strings.SplitN(opt, "x", 2)
if w := size[0]; w != "" {
options.Width, _ = strconv.ParseFloat(w, 64)
}
if part == "fv" {
o.FlipVertical = true
continue
if h := size[1]; h != "" {
options.Height, _ = strconv.ParseFloat(h, 64)
}
if part == "fh" {
o.FlipHorizontal = true
continue
default:
if size, err := strconv.ParseFloat(opt, 64); err == nil {
options.Width = size
options.Height = size
}
if len(part) > 2 && part[:1] == "r" {
o.Rotate, _ = strconv.Atoi(part[1:])
continue
}
if strings.ContainsRune(part, 'x') {
var h, w string
size := strings.SplitN(part, "x", 2)
w = size[0]
if len(size) > 1 {
h = size[1]
} else {
h = w
}
if w != "" {
o.Width, _ = strconv.ParseFloat(w, 64)
}
if h != "" {
o.Height, _ = strconv.ParseFloat(h, 64)
}
continue
}
if size, err := strconv.ParseFloat(part, 64); err == nil {
o.Width = size
o.Height = size
continue
}
}
return o
return options
}
// Request is an imageproxy request which includes a remote URL of an image to
// proxy, and an optional set of transformations to perform.
type Request struct {
URL *url.URL // URL of the image to proxy
Options Options // Image transformation to perform
}
// NewRequest parses an http.Request into an image request.
// NewRequest parses an http.Request into an imageproxy Request. Options and
// the remote image URL are specified in the request path, formatted as:
// /{options}/{remote_url}. Options may be omitted, so a request path may
// simply contian /{remote_url}. The remote URL must be an absolute "http" or
// "https" URL, should not be URL encoded, and may contain a query string.
//
// Assuming an imageproxy server running on localhost, the following are all
// valid imageproxy requests:
//
// http://localhost/100x200/http://example.com/image.jpg
// http://localhost/100x200,r90/http://example.com/image.jpg?foo=bar
// http://localhost//http://example.com/image.jpg
// http://localhost/http://example.com/image.jpg
func NewRequest(r *http.Request) (*Request, error) {
var err error
req := new(Request)
@ -135,7 +181,7 @@ func NewRequest(r *http.Request) (*Request, error) {
path := r.URL.Path[1:] // strip leading slash
req.URL, err = url.Parse(path)
if err != nil || !req.URL.IsAbs() {
// first segment is likely options
// first segment should be options
parts := strings.SplitN(path, "/", 2)
if len(parts) != 2 {
return nil, URLError{"too few path segments", r.URL}

View file

@ -55,7 +55,10 @@ func TestParseOptions(t *testing.T) {
{"1x", Options{Width: 1}},
{"x1", Options{Height: 1}},
{"1x2", Options{Width: 1, Height: 2}},
{"-1x-2", Options{Width: -1, Height: -2}},
{"0.1x0.2", Options{Width: 0.1, Height: 0.2}},
{"1", Options{Width: 1, Height: 1}},
{"0.1", Options{Width: 0.1, Height: 0.1}},
// additional flags
{"fit", Options{Fit: true}},
@ -73,6 +76,7 @@ func TestParseOptions(t *testing.T) {
// mix of valid and invalid flags
{"FOO,1,BAR,r90,BAZ", Options{Width: 1, Height: 1, Rotate: 90}},
// all flags, in different orders
{"1x2,fit,r90,fv,fh", Options{1, 2, true, 90, true, true}},
{"r90,fh,1x2,fv,fit", Options{1, 2, true, 90, true, true}},
}
@ -90,24 +94,16 @@ func TestParseOptions(t *testing.T) {
// the various Options that can be specified; see TestParseOptions for that.
func TestNewRequest(t *testing.T) {
tests := []struct {
URL string
RemoteURL string
Options Options
ExpectError bool
URL string // input URL to parse as an imageproxy request
RemoteURL string // expected URL of remote image parsed from input
Options Options // expected options parsed from input
ExpectError bool // whether an error is expected from NewRequest
}{
// invalid URLs
{
"http://localhost/", "", emptyOptions, true,
},
{
"http://localhost/1/", "", emptyOptions, true,
},
{
"http://localhost//example.com/foo", "", emptyOptions, true,
},
{
"http://localhost//ftp://example.com/foo", "", emptyOptions, true,
},
{"http://localhost/", "", emptyOptions, true},
{"http://localhost/1/", "", emptyOptions, true},
{"http://localhost//example.com/foo", "", emptyOptions, true},
{"http://localhost//ftp://example.com/foo", "", emptyOptions, true},
// invalid options. These won't return errors, but will not fully parse the options
{