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>
This commit is contained in:
Nigel Tao 2015-05-08 16:19:36 +10:00
parent 4a3ed0c124
commit f28211f6e1
3 changed files with 53 additions and 1 deletions

View File

@ -29,8 +29,11 @@ func readUint32(b []byte) uint32 {
// decodePaletted reads an 8 bit-per-pixel BMP image from r. // decodePaletted reads an 8 bit-per-pixel BMP image from r.
// If topDown is false, the image rows will be read bottom-up. // 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) { 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)) 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 y0, y1, yDelta := c.Height-1, -1, -1
if topDown { if topDown {
y0, y1, yDelta = 0, c.Height, +1 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. // 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) { func decodeRGB(r io.Reader, c image.Config, topDown bool) (image.Image, error) {
rgba := image.NewRGBA(image.Rect(0, 0, c.Width, c.Height)) 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. // There are 3 bytes per pixel, and each row is 4-byte aligned.
b := make([]byte, (3*c.Width+3)&^3) b := make([]byte, (3*c.Width+3)&^3)
y0, y1, yDelta := c.Height-1, -1, -1 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. // 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) { func decodeNRGBA(r io.Reader, c image.Config, topDown bool) (image.Image, error) {
rgba := image.NewNRGBA(image.Rect(0, 0, c.Width, c.Height)) 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 y0, y1, yDelta := c.Height-1, -1, -1
if topDown { if topDown {
y0, y1, yDelta = 0, c.Height, +1 y0, y1, yDelta = 0, c.Height, +1

View File

@ -6,6 +6,7 @@ package bmp
import ( import (
"encoding/binary" "encoding/binary"
"errors"
"image" "image"
"io" "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. // Encode writes the image m to w in BMP format.
func Encode(w io.Writer, m image.Image) error { func Encode(w io.Writer, m image.Image) error {
d := m.Bounds().Size() d := m.Bounds().Size()
if d.X < 0 || d.Y < 0 {
return errors.New("bmp: negative bounds")
}
h := &header{ h := &header{
sigBM: [2]byte{'B', 'M'}, sigBM: [2]byte{'B', 'M'},
fileSize: 14 + 40, 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) { switch m := m.(type) {
case *image.Gray: case *image.Gray:
return encodePaletted(w, m.Pix, d.X, d.Y, m.Stride, step) return encodePaletted(w, m.Pix, d.X, d.Y, m.Stride, step)

View File

@ -6,10 +6,12 @@ package bmp
import ( import (
"bytes" "bytes"
"fmt"
"image" "image"
"io/ioutil" "io/ioutil"
"os" "os"
"testing" "testing"
"time"
) )
func openImage(filename string) (image.Image, error) { func openImage(filename string) (image.Image, error) {
@ -41,6 +43,39 @@ func TestEncode(t *testing.T) {
compare(t, img0, img1) 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. // BenchmarkEncode benchmarks the encoding of an image.
func BenchmarkEncode(b *testing.B) { func BenchmarkEncode(b *testing.B) {
img, err := openImage("video-001.bmp") img, err := openImage("video-001.bmp")