Skip to content

Commit

Permalink
bmp: optimize decoding and encoding 0xH sized images.
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
snapbakkhfbav committed Jan 4, 2023
1 parent b439b60 commit 7c42b34
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 1 deletion.
11 changes: 10 additions & 1 deletion bmp/reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
8 changes: 8 additions & 0 deletions bmp/writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package bmp

import (
"encoding/binary"
"errors"
"image"
"io"
)
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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)
Expand Down
35 changes: 35 additions & 0 deletions bmp/writer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ package bmp

import (
"bytes"
"fmt"
"image"
"io/ioutil"
"os"
"testing"
"time"
)

func openImage(filename string) (image.Image, error) {
Expand Down Expand Up @@ -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")
Expand Down

0 comments on commit 7c42b34

Please sign in to comment.