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

commitd16520444260fa986629b6a4b6efb0d411f6269d
parent41cf08abcf
authorBenny Siegert <[email protected]>
date2012-12-12 13:24
go.image/tiff: support for writing compressed images.

Only Deflate compression is supported for now.

R=nigeltao, mikioh.mikioh
CC=golang-dev
https://golang.org/cl/6866051

 tiff/consts.go      | 13 ++++++++++-
 tiff/writer.go      | 65 +++++++++++++++++++++++++++++++++++++----------------
 tiff/writer_test.go |  2 ++
 3 files changed, 60 insertions(+), 20 deletions(-)

diff --git a/tiff/consts.go b/tiff/consts.go
index 3b0a865..772e98a 100644
--- a/tiff/consts.go
+++ b/tiff/consts.go
@@ -109,9 +109,20 @@ const (
 	mNRGBA
 )
 
-// Compression describes the type of compression used in Options.
+// CompressionType describes the type of compression used in Options.
 type CompressionType int
 
 const (
 	Uncompressed CompressionType = iota
+	Deflate
 )
+
+// specValue returns the compression type constant from the TIFF spec that
+// is equivalent to c.
+func (c CompressionType) specValue() uint32 {
+	switch c {
+	case Deflate:
+		return cDeflate
+	}
+	return cNone
+}
diff --git a/tiff/writer.go b/tiff/writer.go
index 0efc06f..274b115 100644
--- a/tiff/writer.go
+++ b/tiff/writer.go
@@ -5,6 +5,8 @@
 package tiff
 
 import (
+	"bytes"
+	"compress/zlib"
 	"encoding/binary"
 	"image"
 	"io"
@@ -179,57 +181,82 @@ type Options struct {
 // image is written.
 func Encode(w io.Writer, m image.Image, opt *Options) error {
 	predictor := false
-	compression := Uncompressed
+	compression := uint32(cNone)
 	if opt != nil {
 		predictor = opt.Predictor
-		compression = opt.Compression
-	}
-	if compression != Uncompressed {
-		return UnsupportedError("compression type")
+		compression = opt.Compression.specValue()
 	}
 
-	var extrasamples uint32
 	_, err := io.WriteString(w, leHeader)
 	if err != nil {
 		return err
 	}
 
+	// Compressed data is written into a buffer first, so that we
+	// know the compressed size.
+	var buf bytes.Buffer
+	// dst holds the destination for the pixel data of the image --
+	// either w or a writer to buf.
+	var dst io.Writer
+	// imageLen is the length of the pixel data in bytes.
+	// The offset of the IFD is imageLen + 8 header bytes.
+	var imageLen int
 	bounds := m.Bounds()
 	width, height := bounds.Dx(), bounds.Dy()
-	// imageLen is the length of the image data in bytes.
-	imageLen := width * height * 4
-	ifdOffset := imageLen + 8 // 8 bytes for TIFF header.
-	err = binary.Write(w, enc, uint32(ifdOffset))
-	if err != nil {
-		return err
+
+	switch compression {
+	case cNone:
+		dst = w
+		// Write IFD offset before outputting pixel data.
+		imageLen = width * height * 4
+		err = binary.Write(w, enc, uint32(imageLen+8))
+		if err != nil {
+			return err
+		}
+	case cDeflate:
+		dst = zlib.NewWriter(&buf)
 	}
+
 	var pr uint32 = prNone
-	extrasamples = 1 // Associated alpha (default).
+	var extrasamples uint32 = 1 // Associated alpha (default).
 	if predictor {
 		pr = prHorizontal
-		err = writeImgData(w, m, predictor)
+		err = writeImgData(dst, m, predictor)
 	} else {
 		switch img := m.(type) {
 		case *image.NRGBA:
 			extrasamples = 2 // Unassociated alpha.
 			off := img.PixOffset(img.Rect.Min.X, img.Rect.Min.Y)
-			err = writePix(w, img.Pix[off:], img.Rect.Dy(), 4*img.Rect.Dx(), img.Stride)
+			err = writePix(dst, img.Pix[off:], img.Rect.Dy(), 4*img.Rect.Dx(), img.Stride)
 		case *image.RGBA:
 			off := img.PixOffset(img.Rect.Min.X, img.Rect.Min.Y)
-			err = writePix(w, img.Pix[off:], img.Rect.Dy(), 4*img.Rect.Dx(), img.Stride)
+			err = writePix(dst, img.Pix[off:], img.Rect.Dy(), 4*img.Rect.Dx(), img.Stride)
 		default:
-			err = writeImgData(w, m, predictor)
+			err = writeImgData(dst, m, predictor)
 		}
 	}
 	if err != nil {
 		return err
 	}
 
-	return writeIFD(w, ifdOffset, []ifdEntry{
+	if compression != cNone {
+		if err = dst.(io.Closer).Close(); err != nil {
+			return err
+		}
+		imageLen = buf.Len()
+		if err = binary.Write(w, enc, uint32(imageLen+8)); err != nil {
+			return err
+		}
+		if _, err = buf.WriteTo(w); err != nil {
+			return err
+		}
+	}
+
+	return writeIFD(w, imageLen+8, []ifdEntry{
 		{tImageWidth, dtShort, []uint32{uint32(width)}},
 		{tImageLength, dtShort, []uint32{uint32(height)}},
 		{tBitsPerSample, dtShort, []uint32{8, 8, 8, 8}},
-		{tCompression, dtShort, []uint32{cNone}},
+		{tCompression, dtShort, []uint32{compression}},
 		{tPhotometricInterpretation, dtShort, []uint32{pRGB}},
 		{tStripOffsets, dtLong, []uint32{8}},
 		{tSamplesPerPixel, dtShort, []uint32{4}},
diff --git a/tiff/writer_test.go b/tiff/writer_test.go
index a82ae06..500a446 100644
--- a/tiff/writer_test.go
+++ b/tiff/writer_test.go
@@ -19,6 +19,8 @@ var roundtripTests = []struct {
 	{"video-001.tiff", nil},
 	{"bw-packbits.tiff", nil},
 	{"video-001.tiff", &Options{Predictor: true}},
+	{"video-001.tiff", &Options{Compression: Deflate}},
+	{"video-001.tiff", &Options{Predictor: true, Compression: Deflate}},
 }
 
 func openImage(filename string) (image.Image, error) {