imageorient.go

  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}