import github.com/camlistore/goexif as a third party

Change-Id: I34842677f4d9335df2478692e0b0d169d00d0942
This commit is contained in:
mpl 2012-11-19 23:04:04 +01:00
parent fbead58f92
commit fa1269da45
13 changed files with 1408 additions and 1 deletions

View File

@ -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

View File

@ -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.

View File

@ -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-->

View File

@ -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)
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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,
}

View File

@ -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.

View File

@ -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())
}
}

View File

@ -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 + "}"
}

View File

@ -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
}