From f28211f6e1f285ea563cc452d38784710e7924b7 Mon Sep 17 00:00:00 2001 From: Nigel Tao Date: Fri, 8 May 2015 16:19:36 +1000 Subject: [PATCH] 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 --- bmp/reader.go | 11 ++++++++++- bmp/writer.go | 8 ++++++++ bmp/writer_test.go | 35 +++++++++++++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 1 deletion(-) 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 @@ func readUint32(b []byte) uint32 { // 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 @@ func decodePaletted(r io.Reader, c image.Config, topDown bool) (image.Image, err // 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 @@ func decodeRGB(r io.Reader, c image.Config, topDown bool) (image.Image, error) { // 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 @@ package bmp import ( "encoding/binary" + "errors" "image" "io" ) @@ -89,6 +90,9 @@ func encode(w io.Writer, m image.Image, step int) error { // 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 @@ func Encode(w io.Writer, m image.Image) error { } } + 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 @@ package bmp import ( "bytes" + "fmt" "image" "io/ioutil" "os" "testing" + "time" ) func openImage(filename string) (image.Image, error) { @@ -41,6 +43,39 @@ func TestEncode(t *testing.T) { 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")