Skip to content

Commit 3fc9db7

Browse files
committed
feat(#315, #309): autorotate / gracefully fallback failed image type encoding
1 parent c68335d commit 3fc9db7

9 files changed

+70
-8
lines changed

.travis.yml

+1-2
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,8 @@ services:
1010
dist: focal
1111

1212
go:
13-
- "1.12"
1413
- "1.13"
15-
# - "1.14"
14+
- "1.14"
1615

1716
env:
1817
global:

Dockerfile

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
ARG GOLANG_VERSION=1.13
1+
ARG GOLANG_VERSION=1.14
22
FROM golang:${GOLANG_VERSION} as builder
33

44
ARG IMAGINARY_VERSION=dev
55
ARG LIBVIPS_VERSION=8.9.2
6-
ARG GOLANGCILINT_VERSION=1.23.3
6+
ARG GOLANGCILINT_VERSION=1.29.0
77

88
# Installs libvips + required libraries
99
RUN DEBIAN_FRONTEND=noninteractive \

README.md

+10
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ To get started, take a look the [installation](#installation) steps, [usage](#co
5353
- Crop
5454
- SmartCrop (based on [libvips built-in algorithm](https://github.com/jcupitt/libvips/blob/master/libvips/conversion/smartcrop.c))
5555
- Rotate (with auto-rotate based on EXIF orientation)
56+
- AutoRotate with further image transformations (based on EXIF metadata orientation)
5657
- Flip (with auto-flip based on EXIF metadata)
5758
- Flop
5859
- Zoom
@@ -883,6 +884,14 @@ The width and height specify a maximum bounding box for the image.
883884
#### GET | POST /rotate
884885
Accepts: `image/*, multipart/form-data`. Content-Type: `image/*`
885886

887+
888+
#### GET | POST /autorotate
889+
Accepts: `image/*, multipart/form-data`. Content-Type: `image/*`
890+
891+
Automatically rotate the image with no further image transformations based on EXIF orientation metadata.
892+
893+
Returns a new image with the same size and format as the input image.
894+
886895
##### Allowed params
887896

888897
- rotate `int` `required`
@@ -1036,6 +1045,7 @@ Self-documented JSON operation schema:
10361045
- **enlarge** - Same as [`/enlarge`](#get--post-enlarge) endpoint.
10371046
- **extract** - Same as [`/extract`](#get--post-extract) endpoint.
10381047
- **rotate** - Same as [`/rotate`](#get--post-rotate) endpoint.
1048+
- **autorotate** - Same as [`/autorotate`](#get--post-autorotate) endpoint.
10391049
- **flip** - Same as [`/flip`](#get--post-flip) endpoint.
10401050
- **flop** - Same as [`/flop`](#get--post-flop) endpoint.
10411051
- **thumbnail** - Same as [`/thumbnail`](#get--post-thumbnail) endpoint.

controllers.go

+1
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ func formController(w http.ResponseWriter, r *http.Request) {
145145
{"Extract", "extract", "top=100&left=100&areawidth=300&areaheight=150"},
146146
{"Enlarge", "enlarge", "width=1440&height=900&quality=95"},
147147
{"Rotate", "rotate", "rotate=180"},
148+
{"AutoRotate", "autorotate", "quality=90"},
148149
{"Flip", "flip", ""},
149150
{"Flop", "flop", ""},
150151
{"Thumbnail", "thumbnail", "width=100"},

go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ require (
66
github.com/garyburd/redigo v1.6.0 // indirect
77
github.com/hashicorp/golang-lru v0.0.0-20160813221303-0a025b7e63ad // indirect
88
github.com/rs/cors v0.0.0-20170727213201-7af7a1e09ba3
9-
github.com/h2non/bimg v1.1.2
9+
github.com/h2non/bimg v1.1.4
1010
github.com/h2non/filetype v1.1.0
1111
gopkg.in/throttled/throttled.v2 v2.0.3
1212
)

go.sum

+2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
github.com/garyburd/redigo v1.6.0/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
22
github.com/h2non/bimg v1.1.2 h1:J75W2eM5FT0KjcwsL2aiy1Ilu0Xy0ENb0sU+HHUJAvw=
33
github.com/h2non/bimg v1.1.2/go.mod h1:R3+UiYwkK4rQl6KVFTOFJHitgLbZXBZNFh2cv3AEbp8=
4+
github.com/h2non/bimg v1.1.4 h1:6qf7qDo3d9axbNUOcSoQmzleBCMTcQ1PwF3FgGhX4O0=
5+
github.com/h2non/bimg v1.1.4/go.mod h1:R3+UiYwkK4rQl6KVFTOFJHitgLbZXBZNFh2cv3AEbp8=
46
github.com/h2non/filetype v1.1.0 h1:Or/gjocJrJRNK/Cri/TDEKFjAR+cfG6eK65NGYB6gBA=
57
github.com/h2non/filetype v1.1.0/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY=
68
github.com/hashicorp/golang-lru v0.0.0-20160813221303-0a025b7e63ad h1:eMxs9EL0PvIGS9TTtxg4R+JxuPGav82J8rA+GFnY7po=

image.go

+38-3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"errors"
66
"fmt"
77
"io"
8+
"strings"
89
"io/ioutil"
910
"math"
1011
"net/http"
@@ -221,6 +222,31 @@ func Rotate(buf []byte, o ImageOptions) (Image, error) {
221222
return Process(buf, opts)
222223
}
223224

225+
func AutoRotate(buf []byte, o ImageOptions) (out Image, err error) {
226+
defer func() {
227+
if r := recover(); r != nil {
228+
switch value := r.(type) {
229+
case error:
230+
err = value
231+
case string:
232+
err = errors.New(value)
233+
default:
234+
err = errors.New("libvips internal error")
235+
}
236+
out = Image{}
237+
}
238+
}()
239+
240+
// Resize image via bimg
241+
ibuf, err := bimg.NewImage(buf).AutoRotate()
242+
if err != nil {
243+
return Image{}, err
244+
}
245+
246+
mime := GetImageMimeType(bimg.DetermineImageType(ibuf))
247+
return Image{Body: ibuf, Mime: mime}, nil
248+
}
249+
224250
func Flip(buf []byte, o ImageOptions) (Image, error) {
225251
opts := BimgOptions(o)
226252
opts.Flip = true
@@ -406,11 +432,20 @@ func Process(buf []byte, opts bimg.Options) (out Image, err error) {
406432
}
407433
}()
408434

409-
buf, err = bimg.Resize(buf, opts)
435+
// Resize image via bimg
436+
ibuf, err := bimg.Resize(buf, opts)
437+
438+
// Handle specific type encode errors gracefully
439+
if err != nil && strings.Contains(err.Error(), "encode") && (opts.Type == bimg.WEBP || opts.Type == bimg.HEIF) {
440+
// Always fallback to JPEG
441+
opts.Type = bimg.JPEG
442+
ibuf, err = bimg.Resize(buf, opts)
443+
}
444+
410445
if err != nil {
411446
return Image{}, err
412447
}
413448

414-
mime := GetImageMimeType(bimg.DetermineImageType(buf))
415-
return Image{Body: buf, Mime: mime}, nil
449+
mime := GetImageMimeType(bimg.DetermineImageType(ibuf))
450+
return Image{Body: ibuf, Mime: mime}, nil
416451
}

image_test.go

+14
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,20 @@ func TestImageFit(t *testing.T) {
9393
}
9494
}
9595

96+
func TestImageAutoRotate(t *testing.T) {
97+
buf, _ := ioutil.ReadAll(readFile("imaginary.jpg"))
98+
img, err := AutoRotate(buf, ImageOptions{})
99+
if err != nil {
100+
t.Errorf("Cannot process image: %s", err)
101+
}
102+
if img.Mime != "image/jpeg" {
103+
t.Error("Invalid image MIME type")
104+
}
105+
if assertSize(img.Body, 550, 740) != nil {
106+
t.Errorf("Invalid image size, expected: %dx%d", 550, 740)
107+
}
108+
}
109+
96110
func TestImagePipelineOperations(t *testing.T) {
97111
width, height := 300, 260
98112

server.go

+1
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ func NewServerMux(o ServerOptions) http.Handler {
122122
mux.Handle(join(o, "/crop"), image(Crop))
123123
mux.Handle(join(o, "/smartcrop"), image(SmartCrop))
124124
mux.Handle(join(o, "/rotate"), image(Rotate))
125+
mux.Handle(join(o, "/autorotate"), image(AutoRotate))
125126
mux.Handle(join(o, "/flip"), image(Flip))
126127
mux.Handle(join(o, "/flop"), image(Flop))
127128
mux.Handle(join(o, "/thumbnail"), image(Thumbnail))

0 commit comments

Comments
 (0)