mirror of
https://github.com/willnorris/imageproxy.git
synced 2024-12-16 21:56:43 -05:00
parent
20c0a50a31
commit
afbd254cdc
4 changed files with 48 additions and 14 deletions
|
@ -71,6 +71,10 @@ image][material-animation] resized to 200px square and rotated 270 degrees:
|
|||
|
||||
<a href="https://willnorris.com/api/imageproxy/200,r270/https://willnorris.com/2015/05/material-animations.gif"><img src="https://willnorris.com/api/imageproxy/200,r270/https://willnorris.com/2015/05/material-animations.gif" alt="200,r270"></a>
|
||||
|
||||
The smart crop feature can best be seen by comparing the following images, with and without smart crop.
|
||||
|
||||
<a href="https://willnorris.com/api/imageproxy/150x300/https://judahnorris.com/images/judah-sheets.jpg"><img src="https://willnorris.com/api/imageproxy/150x300/https://judahnorris.com/images/judah-sheets.jpg" alt="200x400"></a>
|
||||
<a href="https://willnorris.com/api/imageproxy/150x300,sc/https://judahnorris.com/images/judah-sheets.jpg"><img src="https://willnorris.com/api/imageproxy/150x300,sc/https://judahnorris.com/images/judah-sheets.jpg" alt="200x400,sc"></a>
|
||||
|
||||
## Getting Started ##
|
||||
|
||||
|
|
15
data.go
15
data.go
|
@ -39,6 +39,7 @@ const (
|
|||
optCropY = "cy"
|
||||
optCropWidth = "cw"
|
||||
optCropHeight = "ch"
|
||||
optSmartCrop = "sc"
|
||||
)
|
||||
|
||||
// URLError reports a malformed URL error.
|
||||
|
@ -86,6 +87,9 @@ type Options struct {
|
|||
CropY float64
|
||||
CropWidth float64
|
||||
CropHeight float64
|
||||
|
||||
// Automatically find good crop points based on image content.
|
||||
SmartCrop bool
|
||||
}
|
||||
|
||||
func (o Options) String() string {
|
||||
|
@ -126,6 +130,9 @@ func (o Options) String() string {
|
|||
if o.CropHeight != 0 {
|
||||
opts = append(opts, fmt.Sprintf("%s%v", string(optCropHeight), o.CropHeight))
|
||||
}
|
||||
if o.SmartCrop {
|
||||
opts = append(opts, optSmartCrop)
|
||||
}
|
||||
return strings.Join(opts, ",")
|
||||
}
|
||||
|
||||
|
@ -159,6 +166,12 @@ func (o Options) transform() bool {
|
|||
// crop width or height will be adjusted, preserving the specified cx and cy
|
||||
// values. Rectangular crop is applied before any other transformations.
|
||||
//
|
||||
// Smart Crop
|
||||
//
|
||||
// The "sc" option will perform a content-aware smart crop to fit the
|
||||
// requested image width and height dimensions (see Size and Cropping below).
|
||||
// The smart crop option will override any requested rectangular crop.
|
||||
//
|
||||
// Size and Cropping
|
||||
//
|
||||
// The size option takes the general form "{width}x{height}", where width and
|
||||
|
@ -246,6 +259,8 @@ func ParseOptions(str string) Options {
|
|||
options.ScaleUp = true
|
||||
case opt == optFormatJPEG, opt == optFormatPNG, opt == optFormatTIFF:
|
||||
options.Format = opt
|
||||
case opt == optSmartCrop:
|
||||
options.SmartCrop = true
|
||||
case strings.HasPrefix(opt, optRotatePrefix):
|
||||
value := strings.TrimPrefix(opt, optRotatePrefix)
|
||||
options.Rotate, _ = strconv.Atoi(value)
|
||||
|
|
26
data_test.go
26
data_test.go
|
@ -31,19 +31,19 @@ func TestOptions_String(t *testing.T) {
|
|||
"0x0",
|
||||
},
|
||||
{
|
||||
Options{1, 2, true, 90, true, true, 80, "", false, "", 0, 0, 0, 0},
|
||||
Options{1, 2, true, 90, true, true, 80, "", false, "", 0, 0, 0, 0, false},
|
||||
"1x2,fit,r90,fv,fh,q80",
|
||||
},
|
||||
{
|
||||
Options{0.15, 1.3, false, 45, false, false, 95, "c0ffee", false, "png", 0, 0, 0, 0},
|
||||
Options{0.15, 1.3, false, 45, false, false, 95, "c0ffee", false, "png", 0, 0, 0, 0, false},
|
||||
"0.15x1.3,r45,q95,sc0ffee,png",
|
||||
},
|
||||
{
|
||||
Options{0.15, 1.3, false, 45, false, false, 95, "c0ffee", false, "", 100, 200, 0, 0},
|
||||
Options{0.15, 1.3, false, 45, false, false, 95, "c0ffee", false, "", 100, 200, 0, 0, false},
|
||||
"0.15x1.3,r45,q95,sc0ffee,cx100,cy200",
|
||||
},
|
||||
{
|
||||
Options{0.15, 1.3, false, 45, false, false, 95, "c0ffee", false, "png", 100, 200, 300, 400},
|
||||
Options{0.15, 1.3, false, 45, false, false, 95, "c0ffee", false, "png", 100, 200, 300, 400, false},
|
||||
"0.15x1.3,r45,q95,sc0ffee,png,cx100,cy200,cw300,ch400",
|
||||
},
|
||||
}
|
||||
|
@ -94,19 +94,19 @@ func TestParseOptions(t *testing.T) {
|
|||
{"FOO,1,BAR,r90,BAZ", Options{Width: 1, Height: 1, Rotate: 90}},
|
||||
|
||||
// flags, in different orders
|
||||
{"q70,1x2,fit,r90,fv,fh,sc0ffee,png", Options{1, 2, true, 90, true, true, 70, "c0ffee", false, "png", 0, 0, 0, 0}},
|
||||
{"r90,fh,sc0ffee,png,q90,1x2,fv,fit", Options{1, 2, true, 90, true, true, 90, "c0ffee", false, "png", 0, 0, 0, 0}},
|
||||
{"q70,1x2,fit,r90,fv,fh,sc0ffee,png", Options{1, 2, true, 90, true, true, 70, "c0ffee", false, "png", 0, 0, 0, 0, false}},
|
||||
{"r90,fh,sc0ffee,png,q90,1x2,fv,fit", Options{1, 2, true, 90, true, true, 90, "c0ffee", false, "png", 0, 0, 0, 0, false}},
|
||||
|
||||
// all flags, in different orders with crop
|
||||
{"q70,cx100,cw300,1x2,fit,cy200,r90,fv,ch400,fh,sc0ffee,png", Options{1, 2, true, 90, true, true, 70, "c0ffee", false, "png", 100, 200, 300, 400}},
|
||||
{"ch400,r90,cw300,fh,sc0ffee,png,cx100,q90,cy200,1x2,fv,fit", Options{1, 2, true, 90, true, true, 90, "c0ffee", false, "png", 100, 200, 300, 400}},
|
||||
{"q70,cx100,cw300,1x2,fit,cy200,r90,fv,ch400,fh,sc0ffee,png", Options{1, 2, true, 90, true, true, 70, "c0ffee", false, "png", 100, 200, 300, 400, false}},
|
||||
{"ch400,r90,cw300,fh,sc0ffee,png,cx100,q90,cy200,1x2,fv,fit", Options{1, 2, true, 90, true, true, 90, "c0ffee", false, "png", 100, 200, 300, 400, false}},
|
||||
|
||||
// all flags, in different orders with crop & different resizes
|
||||
{"q70,cx100,cw300,x2,fit,cy200,r90,fv,ch400,fh,sc0ffee,png", Options{0, 2, true, 90, true, true, 70, "c0ffee", false, "png", 100, 200, 300, 400}},
|
||||
{"ch400,r90,cw300,fh,sc0ffee,png,cx100,q90,cy200,1x,fv,fit", Options{1, 0, true, 90, true, true, 90, "c0ffee", false, "png", 100, 200, 300, 400}},
|
||||
{"ch400,r90,cw300,fh,sc0ffee,png,cx100,q90,cy200,cw,fv,fit", Options{0, 0, true, 90, true, true, 90, "c0ffee", false, "png", 100, 200, 0, 400}},
|
||||
{"ch400,r90,cw300,fh,sc0ffee,png,cx100,q90,cy200,cw,fv,fit,123x321", Options{123, 321, true, 90, true, true, 90, "c0ffee", false, "png", 100, 200, 0, 400}},
|
||||
{"123x321,ch400,r90,cw300,fh,sc0ffee,png,cx100,q90,cy200,cw,fv,fit", Options{123, 321, true, 90, true, true, 90, "c0ffee", false, "png", 100, 200, 0, 400}},
|
||||
{"q70,cx100,cw300,x2,fit,cy200,r90,fv,ch400,fh,sc0ffee,png", Options{0, 2, true, 90, true, true, 70, "c0ffee", false, "png", 100, 200, 300, 400, false}},
|
||||
{"ch400,r90,cw300,fh,sc0ffee,png,cx100,q90,cy200,1x,fv,fit", Options{1, 0, true, 90, true, true, 90, "c0ffee", false, "png", 100, 200, 300, 400, false}},
|
||||
{"ch400,r90,cw300,fh,sc0ffee,png,cx100,q90,cy200,cw,fv,fit", Options{0, 0, true, 90, true, true, 90, "c0ffee", false, "png", 100, 200, 0, 400, false}},
|
||||
{"ch400,r90,cw300,fh,sc0ffee,png,cx100,q90,cy200,cw,fv,fit,123x321", Options{123, 321, true, 90, true, true, 90, "c0ffee", false, "png", 100, 200, 0, 400, false}},
|
||||
{"123x321,ch400,r90,cw300,fh,sc0ffee,png,cx100,q90,cy200,cw,fv,fit", Options{123, 321, true, 90, true, true, 90, "c0ffee", false, "png", 100, 200, 0, 400, false}},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
|
17
transform.go
17
transform.go
|
@ -22,9 +22,11 @@ import (
|
|||
"image/jpeg"
|
||||
"image/png"
|
||||
"io"
|
||||
"log"
|
||||
"math"
|
||||
|
||||
"github.com/disintegration/imaging"
|
||||
"github.com/muesli/smartcrop"
|
||||
"github.com/rwcarlsen/goexif/exif"
|
||||
"golang.org/x/image/tiff" // register tiff format
|
||||
_ "golang.org/x/image/webp" // register webp format
|
||||
|
@ -156,7 +158,7 @@ func resizeParams(m image.Image, opt Options) (w, h int, resize bool) {
|
|||
|
||||
// cropParams calculates crop rectangle parameters to keep it in image bounds
|
||||
func cropParams(m image.Image, opt Options) image.Rectangle {
|
||||
if opt.CropX == 0 && opt.CropY == 0 && opt.CropWidth == 0 && opt.CropHeight == 0 {
|
||||
if !opt.SmartCrop && opt.CropX == 0 && opt.CropY == 0 && opt.CropWidth == 0 && opt.CropHeight == 0 {
|
||||
return m.Bounds()
|
||||
}
|
||||
|
||||
|
@ -164,6 +166,19 @@ func cropParams(m image.Image, opt Options) image.Rectangle {
|
|||
imgW := m.Bounds().Dx()
|
||||
imgH := m.Bounds().Dy()
|
||||
|
||||
if opt.SmartCrop {
|
||||
w := evaluateFloat(opt.Width, imgW)
|
||||
h := evaluateFloat(opt.Height, imgH)
|
||||
log.Printf("smartcrop input: %dx%d", w, h)
|
||||
r, err := smartcrop.SmartCrop(m, w, h)
|
||||
if err != nil {
|
||||
log.Printf("error with smartcrop: %v", err)
|
||||
} else {
|
||||
log.Printf("smartcrop rectangle: %v", r)
|
||||
return r
|
||||
}
|
||||
}
|
||||
|
||||
// top left coordinate of crop
|
||||
x0 := evaluateFloat(math.Abs(opt.CropX), imgW)
|
||||
if opt.CropX < 0 {
|
||||
|
|
Loading…
Reference in a new issue