-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathexif.go
204 lines (179 loc) · 4.98 KB
/
exif.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
package imaging
import (
"encoding/binary"
"errors"
"io"
)
// ReadOrientation tries to read the orientation EXIF flag from image data in r.
// If the EXIF data block is not found or the orientation flag is not found
// or any other error occures while reading the data, it returns the
// orientationUnspecified (0) value.
func ReadOrientation(r io.Reader) Orientation {
if err := findJPEGSOIMarker(r); err != nil {
return OrientationUnspecified
}
if err := findJPEGAPP1Marker(r); err != nil {
return OrientationUnspecified
}
if err := findEXIFHeader(r); err != nil {
return OrientationUnspecified
}
byteOrder, err := readByteOrder(r)
if err != nil {
return OrientationUnspecified
}
if err := skipEXIFOffset(r, byteOrder); err != nil {
return OrientationUnspecified
}
numTags, err := readNumTags(r, byteOrder)
if err != nil {
return OrientationUnspecified
}
orientation, err := findOrientationTag(r, byteOrder, numTags)
if err != nil {
return OrientationUnspecified
}
return orientation
}
// findJPEGSOIMarker tries to find the JPEG SOI marker in r.
// This function assumes that the reader is positioned at the beginning of the file.
func findJPEGSOIMarker(r io.Reader) error {
const (
markerSOI = 0xffd8
)
var soi uint16
if err := binary.Read(r, binary.BigEndian, &soi); err != nil {
return err
}
if soi != markerSOI {
return errors.New("missing JPEG SOI marker")
}
return nil
}
// findJPEGAPP1Marker tries to find the JPEG APP1 marker in r.
// This function assumes that the reader is positioned after the JPEG SOI marker.
func findJPEGAPP1Marker(r io.Reader) error {
const (
markerAPP1 = 0xffe1
)
for {
var marker, size uint16
if err := binary.Read(r, binary.BigEndian, &marker); err != nil {
return err
}
if err := binary.Read(r, binary.BigEndian, &size); err != nil {
return err
}
if marker>>8 != 0xff {
return errors.New("invalid JPEG marker")
}
if marker == markerAPP1 {
break
}
if size < 2 {
return errors.New("invalid block size")
}
if _, err := io.CopyN(io.Discard, r, int64(size-2)); err != nil {
return err
}
}
return nil
}
// findEXIFHeader tries to find the EXIF header in r.
// This function assumes that the reader is positioned after the JPEG APP1 marker.
func findEXIFHeader(r io.Reader) error {
const (
exifHeader = 0x45786966
)
var header uint32
if err := binary.Read(r, binary.BigEndian, &header); err != nil {
return err
}
if header != exifHeader {
return errors.New("exif header not found")
}
if _, err := io.CopyN(io.Discard, r, 2); err != nil {
return err
}
return nil
}
// readByteOrder reads the byte order from r.
// This function assumes that the reader is positioned after the EXIF header.
func readByteOrder(r io.Reader) (binary.ByteOrder, error) {
const (
byteOrderBE = 0x4d4d
byteOrderLE = 0x4949
)
var byteOrderTag uint16
if err := binary.Read(r, binary.BigEndian, &byteOrderTag); err != nil {
return nil, err
}
var byteOrder binary.ByteOrder
switch byteOrderTag {
case byteOrderBE:
byteOrder = binary.BigEndian
case byteOrderLE:
byteOrder = binary.LittleEndian
default:
return nil, errors.New("invalid byte order flag")
}
if _, err := io.CopyN(io.Discard, r, 2); err != nil {
return nil, err
}
return byteOrder, nil
}
// skipEXIFOffset skips the EXIF offset in r.
// This function assumes that the reader is positioned after the byte order tag.
func skipEXIFOffset(r io.Reader, byteOrder binary.ByteOrder) error {
var offset uint32
if err := binary.Read(r, byteOrder, &offset); err != nil {
return err
}
if offset < 8 {
return errors.New("invalid offset value")
}
if _, err := io.CopyN(io.Discard, r, int64(offset-8)); err != nil {
return err
}
return nil
}
// readNumTags reads the number of tags from r.
// This function assumes that the reader is positioned after the EXIF offset.
func readNumTags(r io.Reader, byteOrder binary.ByteOrder) (uint16, error) {
var numTags uint16
if err := binary.Read(r, byteOrder, &numTags); err != nil {
return 0, err
}
return numTags, nil
}
// findOrientationTag tries to find the orientation tag in r.
// This function assumes that the reader is positioned after the number of tags.
func findOrientationTag(r io.Reader, byteOrder binary.ByteOrder, numTags uint16) (Orientation, error) {
const (
orientationTag = 0x0112
)
for i := 0; i < int(numTags); i++ {
var tag uint16
if err := binary.Read(r, byteOrder, &tag); err != nil {
return OrientationUnspecified, err
}
if tag != orientationTag {
if _, err := io.CopyN(io.Discard, r, 10); err != nil {
return OrientationUnspecified, err
}
continue
}
if _, err := io.CopyN(io.Discard, r, 6); err != nil {
return OrientationUnspecified, err
}
var val uint16
if err := binary.Read(r, byteOrder, &val); err != nil {
return OrientationUnspecified, err
}
if val < 1 || val > 8 {
return OrientationUnspecified, errors.New("invalid tag value")
}
return Orientation(val), nil
}
return OrientationUnspecified, nil // Missing orientation tag.
}