summaryrefslogtreecommitdiff
path: root/src/pkg/image/tiff
diff options
context:
space:
mode:
Diffstat (limited to 'src/pkg/image/tiff')
-rw-r--r--src/pkg/image/tiff/Makefile13
-rw-r--r--src/pkg/image/tiff/buffer.go57
-rw-r--r--src/pkg/image/tiff/buffer_test.go36
-rw-r--r--src/pkg/image/tiff/consts.go103
-rw-r--r--src/pkg/image/tiff/reader.go419
5 files changed, 628 insertions, 0 deletions
diff --git a/src/pkg/image/tiff/Makefile b/src/pkg/image/tiff/Makefile
new file mode 100644
index 000000000..1a001afb9
--- /dev/null
+++ b/src/pkg/image/tiff/Makefile
@@ -0,0 +1,13 @@
+# 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.
+
+include ../../../Make.inc
+
+TARG=image/tiff
+GOFILES=\
+ buffer.go\
+ consts.go\
+ reader.go\
+
+include ../../../Make.pkg
diff --git a/src/pkg/image/tiff/buffer.go b/src/pkg/image/tiff/buffer.go
new file mode 100644
index 000000000..7c0714225
--- /dev/null
+++ b/src/pkg/image/tiff/buffer.go
@@ -0,0 +1,57 @@
+// 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"
+ "os"
+)
+
+// 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, os.Error) {
+ o := int(off)
+ end := o + len(p)
+ if int64(end) != off+int64(len(p)) {
+ return 0, os.EINVAL
+ }
+
+ 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/src/pkg/image/tiff/buffer_test.go b/src/pkg/image/tiff/buffer_test.go
new file mode 100644
index 000000000..4f3e68e83
--- /dev/null
+++ b/src/pkg/image/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 (
+ "os"
+ "strings"
+ "testing"
+)
+
+var readAtTests = []struct {
+ n int
+ off int64
+ s string
+ err os.Error
+}{
+ {2, 0, "ab", nil},
+ {6, 0, "abcdef", nil},
+ {3, 3, "def", nil},
+ {3, 5, "f", os.EOF},
+ {3, 6, "", os.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/src/pkg/image/tiff/consts.go b/src/pkg/image/tiff/consts.go
new file mode 100644
index 000000000..169ba2772
--- /dev/null
+++ b/src/pkg/image/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/src/pkg/image/tiff/reader.go b/src/pkg/image/tiff/reader.go
new file mode 100644
index 000000000..26e52144d
--- /dev/null
+++ b/src/pkg/image/tiff/reader.go
@@ -0,0 +1,419 @@
+// 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"
+ "io"
+ "io/ioutil"
+ "os"
+)
+
+// A FormatError reports that the input is not a valid TIFF image.
+type FormatError string
+
+func (e FormatError) String() 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) String() string {
+ return "tiff: unsupported feature: " + string(e)
+}
+
+// An InternalError reports that an internal error was encountered.
+type InternalError string
+
+func (e InternalError) String() 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 []image.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 os.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) os.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([]image.Color, numcolors)
+ for i := 0; i < numcolors; i++ {
+ d.palette[i] = image.RGBA64Color{
+ 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) os.Error {
+ spp := len(d.features[tBitsPerSample]) // samples per pixel
+ d.off = 0
+ width := dst.Bounds().Dx()
+
+ // 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 {
+ for y := ymin; y < ymax; y++ {
+ d.off += spp
+ for x := 0; x < (width-1)*spp; x++ {
+ d.buf[d.off] += d.buf[d.off-spp]
+ d.off++
+ }
+ }
+ d.off = 0
+ }
+
+ 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, image.GrayColor{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)
+ for y := ymin; y < ymax; y++ {
+ for x := img.Rect.Min.X; x < img.Rect.Max.X; x++ {
+ img.SetRGBA(x, y, image.RGBAColor{d.buf[d.off], d.buf[d.off+1], d.buf[d.off+2], 0xff})
+ d.off += spp
+ }
+ }
+ case mNRGBA:
+ img := dst.(*image.NRGBA)
+ for y := ymin; y < ymax; y++ {
+ for x := img.Rect.Min.X; x < img.Rect.Max.X; x++ {
+ img.SetNRGBA(x, y, image.NRGBAColor{d.buf[d.off], d.buf[d.off+1], d.buf[d.off+2], d.buf[d.off+3]})
+ d.off += spp
+ }
+ }
+ case mRGBA:
+ img := dst.(*image.RGBA)
+ for y := ymin; y < ymax; y++ {
+ for x := img.Rect.Min.X; x < img.Rect.Max.X; x++ {
+ img.SetRGBA(x, y, image.RGBAColor{d.buf[d.off], d.buf[d.off+1], d.buf[d.off+2], d.buf[d.off+3]})
+ d.off += spp
+ }
+ }
+ }
+
+ return nil
+}
+
+func newDecoder(r io.Reader) (*decoder, os.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 = image.RGBAColorModel
+ // 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).
+ 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 = image.NRGBAColorModel
+ default:
+ // The extra sample is discarded.
+ d.mode = mRGB
+ }
+ default:
+ return nil, FormatError("wrong number of samples for RGB")
+ }
+ case pPaletted:
+ d.mode = mPaletted
+ d.config.ColorModel = image.PalettedColorModel(d.palette)
+ case pWhiteIsZero:
+ d.mode = mGrayInvert
+ d.config.ColorModel = image.GrayColorModel
+ case pBlackIsZero:
+ d.mode = mGray
+ d.config.ColorModel = image.GrayColorModel
+ 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, os.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 os.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))
+ 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(d.config.Width, d.config.Height)
+ case mPaletted:
+ img = image.NewPaletted(d.config.Width, d.config.Height, d.palette)
+ case mNRGBA:
+ img = image.NewNRGBA(d.config.Width, d.config.Height)
+ case mRGB, mRGBA:
+ img = image.NewRGBA(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()
+ 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)
+}