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")