mirror of
https://github.com/willnorris/imageproxy.git
synced 2026-04-29 14:56:25 +02:00
collapse data and transform packages into proxy
This commit is contained in:
parent
d8aa853b3d
commit
fc8552e6b4
4 changed files with 19 additions and 26 deletions
112
proxy/data.go
Normal file
112
proxy/data.go
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
// Copyright 2013 Google Inc. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package data provides common shared data structures for imageproxy.
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Options specifies transformations that can be performed on a
|
||||
// requested image.
|
||||
type Options struct {
|
||||
Width float64 // requested width, in pixels
|
||||
Height float64 // requested height, in pixels
|
||||
|
||||
// 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 int
|
||||
|
||||
FlipVertical bool
|
||||
FlipHorizontal bool
|
||||
}
|
||||
|
||||
func (o Options) String() string {
|
||||
return fmt.Sprintf("%vx%v", o.Width, o.Height)
|
||||
}
|
||||
|
||||
func ParseOptions(str string) *Options {
|
||||
o := new(Options)
|
||||
var h, w string
|
||||
|
||||
parts := strings.Split(str, ",")
|
||||
|
||||
// parse size
|
||||
size := strings.SplitN(parts[0], "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)
|
||||
}
|
||||
|
||||
for _, part := range parts[1:] {
|
||||
if part == "fit" {
|
||||
o.Fit = true
|
||||
continue
|
||||
}
|
||||
if part == "fv" {
|
||||
o.FlipVertical = true
|
||||
continue
|
||||
}
|
||||
if part == "fh" {
|
||||
o.FlipHorizontal = true
|
||||
continue
|
||||
}
|
||||
|
||||
if len(part) > 2 && strings.HasPrefix(part, "r") {
|
||||
o.Rotate, _ = strconv.Atoi(part[1:])
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return o
|
||||
}
|
||||
|
||||
type Request struct {
|
||||
URL *url.URL // URL of the image to proxy
|
||||
Options *Options // Image transformation to perform
|
||||
}
|
||||
|
||||
// Image represents a remote image that is being proxied. It tracks where
|
||||
// the image was originally retrieved from and how long the image can be cached.
|
||||
type Image struct {
|
||||
// URL of original remote image.
|
||||
URL string
|
||||
|
||||
// Expires is the cache expiration time for the original image, as
|
||||
// returned by the remote server.
|
||||
Expires time.Time
|
||||
|
||||
// Etag returned from server when fetching image.
|
||||
Etag string
|
||||
|
||||
// Bytes contains the actual image.
|
||||
Bytes []byte
|
||||
}
|
||||
|
|
@ -27,8 +27,6 @@ import (
|
|||
|
||||
"github.com/golang/glog"
|
||||
"github.com/gregjones/httpcache"
|
||||
"github.com/willnorris/imageproxy/data"
|
||||
"github.com/willnorris/imageproxy/transform"
|
||||
)
|
||||
|
||||
// URLError reports a malformed URL error.
|
||||
|
|
@ -42,9 +40,9 @@ func (e URLError) Error() string {
|
|||
}
|
||||
|
||||
// NewRequest parses an http.Request into an image request.
|
||||
func NewRequest(r *http.Request) (*data.Request, error) {
|
||||
func NewRequest(r *http.Request) (*Request, error) {
|
||||
var err error
|
||||
req := new(data.Request)
|
||||
req := new(Request)
|
||||
|
||||
path := r.URL.Path[1:] // strip leading slash
|
||||
req.URL, err = url.Parse(path)
|
||||
|
|
@ -60,7 +58,7 @@ func NewRequest(r *http.Request) (*data.Request, error) {
|
|||
return nil, URLError{fmt.Sprintf("unable to parse remote URL: %v", err), r.URL}
|
||||
}
|
||||
|
||||
req.Options = data.ParseOptions(parts[0])
|
||||
req.Options = ParseOptions(parts[0])
|
||||
}
|
||||
|
||||
if !req.URL.IsAbs() {
|
||||
|
|
@ -142,7 +140,7 @@ func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
b, _ := transform.Transform(image.Bytes, req.Options)
|
||||
b, _ := Transform(image.Bytes, req.Options)
|
||||
image.Bytes = b
|
||||
|
||||
w.Header().Add("Content-Length", strconv.Itoa(len(image.Bytes)))
|
||||
|
|
@ -150,7 +148,7 @@ func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||
w.Write(image.Bytes)
|
||||
}
|
||||
|
||||
func (p *Proxy) fetchRemoteImage(u string) (*data.Image, error) {
|
||||
func (p *Proxy) fetchRemoteImage(u string) (*Image, error) {
|
||||
resp, err := p.Client.Get(u)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -166,7 +164,7 @@ func (p *Proxy) fetchRemoteImage(u string) (*data.Image, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
return &data.Image{
|
||||
return &Image{
|
||||
URL: u,
|
||||
Expires: parseExpires(resp),
|
||||
Etag: resp.Header.Get("Etag"),
|
||||
|
|
|
|||
|
|
@ -18,17 +18,13 @@ import (
|
|||
"net/http"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/willnorris/imageproxy/data"
|
||||
)
|
||||
|
||||
var emptyOptions = new(data.Options)
|
||||
|
||||
func TestNewRequest(t *testing.T) {
|
||||
tests := []struct {
|
||||
URL string
|
||||
RemoteURL string
|
||||
Options *data.Options
|
||||
Options *Options
|
||||
ExpectError bool
|
||||
}{
|
||||
// invalid URLs
|
||||
|
|
@ -52,7 +48,7 @@ func TestNewRequest(t *testing.T) {
|
|||
},
|
||||
{
|
||||
"http://localhost/1xs/http://example.com/",
|
||||
"http://example.com/", &data.Options{Width: 1}, false,
|
||||
"http://example.com/", &Options{Width: 1}, false,
|
||||
},
|
||||
|
||||
// valid URLs
|
||||
|
|
@ -84,27 +80,27 @@ func TestNewRequest(t *testing.T) {
|
|||
},
|
||||
{
|
||||
"http://localhost/1x/http://example.com/",
|
||||
"http://example.com/", &data.Options{1, 0, false, 0, false, false}, false,
|
||||
"http://example.com/", &Options{1, 0, false, 0, false, false}, false,
|
||||
},
|
||||
{
|
||||
"http://localhost/x1/http://example.com/",
|
||||
"http://example.com/", &data.Options{0, 1, false, 0, false, false}, false,
|
||||
"http://example.com/", &Options{0, 1, false, 0, false, false}, false,
|
||||
},
|
||||
{
|
||||
"http://localhost/1x2/http://example.com/",
|
||||
"http://example.com/", &data.Options{1, 2, false, 0, false, false}, false,
|
||||
"http://example.com/", &Options{1, 2, false, 0, false, false}, false,
|
||||
},
|
||||
{
|
||||
"http://localhost/0.1x0.2/http://example.com/",
|
||||
"http://example.com/", &data.Options{0.1, 0.2, false, 0, false, false}, false,
|
||||
"http://example.com/", &Options{0.1, 0.2, false, 0, false, false}, false,
|
||||
},
|
||||
{
|
||||
"http://localhost/,fit/http://example.com/",
|
||||
"http://example.com/", &data.Options{0, 0, true, 0, false, false}, false,
|
||||
"http://example.com/", &Options{0, 0, true, 0, false, false}, false,
|
||||
},
|
||||
{
|
||||
"http://localhost/1x2,fit,r=90,fv,fh/http://example.com/",
|
||||
"http://example.com/", &data.Options{1, 2, true, 90, true, true}, false,
|
||||
"http://localhost/1x2,fit,r90,fv,fh/http://example.com/",
|
||||
"http://example.com/", &Options{1, 2, true, 90, true, true}, false,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
|||
106
proxy/transform.go
Normal file
106
proxy/transform.go
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
// Copyright 2013 Google Inc. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package transform handles image transformation such as resizing.
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"image"
|
||||
"image/gif"
|
||||
"image/jpeg"
|
||||
"image/png"
|
||||
"reflect"
|
||||
|
||||
"github.com/disintegration/imaging"
|
||||
)
|
||||
|
||||
var emptyOptions = new(Options)
|
||||
|
||||
// Transform the provided image.
|
||||
func Transform(img []byte, opt *Options) ([]byte, error) {
|
||||
if opt == nil || reflect.DeepEqual(opt, emptyOptions) {
|
||||
// bail if no transformation was requested
|
||||
return img, nil
|
||||
}
|
||||
|
||||
// decode image
|
||||
m, format, err := image.Decode(bytes.NewReader(img))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// convert percentage width and height values to absolute values
|
||||
var h, w int
|
||||
if opt.Width > 0 && opt.Width < 1 {
|
||||
w = int(float64(m.Bounds().Max.X-m.Bounds().Min.X) * opt.Width)
|
||||
} else {
|
||||
w = int(opt.Width)
|
||||
}
|
||||
if opt.Height > 0 && opt.Height < 1 {
|
||||
h = int(float64(m.Bounds().Max.Y-m.Bounds().Min.Y) * opt.Height)
|
||||
} else {
|
||||
h = int(opt.Height)
|
||||
}
|
||||
|
||||
// resize
|
||||
if w != 0 || h != 0 {
|
||||
if opt.Fit {
|
||||
m = imaging.Fit(m, w, h, imaging.Lanczos)
|
||||
} else {
|
||||
if w == 0 || h == 0 {
|
||||
m = imaging.Resize(m, w, h, imaging.Lanczos)
|
||||
} else {
|
||||
m = imaging.Thumbnail(m, w, h, imaging.Lanczos)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// flip
|
||||
if opt.FlipVertical {
|
||||
m = imaging.FlipV(m)
|
||||
}
|
||||
if opt.FlipHorizontal {
|
||||
m = imaging.FlipH(m)
|
||||
}
|
||||
|
||||
// rotate
|
||||
switch opt.Rotate {
|
||||
case 90:
|
||||
m = imaging.Rotate90(m)
|
||||
break
|
||||
case 180:
|
||||
m = imaging.Rotate180(m)
|
||||
break
|
||||
case 270:
|
||||
m = imaging.Rotate270(m)
|
||||
break
|
||||
}
|
||||
|
||||
// encode image
|
||||
buf := new(bytes.Buffer)
|
||||
switch format {
|
||||
case "gif":
|
||||
gif.Encode(buf, m, nil)
|
||||
break
|
||||
case "jpeg":
|
||||
jpeg.Encode(buf, m, nil)
|
||||
break
|
||||
case "png":
|
||||
png.Encode(buf, m)
|
||||
break
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue