git.lucas.co / go_mono
git clone https://git.lucas.co/go_mono.git

commitcc3e6234e74d312b6ab508f5dec8c8ea619c04c4
parentf61fbb80d2
authorNigel Tao <[email protected]>
date2012-01-31 11:32
go.image: initial code.

Manual edits to README.
Moved from main Go repository, deleted Makefiles.

Tested with go test code.google.com/p/go.image/...

Fixes golang/go#2797.

R=rsc, rsc, r
CC=golang-dev
https://golang.org/cl/5593053

 README                                    |  17 +-
 bmp/reader.go                             | 152 +++++++++++
 tiff/buffer.go                            |  54 ++++
 tiff/buffer_test.go                       |  36 +++
 tiff/compress.go                          |  59 ++++
 tiff/consts.go                            | 103 +++++++
 tiff/reader.go                            | 430 ++++++++++++++++++++++++++++++
 tiff/reader_test.go                       | 119 +++++++++
 tiff/testdata/bw-deflate.tiff             | Bin 0 -> 594 bytes
 tiff/testdata/bw-packbits.tiff            | Bin 0 -> 890 bytes
 tiff/testdata/bw-uncompressed.tiff        | Bin 0 -> 1396 bytes
 tiff/testdata/no_rps.tiff                 | Bin 0 -> 1294 bytes
 tiff/testdata/video-001-uncompressed.tiff | Bin 0 -> 46674 bytes
 13 files changed, 955 insertions(+), 15 deletions(-)

diff --git a/README b/README
index bc954b0..4620380 100644
--- a/README
+++ b/README
@@ -1,16 +1,3 @@
-This is an empty Go subrepository.  To create a new subrepository,
-visit http://code.google.com/p/go/adminSource and under repositories,
-type the new name, check [x] Clone contents and select [empty] as
-the one you want to clone.
+This repository holds supplementary Go image libraries.
 
-Then execute:
-	go get code.google.com/p/go.newrepo
-	cd $(go list -e -f '{{.Dir}}' code.google.com/p/go.newrepo)
-
-The go get will complain about not finding source code, but it will
-successfully check out the repository.
-
-Edit the README (this file) to describe the new subrepository, and then
-use the usual hg change, mail, submit to send in the change.
-You will need to follow http://golang.org/doc/contribute.html#Code_review
-to enable the Code Review extension (pointing into your Go root).
+To submit changes to this repository, see http://golang.org/doc/contribute.html.
diff --git a/bmp/reader.go b/bmp/reader.go
new file mode 100644
index 0000000..58bd781
--- /dev/null
+++ b/bmp/reader.go
@@ -0,0 +1,152 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package bmp implements a BMP image decoder.
+//
+// The BMP specification is at http://www.digicamsoft.com/bmp/bmp.html.
+package bmp
+
+import (
+	"errors"
+	"image"
+	"image/color"
+	"io"
+)
+
+// ErrUnsupported means that the input BMP image uses a valid but unsupported
+// feature.
+var ErrUnsupported = errors.New("bmp: unsupported BMP image")
+
+func readUint16(b []byte) uint16 {
+	return uint16(b[0]) | uint16(b[1])<<8
+}
+
+func readUint32(b []byte) uint32 {
+	return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24
+}
+
+// decodePaletted reads an 8 bit-per-pixel BMP image from r.
+func decodePaletted(r io.Reader, c image.Config) (image.Image, error) {
+	var tmp [4]byte
+	paletted := image.NewPaletted(image.Rect(0, 0, c.Width, c.Height), c.ColorModel.(color.Palette))
+	// BMP images are stored bottom-up rather than top-down.
+	for y := c.Height - 1; y >= 0; y-- {
+		p := paletted.Pix[y*paletted.Stride : y*paletted.Stride+c.Width]
+		_, err := io.ReadFull(r, p)
+		if err != nil {
+			return nil, err
+		}
+		// Each row is 4-byte aligned.
+		if c.Width%4 != 0 {
+			_, err := io.ReadFull(r, tmp[:4-c.Width%4])
+			if err != nil {
+				return nil, err
+			}
+		}
+	}
+	return paletted, nil
+}
+
+// decodeRGBA reads a 24 bit-per-pixel BMP image from r.
+func decodeRGBA(r io.Reader, c image.Config) (image.Image, error) {
+	rgba := image.NewRGBA(image.Rect(0, 0, c.Width, c.Height))
+	// There are 3 bytes per pixel, and each row is 4-byte aligned.
+	b := make([]byte, (3*c.Width+3)&^3)
+	// BMP images are stored bottom-up rather than top-down.
+	for y := c.Height - 1; y >= 0; y-- {
+		_, err := io.ReadFull(r, b)
+		if err != nil {
+			return nil, err
+		}
+		p := rgba.Pix[y*rgba.Stride : y*rgba.Stride+c.Width*4]
+		for i, j := 0, 0; i < len(p); i, j = i+4, j+3 {
+			// BMP images are stored in BGR order rather than RGB order.
+			p[i+0] = b[j+2]
+			p[i+1] = b[j+1]
+			p[i+2] = b[j+0]
+			p[i+3] = 0xFF
+		}
+	}
+	return rgba, nil
+}
+
+// Decode reads a BMP image from r and returns it as an image.Image.
+// Limitation: The file must be 8 or 24 bits per pixel.
+func Decode(r io.Reader) (image.Image, error) {
+	c, err := DecodeConfig(r)
+	if err != nil {
+		return nil, err
+	}
+	if c.ColorModel == color.RGBAModel {
+		return decodeRGBA(r, c)
+	}
+	return decodePaletted(r, c)
+}
+
+// DecodeConfig returns the color model and dimensions of a BMP image without
+// decoding the entire image.
+// Limitation: The file must be 8 or 24 bits per pixel.
+func DecodeConfig(r io.Reader) (config image.Config, err error) {
+	// We only support those BMP images that are a BITMAPFILEHEADER
+	// immediately followed by a BITMAPINFOHEADER.
+	const (
+		fileHeaderLen = 14
+		infoHeaderLen = 40
+	)
+	var b [1024]byte
+	if _, err = io.ReadFull(r, b[:fileHeaderLen+infoHeaderLen]); err != nil {
+		return
+	}
+	if string(b[:2]) != "BM" {
+		err = errors.New("bmp: invalid format")
+		return
+	}
+	offset := readUint32(b[10:14])
+	if readUint32(b[14:18]) != infoHeaderLen {
+		err = ErrUnsupported
+		return
+	}
+	width := int(readUint32(b[18:22]))
+	height := int(readUint32(b[22:26]))
+	if width < 0 || height < 0 {
+		err = ErrUnsupported
+		return
+	}
+	// We only support 1 plane, 8 or 24 bits per pixel and no compression.
+	planes, bpp, compression := readUint16(b[26:28]), readUint16(b[28:30]), readUint32(b[30:34])
+	if planes != 1 || compression != 0 {
+		err = ErrUnsupported
+		return
+	}
+	switch bpp {
+	case 8:
+		if offset != fileHeaderLen+infoHeaderLen+256*4 {
+			err = ErrUnsupported
+			return
+		}
+		_, err = io.ReadFull(r, b[:256*4])
+		if err != nil {
+			return
+		}
+		pcm := make(color.Palette, 256)
+		for i := range pcm {
+			// BMP images are stored in BGR order rather than RGB order.
+			// Every 4th byte is padding.
+			pcm[i] = color.RGBA{b[4*i+2], b[4*i+1], b[4*i+0], 0xFF}
+		}
+		return image.Config{pcm, width, height}, nil
+	case 24:
+		if offset != fileHeaderLen+infoHeaderLen {
+			err = ErrUnsupported
+			return
+		}
+		return image.Config{color.RGBAModel, width, height}, nil
+	}
+	err = ErrUnsupported
+	return
+}
+
+func init() {
+	image.RegisterFormat("bmp", "BM????\x00\x00\x00\x00", Decode, DecodeConfig)
+}
diff --git a/tiff/buffer.go b/tiff/buffer.go
new file mode 100644
index 0000000..27533c6
--- /dev/null
+++ b/tiff/buffer.go
@@ -0,0 +1,54 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package tiff
+
+import "io"
+
+// buffer buffers an io.Reader to satisfy io.ReaderAt.
+type buffer struct {
+	r   io.Reader
+	buf []byte
+}
+
+func (b *buffer) ReadAt(p []byte, off int64) (int, error) {
+	o := int(off)
+	end := o + len(p)
+	if int64(end) != off+int64(len(p)) {
+		return 0, io.ErrUnexpectedEOF
+	}
+
+	m := len(b.buf)
+	if end > m {
+		if end > cap(b.buf) {
+			newcap := 1024
+			for newcap < end {
+				newcap *= 2
+			}
+			newbuf := make([]byte, end, newcap)
+			copy(newbuf, b.buf)
+			b.buf = newbuf
+		} else {
+			b.buf = b.buf[:end]
+		}
+		if n, err := io.ReadFull(b.r, b.buf[m:end]); err != nil {
+			end = m + n
+			b.buf = b.buf[:end]
+			return copy(p, b.buf[o:end]), err
+		}
+	}
+
+	return copy(p, b.buf[o:end]), nil
+}
+
+// newReaderAt converts an io.Reader into an io.ReaderAt.
+func newReaderAt(r io.Reader) io.ReaderAt {
+	if ra, ok := r.(io.ReaderAt); ok {
+		return ra
+	}
+	return &buffer{
+		r:   r,
+		buf: make([]byte, 0, 1024),
+	}
+}
diff --git a/tiff/buffer_test.go b/tiff/buffer_test.go
new file mode 100644
index 0000000..e13afb3
--- /dev/null
+++ b/tiff/buffer_test.go
@@ -0,0 +1,36 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package tiff
+
+import (
+	"io"
+	"strings"
+	"testing"
+)
+
+var readAtTests = []struct {
+	n   int
+	off int64
+	s   string
+	err error
+}{
+	{2, 0, "ab", nil},
+	{6, 0, "abcdef", nil},
+	{3, 3, "def", nil},
+	{3, 5, "f", io.EOF},
+	{3, 6, "", io.EOF},
+}
+
+func TestReadAt(t *testing.T) {
+	r := newReaderAt(strings.NewReader("abcdef"))
+	b := make([]byte, 10)
+	for _, test := range readAtTests {
+		n, err := r.ReadAt(b[:test.n], test.off)
+		s := string(b[:n])
+		if s != test.s || err != test.err {
+			t.Errorf("buffer.ReadAt(<%v bytes>, %v): got %v, %q; want %v, %q", test.n, test.off, err, s, test.err, test.s)
+		}
+	}
+}
diff --git a/tiff/compress.go b/tiff/compress.go
new file mode 100644
index 0000000..40c7fd8
--- /dev/null
+++ b/tiff/compress.go
@@ -0,0 +1,59 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package tiff
+
+import (
+	"bufio"
+	"io"
+)
+
+type byteReader interface {
+	io.Reader
+	io.ByteReader
+}
+
+// unpackBits decodes the PackBits-compressed data in src and returns the
+// uncompressed data.
+//
+// The PackBits compression format is described in section 9 (p. 42)
+// of the TIFF spec.
+func unpackBits(r io.Reader) ([]byte, error) {
+	buf := make([]byte, 128)
+	dst := make([]byte, 0, 1024)
+	br, ok := r.(byteReader)
+	if !ok {
+		br = bufio.NewReader(r)
+	}
+
+	for {
+		b, err := br.ReadByte()
+		if err != nil {
+			if err == io.EOF {
+				return dst, nil
+			}
+			return nil, err
+		}
+		code := int(int8(b))
+		switch {
+		case code >= 0:
+			n, err := io.ReadFull(br, buf[:code+1])
+			if err != nil {
+				return nil, err
+			}
+			dst = append(dst, buf[:n]...)
+		case code == -128:
+			// No-op.
+		default:
+			if b, err = br.ReadByte(); err != nil {
+				return nil, err
+			}
+			for j := 0; j < 1-code; j++ {
+				buf[j] = b
+			}
+			dst = append(dst, buf[:1-code]...)
+		}
+	}
+	panic("unreachable")
+}
diff --git a/tiff/consts.go b/tiff/consts.go
new file mode 100644
index 0000000..169ba27
--- /dev/null
+++ b/tiff/consts.go
@@ -0,0 +1,103 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package tiff
+
+// A tiff image file contains one or more images. The metadata
+// of each image is contained in an Image File Directory (IFD),
+// which contains entries of 12 bytes each and is described
+// on page 14-16 of the specification. An IFD entry consists of
+//
+//  - a tag, which describes the signification of the entry,
+//  - the data type and length of the entry,
+//  - the data itself or a pointer to it if it is more than 4 bytes.
+//
+// The presence of a length means that each IFD is effectively an array.
+
+const (
+	leHeader = "II\x2A\x00" // Header for little-endian files.
+	beHeader = "MM\x00\x2A" // Header for big-endian files.
+
+	ifdLen = 12 // Length of an IFD entry in bytes.
+)
+
+// Data types (p. 14-16 of the spec).
+const (
+	dtByte     = 1
+	dtASCII    = 2
+	dtShort    = 3
+	dtLong     = 4
+	dtRational = 5
+)
+
+// The length of one instance of each data type in bytes.
+var lengths = [...]uint32{0, 1, 1, 2, 4, 8}
+
+// Tags (see p. 28-41 of the spec).
+const (
+	tImageWidth                = 256
+	tImageLength               = 257
+	tBitsPerSample             = 258
+	tCompression               = 259
+	tPhotometricInterpretation = 262
+
+	tStripOffsets    = 273
+	tSamplesPerPixel = 277
+	tRowsPerStrip    = 278
+	tStripByteCounts = 279
+
+	tXResolution    = 282
+	tYResolution    = 283
+	tResolutionUnit = 296
+
+	tPredictor    = 317
+	tColorMap     = 320
+	tExtraSamples = 338
+	tSampleFormat = 339
+)
+
+// Compression types (defined in various places in the spec and supplements).
+const (
+	cNone       = 1
+	cCCITT      = 2
+	cG3         = 3 // Group 3 Fax.
+	cG4         = 4 // Group 4 Fax.
+	cLZW        = 5
+	cJPEGOld    = 6 // Superseded by cJPEG.
+	cJPEG       = 7
+	cDeflate    = 8 // zlib compression.
+	cPackBits   = 32773
+	cDeflateOld = 32946 // Superseded by cDeflate.
+)
+
+// Photometric interpretation values (see p. 37 of the spec).
+const (
+	pWhiteIsZero = 0
+	pBlackIsZero = 1
+	pRGB         = 2
+	pPaletted    = 3
+	pTransMask   = 4 // transparency mask
+	pCMYK        = 5
+	pYCbCr       = 6
+	pCIELab      = 8
+)
+
+// Values for the tPredictor tag (page 64-65 of the spec).
+const (
+	prNone       = 1
+	prHorizontal = 2
+)
+
+// imageMode represents the mode of the image.
+type imageMode int
+
+const (
+	mBilevel imageMode = iota
+	mPaletted
+	mGray
+	mGrayInvert
+	mRGB
+	mRGBA
+	mNRGBA
+)
diff --git a/tiff/reader.go b/tiff/reader.go
new file mode 100644
index 0000000..dc5a87a
--- /dev/null
+++ b/tiff/reader.go
@@ -0,0 +1,430 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package tiff implements a TIFF image decoder.
+//
+// The TIFF specification is at http://partners.adobe.com/public/developer/en/tiff/TIFF6.pdf
+package tiff
+
+import (
+	"compress/lzw"
+	"compress/zlib"
+	"encoding/binary"
+	"image"
+	"image/color"
+	"io"
+	"io/ioutil"
+)
+
+// A FormatError reports that the input is not a valid TIFF image.
+type FormatError string
+
+func (e FormatError) Error() string {
+	return "tiff: invalid format: " + string(e)
+}
+
+// An UnsupportedError reports that the input uses a valid but
+// unimplemented feature.
+type UnsupportedError string
+
+func (e UnsupportedError) Error() string {
+	return "tiff: unsupported feature: " + string(e)
+}
+
+// An InternalError reports that an internal error was encountered.
+type InternalError string
+
+func (e InternalError) Error() string {
+	return "tiff: internal error: " + string(e)
+}
+
+type decoder struct {
+	r         io.ReaderAt
+	byteOrder binary.ByteOrder
+	config    image.Config
+	mode      imageMode
+	features  map[int][]uint
+	palette   []color.Color
+
+	buf   []byte
+	off   int    // Current offset in buf.
+	v     uint32 // Buffer value for reading with arbitrary bit depths.
+	nbits uint   // Remaining number of bits in v.
+}
+
+// firstVal returns the first uint of the features entry with the given tag,
+// or 0 if the tag does not exist.
+func (d *decoder) firstVal(tag int) uint {
+	f := d.features[tag]
+	if len(f) == 0 {
+		return 0
+	}
+	return f[0]
+}
+
+// ifdUint decodes the IFD entry in p, which must be of the Byte, Short
+// or Long type, and returns the decoded uint values.
+func (d *decoder) ifdUint(p []byte) (u []uint, err error) {
+	var raw []byte
+	datatype := d.byteOrder.Uint16(p[2:4])
+	count := d.byteOrder.Uint32(p[4:8])
+	if datalen := lengths[datatype] * count; datalen > 4 {
+		// The IFD contains a pointer to the real value.
+		raw = make([]byte, datalen)
+		_, err = d.r.ReadAt(raw, int64(d.byteOrder.Uint32(p[8:12])))
+	} else {
+		raw = p[8 : 8+datalen]
+	}
+	if err != nil {
+		return nil, err
+	}
+
+	u = make([]uint, count)
+	switch datatype {
+	case dtByte:
+		for i := uint32(0); i < count; i++ {
+			u[i] = uint(raw[i])
+		}
+	case dtShort:
+		for i := uint32(0); i < count; i++ {
+			u[i] = uint(d.byteOrder.Uint16(raw[2*i : 2*(i+1)]))
+		}
+	case dtLong:
+		for i := uint32(0); i < count; i++ {
+			u[i] = uint(d.byteOrder.Uint32(raw[4*i : 4*(i+1)]))
+		}
+	default:
+		return nil, UnsupportedError("data type")
+	}
+	return u, nil
+}
+
+// parseIFD decides whether the the IFD entry in p is "interesting" and
+// stows away the data in the decoder.
+func (d *decoder) parseIFD(p []byte) error {
+	tag := d.byteOrder.Uint16(p[0:2])
+	switch tag {
+	case tBitsPerSample,
+		tExtraSamples,
+		tPhotometricInterpretation,
+		tCompression,
+		tPredictor,
+		tStripOffsets,
+		tStripByteCounts,
+		tRowsPerStrip,
+		tImageLength,
+		tImageWidth:
+		val, err := d.ifdUint(p)
+		if err != nil {
+			return err
+		}
+		d.features[int(tag)] = val
+	case tColorMap:
+		val, err := d.ifdUint(p)
+		if err != nil {
+			return err
+		}
+		numcolors := len(val) / 3
+		if len(val)%3 != 0 || numcolors <= 0 || numcolors > 256 {
+			return FormatError("bad ColorMap length")
+		}
+		d.palette = make([]color.Color, numcolors)
+		for i := 0; i < numcolors; i++ {
+			d.palette[i] = color.RGBA64{
+				uint16(val[i]),
+				uint16(val[i+numcolors]),
+				uint16(val[i+2*numcolors]),
+				0xffff,
+			}
+		}
+	case tSampleFormat:
+		// Page 27 of the spec: If the SampleFormat is present and
+		// the value is not 1 [= unsigned integer data], a Baseline
+		// TIFF reader that cannot handle the SampleFormat value
+		// must terminate the import process gracefully.
+		val, err := d.ifdUint(p)
+		if err != nil {
+			return err
+		}
+		for _, v := range val {
+			if v != 1 {
+				return UnsupportedError("sample format")
+			}
+		}
+	}
+	return nil
+}
+
+// readBits reads n bits from the internal buffer starting at the current offset.
+func (d *decoder) readBits(n uint) uint32 {
+	for d.nbits < n {
+		d.v <<= 8
+		d.v |= uint32(d.buf[d.off])
+		d.off++
+		d.nbits += 8
+	}
+	d.nbits -= n
+	rv := d.v >> d.nbits
+	d.v &^= rv << d.nbits
+	return rv
+}
+
+// flushBits discards the unread bits in the buffer used by readBits.
+// It is used at the end of a line.
+func (d *decoder) flushBits() {
+	d.v = 0
+	d.nbits = 0
+}
+
+// decode decodes the raw data of an image.
+// It reads from d.buf and writes the strip with ymin <= y < ymax into dst.
+func (d *decoder) decode(dst image.Image, ymin, ymax int) error {
+	d.off = 0
+
+	// Apply horizontal predictor if necessary.
+	// In this case, p contains the color difference to the preceding pixel.
+	// See page 64-65 of the spec.
+	if d.firstVal(tPredictor) == prHorizontal && d.firstVal(tBitsPerSample) == 8 {
+		var off int
+		spp := len(d.features[tBitsPerSample]) // samples per pixel
+		for y := ymin; y < ymax; y++ {
+			off += spp
+			for x := 0; x < (dst.Bounds().Dx()-1)*spp; x++ {
+				d.buf[off] += d.buf[off-spp]
+				off++
+			}
+		}
+	}
+
+	switch d.mode {
+	case mGray, mGrayInvert:
+		img := dst.(*image.Gray)
+		bpp := d.firstVal(tBitsPerSample)
+		max := uint32((1 << bpp) - 1)
+		for y := ymin; y < ymax; y++ {
+			for x := img.Rect.Min.X; x < img.Rect.Max.X; x++ {
+				v := uint8(d.readBits(bpp) * 0xff / max)
+				if d.mode == mGrayInvert {
+					v = 0xff - v
+				}
+				img.SetGray(x, y, color.Gray{v})
+			}
+			d.flushBits()
+		}
+	case mPaletted:
+		img := dst.(*image.Paletted)
+		bpp := d.firstVal(tBitsPerSample)
+		for y := ymin; y < ymax; y++ {
+			for x := img.Rect.Min.X; x < img.Rect.Max.X; x++ {
+				img.SetColorIndex(x, y, uint8(d.readBits(bpp)))
+			}
+			d.flushBits()
+		}
+	case mRGB:
+		img := dst.(*image.RGBA)
+		min := img.PixOffset(0, ymin)
+		max := img.PixOffset(0, ymax)
+		var off int
+		for i := min; i < max; i += 4 {
+			img.Pix[i+0] = d.buf[off+0]
+			img.Pix[i+1] = d.buf[off+1]
+			img.Pix[i+2] = d.buf[off+2]
+			img.Pix[i+3] = 0xff
+			off += 3
+		}
+	case mNRGBA:
+		img := dst.(*image.NRGBA)
+		min := img.PixOffset(0, ymin)
+		max := img.PixOffset(0, ymax)
+		if len(d.buf) != max-min {
+			return FormatError("short data strip")
+		}
+		copy(img.Pix[min:max], d.buf)
+	case mRGBA:
+		img := dst.(*image.RGBA)
+		min := img.PixOffset(0, ymin)
+		max := img.PixOffset(0, ymax)
+		if len(d.buf) != max-min {
+			return FormatError("short data strip")
+		}
+		copy(img.Pix[min:max], d.buf)
+	}
+
+	return nil
+}
+
+func newDecoder(r io.Reader) (*decoder, error) {
+	d := &decoder{
+		r:        newReaderAt(r),
+		features: make(map[int][]uint),
+	}
+
+	p := make([]byte, 8)
+	if _, err := d.r.ReadAt(p, 0); err != nil {
+		return nil, err
+	}
+	switch string(p[0:4]) {
+	case leHeader:
+		d.byteOrder = binary.LittleEndian
+	case beHeader:
+		d.byteOrder = binary.BigEndian
+	default:
+		return nil, FormatError("malformed header")
+	}
+
+	ifdOffset := int64(d.byteOrder.Uint32(p[4:8]))
+
+	// The first two bytes contain the number of entries (12 bytes each).
+	if _, err := d.r.ReadAt(p[0:2], ifdOffset); err != nil {
+		return nil, err
+	}
+	numItems := int(d.byteOrder.Uint16(p[0:2]))
+
+	// All IFD entries are read in one chunk.
+	p = make([]byte, ifdLen*numItems)
+	if _, err := d.r.ReadAt(p, ifdOffset+2); err != nil {
+		return nil, err
+	}
+
+	for i := 0; i < len(p); i += ifdLen {
+		if err := d.parseIFD(p[i : i+ifdLen]); err != nil {
+			return nil, err
+		}
+	}
+
+	d.config.Width = int(d.firstVal(tImageWidth))
+	d.config.Height = int(d.firstVal(tImageLength))
+
+	if _, ok := d.features[tBitsPerSample]; !ok {
+		return nil, FormatError("BitsPerSample tag missing")
+	}
+
+	// Determine the image mode.
+	switch d.firstVal(tPhotometricInterpretation) {
+	case pRGB:
+		for _, b := range d.features[tBitsPerSample] {
+			if b != 8 {
+				return nil, UnsupportedError("non-8-bit RGB image")
+			}
+		}
+		d.config.ColorModel = color.RGBAModel
+		// RGB images normally have 3 samples per pixel.
+		// If there are more, ExtraSamples (p. 31-32 of the spec)
+		// gives their meaning (usually an alpha channel).
+		//
+		// This implementation does not support extra samples
+		// of an unspecified type.
+		switch len(d.features[tBitsPerSample]) {
+		case 3:
+			d.mode = mRGB
+		case 4:
+			switch d.firstVal(tExtraSamples) {
+			case 1:
+				d.mode = mRGBA
+			case 2:
+				d.mode = mNRGBA
+				d.config.ColorModel = color.NRGBAModel
+			default:
+				return nil, FormatError("wrong number of samples for RGB")
+			}
+		default:
+			return nil, FormatError("wrong number of samples for RGB")
+		}
+	case pPaletted:
+		d.mode = mPaletted
+		d.config.ColorModel = color.Palette(d.palette)
+	case pWhiteIsZero:
+		d.mode = mGrayInvert
+		d.config.ColorModel = color.GrayModel
+	case pBlackIsZero:
+		d.mode = mGray
+		d.config.ColorModel = color.GrayModel
+	default:
+		return nil, UnsupportedError("color model")
+	}
+
+	return d, nil
+}
+
+// DecodeConfig returns the color model and dimensions of a TIFF image without
+// decoding the entire image.
+func DecodeConfig(r io.Reader) (image.Config, error) {
+	d, err := newDecoder(r)
+	if err != nil {
+		return image.Config{}, err
+	}
+	return d.config, nil
+}
+
+// Decode reads a TIFF image from r and returns it as an image.Image.
+// The type of Image returned depends on the contents of the TIFF.
+func Decode(r io.Reader) (img image.Image, err error) {
+	d, err := newDecoder(r)
+	if err != nil {
+		return
+	}
+
+	// Check if we have the right number of strips, offsets and counts.
+	rps := int(d.firstVal(tRowsPerStrip))
+	if rps == 0 {
+		// Assume only one strip.
+		rps = d.config.Height
+	}
+	numStrips := (d.config.Height + rps - 1) / rps
+	if rps == 0 || len(d.features[tStripOffsets]) < numStrips || len(d.features[tStripByteCounts]) < numStrips {
+		return nil, FormatError("inconsistent header")
+	}
+
+	switch d.mode {
+	case mGray, mGrayInvert:
+		img = image.NewGray(image.Rect(0, 0, d.config.Width, d.config.Height))
+	case mPaletted:
+		img = image.NewPaletted(image.Rect(0, 0, d.config.Width, d.config.Height), d.palette)
+	case mNRGBA:
+		img = image.NewNRGBA(image.Rect(0, 0, d.config.Width, d.config.Height))
+	case mRGB, mRGBA:
+		img = image.NewRGBA(image.Rect(0, 0, d.config.Width, d.config.Height))
+	}
+
+	for i := 0; i < numStrips; i++ {
+		ymin := i * rps
+		// The last strip may be shorter.
+		if i == numStrips-1 && d.config.Height%rps != 0 {
+			rps = d.config.Height % rps
+		}
+		offset := int64(d.features[tStripOffsets][i])
+		n := int64(d.features[tStripByteCounts][i])
+		switch d.firstVal(tCompression) {
+		case cNone:
+			// TODO(bsiegert): Avoid copy if r is a tiff.buffer.
+			d.buf = make([]byte, n)
+			_, err = d.r.ReadAt(d.buf, offset)
+		case cLZW:
+			r := lzw.NewReader(io.NewSectionReader(d.r, offset, n), lzw.MSB, 8)
+			d.buf, err = ioutil.ReadAll(r)
+			r.Close()
+		case cDeflate, cDeflateOld:
+			r, err := zlib.NewReader(io.NewSectionReader(d.r, offset, n))
+			if err != nil {
+				return nil, err
+			}
+			d.buf, err = ioutil.ReadAll(r)
+			r.Close()
+		case cPackBits:
+			d.buf, err = unpackBits(io.NewSectionReader(d.r, offset, n))
+		default:
+			err = UnsupportedError("compression")
+		}
+		if err != nil {
+			return
+		}
+		err = d.decode(img, ymin, ymin+rps)
+	}
+	return
+}
+
+func init() {
+	image.RegisterFormat("tiff", leHeader, Decode, DecodeConfig)
+	image.RegisterFormat("tiff", beHeader, Decode, DecodeConfig)
+}
diff --git a/tiff/reader_test.go b/tiff/reader_test.go
new file mode 100644
index 0000000..ee5dafd
--- /dev/null
+++ b/tiff/reader_test.go
@@ -0,0 +1,119 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package tiff
+
+import (
+	"image"
+	"io/ioutil"
+	"os"
+	"strings"
+	"testing"
+)
+
+// Read makes *buffer implements io.Reader, so that we can pass one to Decode.
+func (*buffer) Read([]byte) (int, error) {
+	panic("unimplemented")
+}
+
+// TestNoRPS tries to decode an image that has no RowsPerStrip tag.
+// The tag is mandatory according to the spec but some software omits
+// it in the case of a single strip.
+func TestNoRPS(t *testing.T) {
+	f, err := os.Open("testdata/no_rps.tiff")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer f.Close()
+	_, err = Decode(f)
+	if err != nil {
+		t.Fatal(err)
+	}
+}
+
+// TestUnpackBits tests the decoding of PackBits-encoded data.
+func TestUnpackBits(t *testing.T) {
+	var unpackBitsTests = []struct {
+		compressed   string
+		uncompressed string
+	}{{
+		// Example data from Wikipedia.
+		"\xfe\xaa\x02\x80\x00\x2a\xfd\xaa\x03\x80\x00\x2a\x22\xf7\xaa",
+		"\xaa\xaa\xaa\x80\x00\x2a\xaa\xaa\xaa\xaa\x80\x00\x2a\x22\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa",
+	}}
+	for _, u := range unpackBitsTests {
+		buf, err := unpackBits(strings.NewReader(u.compressed))
+		if err != nil {
+			t.Fatal(err)
+		}
+		if string(buf) != u.uncompressed {
+			t.Fatalf("unpackBits: want %x, got %x", u.uncompressed, buf)
+		}
+	}
+}
+
+// TestDecompress tests that decoding some TIFF images that use different
+// compression formats result in the same pixel data.
+func TestDecompress(t *testing.T) {
+	var decompressTests = []string{
+		"bw-uncompressed.tiff",
+		"bw-deflate.tiff",
+		"bw-packbits.tiff",
+	}
+	var img0 image.Image
+	for _, name := range decompressTests {
+		f, err := os.Open("testdata/" + name)
+		if err != nil {
+			t.Fatal(err)
+		}
+		defer f.Close()
+		if img0 == nil {
+			img0, err = Decode(f)
+			if err != nil {
+				t.Fatalf("decoding %s: %v", name, err)
+			}
+			continue
+		}
+
+		img1, err := Decode(f)
+		if err != nil {
+			t.Fatalf("decoding %s: %v", name, err)
+		}
+		b := img1.Bounds()
+		// Compare images.
+		if !b.Eq(img0.Bounds()) {
+			t.Fatalf("wrong image size: want %s, got %s", img0.Bounds(), b)
+		}
+		for y := b.Min.Y; y < b.Max.Y; y++ {
+			for x := b.Min.X; x < b.Max.X; x++ {
+				c0 := img0.At(x, y)
+				c1 := img1.At(x, y)
+				r0, g0, b0, a0 := c0.RGBA()
+				r1, g1, b1, a1 := c1.RGBA()
+				if r0 != r1 || g0 != g1 || b0 != b1 || a0 != a1 {
+					t.Fatalf("pixel at (%d, %d) has wrong color: want %v, got %v", x, y, c0, c1)
+				}
+			}
+		}
+	}
+}
+
+const filename = "testdata/video-001-uncompressed.tiff"
+
+// BenchmarkDecode benchmarks the decoding of an image.
+func BenchmarkDecode(b *testing.B) {
+	b.StopTimer()
+	contents, err := ioutil.ReadFile(filename)
+	if err != nil {
+		panic(err)
+	}
+	r := &buffer{buf: contents}
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		_, err := Decode(r)
+		if err != nil {
+			b.Fatal("Decode:", err)
+		}
+	}
+}
diff --git a/tiff/testdata/bw-deflate.tiff b/tiff/testdata/bw-deflate.tiff
new file mode 100644
index 0000000..137a0c3
Binary files /dev/null and b/tiff/testdata/bw-deflate.tiff differ
diff --git a/tiff/testdata/bw-packbits.tiff b/tiff/testdata/bw-packbits.tiff
new file mode 100644
index 0000000..d59fa4a
Binary files /dev/null and b/tiff/testdata/bw-packbits.tiff differ
diff --git a/tiff/testdata/bw-uncompressed.tiff b/tiff/testdata/bw-uncompressed.tiff
new file mode 100644
index 0000000..8390f11
Binary files /dev/null and b/tiff/testdata/bw-uncompressed.tiff differ
diff --git a/tiff/testdata/no_rps.tiff b/tiff/testdata/no_rps.tiff
new file mode 100644
index 0000000..3280cf8
Binary files /dev/null and b/tiff/testdata/no_rps.tiff differ
diff --git a/tiff/testdata/video-001-uncompressed.tiff b/tiff/testdata/video-001-uncompressed.tiff
new file mode 100644
index 0000000..fad1471
Binary files /dev/null and b/tiff/testdata/video-001-uncompressed.tiff differ