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
This commit is contained in:
Benny Siegert 2012-12-12 13:24:12 +11:00 committed by Nigel Tao
parent 41cf08abcf
commit d165204442
3 changed files with 60 additions and 20 deletions

View File

@ -109,9 +109,20 @@ const (
mNRGBA mNRGBA
) )
// Compression describes the type of compression used in Options. // CompressionType describes the type of compression used in Options.
type CompressionType int type CompressionType int
const ( const (
Uncompressed CompressionType = iota 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
}

View File

@ -5,6 +5,8 @@
package tiff package tiff
import ( import (
"bytes"
"compress/zlib"
"encoding/binary" "encoding/binary"
"image" "image"
"io" "io"
@ -179,57 +181,82 @@ type Options struct {
// image is written. // image is written.
func Encode(w io.Writer, m image.Image, opt *Options) error { func Encode(w io.Writer, m image.Image, opt *Options) error {
predictor := false predictor := false
compression := Uncompressed compression := uint32(cNone)
if opt != nil { if opt != nil {
predictor = opt.Predictor predictor = opt.Predictor
compression = opt.Compression compression = opt.Compression.specValue()
}
if compression != Uncompressed {
return UnsupportedError("compression type")
} }
var extrasamples uint32
_, err := io.WriteString(w, leHeader) _, err := io.WriteString(w, leHeader)
if err != nil { if err != nil {
return err 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() bounds := m.Bounds()
width, height := bounds.Dx(), bounds.Dy() width, height := bounds.Dx(), bounds.Dy()
// imageLen is the length of the image data in bytes.
imageLen := width * height * 4 switch compression {
ifdOffset := imageLen + 8 // 8 bytes for TIFF header. case cNone:
err = binary.Write(w, enc, uint32(ifdOffset)) dst = w
// Write IFD offset before outputting pixel data.
imageLen = width * height * 4
err = binary.Write(w, enc, uint32(imageLen+8))
if err != nil { if err != nil {
return err return err
} }
case cDeflate:
dst = zlib.NewWriter(&buf)
}
var pr uint32 = prNone var pr uint32 = prNone
extrasamples = 1 // Associated alpha (default). var extrasamples uint32 = 1 // Associated alpha (default).
if predictor { if predictor {
pr = prHorizontal pr = prHorizontal
err = writeImgData(w, m, predictor) err = writeImgData(dst, m, predictor)
} else { } else {
switch img := m.(type) { switch img := m.(type) {
case *image.NRGBA: case *image.NRGBA:
extrasamples = 2 // Unassociated alpha. extrasamples = 2 // Unassociated alpha.
off := img.PixOffset(img.Rect.Min.X, img.Rect.Min.Y) 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: case *image.RGBA:
off := img.PixOffset(img.Rect.Min.X, img.Rect.Min.Y) 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: default:
err = writeImgData(w, m, predictor) err = writeImgData(dst, m, predictor)
} }
} }
if err != nil { if err != nil {
return err 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)}}, {tImageWidth, dtShort, []uint32{uint32(width)}},
{tImageLength, dtShort, []uint32{uint32(height)}}, {tImageLength, dtShort, []uint32{uint32(height)}},
{tBitsPerSample, dtShort, []uint32{8, 8, 8, 8}}, {tBitsPerSample, dtShort, []uint32{8, 8, 8, 8}},
{tCompression, dtShort, []uint32{cNone}}, {tCompression, dtShort, []uint32{compression}},
{tPhotometricInterpretation, dtShort, []uint32{pRGB}}, {tPhotometricInterpretation, dtShort, []uint32{pRGB}},
{tStripOffsets, dtLong, []uint32{8}}, {tStripOffsets, dtLong, []uint32{8}},
{tSamplesPerPixel, dtShort, []uint32{4}}, {tSamplesPerPixel, dtShort, []uint32{4}},

View File

@ -19,6 +19,8 @@ var roundtripTests = []struct {
{"video-001.tiff", nil}, {"video-001.tiff", nil},
{"bw-packbits.tiff", nil}, {"bw-packbits.tiff", nil},
{"video-001.tiff", &Options{Predictor: true}}, {"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) { func openImage(filename string) (image.Image, error) {