diff options
author | Ian Lance Taylor <iant@golang.org> | 2018-01-09 01:23:08 +0000 |
---|---|---|
committer | Ian Lance Taylor <ian@gcc.gnu.org> | 2018-01-09 01:23:08 +0000 |
commit | 1a2f01efa63036a5104f203a4789e682c0e0915d (patch) | |
tree | 373e15778dc8295354584e1f86915ae493b604ff /libgo/go/image | |
parent | 8799df67f2dab88f9fda11739c501780a85575e2 (diff) | |
download | gcc-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.go | 33 | ||||
-rw-r--r-- | libgo/go/image/color/color_test.go | 47 | ||||
-rw-r--r-- | libgo/go/image/draw/draw.go | 24 | ||||
-rw-r--r-- | libgo/go/image/draw/draw_test.go | 45 | ||||
-rw-r--r-- | libgo/go/image/gif/reader.go | 339 | ||||
-rw-r--r-- | libgo/go/image/gif/reader_test.go | 58 | ||||
-rw-r--r-- | libgo/go/image/gif/writer.go | 144 | ||||
-rw-r--r-- | libgo/go/image/gif/writer_test.go | 42 | ||||
-rw-r--r-- | libgo/go/image/jpeg/reader_test.go | 4 | ||||
-rw-r--r-- | libgo/go/image/jpeg/writer_test.go | 8 | ||||
-rw-r--r-- | libgo/go/image/png/reader.go | 6 | ||||
-rw-r--r-- | libgo/go/image/png/reader_test.go | 29 | ||||
-rw-r--r-- | libgo/go/image/png/writer_test.go | 28 |
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) } |