Skip to content

Commit bd85aee

Browse files
committed
Add TIFF support, XMP support for JPEGs etc.
1 parent c5c49b0 commit bd85aee

File tree

11 files changed

+369
-177
lines changed

11 files changed

+369
-177
lines changed

‎imagedecoder_jpg.go‎

Lines changed: 87 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -2,96 +2,119 @@ package imagemeta
22

33
import (
44
"encoding/binary"
5+
"io"
56
)
67

78
type imageDecoderJPEG struct {
89
*baseStreamingDecoder
910
}
1011

11-
func (e *imageDecoderJPEG) decode() (err error) {
12+
func (e *imageDecoderJPEG) decode() error {
1213
// JPEG SOI marker.
13-
var soi uint16
14-
if soi, err = e.read2E(); err != nil {
14+
soi, err := e.read2E()
15+
if err != nil {
1516
return nil
1617
}
18+
1719
if soi != markerSOI {
18-
return
20+
return nil
1921
}
2022

21-
findMarker := func(markerToFind uint16) int {
22-
for {
23-
var marker, length uint16
24-
if marker, err = e.read2E(); err != nil {
25-
return -1
26-
}
27-
if length, err = e.read2E(); err != nil {
28-
return -1
29-
}
23+
// These are the sources we support.
24+
sourceSet := TagSourceEXIF | TagSourceIPTC | TagSourceXMP
25+
// Remove sources that are not requested.
26+
sourceSet = sourceSet & e.opts.Sources
3027

31-
// All JPEG markers begin with 0xff.
32-
if marker>>8 != 0xff {
33-
return -1
34-
}
28+
for {
29+
if sourceSet.IsZero() {
30+
// Done.
31+
return nil
32+
}
33+
marker := e.read2()
34+
if e.isEOF {
35+
return nil
36+
}
3537

36-
if marker == markerToFind {
37-
return int(length)
38-
}
38+
if marker == 0 {
39+
continue
40+
}
3941

40-
if length < 2 {
41-
return -1
42-
}
42+
if marker == markerSOS {
43+
// Start of scan. We're done.
44+
return nil
45+
}
4346

44-
e.skip(int64(length - 2))
47+
// Read the 16-bit length of the segment. The value includes the 2 bytes for the
48+
// length itself, so we subtract 2 to get the number of remaining bytes.
49+
length := e.read2()
50+
if length < 2 {
51+
return ErrInvalidFormat
4552
}
46-
}
53+
length -= 2
4754

48-
if e.opts.Sources.Has(TagSourceEXIF) {
49-
pos := e.pos()
50-
if length := findMarker(markerAPP1); length > 0 {
51-
err := func() error {
52-
r, err := e.bufferedReader(length)
53-
if err != nil {
54-
return err
55-
}
56-
defer r.Close()
57-
exifr := newMetaDecoderEXIF(r, e.opts.HandleTag)
58-
59-
header := exifr.read4()
60-
if header != exifHeader {
61-
return err
62-
}
63-
exifr.skip(2)
64-
if err := exifr.decode(); err != nil {
65-
return err
66-
}
67-
return nil
68-
69-
}()
70-
71-
if err != nil {
55+
if marker == markerApp1EXIF && sourceSet.Has(TagSourceEXIF) {
56+
sourceSet = sourceSet.Remove(TagSourceEXIF)
57+
if err := e.handleEXIF(int(length)); err != nil {
7258
return err
7359
}
60+
continue
61+
}
7462

63+
if marker == markerApp13 && sourceSet.Has(TagSourceIPTC) {
64+
sourceSet = sourceSet.Remove(TagSourceIPTC)
65+
if err := e.handleIPTC(int(length)); err != nil {
66+
return err
67+
}
68+
continue
7569
}
76-
e.seek(pos)
77-
}
7870

79-
if e.opts.Sources.Has(TagSourceIPTC) {
80-
// EXIF may be stored in a different order, but IPTC is always big-endian.
81-
e.byteOrder = binary.BigEndian
82-
if length := findMarker(markerApp13); length > 0 {
83-
if err := func() error {
84-
r, err := e.bufferedReader(length)
85-
if err != nil {
86-
return err
87-
}
88-
defer r.Close()
89-
dec := newMetaDecoderIPTC(r, e.opts.HandleTag)
90-
return dec.decode()
91-
}(); err != nil {
71+
if marker == markerrApp1XMP && sourceSet.Has(TagSourceXMP) {
72+
sourceSet = sourceSet.Remove(TagSourceXMP)
73+
const xmpIDLen = 29
74+
if length < xmpIDLen {
75+
return ErrInvalidFormat
76+
}
77+
e.skip(int64(xmpIDLen))
78+
length -= xmpIDLen
79+
r := io.LimitReader(e.r, int64(length))
80+
if err := decodeXMP(r, e.opts.HandleTag); err != nil {
9281
return err
9382
}
83+
continue
9484
}
85+
86+
e.skip(int64(length))
87+
}
88+
}
89+
90+
func (e *imageDecoderJPEG) handleIPTC(length int) error {
91+
// EXIF may be stored in a different order, but IPTC is always big-endian.
92+
e.byteOrder = binary.BigEndian
93+
r, err := e.bufferedReader(length)
94+
if err != nil {
95+
return err
96+
}
97+
defer r.Close()
98+
dec := newMetaDecoderIPTC(r, e.opts.HandleTag)
99+
return dec.decode()
100+
}
101+
102+
func (e *imageDecoderJPEG) handleEXIF(length int) error {
103+
r, err := e.bufferedReader(length)
104+
if err != nil {
105+
return err
106+
}
107+
defer r.Close()
108+
exifr := newMetaDecoderEXIF(r, e.opts.HandleTag)
109+
110+
header := exifr.read4()
111+
if header != exifHeader {
112+
return err
113+
}
114+
exifr.skip(2)
115+
if err := exifr.decode(); err != nil {
116+
return err
95117
}
96118
return nil
119+
97120
}

‎imagedecoder_png.go‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ type imageDecoderPNG struct {
44
*baseStreamingDecoder
55
}
66

7-
func (e *imageDecoderPNG) decode() (err error) {
7+
func (e *imageDecoderPNG) decode() error {
88
// http://ftp-osl.osuosl.org/pub/libpng/documents/pngext-1.5.0.html#C.eXIf
99
// The four-byte chunk type field contains the decimal values:
1010
// 101 88 73 102 (ASCII "eXIf")
@@ -19,7 +19,7 @@ func (e *imageDecoderPNG) decode() (err error) {
1919
for {
2020
chunkLength, typ := e.read4(), e.read4()
2121

22-
if typ == pngExifMarker {
22+
if typ == pngEXIFMarker {
2323
return func() error {
2424
r, err := e.bufferedReader(int(chunkLength))
2525
if err != nil {

‎imagedecoder_tif.go‎

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package imagemeta
2+
3+
import (
4+
"encoding/binary"
5+
"io"
6+
)
7+
8+
type imageDecoderTIF struct {
9+
*baseStreamingDecoder
10+
}
11+
12+
func (e *imageDecoderTIF) decode() error {
13+
const (
14+
xmpMarker = 0x02bc
15+
meaningOfLife = 42
16+
)
17+
18+
// These are the sources we currently support in TIFF.
19+
sourceSet := TagSourceXMP
20+
// Remove sources that are not requested.
21+
sourceSet = sourceSet & e.opts.Sources
22+
23+
if sourceSet.IsZero() {
24+
// Done.
25+
return nil
26+
}
27+
28+
byteOrderTag := e.read2()
29+
switch byteOrderTag {
30+
case byteOrderBigEndian:
31+
e.byteOrder = binary.BigEndian
32+
case byteOrderLittleEndian:
33+
e.byteOrder = binary.LittleEndian
34+
default:
35+
return ErrInvalidFormat
36+
}
37+
38+
if id := e.read2(); id != meaningOfLife {
39+
return ErrInvalidFormat
40+
}
41+
42+
ifdOffset := e.read4()
43+
44+
if ifdOffset < 8 {
45+
return ErrInvalidFormat
46+
}
47+
48+
e.skip(int64(ifdOffset - 8))
49+
50+
entryCount := e.read2()
51+
52+
for i := 0; i < int(entryCount); i++ {
53+
tag := e.read2()
54+
// Skip type
55+
e.skip(2)
56+
count := e.read4()
57+
valueOffset := e.read4() // Offset relative to the start of the file.
58+
if tag == xmpMarker {
59+
pos := e.pos()
60+
e.seek(int(valueOffset))
61+
r := io.LimitReader(e.r, int64(count))
62+
if err := decodeXMP(r, e.opts.HandleTag); err != nil {
63+
return err
64+
}
65+
sourceSet = sourceSet.Remove(TagSourceXMP)
66+
if sourceSet.IsZero() {
67+
return nil
68+
}
69+
e.seek(pos)
70+
}
71+
}
72+
73+
return nil
74+
75+
}

0 commit comments

Comments
 (0)