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  }