Skip to content

Commit

Permalink
Add support for extracting EXIF data from PNG files.
Browse files Browse the repository at this point in the history
  • Loading branch information
abrander committed Jan 27, 2023
1 parent 6924485 commit afd7cfe
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 1 deletion.
2 changes: 1 addition & 1 deletion exif2/reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func Decode(r io.ReadSeeker) (Exif, error) {
return ir.Exif, nil
}

func (ir *ifdReader) DecodeTiff(r io.Reader, h meta.ExifHeader) error {
func (ir *ifdReader) DecodeTiff(_ io.Reader, h meta.ExifHeader) error {
ir.buffer.clear()
// Log Header Info
if ir.logInfo() {
Expand Down
25 changes: 25 additions & 0 deletions png.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package imagemeta

import (
"io"

"github.com/evanoberholster/imagemeta/exif2"
"github.com/evanoberholster/imagemeta/png"
)

// DecodePng decodes a PNG file from an io.Reader returning Exif or an error.
func DecodePng(r io.ReadSeeker) (exif2.Exif, error) {
header, err := png.ScanPngHeader(r)
if err != nil {
return exif2.Exif{}, err
}

ir := exif2.NewIfdReader(r)
defer ir.Close()

if err := ir.DecodeTiff(nil, header); err != nil {
return ir.Exif, err
}

return ir.Exif, nil
}
66 changes: 66 additions & 0 deletions png/png.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Package png reads PNG Header metadata information from image files before being processed by exif package
package png

import (
"encoding/binary"
"io"

"github.com/evanoberholster/imagemeta/imagetype"
"github.com/evanoberholster/imagemeta/meta"
)

func ScanPngHeader(r io.ReadSeeker) (header meta.ExifHeader, err error) {
// 5.2 PNG signature
const signature = "\x89PNG\r\n\x1a\n"

// 5.3 Chunk layout
const crcSize = 4

// 8 is the size of both the signature and the chunk
// id (4 bytes) + chunk length (4 bytes).
// This is just a coincidence.
buf := make([]byte, 8)

var n int
n, err = r.Read(buf)
if err != nil {
return
}

if n != len(signature) || string(buf) != signature {
err = meta.ErrNoExif

return
}

for {
// 5.3 Chunk layout
n, err = r.Read(buf)
if err != nil {
break
}

if n != len(buf) {
break
}

length := binary.BigEndian.Uint32(buf[0:4])
chunkType := string(buf[4:8])

switch chunkType {
case "eXIf":
offset, _ := r.Seek(0, io.SeekCurrent)

return meta.NewExifHeader(meta.BigEndian, 8, uint32(offset), length, imagetype.ImagePNG), nil

default:
// Discard the chunk length + CRC.
_, err := r.Seek(int64(length+crcSize), io.SeekCurrent)
if err != nil {
return header, err
}
}
}

return header, meta.ErrNoExif
}

1 comment on commit afd7cfe

@evanoberholster
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't been able to test this but it looks appropriate. Thanks for your work on this.

Please sign in to comment.