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

commitc35a1ecb6ccbb258456cfc208a0e3bfeb83e28bb
parentde792da79c
authorBenny Siegert <bsiegert@gmail.com>
date2012-06-13 10:40
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")