go.charczuk.com@v0.0.0-20240327042549-bc490516bd1a/projects/blogctl/pkg/tiff/tiff.go (about) 1 /* 2 3 Copyright (c) 2023 - Present. Will Charczuk. All rights reserved. 4 Use of this source code is governed by a MIT license that can be found in the LICENSE file at the root of the repository. 5 6 */ 7 8 package tiff 9 10 import ( 11 "bytes" 12 "encoding/binary" 13 "errors" 14 "fmt" 15 "io" 16 "io/ioutil" 17 ) 18 19 // ReadAtReader is used when decoding Tiff tags and directories 20 type ReadAtReader interface { 21 io.Reader 22 io.ReaderAt 23 } 24 25 // Tiff provides access to a decoded tiff data structure. 26 type Tiff struct { 27 // Dirs is an ordered slice of the tiff's Image File Directories (IFDs). 28 // The IFD at index 0 is IFD0. 29 Dirs []*Dir 30 // The tiff's byte-encoding (i.e. big/little endian). 31 Order binary.ByteOrder 32 } 33 34 // Decode parses tiff-encoded data from r and returns a Tiff struct that 35 // reflects the structure and content of the tiff data. The first read from r 36 // should be the first byte of the tiff-encoded data and not necessarily the 37 // first byte of an os.File object. 38 func Decode(r io.Reader) (*Tiff, error) { 39 data, err := ioutil.ReadAll(r) 40 if err != nil { 41 return nil, errors.New("tiff: could not read data") 42 } 43 buf := bytes.NewReader(data) 44 45 t := new(Tiff) 46 47 // read byte order 48 bo := make([]byte, 2) 49 if _, err = io.ReadFull(buf, bo); err != nil { 50 return nil, errors.New("tiff: could not read tiff byte order") 51 } 52 if string(bo) == "II" { 53 t.Order = binary.LittleEndian 54 } else if string(bo) == "MM" { 55 t.Order = binary.BigEndian 56 } else { 57 return nil, errors.New("tiff: could not read tiff byte order") 58 } 59 60 // check for special tiff marker 61 var sp int16 62 err = binary.Read(buf, t.Order, &sp) 63 if err != nil || sp != 42 { 64 return nil, errors.New("tiff: could not find special tiff marker") 65 } 66 67 // load offset to first IFD 68 var offset int32 69 err = binary.Read(buf, t.Order, &offset) 70 if err != nil { 71 return nil, errors.New("tiff: could not read offset to first IFD") 72 } 73 74 // load IFD's 75 var d *Dir 76 prev := offset 77 for offset != 0 { 78 // seek to offset 79 _, err := buf.Seek(int64(offset), 0) 80 if err != nil { 81 return nil, errors.New("tiff: seek to IFD failed") 82 } 83 84 if buf.Len() == 0 { 85 return nil, errors.New("tiff: seek offset after EOF") 86 } 87 88 // load the dir 89 d, offset, err = DecodeDir(buf, t.Order) 90 if err != nil { 91 return nil, err 92 } 93 94 if offset == prev { 95 return nil, errors.New("tiff: recursive IFD") 96 } 97 prev = offset 98 99 t.Dirs = append(t.Dirs, d) 100 } 101 102 return t, nil 103 } 104 105 func (tf *Tiff) String() string { 106 var buf bytes.Buffer 107 fmt.Fprint(&buf, "Tiff{") 108 for _, d := range tf.Dirs { 109 fmt.Fprintf(&buf, "%s, ", d.String()) 110 } 111 fmt.Fprintf(&buf, "}") 112 return buf.String() 113 } 114 115 // Dir provides access to the parsed content of a tiff Image File Directory (IFD). 116 type Dir struct { 117 Tags []*Tag 118 } 119 120 // DecodeDir parses a tiff-encoded IFD from r and returns a Dir object. offset 121 // is the offset to the next IFD. The first read from r should be at the first 122 // byte of the IFD. ReadAt offsets should generally be relative to the 123 // beginning of the tiff structure (not relative to the beginning of the IFD). 124 func DecodeDir(r ReadAtReader, order binary.ByteOrder) (d *Dir, offset int32, err error) { 125 d = new(Dir) 126 127 // get num of tags in ifd 128 var nTags int16 129 err = binary.Read(r, order, &nTags) 130 if err != nil { 131 return nil, 0, errors.New("tiff: failed to read IFD tag count: " + err.Error()) 132 } 133 134 // load tags 135 for n := 0; n < int(nTags); n++ { 136 t, err := DecodeTag(r, order) 137 if err != nil { 138 return nil, 0, err 139 } 140 d.Tags = append(d.Tags, t) 141 } 142 143 // get offset to next ifd 144 err = binary.Read(r, order, &offset) 145 if err != nil { 146 return nil, 0, errors.New("tiff: falied to read offset to next IFD: " + err.Error()) 147 } 148 149 return d, offset, nil 150 } 151 152 // String returns the dir as a readable string. 153 func (d *Dir) String() string { 154 s := "Dir{" 155 for _, t := range d.Tags { 156 s += t.String() + ", " 157 } 158 return s + "}" 159 }