Skip to content

Commit

Permalink
Add TIFF support, XMP support for JPEGs etc.
Browse files Browse the repository at this point in the history
  • Loading branch information
bep committed Aug 15, 2023
1 parent c5c49b0 commit ea84217
Show file tree
Hide file tree
Showing 11 changed files with 369 additions and 177 deletions.
151 changes: 87 additions & 64 deletions imagedecoder_jpg.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,96 +2,119 @@ package imagemeta

import (
"encoding/binary"
"io"
)

type imageDecoderJPEG struct {
*baseStreamingDecoder
}

func (e *imageDecoderJPEG) decode() (err error) {
func (e *imageDecoderJPEG) decode() error {
// JPEG SOI marker.
var soi uint16
if soi, err = e.read2E(); err != nil {
soi, err := e.read2E()
if err != nil {
return nil
}

if soi != markerSOI {
return
return nil
}

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

// All JPEG markers begin with 0xff.
if marker>>8 != 0xff {
return -1
}
for {
if sourceSet.IsZero() {
// Done.
return nil
}
marker := e.read2()
if e.isEOF {
return nil
}

if marker == markerToFind {
return int(length)
}
if marker == 0 {
continue
}

if length < 2 {
return -1
}
if marker == markerSOS {
// Start of scan. We're done.
return nil
}

e.skip(int64(length - 2))
// Read the 16-bit length of the segment. The value includes the 2 bytes for the
// length itself, so we subtract 2 to get the number of remaining bytes.
length := e.read2()
if length < 2 {
return ErrInvalidFormat
}
}
length -= 2

if e.opts.Sources.Has(TagSourceEXIF) {
pos := e.pos()
if length := findMarker(markerAPP1); length > 0 {
err := func() error {
r, err := e.bufferedReader(length)
if err != nil {
return err
}
defer r.Close()
exifr := newMetaDecoderEXIF(r, e.opts.HandleTag)

header := exifr.read4()
if header != exifHeader {
return err
}
exifr.skip(2)
if err := exifr.decode(); err != nil {
return err
}
return nil

}()

if err != nil {
if marker == markerApp1EXIF && sourceSet.Has(TagSourceEXIF) {
sourceSet = sourceSet.Remove(TagSourceEXIF)
if err := e.handleEXIF(int(length)); err != nil {
return err
}
continue
}

if marker == markerApp13 && sourceSet.Has(TagSourceIPTC) {
sourceSet = sourceSet.Remove(TagSourceIPTC)
if err := e.handleIPTC(int(length)); err != nil {
return err
}
continue
}
e.seek(pos)
}

if e.opts.Sources.Has(TagSourceIPTC) {
// EXIF may be stored in a different order, but IPTC is always big-endian.
e.byteOrder = binary.BigEndian
if length := findMarker(markerApp13); length > 0 {
if err := func() error {
r, err := e.bufferedReader(length)
if err != nil {
return err
}
defer r.Close()
dec := newMetaDecoderIPTC(r, e.opts.HandleTag)
return dec.decode()
}(); err != nil {
if marker == markerrApp1XMP && sourceSet.Has(TagSourceXMP) {
sourceSet = sourceSet.Remove(TagSourceXMP)
const xmpIDLen = 29
if length < xmpIDLen {
return ErrInvalidFormat
}
e.skip(int64(xmpIDLen))
length -= xmpIDLen
r := io.LimitReader(e.r, int64(length))
if err := decodeXMP(r, e.opts.HandleTag); err != nil {
return err
}
continue
}

e.skip(int64(length))
}
}

func (e *imageDecoderJPEG) handleIPTC(length int) error {
// EXIF may be stored in a different order, but IPTC is always big-endian.
e.byteOrder = binary.BigEndian
r, err := e.bufferedReader(length)
if err != nil {
return err
}
defer r.Close()
dec := newMetaDecoderIPTC(r, e.opts.HandleTag)
return dec.decode()
}

func (e *imageDecoderJPEG) handleEXIF(length int) error {
r, err := e.bufferedReader(length)
if err != nil {
return err
}
defer r.Close()
exifr := newMetaDecoderEXIF(r, e.opts.HandleTag)

header := exifr.read4()
if header != exifHeader {
return err
}
exifr.skip(2)
if err := exifr.decode(); err != nil {
return err
}
return nil

}
4 changes: 2 additions & 2 deletions imagedecoder_png.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ type imageDecoderPNG struct {
*baseStreamingDecoder
}

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

if typ == pngExifMarker {
if typ == pngEXIFMarker {
return func() error {
r, err := e.bufferedReader(int(chunkLength))
if err != nil {
Expand Down
75 changes: 75 additions & 0 deletions imagedecoder_tif.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package imagemeta

import (
"encoding/binary"
"io"
)

type imageDecoderTIF struct {
*baseStreamingDecoder
}

func (e *imageDecoderTIF) decode() error {
const (
xmpMarker = 0x02bc
meaningOfLife = 42
)

// These are the sources we currently support in TIFF.
sourceSet := TagSourceXMP
// Remove sources that are not requested.
sourceSet = sourceSet & e.opts.Sources

if sourceSet.IsZero() {
// Done.
return nil
}

byteOrderTag := e.read2()
switch byteOrderTag {
case byteOrderBigEndian:
e.byteOrder = binary.BigEndian
case byteOrderLittleEndian:
e.byteOrder = binary.LittleEndian
default:
return ErrInvalidFormat
}

if id := e.read2(); id != meaningOfLife {
return ErrInvalidFormat
}

ifdOffset := e.read4()

if ifdOffset < 8 {
return ErrInvalidFormat
}

e.skip(int64(ifdOffset - 8))

entryCount := e.read2()

for i := 0; i < int(entryCount); i++ {
tag := e.read2()
// Skip type
e.skip(2)
count := e.read4()
valueOffset := e.read4() // Offset relative to the start of the file.
if tag == xmpMarker {
pos := e.pos()
e.seek(int(valueOffset))
r := io.LimitReader(e.r, int64(count))
if err := decodeXMP(r, e.opts.HandleTag); err != nil {
return err
}
sourceSet = sourceSet.Remove(TagSourceXMP)
if sourceSet.IsZero() {
return nil
}
e.seek(pos)
}
}

return nil

}
Loading

0 comments on commit ea84217

Please sign in to comment.