From c35a1ecb6ccbb258456cfc208a0e3bfeb83e28bb Mon Sep 17 00:00:00 2001 From: Benny Siegert Date: Wed, 13 Jun 2012 10:40:38 +1000 Subject: [PATCH] go.image/tiff: use optimized routines for RGBA and NRGBA Performance for an RGBA image: benchmark old ns/op new ns/op delta BenchmarkEncode 1633495 7897 -99.52% benchmark old MB/s new MB/s speedup BenchmarkEncode 37.83 7824.96 206.85x NRGBA images are now encoded as such, the spec calls it "unassociated alpha". R=nigeltao, rsc CC=golang-dev https://golang.org/cl/6308049 --- tiff/reader_test.go | 15 +++++++++------ tiff/writer.go | 33 ++++++++++++++++++++++++++++++--- tiff/writer_test.go | 18 ++++++++++++++++++ 3 files changed, 57 insertions(+), 9 deletions(-) diff --git a/tiff/reader_test.go b/tiff/reader_test.go index c1c4c53..0af9e5c 100644 --- a/tiff/reader_test.go +++ b/tiff/reader_test.go @@ -58,14 +58,17 @@ func TestUnpackBits(t *testing.T) { } func compare(t *testing.T, img0, img1 image.Image) { - b := img1.Bounds() - if !b.Eq(img0.Bounds()) { - t.Fatalf("wrong image size: want %s, got %s", img0.Bounds(), b) + b0 := img0.Bounds() + b1 := img1.Bounds() + if b0.Dx() != b1.Dx() || b0.Dy() != b1.Dy() { + t.Fatalf("wrong image size: want %s, got %s", b0, b1) } - for y := b.Min.Y; y < b.Max.Y; y++ { - for x := b.Min.X; x < b.Max.X; x++ { + x1 := b1.Min.X - b0.Min.X + y1 := b1.Min.Y - b0.Min.Y + for y := b0.Min.Y; y < b0.Max.Y; y++ { + for x := b0.Min.X; x < b0.Max.X; x++ { c0 := img0.At(x, y) - c1 := img1.At(x, y) + c1 := img1.At(x+x1, y+y1) r0, g0, b0, a0 := c0.RGBA() r1, g1, b1, a1 := c1.RGBA() if r0 != r1 || g0 != g1 || b0 != b1 || a0 != a1 { diff --git a/tiff/writer.go b/tiff/writer.go index 9c8b893..1012368 100644 --- a/tiff/writer.go +++ b/tiff/writer.go @@ -73,6 +73,20 @@ func writeImgData(w io.Writer, m image.Image) error { return nil } +func writePix(w io.Writer, pix []byte, nrows, length, stride int) error { + if length == stride { + _, err := w.Write(pix[:nrows*length]) + return err + } + for ; nrows > 0; nrows-- { + if _, err := w.Write(pix[:length]); err != nil { + return err + } + pix = pix[stride:] + } + return nil +} + func writeIFD(w io.Writer, ifdOffset int, d []ifdEntry) error { var buf [ifdLen]byte // Make space for "pointer area" containing IFD entry data @@ -126,8 +140,9 @@ func writeIFD(w io.Writer, ifdOffset int, d []ifdEntry) error { return err } -// Encode writes the image m to w in uncompressed RGBA format. +// Encode writes the image m to w in uncompressed 8-bit RGBA or NRGBA format. func Encode(w io.Writer, m image.Image) error { + var extrasamples uint32 _, err := io.WriteString(w, leHeader) if err != nil { return err @@ -142,7 +157,19 @@ func Encode(w io.Writer, m image.Image) error { if err != nil { return err } - err = writeImgData(w, m) + 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) + } if err != nil { return err } @@ -161,6 +188,6 @@ func Encode(w io.Writer, m image.Image) error { {tXResolution, dtRational, []uint32{72, 1}}, {tYResolution, dtRational, []uint32{72, 1}}, {tResolutionUnit, dtShort, []uint32{resPerInch}}, - {tExtraSamples, dtShort, []uint32{1}}, // RGBA. + {tExtraSamples, dtShort, []uint32{extrasamples}}, }) } diff --git a/tiff/writer_test.go b/tiff/writer_test.go index 4f5ab70..b511755 100644 --- a/tiff/writer_test.go +++ b/tiff/writer_test.go @@ -46,6 +46,24 @@ func TestRoundtrip(t *testing.T) { } } +// TestRoundtrip2 tests that encoding and decoding an image whose +// origin is not (0, 0) gives the same thing. +func TestRoundtrip2(t *testing.T) { + m0 := image.NewRGBA(image.Rect(3, 4, 9, 8)) + for i := range m0.Pix { + m0.Pix[i] = byte(i) + } + out := new(bytes.Buffer) + if err := Encode(out, m0); err != nil { + t.Fatal(err) + } + m1, err := Decode(&buffer{buf: out.Bytes()}) + if err != nil { + t.Fatal(err) + } + compare(t, m0, m1) +} + // BenchmarkEncode benchmarks the encoding of an image. func BenchmarkEncode(b *testing.B) { img, err := openImage("video-001.tiff")