From 41cf08abcf6a0ca0d9b8f83fff1ee950af08a423 Mon Sep 17 00:00:00 2001 From: Benny Siegert Date: Wed, 3 Oct 2012 10:11:21 +1000 Subject: [PATCH] tiff: support writing files with differencing predictor. This will make more sense after implementing compression. R=nigeltao CC=golang-dev https://golang.org/cl/6567077 --- tiff/writer.go | 71 +++++++++++++++++++++++++++++++-------------- tiff/writer_test.go | 16 ++++++---- 2 files changed, 60 insertions(+), 27 deletions(-) diff --git a/tiff/writer.go b/tiff/writer.go index 37ba47f..0efc06f 100644 --- a/tiff/writer.go +++ b/tiff/writer.go @@ -53,18 +53,37 @@ func (d byTag) Len() int { return len(d) } func (d byTag) Less(i, j int) bool { return d[i].tag < d[j].tag } func (d byTag) Swap(i, j int) { d[i], d[j] = d[j], d[i] } -func writeImgData(w io.Writer, m image.Image) error { +// writeImgData writes the raw data of m into w, optionally using a +// differencing predictor. +func writeImgData(w io.Writer, m image.Image, predictor bool) error { bounds := m.Bounds() buf := make([]byte, 4*bounds.Dx()) for y := bounds.Min.Y; y < bounds.Max.Y; y++ { i := 0 - for x := bounds.Min.X; x < bounds.Max.X; x++ { - r, g, b, a := m.At(x, y).RGBA() - buf[i+0] = uint8(r >> 8) - buf[i+1] = uint8(g >> 8) - buf[i+2] = uint8(b >> 8) - buf[i+3] = uint8(a >> 8) - i += 4 + if predictor { + var r0, g0, b0, a0 uint8 + for x := bounds.Min.X; x < bounds.Max.X; x++ { + r, g, b, a := m.At(x, y).RGBA() + r1 := uint8(r >> 8) + g1 := uint8(g >> 8) + b1 := uint8(b >> 8) + a1 := uint8(a >> 8) + buf[i+0] = r1 - r0 + buf[i+1] = g1 - g0 + buf[i+2] = b1 - b0 + buf[i+3] = a1 - a0 + i += 4 + r0, g0, b0, a0 = r1, g1, b1, a1 + } + } else { + for x := bounds.Min.X; x < bounds.Max.X; x++ { + r, g, b, a := m.At(x, y).RGBA() + buf[i+0] = uint8(r >> 8) + buf[i+1] = uint8(g >> 8) + buf[i+2] = uint8(b >> 8) + buf[i+3] = uint8(a >> 8) + i += 4 + } } if _, err := w.Write(buf); err != nil { return err @@ -73,6 +92,9 @@ func writeImgData(w io.Writer, m image.Image) error { return nil } +// writePix writes the internal byte array of an image to w. It is less general +// but much faster then writeImgData. writePix is used when pix directly +// corresponds to one of the TIFF image types. func writePix(w io.Writer, pix []byte, nrows, length, stride int) error { if length == stride { _, err := w.Write(pix[:nrows*length]) @@ -162,7 +184,7 @@ func Encode(w io.Writer, m image.Image, opt *Options) error { predictor = opt.Predictor compression = opt.Compression } - if compression != Uncompressed || predictor { + if compression != Uncompressed { return UnsupportedError("compression type") } @@ -181,22 +203,28 @@ func Encode(w io.Writer, m image.Image, opt *Options) error { if err != nil { return err } - 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) - case *image.RGBA: - extrasamples = 1 // Associated 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) - default: - extrasamples = 1 // Associated alpha. - err = writeImgData(w, m) + var pr uint32 = prNone + extrasamples = 1 // Associated alpha (default). + if predictor { + pr = prHorizontal + err = writeImgData(w, 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) + 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) + default: + err = writeImgData(w, m, predictor) + } } if err != nil { return err } + return writeIFD(w, ifdOffset, []ifdEntry{ {tImageWidth, dtShort, []uint32{uint32(width)}}, {tImageLength, dtShort, []uint32{uint32(height)}}, @@ -212,6 +240,7 @@ func Encode(w io.Writer, m image.Image, opt *Options) error { {tXResolution, dtRational, []uint32{72, 1}}, {tYResolution, dtRational, []uint32{72, 1}}, {tResolutionUnit, dtShort, []uint32{resPerInch}}, + {tPredictor, dtShort, []uint32{pr}}, {tExtraSamples, dtShort, []uint32{extrasamples}}, }) } diff --git a/tiff/writer_test.go b/tiff/writer_test.go index f8d809e..a82ae06 100644 --- a/tiff/writer_test.go +++ b/tiff/writer_test.go @@ -12,9 +12,13 @@ import ( "testing" ) -var roundtripTests = []string{ - "video-001.tiff", - "bw-packbits.tiff", +var roundtripTests = []struct { + filename string + opts *Options +}{ + {"video-001.tiff", nil}, + {"bw-packbits.tiff", nil}, + {"video-001.tiff", &Options{Predictor: true}}, } func openImage(filename string) (image.Image, error) { @@ -27,13 +31,13 @@ func openImage(filename string) (image.Image, error) { } func TestRoundtrip(t *testing.T) { - for _, filename := range roundtripTests { - img, err := openImage(filename) + for _, rt := range roundtripTests { + img, err := openImage(rt.filename) if err != nil { t.Fatal(err) } out := new(bytes.Buffer) - err = Encode(out, img, nil) + err = Encode(out, img, rt.opts) if err != nil { t.Fatal(err) }