mirror of
https://github.com/hanzoai/goexif.git
synced 2026-09-30 22:36:24 +00:00
Revert "Implement a lazy tiff loader (#60)"
This reverts commit 00239e02db.
This commit is contained in:
+138
-114
@@ -3,12 +3,14 @@
|
|||||||
package exif
|
package exif
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"math"
|
"math"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -138,15 +140,6 @@ var stagePrefix = map[tiffError]string{
|
|||||||
loadInteroperability: "loading Interoperability sub-IFD",
|
loadInteroperability: "loading Interoperability sub-IFD",
|
||||||
}
|
}
|
||||||
|
|
||||||
type offsetReaderAt struct {
|
|
||||||
offset int64
|
|
||||||
r io.ReaderAt
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *offsetReaderAt) ReadAt(p []byte, off int64) (int, error) {
|
|
||||||
return r.r.ReadAt(p, off+r.offset)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse reads data from the tiff data in x and populates the tags
|
// Parse reads data from the tiff data in x and populates the tags
|
||||||
// in x. If parsing a sub-IFD fails, the error is recorded and
|
// in x. If parsing a sub-IFD fails, the error is recorded and
|
||||||
// parsing continues with the remaining sub-IFDs.
|
// parsing continues with the remaining sub-IFDs.
|
||||||
@@ -181,6 +174,7 @@ func (p *parser) Parse(x *Exif) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func loadSubDir(x *Exif, ptr FieldName, fieldMap map[uint16]FieldName) error {
|
func loadSubDir(x *Exif, ptr FieldName, fieldMap map[uint16]FieldName) error {
|
||||||
|
r := bytes.NewReader(x.Raw)
|
||||||
|
|
||||||
tag, err := x.Get(ptr)
|
tag, err := x.Get(ptr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -191,7 +185,11 @@ func loadSubDir(x *Exif, ptr FieldName, fieldMap map[uint16]FieldName) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
subDir, _, err := tiff.DecodeDir(x.ReaderAt, x.Tiff.Order, uint32(offset))
|
_, err = r.Seek(offset, 0)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("exif: seek to sub-IFD %s failed: %v", ptr, err)
|
||||||
|
}
|
||||||
|
subDir, _, err := tiff.DecodeDir(r, x.Tiff.Order)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("exif: sub-IFD %s decode failed: %v", ptr, err)
|
return fmt.Errorf("exif: sub-IFD %s decode failed: %v", ptr, err)
|
||||||
}
|
}
|
||||||
@@ -201,12 +199,9 @@ func loadSubDir(x *Exif, ptr FieldName, fieldMap map[uint16]FieldName) error {
|
|||||||
|
|
||||||
// Exif provides access to decoded EXIF metadata fields and values.
|
// Exif provides access to decoded EXIF metadata fields and values.
|
||||||
type Exif struct {
|
type Exif struct {
|
||||||
Tiff *tiff.Tiff
|
Tiff *tiff.Tiff
|
||||||
main map[FieldName]*tiff.Tag
|
main map[FieldName]*tiff.Tag
|
||||||
Raw []byte
|
Raw []byte
|
||||||
ReaderAt io.ReaderAt
|
|
||||||
exifLength uint16
|
|
||||||
exifStart uint32
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decode parses EXIF data from r (a TIFF, JPEG, or raw EXIF block)
|
// Decode parses EXIF data from r (a TIFF, JPEG, or raw EXIF block)
|
||||||
@@ -218,39 +213,6 @@ type Exif struct {
|
|||||||
// The error can be inspected with functions such as IsCriticalError
|
// The error can be inspected with functions such as IsCriticalError
|
||||||
// to determine whether the returned object might still be usable.
|
// to determine whether the returned object might still be usable.
|
||||||
func Decode(r io.Reader) (*Exif, error) {
|
func Decode(r io.Reader) (*Exif, error) {
|
||||||
reader := &readerToReaderAt{reader: r}
|
|
||||||
exif, err := LazyDecode(reader)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
_, err = exif.Tiff.LoadAllVals()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if exif.exifLength > 0 {
|
|
||||||
exif.Raw = make([]byte, exif.exifLength)
|
|
||||||
_, err := exif.ReaderAt.ReadAt(exif.Raw, 0)
|
|
||||||
if err != nil {
|
|
||||||
return nil, decodeError{cause: err}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
exif.Raw = reader.buffer[exif.exifStart:]
|
|
||||||
}
|
|
||||||
return exif, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// LazyDecode parses EXIF data from r (a TIFF, JPEG, or raw EXIF block)
|
|
||||||
// and returns a queryable Exif object. After the EXIF data section is
|
|
||||||
// called and the TIFF structure is decoded, each registered parser is
|
|
||||||
// called (in order of registration). If one parser returns an error,
|
|
||||||
// decoding terminates and the remaining parsers are not called.
|
|
||||||
//
|
|
||||||
// The error can be inspected with functions such as IsCriticalError
|
|
||||||
// to determine whether the returned object might still be usable.
|
|
||||||
//
|
|
||||||
// It differs from Decode as it only reads known exif fields and does not attempt to read the
|
|
||||||
// entire contents of the exif tiff subdirectory; the Exif.Raw field will be nil.
|
|
||||||
func LazyDecode(r io.ReaderAt) (*Exif, error) {
|
|
||||||
|
|
||||||
// EXIF data in JPEG is stored in the APP1 marker. EXIF data uses the TIFF
|
// EXIF data in JPEG is stored in the APP1 marker. EXIF data uses the TIFF
|
||||||
// format to store data.
|
// format to store data.
|
||||||
@@ -258,41 +220,86 @@ func LazyDecode(r io.ReaderAt) (*Exif, error) {
|
|||||||
// If we're parsing a JPEG image, we need to strip away the JPEG APP1
|
// If we're parsing a JPEG image, we need to strip away the JPEG APP1
|
||||||
// marker and also the EXIF header.
|
// marker and also the EXIF header.
|
||||||
|
|
||||||
// build an exif structure from the tiff
|
header := make([]byte, 4)
|
||||||
x := &Exif{
|
n, err := io.ReadFull(r, header)
|
||||||
main: map[FieldName]*tiff.Tag{},
|
if err != nil {
|
||||||
exifStart: 0,
|
return nil, fmt.Errorf("exif: error reading 4 byte header, got %d, %v", n, err)
|
||||||
exifLength: 0,
|
|
||||||
ReaderAt: r,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
b := make([]byte, 6)
|
var isTiff bool
|
||||||
_, err := r.ReadAt(b, 0)
|
var isRawExif bool
|
||||||
if bytes.Compare(b, []byte("Exif\x00\x00")) == 0 {
|
var assumeJPEG bool
|
||||||
x.exifStart = 6
|
switch string(header) {
|
||||||
x.ReaderAt = &offsetReaderAt{
|
case "II*\x00":
|
||||||
offset: 6,
|
// TIFF - Little endian (Intel)
|
||||||
r: x.ReaderAt,
|
isTiff = true
|
||||||
|
case "MM\x00*":
|
||||||
|
// TIFF - Big endian (Motorola)
|
||||||
|
isTiff = true
|
||||||
|
case "Exif":
|
||||||
|
isRawExif = true
|
||||||
|
default:
|
||||||
|
// Not TIFF, assume JPEG
|
||||||
|
assumeJPEG = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put the header bytes back into the reader.
|
||||||
|
r = io.MultiReader(bytes.NewReader(header), r)
|
||||||
|
var (
|
||||||
|
er *bytes.Reader
|
||||||
|
tif *tiff.Tiff
|
||||||
|
sec *appSec
|
||||||
|
)
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case isRawExif:
|
||||||
|
var header [6]byte
|
||||||
|
if _, err := io.ReadFull(r, header[:]); err != nil {
|
||||||
|
return nil, fmt.Errorf("exif: unexpected raw exif header read error")
|
||||||
}
|
}
|
||||||
}
|
if got, want := string(header[:]), "Exif\x00\x00"; got != want {
|
||||||
|
return nil, fmt.Errorf("exif: unexpected raw exif header; got %q, want %q", got, want)
|
||||||
x.Tiff, err = tiff.LazyDecode(x.ReaderAt)
|
}
|
||||||
if tiff.IsInvalidTiff(err) {
|
fallthrough
|
||||||
x.exifStart, x.exifLength, err = findJPEGExifOffset(jpeg_APP1, r)
|
case isTiff:
|
||||||
|
// Functions below need the IFDs from the TIFF data to be stored in a
|
||||||
|
// *bytes.Reader. We use TeeReader to get a copy of the bytes as a
|
||||||
|
// side-effect of tiff.Decode() doing its work.
|
||||||
|
b := &bytes.Buffer{}
|
||||||
|
tr := io.TeeReader(r, b)
|
||||||
|
tif, err = tiff.Decode(tr)
|
||||||
|
er = bytes.NewReader(b.Bytes())
|
||||||
|
case assumeJPEG:
|
||||||
|
// Locate the JPEG APP1 header.
|
||||||
|
sec, err = newAppSec(jpeg_APP1, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, decodeError{cause: err}
|
return nil, err
|
||||||
}
|
}
|
||||||
x.ReaderAt = &offsetReaderAt{
|
// Strip away EXIF header.
|
||||||
offset: int64(x.exifStart),
|
er, err = sec.exifReader()
|
||||||
r: x.ReaderAt,
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
x.Tiff, err = tiff.LazyDecode(x.ReaderAt)
|
tif, err = tiff.Decode(er)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, decodeError{cause: err}
|
return nil, decodeError{cause: err}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
er.Seek(0, 0)
|
||||||
|
raw, err := ioutil.ReadAll(er)
|
||||||
|
if err != nil {
|
||||||
|
return nil, decodeError{cause: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
// build an exif structure from the tiff
|
||||||
|
x := &Exif{
|
||||||
|
main: map[FieldName]*tiff.Tag{},
|
||||||
|
Tiff: tif,
|
||||||
|
Raw: raw,
|
||||||
|
}
|
||||||
|
|
||||||
for i, p := range parsers {
|
for i, p := range parsers {
|
||||||
if err := p.Parse(x); err != nil {
|
if err := p.Parse(x); err != nil {
|
||||||
if _, ok := err.(tiffErrors); ok {
|
if _, ok := err.(tiffErrors); ok {
|
||||||
@@ -347,10 +354,6 @@ type Walker interface {
|
|||||||
// EXIF field. If w aborts the walk with an error, that error is returned.
|
// EXIF field. If w aborts the walk with an error, that error is returned.
|
||||||
func (x *Exif) Walk(w Walker) error {
|
func (x *Exif) Walk(w Walker) error {
|
||||||
for name, tag := range x.main {
|
for name, tag := range x.main {
|
||||||
err := tag.LoadVal()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := w.Walk(name, tag); err != nil {
|
if err := w.Walk(name, tag); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -572,13 +575,8 @@ func (x *Exif) JpegThumbnail() ([]byte, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
tiff := make([]byte, l)
|
|
||||||
_, err = x.ReaderAt.ReadAt(tiff, int64(start))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return tiff, nil
|
return x.Raw[start : start+l], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalJson implements the encoding/json.Marshaler interface providing output of
|
// MarshalJson implements the encoding/json.Marshaler interface providing output of
|
||||||
@@ -587,45 +585,71 @@ func (x Exif) MarshalJSON() ([]byte, error) {
|
|||||||
return json.Marshal(x.main)
|
return json.Marshal(x.main)
|
||||||
}
|
}
|
||||||
|
|
||||||
// findJPEGExifOffset finds marker in r and returns the offset of the Exif data
|
type appSec struct {
|
||||||
func findJPEGExifOffset(marker byte, r io.ReaderAt) (uint32, uint16, error) {
|
marker byte
|
||||||
// read the file 1MB per 1MB to locate the marker
|
data []byte
|
||||||
bufferLength := 4096
|
}
|
||||||
// Ensure there is a small overlap between buffers to handle the case
|
|
||||||
// where the marker is located exactly in-between 2 reads
|
// newAppSec finds marker in r and returns the corresponding application data
|
||||||
seeker := make([]byte, bufferLength+2)
|
// section.
|
||||||
appHeader := []byte{0xFF, marker}
|
func newAppSec(marker byte, r io.Reader) (*appSec, error) {
|
||||||
|
br := bufio.NewReader(r)
|
||||||
|
app := &appSec{marker: marker}
|
||||||
|
var dataLen int
|
||||||
|
|
||||||
// seek to marker
|
// seek to marker
|
||||||
var markerIndex int
|
for dataLen == 0 {
|
||||||
i := 0
|
if _, err := br.ReadBytes(0xFF); err != nil {
|
||||||
for {
|
return nil, err
|
||||||
count, err := r.ReadAt(seeker, int64(i*bufferLength))
|
}
|
||||||
if err != nil && err != io.EOF {
|
c, err := br.ReadByte()
|
||||||
return 0, 0, err
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if c != marker {
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
markerIndex = bytes.Index(seeker, appHeader)
|
dataLenBytes := make([]byte, 2)
|
||||||
// stop at the end of the file or when the marker is found
|
for k, _ := range dataLenBytes {
|
||||||
if markerIndex >= 0 || count < bufferLength {
|
c, err := br.ReadByte()
|
||||||
break
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
dataLenBytes[k] = c
|
||||||
}
|
}
|
||||||
i++
|
dataLen = int(binary.BigEndian.Uint16(dataLenBytes)) - 2
|
||||||
}
|
}
|
||||||
if markerIndex < 0 {
|
|
||||||
return 0, 0, fmt.Errorf("Unable to find the JPEG Exif marker")
|
// read section data
|
||||||
|
nread := 0
|
||||||
|
for nread < dataLen {
|
||||||
|
s := make([]byte, dataLen-nread)
|
||||||
|
n, err := br.Read(s)
|
||||||
|
nread += n
|
||||||
|
if err != nil && nread < dataLen {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
app.data = append(app.data, s[:n]...)
|
||||||
}
|
}
|
||||||
// Skip the application header
|
return app, nil
|
||||||
markerIndex += i*bufferLength + 2
|
}
|
||||||
// Read content length and check that it actually contains an exif
|
|
||||||
header := make([]byte, 8)
|
// reader returns a reader on this appSec.
|
||||||
_, err := r.ReadAt(header, int64(markerIndex))
|
func (app *appSec) reader() *bytes.Reader {
|
||||||
if err != nil {
|
return bytes.NewReader(app.data)
|
||||||
return 0, 0, err
|
}
|
||||||
|
|
||||||
|
// exifReader returns a reader on this appSec with the read cursor advanced to
|
||||||
|
// the start of the exif's tiff encoded portion.
|
||||||
|
func (app *appSec) exifReader() (*bytes.Reader, error) {
|
||||||
|
if len(app.data) < 6 {
|
||||||
|
return nil, errors.New("exif: failed to find exif intro marker")
|
||||||
}
|
}
|
||||||
if !bytes.Equal(header[2:], []byte("Exif\000\000")) {
|
|
||||||
return 0, 0, fmt.Errorf("Unable to find the JPEG Exif marker")
|
// read/check for exif special mark
|
||||||
|
exif := app.data[:6]
|
||||||
|
if !bytes.Equal(exif, append([]byte("Exif"), 0x00, 0x00)) {
|
||||||
|
return nil, errors.New("exif: failed to find exif intro marker")
|
||||||
}
|
}
|
||||||
dataLen := binary.BigEndian.Uint16(header[:2]) - 2
|
return bytes.NewReader(app.data[6:]), nil
|
||||||
return uint32(markerIndex + len(header)), dataLen, nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-112
@@ -7,10 +7,8 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"math"
|
"math"
|
||||||
"net/http"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -52,14 +50,6 @@ func TestDecode(t *testing.T) {
|
|||||||
t.Fatalf("No error and yet %v was not decoded", name)
|
t.Fatalf("No error and yet %v was not decoded", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Run("the raw buffer contains a valid tiff", func(t *testing.T) {
|
|
||||||
b := bytes.NewReader(x.Raw)
|
|
||||||
_, err := tiff.Decode(b)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("The raw buffer does not contain a valid TIFF format: %v", err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Logf("checking pic %v", name)
|
t.Logf("checking pic %v", name)
|
||||||
x.Walk(&walker{name, t})
|
x.Walk(&walker{name, t})
|
||||||
cnt++
|
cnt++
|
||||||
@@ -79,15 +69,6 @@ func TestDecodeRawEXIF(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Decode: %v", err)
|
t.Fatalf("Decode: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Run("the raw buffer contains a valid tiff", func(t *testing.T) {
|
|
||||||
b := bytes.NewReader(x.Raw)
|
|
||||||
_, err := tiff.Decode(b)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("The raw buffer does not contain a valid TIFF format: %v", err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
got := map[string]string{}
|
got := map[string]string{}
|
||||||
err = x.Walk(walkFunc(func(name FieldName, tag *tiff.Tag) error {
|
err = x.Walk(walkFunc(func(name FieldName, tag *tiff.Tag) error {
|
||||||
got[fmt.Sprint(name)] = fmt.Sprint(tag)
|
got[fmt.Sprint(name)] = fmt.Sprint(tag)
|
||||||
@@ -234,7 +215,7 @@ func TestHugeTagError(t *testing.T) {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("no error on bad exif data")
|
t.Fatal("no error on bad exif data")
|
||||||
}
|
}
|
||||||
if !strings.Contains(err.Error(), "zero length tag value") {
|
if !strings.Contains(err.Error(), "short read") {
|
||||||
t.Fatal("wrong error:", err.Error())
|
t.Fatal("wrong error:", err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -256,95 +237,3 @@ func TestZeroLengthTagError(t *testing.T) {
|
|||||||
t.Fatal("wrong error:", err.Error())
|
t.Fatal("wrong error:", err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func downloadRAW(link, destFile string) error {
|
|
||||||
_, err := os.Stat(destFile)
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
resp, err := http.Get(link)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Failed to download image %s: %s", link, err)
|
|
||||||
} else {
|
|
||||||
defer resp.Body.Close()
|
|
||||||
fd, err := os.Create(destFile)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Failed to download image %s: %s", link, err)
|
|
||||||
} else {
|
|
||||||
io.Copy(fd, resp.Body)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fmt.Println("Downloaded", link, "to", destFile)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkDecode(b *testing.B) {
|
|
||||||
testFile := "test.jpg"
|
|
||||||
downloadRAW("http://web.canon.jp/imaging/eosd/samples/eos5ds/downloads/02.jpg", testFile)
|
|
||||||
b.ResetTimer()
|
|
||||||
|
|
||||||
for n := 0; n < b.N; n++ {
|
|
||||||
fd, err := os.Open(testFile)
|
|
||||||
if err != nil {
|
|
||||||
b.Errorf("Failed to open test file %s", err.Error())
|
|
||||||
}
|
|
||||||
_, err = Decode(fd)
|
|
||||||
if err != nil {
|
|
||||||
b.Errorf("Failed to decode test file %s", err.Error())
|
|
||||||
}
|
|
||||||
fd.Close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkDecodeRaw(b *testing.B) {
|
|
||||||
testFile := "test.cr2"
|
|
||||||
downloadRAW("http://www.rawsamples.ch/raws/canon/RAW_CANON_EOS_5DS.CR2", testFile)
|
|
||||||
b.ResetTimer()
|
|
||||||
|
|
||||||
for n := 0; n < b.N; n++ {
|
|
||||||
fd, err := os.Open(testFile)
|
|
||||||
if err != nil {
|
|
||||||
b.Errorf("Failed to open test file %s", err.Error())
|
|
||||||
}
|
|
||||||
_, err = Decode(fd)
|
|
||||||
if err != nil {
|
|
||||||
b.Errorf("Failed to decode test file %s", err.Error())
|
|
||||||
}
|
|
||||||
fd.Close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkLazyDecode(b *testing.B) {
|
|
||||||
testFile := "test.jpg"
|
|
||||||
downloadRAW("http://web.canon.jp/imaging/eosd/samples/eos5ds/downloads/02.jpg", testFile)
|
|
||||||
b.ResetTimer()
|
|
||||||
|
|
||||||
for n := 0; n < b.N; n++ {
|
|
||||||
fd, err := os.Open(testFile)
|
|
||||||
if err != nil {
|
|
||||||
b.Errorf("Failed to open test file %s", err.Error())
|
|
||||||
}
|
|
||||||
_, err = LazyDecode(fd)
|
|
||||||
if err != nil {
|
|
||||||
b.Errorf("Failed to decode test file %s", err.Error())
|
|
||||||
}
|
|
||||||
fd.Close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkLazyDecodeRaw(b *testing.B) {
|
|
||||||
testFile := "test.cr2"
|
|
||||||
downloadRAW("http://www.rawsamples.ch/raws/canon/RAW_CANON_EOS_5DS.CR2", testFile)
|
|
||||||
b.ResetTimer()
|
|
||||||
|
|
||||||
for n := 0; n < b.N; n++ {
|
|
||||||
fd, err := os.Open(testFile)
|
|
||||||
if err != nil {
|
|
||||||
b.Errorf("Failed to open test file %s", err.Error())
|
|
||||||
}
|
|
||||||
_, err = LazyDecode(fd)
|
|
||||||
if err != nil {
|
|
||||||
b.Errorf("Failed to decode test file %s", err.Error())
|
|
||||||
}
|
|
||||||
fd.Close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
package exif
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
type readerToReaderAt struct {
|
|
||||||
reader io.Reader
|
|
||||||
buffer []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *readerToReaderAt) ReadAt(p []byte, start int64) (n int, err error) {
|
|
||||||
end := start + int64(len(p))
|
|
||||||
n = 0
|
|
||||||
l := len(r.buffer)
|
|
||||||
if end > int64(l) {
|
|
||||||
new := make([]byte, end-int64(l))
|
|
||||||
n, err = r.reader.Read(new)
|
|
||||||
if err == io.EOF {
|
|
||||||
err = nil
|
|
||||||
}
|
|
||||||
r.buffer = append(r.buffer, new[:n]...)
|
|
||||||
}
|
|
||||||
if end > int64(len(r.buffer)) {
|
|
||||||
end = int64(len(r.buffer))
|
|
||||||
}
|
|
||||||
n = copy(p[:], r.buffer[start:end])
|
|
||||||
return
|
|
||||||
}
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
package exif
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestReadToReaderAt(t *testing.T) {
|
|
||||||
b := []byte{0, 1, 2, 3, 4, 5}
|
|
||||||
readerAt := &readerToReaderAt{
|
|
||||||
reader: bytes.NewReader(b),
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("reading from begining", func(t *testing.T) {
|
|
||||||
for i := 0; i < 2; i++ {
|
|
||||||
test := make([]byte, 2)
|
|
||||||
// check the idempotence reading from reader or from cache
|
|
||||||
count, err := readerAt.ReadAt(test, 0)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Unexpected read error for while reading at offset 0: %v", err)
|
|
||||||
}
|
|
||||||
if count != 2 {
|
|
||||||
t.Errorf("Unexpected count returned for while reading at offset 0: %v", count)
|
|
||||||
}
|
|
||||||
if bytes.Compare(test, []byte{0, 1}) != 0 {
|
|
||||||
t.Errorf("unexpected read at offset 0, expecting to read [0,1], got %v", test)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
t.Run("reading skipping bytes", func(t *testing.T) {
|
|
||||||
test := make([]byte, 2)
|
|
||||||
count, err := readerAt.ReadAt(test, 2)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Unexpected read error for while reading at offset 2: %v", err)
|
|
||||||
}
|
|
||||||
if count != 2 {
|
|
||||||
t.Errorf("Unexpected count returned for while reading at offset 2: %v", count)
|
|
||||||
}
|
|
||||||
if bytes.Compare(test, []byte{2, 3}) != 0 {
|
|
||||||
t.Errorf("unexpected read at offset 2, expecting to read [0,1], got %v", test)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
t.Run("reading larger than available", func(t *testing.T) {
|
|
||||||
test := make([]byte, 7)
|
|
||||||
count, err := readerAt.ReadAt(test, 0)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Unexpected read error for while reading at offset 2: %v", err)
|
|
||||||
}
|
|
||||||
if count != 6 {
|
|
||||||
t.Errorf("Unexpected count returned for while reading at offset 2: %v", count)
|
|
||||||
}
|
|
||||||
if bytes.Compare(test, append(b, 0)) != 0 {
|
|
||||||
t.Errorf("unexpected read at offset 2, expecting to read [0,1], got %v", test)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
+2
-1
@@ -38,8 +38,9 @@ func (_ *canon) Parse(x *exif.Exif) error {
|
|||||||
// Canon notes are a single IFD directory with no header.
|
// Canon notes are a single IFD directory with no header.
|
||||||
// Reader offsets need to be w.r.t. the original tiff structure.
|
// Reader offsets need to be w.r.t. the original tiff structure.
|
||||||
buf := bytes.NewReader(append(make([]byte, m.ValOffset), m.Val...))
|
buf := bytes.NewReader(append(make([]byte, m.ValOffset), m.Val...))
|
||||||
|
buf.Seek(int64(m.ValOffset), 0)
|
||||||
|
|
||||||
mkNotesDir, _, err := tiff.DecodeDir(buf, x.Tiff.Order, m.ValOffset)
|
mkNotesDir, _, err := tiff.DecodeDir(buf, x.Tiff.Order)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
+41
-63
@@ -107,59 +107,71 @@ type Tag struct {
|
|||||||
ratVals [][]int64
|
ratVals [][]int64
|
||||||
strVal string
|
strVal string
|
||||||
format Format
|
format Format
|
||||||
// raw contains the raw content of the value
|
|
||||||
// either the value if it fits in 4 bytes or a pointer to it if bigger
|
|
||||||
r io.ReaderAt
|
|
||||||
raw []byte
|
|
||||||
valLen uint32
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DecodeTag parses a tiff-encoded IFD tag from r and returns a Tag object. The
|
// DecodeTag parses a tiff-encoded IFD tag from r and returns a Tag object. The
|
||||||
// first read from r should be the first byte of the tag. ReadAt offsets should
|
// first read from r should be the first byte of the tag. ReadAt offsets should
|
||||||
// generally be relative to the beginning of the tiff structure (not relative
|
// generally be relative to the beginning of the tiff structure (not relative
|
||||||
// to the beginning of the tag).
|
// to the beginning of the tag).
|
||||||
func DecodeTag(r io.ReaderAt, rawData []byte, order binary.ByteOrder) (*Tag, error) {
|
func DecodeTag(r ReadAtReader, order binary.ByteOrder) (*Tag, error) {
|
||||||
t := new(Tag)
|
t := new(Tag)
|
||||||
t.order = order
|
t.order = order
|
||||||
t.r = r
|
|
||||||
|
|
||||||
t.Id = order.Uint16(rawData[0:2])
|
err := binary.Read(r, order, &t.Id)
|
||||||
t.Type = DataType(order.Uint16(rawData[2:4]))
|
if err != nil {
|
||||||
t.Count = order.Uint32(rawData[4:8])
|
return nil, errors.New("tiff: tag id read failed: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
err = binary.Read(r, order, &t.Type)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("tiff: tag type read failed: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
err = binary.Read(r, order, &t.Count)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("tiff: tag component count read failed: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
// There seems to be a relatively common corrupt tag which has a Count of
|
// There seems to be a relatively common corrupt tag which has a Count of
|
||||||
// MaxUint32. This is probably not a valid value, so return early.
|
// MaxUint32. This is probably not a valid value, so return early.
|
||||||
if t.Count >= 1<<32-1 {
|
if t.Count == 1<<32-1 {
|
||||||
return t, errors.New("invalid Count offset in tag")
|
return t, errors.New("invalid Count offset in tag")
|
||||||
}
|
}
|
||||||
|
|
||||||
t.valLen = typeSize[t.Type] * t.Count
|
valLen := typeSize[t.Type] * t.Count
|
||||||
if t.valLen == 0 {
|
if valLen == 0 {
|
||||||
return t, errors.New("zero length tag value")
|
return t, errors.New("zero length tag value")
|
||||||
}
|
}
|
||||||
|
|
||||||
t.raw = rawData[8:12]
|
if valLen > 4 {
|
||||||
|
binary.Read(r, order, &t.ValOffset)
|
||||||
|
|
||||||
return t, nil
|
// Use a bytes.Buffer so we don't allocate a huge slice if the tag
|
||||||
}
|
// is corrupt.
|
||||||
|
var buff bytes.Buffer
|
||||||
func (t *Tag) LoadVal() error {
|
sr := io.NewSectionReader(r, int64(t.ValOffset), int64(valLen))
|
||||||
if t.Val != nil {
|
n, err := io.Copy(&buff, sr)
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if t.valLen > 4 {
|
|
||||||
offset := t.order.Uint32(t.raw)
|
|
||||||
val := make([]byte, t.valLen)
|
|
||||||
_, err := t.r.ReadAt(val, int64(offset))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("tiff: tag value read failed: " + err.Error())
|
return t, errors.New("tiff: tag value read failed: " + err.Error())
|
||||||
|
} else if n != int64(valLen) {
|
||||||
|
return t, ErrShortReadTagValue
|
||||||
}
|
}
|
||||||
t.Val = val
|
t.Val = buff.Bytes()
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
t.Val = t.raw[0:t.valLen]
|
val := make([]byte, valLen)
|
||||||
|
if _, err = io.ReadFull(r, val); err != nil {
|
||||||
|
return t, errors.New("tiff: tag offset read failed: " + err.Error())
|
||||||
|
}
|
||||||
|
// ignore padding.
|
||||||
|
if _, err = io.ReadFull(r, make([]byte, 4-valLen)); err != nil {
|
||||||
|
return t, errors.New("tiff: tag offset read failed: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Val = val
|
||||||
}
|
}
|
||||||
return t.convertVals()
|
|
||||||
|
return t, t.convertVals()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tag) convertVals() error {
|
func (t *Tag) convertVals() error {
|
||||||
@@ -328,12 +340,6 @@ func (t *Tag) Rat(i int) (*big.Rat, error) {
|
|||||||
// numerator-denominator pair. It returns an error if the tag's Format is not
|
// numerator-denominator pair. It returns an error if the tag's Format is not
|
||||||
// RatVal. It panics if i is out of range.
|
// RatVal. It panics if i is out of range.
|
||||||
func (t *Tag) Rat2(i int) (num, den int64, err error) {
|
func (t *Tag) Rat2(i int) (num, den int64, err error) {
|
||||||
if t.ratVals == nil {
|
|
||||||
err := t.LoadVal()
|
|
||||||
if err != nil {
|
|
||||||
return 0, 0, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if t.format != RatVal {
|
if t.format != RatVal {
|
||||||
return 0, 0, t.typeErr(RatVal)
|
return 0, 0, t.typeErr(RatVal)
|
||||||
}
|
}
|
||||||
@@ -343,12 +349,6 @@ func (t *Tag) Rat2(i int) (num, den int64, err error) {
|
|||||||
// Int64 returns the tag's i'th value as an integer. It returns an error if the
|
// Int64 returns the tag's i'th value as an integer. It returns an error if the
|
||||||
// tag's Format is not IntVal. It panics if i is out of range.
|
// tag's Format is not IntVal. It panics if i is out of range.
|
||||||
func (t *Tag) Int64(i int) (int64, error) {
|
func (t *Tag) Int64(i int) (int64, error) {
|
||||||
if t.intVals == nil {
|
|
||||||
err := t.LoadVal()
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if t.format != IntVal {
|
if t.format != IntVal {
|
||||||
return 0, t.typeErr(IntVal)
|
return 0, t.typeErr(IntVal)
|
||||||
}
|
}
|
||||||
@@ -358,12 +358,6 @@ func (t *Tag) Int64(i int) (int64, error) {
|
|||||||
// Int returns the tag's i'th value as an integer. It returns an error if the
|
// Int returns the tag's i'th value as an integer. It returns an error if the
|
||||||
// tag's Format is not IntVal. It panics if i is out of range.
|
// tag's Format is not IntVal. It panics if i is out of range.
|
||||||
func (t *Tag) Int(i int) (int, error) {
|
func (t *Tag) Int(i int) (int, error) {
|
||||||
if t.intVals == nil {
|
|
||||||
err := t.LoadVal()
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if t.format != IntVal {
|
if t.format != IntVal {
|
||||||
return 0, t.typeErr(IntVal)
|
return 0, t.typeErr(IntVal)
|
||||||
}
|
}
|
||||||
@@ -373,12 +367,6 @@ func (t *Tag) Int(i int) (int, error) {
|
|||||||
// Float returns the tag's i'th value as a float. It returns an error if the
|
// Float returns the tag's i'th value as a float. It returns an error if the
|
||||||
// tag's Format is not IntVal. It panics if i is out of range.
|
// tag's Format is not IntVal. It panics if i is out of range.
|
||||||
func (t *Tag) Float(i int) (float64, error) {
|
func (t *Tag) Float(i int) (float64, error) {
|
||||||
if t.floatVals == nil {
|
|
||||||
err := t.LoadVal()
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if t.format != FloatVal {
|
if t.format != FloatVal {
|
||||||
return 0, t.typeErr(FloatVal)
|
return 0, t.typeErr(FloatVal)
|
||||||
}
|
}
|
||||||
@@ -388,12 +376,6 @@ func (t *Tag) Float(i int) (float64, error) {
|
|||||||
// StringVal returns the tag's value as a string. It returns an error if the
|
// StringVal returns the tag's value as a string. It returns an error if the
|
||||||
// tag's Format is not StringVal. It panics if i is out of range.
|
// tag's Format is not StringVal. It panics if i is out of range.
|
||||||
func (t *Tag) StringVal() (string, error) {
|
func (t *Tag) StringVal() (string, error) {
|
||||||
if t.strVal == "" {
|
|
||||||
err := t.LoadVal()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if t.format != StringVal {
|
if t.format != StringVal {
|
||||||
return "", t.typeErr(StringVal)
|
return "", t.typeErr(StringVal)
|
||||||
}
|
}
|
||||||
@@ -414,10 +396,6 @@ func (t *Tag) String() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tag) MarshalJSON() ([]byte, error) {
|
func (t *Tag) MarshalJSON() ([]byte, error) {
|
||||||
err := t.LoadVal()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
switch t.format {
|
switch t.format {
|
||||||
case StringVal, UndefVal:
|
case StringVal, UndefVal:
|
||||||
return nullString(t.Val), nil
|
return nullString(t.Val), nil
|
||||||
|
|||||||
+24
-59
@@ -11,8 +11,6 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
const notValidTIFF = "tiff: invalid TIFF header"
|
|
||||||
|
|
||||||
// ReadAtReader is used when decoding Tiff tags and directories
|
// ReadAtReader is used when decoding Tiff tags and directories
|
||||||
type ReadAtReader interface {
|
type ReadAtReader interface {
|
||||||
io.Reader
|
io.Reader
|
||||||
@@ -28,10 +26,6 @@ type Tiff struct {
|
|||||||
Order binary.ByteOrder
|
Order binary.ByteOrder
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsInvalidTiff(err error) bool {
|
|
||||||
return err != nil && err.Error() == notValidTIFF
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decode parses tiff-encoded data from r and returns a Tiff struct that
|
// Decode parses tiff-encoded data from r and returns a Tiff struct that
|
||||||
// reflects the structure and content of the tiff data. The first read from r
|
// reflects the structure and content of the tiff data. The first read from r
|
||||||
// should be the first byte of the tiff-encoded data and not necessarily the
|
// should be the first byte of the tiff-encoded data and not necessarily the
|
||||||
@@ -42,27 +36,12 @@ func Decode(r io.Reader) (*Tiff, error) {
|
|||||||
return nil, errors.New("tiff: could not read data")
|
return nil, errors.New("tiff: could not read data")
|
||||||
}
|
}
|
||||||
buf := bytes.NewReader(data)
|
buf := bytes.NewReader(data)
|
||||||
t, err := LazyDecode(buf)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return t.LoadAllVals()
|
|
||||||
}
|
|
||||||
|
|
||||||
// LazyDecode parses tiff-encoded data from r and returns a Tiff struct that
|
|
||||||
// reflects the structure and content of the tiff data. The first read from r
|
|
||||||
// should be the first byte of the tiff-encoded data and not necessarily the
|
|
||||||
// first byte of an os.File object.
|
|
||||||
//
|
|
||||||
// It differs from Decode as it is restricted to parsing the tiff structure
|
|
||||||
// and only decodes the values on demand.
|
|
||||||
func LazyDecode(r io.ReaderAt) (*Tiff, error) {
|
|
||||||
|
|
||||||
t := new(Tiff)
|
t := new(Tiff)
|
||||||
|
|
||||||
// read byte order
|
// read byte order
|
||||||
bo := make([]byte, 2)
|
bo := make([]byte, 2)
|
||||||
if _, err := r.ReadAt(bo, 0); err != nil {
|
if _, err = io.ReadFull(buf, bo); err != nil {
|
||||||
return nil, errors.New("tiff: could not read tiff byte order")
|
return nil, errors.New("tiff: could not read tiff byte order")
|
||||||
}
|
}
|
||||||
if string(bo) == "II" {
|
if string(bo) == "II" {
|
||||||
@@ -70,31 +49,39 @@ func LazyDecode(r io.ReaderAt) (*Tiff, error) {
|
|||||||
} else if string(bo) == "MM" {
|
} else if string(bo) == "MM" {
|
||||||
t.Order = binary.BigEndian
|
t.Order = binary.BigEndian
|
||||||
} else {
|
} else {
|
||||||
return nil, errors.New(notValidTIFF)
|
return nil, errors.New("tiff: could not read tiff byte order")
|
||||||
}
|
}
|
||||||
|
|
||||||
// check for special tiff marker
|
// check for special tiff marker
|
||||||
sp := make([]byte, 2)
|
var sp int16
|
||||||
_, err := r.ReadAt(sp, 2)
|
err = binary.Read(buf, t.Order, &sp)
|
||||||
if err != nil || 42 != t.Order.Uint16(sp) {
|
if err != nil || 42 != sp {
|
||||||
return nil, errors.New("tiff: could not find special tiff marker")
|
return nil, errors.New("tiff: could not find special tiff marker")
|
||||||
}
|
}
|
||||||
|
|
||||||
// load offset to first IFD
|
// load offset to first IFD
|
||||||
binaryOffset := make([]byte, 4)
|
var offset int32
|
||||||
_, err = r.ReadAt(binaryOffset, 4)
|
err = binary.Read(buf, t.Order, &offset)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("tiff: could not read offset to first IFD")
|
return nil, errors.New("tiff: could not read offset to first IFD")
|
||||||
}
|
}
|
||||||
offset := t.Order.Uint32(binaryOffset)
|
|
||||||
|
|
||||||
// load IFD's
|
// load IFD's
|
||||||
var d *Dir
|
var d *Dir
|
||||||
prev := offset
|
prev := offset
|
||||||
for offset != 0 {
|
for offset != 0 {
|
||||||
|
// seek to offset
|
||||||
|
_, err := buf.Seek(int64(offset), 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("tiff: seek to IFD failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
if buf.Len() == 0 {
|
||||||
|
return nil, errors.New("tiff: seek offset after EOF")
|
||||||
|
}
|
||||||
|
|
||||||
// load the dir
|
// load the dir
|
||||||
d, offset, err = DecodeDir(r, t.Order, offset)
|
d, offset, err = DecodeDir(buf, t.Order)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -110,19 +97,6 @@ func LazyDecode(r io.ReaderAt) (*Tiff, error) {
|
|||||||
return t, nil
|
return t, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadAllVals loads and parses all tiff directory values in memory
|
|
||||||
func (t *Tiff) LoadAllVals() (*Tiff, error) {
|
|
||||||
for _, d := range t.Dirs {
|
|
||||||
for _, tag := range d.Tags {
|
|
||||||
err := tag.LoadVal()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return t, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tf *Tiff) String() string {
|
func (tf *Tiff) String() string {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
fmt.Fprint(&buf, "Tiff{")
|
fmt.Fprint(&buf, "Tiff{")
|
||||||
@@ -142,41 +116,32 @@ type Dir struct {
|
|||||||
// is the offset to the next IFD. The first read from r should be at the first
|
// is the offset to the next IFD. The first read from r should be at the first
|
||||||
// byte of the IFD. ReadAt offsets should generally be relative to the
|
// byte of the IFD. ReadAt offsets should generally be relative to the
|
||||||
// beginning of the tiff structure (not relative to the beginning of the IFD).
|
// beginning of the tiff structure (not relative to the beginning of the IFD).
|
||||||
func DecodeDir(r io.ReaderAt, order binary.ByteOrder, offset uint32) (*Dir, uint32, error) {
|
func DecodeDir(r ReadAtReader, order binary.ByteOrder) (d *Dir, offset int32, err error) {
|
||||||
d := new(Dir)
|
d = new(Dir)
|
||||||
|
|
||||||
// get num of tags in ifd
|
// get num of tags in ifd
|
||||||
b := make([]byte, 4)
|
var nTags int16
|
||||||
_, err := r.ReadAt(b[0:2], int64(offset))
|
err = binary.Read(r, order, &nTags)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, errors.New("tiff: failed to read IFD tag count: " + err.Error())
|
return nil, 0, errors.New("tiff: failed to read IFD tag count: " + err.Error())
|
||||||
}
|
}
|
||||||
nTags := order.Uint16(b[0:2])
|
|
||||||
tags := make([]byte, 12*nTags)
|
|
||||||
|
|
||||||
offset += 2
|
|
||||||
_, err = r.ReadAt(tags, int64(offset))
|
|
||||||
if err != nil {
|
|
||||||
return nil, 0, errors.New("tiff: falied to read offset to next IFD: " + err.Error())
|
|
||||||
}
|
|
||||||
// load tags
|
// load tags
|
||||||
for n := 0; n < int(nTags); n++ {
|
for n := 0; n < int(nTags); n++ {
|
||||||
t, err := DecodeTag(r, tags[n*12:(n+1)*12], order)
|
t, err := DecodeTag(r, order)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
d.Tags = append(d.Tags, t)
|
d.Tags = append(d.Tags, t)
|
||||||
offset += 12
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// get offset to next ifd
|
// get offset to next ifd
|
||||||
|
err = binary.Read(r, order, &offset)
|
||||||
_, err = r.ReadAt(b, int64(offset))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, errors.New("tiff: falied to read offset to next IFD: " + err.Error())
|
return nil, 0, errors.New("tiff: falied to read offset to next IFD: " + err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
return d, order.Uint32(b), nil
|
return d, offset, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Dir) String() string {
|
func (d *Dir) String() string {
|
||||||
|
|||||||
+5
-121
@@ -5,9 +5,6 @@ import (
|
|||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -156,7 +153,7 @@ func TestDecodeTag(t *testing.T) {
|
|||||||
func testSingle(t *testing.T, order binary.ByteOrder, in input, out output, i int) {
|
func testSingle(t *testing.T, order binary.ByteOrder, in input, out output, i int) {
|
||||||
data := buildInput(in, order)
|
data := buildInput(in, order)
|
||||||
buf := bytes.NewReader(data)
|
buf := bytes.NewReader(data)
|
||||||
tg, err := DecodeTag(buf, data, order)
|
tg, err := DecodeTag(buf, order)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("(%v) tag %v%+v decode failed: %v", order, i, in, err)
|
t.Errorf("(%v) tag %v%+v decode failed: %v", order, i, in, err)
|
||||||
return
|
return
|
||||||
@@ -172,22 +169,11 @@ func testSingle(t *testing.T, order binary.ByteOrder, in input, out output, i in
|
|||||||
t.Errorf("(%v) tag %v component count decode: expected %v, got %v", order, i, out.count, tg.Count)
|
t.Errorf("(%v) tag %v component count decode: expected %v, got %v", order, i, out.count, tg.Count)
|
||||||
}
|
}
|
||||||
if tg.Type == DTAscii && in.val != "" {
|
if tg.Type == DTAscii && in.val != "" {
|
||||||
val, err := tg.StringVal()
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("(%v) tag %v value decoding error: %s", order, i, err.Error())
|
|
||||||
}
|
|
||||||
strOut := string(out.val)
|
strOut := string(out.val)
|
||||||
if val != strOut {
|
|
||||||
t.Errorf("(%v) tag %v string value decode: expected %q, got %q", order, i, strOut, val)
|
|
||||||
}
|
|
||||||
if tg.strVal != strOut {
|
if tg.strVal != strOut {
|
||||||
t.Errorf("(%v) tag %v string value cached decode: expected %q, got %q", order, i, strOut, tg.strVal)
|
t.Errorf("(%v) tag %v string value decode: expected %q, got %q", order, i, strOut, tg.strVal)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
err := tg.LoadVal()
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("(%v) tag %v value decoding error: %s", order, i, err.Error())
|
|
||||||
}
|
|
||||||
if !bytes.Equal(tg.Val, out.val) {
|
if !bytes.Equal(tg.Val, out.val) {
|
||||||
t.Errorf("(%v) tag %v value decode: expected %q, got %q", order, i, out.val, tg.Val)
|
t.Errorf("(%v) tag %v value decode: expected %q, got %q", order, i, out.val, tg.Val)
|
||||||
}
|
}
|
||||||
@@ -234,56 +220,10 @@ func TestDecode(t *testing.T) {
|
|||||||
t.Log(tif)
|
t.Log(tif)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDecodeLoadsTagValues(t *testing.T) {
|
|
||||||
f, err := os.Open("sample1.tif")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("%v\n", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
tif, err := Decode(f)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
vals := [][]byte{
|
|
||||||
{192, 6},
|
|
||||||
{72, 9},
|
|
||||||
{1, 0},
|
|
||||||
{4, 0},
|
|
||||||
{0, 0},
|
|
||||||
{2, 0},
|
|
||||||
{83, 116, 97, 110, 100, 97, 114, 100, 32, 73, 110, 112, 117, 116, 0},
|
|
||||||
{99, 111, 110, 118, 101, 114, 116, 101, 100, 32, 80, 66, 77, 32, 102, 105, 108, 101, 0},
|
|
||||||
{8, 0, 0, 0},
|
|
||||||
{1, 0},
|
|
||||||
{1, 0},
|
|
||||||
{72, 9, 0, 0},
|
|
||||||
{192, 70, 0, 0},
|
|
||||||
{128, 132, 30, 0, 16, 39, 0, 0},
|
|
||||||
{128, 132, 30, 0, 16, 39, 0, 0},
|
|
||||||
{1, 0},
|
|
||||||
{2, 0},
|
|
||||||
}
|
|
||||||
i := 0
|
|
||||||
for _, d := range tif.Dirs {
|
|
||||||
for _, tag := range d.Tags {
|
|
||||||
if len(tag.Val) != len(vals[i]) {
|
|
||||||
t.Errorf("tag value length mismatch for tag #%d", i)
|
|
||||||
}
|
|
||||||
for j, v := range vals[i] {
|
|
||||||
if tag.Val[j] != v {
|
|
||||||
t.Errorf("tag value mismatch for tag #%d index %d", i, j)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
t.Log(tif)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDecodeTag_blob(t *testing.T) {
|
func TestDecodeTag_blob(t *testing.T) {
|
||||||
data := data()
|
buf := bytes.NewReader(data())
|
||||||
buf := bytes.NewReader(data)
|
buf.Seek(10, 1)
|
||||||
tg, err := DecodeTag(buf, data[10:len(data)], binary.LittleEndian)
|
tg, err := DecodeTag(buf, binary.LittleEndian)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("tag decode failed: %v", err)
|
t.Fatalf("tag decode failed: %v", err)
|
||||||
}
|
}
|
||||||
@@ -307,59 +247,3 @@ func data() []byte {
|
|||||||
}
|
}
|
||||||
return dat
|
return dat
|
||||||
}
|
}
|
||||||
|
|
||||||
func downloadRAW(link, destFile string) error {
|
|
||||||
_, err := os.Stat(destFile)
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
resp, err := http.Get(link)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Failed to download image %s: %s", link, err)
|
|
||||||
} else {
|
|
||||||
defer resp.Body.Close()
|
|
||||||
fd, err := os.Create(destFile)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Failed to download image %s: %s", link, err)
|
|
||||||
} else {
|
|
||||||
io.Copy(fd, resp.Body)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fmt.Println("Downloaded", link, "to", destFile)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkDecode(b *testing.B) {
|
|
||||||
testFile := "test.raw"
|
|
||||||
downloadRAW("http://www.rawsamples.ch/raws/canon/RAW_CANON_EOS_5DS.CR2", testFile)
|
|
||||||
b.ResetTimer()
|
|
||||||
|
|
||||||
for n := 0; n < b.N; n++ {
|
|
||||||
fd, err := os.Open(testFile)
|
|
||||||
if err != nil {
|
|
||||||
b.Errorf("Failed to open test file %s", err.Error())
|
|
||||||
}
|
|
||||||
_, err = Decode(fd)
|
|
||||||
if err != nil {
|
|
||||||
b.Errorf("Failed to decode test file %s", err.Error())
|
|
||||||
}
|
|
||||||
fd.Close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkLazyDecode(b *testing.B) {
|
|
||||||
testFile := "test.raw"
|
|
||||||
downloadRAW("http://www.rawsamples.ch/raws/canon/RAW_CANON_EOS_5DS.CR2", testFile)
|
|
||||||
b.ResetTimer()
|
|
||||||
|
|
||||||
for n := 0; n < b.N; n++ {
|
|
||||||
fd, err := os.Open(testFile)
|
|
||||||
if err != nil {
|
|
||||||
b.Errorf("Failed to open test file %s", err.Error())
|
|
||||||
}
|
|
||||||
_, err = LazyDecode(fd)
|
|
||||||
if err != nil {
|
|
||||||
b.Errorf("Failed to decode test file %s", err.Error())
|
|
||||||
}
|
|
||||||
fd.Close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user