From 1a656e25606ff25799fac0fa01c3e79bf1c6a7cc Mon Sep 17 00:00:00 2001 From: Barnaby Gray Date: Thu, 2 Nov 2017 13:22:06 +0000 Subject: [PATCH] Add /fit action --- README.md | 31 +++++++++++++++++++++++++++++++ image.go | 31 +++++++++++++++++++++++++++++++ image_test.go | 17 +++++++++++++++++ server.go | 1 + server_test.go | 33 +++++++++++++++++++++++++++++++++ 5 files changed, 113 insertions(+) diff --git a/README.md b/README.md index f274fc2..e471f68 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,7 @@ To get started, take a look the [installation](#installation) steps, [usage](#us - Flop - Zoom - Thumbnail +- Fit - [Pipeline](#get--post-pipeline) of multiple independent image transformations in a single HTTP request. - Configurable image area extraction - Embed/Extend image, supporting multiple modes (white, black, mirror, copy or custom background color) @@ -758,6 +759,36 @@ Accepts: `image/*, multipart/form-data`. Content-Type: `image/*` - minampl `float` - field `string` - Only POST and `multipart/form` payloads +#### GET | POST /fit +Accepts: `image/*, multipart/form-data`. Content-Type: `image/*` + +Resize an image to fit within width and height, without cropping. Image aspect ratio is maintained +The width and height specify a maximum bounding box for the image. + +##### Allowed params + +- width `int` `required` +- height `int` `required` +- quality `int` (JPEG-only) +- compression `int` (PNG-only) +- type `string` +- file `string` - Only GET method and if the `-mount` flag is present +- url `string` - Only GET method and if the `-enable-url-source` flag is present +- embed `bool` +- force `bool` +- rotate `int` +- norotation `bool` +- noprofile `bool` +- stripmeta `bool` +- flip `bool` +- flop `bool` +- extend `string` +- background `string` - Example: `?background=250,20,10` +- colorspace `string` +- sigma `float` +- minampl `float` +- field `string` - Only POST and `multipart/form` payloads + #### GET | POST /rotate Accepts: `image/*, multipart/form-data`. Content-Type: `image/*` diff --git a/image.go b/image.go index 428bc85..5bcbae9 100644 --- a/image.go +++ b/image.go @@ -25,6 +25,7 @@ var OperationsMap = map[string]Operation{ "watermark": Watermark, "blur": GaussianBlur, "smartcrop": SmartCrop, + "fit": Fit, } // Image stores an image binary buffer and its MIME type @@ -95,6 +96,36 @@ func Resize(buf []byte, o ImageOptions) (Image, error) { return Process(buf, opts) } +func Fit(buf []byte, o ImageOptions) (Image, error) { + if o.Width == 0 || o.Height == 0 { + return Image{}, NewError("Missing required params: height, width", BadRequest) + } + + dims, err := bimg.Size(buf) + if err != nil { + return Image{}, err + } + + // if input ratio > output ratio + // (calculation multiplied through by denominators to avoid float division) + if dims.Width*o.Height > o.Width*dims.Height { + // constrained by width + if dims.Width != 0 { + o.Height = o.Width * dims.Height / dims.Width + } + } else { + // constrained by height + if dims.Height != 0 { + o.Width = o.Height * dims.Width / dims.Height + } + } + + opts := BimgOptions(o) + opts.Embed = true + + return Process(buf, opts) +} + func Enlarge(buf []byte, o ImageOptions) (Image, error) { if o.Width == 0 || o.Height == 0 { return Image{}, NewError("Missing required params: height, width", BadRequest) diff --git a/image_test.go b/image_test.go index 277c2bf..55a6de6 100644 --- a/image_test.go +++ b/image_test.go @@ -21,6 +21,23 @@ func TestImageResize(t *testing.T) { } } +func TestImageFit(t *testing.T) { + opts := ImageOptions{Width: 300, Height: 300} + buf, _ := ioutil.ReadAll(readFile("imaginary.jpg")) + + img, err := Fit(buf, opts) + if err != nil { + t.Errorf("Cannot process image: %s", err) + } + if img.Mime != "image/jpeg" { + t.Error("Invalid image MIME type") + } + // 550x740 -> 222x300 + if assertSize(img.Body, 222, 300) != nil { + t.Errorf("Invalid image size, expected: %dx%d", opts.Width, opts.Height) + } +} + func TestImagePipelineOperations(t *testing.T) { width, height := 300, 260 diff --git a/server.go b/server.go index 64d9c99..a54f12c 100644 --- a/server.go +++ b/server.go @@ -87,6 +87,7 @@ func NewServerMux(o ServerOptions) http.Handler { image := ImageMiddleware(o) mux.Handle(join(o, "/resize"), image(Resize)) + mux.Handle(join(o, "/fit"), image(Fit)) mux.Handle(join(o, "/enlarge"), image(Enlarge)) mux.Handle(join(o, "/extract"), image(Extract)) mux.Handle(join(o, "/crop"), image(Crop)) diff --git a/server_test.go b/server_test.go index 9a3e918..3198d8d 100644 --- a/server_test.go +++ b/server_test.go @@ -173,6 +173,39 @@ func TestExtract(t *testing.T) { } } +func TestFit(t *testing.T) { + ts := testServer(controller(Fit)) + buf := readFile("large.jpg") + url := ts.URL + "?width=300&height=300" + defer ts.Close() + + res, err := http.Post(url, "image/jpeg", buf) + if err != nil { + t.Fatal("Cannot perform the request") + } + + if res.StatusCode != 200 { + t.Fatalf("Invalid response status: %s", res.Status) + } + + image, err := ioutil.ReadAll(res.Body) + if err != nil { + t.Fatal(err) + } + if len(image) == 0 { + t.Fatalf("Empty response body") + } + + err = assertSize(image, 300, 168) + if err != nil { + t.Error(err) + } + + if bimg.DetermineImageTypeName(image) != "jpeg" { + t.Fatalf("Invalid image type") + } +} + func TestRemoteHTTPSource(t *testing.T) { opts := ServerOptions{EnableURLSource: true} fn := ImageMiddleware(opts)(Crop)