bmp: optimize decoding and encoding 0xH sized images.

It should be quick regardless of how large H is.

Fixes golang/go#10746

Change-Id: Icde36047e88d9786e64f44724b7ba8b38db2a33f
Reviewed-on: https://go-review.googlesource.com/9836
Reviewed-by: Nigel Tao <nigeltao@golang.org>
diff --git a/bmp/reader.go b/bmp/reader.go
index 173d3d4..a0f2715 100644
--- a/bmp/reader.go
+++ b/bmp/reader.go
@@ -29,8 +29,11 @@
 // decodePaletted reads an 8 bit-per-pixel BMP image from r.
 // If topDown is false, the image rows will be read bottom-up.
 func decodePaletted(r io.Reader, c image.Config, topDown bool) (image.Image, error) {
-	var tmp [4]byte
 	paletted := image.NewPaletted(image.Rect(0, 0, c.Width, c.Height), c.ColorModel.(color.Palette))
+	if c.Width == 0 || c.Height == 0 {
+		return paletted, nil
+	}
+	var tmp [4]byte
 	y0, y1, yDelta := c.Height-1, -1, -1
 	if topDown {
 		y0, y1, yDelta = 0, c.Height, +1
@@ -55,6 +58,9 @@
 // If topDown is false, the image rows will be read bottom-up.
 func decodeRGB(r io.Reader, c image.Config, topDown bool) (image.Image, error) {
 	rgba := image.NewRGBA(image.Rect(0, 0, c.Width, c.Height))
+	if c.Width == 0 || c.Height == 0 {
+		return rgba, nil
+	}
 	// There are 3 bytes per pixel, and each row is 4-byte aligned.
 	b := make([]byte, (3*c.Width+3)&^3)
 	y0, y1, yDelta := c.Height-1, -1, -1
@@ -81,6 +87,9 @@
 // If topDown is false, the image rows will be read bottom-up.
 func decodeNRGBA(r io.Reader, c image.Config, topDown bool) (image.Image, error) {
 	rgba := image.NewNRGBA(image.Rect(0, 0, c.Width, c.Height))
+	if c.Width == 0 || c.Height == 0 {
+		return rgba, nil
+	}
 	y0, y1, yDelta := c.Height-1, -1, -1
 	if topDown {
 		y0, y1, yDelta = 0, c.Height, +1
diff --git a/bmp/writer.go b/bmp/writer.go
index 4e75e56..6947968 100644
--- a/bmp/writer.go
+++ b/bmp/writer.go
@@ -6,6 +6,7 @@
 
 import (
 	"encoding/binary"
+	"errors"
 	"image"
 	"io"
 )
@@ -89,6 +90,9 @@
 // Encode writes the image m to w in BMP format.
 func Encode(w io.Writer, m image.Image) error {
 	d := m.Bounds().Size()
+	if d.X < 0 || d.Y < 0 {
+		return errors.New("bmp: negative bounds")
+	}
 	h := &header{
 		sigBM:         [2]byte{'B', 'M'},
 		fileSize:      14 + 40,
@@ -146,6 +150,10 @@
 		}
 	}
 
+	if d.X == 0 || d.Y == 0 {
+		return nil
+	}
+
 	switch m := m.(type) {
 	case *image.Gray:
 		return encodePaletted(w, m.Pix, d.X, d.Y, m.Stride, step)
diff --git a/bmp/writer_test.go b/bmp/writer_test.go
index cf1dbc5..9e5a327 100644
--- a/bmp/writer_test.go
+++ b/bmp/writer_test.go
@@ -6,10 +6,12 @@
 
 import (
 	"bytes"
+	"fmt"
 	"image"
 	"io/ioutil"
 	"os"
 	"testing"
+	"time"
 )
 
 func openImage(filename string) (image.Image, error) {
@@ -41,6 +43,39 @@
 	compare(t, img0, img1)
 }
 
+// TestZeroWidthVeryLargeHeight tests that encoding and decoding a degenerate
+// image with zero width but over one billion pixels in height is faster than
+// naively calling an io.Reader or io.Writer method once per row.
+func TestZeroWidthVeryLargeHeight(t *testing.T) {
+	c := make(chan error, 1)
+	go func() {
+		b := image.Rect(0, 0, 0, 0x3fffffff)
+		var buf bytes.Buffer
+		if err := Encode(&buf, image.NewRGBA(b)); err != nil {
+			c <- err
+			return
+		}
+		m, err := Decode(&buf)
+		if err != nil {
+			c <- err
+			return
+		}
+		if got := m.Bounds(); got != b {
+			c <- fmt.Errorf("bounds: got %v, want %v", got, b)
+			return
+		}
+		c <- nil
+	}()
+	select {
+	case err := <-c:
+		if err != nil {
+			t.Fatal(err)
+		}
+	case <-time.After(3 * time.Second):
+		t.Fatalf("timed out")
+	}
+}
+
 // BenchmarkEncode benchmarks the encoding of an image.
 func BenchmarkEncode(b *testing.B) {
 	img, err := openImage("video-001.bmp")