summaryrefslogtreecommitdiff
path: root/vendor/github.com/dsoprea/go-png-image-structure/v2/png.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/dsoprea/go-png-image-structure/v2/png.go')
-rw-r--r--vendor/github.com/dsoprea/go-png-image-structure/v2/png.go416
1 files changed, 416 insertions, 0 deletions
diff --git a/vendor/github.com/dsoprea/go-png-image-structure/v2/png.go b/vendor/github.com/dsoprea/go-png-image-structure/v2/png.go
new file mode 100644
index 000000000..fbb022887
--- /dev/null
+++ b/vendor/github.com/dsoprea/go-png-image-structure/v2/png.go
@@ -0,0 +1,416 @@
+package pngstructure
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+ "io"
+
+ "encoding/binary"
+ "hash/crc32"
+
+ "github.com/dsoprea/go-exif/v3"
+ "github.com/dsoprea/go-exif/v3/common"
+ "github.com/dsoprea/go-logging"
+ "github.com/dsoprea/go-utility/v2/image"
+)
+
+var (
+ PngSignature = [8]byte{137, 'P', 'N', 'G', '\r', '\n', 26, '\n'}
+ EXifChunkType = "eXIf"
+ IHDRChunkType = "IHDR"
+)
+
+var (
+ ErrNotPng = errors.New("not png data")
+ ErrCrcFailure = errors.New("crc failure")
+)
+
+// ChunkSlice encapsulates a slice of chunks.
+type ChunkSlice struct {
+ chunks []*Chunk
+}
+
+func NewChunkSlice(chunks []*Chunk) *ChunkSlice {
+ if len(chunks) == 0 {
+ log.Panicf("ChunkSlice must be initialized with at least one chunk (IHDR)")
+ } else if chunks[0].Type != IHDRChunkType {
+ log.Panicf("first chunk in any ChunkSlice must be an IHDR")
+ }
+
+ return &ChunkSlice{
+ chunks: chunks,
+ }
+}
+
+func NewPngChunkSlice() *ChunkSlice {
+
+ ihdrChunk := &Chunk{
+ Type: IHDRChunkType,
+ }
+
+ ihdrChunk.UpdateCrc32()
+
+ return NewChunkSlice([]*Chunk{ihdrChunk})
+}
+
+func (cs *ChunkSlice) String() string {
+ return fmt.Sprintf("ChunkSlize<LEN=(%d)>", len(cs.chunks))
+}
+
+// Chunks exposes the actual slice.
+func (cs *ChunkSlice) Chunks() []*Chunk {
+ return cs.chunks
+}
+
+// Write encodes and writes all chunks.
+func (cs *ChunkSlice) WriteTo(w io.Writer) (err error) {
+ defer func() {
+ if state := recover(); state != nil {
+ err = log.Wrap(state.(error))
+ }
+ }()
+
+ _, err = w.Write(PngSignature[:])
+ log.PanicIf(err)
+
+ // TODO(dustin): !! This should respect the safe-to-copy characteristic.
+ for _, c := range cs.chunks {
+ _, err := c.WriteTo(w)
+ log.PanicIf(err)
+ }
+
+ return nil
+}
+
+// Index returns a map of chunk types to chunk slices, grouping all like chunks.
+func (cs *ChunkSlice) Index() (index map[string][]*Chunk) {
+ index = make(map[string][]*Chunk)
+ for _, c := range cs.chunks {
+ if grouped, found := index[c.Type]; found == true {
+ index[c.Type] = append(grouped, c)
+ } else {
+ index[c.Type] = []*Chunk{c}
+ }
+ }
+
+ return index
+}
+
+// FindExif returns the the segment that hosts the EXIF data.
+func (cs *ChunkSlice) FindExif() (chunk *Chunk, err error) {
+ defer func() {
+ if state := recover(); state != nil {
+ err = log.Wrap(state.(error))
+ }
+ }()
+
+ index := cs.Index()
+
+ if chunks, found := index[EXifChunkType]; found == true {
+ return chunks[0], nil
+ }
+
+ log.Panic(exif.ErrNoExif)
+
+ // Never called.
+ return nil, nil
+}
+
+// Exif returns an `exif.Ifd` instance with the existing tags.
+func (cs *ChunkSlice) Exif() (rootIfd *exif.Ifd, data []byte, err error) {
+ defer func() {
+ if state := recover(); state != nil {
+ err = log.Wrap(state.(error))
+ }
+ }()
+
+ chunk, err := cs.FindExif()
+ log.PanicIf(err)
+
+ im, err := exifcommon.NewIfdMappingWithStandard()
+ log.PanicIf(err)
+
+ ti := exif.NewTagIndex()
+
+ // TODO(dustin): Refactor and support `exif.GetExifData()`.
+
+ _, index, err := exif.Collect(im, ti, chunk.Data)
+ log.PanicIf(err)
+
+ return index.RootIfd, chunk.Data, nil
+}
+
+// ConstructExifBuilder returns an `exif.IfdBuilder` instance (needed for
+// modifying) preloaded with all existing tags.
+func (cs *ChunkSlice) ConstructExifBuilder() (rootIb *exif.IfdBuilder, err error) {
+ defer func() {
+ if state := recover(); state != nil {
+ err = log.Wrap(state.(error))
+ }
+ }()
+
+ rootIfd, _, err := cs.Exif()
+ log.PanicIf(err)
+
+ ib := exif.NewIfdBuilderFromExistingChain(rootIfd)
+
+ return ib, nil
+}
+
+// SetExif encodes and sets EXIF data into this segment.
+func (cs *ChunkSlice) SetExif(ib *exif.IfdBuilder) (err error) {
+ defer func() {
+ if state := recover(); state != nil {
+ err = log.Wrap(state.(error))
+ }
+ }()
+
+ // Encode.
+
+ ibe := exif.NewIfdByteEncoder()
+
+ exifData, err := ibe.EncodeToExif(ib)
+ log.PanicIf(err)
+
+ // Set.
+
+ exifChunk, err := cs.FindExif()
+ if err == nil {
+ // EXIF chunk already exists.
+
+ exifChunk.Data = exifData
+ exifChunk.Length = uint32(len(exifData))
+ } else {
+ if log.Is(err, exif.ErrNoExif) != true {
+ log.Panic(err)
+ }
+
+ // Add a EXIF chunk for the first time.
+
+ exifChunk = &Chunk{
+ Type: EXifChunkType,
+ Data: exifData,
+ Length: uint32(len(exifData)),
+ }
+
+ // Insert it after the IHDR chunk (it's a reliably appropriate place to
+ // put it).
+ cs.chunks = append(cs.chunks[:1], append([]*Chunk{exifChunk}, cs.chunks[1:]...)...)
+ }
+
+ exifChunk.UpdateCrc32()
+
+ return nil
+}
+
+// PngSplitter hosts the princpal `Split()` method uses by `bufio.Scanner`.
+type PngSplitter struct {
+ chunks []*Chunk
+ currentOffset int
+
+ doCheckCrc bool
+ crcErrors []string
+}
+
+func (ps *PngSplitter) Chunks() *ChunkSlice {
+ return NewChunkSlice(ps.chunks)
+}
+
+func (ps *PngSplitter) DoCheckCrc(doCheck bool) {
+ ps.doCheckCrc = doCheck
+}
+
+func (ps *PngSplitter) CrcErrors() []string {
+ return ps.crcErrors
+}
+
+func NewPngSplitter() *PngSplitter {
+ return &PngSplitter{
+ chunks: make([]*Chunk, 0),
+ doCheckCrc: true,
+ crcErrors: make([]string, 0),
+ }
+}
+
+// Chunk describes a single chunk.
+type Chunk struct {
+ Offset int
+ Length uint32
+ Type string
+ Data []byte
+ Crc uint32
+}
+
+func (c *Chunk) String() string {
+ return fmt.Sprintf("Chunk<OFFSET=(%d) LENGTH=(%d) TYPE=[%s] CRC=(%d)>", c.Offset, c.Length, c.Type, c.Crc)
+}
+
+func calculateCrc32(chunk *Chunk) uint32 {
+ c := crc32.NewIEEE()
+
+ c.Write([]byte(chunk.Type))
+ c.Write(chunk.Data)
+
+ return c.Sum32()
+}
+
+func (c *Chunk) UpdateCrc32() {
+ c.Crc = calculateCrc32(c)
+}
+
+func (c *Chunk) CheckCrc32() bool {
+ expected := calculateCrc32(c)
+ return c.Crc == expected
+}
+
+// Bytes encodes and returns the bytes for this chunk.
+func (c *Chunk) Bytes() []byte {
+ defer func() {
+ if state := recover(); state != nil {
+ err := log.Wrap(state.(error))
+ log.Panic(err)
+ }
+ }()
+
+ if len(c.Data) != int(c.Length) {
+ log.Panicf("length of data not correct")
+ }
+
+ preallocated := make([]byte, 0, 4+4+c.Length+4)
+ b := bytes.NewBuffer(preallocated)
+
+ err := binary.Write(b, binary.BigEndian, c.Length)
+ log.PanicIf(err)
+
+ _, err = b.Write([]byte(c.Type))
+ log.PanicIf(err)
+
+ if c.Data != nil {
+ _, err = b.Write(c.Data)
+ log.PanicIf(err)
+ }
+
+ err = binary.Write(b, binary.BigEndian, c.Crc)
+ log.PanicIf(err)
+
+ return b.Bytes()
+}
+
+// Write encodes and writes the bytes for this chunk.
+func (c *Chunk) WriteTo(w io.Writer) (count int, err error) {
+ defer func() {
+ if state := recover(); state != nil {
+ err = log.Wrap(state.(error))
+ }
+ }()
+
+ if len(c.Data) != int(c.Length) {
+ log.Panicf("length of data not correct")
+ }
+
+ err = binary.Write(w, binary.BigEndian, c.Length)
+ log.PanicIf(err)
+
+ _, err = w.Write([]byte(c.Type))
+ log.PanicIf(err)
+
+ _, err = w.Write(c.Data)
+ log.PanicIf(err)
+
+ err = binary.Write(w, binary.BigEndian, c.Crc)
+ log.PanicIf(err)
+
+ return 4 + len(c.Type) + len(c.Data) + 4, nil
+}
+
+// readHeader verifies that the PNG header bytes appear next.
+func (ps *PngSplitter) readHeader(r io.Reader) (err error) {
+ defer func() {
+ if state := recover(); state != nil {
+ err = log.Wrap(state.(error))
+ }
+ }()
+
+ len_ := len(PngSignature)
+ header := make([]byte, len_)
+
+ _, err = r.Read(header)
+ log.PanicIf(err)
+
+ ps.currentOffset += len_
+
+ if bytes.Compare(header, PngSignature[:]) != 0 {
+ log.Panic(ErrNotPng)
+ }
+
+ return nil
+}
+
+// Split fulfills the `bufio.SplitFunc` function definition for
+// `bufio.Scanner`.
+func (ps *PngSplitter) Split(data []byte, atEOF bool) (advance int, token []byte, err error) {
+ defer func() {
+ if state := recover(); state != nil {
+ err = log.Wrap(state.(error))
+ }
+ }()
+
+ // We might have more than one chunk's worth, and, if `atEOF` is true, we
+ // won't be called again. We'll repeatedly try to read additional chunks,
+ // but, when we run out of the data we were given then we'll return the
+ // number of bytes fo rthe chunks we've already completely read. Then,
+ // we'll be called again from theend ofthose bytes, at which point we'll
+ // indicate that we don't yet have enough for another chunk, and we should
+ // be then called with more.
+ for {
+ len_ := len(data)
+ if len_ < 8 {
+ return advance, nil, nil
+ }
+
+ length := binary.BigEndian.Uint32(data[:4])
+ type_ := string(data[4:8])
+ chunkSize := (8 + int(length) + 4)
+
+ if len_ < chunkSize {
+ return advance, nil, nil
+ }
+
+ crcIndex := 8 + length
+ crc := binary.BigEndian.Uint32(data[crcIndex : crcIndex+4])
+
+ content := make([]byte, length)
+ copy(content, data[8:8+length])
+
+ c := &Chunk{
+ Length: length,
+ Type: type_,
+ Data: content,
+ Crc: crc,
+ Offset: ps.currentOffset,
+ }
+
+ ps.chunks = append(ps.chunks, c)
+
+ if c.CheckCrc32() == false {
+ ps.crcErrors = append(ps.crcErrors, type_)
+
+ if ps.doCheckCrc == true {
+ log.Panic(ErrCrcFailure)
+ }
+ }
+
+ advance += chunkSize
+ ps.currentOffset += chunkSize
+
+ data = data[chunkSize:]
+ }
+
+ return advance, nil, nil
+}
+
+var (
+ // Enforce interface conformance.
+ _ riimage.MediaContext = new(ChunkSlice)
+)