summaryrefslogtreecommitdiff
path: root/libgo/go/image
diff options
context:
space:
mode:
authorIan Lance Taylor <iant@golang.org>2018-01-09 01:23:08 +0000
committerIan Lance Taylor <ian@gcc.gnu.org>2018-01-09 01:23:08 +0000
commit1a2f01efa63036a5104f203a4789e682c0e0915d (patch)
tree373e15778dc8295354584e1f86915ae493b604ff /libgo/go/image
parent8799df67f2dab88f9fda11739c501780a85575e2 (diff)
downloadgcc-1a2f01efa63036a5104f203a4789e682c0e0915d.tar.gz
libgo: update to Go1.10beta1
Update the Go library to the 1.10beta1 release. Requires a few changes to the compiler for modifications to the map runtime code, and to handle some nowritebarrier cases in the runtime. Reviewed-on: https://go-review.googlesource.com/86455 gotools/: * Makefile.am (go_cmd_vet_files): New variable. (go_cmd_buildid_files, go_cmd_test2json_files): New variables. (s-zdefaultcc): Change from constants to functions. (noinst_PROGRAMS): Add vet, buildid, and test2json. (cgo$(EXEEXT)): Link against $(LIBGOTOOL). (vet$(EXEEXT)): New target. (buildid$(EXEEXT)): New target. (test2json$(EXEEXT)): New target. (install-exec-local): Install all $(noinst_PROGRAMS). (uninstall-local): Uninstasll all $(noinst_PROGRAMS). (check-go-tool): Depend on $(noinst_PROGRAMS). Copy down objabi.go. (check-runtime): Depend on $(noinst_PROGRAMS). (check-cgo-test, check-carchive-test): Likewise. (check-vet): New target. (check): Depend on check-vet. Look at cmd_vet-testlog. (.PHONY): Add check-vet. * Makefile.in: Rebuild. From-SVN: r256365
Diffstat (limited to 'libgo/go/image')
-rw-r--r--libgo/go/image/color/color.go33
-rw-r--r--libgo/go/image/color/color_test.go47
-rw-r--r--libgo/go/image/draw/draw.go24
-rw-r--r--libgo/go/image/draw/draw_test.go45
-rw-r--r--libgo/go/image/gif/reader.go339
-rw-r--r--libgo/go/image/gif/reader_test.go58
-rw-r--r--libgo/go/image/gif/writer.go144
-rw-r--r--libgo/go/image/gif/writer_test.go42
-rw-r--r--libgo/go/image/jpeg/reader_test.go4
-rw-r--r--libgo/go/image/jpeg/writer_test.go8
-rw-r--r--libgo/go/image/png/reader.go6
-rw-r--r--libgo/go/image/png/reader_test.go29
-rw-r--r--libgo/go/image/png/writer_test.go28
13 files changed, 583 insertions, 224 deletions
diff --git a/libgo/go/image/color/color.go b/libgo/go/image/color/color.go
index 0832c597293..88958391404 100644
--- a/libgo/go/image/color/color.go
+++ b/libgo/go/image/color/color.go
@@ -200,7 +200,7 @@ func nrgbaModel(c Color) Color {
if a == 0 {
return NRGBA{0, 0, 0, 0}
}
- // Since Color.RGBA returns a alpha-premultiplied color, we should have r <= a && g <= a && b <= a.
+ // Since Color.RGBA returns an alpha-premultiplied color, we should have r <= a && g <= a && b <= a.
r = (r * 0xffff) / a
g = (g * 0xffff) / a
b = (b * 0xffff) / a
@@ -218,7 +218,7 @@ func nrgba64Model(c Color) Color {
if a == 0 {
return NRGBA64{0, 0, 0, 0}
}
- // Since Color.RGBA returns a alpha-premultiplied color, we should have r <= a && g <= a && b <= a.
+ // Since Color.RGBA returns an alpha-premultiplied color, we should have r <= a && g <= a && b <= a.
r = (r * 0xffff) / a
g = (g * 0xffff) / a
b = (b * 0xffff) / a
@@ -312,12 +312,29 @@ func (p Palette) Index(c Color) int {
//
// x and y are both assumed to be in the range [0, 0xffff].
func sqDiff(x, y uint32) uint32 {
- var d uint32
- if x > y {
- d = x - y
- } else {
- d = y - x
- }
+ // The canonical code of this function looks as follows:
+ //
+ // var d uint32
+ // if x > y {
+ // d = x - y
+ // } else {
+ // d = y - x
+ // }
+ // return (d * d) >> 2
+ //
+ // Language spec guarantees the following properties of unsigned integer
+ // values operations with respect to overflow/wrap around:
+ //
+ // > For unsigned integer values, the operations +, -, *, and << are
+ // > computed modulo 2n, where n is the bit width of the unsigned
+ // > integer's type. Loosely speaking, these unsigned integer operations
+ // > discard high bits upon overflow, and programs may rely on ``wrap
+ // > around''.
+ //
+ // Considering these properties and the fact that this function is
+ // called in the hot paths (x,y loops), it is reduced to the below code
+ // which is slightly faster. See TestSqDiff for correctness check.
+ d := x - y
return (d * d) >> 2
}
diff --git a/libgo/go/image/color/color_test.go b/libgo/go/image/color/color_test.go
new file mode 100644
index 00000000000..ea66b7bef23
--- /dev/null
+++ b/libgo/go/image/color/color_test.go
@@ -0,0 +1,47 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package color
+
+import (
+ "testing"
+ "testing/quick"
+)
+
+func TestSqDiff(t *testing.T) {
+ // canonical sqDiff implementation
+ orig := func(x, y uint32) uint32 {
+ var d uint32
+ if x > y {
+ d = uint32(x - y)
+ } else {
+ d = uint32(y - x)
+ }
+ return (d * d) >> 2
+ }
+ testCases := []uint32{
+ 0,
+ 1,
+ 2,
+ 0x0fffd,
+ 0x0fffe,
+ 0x0ffff,
+ 0x10000,
+ 0x10001,
+ 0x10002,
+ 0xfffffffd,
+ 0xfffffffe,
+ 0xffffffff,
+ }
+ for _, x := range testCases {
+ for _, y := range testCases {
+ if got, want := sqDiff(x, y), orig(x, y); got != want {
+ t.Fatalf("sqDiff(%#x, %#x): got %d, want %d", x, y, got, want)
+ }
+ }
+ }
+ if err := quick.CheckEqual(orig, sqDiff, &quick.Config{MaxCountScale: 10}); err != nil {
+ t.Fatal(err)
+ }
+}
diff --git a/libgo/go/image/draw/draw.go b/libgo/go/image/draw/draw.go
index a31dd427ce1..977d7c52215 100644
--- a/libgo/go/image/draw/draw.go
+++ b/libgo/go/image/draw/draw.go
@@ -564,12 +564,10 @@ func clamp(i int32) int32 {
//
// x and y are both assumed to be in the range [0, 0xffff].
func sqDiff(x, y int32) uint32 {
- var d uint32
- if x > y {
- d = uint32(x - y)
- } else {
- d = uint32(y - x)
- }
+ // This is an optimized code relying on the overflow/wrap around
+ // properties of unsigned integers operations guaranteed by the language
+ // spec. See sqDiff from the image/color package for more details.
+ d := uint32(x - y)
return (d * d) >> 2
}
@@ -603,6 +601,18 @@ func drawPaletted(dst Image, r image.Rectangle, src image.Image, sp image.Point,
quantErrorCurr = make([][4]int32, r.Dx()+2)
quantErrorNext = make([][4]int32, r.Dx()+2)
}
+ pxRGBA := func(x, y int) (r, g, b, a uint32) { return src.At(x, y).RGBA() }
+ // Fast paths for special cases to avoid excessive use of the color.Color
+ // interface which escapes to the heap but need to be discovered for
+ // each pixel on r. See also https://golang.org/issues/15759.
+ switch src0 := src.(type) {
+ case *image.RGBA:
+ pxRGBA = func(x, y int) (r, g, b, a uint32) { return src0.RGBAAt(x, y).RGBA() }
+ case *image.NRGBA:
+ pxRGBA = func(x, y int) (r, g, b, a uint32) { return src0.NRGBAAt(x, y).RGBA() }
+ case *image.YCbCr:
+ pxRGBA = func(x, y int) (r, g, b, a uint32) { return src0.YCbCrAt(x, y).RGBA() }
+ }
// Loop over each source pixel.
out := color.RGBA64{A: 0xffff}
@@ -610,7 +620,7 @@ func drawPaletted(dst Image, r image.Rectangle, src image.Image, sp image.Point,
for x := 0; x != r.Dx(); x++ {
// er, eg and eb are the pixel's R,G,B values plus the
// optional Floyd-Steinberg error.
- sr, sg, sb, sa := src.At(sp.X+x, sp.Y+y).RGBA()
+ sr, sg, sb, sa := pxRGBA(sp.X+x, sp.Y+y)
er, eg, eb, ea := int32(sr), int32(sg), int32(sb), int32(sa)
if floydSteinberg {
er = clamp(er + quantErrorCurr[x+1][0]/16)
diff --git a/libgo/go/image/draw/draw_test.go b/libgo/go/image/draw/draw_test.go
index a58f0f49849..dea51b6bc50 100644
--- a/libgo/go/image/draw/draw_test.go
+++ b/libgo/go/image/draw/draw_test.go
@@ -10,6 +10,7 @@ import (
"image/png"
"os"
"testing"
+ "testing/quick"
)
func eq(c0, c1 color.Color) bool {
@@ -467,3 +468,47 @@ loop:
}
}
}
+
+func TestSqDiff(t *testing.T) {
+ // This test is similar to the one from the image/color package, but
+ // sqDiff in this package accepts int32 instead of uint32, so test it
+ // for appropriate input.
+
+ // canonical sqDiff implementation
+ orig := func(x, y int32) uint32 {
+ var d uint32
+ if x > y {
+ d = uint32(x - y)
+ } else {
+ d = uint32(y - x)
+ }
+ return (d * d) >> 2
+ }
+ testCases := []int32{
+ 0,
+ 1,
+ 2,
+ 0x0fffd,
+ 0x0fffe,
+ 0x0ffff,
+ 0x10000,
+ 0x10001,
+ 0x10002,
+ 0x7ffffffd,
+ 0x7ffffffe,
+ 0x7fffffff,
+ -0x7ffffffd,
+ -0x7ffffffe,
+ -0x80000000,
+ }
+ for _, x := range testCases {
+ for _, y := range testCases {
+ if got, want := sqDiff(x, y), orig(x, y); got != want {
+ t.Fatalf("sqDiff(%#x, %#x): got %d, want %d", x, y, got, want)
+ }
+ }
+ }
+ if err := quick.CheckEqual(orig, sqDiff, &quick.Config{MaxCountScale: 10}); err != nil {
+ t.Fatal(err)
+ }
+}
diff --git a/libgo/go/image/gif/reader.go b/libgo/go/image/gif/reader.go
index b1335e61259..c1c9562067d 100644
--- a/libgo/go/image/gif/reader.go
+++ b/libgo/go/image/gif/reader.go
@@ -109,48 +109,114 @@ type decoder struct {
tmp [1024]byte // must be at least 768 so we can read color table
}
-// blockReader parses the block structure of GIF image data, which
-// comprises (n, (n bytes)) blocks, with 1 <= n <= 255. It is the
-// reader given to the LZW decoder, which is thus immune to the
-// blocking. After the LZW decoder completes, there will be a 0-byte
-// block remaining (0, ()), which is consumed when checking that the
-// blockReader is exhausted.
+// blockReader parses the block structure of GIF image data, which comprises
+// (n, (n bytes)) blocks, with 1 <= n <= 255. It is the reader given to the
+// LZW decoder, which is thus immune to the blocking. After the LZW decoder
+// completes, there will be a 0-byte block remaining (0, ()), which is
+// consumed when checking that the blockReader is exhausted.
+//
+// To avoid the allocation of a bufio.Reader for the lzw Reader, blockReader
+// implements io.ReadByte and buffers blocks into the decoder's "tmp" buffer.
type blockReader struct {
- r reader
- slice []byte
- err error
- tmp [256]byte
+ d *decoder
+ i, j uint8 // d.tmp[i:j] contains the buffered bytes
+ err error
}
-func (b *blockReader) Read(p []byte) (int, error) {
+func (b *blockReader) fill() {
if b.err != nil {
- return 0, b.err
+ return
+ }
+ b.j, b.err = readByte(b.d.r)
+ if b.j == 0 && b.err == nil {
+ b.err = io.EOF
+ }
+ if b.err != nil {
+ return
}
- if len(p) == 0 {
- return 0, nil
+
+ b.i = 0
+ b.err = readFull(b.d.r, b.d.tmp[:b.j])
+ if b.err != nil {
+ b.j = 0
}
- if len(b.slice) == 0 {
- var blockLen uint8
- blockLen, b.err = b.r.ReadByte()
+}
+
+func (b *blockReader) ReadByte() (byte, error) {
+ if b.i == b.j {
+ b.fill()
if b.err != nil {
return 0, b.err
}
- if blockLen == 0 {
- b.err = io.EOF
- return 0, b.err
- }
- b.slice = b.tmp[:blockLen]
- if b.err = readFull(b.r, b.slice); b.err != nil {
+ }
+
+ c := b.d.tmp[b.i]
+ b.i++
+ return c, nil
+}
+
+// blockReader must implement io.Reader, but its Read shouldn't ever actually
+// be called in practice. The compress/lzw package will only call ReadByte.
+func (b *blockReader) Read(p []byte) (int, error) {
+ if len(p) == 0 || b.err != nil {
+ return 0, b.err
+ }
+ if b.i == b.j {
+ b.fill()
+ if b.err != nil {
return 0, b.err
}
}
- n := copy(p, b.slice)
- b.slice = b.slice[n:]
+
+ n := copy(p, b.d.tmp[b.i:b.j])
+ b.i += uint8(n)
return n, nil
}
+// close primarily detects whether or not a block terminator was encountered
+// after reading a sequence of data sub-blocks. It allows at most one trailing
+// sub-block worth of data. I.e., if some number of bytes exist in one sub-block
+// following the end of LZW data, the very next sub-block must be the block
+// terminator. If the very end of LZW data happened to fill one sub-block, at
+// most one more sub-block of length 1 may exist before the block-terminator.
+// These accomodations allow us to support GIFs created by less strict encoders.
+// See https://golang.org/issue/16146.
+func (b *blockReader) close() error {
+ if b.err == io.EOF {
+ // A clean block-sequence terminator was encountered while reading.
+ return nil
+ } else if b.err != nil {
+ // Some other error was encountered while reading.
+ return b.err
+ }
+
+ if b.i == b.j {
+ // We reached the end of a sub block reading LZW data. We'll allow at
+ // most one more sub block of data with a length of 1 byte.
+ b.fill()
+ if b.err == io.EOF {
+ return nil
+ } else if b.err != nil {
+ return b.err
+ } else if b.j > 1 {
+ return errTooMuch
+ }
+ }
+
+ // Part of a sub-block remains buffered. We expect that the next attempt to
+ // buffer a sub-block will reach the block terminator.
+ b.fill()
+ if b.err == io.EOF {
+ return nil
+ } else if b.err != nil {
+ return b.err
+ }
+
+ return errTooMuch
+}
+
// decode reads a GIF image from r and stores the result in d.
-func (d *decoder) decode(r io.Reader, configOnly bool) error {
+func (d *decoder) decode(r io.Reader, configOnly, keepAllFrames bool) error {
// Add buffering if r does not provide ReadByte.
if rr, ok := r.(reader); ok {
d.r = rr
@@ -178,115 +244,9 @@ func (d *decoder) decode(r io.Reader, configOnly bool) error {
}
case sImageDescriptor:
- m, err := d.newImageFromDescriptor()
- if err != nil {
+ if err = d.readImageDescriptor(keepAllFrames); err != nil {
return err
}
- useLocalColorTable := d.imageFields&fColorTable != 0
- if useLocalColorTable {
- m.Palette, err = d.readColorTable(d.imageFields)
- if err != nil {
- return err
- }
- } else {
- if d.globalColorTable == nil {
- return errors.New("gif: no color table")
- }
- m.Palette = d.globalColorTable
- }
- if d.hasTransparentIndex {
- if !useLocalColorTable {
- // Clone the global color table.
- m.Palette = append(color.Palette(nil), d.globalColorTable...)
- }
- if ti := int(d.transparentIndex); ti < len(m.Palette) {
- m.Palette[ti] = color.RGBA{}
- } else {
- // The transparentIndex is out of range, which is an error
- // according to the spec, but Firefox and Google Chrome
- // seem OK with this, so we enlarge the palette with
- // transparent colors. See golang.org/issue/15059.
- p := make(color.Palette, ti+1)
- copy(p, m.Palette)
- for i := len(m.Palette); i < len(p); i++ {
- p[i] = color.RGBA{}
- }
- m.Palette = p
- }
- }
- litWidth, err := readByte(d.r)
- if err != nil {
- return fmt.Errorf("gif: reading image data: %v", err)
- }
- if litWidth < 2 || litWidth > 8 {
- return fmt.Errorf("gif: pixel size in decode out of range: %d", litWidth)
- }
- // A wonderfully Go-like piece of magic.
- br := &blockReader{r: d.r}
- lzwr := lzw.NewReader(br, lzw.LSB, int(litWidth))
- defer lzwr.Close()
- if err = readFull(lzwr, m.Pix); err != nil {
- if err != io.ErrUnexpectedEOF {
- return fmt.Errorf("gif: reading image data: %v", err)
- }
- return errNotEnough
- }
- // In theory, both lzwr and br should be exhausted. Reading from them
- // should yield (0, io.EOF).
- //
- // The spec (Appendix F - Compression), says that "An End of
- // Information code... must be the last code output by the encoder
- // for an image". In practice, though, giflib (a widely used C
- // library) does not enforce this, so we also accept lzwr returning
- // io.ErrUnexpectedEOF (meaning that the encoded stream hit io.EOF
- // before the LZW decoder saw an explicit end code), provided that
- // the io.ReadFull call above successfully read len(m.Pix) bytes.
- // See https://golang.org/issue/9856 for an example GIF.
- if n, err := lzwr.Read(d.tmp[:1]); n != 0 || (err != io.EOF && err != io.ErrUnexpectedEOF) {
- if err != nil {
- return fmt.Errorf("gif: reading image data: %v", err)
- }
- return errTooMuch
- }
-
- // In practice, some GIFs have an extra byte in the data sub-block
- // stream, which we ignore. See https://golang.org/issue/16146.
- for nExtraBytes := 0; ; {
- n, err := br.Read(d.tmp[:2])
- nExtraBytes += n
- if nExtraBytes > 1 {
- return errTooMuch
- }
- if err == io.EOF {
- break
- }
- if err != nil {
- return fmt.Errorf("gif: reading image data: %v", err)
- }
- }
-
- // Check that the color indexes are inside the palette.
- if len(m.Palette) < 256 {
- for _, pixel := range m.Pix {
- if int(pixel) >= len(m.Palette) {
- return errBadPixel
- }
- }
- }
-
- // Undo the interlacing if necessary.
- if d.imageFields&fInterlace != 0 {
- uninterlace(m)
- }
-
- d.image = append(d.image, m)
- d.delay = append(d.delay, d.delayTime)
- d.disposal = append(d.disposal, d.disposalMethod)
- // The GIF89a spec, Section 23 (Graphic Control Extension) says:
- // "The scope of this extension is the first graphic rendering block
- // to follow." We therefore reset the GCE fields to zero.
- d.delayTime = 0
- d.hasTransparentIndex = false
case sTrailer:
if len(d.image) == 0 {
@@ -410,6 +370,113 @@ func (d *decoder) readGraphicControl() error {
return nil
}
+func (d *decoder) readImageDescriptor(keepAllFrames bool) error {
+ m, err := d.newImageFromDescriptor()
+ if err != nil {
+ return err
+ }
+ useLocalColorTable := d.imageFields&fColorTable != 0
+ if useLocalColorTable {
+ m.Palette, err = d.readColorTable(d.imageFields)
+ if err != nil {
+ return err
+ }
+ } else {
+ if d.globalColorTable == nil {
+ return errors.New("gif: no color table")
+ }
+ m.Palette = d.globalColorTable
+ }
+ if d.hasTransparentIndex {
+ if !useLocalColorTable {
+ // Clone the global color table.
+ m.Palette = append(color.Palette(nil), d.globalColorTable...)
+ }
+ if ti := int(d.transparentIndex); ti < len(m.Palette) {
+ m.Palette[ti] = color.RGBA{}
+ } else {
+ // The transparentIndex is out of range, which is an error
+ // according to the spec, but Firefox and Google Chrome
+ // seem OK with this, so we enlarge the palette with
+ // transparent colors. See golang.org/issue/15059.
+ p := make(color.Palette, ti+1)
+ copy(p, m.Palette)
+ for i := len(m.Palette); i < len(p); i++ {
+ p[i] = color.RGBA{}
+ }
+ m.Palette = p
+ }
+ }
+ litWidth, err := readByte(d.r)
+ if err != nil {
+ return fmt.Errorf("gif: reading image data: %v", err)
+ }
+ if litWidth < 2 || litWidth > 8 {
+ return fmt.Errorf("gif: pixel size in decode out of range: %d", litWidth)
+ }
+ // A wonderfully Go-like piece of magic.
+ br := &blockReader{d: d}
+ lzwr := lzw.NewReader(br, lzw.LSB, int(litWidth))
+ defer lzwr.Close()
+ if err = readFull(lzwr, m.Pix); err != nil {
+ if err != io.ErrUnexpectedEOF {
+ return fmt.Errorf("gif: reading image data: %v", err)
+ }
+ return errNotEnough
+ }
+ // In theory, both lzwr and br should be exhausted. Reading from them
+ // should yield (0, io.EOF).
+ //
+ // The spec (Appendix F - Compression), says that "An End of
+ // Information code... must be the last code output by the encoder
+ // for an image". In practice, though, giflib (a widely used C
+ // library) does not enforce this, so we also accept lzwr returning
+ // io.ErrUnexpectedEOF (meaning that the encoded stream hit io.EOF
+ // before the LZW decoder saw an explicit end code), provided that
+ // the io.ReadFull call above successfully read len(m.Pix) bytes.
+ // See https://golang.org/issue/9856 for an example GIF.
+ if n, err := lzwr.Read(d.tmp[256:257]); n != 0 || (err != io.EOF && err != io.ErrUnexpectedEOF) {
+ if err != nil {
+ return fmt.Errorf("gif: reading image data: %v", err)
+ }
+ return errTooMuch
+ }
+
+ // In practice, some GIFs have an extra byte in the data sub-block
+ // stream, which we ignore. See https://golang.org/issue/16146.
+ if err := br.close(); err == errTooMuch {
+ return errTooMuch
+ } else if err != nil {
+ return fmt.Errorf("gif: reading image data: %v", err)
+ }
+
+ // Check that the color indexes are inside the palette.
+ if len(m.Palette) < 256 {
+ for _, pixel := range m.Pix {
+ if int(pixel) >= len(m.Palette) {
+ return errBadPixel
+ }
+ }
+ }
+
+ // Undo the interlacing if necessary.
+ if d.imageFields&fInterlace != 0 {
+ uninterlace(m)
+ }
+
+ if keepAllFrames || len(d.image) == 0 {
+ d.image = append(d.image, m)
+ d.delay = append(d.delay, d.delayTime)
+ d.disposal = append(d.disposal, d.disposalMethod)
+ }
+ // The GIF89a spec, Section 23 (Graphic Control Extension) says:
+ // "The scope of this extension is the first graphic rendering block
+ // to follow." We therefore reset the GCE fields to zero.
+ d.delayTime = 0
+ d.hasTransparentIndex = false
+ return nil
+}
+
func (d *decoder) newImageFromDescriptor() (*image.Paletted, error) {
if err := readFull(d.r, d.tmp[:9]); err != nil {
return nil, fmt.Errorf("gif: can't read image descriptor: %s", err)
@@ -491,7 +558,7 @@ func uninterlace(m *image.Paletted) {
// image as an image.Image.
func Decode(r io.Reader) (image.Image, error) {
var d decoder
- if err := d.decode(r, false); err != nil {
+ if err := d.decode(r, false, false); err != nil {
return nil, err
}
return d.image[0], nil
@@ -526,7 +593,7 @@ type GIF struct {
// and timing information.
func DecodeAll(r io.Reader) (*GIF, error) {
var d decoder
- if err := d.decode(r, false); err != nil {
+ if err := d.decode(r, false, true); err != nil {
return nil, err
}
gif := &GIF{
@@ -548,7 +615,7 @@ func DecodeAll(r io.Reader) (*GIF, error) {
// without decoding the entire image.
func DecodeConfig(r io.Reader) (image.Config, error) {
var d decoder
- if err := d.decode(r, true); err != nil {
+ if err := d.decode(r, true, false); err != nil {
return image.Config{}, err
}
return image.Config{
diff --git a/libgo/go/image/gif/reader_test.go b/libgo/go/image/gif/reader_test.go
index 51c64b7328f..220e8f52d48 100644
--- a/libgo/go/image/gif/reader_test.go
+++ b/libgo/go/image/gif/reader_test.go
@@ -9,7 +9,12 @@ import (
"compress/lzw"
"image"
"image/color"
+ "image/color/palette"
+ "io"
+ "io/ioutil"
"reflect"
+ "runtime"
+ "runtime/debug"
"strings"
"testing"
)
@@ -23,6 +28,9 @@ const (
trailerStr = "\x3b"
)
+// lzw.NewReader wants a io.ByteReader, this ensures we're compatible.
+var _ io.ByteReader = (*blockReader)(nil)
+
// lzwEncode returns an LZW encoding (with 2-bit literals) of in.
func lzwEncode(in []byte) []byte {
b := &bytes.Buffer{}
@@ -66,6 +74,9 @@ func TestDecode(t *testing.T) {
{2, 1, 0, nil},
// Two extra bytes after LZW data, but inside the same data sub-block.
{2, 2, 0, nil},
+ // Extra data exists in the final sub-block with LZW data, AND there is
+ // a bogus sub-block following.
+ {2, 1, 1, errTooMuch},
}
for _, tc := range testCases {
b := &bytes.Buffer{}
@@ -342,3 +353,50 @@ func TestUnexpectedEOF(t *testing.T) {
}
}
}
+
+// See golang.org/issue/22237
+func TestDecodeMemoryConsumption(t *testing.T) {
+ const frames = 3000
+ img := image.NewPaletted(image.Rectangle{Max: image.Point{1, 1}}, palette.WebSafe)
+ hugeGIF := &GIF{
+ Image: make([]*image.Paletted, frames),
+ Delay: make([]int, frames),
+ Disposal: make([]byte, frames),
+ }
+ for i := 0; i < frames; i++ {
+ hugeGIF.Image[i] = img
+ hugeGIF.Delay[i] = 60
+ }
+ buf := new(bytes.Buffer)
+ if err := EncodeAll(buf, hugeGIF); err != nil {
+ t.Fatal("EncodeAll:", err)
+ }
+ s0, s1 := new(runtime.MemStats), new(runtime.MemStats)
+ runtime.GC()
+ defer debug.SetGCPercent(debug.SetGCPercent(5))
+ runtime.ReadMemStats(s0)
+ if _, err := Decode(buf); err != nil {
+ t.Fatal("Decode:", err)
+ }
+ runtime.ReadMemStats(s1)
+ if heapDiff := int64(s1.HeapAlloc - s0.HeapAlloc); heapDiff > 30<<20 {
+ t.Fatalf("Decode of %d frames increased heap by %dMB", frames, heapDiff>>20)
+ }
+}
+
+func BenchmarkDecode(b *testing.B) {
+ data, err := ioutil.ReadFile("../testdata/video-001.gif")
+ if err != nil {
+ b.Fatal(err)
+ }
+ cfg, err := DecodeConfig(bytes.NewReader(data))
+ if err != nil {
+ b.Fatal(err)
+ }
+ b.SetBytes(int64(cfg.Width * cfg.Height))
+ b.ReportAllocs()
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ Decode(bytes.NewReader(data))
+ }
+}
diff --git a/libgo/go/image/gif/writer.go b/libgo/go/image/gif/writer.go
index 493c7549eb2..f26af8be478 100644
--- a/libgo/go/image/gif/writer.go
+++ b/libgo/go/image/gif/writer.go
@@ -70,25 +70,54 @@ type blockWriter struct {
e *encoder
}
-func (b blockWriter) Write(data []byte) (int, error) {
+func (b blockWriter) setup() {
+ b.e.buf[0] = 0
+}
+
+func (b blockWriter) Flush() error {
+ return b.e.err
+}
+
+func (b blockWriter) WriteByte(c byte) error {
if b.e.err != nil {
- return 0, b.e.err
+ return b.e.err
}
- if len(data) == 0 {
- return 0, nil
+
+ // Append c to buffered sub-block.
+ b.e.buf[0]++
+ b.e.buf[b.e.buf[0]] = c
+ if b.e.buf[0] < 255 {
+ return nil
}
- total := 0
- for total < len(data) {
- n := copy(b.e.buf[1:256], data[total:])
- total += n
- b.e.buf[0] = uint8(n)
- _, b.e.err = b.e.w.Write(b.e.buf[:n+1])
- if b.e.err != nil {
- return 0, b.e.err
+ // Flush block
+ b.e.write(b.e.buf[:256])
+ b.e.buf[0] = 0
+ return b.e.err
+}
+
+// blockWriter must be an io.Writer for lzw.NewWriter, but this is never
+// actually called.
+func (b blockWriter) Write(data []byte) (int, error) {
+ for i, c := range data {
+ if err := b.WriteByte(c); err != nil {
+ return i, err
}
}
- return total, b.e.err
+ return len(data), nil
+}
+
+func (b blockWriter) close() {
+ // Write the block terminator (0x00), either by itself, or along with a
+ // pending sub-block.
+ if b.e.buf[0] == 0 {
+ b.e.writeByte(0)
+ } else {
+ n := uint(b.e.buf[0])
+ b.e.buf[n+1] = 0
+ b.e.write(b.e.buf[:n+2])
+ }
+ b.e.flush()
}
func (e *encoder) flush() {
@@ -171,27 +200,44 @@ func encodeColorTable(dst []byte, p color.Palette, size int) (int, error) {
if uint(size) >= uint(len(log2Lookup)) {
return 0, errors.New("gif: cannot encode color table with more than 256 entries")
}
- n := log2Lookup[size]
- for i := 0; i < n; i++ {
- if i < len(p) {
- c := p[i]
- if c == nil {
- return 0, errors.New("gif: cannot encode color table with nil entries")
- }
- r, g, b, _ := c.RGBA()
- dst[3*i+0] = uint8(r >> 8)
- dst[3*i+1] = uint8(g >> 8)
- dst[3*i+2] = uint8(b >> 8)
+ for i, c := range p {
+ if c == nil {
+ return 0, errors.New("gif: cannot encode color table with nil entries")
+ }
+ var r, g, b uint8
+ // It is most likely that the palette is full of color.RGBAs, so they
+ // get a fast path.
+ if rgba, ok := c.(color.RGBA); ok {
+ r, g, b = rgba.R, rgba.G, rgba.B
} else {
- // Pad with black.
- dst[3*i+0] = 0x00
- dst[3*i+1] = 0x00
- dst[3*i+2] = 0x00
+ rr, gg, bb, _ := c.RGBA()
+ r, g, b = uint8(rr>>8), uint8(gg>>8), uint8(bb>>8)
+ }
+ dst[3*i+0] = r
+ dst[3*i+1] = g
+ dst[3*i+2] = b
+ }
+ n := log2Lookup[size]
+ if n > len(p) {
+ // Pad with black.
+ fill := dst[3*len(p) : 3*n]
+ for i := range fill {
+ fill[i] = 0
}
}
return 3 * n, nil
}
+func (e *encoder) colorTablesMatch(localLen, transparentIndex int) bool {
+ localSize := 3 * localLen
+ if transparentIndex >= 0 {
+ trOff := 3 * transparentIndex
+ return bytes.Equal(e.globalColorTable[:trOff], e.localColorTable[:trOff]) &&
+ bytes.Equal(e.globalColorTable[trOff+3:localSize], e.localColorTable[trOff+3:localSize])
+ }
+ return bytes.Equal(e.globalColorTable[:localSize], e.localColorTable[:localSize])
+}
+
func (e *encoder) writeImageBlock(pm *image.Paletted, delay int, disposal byte) {
if e.err != nil {
return
@@ -251,19 +297,31 @@ func (e *encoder) writeImageBlock(pm *image.Paletted, delay int, disposal byte)
writeUint16(e.buf[7:9], uint16(b.Dy()))
e.write(e.buf[:9])
+ // To determine whether or not this frame's palette is the same as the
+ // global palette, we can check a couple things. First, do they actually
+ // point to the same []color.Color? If so, they are equal so long as the
+ // frame's palette is not longer than the global palette...
paddedSize := log2(len(pm.Palette)) // Size of Local Color Table: 2^(1+n).
- if ct, err := encodeColorTable(e.localColorTable[:], pm.Palette, paddedSize); err != nil {
- if e.err == nil {
- e.err = err
- }
- return
- } else if ct != e.globalCT || !bytes.Equal(e.globalColorTable[:ct], e.localColorTable[:ct]) {
- // Use a local color table.
- e.writeByte(fColorTable | uint8(paddedSize))
- e.write(e.localColorTable[:ct])
+ if gp, ok := e.g.Config.ColorModel.(color.Palette); ok && len(pm.Palette) <= len(gp) && &gp[0] == &pm.Palette[0] {
+ e.writeByte(0) // Use the global color table.
} else {
- // Use the global color table.
- e.writeByte(0)
+ ct, err := encodeColorTable(e.localColorTable[:], pm.Palette, paddedSize)
+ if err != nil {
+ if e.err == nil {
+ e.err = err
+ }
+ return
+ }
+ // This frame's palette is not the very same slice as the global
+ // palette, but it might be a copy, possibly with one value turned into
+ // transparency by DecodeAll.
+ if ct <= e.globalCT && e.colorTablesMatch(len(pm.Palette), transparentIndex) {
+ e.writeByte(0) // Use the global color table.
+ } else {
+ // Use a local color table.
+ e.writeByte(fColorTable | uint8(paddedSize))
+ e.write(e.localColorTable[:ct])
+ }
}
litWidth := paddedSize + 1
@@ -272,7 +330,9 @@ func (e *encoder) writeImageBlock(pm *image.Paletted, delay int, disposal byte)
}
e.writeByte(uint8(litWidth)) // LZW Minimum Code Size.
- lzww := lzw.NewWriter(blockWriter{e: e}, lzw.LSB, litWidth)
+ bw := blockWriter{e: e}
+ bw.setup()
+ lzww := lzw.NewWriter(bw, lzw.LSB, litWidth)
if dx := b.Dx(); dx == pm.Stride {
_, e.err = lzww.Write(pm.Pix[:dx*b.Dy()])
if e.err != nil {
@@ -288,8 +348,8 @@ func (e *encoder) writeImageBlock(pm *image.Paletted, delay int, disposal byte)
}
}
}
- lzww.Close()
- e.writeByte(0x00) // Block Terminator.
+ lzww.Close() // flush to bw
+ bw.close() // flush to e.w
}
// Options are the encoding parameters.
diff --git a/libgo/go/image/gif/writer_test.go b/libgo/go/image/gif/writer_test.go
index 1bba9b8ece5..69042ec6743 100644
--- a/libgo/go/image/gif/writer_test.go
+++ b/libgo/go/image/gif/writer_test.go
@@ -64,6 +64,10 @@ func averageDelta(m0, m1 image.Image) int64 {
return sum / n
}
+// lzw.NewWriter wants an interface which is basically the same thing as gif's
+// writer interface. This ensures we're compatible.
+var _ writer = blockWriter{}
+
var testCase = []struct {
filename string
tolerance int64
@@ -471,6 +475,35 @@ func TestEncodeBadPalettes(t *testing.T) {
}
}
+func TestColorTablesMatch(t *testing.T) {
+ const trIdx = 100
+ global := color.Palette(palette.Plan9)
+ if rgb := global[trIdx].(color.RGBA); rgb.R == 0 && rgb.G == 0 && rgb.B == 0 {
+ t.Fatalf("trIdx (%d) is already black", trIdx)
+ }
+
+ // Make a copy of the palette, substituting trIdx's slot with transparent,
+ // just like decoder.decode.
+ local := append(color.Palette(nil), global...)
+ local[trIdx] = color.RGBA{}
+
+ const testLen = 3 * 256
+ const padded = 7
+ e := new(encoder)
+ if l, err := encodeColorTable(e.globalColorTable[:], global, padded); err != nil || l != testLen {
+ t.Fatalf("Failed to encode global color table: got %d, %v; want nil, %d", l, err, testLen)
+ }
+ if l, err := encodeColorTable(e.localColorTable[:], local, padded); err != nil || l != testLen {
+ t.Fatalf("Failed to encode local color table: got %d, %v; want nil, %d", l, err, testLen)
+ }
+ if bytes.Equal(e.globalColorTable[:testLen], e.localColorTable[:testLen]) {
+ t.Fatal("Encoded color tables are equal, expected mismatch")
+ }
+ if !e.colorTablesMatch(len(local), trIdx) {
+ t.Fatal("colorTablesMatch() == false, expected true")
+ }
+}
+
func TestEncodeCroppedSubImages(t *testing.T) {
// This test means to ensure that Encode honors the Bounds and Strides of
// images correctly when encoding.
@@ -500,8 +533,6 @@ func TestEncodeCroppedSubImages(t *testing.T) {
}
func BenchmarkEncode(b *testing.B) {
- b.StopTimer()
-
bo := image.Rect(0, 0, 640, 480)
rnd := rand.New(rand.NewSource(123))
@@ -523,14 +554,14 @@ func BenchmarkEncode(b *testing.B) {
}
b.SetBytes(640 * 480 * 4)
- b.StartTimer()
+ b.ReportAllocs()
+ b.ResetTimer()
for i := 0; i < b.N; i++ {
Encode(ioutil.Discard, img, nil)
}
}
func BenchmarkQuantizedEncode(b *testing.B) {
- b.StopTimer()
img := image.NewRGBA(image.Rect(0, 0, 640, 480))
bo := img.Bounds()
rnd := rand.New(rand.NewSource(123))
@@ -545,7 +576,8 @@ func BenchmarkQuantizedEncode(b *testing.B) {
}
}
b.SetBytes(640 * 480 * 4)
- b.StartTimer()
+ b.ReportAllocs()
+ b.ResetTimer()
for i := 0; i < b.N; i++ {
Encode(ioutil.Discard, img, nil)
}
diff --git a/libgo/go/image/jpeg/reader_test.go b/libgo/go/image/jpeg/reader_test.go
index 77376152bc0..a62b509234e 100644
--- a/libgo/go/image/jpeg/reader_test.go
+++ b/libgo/go/image/jpeg/reader_test.go
@@ -323,7 +323,6 @@ func TestExtraneousData(t *testing.T) {
}
func benchmarkDecode(b *testing.B, filename string) {
- b.StopTimer()
data, err := ioutil.ReadFile(filename)
if err != nil {
b.Fatal(err)
@@ -333,7 +332,8 @@ func benchmarkDecode(b *testing.B, filename string) {
b.Fatal(err)
}
b.SetBytes(int64(cfg.Width * cfg.Height * 4))
- b.StartTimer()
+ b.ReportAllocs()
+ b.ResetTimer()
for i := 0; i < b.N; i++ {
Decode(bytes.NewReader(data))
}
diff --git a/libgo/go/image/jpeg/writer_test.go b/libgo/go/image/jpeg/writer_test.go
index a6c056174bd..3aff7426325 100644
--- a/libgo/go/image/jpeg/writer_test.go
+++ b/libgo/go/image/jpeg/writer_test.go
@@ -243,7 +243,6 @@ func TestEncodeYCbCr(t *testing.T) {
}
func BenchmarkEncodeRGBA(b *testing.B) {
- b.StopTimer()
img := image.NewRGBA(image.Rect(0, 0, 640, 480))
bo := img.Bounds()
rnd := rand.New(rand.NewSource(123))
@@ -258,7 +257,8 @@ func BenchmarkEncodeRGBA(b *testing.B) {
}
}
b.SetBytes(640 * 480 * 4)
- b.StartTimer()
+ b.ReportAllocs()
+ b.ResetTimer()
options := &Options{Quality: 90}
for i := 0; i < b.N; i++ {
Encode(ioutil.Discard, img, options)
@@ -266,7 +266,6 @@ func BenchmarkEncodeRGBA(b *testing.B) {
}
func BenchmarkEncodeYCbCr(b *testing.B) {
- b.StopTimer()
img := image.NewYCbCr(image.Rect(0, 0, 640, 480), image.YCbCrSubsampleRatio420)
bo := img.Bounds()
rnd := rand.New(rand.NewSource(123))
@@ -280,7 +279,8 @@ func BenchmarkEncodeYCbCr(b *testing.B) {
}
}
b.SetBytes(640 * 480 * 3)
- b.StartTimer()
+ b.ReportAllocs()
+ b.ResetTimer()
options := &Options{Quality: 90}
for i := 0; i < b.N; i++ {
Encode(ioutil.Discard, img, options)
diff --git a/libgo/go/image/png/reader.go b/libgo/go/image/png/reader.go
index 4f043a0e424..4fbcef84b7c 100644
--- a/libgo/go/image/png/reader.go
+++ b/libgo/go/image/png/reader.go
@@ -157,6 +157,7 @@ func (d *decoder) parseIHDR(length uint32) error {
return FormatError("invalid interlace method")
}
d.interlace = int(d.tmp[12])
+
w := int32(binary.BigEndian.Uint32(d.tmp[0:4]))
h := int32(binary.BigEndian.Uint32(d.tmp[4:8]))
if w <= 0 || h <= 0 {
@@ -166,6 +167,11 @@ func (d *decoder) parseIHDR(length uint32) error {
if nPixels != int64(int(nPixels)) {
return UnsupportedError("dimension overflow")
}
+ // There can be up to 8 bytes per pixel, for 16 bits per channel RGBA.
+ if nPixels != (nPixels*8)/8 {
+ return UnsupportedError("dimension overflow")
+ }
+
d.cb = cbInvalid
d.depth = int(d.tmp[8])
switch d.depth {
diff --git a/libgo/go/image/png/reader_test.go b/libgo/go/image/png/reader_test.go
index cabf533adcd..66bcfcb437e 100644
--- a/libgo/go/image/png/reader_test.go
+++ b/libgo/go/image/png/reader_test.go
@@ -589,7 +589,7 @@ func TestUnknownChunkLengthUnderflow(t *testing.T) {
}
func TestGray8Transparent(t *testing.T) {
- // These bytes come from https://github.com/golang/go/issues/19553
+ // These bytes come from https://golang.org/issues/19553
m, err := Decode(bytes.NewReader([]byte{
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52,
0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x0b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x85, 0x2c, 0x88,
@@ -649,21 +649,38 @@ func TestGray8Transparent(t *testing.T) {
}
}
+func TestDimensionOverflow(t *testing.T) {
+ // These bytes come from https://golang.org/issues/22304
+ //
+ // It encodes a 2147483646 × 2147483646 (i.e. 0x7ffffffe × 0x7ffffffe)
+ // NRGBA image. The (width × height) per se doesn't overflow an int64, but
+ // (width × height × bytesPerPixel) will.
+ _, err := Decode(bytes.NewReader([]byte{
+ 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52,
+ 0x7f, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0xff, 0xfe, 0x08, 0x06, 0x00, 0x00, 0x00, 0x30, 0x57, 0xb3,
+ 0xfd, 0x00, 0x00, 0x00, 0x15, 0x49, 0x44, 0x41, 0x54, 0x78, 0x9c, 0x62, 0x62, 0x20, 0x12, 0x8c,
+ 0x2a, 0xa4, 0xb3, 0x42, 0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0x13, 0x38, 0x00, 0x15, 0x2d, 0xef,
+ 0x5f, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82,
+ }))
+ if _, ok := err.(UnsupportedError); !ok {
+ t.Fatalf("Decode: got %v (of type %T), want non-nil error (of type png.UnsupportedError)", err, err)
+ }
+}
+
func benchmarkDecode(b *testing.B, filename string, bytesPerPixel int) {
- b.StopTimer()
data, err := ioutil.ReadFile(filename)
if err != nil {
b.Fatal(err)
}
- s := string(data)
- cfg, err := DecodeConfig(strings.NewReader(s))
+ cfg, err := DecodeConfig(bytes.NewReader(data))
if err != nil {
b.Fatal(err)
}
b.SetBytes(int64(cfg.Width * cfg.Height * bytesPerPixel))
- b.StartTimer()
+ b.ReportAllocs()
+ b.ResetTimer()
for i := 0; i < b.N; i++ {
- Decode(strings.NewReader(s))
+ Decode(bytes.NewReader(data))
}
}
diff --git a/libgo/go/image/png/writer_test.go b/libgo/go/image/png/writer_test.go
index b1f97b1d7bf..1107ea0e7fc 100644
--- a/libgo/go/image/png/writer_test.go
+++ b/libgo/go/image/png/writer_test.go
@@ -121,10 +121,10 @@ func TestSubImage(t *testing.T) {
}
func BenchmarkEncodeGray(b *testing.B) {
- b.StopTimer()
img := image.NewGray(image.Rect(0, 0, 640, 480))
b.SetBytes(640 * 480 * 1)
- b.StartTimer()
+ b.ReportAllocs()
+ b.ResetTimer()
for i := 0; i < b.N; i++ {
Encode(ioutil.Discard, img)
}
@@ -143,20 +143,19 @@ func (p *pool) Put(b *EncoderBuffer) {
}
func BenchmarkEncodeGrayWithBufferPool(b *testing.B) {
- b.StopTimer()
img := image.NewGray(image.Rect(0, 0, 640, 480))
e := Encoder{
BufferPool: &pool{},
}
b.SetBytes(640 * 480 * 1)
- b.StartTimer()
+ b.ReportAllocs()
+ b.ResetTimer()
for i := 0; i < b.N; i++ {
e.Encode(ioutil.Discard, img)
}
}
func BenchmarkEncodeNRGBOpaque(b *testing.B) {
- b.StopTimer()
img := image.NewNRGBA(image.Rect(0, 0, 640, 480))
// Set all pixels to 0xFF alpha to force opaque mode.
bo := img.Bounds()
@@ -169,40 +168,40 @@ func BenchmarkEncodeNRGBOpaque(b *testing.B) {
b.Fatal("expected image to be opaque")
}
b.SetBytes(640 * 480 * 4)
- b.StartTimer()
+ b.ReportAllocs()
+ b.ResetTimer()
for i := 0; i < b.N; i++ {
Encode(ioutil.Discard, img)
}
}
func BenchmarkEncodeNRGBA(b *testing.B) {
- b.StopTimer()
img := image.NewNRGBA(image.Rect(0, 0, 640, 480))
if img.Opaque() {
b.Fatal("expected image not to be opaque")
}
b.SetBytes(640 * 480 * 4)
- b.StartTimer()
+ b.ReportAllocs()
+ b.ResetTimer()
for i := 0; i < b.N; i++ {
Encode(ioutil.Discard, img)
}
}
func BenchmarkEncodePaletted(b *testing.B) {
- b.StopTimer()
img := image.NewPaletted(image.Rect(0, 0, 640, 480), color.Palette{
color.RGBA{0, 0, 0, 255},
color.RGBA{255, 255, 255, 255},
})
b.SetBytes(640 * 480 * 1)
- b.StartTimer()
+ b.ReportAllocs()
+ b.ResetTimer()
for i := 0; i < b.N; i++ {
Encode(ioutil.Discard, img)
}
}
func BenchmarkEncodeRGBOpaque(b *testing.B) {
- b.StopTimer()
img := image.NewRGBA(image.Rect(0, 0, 640, 480))
// Set all pixels to 0xFF alpha to force opaque mode.
bo := img.Bounds()
@@ -215,20 +214,21 @@ func BenchmarkEncodeRGBOpaque(b *testing.B) {
b.Fatal("expected image to be opaque")
}
b.SetBytes(640 * 480 * 4)
- b.StartTimer()
+ b.ReportAllocs()
+ b.ResetTimer()
for i := 0; i < b.N; i++ {
Encode(ioutil.Discard, img)
}
}
func BenchmarkEncodeRGBA(b *testing.B) {
- b.StopTimer()
img := image.NewRGBA(image.Rect(0, 0, 640, 480))
if img.Opaque() {
b.Fatal("expected image not to be opaque")
}
b.SetBytes(640 * 480 * 4)
- b.StartTimer()
+ b.ReportAllocs()
+ b.ResetTimer()
for i := 0; i < b.N; i++ {
Encode(ioutil.Discard, img)
}