1// Package imageorient provides image decoding functions similar to standard library's
2// image.Decode and image.DecodeConfig with the addition that they also handle the
3// EXIF orientation tag (if present).
4//
5// See also: http://www.daveperrett.com/articles/2012/07/28/exif-orientation-handling-is-a-ghetto/
6//
7package imageorient
8
9import (
10 "bytes"
11 "encoding/binary"
12 "image"
13 "io"
14 "io/ioutil"
15
16 "github.com/disintegration/gift"
17)
18
19// maxBufLen is the maximum size of a buffer that should be enough to read
20// the EXIF metadata. According to the EXIF specs, it is located inside the
21// APP1 block that goes right after the start of image (SOI).
22const maxBufLen = 1 << 20
23
24// Decode decodes an image and changes its orientation
25// according to the EXIF orientation tag (if present).
26func Decode(r io.Reader) (image.Image, string, error) {
27 orientation, r := getOrientation(r)
28
29 img, format, err := image.Decode(r)
30 if err != nil {
31 return img, format, err
32 }
33
34 return fixOrientation(img, orientation), format, nil
35}
36
37// DecodeConfig decodes the color model and dimensions of an image
38// with the respect to the EXIF orientation tag (if present).
39//
40// Note that after using imageorient.Decode on the same image,
41// the color model of the decoded image may be different if the
42// orientation-related transformation is needed.
43func DecodeConfig(r io.Reader) (image.Config, string, error) {
44 orientation, r := getOrientation(r)
45
46 cfg, format, err := image.DecodeConfig(r)
47 if err != nil {
48 return cfg, format, err
49 }
50
51 if orientation >= 5 && orientation <= 8 {
52 cfg.Width, cfg.Height = cfg.Height, cfg.Width
53 }
54
55 return cfg, format, nil
56}
57
58// getOrientation returns the EXIF orientation tag from the given image
59// and a new io.Reader with the same state as the original reader r.
60func getOrientation(r io.Reader) (int, io.Reader) {
61 buf := new(bytes.Buffer)
62 tr := io.TeeReader(io.LimitReader(r, maxBufLen), buf)
63 orientation := readOrientation(tr)
64 return orientation, io.MultiReader(buf, r)
65}
66
67// readOrientation reads the EXIF orientation tag from the given image.
68// It returns 0 if the orientation tag is not found or invalid.
69func readOrientation(r io.Reader) int {
70 const (
71 markerSOI = 0xffd8
72 markerAPP1 = 0xffe1
73 exifHeader = 0x45786966
74 byteOrderBE = 0x4d4d
75 byteOrderLE = 0x4949
76 orientationTag = 0x0112
77 )
78
79 // Check if JPEG SOI marker is present.
80 var soi uint16
81 if err := binary.Read(r, binary.BigEndian, &soi); err != nil {
82 return 0
83 }
84 if soi != markerSOI {
85 return 0 // Missing JPEG SOI marker.
86 }
87
88 // Find JPEG APP1 marker.
89 for {
90 var marker, size uint16
91 if err := binary.Read(r, binary.BigEndian, &marker); err != nil {
92 return 0
93 }
94 if err := binary.Read(r, binary.BigEndian, &size); err != nil {
95 return 0
96 }
97 if marker>>8 != 0xff {
98 return 0 // Invalid JPEG marker.
99 }
100 if marker == markerAPP1 {
101 break
102 }
103 if size < 2 {
104 return 0 // Invalid block size.
105 }
106 if _, err := io.CopyN(ioutil.Discard, r, int64(size-2)); err != nil {
107 return 0
108 }
109 }
110
111 // Check if EXIF header is present.
112 var header uint32
113 if err := binary.Read(r, binary.BigEndian, &header); err != nil {
114 return 0
115 }
116 if header != exifHeader {
117 return 0
118 }
119 if _, err := io.CopyN(ioutil.Discard, r, 2); err != nil {
120 return 0
121 }
122
123 // Read byte order information.
124 var (
125 byteOrderTag uint16
126 byteOrder binary.ByteOrder
127 )
128 if err := binary.Read(r, binary.BigEndian, &byteOrderTag); err != nil {
129 return 0
130 }
131 switch byteOrderTag {
132 case byteOrderBE:
133 byteOrder = binary.BigEndian
134 case byteOrderLE:
135 byteOrder = binary.LittleEndian
136 default:
137 return 0 // Invalid byte order flag.
138 }
139 if _, err := io.CopyN(ioutil.Discard, r, 2); err != nil {
140 return 0
141 }
142
143 // Skip the EXIF offset.
144 var offset uint32
145 if err := binary.Read(r, byteOrder, &offset); err != nil {
146 return 0
147 }
148 if offset < 8 {
149 return 0 // Invalid offset value.
150 }
151 if _, err := io.CopyN(ioutil.Discard, r, int64(offset-8)); err != nil {
152 return 0
153 }
154
155 // Read the number of tags.
156 var numTags uint16
157 if err := binary.Read(r, byteOrder, &numTags); err != nil {
158 return 0
159 }
160
161 // Find the orientation tag.
162 for i := 0; i < int(numTags); i++ {
163 var tag uint16
164 if err := binary.Read(r, byteOrder, &tag); err != nil {
165 return 0
166 }
167 if tag != orientationTag {
168 if _, err := io.CopyN(ioutil.Discard, r, 10); err != nil {
169 return 0
170 }
171 continue
172 }
173 if _, err := io.CopyN(ioutil.Discard, r, 6); err != nil {
174 return 0
175 }
176 var val uint16
177 if err := binary.Read(r, byteOrder, &val); err != nil {
178 return 0
179 }
180 if val < 1 || val > 8 {
181 return 0 // Invalid tag value.
182 }
183 return int(val)
184 }
185 return 0 // Missing orientation tag.
186}
187
188// Filters needed to fix the given image orientation.
189var filters = map[int]gift.Filter{
190 2: gift.FlipHorizontal(),
191 3: gift.Rotate180(),
192 4: gift.FlipVertical(),
193 5: gift.Transpose(),
194 6: gift.Rotate270(),
195 7: gift.Transverse(),
196 8: gift.Rotate90(),
197}
198
199// fixOrientation changes the image orientation based on the EXIF orientation tag value.
200func fixOrientation(img image.Image, orientation int) image.Image {
201 filter, ok := filters[orientation]
202 if !ok {
203 return img
204 }
205 g := gift.New(filter)
206 newImg := image.NewRGBA(g.Bounds(img.Bounds()))
207 g.Draw(newImg, img)
208 return newImg
209}