Dead simple exchangeable image file format tools for Go optimized for large image files using lazy loading.

  • By Patricio Whittingslow
  • Last update: Apr 12, 2023
  • Comments: 1


Exchangeable image file format tools for Go.

This library is at least 200 times faster for extracting EXIF data from a small image when compared to go-exif and can be up to thousands of times faster for images in the size of megabytes. See benchmarks below.

  • The root directory contains common EXIF functions and data types.
  • The tiff directory contains a TIFF image parser that uses lazy loading.
    • TODO: Implement the image.Image interface using a cache for low memory requirement lazy loading
  • The rational directory contains signed and unsigned 64bit rational number types

Benchmarks- comparisons with the popular go-exif library.

  • Small image: 15kB TIFF
  • Large image: 19.6MB TIFF
goos: linux
goarch: amd64
cpu: 12th Gen Intel(R) Core(TM) i5-12400F
BenchmarkThisPackage_SmallImage-12    	  136064	      8411 ns/op	    2457 B/op	      57 allocs/op
BenchmarkDsoprea_SmallImage-12        	     798	   1650455 ns/op	  659437 B/op	   11252 allocs/op
BenchmarkThisPackage_LargeImage-12    	  127800	      8593 ns/op	    1977 B/op	      50 allocs/op
BenchmarkDsoprea_LargeImage-12        	      52	  23897506 ns/op	123760580 B/op	   11588 allocs/op
coverage: 41.6% of statements
Benchmark code

I have removed the following benchmark from this package since dsoprea's Go package has two high severity security issues and github's dependabot was bothering me some. You are free to run it on your computer and compare with the benchmarks under benchmarks_test.go

package exif_test

import (

	dsoprea ""
	exifcommon ""

const (
	smallImageName = "testdata/sample1.tiff"

func BenchmarkDsoprea_SmallImage(b *testing.B) {
	for i := 0; i < b.N; i++ {
		rawExif, err := dsoprea.SearchFileAndExtractExif(smallImageName)
		if err != nil {
		mapping, _ := exifcommon.NewIfdMappingWithStandard()
		ti := dsoprea.NewTagIndex()
		_, index, err := dsoprea.Collect(mapping, ti, rawExif)
		if err != nil {
		err = index.RootIfd.EnumerateTagsRecursively(func(i *dsoprea.Ifd, ite *dsoprea.IfdTagEntry) error {
			return nil
		if err != nil {


Example of usage of this library. We read a single tag value for the XResolution tag and then print out all the Exif tags in the file under the Image File Directories. In this case we only have IFD0.

	fp, err := os.Open("testdata/sample1.tiff")
	if err != nil {
	defer fp.Close()
	var decoder exif.LazyDecoder
	err = decoder.Decode(fp)
	if err != nil {

	// Read a single tag from the decoded tags.
	xResTag, err := decoder.GetTag(fp, 0, exifid.XResolution)
	if err != nil {
		panic("compression tag not found")
	// One can also use the Rational, Float and Int methods to obtain 
	// statically typed tag values. The Value method returns interface{} type.
	fmt.Printf("the x resolution is %v", xResTag.Value())

	// Generate all tags of a certain constrained size using a callback.
	ifds, err := decoder.MakeIFDs(fp, func(ifd, size int, id exif.ID) bool {
		return size < 1024 // Tags less than a kilobyte in size.
	if err != nil {
	for _, ifd := range ifds {
		fmt.Printf("%s:\n", ifd.Group.String())
		for _, tag := range ifd.Tags {
			fmt.Println("\t" + strings.Trim(tag.String(), "\x00"))


the x resolution is 2000000/10000
	ImageWidth (uint32): 1728
	ImageHeight (uint32): 2376
	BitsPerSample (uint16): 1
	Compression (uint16): 4
	PhotometricInterpretation (uint16): 0
	FillOrder (uint16): 2
	DocumentName (string): Standard Input
	ImageDescription (string): converted PBM file
	StripOffsets (unknown): 8
	Orientation (uint16): 1
	SamplesPerPixel (uint16): 1
	RowsPerStrip (uint32): 2376
	StripByteCounts (unknown): 18112
	XResolution (rational): 2000000/10000
	YResolution (rational): 2000000/10000
	PlanarConfiguration (uint16): 1
	ResolutionUnit (uint16): 2



  • 1

    Compare to imagemeta?

    Hi -- I just found this repo and since it's focused on performance, I was wondering if you could add a benchmark comparing imagemeta as well. (I'm traveling currently or I'd just do it myself, but I'm also not familiar with your API yet.)

    I know that lib is also under active development, so maybe there's room to share ideas and even consolidate if that would make sense.