mirror of https://github.com/perkeep/perkeep.git
import github.com/camlistore/goexif as a third party
Change-Id: I34842677f4d9335df2478692e0b0d169d00d0942
This commit is contained in:
parent
fbead58f92
commit
fa1269da45
|
@ -28,7 +28,7 @@ import (
|
|||
_ "image/gif"
|
||||
_ "image/png"
|
||||
|
||||
"github.com/camlistore/goexif/exif"
|
||||
"camlistore.org/third_party/github.com/camlistore/goexif/exif"
|
||||
)
|
||||
|
||||
// The FlipDirection type is used by the Flip option in DecodeOpts
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
|
||||
Copyright (c) 2012, Robert Carlsen & Contributors
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,59 @@
|
|||
goexif
|
||||
======
|
||||
|
||||
Provides decoding of basic exif and tiff encoded data. Still in alpha - no guarantees.
|
||||
Suggestions and pull requests are welcome. Functionality is split into two packages - "exif" and "tiff"
|
||||
The exif package depends on the tiff package.
|
||||
Documentation can be found at http://go.pkgdoc.org/github.com/camlistore/goexif
|
||||
|
||||
To install, in a terminal type:
|
||||
|
||||
```
|
||||
go get github.com/camlistore/goexif/exif
|
||||
```
|
||||
|
||||
Or if you just want the tiff package:
|
||||
|
||||
```
|
||||
go get github.com/camlistore/goexif/tiff
|
||||
```
|
||||
|
||||
Example usage:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"log"
|
||||
"fmt"
|
||||
|
||||
"github.com/camlistore/goexif/exif"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fname := "sample1.jpg"
|
||||
|
||||
f, err := os.Open(fname)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
x, err := exif.Decode(f)
|
||||
f.Close()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
camModel, _ := x.Get("Model")
|
||||
date, _ := x.Get("DateTimeOriginal")
|
||||
fmt.Println(camModel.StringVal())
|
||||
fmt.Println(date.StringVal())
|
||||
|
||||
focal, _ := x.Get("FocalLength")
|
||||
numer, denom := focal.Rat2(0) // retrieve first (only) rat. value
|
||||
fmt.Printf("%v/%v", numer, denom)
|
||||
}
|
||||
```
|
||||
|
||||
<!--golang-->
|
|
@ -0,0 +1,32 @@
|
|||
package exif_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"camlistore.org/third_party/github.com/camlistore/goexif/exif"
|
||||
)
|
||||
|
||||
func ExampleDecode() {
|
||||
fname := "sample1.jpg"
|
||||
|
||||
f, err := os.Open(fname)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
x, err := exif.Decode(f)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
camModel, _ := x.Get("Model")
|
||||
date, _ := x.Get("DateTimeOriginal")
|
||||
fmt.Println(camModel.StringVal())
|
||||
fmt.Println(date.StringVal())
|
||||
|
||||
focal, _ := x.Get("FocalLength")
|
||||
numer, denom := focal.Rat2(0) // retrieve first (only) rat. value
|
||||
fmt.Printf("%v/%v", numer, denom)
|
||||
}
|
|
@ -0,0 +1,232 @@
|
|||
// Package exif implements decoding of EXIF data as defined in the EXIF 2.2
|
||||
// specification.
|
||||
package exif
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"camlistore.org/third_party/github.com/camlistore/goexif/tiff"
|
||||
)
|
||||
|
||||
const (
|
||||
exifPointer = 0x8769
|
||||
gpsPointer = 0x8825
|
||||
interopPointer = 0xA005
|
||||
)
|
||||
|
||||
// A TagNotPresentError is returned when the requested field is not
|
||||
// present in the EXIF.
|
||||
type TagNotPresentError FieldName
|
||||
|
||||
func (tag TagNotPresentError) Error() string {
|
||||
return fmt.Sprintf("exif: tag %q is not present", string(tag))
|
||||
}
|
||||
|
||||
func isTagNotPresentErr(err error) bool {
|
||||
_, ok := err.(TagNotPresentError)
|
||||
return ok
|
||||
}
|
||||
|
||||
type Exif struct {
|
||||
tif *tiff.Tiff
|
||||
|
||||
main map[uint16]*tiff.Tag
|
||||
}
|
||||
|
||||
// Decode parses EXIF-encoded data from r and returns a queryable Exif object.
|
||||
func Decode(r io.Reader) (*Exif, error) {
|
||||
sec, err := newAppSec(0xE1, r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
er, err := sec.exifReader()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tif, err := tiff.Decode(er)
|
||||
if err != nil {
|
||||
return nil, errors.New("exif: decode failed: " + err.Error())
|
||||
}
|
||||
|
||||
// build an exif structure from the tiff
|
||||
x := &Exif{
|
||||
main: map[uint16]*tiff.Tag{},
|
||||
tif: tif,
|
||||
}
|
||||
|
||||
ifd0 := tif.Dirs[0]
|
||||
for _, tag := range ifd0.Tags {
|
||||
x.main[tag.Id] = tag
|
||||
}
|
||||
|
||||
// recurse into exif, gps, and interop sub-IFDs
|
||||
if err = x.loadSubDir(er, exifPointer); err != nil {
|
||||
return x, err
|
||||
}
|
||||
if err = x.loadSubDir(er, gpsPointer); err != nil {
|
||||
return x, err
|
||||
}
|
||||
if err = x.loadSubDir(er, interopPointer); err != nil {
|
||||
return x, err
|
||||
}
|
||||
|
||||
return x, nil
|
||||
}
|
||||
|
||||
func (x *Exif) loadSubDir(r *bytes.Reader, tagId uint16) error {
|
||||
tag, ok := x.main[tagId]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
offset := tag.Int(0)
|
||||
|
||||
_, err := r.Seek(offset, 0)
|
||||
if err != nil {
|
||||
return errors.New("exif: seek to sub-IFD failed: " + err.Error())
|
||||
}
|
||||
subDir, _, err := tiff.DecodeDir(r, x.tif.Order)
|
||||
if err != nil {
|
||||
return errors.New("exif: sub-IFD decode failed: " + err.Error())
|
||||
}
|
||||
for _, tag := range subDir.Tags {
|
||||
x.main[tag.Id] = tag
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get retrieves the EXIF tag for the given field name.
|
||||
//
|
||||
// If the tag is not known or not present, an error is returned. If the
|
||||
// tag name is known, the error will be a TagNotPresentError.
|
||||
func (x *Exif) Get(name FieldName) (*tiff.Tag, error) {
|
||||
id, ok := fields[name]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("exif: invalid tag name %q", name)
|
||||
}
|
||||
if tg, ok := x.main[id]; ok {
|
||||
return tg, nil
|
||||
}
|
||||
return nil, TagNotPresentError(name)
|
||||
}
|
||||
|
||||
// Walker is the interface used to traverse all exif fields of an Exif object.
|
||||
// Returning a non-nil error aborts the walk/traversal.
|
||||
type Walker interface {
|
||||
Walk(name FieldName, tag *tiff.Tag) error
|
||||
}
|
||||
|
||||
// Walk calls the Walk method of w with the name and tag for every non-nil exif
|
||||
// field.
|
||||
func (x *Exif) Walk(w Walker) error {
|
||||
for name, _ := range fields {
|
||||
tag, err := x.Get(name)
|
||||
if isTagNotPresentErr(err) {
|
||||
continue
|
||||
} else if err != nil {
|
||||
panic("field list access/construction is broken - this should never happen")
|
||||
}
|
||||
|
||||
err = w.Walk(name, tag)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// String returns a pretty text representation of the decoded exif data.
|
||||
func (x *Exif) String() string {
|
||||
var buf bytes.Buffer
|
||||
for name, id := range fields {
|
||||
if tag, ok := x.main[id]; ok {
|
||||
fmt.Fprintf(&buf, "%s: %s\n", name, tag)
|
||||
}
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func (x Exif) MarshalJSON() ([]byte, error) {
|
||||
m := map[string]interface{}{}
|
||||
|
||||
for name, id := range fields {
|
||||
if tag, ok := x.main[id]; ok {
|
||||
m[string(name)] = tag
|
||||
}
|
||||
}
|
||||
|
||||
return json.Marshal(m)
|
||||
}
|
||||
|
||||
type appSec struct {
|
||||
marker byte
|
||||
data []byte
|
||||
}
|
||||
|
||||
// newAppSec finds marker in r and returns the corresponding application data
|
||||
// section.
|
||||
func newAppSec(marker byte, r io.Reader) (*appSec, error) {
|
||||
app := &appSec{marker: marker}
|
||||
|
||||
buf := bufio.NewReader(r)
|
||||
|
||||
// seek to marker
|
||||
for {
|
||||
b, err := buf.ReadByte()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
n, err := buf.Peek(1)
|
||||
if b == 0xFF && n[0] == marker {
|
||||
buf.ReadByte()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// read section size
|
||||
var dataLen uint16
|
||||
err := binary.Read(buf, binary.BigEndian, &dataLen)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dataLen -= 2 // subtract length of the 2 byte size marker itself
|
||||
|
||||
// read section data
|
||||
nread := 0
|
||||
for nread < int(dataLen) {
|
||||
s := make([]byte, int(dataLen)-nread)
|
||||
n, err := buf.Read(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nread += n
|
||||
app.data = append(app.data, s...)
|
||||
}
|
||||
|
||||
return app, nil
|
||||
}
|
||||
|
||||
// reader returns a reader on this appSec.
|
||||
func (app *appSec) reader() *bytes.Reader {
|
||||
return bytes.NewReader(app.data)
|
||||
}
|
||||
|
||||
// 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) {
|
||||
// read/check for exif special mark
|
||||
if len(app.data) < 6 {
|
||||
return nil, errors.New("exif: failed to find exif intro marker")
|
||||
}
|
||||
|
||||
exif := app.data[:6]
|
||||
if string(exif) != "Exif"+string([]byte{0x00, 0x00}) {
|
||||
return nil, errors.New("exif: failed to find exif intro marker")
|
||||
}
|
||||
return bytes.NewReader(app.data[6:]), nil
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
package exif
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"camlistore.org/third_party/github.com/camlistore/goexif/tiff"
|
||||
)
|
||||
|
||||
func TestDecode(t *testing.T) {
|
||||
name := "sample1.jpg"
|
||||
f, err := os.Open(name)
|
||||
if err != nil {
|
||||
t.Fatalf("%v\n", err)
|
||||
}
|
||||
|
||||
x, err := Decode(f)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if x == nil {
|
||||
t.Fatal("No error and yet %v was not decoded\n", name)
|
||||
}
|
||||
|
||||
val, err := x.Get("Model")
|
||||
t.Logf("Model: %v", val)
|
||||
t.Log(x)
|
||||
}
|
||||
|
||||
type walker struct {
|
||||
t *testing.T
|
||||
}
|
||||
|
||||
func (w *walker) Walk(name FieldName, tag *tiff.Tag) error {
|
||||
w.t.Logf("%v: %v", name, tag)
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestWalk(t *testing.T) {
|
||||
name := "sample1.jpg"
|
||||
f, err := os.Open(name)
|
||||
if err != nil {
|
||||
t.Fatalf("%v\n", err)
|
||||
}
|
||||
|
||||
x, err := Decode(f)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if x == nil {
|
||||
t.Fatal("bad err")
|
||||
}
|
||||
|
||||
x.Walk(&walker{t})
|
||||
|
||||
}
|
||||
|
||||
func TestMarshal(t *testing.T) {
|
||||
name := "sample1.jpg"
|
||||
f, err := os.Open(name)
|
||||
if err != nil {
|
||||
t.Fatalf("%v\n", err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
x, err := Decode(f)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if x == nil {
|
||||
t.Fatal("bad err")
|
||||
}
|
||||
|
||||
b, err := x.MarshalJSON()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Logf("%s", b)
|
||||
}
|
|
@ -0,0 +1,153 @@
|
|||
package exif
|
||||
|
||||
type FieldName string
|
||||
|
||||
const (
|
||||
ImageWidth FieldName = "ImageWidth"
|
||||
ImageLength FieldName = "ImageLength" // height
|
||||
Orientation FieldName = "Orientation"
|
||||
)
|
||||
|
||||
var fields = map[FieldName]uint16{
|
||||
/////////////////////////////////////
|
||||
////////// IFD 0 ////////////////////
|
||||
/////////////////////////////////////
|
||||
|
||||
// image data structure
|
||||
"ImageWidth": 0x0100,
|
||||
"ImageLength": 0x0101,
|
||||
"BitsPerSample": 0x0102,
|
||||
"Compression": 0x0103,
|
||||
"PhotometricInterpretation": 0x0106,
|
||||
"Orientation": 0x0112,
|
||||
"SamplesPerPixel": 0x0115,
|
||||
"PlanarConfiguration": 0x011C,
|
||||
"YCbCrSubSampling": 0x0212,
|
||||
"YCbCrPositioning": 0x0213,
|
||||
"XResolution": 0x011A,
|
||||
"YResolution": 0x011B,
|
||||
"ResolutionUnit": 0x0128,
|
||||
|
||||
// Other tags
|
||||
"DateTime": 0x0132,
|
||||
"ImageDescription": 0x010E,
|
||||
"Make": 0x010F,
|
||||
"Model": 0x0110,
|
||||
"Software": 0x0131,
|
||||
"Artist": 0x010e,
|
||||
"Copyright": 0x010e,
|
||||
|
||||
// private tags
|
||||
"ExifIFDPointer": exifPointer,
|
||||
|
||||
/////////////////////////////////////
|
||||
////////// Exif sub IFD /////////////
|
||||
/////////////////////////////////////
|
||||
|
||||
"GPSInfoIFDPointer": gpsPointer,
|
||||
"InteroperabilityIFDPointer": interopPointer,
|
||||
|
||||
"ExifVersion": 0x9000,
|
||||
"FlashpixVersion": 0xA000,
|
||||
|
||||
"ColorSpace": 0xA001,
|
||||
|
||||
"ComponentsConfiguration": 0x9101,
|
||||
"CompressedBitsPerPixel": 0x9102,
|
||||
"PixelXDimension": 0xA002,
|
||||
"PixelYDimension": 0xA003,
|
||||
|
||||
"MakerNote": 0x927C,
|
||||
"UserComment": 0x9286,
|
||||
|
||||
"RelatedSoundFile": 0xA004,
|
||||
"DateTimeOriginal": 0x9003,
|
||||
"DateTimeDigitized": 0x9004,
|
||||
"SubSecTime": 0x9290,
|
||||
"SubSecTimeOriginal": 0x9291,
|
||||
"SubSecTimeDigitized": 0x9292,
|
||||
|
||||
"ImageUniqueID": 0xA420,
|
||||
|
||||
// picture conditions
|
||||
"ExposureTime": 0x829A,
|
||||
"FNumber": 0x829D,
|
||||
"ExposureProgram": 0x8822,
|
||||
"SpectralSensitivity": 0x8824,
|
||||
"ISOSpeedRatings": 0x8827,
|
||||
"OECF": 0x8828,
|
||||
"ShutterSpeedValue": 0x9201,
|
||||
"ApertureValue": 0x9202,
|
||||
"BrightnessValue": 0x9203,
|
||||
"ExposureBiasValue": 0x9204,
|
||||
"MaxApertureValue": 0x9205,
|
||||
"SubjectDistance": 0x9206,
|
||||
"MeteringMode": 0x9207,
|
||||
"LightSource": 0x9208,
|
||||
"Flash": 0x9209,
|
||||
"FocalLength": 0x920A,
|
||||
"SubjectArea": 0x9214,
|
||||
"FlashEnergy": 0xA20B,
|
||||
"SpatialFrequencyResponse": 0xA20C,
|
||||
"FocalPlaneXResolution": 0xA20E,
|
||||
"FocalPlaneYResolution": 0xA20F,
|
||||
"FocalPlaneResolutionUnit": 0xA210,
|
||||
"SubjectLocation": 0xA214,
|
||||
"ExposureIndex": 0xA215,
|
||||
"SensingMethod": 0xA217,
|
||||
"FileSource": 0xA300,
|
||||
"SceneType": 0xA301,
|
||||
"CFAPattern": 0xA302,
|
||||
"CustomRendered": 0xA401,
|
||||
"ExposureMode": 0xA402,
|
||||
"WhiteBalance": 0xA403,
|
||||
"DigitalZoomRatio": 0xA404,
|
||||
"FocalLengthIn35mmFilm": 0xA405,
|
||||
"SceneCaptureType": 0xA406,
|
||||
"GainControl": 0xA407,
|
||||
"Contrast": 0xA408,
|
||||
"Saturation": 0xA409,
|
||||
"Sharpness": 0xA40A,
|
||||
"DeviceSettingDescription": 0xA40B,
|
||||
"SubjectDistanceRange": 0xA40C,
|
||||
|
||||
/////////////////////////////////////
|
||||
//// GPS sub-IFD ////////////////////
|
||||
/////////////////////////////////////
|
||||
"GPSVersionID": 0x0,
|
||||
"GPSLatitudeRef": 0x1,
|
||||
"GPSLatitude": 0x2,
|
||||
"GPSLongitudeRef": 0x3,
|
||||
"GPSLongitude": 0x4,
|
||||
"GPSAltitudeRef": 0x5,
|
||||
"GPSAltitude": 0x6,
|
||||
"GPSTimeStamp": 0x7,
|
||||
"GPSSatelites": 0x8,
|
||||
"GPSStatus": 0x9,
|
||||
"GPSMeasureMode": 0xA,
|
||||
"GPSDOP": 0xB,
|
||||
"GPSSpeedRef": 0xC,
|
||||
"GPSSpeed": 0xD,
|
||||
"GPSTrackRef": 0xE,
|
||||
"GPSTrack": 0xF,
|
||||
"GPSImgDirectionRef": 0x10,
|
||||
"GPSImgDirection": 0x11,
|
||||
"GPSMapDatum": 0x12,
|
||||
"GPSDestLatitudeRef": 0x13,
|
||||
"GPSDestLatitude": 0x14,
|
||||
"GPSDestLongitudeRef": 0x15,
|
||||
"GPSDestLongitude": 0x16,
|
||||
"GPSDestBearingRef": 0x17,
|
||||
"GPSDestBearing": 0x18,
|
||||
"GPSDestDistanceRef": 0x19,
|
||||
"GPSDestDistance": 0x1A,
|
||||
"GPSProcessingMethod": 0x1B,
|
||||
"GPSAreaInformation": 0x1C,
|
||||
"GPSDateStamp": 0x1D,
|
||||
"GPSDifferential": 0x1E,
|
||||
|
||||
/////////////////////////////////////
|
||||
//// Interoperability sub-IFD ///////
|
||||
/////////////////////////////////////
|
||||
"InteroperabilityIndex": 0x1,
|
||||
}
|
|
@ -0,0 +1,134 @@
|
|||
package fields
|
||||
|
||||
type Std struct {
|
||||
ImageWidth
|
||||
ImageLength
|
||||
BitsPerSample
|
||||
Compression
|
||||
PhotometricInterpretation
|
||||
Orientation
|
||||
SamplesPerPixel
|
||||
PlanarConfiguration
|
||||
YCbCrSubSampling
|
||||
YCbCrPositioning
|
||||
XResolution
|
||||
YResolution
|
||||
ResolutionUnit
|
||||
DateTime
|
||||
ImageDescription
|
||||
Make
|
||||
Model
|
||||
Software
|
||||
Artist
|
||||
Copyright
|
||||
ExifIFDPointer
|
||||
}
|
||||
|
||||
type Sub struct {
|
||||
GPSInfoIFDPointer
|
||||
InteroperabilityIFDPointer
|
||||
ExifVersion
|
||||
FlashpixVersion
|
||||
ColorSpace
|
||||
ComponentsConfiguration
|
||||
CompressedBitsPerPixel
|
||||
PixelXDimension
|
||||
PixelYDimension
|
||||
MakerNote
|
||||
UserComment
|
||||
RelatedSoundFile
|
||||
DateTimeOriginal
|
||||
DateTimeDigitized
|
||||
SubSecTime
|
||||
SubSecTimeOriginal
|
||||
SubSecTimeDigitized
|
||||
ImageUniqueID
|
||||
ExposureTime
|
||||
FNumber
|
||||
ExposureProgram
|
||||
SpectralSensitivity
|
||||
ISOSpeedRatings
|
||||
OECF
|
||||
ShutterSpeedValue
|
||||
ApertureValue
|
||||
BrightnessValue
|
||||
ExposureBiasValue
|
||||
MaxApertureValue
|
||||
SubjectDistance
|
||||
MeteringMode
|
||||
LightSource
|
||||
Flash
|
||||
FocalLength
|
||||
SubjectArea
|
||||
FlashEnergy
|
||||
SpatialFrequencyResponse
|
||||
FocalPlaneXResolution
|
||||
FocalPlaneYResolution
|
||||
FocalPlaneResolutionUnit
|
||||
SubjectLocation
|
||||
ExposureIndex
|
||||
SensingMethod
|
||||
FileSource
|
||||
SceneType
|
||||
CFAPattern
|
||||
CustomRendered
|
||||
ExposureMode
|
||||
WhiteBalance
|
||||
DigitalZoomRatio
|
||||
FocalLengthIn35mmFilm
|
||||
SceneCaptureType
|
||||
GainControl
|
||||
Contrast
|
||||
Saturation
|
||||
Sharpness
|
||||
DeviceSettingDescription
|
||||
SubjectDistanceRange
|
||||
}
|
||||
|
||||
type GPS struct {
|
||||
GPSVersionID
|
||||
GPSLatitudeRef
|
||||
GPSLatitude
|
||||
GPSLongitudeRef
|
||||
GPSLongitude
|
||||
GPSAltitudeRef
|
||||
GPSAltitude
|
||||
GPSTimeStamp
|
||||
GPSSatelites
|
||||
GPSStatus
|
||||
GPSMeasureMode
|
||||
GPSDOP
|
||||
GPSSpeedRef
|
||||
GPSSpeed
|
||||
GPSTrackRef
|
||||
GPSTrack
|
||||
GPSImgDirectionRef
|
||||
GPSImgDirection
|
||||
GPSMapDatum
|
||||
GPSDestLatitudeRef
|
||||
GPSDestLatitude
|
||||
GPSDestLongitudeRef
|
||||
GPSDestLongitude
|
||||
GPSDestBearingRef
|
||||
GPSDestBearing
|
||||
GPSDestDistanceRef
|
||||
GPSDestDistance
|
||||
GPSProcessingMethod
|
||||
GPSAreaInformation
|
||||
GPSDateStamp
|
||||
GPSDifferential
|
||||
}
|
||||
|
||||
type InterOp struct {
|
||||
InteroperabilityIndex
|
||||
}
|
||||
|
||||
type Fields struct {
|
||||
Std
|
||||
Sub
|
||||
GPS
|
||||
}
|
||||
|
||||
func New(x exif.Exif) *Fields {
|
||||
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 79 KiB |
Binary file not shown.
|
@ -0,0 +1,321 @@
|
|||
package tiff
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
type FormatType int
|
||||
|
||||
const (
|
||||
IntVal FormatType = iota
|
||||
FloatVal
|
||||
RatVal
|
||||
StringVal
|
||||
UndefVal
|
||||
OtherVal
|
||||
)
|
||||
|
||||
var fmtSize = map[uint16]uint32{
|
||||
1: 1,
|
||||
2: 1,
|
||||
3: 2,
|
||||
4: 4,
|
||||
5: 8,
|
||||
6: 1,
|
||||
7: 1,
|
||||
8: 2,
|
||||
9: 4,
|
||||
10: 8,
|
||||
11: 4,
|
||||
12: 8,
|
||||
}
|
||||
|
||||
// Tag reflects the parsed content of a tiff IFD tag.
|
||||
type Tag struct {
|
||||
// Id is the 2-byte tiff tag identifier
|
||||
Id uint16
|
||||
// Fmt is an integer (1 through 12) indicating the tag value's format.
|
||||
Fmt uint16
|
||||
// Ncomp is the number of type Fmt stored in the tag's value (i.e. the tag's
|
||||
// value is an array of type Fmt and length Ncomp).
|
||||
Ncomp uint32
|
||||
// Val holds the bytes that represent the tag's value.
|
||||
Val []byte
|
||||
|
||||
order binary.ByteOrder
|
||||
|
||||
intVals []int64
|
||||
floatVals []float64
|
||||
ratVals [][]int64
|
||||
strVal string
|
||||
}
|
||||
|
||||
// DecodeTag parses a tiff-encoded IFD tag from r and returns Tag object. The
|
||||
// first read from r should be the first byte of the tag. ReadAt offsets should
|
||||
// be relative to the beginning of the tiff structure (not relative to the
|
||||
// beginning of the tag).
|
||||
func DecodeTag(r ReadAtReader, order binary.ByteOrder) (*Tag, error) {
|
||||
t := new(Tag)
|
||||
t.order = order
|
||||
|
||||
err := binary.Read(r, order, &t.Id)
|
||||
if err != nil {
|
||||
return nil, errors.New("tiff: tag id read failed: " + err.Error())
|
||||
}
|
||||
|
||||
err = binary.Read(r, order, &t.Fmt)
|
||||
if err != nil {
|
||||
return nil, errors.New("tiff: tag format read failed: " + err.Error())
|
||||
}
|
||||
|
||||
err = binary.Read(r, order, &t.Ncomp)
|
||||
if err != nil {
|
||||
return nil, errors.New("tiff: tag component count read failed: " + err.Error())
|
||||
}
|
||||
|
||||
valLen := fmtSize[t.Fmt] * t.Ncomp
|
||||
var offset uint32
|
||||
if valLen > 4 {
|
||||
binary.Read(r, order, &offset)
|
||||
t.Val = make([]byte, valLen)
|
||||
n, err := r.ReadAt(t.Val, int64(offset))
|
||||
if n != int(valLen) || err != nil {
|
||||
return nil, errors.New("tiff: tag value read failed: " + err.Error())
|
||||
}
|
||||
} else {
|
||||
val := make([]byte, valLen)
|
||||
n, err := r.Read(val)
|
||||
if err != nil || n != int(valLen) {
|
||||
return nil, errors.New("tiff: tag offset read failed: " + err.Error())
|
||||
}
|
||||
|
||||
n, err = r.Read(make([]byte, 4-valLen))
|
||||
if err != nil || n != 4-int(valLen) {
|
||||
return nil, errors.New("tiff: tag offset read failed: " + err.Error())
|
||||
}
|
||||
|
||||
t.Val = val
|
||||
}
|
||||
|
||||
t.convertVals()
|
||||
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func (t *Tag) convertVals() {
|
||||
r := bytes.NewReader(t.Val)
|
||||
|
||||
switch t.Fmt {
|
||||
case 2: // ascii string
|
||||
t.strVal = string(t.Val)
|
||||
case 1:
|
||||
var v uint8
|
||||
t.intVals = make([]int64, int(t.Ncomp))
|
||||
for i := 0; i < int(t.Ncomp); i++ {
|
||||
err := binary.Read(r, t.order, &v)
|
||||
panicOn(err)
|
||||
t.intVals[i] = int64(v)
|
||||
}
|
||||
case 3:
|
||||
var v uint16
|
||||
t.intVals = make([]int64, int(t.Ncomp))
|
||||
for i := 0; i < int(t.Ncomp); i++ {
|
||||
err := binary.Read(r, t.order, &v)
|
||||
panicOn(err)
|
||||
t.intVals[i] = int64(v)
|
||||
}
|
||||
case 4:
|
||||
var v uint32
|
||||
t.intVals = make([]int64, int(t.Ncomp))
|
||||
for i := 0; i < int(t.Ncomp); i++ {
|
||||
err := binary.Read(r, t.order, &v)
|
||||
panicOn(err)
|
||||
t.intVals[i] = int64(v)
|
||||
}
|
||||
case 6:
|
||||
var v int8
|
||||
t.intVals = make([]int64, int(t.Ncomp))
|
||||
for i := 0; i < int(t.Ncomp); i++ {
|
||||
err := binary.Read(r, t.order, &v)
|
||||
panicOn(err)
|
||||
t.intVals[i] = int64(v)
|
||||
}
|
||||
case 8:
|
||||
var v int16
|
||||
t.intVals = make([]int64, int(t.Ncomp))
|
||||
for i := 0; i < int(t.Ncomp); i++ {
|
||||
err := binary.Read(r, t.order, &v)
|
||||
panicOn(err)
|
||||
t.intVals[i] = int64(v)
|
||||
}
|
||||
case 9:
|
||||
var v int32
|
||||
t.intVals = make([]int64, int(t.Ncomp))
|
||||
for i := 0; i < int(t.Ncomp); i++ {
|
||||
err := binary.Read(r, t.order, &v)
|
||||
panicOn(err)
|
||||
t.intVals[i] = int64(v)
|
||||
}
|
||||
case 5: // unsigned rational
|
||||
t.ratVals = make([][]int64, int(t.Ncomp))
|
||||
for i := 0; i < int(t.Ncomp); i++ {
|
||||
var n, d uint32
|
||||
err := binary.Read(r, t.order, &n)
|
||||
panicOn(err)
|
||||
err = binary.Read(r, t.order, &d)
|
||||
panicOn(err)
|
||||
t.ratVals[i] = []int64{int64(n), int64(d)}
|
||||
}
|
||||
case 10: // signed rational
|
||||
t.ratVals = make([][]int64, int(t.Ncomp))
|
||||
for i := 0; i < int(t.Ncomp); i++ {
|
||||
var n, d int32
|
||||
err := binary.Read(r, t.order, &n)
|
||||
panicOn(err)
|
||||
err = binary.Read(r, t.order, &d)
|
||||
panicOn(err)
|
||||
t.ratVals[i] = []int64{int64(n), int64(d)}
|
||||
}
|
||||
case 11: // float32
|
||||
for i := 0; i < int(t.Ncomp); i++ {
|
||||
t.floatVals = make([]float64, int(t.Ncomp))
|
||||
var v float32
|
||||
err := binary.Read(r, t.order, &v)
|
||||
panicOn(err)
|
||||
t.floatVals[i] = float64(v)
|
||||
}
|
||||
case 12: // float64 (double)
|
||||
for i := 0; i < int(t.Ncomp); i++ {
|
||||
t.floatVals = make([]float64, int(t.Ncomp))
|
||||
var u float64
|
||||
err := binary.Read(r, t.order, &u)
|
||||
panicOn(err)
|
||||
t.floatVals[i] = u
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Format returns a value indicating which method can be called to retrieve the
|
||||
// tag's value properly typed (e.g. integer, rational, etc.).
|
||||
func (t *Tag) Format() FormatType {
|
||||
switch t.Fmt {
|
||||
case 1, 3, 4, 6, 8, 9:
|
||||
return IntVal
|
||||
case 5, 10:
|
||||
return RatVal
|
||||
case 11, 12:
|
||||
return FloatVal
|
||||
case 2:
|
||||
return StringVal
|
||||
case 7:
|
||||
return UndefVal
|
||||
}
|
||||
return OtherVal
|
||||
}
|
||||
|
||||
// Rat returns the tag's i'th value as a rational number. It panics if the tag format
|
||||
// is not RatVal, if the denominator is zero, or if the tag has no i'th
|
||||
// component. If a denominator could be zero, use Rat2.
|
||||
func (t *Tag) Rat(i int) *big.Rat {
|
||||
n, d := t.Rat2(i)
|
||||
return big.NewRat(n, d)
|
||||
}
|
||||
|
||||
// Rat2 returns the tag's i'th value as a rational number represented by a
|
||||
// numerator-denominator pair. It panics if the tag format is not RatVal
|
||||
// or if the tag value has no i'th component.
|
||||
func (t *Tag) Rat2(i int) (num, den int64) {
|
||||
if t.Format() != RatVal {
|
||||
panic("Tag format is not 'rational'")
|
||||
}
|
||||
return t.ratVals[i][0], t.ratVals[i][1]
|
||||
}
|
||||
|
||||
// Int returns the tag's i'th value as an integer. It panics if the tag format is not
|
||||
// IntVal or if the tag value has no i'th component.
|
||||
func (t *Tag) Int(i int) int64 {
|
||||
if t.Format() != IntVal {
|
||||
panic("Tag format is not 'int'")
|
||||
}
|
||||
return t.intVals[i]
|
||||
}
|
||||
|
||||
// Float returns the tag's i'th value as a float. It panics if the tag format is not
|
||||
// FloatVal or if the tag value has no i'th component.
|
||||
func (t *Tag) Float(i int) float64 {
|
||||
if t.Format() != FloatVal {
|
||||
panic("Tag format is not 'float'")
|
||||
}
|
||||
return t.floatVals[i]
|
||||
}
|
||||
|
||||
// StringVal returns the tag's value as a string. It panics if the tag
|
||||
// format is not StringVal
|
||||
func (t *Tag) StringVal() string {
|
||||
if t.Format() != StringVal {
|
||||
panic("Tag format is not 'ascii string'")
|
||||
}
|
||||
return t.strVal
|
||||
}
|
||||
|
||||
// String returns a nicely formatted version of the tag.
|
||||
func (t *Tag) String() string {
|
||||
data, err := t.MarshalJSON()
|
||||
panicOn(err)
|
||||
val := string(data)
|
||||
return fmt.Sprintf("{Id: %X, Val: %v}", t.Id, val)
|
||||
}
|
||||
|
||||
func (t *Tag) MarshalJSON() ([]byte, error) {
|
||||
f := t.Format()
|
||||
|
||||
switch f {
|
||||
case StringVal, UndefVal:
|
||||
return nullString(t.Val), nil
|
||||
case OtherVal:
|
||||
panic(fmt.Sprintf("Unhandled type Fmt=%v", t.Fmt))
|
||||
}
|
||||
|
||||
rv := []string{}
|
||||
for i := 0; i < int(t.Ncomp); i++ {
|
||||
switch f {
|
||||
case RatVal:
|
||||
n, d := t.Rat2(i)
|
||||
rv = append(rv, fmt.Sprintf(`"%v/%v"`, n, d))
|
||||
case FloatVal:
|
||||
rv = append(rv, fmt.Sprintf("%v", t.Float(i)))
|
||||
case IntVal:
|
||||
rv = append(rv, fmt.Sprintf("%v", t.Int(i)))
|
||||
}
|
||||
}
|
||||
return []byte(fmt.Sprintf(`[%s]`, strings.Join(rv, ","))), nil
|
||||
}
|
||||
|
||||
func nullString(in []byte) []byte {
|
||||
rv := bytes.Buffer{}
|
||||
rv.WriteByte('"')
|
||||
for _, b := range in {
|
||||
if unicode.IsPrint(rune(b)) {
|
||||
rv.WriteByte(b)
|
||||
}
|
||||
}
|
||||
rv.WriteByte('"')
|
||||
rvb := rv.Bytes()
|
||||
if utf8.Valid(rvb) {
|
||||
return rvb
|
||||
}
|
||||
return []byte(`""`)
|
||||
}
|
||||
|
||||
func panicOn(err error) {
|
||||
if err != nil {
|
||||
panic("unexpected error: " + err.Error())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,144 @@
|
|||
// Package tiff implements TIFF decoding as defined in TIFF 6.0 specification.
|
||||
package tiff
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
// ReadAtReader is used when decoding Tiff tags and directories
|
||||
type ReadAtReader interface {
|
||||
io.Reader
|
||||
io.ReaderAt
|
||||
}
|
||||
|
||||
// Tiff provides access to decoded tiff data.
|
||||
type Tiff struct {
|
||||
// Dirs is an ordered slice of the tiff's Image File Directories (IFDs).
|
||||
// The IFD at index 0 is IFD0.
|
||||
Dirs []*Dir
|
||||
// The tiff's byte-encoding (i.e. big/little endian).
|
||||
Order binary.ByteOrder
|
||||
}
|
||||
|
||||
// Decode parses tiff-encoded data from r and returns a Tiff 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 (not necessarily the first byte of an
|
||||
// os.File object).
|
||||
func Decode(r io.Reader) (*Tiff, error) {
|
||||
data, err := ioutil.ReadAll(r)
|
||||
if err != nil {
|
||||
return nil, errors.New("tiff: could not read data")
|
||||
}
|
||||
buf := bytes.NewReader(data)
|
||||
|
||||
t := new(Tiff)
|
||||
|
||||
// read byte order
|
||||
bo := make([]byte, 2)
|
||||
n, err := buf.Read(bo)
|
||||
if n < len(bo) || err != nil {
|
||||
return nil, errors.New("tiff: could not read tiff byte order")
|
||||
} else {
|
||||
if string(bo) == "II" {
|
||||
t.Order = binary.LittleEndian
|
||||
} else if string(bo) == "MM" {
|
||||
t.Order = binary.BigEndian
|
||||
} else {
|
||||
return nil, errors.New("tiff: could not read tiff byte order")
|
||||
}
|
||||
}
|
||||
|
||||
// check for special tiff marker
|
||||
var sp int16
|
||||
err = binary.Read(buf, t.Order, &sp)
|
||||
if err != nil || 0x002A != sp {
|
||||
return nil, errors.New("tiff: could not find special tiff marker")
|
||||
}
|
||||
|
||||
// load offset to first IFD
|
||||
var offset int32
|
||||
err = binary.Read(buf, t.Order, &offset)
|
||||
if err != nil {
|
||||
return nil, errors.New("tiff: could not read offset to first IFD")
|
||||
}
|
||||
|
||||
// load IFD's
|
||||
var d *Dir
|
||||
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
|
||||
d, offset, err = DecodeDir(buf, t.Order)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t.Dirs = append(t.Dirs, d)
|
||||
}
|
||||
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func (tf *Tiff) String() string {
|
||||
s := "Tiff{"
|
||||
for _, d := range tf.Dirs {
|
||||
s += d.String() + ", "
|
||||
}
|
||||
return s + "}"
|
||||
}
|
||||
|
||||
// Dir reflects the parsed content of a tiff Image File Directory (IFD).
|
||||
type Dir struct {
|
||||
Tags []*Tag
|
||||
}
|
||||
|
||||
// DecodeDir parses a tiff-encoded IFD from r and returns a Dir object. offset
|
||||
// is the offset to the next IFD. The first read from r should be at the first
|
||||
// byte of the IFD. ReadAt offsets should be relative to the beginning of the
|
||||
// tiff structure (not relative to the beginning of the IFD).
|
||||
func DecodeDir(r ReadAtReader, order binary.ByteOrder) (d *Dir, offset int32, err error) {
|
||||
d = new(Dir)
|
||||
|
||||
// get num of tags in ifd
|
||||
var nTags int16
|
||||
err = binary.Read(r, order, &nTags)
|
||||
if err != nil {
|
||||
return nil, 0, errors.New("tiff: falied to read IFD tag count: " + err.Error())
|
||||
}
|
||||
|
||||
// load tags
|
||||
for n := 0; n < int(nTags); n++ {
|
||||
t, err := DecodeTag(r, order)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
d.Tags = append(d.Tags, t)
|
||||
}
|
||||
|
||||
// get offset to next ifd
|
||||
err = binary.Read(r, order, &offset)
|
||||
if err != nil {
|
||||
return nil, 0, errors.New("tiff: falied to read offset to next IFD: " + err.Error())
|
||||
}
|
||||
|
||||
return d, offset, nil
|
||||
}
|
||||
|
||||
func (d *Dir) String() string {
|
||||
s := "Dir{"
|
||||
for _, t := range d.Tags {
|
||||
s += t.String() + ", "
|
||||
}
|
||||
return s + "}"
|
||||
}
|
|
@ -0,0 +1,228 @@
|
|||
package tiff
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type input struct {
|
||||
tgId string
|
||||
tpe string
|
||||
nVals string
|
||||
offset string
|
||||
val string
|
||||
}
|
||||
|
||||
type output struct {
|
||||
id uint16
|
||||
format uint16
|
||||
count uint32
|
||||
val []byte
|
||||
}
|
||||
|
||||
type tagTest struct {
|
||||
big input // big endian
|
||||
little input // little endian
|
||||
out output
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////
|
||||
//// Big endian Tests /////////////////////////
|
||||
///////////////////////////////////////////////
|
||||
var set1 = []tagTest{
|
||||
//////////// string type //////////////
|
||||
tagTest{
|
||||
// {"TgId", "TYPE", "N-VALUES", "OFFSET--", "VAL..."},
|
||||
input{"0003", "0002", "00000002", "11000000", ""},
|
||||
input{"0300", "0200", "02000000", "11000000", ""},
|
||||
output{0x0003, 0x0002, 0x0002, []byte{0x11, 0x00}},
|
||||
},
|
||||
tagTest{
|
||||
input{"0001", "0002", "00000006", "00000012", "111213141516"},
|
||||
input{"0100", "0200", "06000000", "12000000", "111213141516"},
|
||||
output{0x0001, 0x0002, 0x0006, []byte{0x11, 0x12, 0x13, 0x14, 0x15, 0x16}},
|
||||
},
|
||||
//////////// int (1-byte) type ////////////////
|
||||
tagTest{
|
||||
input{"0001", "0001", "00000001", "11000000", ""},
|
||||
input{"0100", "0100", "01000000", "11000000", ""},
|
||||
output{0x0001, 0x0001, 0x0001, []byte{0x11}},
|
||||
},
|
||||
tagTest{
|
||||
input{"0001", "0001", "00000005", "00000010", "1112131415"},
|
||||
input{"0100", "0100", "05000000", "10000000", "1112131415"},
|
||||
output{0x0001, 0x0001, 0x0005, []byte{0x11, 0x12, 0x13, 0x14, 0x15}},
|
||||
},
|
||||
tagTest{
|
||||
input{"0001", "0006", "00000001", "11000000", ""},
|
||||
input{"0100", "0600", "01000000", "11000000", ""},
|
||||
output{0x0001, 0x0006, 0x0001, []byte{0x11}},
|
||||
},
|
||||
tagTest{
|
||||
input{"0001", "0006", "00000005", "00000010", "1112131415"},
|
||||
input{"0100", "0600", "05000000", "10000000", "1112131415"},
|
||||
output{0x0001, 0x0006, 0x0005, []byte{0x11, 0x12, 0x13, 0x14, 0x15}},
|
||||
},
|
||||
//////////// int (2-byte) types ////////////////
|
||||
tagTest{
|
||||
input{"0001", "0003", "00000002", "11111212", ""},
|
||||
input{"0100", "0300", "02000000", "11111212", ""},
|
||||
output{0x0001, 0x0003, 0x0002, []byte{0x11, 0x11, 0x12, 0x12}},
|
||||
},
|
||||
tagTest{
|
||||
input{"0001", "0003", "00000003", "00000010", "111213141516"},
|
||||
input{"0100", "0300", "03000000", "10000000", "111213141516"},
|
||||
output{0x0001, 0x0003, 0x0003, []byte{0x11, 0x12, 0x13, 0x14, 0x15, 0x16}},
|
||||
},
|
||||
tagTest{
|
||||
input{"0001", "0008", "00000001", "11120000", ""},
|
||||
input{"0100", "0800", "01000000", "11120000", ""},
|
||||
output{0x0001, 0x0008, 0x0001, []byte{0x11, 0x12}},
|
||||
},
|
||||
tagTest{
|
||||
input{"0001", "0008", "00000003", "00000100", "111213141516"},
|
||||
input{"0100", "0800", "03000000", "00100000", "111213141516"},
|
||||
output{0x0001, 0x0008, 0x0003, []byte{0x11, 0x12, 0x13, 0x14, 0x15, 0x16}},
|
||||
},
|
||||
//////////// int (4-byte) types ////////////////
|
||||
tagTest{
|
||||
input{"0001", "0004", "00000001", "11121314", ""},
|
||||
input{"0100", "0400", "01000000", "11121314", ""},
|
||||
output{0x0001, 0x0004, 0x0001, []byte{0x11, 0x12, 0x13, 0x14}},
|
||||
},
|
||||
tagTest{
|
||||
input{"0001", "0004", "00000002", "00000010", "1112131415161718"},
|
||||
input{"0100", "0400", "02000000", "10000000", "1112131415161718"},
|
||||
output{0x0001, 0x0004, 0x0002, []byte{0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18}},
|
||||
},
|
||||
tagTest{
|
||||
input{"0001", "0009", "00000001", "11121314", ""},
|
||||
input{"0100", "0900", "01000000", "11121314", ""},
|
||||
output{0x0001, 0x0009, 0x0001, []byte{0x11, 0x12, 0x13, 0x14}},
|
||||
},
|
||||
tagTest{
|
||||
input{"0001", "0009", "00000002", "00000011", "1112131415161819"},
|
||||
input{"0100", "0900", "02000000", "11000000", "1112131415161819"},
|
||||
output{0x0001, 0x0009, 0x0002, []byte{0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x18, 0x19}},
|
||||
},
|
||||
//////////// rational types ////////////////////
|
||||
tagTest{
|
||||
input{"0001", "0005", "00000001", "00000010", "1112131415161718"},
|
||||
input{"0100", "0500", "01000000", "10000000", "1112131415161718"},
|
||||
output{0x0001, 0x0005, 0x0001, []byte{0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18}},
|
||||
},
|
||||
tagTest{
|
||||
input{"0001", "000A", "00000001", "00000011", "1112131415161819"},
|
||||
input{"0100", "0A00", "01000000", "11000000", "1112131415161819"},
|
||||
output{0x0001, 0x000A, 0x0001, []byte{0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x18, 0x19}},
|
||||
},
|
||||
//////////// float types ///////////////////////
|
||||
tagTest{
|
||||
input{"0001", "0005", "00000001", "00000010", "1112131415161718"},
|
||||
input{"0100", "0500", "01000000", "10000000", "1112131415161718"},
|
||||
output{0x0001, 0x0005, 0x0001, []byte{0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18}},
|
||||
},
|
||||
tagTest{
|
||||
input{"0101", "000A", "00000001", "00000011", "1112131415161819"},
|
||||
input{"0101", "0A00", "01000000", "11000000", "1112131415161819"},
|
||||
output{0x0101, 0x000A, 0x0001, []byte{0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x18, 0x19}},
|
||||
},
|
||||
}
|
||||
|
||||
func TestDecodeTag(t *testing.T) {
|
||||
for i, tst := range set1 {
|
||||
testSingle(t, binary.BigEndian, tst.big, tst.out, i)
|
||||
testSingle(t, binary.LittleEndian, tst.little, tst.out, i)
|
||||
}
|
||||
}
|
||||
|
||||
func testSingle(t *testing.T, order binary.ByteOrder, in input, out output, i int) {
|
||||
data := buildInput(in, order)
|
||||
buf := bytes.NewReader(data)
|
||||
tg, err := DecodeTag(buf, order)
|
||||
if err != nil {
|
||||
t.Errorf("(%v) tag %v%+v decode failed: %v", order, i, in, err)
|
||||
return
|
||||
}
|
||||
|
||||
if tg.Id != out.id {
|
||||
t.Errorf("(%v) tag %v id decode: expected %v, got %v", order, i, out.id, tg.Id)
|
||||
}
|
||||
if tg.Fmt != out.format {
|
||||
t.Errorf("(%v) tag %v format decode: expected %v, got %v", order, i, out.format, tg.Fmt)
|
||||
}
|
||||
if tg.Ncomp != out.count {
|
||||
t.Errorf("(%v) tag %v N-components decode: expected %v, got %v", order, i, out.count, tg.Ncomp)
|
||||
}
|
||||
if !bytes.Equal(tg.Val, out.val) {
|
||||
t.Errorf("(%v) tag %v value decode: expected %v, got %v", order, i, out.val, tg.Val)
|
||||
}
|
||||
}
|
||||
|
||||
// buildInputBig creates a byte-slice based on big-endian ordered input
|
||||
func buildInput(in input, order binary.ByteOrder) []byte {
|
||||
data := make([]byte, 0)
|
||||
d, _ := hex.DecodeString(in.tgId)
|
||||
data = append(data, d...)
|
||||
d, _ = hex.DecodeString(in.tpe)
|
||||
data = append(data, d...)
|
||||
d, _ = hex.DecodeString(in.nVals)
|
||||
data = append(data, d...)
|
||||
d, _ = hex.DecodeString(in.offset)
|
||||
data = append(data, d...)
|
||||
|
||||
if in.val != "" {
|
||||
off := order.Uint32(d)
|
||||
for i := 0; i < int(off)-12; i++ {
|
||||
data = append(data, 0xFF)
|
||||
}
|
||||
|
||||
d, _ = hex.DecodeString(in.val)
|
||||
data = append(data, d...)
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
func TestDecode(t *testing.T) {
|
||||
name := "sample1.tif"
|
||||
f, err := os.Open(name)
|
||||
if err != nil {
|
||||
t.Fatalf("%v\n", err)
|
||||
}
|
||||
|
||||
tif, err := Decode(f)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Log(tif)
|
||||
}
|
||||
|
||||
func TestDecodeTag_blob(t *testing.T) {
|
||||
buf := bytes.NewReader(data())
|
||||
buf.Seek(10, 1)
|
||||
tg, err := DecodeTag(buf, binary.LittleEndian)
|
||||
if err != nil {
|
||||
t.Fatalf("tag decode failed: %v", err)
|
||||
}
|
||||
|
||||
t.Logf("tag: %v+\n", tg)
|
||||
n, d := tg.Rat2(0)
|
||||
t.Logf("tag rat val: %v\n", n, d)
|
||||
}
|
||||
|
||||
func data() []byte {
|
||||
s1 := "49492A000800000002001A0105000100"
|
||||
s1 += "00002600000069870400010000001102"
|
||||
s1 += "0000000000004800000001000000"
|
||||
|
||||
dat, err := hex.DecodeString(s1)
|
||||
if err != nil {
|
||||
panic("invalid string fixture")
|
||||
}
|
||||
return dat
|
||||
}
|
Loading…
Reference in New Issue