diff options
author | Ian Lance Taylor <iant@golang.org> | 2017-09-14 17:11:35 +0000 |
---|---|---|
committer | Ian Lance Taylor <ian@gcc.gnu.org> | 2017-09-14 17:11:35 +0000 |
commit | bc998d034f45d1828a8663b2eed928faf22a7d01 (patch) | |
tree | 8d262a22ca7318f4bcd64269fe8fe9e45bcf8d0f /libgo/go/image | |
parent | a41a6142df74219f596e612d3a7775f68ca6e96f (diff) |
libgo: update to go1.9
Reviewed-on: https://go-review.googlesource.com/63753
From-SVN: r252767
Diffstat (limited to 'libgo/go/image')
-rw-r--r-- | libgo/go/image/color/ycbcr.go | 58 | ||||
-rw-r--r-- | libgo/go/image/geom.go | 6 | ||||
-rw-r--r-- | libgo/go/image/geom_test.go | 7 | ||||
-rw-r--r-- | libgo/go/image/gif/reader.go | 45 | ||||
-rw-r--r-- | libgo/go/image/gif/reader_test.go | 63 | ||||
-rw-r--r-- | libgo/go/image/gif/writer.go | 39 | ||||
-rw-r--r-- | libgo/go/image/gif/writer_test.go | 61 | ||||
-rw-r--r-- | libgo/go/image/image_test.go | 12 | ||||
-rw-r--r-- | libgo/go/image/internal/imageutil/gen.go | 2 | ||||
-rw-r--r-- | libgo/go/image/internal/imageutil/impl.go | 8 | ||||
-rw-r--r-- | libgo/go/image/jpeg/huffman.go | 3 | ||||
-rw-r--r-- | libgo/go/image/jpeg/reader.go | 17 | ||||
-rw-r--r-- | libgo/go/image/jpeg/scan.go | 10 | ||||
-rw-r--r-- | libgo/go/image/jpeg/writer.go | 29 | ||||
-rw-r--r-- | libgo/go/image/jpeg/writer_test.go | 58 | ||||
-rw-r--r-- | libgo/go/image/png/reader.go | 17 | ||||
-rw-r--r-- | libgo/go/image/png/reader_test.go | 71 | ||||
-rw-r--r-- | libgo/go/image/png/writer.go | 106 | ||||
-rw-r--r-- | libgo/go/image/png/writer_test.go | 25 |
19 files changed, 534 insertions, 103 deletions
diff --git a/libgo/go/image/color/ycbcr.go b/libgo/go/image/color/ycbcr.go index 18d1a568aac..fd2443078c4 100644 --- a/libgo/go/image/color/ycbcr.go +++ b/libgo/go/image/color/ycbcr.go @@ -61,8 +61,58 @@ func YCbCrToRGB(y, cb, cr uint8) (uint8, uint8, uint8) { // G = Y' - 0.34414*(Cb-128) - 0.71414*(Cr-128) // B = Y' + 1.77200*(Cb-128) // http://www.w3.org/Graphics/JPEG/jfif3.pdf says Y but means Y'. - - yy1 := int32(y) * 0x010100 // Convert 0x12 to 0x121200. + // + // Those formulae use non-integer multiplication factors. When computing, + // integer math is generally faster than floating point math. We multiply + // all of those factors by 1<<16 and round to the nearest integer: + // 91881 = roundToNearestInteger(1.40200 * 65536). + // 22554 = roundToNearestInteger(0.34414 * 65536). + // 46802 = roundToNearestInteger(0.71414 * 65536). + // 116130 = roundToNearestInteger(1.77200 * 65536). + // + // Adding a rounding adjustment in the range [0, 1<<16-1] and then shifting + // right by 16 gives us an integer math version of the original formulae. + // R = (65536*Y' + 91881 *(Cr-128) + adjustment) >> 16 + // G = (65536*Y' - 22554 *(Cb-128) - 46802*(Cr-128) + adjustment) >> 16 + // B = (65536*Y' + 116130 *(Cb-128) + adjustment) >> 16 + // A constant rounding adjustment of 1<<15, one half of 1<<16, would mean + // round-to-nearest when dividing by 65536 (shifting right by 16). + // Similarly, a constant rounding adjustment of 0 would mean round-down. + // + // Defining YY1 = 65536*Y' + adjustment simplifies the formulae and + // requires fewer CPU operations: + // R = (YY1 + 91881 *(Cr-128) ) >> 16 + // G = (YY1 - 22554 *(Cb-128) - 46802*(Cr-128)) >> 16 + // B = (YY1 + 116130 *(Cb-128) ) >> 16 + // + // The inputs (y, cb, cr) are 8 bit color, ranging in [0x00, 0xff]. In this + // function, the output is also 8 bit color, but in the related YCbCr.RGBA + // method, below, the output is 16 bit color, ranging in [0x0000, 0xffff]. + // Outputting 16 bit color simply requires changing the 16 to 8 in the "R = + // etc >> 16" equation, and likewise for G and B. + // + // As mentioned above, a constant rounding adjustment of 1<<15 is a natural + // choice, but there is an additional constraint: if c0 := YCbCr{Y: y, Cb: + // 0x80, Cr: 0x80} and c1 := Gray{Y: y} then c0.RGBA() should equal + // c1.RGBA(). Specifically, if y == 0 then "R = etc >> 8" should yield + // 0x0000 and if y == 0xff then "R = etc >> 8" should yield 0xffff. If we + // used a constant rounding adjustment of 1<<15, then it would yield 0x0080 + // and 0xff80 respectively. + // + // Note that when cb == 0x80 and cr == 0x80 then the formulae collapse to: + // R = YY1 >> n + // G = YY1 >> n + // B = YY1 >> n + // where n is 16 for this function (8 bit color output) and 8 for the + // YCbCr.RGBA method (16 bit color output). + // + // The solution is to make the rounding adjustment non-constant, and equal + // to 257*Y', which ranges over [0, 1<<16-1] as Y' ranges over [0, 255]. + // YY1 is then defined as: + // YY1 = 65536*Y' + 257*Y' + // or equivalently: + // YY1 = Y' * 0x10101 + yy1 := int32(y) * 0x10101 cb1 := int32(cb) - 128 cr1 := int32(cr) - 128 @@ -136,7 +186,7 @@ func (c YCbCr) RGBA() (uint32, uint32, uint32, uint32) { // 0x7e18 0x808d 0x7db9 // 0x7e7e 0x8080 0x7d7d - yy1 := int32(c.Y) * 0x10100 // Convert 0x12 to 0x121200. + yy1 := int32(c.Y) * 0x10101 cb1 := int32(c.Cb) - 128 cr1 := int32(c.Cr) - 128 @@ -196,7 +246,7 @@ type NYCbCrA struct { func (c NYCbCrA) RGBA() (uint32, uint32, uint32, uint32) { // The first part of this method is the same as YCbCr.RGBA. - yy1 := int32(c.Y) * 0x10100 // Convert 0x12 to 0x121200. + yy1 := int32(c.Y) * 0x10101 cb1 := int32(c.Cb) - 128 cr1 := int32(c.Cr) - 128 diff --git a/libgo/go/image/geom.go b/libgo/go/image/geom.go index e1cd4dc1e3e..ed7dde2c84d 100644 --- a/libgo/go/image/geom.go +++ b/libgo/go/image/geom.go @@ -161,7 +161,11 @@ func (r Rectangle) Intersect(s Rectangle) Rectangle { if r.Max.Y > s.Max.Y { r.Max.Y = s.Max.Y } - if r.Min.X > r.Max.X || r.Min.Y > r.Max.Y { + // Letting r0 and s0 be the values of r and s at the time that the method + // is called, this next line is equivalent to: + // + // if max(r0.Min.X, s0.Min.X) >= min(r0.Max.X, s0.Max.X) || likewiseForY { etc } + if r.Empty() { return ZR } return r diff --git a/libgo/go/image/geom_test.go b/libgo/go/image/geom_test.go index 6e9c6a13c2c..9fede027218 100644 --- a/libgo/go/image/geom_test.go +++ b/libgo/go/image/geom_test.go @@ -28,6 +28,7 @@ func TestRectangle(t *testing.T) { rects := []Rectangle{ Rect(0, 0, 10, 10), + Rect(10, 0, 20, 10), Rect(1, 2, 3, 4), Rect(4, 6, 10, 10), Rect(2, 3, 12, 5), @@ -62,9 +63,9 @@ func TestRectangle(t *testing.T) { if err := in(a, s); err != nil { t.Errorf("Intersect: r=%s, s=%s, a=%s, a not in s: %v", r, s, a, err) } - if a.Empty() == r.Overlaps(s) { - t.Errorf("Intersect: r=%s, s=%s, a=%s: empty=%t same as overlaps=%t", - r, s, a, a.Empty(), r.Overlaps(s)) + if isZero, overlaps := a == (Rectangle{}), r.Overlaps(s); isZero == overlaps { + t.Errorf("Intersect: r=%s, s=%s, a=%s: isZero=%t same as overlaps=%t", + r, s, a, isZero, overlaps) } largerThanA := [4]Rectangle{a, a, a, a} largerThanA[0].Min.X-- diff --git a/libgo/go/image/gif/reader.go b/libgo/go/image/gif/reader.go index e61112817b7..b1335e61259 100644 --- a/libgo/go/image/gif/reader.go +++ b/libgo/go/image/gif/reader.go @@ -231,8 +231,8 @@ func (d *decoder) decode(r io.Reader, configOnly bool) error { } return errNotEnough } - // Both lzwr and br should be exhausted. Reading from them should - // yield (0, io.EOF). + // 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 @@ -248,11 +248,21 @@ func (d *decoder) decode(r io.Reader, configOnly bool) error { } return errTooMuch } - if n, err := br.Read(d.tmp[:1]); n != 0 || err != io.EOF { + + // 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) } - return errTooMuch } // Check that the color indexes are inside the palette. @@ -410,14 +420,29 @@ func (d *decoder) newImageFromDescriptor() (*image.Paletted, error) { height := int(d.tmp[6]) + int(d.tmp[7])<<8 d.imageFields = d.tmp[8] - // The GIF89a spec, Section 20 (Image Descriptor) says: - // "Each image must fit within the boundaries of the Logical - // Screen, as defined in the Logical Screen Descriptor." - bounds := image.Rect(left, top, left+width, top+height) - if bounds != bounds.Intersect(image.Rect(0, 0, d.width, d.height)) { + // The GIF89a spec, Section 20 (Image Descriptor) says: "Each image must + // fit within the boundaries of the Logical Screen, as defined in the + // Logical Screen Descriptor." + // + // This is conceptually similar to testing + // frameBounds := image.Rect(left, top, left+width, top+height) + // imageBounds := image.Rect(0, 0, d.width, d.height) + // if !frameBounds.In(imageBounds) { etc } + // but the semantics of the Go image.Rectangle type is that r.In(s) is true + // whenever r is an empty rectangle, even if r.Min.X > s.Max.X. Here, we + // want something stricter. + // + // Note that, by construction, left >= 0 && top >= 0, so we only have to + // explicitly compare frameBounds.Max (left+width, top+height) against + // imageBounds.Max (d.width, d.height) and not frameBounds.Min (left, top) + // against imageBounds.Min (0, 0). + if left+width > d.width || top+height > d.height { return nil, errors.New("gif: frame bounds larger than image bounds") } - return image.NewPaletted(bounds, nil), nil + return image.NewPaletted(image.Rectangle{ + Min: image.Point{left, top}, + Max: image.Point{left + width, top + height}, + }, nil), nil } func (d *decoder) readBlock() (int, error) { diff --git a/libgo/go/image/gif/reader_test.go b/libgo/go/image/gif/reader_test.go index 1267ba06a9d..51c64b7328f 100644 --- a/libgo/go/image/gif/reader_test.go +++ b/libgo/go/image/gif/reader_test.go @@ -37,16 +37,35 @@ func lzwEncode(in []byte) []byte { } func TestDecode(t *testing.T) { + // extra contains superfluous bytes to inject into the GIF, either at the end + // of an existing data sub-block (past the LZW End of Information code) or in + // a separate data sub-block. The 0x02 values are arbitrary. + const extra = "\x02\x02\x02\x02" + testCases := []struct { - nPix int // The number of pixels in the image data. - extra bool // Whether to write an extra block after the LZW-encoded data. - wantErr error + nPix int // The number of pixels in the image data. + // If non-zero, write this many extra bytes inside the data sub-block + // containing the LZW end code. + extraExisting int + // If non-zero, write an extra block of this many bytes. + extraSeparate int + wantErr error }{ - {0, false, errNotEnough}, - {1, false, errNotEnough}, - {2, false, nil}, - {2, true, errTooMuch}, - {3, false, errTooMuch}, + {0, 0, 0, errNotEnough}, + {1, 0, 0, errNotEnough}, + {2, 0, 0, nil}, + // An extra data sub-block after the compressed section with 1 byte which we + // silently skip. + {2, 0, 1, nil}, + // An extra data sub-block after the compressed section with 2 bytes. In + // this case we complain that there is too much data. + {2, 0, 2, errTooMuch}, + // Too much pixel data. + {3, 0, 0, errTooMuch}, + // An extra byte after LZW data, but inside the same data sub-block. + {2, 1, 0, nil}, + // Two extra bytes after LZW data, but inside the same data sub-block. + {2, 2, 0, nil}, } for _, tc := range testCases { b := &bytes.Buffer{} @@ -59,22 +78,35 @@ func TestDecode(t *testing.T) { b.WriteString("\x2c\x00\x00\x00\x00\x02\x00\x01\x00\x00\x02") if tc.nPix > 0 { enc := lzwEncode(make([]byte, tc.nPix)) - if len(enc) > 0xff { - t.Errorf("nPix=%d, extra=%t: compressed length %d is too large", tc.nPix, tc.extra, len(enc)) + if len(enc)+tc.extraExisting > 0xff { + t.Errorf("nPix=%d, extraExisting=%d, extraSeparate=%d: compressed length %d is too large", + tc.nPix, tc.extraExisting, tc.extraSeparate, len(enc)) continue } - b.WriteByte(byte(len(enc))) + + // Write the size of the data sub-block containing the LZW data. + b.WriteByte(byte(len(enc) + tc.extraExisting)) + + // Write the LZW data. b.Write(enc) + + // Write extra bytes inside the same data sub-block where LZW data + // ended. Each arbitrarily 0x02. + b.WriteString(extra[:tc.extraExisting]) } - if tc.extra { - b.WriteString("\x01\x02") // A 1-byte payload with an 0x02 byte. + + if tc.extraSeparate > 0 { + // Data sub-block size. This indicates how many extra bytes follow. + b.WriteByte(byte(tc.extraSeparate)) + b.WriteString(extra[:tc.extraSeparate]) } b.WriteByte(0x00) // An empty block signifies the end of the image data. b.WriteString(trailerStr) got, err := Decode(b) if err != tc.wantErr { - t.Errorf("nPix=%d, extra=%t\ngot %v\nwant %v", tc.nPix, tc.extra, err, tc.wantErr) + t.Errorf("nPix=%d, extraExisting=%d, extraSeparate=%d\ngot %v\nwant %v", + tc.nPix, tc.extraExisting, tc.extraSeparate, err, tc.wantErr) } if tc.wantErr != nil { @@ -90,7 +122,8 @@ func TestDecode(t *testing.T) { }, } if !reflect.DeepEqual(got, want) { - t.Errorf("nPix=%d, extra=%t\ngot %v\nwant %v", tc.nPix, tc.extra, got, want) + t.Errorf("nPix=%d, extraExisting=%d, extraSeparate=%d\ngot %v\nwant %v", + tc.nPix, tc.extraExisting, tc.extraSeparate, got, want) } } } diff --git a/libgo/go/image/gif/writer.go b/libgo/go/image/gif/writer.go index 1918196884d..493c7549eb2 100644 --- a/libgo/go/image/gif/writer.go +++ b/libgo/go/image/gif/writer.go @@ -132,7 +132,12 @@ func (e *encoder) writeHeader() { e.buf[1] = e.g.BackgroundIndex e.buf[2] = 0x00 // Pixel Aspect Ratio. e.write(e.buf[:3]) - e.globalCT = encodeColorTable(e.globalColorTable[:], p, paddedSize) + var err error + e.globalCT, err = encodeColorTable(e.globalColorTable[:], p, paddedSize) + if err != nil && e.err == nil { + e.err = err + return + } e.write(e.globalColorTable[:e.globalCT]) } else { // All frames have a local color table, so a global color table @@ -149,8 +154,9 @@ func (e *encoder) writeHeader() { e.buf[1] = 0xff // Application Label. e.buf[2] = 0x0b // Block Size. e.write(e.buf[:3]) - _, e.err = io.WriteString(e.w, "NETSCAPE2.0") // Application Identifier. - if e.err != nil { + _, err := io.WriteString(e.w, "NETSCAPE2.0") // Application Identifier. + if err != nil && e.err == nil { + e.err = err return } e.buf[0] = 0x03 // Block Size. @@ -161,11 +167,18 @@ func (e *encoder) writeHeader() { } } -func encodeColorTable(dst []byte, p color.Palette, size int) int { +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) { - r, g, b, _ := p[i].RGBA() + 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) @@ -176,7 +189,7 @@ func encodeColorTable(dst []byte, p color.Palette, size int) int { dst[3*i+2] = 0x00 } } - return 3 * n + return 3 * n, nil } func (e *encoder) writeImageBlock(pm *image.Paletted, delay int, disposal byte) { @@ -201,6 +214,10 @@ func (e *encoder) writeImageBlock(pm *image.Paletted, delay int, disposal byte) transparentIndex := -1 for i, c := range pm.Palette { + if c == nil { + e.err = errors.New("gif: cannot encode color table with nil entries") + return + } if _, _, _, a := c.RGBA(); a == 0 { transparentIndex = i break @@ -235,8 +252,12 @@ func (e *encoder) writeImageBlock(pm *image.Paletted, delay int, disposal byte) e.write(e.buf[:9]) paddedSize := log2(len(pm.Palette)) // Size of Local Color Table: 2^(1+n). - ct := encodeColorTable(e.localColorTable[:], pm.Palette, paddedSize) - if ct != e.globalCT || !bytes.Equal(e.globalColorTable[:ct], e.localColorTable[:ct]) { + 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]) @@ -253,7 +274,7 @@ func (e *encoder) writeImageBlock(pm *image.Paletted, delay int, disposal byte) lzww := lzw.NewWriter(blockWriter{e: e}, lzw.LSB, litWidth) if dx := b.Dx(); dx == pm.Stride { - _, e.err = lzww.Write(pm.Pix) + _, e.err = lzww.Write(pm.Pix[:dx*b.Dy()]) if e.err != nil { lzww.Close() return diff --git a/libgo/go/image/gif/writer_test.go b/libgo/go/image/gif/writer_test.go index 775ccea31dc..1bba9b8ece5 100644 --- a/libgo/go/image/gif/writer_test.go +++ b/libgo/go/image/gif/writer_test.go @@ -438,6 +438,67 @@ func TestEncodePalettes(t *testing.T) { } } +func TestEncodeBadPalettes(t *testing.T) { + const w, h = 5, 5 + for _, n := range []int{256, 257} { + for _, nilColors := range []bool{false, true} { + pal := make(color.Palette, n) + if !nilColors { + for i := range pal { + pal[i] = color.Black + } + } + + err := EncodeAll(ioutil.Discard, &GIF{ + Image: []*image.Paletted{ + image.NewPaletted(image.Rect(0, 0, w, h), pal), + }, + Delay: make([]int, 1), + Disposal: make([]byte, 1), + Config: image.Config{ + ColorModel: pal, + Width: w, + Height: h, + }, + }) + + got := err != nil + want := n > 256 || nilColors + if got != want { + t.Errorf("n=%d, nilColors=%t: err != nil: got %t, want %t", n, nilColors, got, want) + } + } + } +} + +func TestEncodeCroppedSubImages(t *testing.T) { + // This test means to ensure that Encode honors the Bounds and Strides of + // images correctly when encoding. + whole := image.NewPaletted(image.Rect(0, 0, 100, 100), palette.Plan9) + subImages := []image.Rectangle{ + image.Rect(0, 0, 50, 50), + image.Rect(50, 0, 100, 50), + image.Rect(0, 50, 50, 50), + image.Rect(50, 50, 100, 100), + image.Rect(25, 25, 75, 75), + image.Rect(0, 0, 100, 50), + image.Rect(0, 50, 100, 100), + image.Rect(0, 0, 50, 100), + image.Rect(50, 0, 100, 100), + } + for _, sr := range subImages { + si := whole.SubImage(sr) + buf := bytes.NewBuffer(nil) + if err := Encode(buf, si, nil); err != nil { + t.Errorf("Encode: sr=%v: %v", sr, err) + continue + } + if _, err := Decode(buf); err != nil { + t.Errorf("Decode: sr=%v: %v", sr, err) + } + } +} + func BenchmarkEncode(b *testing.B) { b.StopTimer() diff --git a/libgo/go/image/image_test.go b/libgo/go/image/image_test.go index 799c1a7a11d..08ba61ea0c7 100644 --- a/libgo/go/image/image_test.go +++ b/libgo/go/image/image_test.go @@ -16,7 +16,7 @@ type image interface { SubImage(Rectangle) Image } -func cmp(t *testing.T, cm color.Model, c0, c1 color.Color) bool { +func cmp(cm color.Model, c0, c1 color.Color) bool { r0, g0, b0, a0 := cm.Convert(c0).RGBA() r1, g1, b1, a1 := cm.Convert(c1).RGBA() return r0 == r1 && g0 == g1 && b0 == b1 && a0 == a1 @@ -42,12 +42,12 @@ func TestImage(t *testing.T) { t.Errorf("%T: want bounds %v, got %v", m, Rect(0, 0, 10, 10), m.Bounds()) continue } - if !cmp(t, m.ColorModel(), Transparent, m.At(6, 3)) { + if !cmp(m.ColorModel(), Transparent, m.At(6, 3)) { t.Errorf("%T: at (6, 3), want a zero color, got %v", m, m.At(6, 3)) continue } m.Set(6, 3, Opaque) - if !cmp(t, m.ColorModel(), Opaque, m.At(6, 3)) { + if !cmp(m.ColorModel(), Opaque, m.At(6, 3)) { t.Errorf("%T: at (6, 3), want a non-zero color, got %v", m, m.At(6, 3)) continue } @@ -60,16 +60,16 @@ func TestImage(t *testing.T) { t.Errorf("%T: sub-image want bounds %v, got %v", m, Rect(3, 2, 9, 8), m.Bounds()) continue } - if !cmp(t, m.ColorModel(), Opaque, m.At(6, 3)) { + if !cmp(m.ColorModel(), Opaque, m.At(6, 3)) { t.Errorf("%T: sub-image at (6, 3), want a non-zero color, got %v", m, m.At(6, 3)) continue } - if !cmp(t, m.ColorModel(), Transparent, m.At(3, 3)) { + if !cmp(m.ColorModel(), Transparent, m.At(3, 3)) { t.Errorf("%T: sub-image at (3, 3), want a zero color, got %v", m, m.At(3, 3)) continue } m.Set(3, 3, Opaque) - if !cmp(t, m.ColorModel(), Opaque, m.At(3, 3)) { + if !cmp(m.ColorModel(), Opaque, m.At(3, 3)) { t.Errorf("%T: sub-image at (3, 3), want a non-zero color, got %v", m, m.At(3, 3)) continue } diff --git a/libgo/go/image/internal/imageutil/gen.go b/libgo/go/image/internal/imageutil/gen.go index 6792b28a45b..8b2c42703a7 100644 --- a/libgo/go/image/internal/imageutil/gen.go +++ b/libgo/go/image/internal/imageutil/gen.go @@ -95,7 +95,7 @@ const sratioCase = ` %s // This is an inline version of image/color/ycbcr.go's func YCbCrToRGB. - yy1 := int32(src.Y[yi]) * 0x010100 // Convert 0x12 to 0x121200. + yy1 := int32(src.Y[yi]) * 0x10101 cb1 := int32(src.Cb[ci]) - 128 cr1 := int32(src.Cr[ci]) - 128 diff --git a/libgo/go/image/internal/imageutil/impl.go b/libgo/go/image/internal/imageutil/impl.go index 3696b08e419..cfd5047879a 100644 --- a/libgo/go/image/internal/imageutil/impl.go +++ b/libgo/go/image/internal/imageutil/impl.go @@ -44,7 +44,7 @@ func DrawYCbCr(dst *image.RGBA, r image.Rectangle, src *image.YCbCr, sp image.Po for x := x0; x != x1; x, yi, ci = x+4, yi+1, ci+1 { // This is an inline version of image/color/ycbcr.go's func YCbCrToRGB. - yy1 := int32(src.Y[yi]) * 0x010100 // Convert 0x12 to 0x121200. + yy1 := int32(src.Y[yi]) * 0x10101 cb1 := int32(src.Cb[ci]) - 128 cr1 := int32(src.Cr[ci]) - 128 @@ -101,7 +101,7 @@ func DrawYCbCr(dst *image.RGBA, r image.Rectangle, src *image.YCbCr, sp image.Po ci := ciBase + sx/2 // This is an inline version of image/color/ycbcr.go's func YCbCrToRGB. - yy1 := int32(src.Y[yi]) * 0x010100 // Convert 0x12 to 0x121200. + yy1 := int32(src.Y[yi]) * 0x10101 cb1 := int32(src.Cb[ci]) - 128 cr1 := int32(src.Cr[ci]) - 128 @@ -158,7 +158,7 @@ func DrawYCbCr(dst *image.RGBA, r image.Rectangle, src *image.YCbCr, sp image.Po ci := ciBase + sx/2 // This is an inline version of image/color/ycbcr.go's func YCbCrToRGB. - yy1 := int32(src.Y[yi]) * 0x010100 // Convert 0x12 to 0x121200. + yy1 := int32(src.Y[yi]) * 0x10101 cb1 := int32(src.Cb[ci]) - 128 cr1 := int32(src.Cr[ci]) - 128 @@ -214,7 +214,7 @@ func DrawYCbCr(dst *image.RGBA, r image.Rectangle, src *image.YCbCr, sp image.Po for x := x0; x != x1; x, yi, ci = x+4, yi+1, ci+1 { // This is an inline version of image/color/ycbcr.go's func YCbCrToRGB. - yy1 := int32(src.Y[yi]) * 0x010100 // Convert 0x12 to 0x121200. + yy1 := int32(src.Y[yi]) * 0x10101 cb1 := int32(src.Cb[ci]) - 128 cr1 := int32(src.Cr[ci]) - 128 diff --git a/libgo/go/image/jpeg/huffman.go b/libgo/go/image/jpeg/huffman.go index 4f8fe8eff32..95aaf71e2f3 100644 --- a/libgo/go/image/jpeg/huffman.go +++ b/libgo/go/image/jpeg/huffman.go @@ -101,7 +101,8 @@ func (d *decoder) processDHT(n int) error { return FormatError("bad Tc value") } th := d.tmp[0] & 0x0f - if th > maxTh || !d.progressive && th > 1 { + // The baseline th <= 1 restriction is specified in table B.5. + if th > maxTh || (d.baseline && th > 1) { return FormatError("bad Th value") } h := &d.huff[tc][th] diff --git a/libgo/go/image/jpeg/reader.go b/libgo/go/image/jpeg/reader.go index c5834219a3e..a915e96a4cf 100644 --- a/libgo/go/image/jpeg/reader.go +++ b/libgo/go/image/jpeg/reader.go @@ -48,7 +48,7 @@ const ( ) const ( - sof0Marker = 0xc0 // Start Of Frame (Baseline). + sof0Marker = 0xc0 // Start Of Frame (Baseline Sequential). sof1Marker = 0xc1 // Start Of Frame (Extended Sequential). sof2Marker = 0xc2 // Start Of Frame (Progressive). dhtMarker = 0xc4 // Define Huffman Table. @@ -126,9 +126,17 @@ type decoder struct { blackPix []byte blackStride int - ri int // Restart Interval. - nComp int - progressive bool + ri int // Restart Interval. + nComp int + + // As per section 4.5, there are four modes of operation (selected by the + // SOF? markers): sequential DCT, progressive DCT, lossless and + // hierarchical, although this implementation does not support the latter + // two non-DCT modes. Sequential DCT is further split into baseline and + // extended, as per section 4.11. + baseline bool + progressive bool + jfif bool adobeTransformValid bool adobeTransform uint8 @@ -596,6 +604,7 @@ func (d *decoder) decode(r io.Reader, configOnly bool) (image.Image, error) { switch marker { case sof0Marker, sof1Marker, sof2Marker: + d.baseline = marker == sof0Marker d.progressive = marker == sof2Marker err = d.processSOF(n) if configOnly && d.jfif { diff --git a/libgo/go/image/jpeg/scan.go b/libgo/go/image/jpeg/scan.go index e1104d27c23..712e7e35ff8 100644 --- a/libgo/go/image/jpeg/scan.go +++ b/libgo/go/image/jpeg/scan.go @@ -92,12 +92,13 @@ func (d *decoder) processSOS(n int) error { } totalHV += d.comp[compIndex].h * d.comp[compIndex].v + // The baseline t <= 1 restriction is specified in table B.3. scan[i].td = d.tmp[2+2*i] >> 4 - if scan[i].td > maxTh { + if t := scan[i].td; t > maxTh || (d.baseline && t > 1) { return FormatError("bad Td value") } scan[i].ta = d.tmp[2+2*i] & 0x0f - if scan[i].ta > maxTh { + if t := scan[i].ta; t > maxTh || (d.baseline && t > 1) { return FormatError("bad Ta value") } } @@ -122,7 +123,8 @@ func (d *decoder) processSOS(n int) error { // by the second-least significant bit, followed by the least // significant bit. // - // For baseline JPEGs, these parameters are hard-coded to 0/63/0/0. + // For sequential JPEGs, these parameters are hard-coded to 0/63/0/0, as + // per table B.3. zigStart, zigEnd, ah, al := int32(0), int32(blockSize-1), uint32(0), uint32(0) if d.progressive { zigStart = int32(d.tmp[1+2*nComp]) @@ -177,7 +179,7 @@ func (d *decoder) processSOS(n int) error { // The blocks are traversed one MCU at a time. For 4:2:0 chroma // subsampling, there are four Y 8x8 blocks in every 16x16 MCU. // - // For a baseline 32x16 pixel image, the Y blocks visiting order is: + // For a sequential 32x16 pixel image, the Y blocks visiting order is: // 0 1 4 5 // 2 3 6 7 // diff --git a/libgo/go/image/jpeg/writer.go b/libgo/go/image/jpeg/writer.go index 91bbde3bf80..a6004990047 100644 --- a/libgo/go/image/jpeg/writer.go +++ b/libgo/go/image/jpeg/writer.go @@ -311,7 +311,7 @@ func (e *encoder) writeDQT() { } } -// writeSOF0 writes the Start Of Frame (Baseline) marker. +// writeSOF0 writes the Start Of Frame (Baseline Sequential) marker. func (e *encoder) writeSOF0(size image.Point, nComponent int) { markerlen := 8 + 3*nComponent e.writeMarkerHeader(sof0Marker, markerlen) @@ -441,6 +441,30 @@ func rgbaToYCbCr(m *image.RGBA, p image.Point, yBlock, cbBlock, crBlock *block) } } +// yCbCrToYCbCr is a specialized version of toYCbCr for image.YCbCr images. +func yCbCrToYCbCr(m *image.YCbCr, p image.Point, yBlock, cbBlock, crBlock *block) { + b := m.Bounds() + xmax := b.Max.X - 1 + ymax := b.Max.Y - 1 + for j := 0; j < 8; j++ { + sy := p.Y + j + if sy > ymax { + sy = ymax + } + for i := 0; i < 8; i++ { + sx := p.X + i + if sx > xmax { + sx = xmax + } + yi := m.YOffset(sx, sy) + ci := m.COffset(sx, sy) + yBlock[8*j+i] = int32(m.Y[yi]) + cbBlock[8*j+i] = int32(m.Cb[ci]) + crBlock[8*j+i] = int32(m.Cr[ci]) + } + } +} + // scale scales the 16x16 region represented by the 4 src blocks to the 8x8 // dst block. func scale(dst *block, src *[4]block) { @@ -510,6 +534,7 @@ func (e *encoder) writeSOS(m image.Image) { } default: rgba, _ := m.(*image.RGBA) + ycbcr, _ := m.(*image.YCbCr) for y := bounds.Min.Y; y < bounds.Max.Y; y += 16 { for x := bounds.Min.X; x < bounds.Max.X; x += 16 { for i := 0; i < 4; i++ { @@ -518,6 +543,8 @@ func (e *encoder) writeSOS(m image.Image) { p := image.Pt(x+xOff, y+yOff) if rgba != nil { rgbaToYCbCr(rgba, p, &b, &cb[i], &cr[i]) + } else if ycbcr != nil { + yCbCrToYCbCr(ycbcr, p, &b, &cb[i], &cr[i]) } else { toYCbCr(m, p, &b, &cb[i], &cr[i]) } diff --git a/libgo/go/image/jpeg/writer_test.go b/libgo/go/image/jpeg/writer_test.go index 3df3cfcc5bb..a6c056174bd 100644 --- a/libgo/go/image/jpeg/writer_test.go +++ b/libgo/go/image/jpeg/writer_test.go @@ -208,7 +208,41 @@ func averageDelta(m0, m1 image.Image) int64 { return sum / n } -func BenchmarkEncode(b *testing.B) { +func TestEncodeYCbCr(t *testing.T) { + bo := image.Rect(0, 0, 640, 480) + imgRGBA := image.NewRGBA(bo) + // Must use 444 subsampling to avoid lossy RGBA to YCbCr conversion. + imgYCbCr := image.NewYCbCr(bo, image.YCbCrSubsampleRatio444) + rnd := rand.New(rand.NewSource(123)) + // Create identical rgba and ycbcr images. + for y := bo.Min.Y; y < bo.Max.Y; y++ { + for x := bo.Min.X; x < bo.Max.X; x++ { + col := color.RGBA{ + uint8(rnd.Intn(256)), + uint8(rnd.Intn(256)), + uint8(rnd.Intn(256)), + 255, + } + imgRGBA.SetRGBA(x, y, col) + yo := imgYCbCr.YOffset(x, y) + co := imgYCbCr.COffset(x, y) + cy, ccr, ccb := color.RGBToYCbCr(col.R, col.G, col.B) + imgYCbCr.Y[yo] = cy + imgYCbCr.Cb[co] = ccr + imgYCbCr.Cr[co] = ccb + } + } + + // Now check that both images are identical after an encode. + var bufRGBA, bufYCbCr bytes.Buffer + Encode(&bufRGBA, imgRGBA, nil) + Encode(&bufYCbCr, imgYCbCr, nil) + if !bytes.Equal(bufRGBA.Bytes(), bufYCbCr.Bytes()) { + t.Errorf("RGBA and YCbCr encoded bytes differ") + } +} + +func BenchmarkEncodeRGBA(b *testing.B) { b.StopTimer() img := image.NewRGBA(image.Rect(0, 0, 640, 480)) bo := img.Bounds() @@ -230,3 +264,25 @@ func BenchmarkEncode(b *testing.B) { Encode(ioutil.Discard, img, options) } } + +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)) + for y := bo.Min.Y; y < bo.Max.Y; y++ { + for x := bo.Min.X; x < bo.Max.X; x++ { + cy := img.YOffset(x, y) + ci := img.COffset(x, y) + img.Y[cy] = uint8(rnd.Intn(256)) + img.Cb[ci] = uint8(rnd.Intn(256)) + img.Cr[ci] = uint8(rnd.Intn(256)) + } + } + b.SetBytes(640 * 480 * 3) + b.StartTimer() + 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 8299df56735..4f043a0e424 100644 --- a/libgo/go/image/png/reader.go +++ b/libgo/go/image/png/reader.go @@ -613,12 +613,19 @@ func (d *decoder) readImagePass(r io.Reader, pass int, allocateOnly bool) (image } case cbG8: if d.useTransparent { - // Match error from Go 1.7 and earlier. - // Go 1.9 will decode this properly. - return nil, chunkOrderError + ty := d.transparent[1] + for x := 0; x < width; x++ { + ycol := cdat[x] + acol := uint8(0xff) + if ycol == ty { + acol = 0x00 + } + nrgba.SetNRGBA(x, y, color.NRGBA{ycol, ycol, ycol, acol}) + } + } else { + copy(gray.Pix[pixOffset:], cdat) + pixOffset += gray.Stride } - copy(gray.Pix[pixOffset:], cdat) - pixOffset += gray.Stride case cbGA8: for x := 0; x < width; x++ { ycol := cdat[2*x+0] diff --git a/libgo/go/image/png/reader_test.go b/libgo/go/image/png/reader_test.go index 503b5dc567b..cabf533adcd 100644 --- a/libgo/go/image/png/reader_test.go +++ b/libgo/go/image/png/reader_test.go @@ -588,6 +588,67 @@ func TestUnknownChunkLengthUnderflow(t *testing.T) { } } +func TestGray8Transparent(t *testing.T) { + // These bytes come from https://github.com/golang/go/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, + 0x80, 0x00, 0x00, 0x00, 0x02, 0x74, 0x52, 0x4e, 0x53, 0x00, 0xff, 0x5b, 0x91, 0x22, 0xb5, 0x00, + 0x00, 0x00, 0x02, 0x62, 0x4b, 0x47, 0x44, 0x00, 0xff, 0x87, 0x8f, 0xcc, 0xbf, 0x00, 0x00, 0x00, + 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, 0x0a, 0xf0, 0x00, 0x00, 0x0a, 0xf0, 0x01, 0x42, 0xac, + 0x34, 0x98, 0x00, 0x00, 0x00, 0x07, 0x74, 0x49, 0x4d, 0x45, 0x07, 0xd5, 0x04, 0x02, 0x12, 0x11, + 0x11, 0xf7, 0x65, 0x3d, 0x8b, 0x00, 0x00, 0x00, 0x4f, 0x49, 0x44, 0x41, 0x54, 0x08, 0xd7, 0x63, + 0xf8, 0xff, 0xff, 0xff, 0xb9, 0xbd, 0x70, 0xf0, 0x8c, 0x01, 0xc8, 0xaf, 0x6e, 0x99, 0x02, 0x05, + 0xd9, 0x7b, 0xc1, 0xfc, 0x6b, 0xff, 0xa1, 0xa0, 0x87, 0x30, 0xff, 0xd9, 0xde, 0xbd, 0xd5, 0x4b, + 0xf7, 0xee, 0xfd, 0x0e, 0xe3, 0xef, 0xcd, 0x06, 0x19, 0x14, 0xf5, 0x1e, 0xce, 0xef, 0x01, 0x31, + 0x92, 0xd7, 0x82, 0x41, 0x31, 0x9c, 0x3f, 0x07, 0x02, 0xee, 0xa1, 0xaa, 0xff, 0xff, 0x9f, 0xe1, + 0xd9, 0x56, 0x30, 0xf8, 0x0e, 0xe5, 0x03, 0x00, 0xa9, 0x42, 0x84, 0x3d, 0xdf, 0x8f, 0xa6, 0x8f, + 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82, + })) + if err != nil { + t.Fatalf("Decode: %v", err) + } + + const hex = "0123456789abcdef" + var got []byte + bounds := m.Bounds() + for y := bounds.Min.Y; y < bounds.Max.Y; y++ { + for x := bounds.Min.X; x < bounds.Max.X; x++ { + if r, _, _, a := m.At(x, y).RGBA(); a != 0 { + got = append(got, + hex[0x0f&(r>>12)], + hex[0x0f&(r>>8)], + ' ', + ) + } else { + got = append(got, + '.', + '.', + ' ', + ) + } + } + got = append(got, '\n') + } + + const want = "" + + ".. .. .. ce bd bd bd bd bd bd bd bd bd bd e6 \n" + + ".. .. .. 7b 84 94 94 94 94 94 94 94 94 6b bd \n" + + ".. .. .. 7b d6 .. .. .. .. .. .. .. .. 8c bd \n" + + ".. .. .. 7b d6 .. .. .. .. .. .. .. .. 8c bd \n" + + ".. .. .. 7b d6 .. .. .. .. .. .. .. .. 8c bd \n" + + "e6 bd bd 7b a5 bd bd f7 .. .. .. .. .. 8c bd \n" + + "bd 6b 94 94 94 94 5a ef .. .. .. .. .. 8c bd \n" + + "bd 8c .. .. .. .. 63 ad ad ad ad ad ad 73 bd \n" + + "bd 8c .. .. .. .. 63 9c 9c 9c 9c 9c 9c 9c de \n" + + "bd 6b 94 94 94 94 5a ef .. .. .. .. .. .. .. \n" + + "e6 b5 b5 b5 b5 b5 b5 f7 .. .. .. .. .. .. .. \n" + + if string(got) != want { + t.Errorf("got:\n%swant:\n%s", got, want) + } +} + func benchmarkDecode(b *testing.B, filename string, bytesPerPixel int) { b.StopTimer() data, err := ioutil.ReadFile(filename) @@ -629,13 +690,3 @@ func BenchmarkDecodeRGB(b *testing.B) { func BenchmarkDecodeInterlacing(b *testing.B) { benchmarkDecode(b, "testdata/benchRGB-interlace.png", 4) } - -func TestIssue19553(t *testing.T) { - var buf = []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, 0x80, 0x00, 0x00, 0x00, 0x02, 0x74, 0x52, 0x4e, 0x53, 0x00, 0xff, 0x5b, 0x91, 0x22, 0xb5, 0x00, 0x00, 0x00, 0x02, 0x62, 0x4b, 0x47, 0x44, 0x00, 0xff, 0x87, 0x8f, 0xcc, 0xbf, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, 0x0a, 0xf0, 0x00, 0x00, 0x0a, 0xf0, 0x01, 0x42, 0xac, 0x34, 0x98, 0x00, 0x00, 0x00, 0x07, 0x74, 0x49, 0x4d, 0x45, 0x07, 0xd5, 0x04, 0x02, 0x12, 0x11, 0x11, 0xf7, 0x65, 0x3d, 0x8b, 0x00, 0x00, 0x00, 0x4f, 0x49, 0x44, 0x41, 0x54, 0x08, 0xd7, 0x63, 0xf8, 0xff, 0xff, 0xff, 0xb9, 0xbd, 0x70, 0xf0, 0x8c, 0x01, 0xc8, 0xaf, 0x6e, 0x99, 0x02, 0x05, 0xd9, 0x7b, 0xc1, 0xfc, 0x6b, 0xff, 0xa1, 0xa0, 0x87, 0x30, 0xff, 0xd9, 0xde, 0xbd, 0xd5, 0x4b, 0xf7, 0xee, 0xfd, 0x0e, 0xe3, 0xef, 0xcd, 0x06, 0x19, 0x14, 0xf5, 0x1e, 0xce, 0xef, 0x01, 0x31, 0x92, 0xd7, 0x82, 0x41, 0x31, 0x9c, 0x3f, 0x07, 0x02, 0xee, 0xa1, 0xaa, 0xff, 0xff, 0x9f, 0xe1, 0xd9, 0x56, 0x30, 0xf8, 0x0e, 0xe5, 0x03, 0x00, 0xa9, 0x42, 0x84, 0x3d, 0xdf, 0x8f, 0xa6, 0x8f, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82, - } - _, err := Decode(bytes.NewReader(buf)) - if err != chunkOrderError { - t.Errorf("Decode: expected chunkOrderError for transparent gray8, got %v", err) - } -} diff --git a/libgo/go/image/png/writer.go b/libgo/go/image/png/writer.go index dd87d816291..49f1ad2e7fa 100644 --- a/libgo/go/image/png/writer.go +++ b/libgo/go/image/png/writer.go @@ -17,17 +17,37 @@ import ( // Encoder configures encoding PNG images. type Encoder struct { CompressionLevel CompressionLevel + + // BufferPool optionally specifies a buffer pool to get temporary + // EncoderBuffers when encoding an image. + BufferPool EncoderBufferPool +} + +// EncoderBufferPool is an interface for getting and returning temporary +// instances of the EncoderBuffer struct. This can be used to reuse buffers +// when encoding multiple images. +type EncoderBufferPool interface { + Get() *EncoderBuffer + Put(*EncoderBuffer) } +// EncoderBuffer holds the buffers used for encoding PNG images. +type EncoderBuffer encoder + type encoder struct { - enc *Encoder - w io.Writer - m image.Image - cb int - err error - header [8]byte - footer [4]byte - tmp [4 * 256]byte + enc *Encoder + w io.Writer + m image.Image + cb int + err error + header [8]byte + footer [4]byte + tmp [4 * 256]byte + cr [nFilter][]uint8 + pr []uint8 + zw *zlib.Writer + zwLevel int + bw *bufio.Writer } type CompressionLevel int @@ -273,12 +293,24 @@ func filter(cr *[nFilter][]byte, pr []byte, bpp int) int { return filter } -func writeImage(w io.Writer, m image.Image, cb int, level int) error { - zw, err := zlib.NewWriterLevel(w, level) - if err != nil { - return err +func zeroMemory(v []uint8) { + for i := range v { + v[i] = 0 + } +} + +func (e *encoder) writeImage(w io.Writer, m image.Image, cb int, level int) error { + if e.zw == nil || e.zwLevel != level { + zw, err := zlib.NewWriterLevel(w, level) + if err != nil { + return err + } + e.zw = zw + e.zwLevel = level + } else { + e.zw.Reset(w) } - defer zw.Close() + defer e.zw.Close() bpp := 0 // Bytes per pixel. @@ -304,12 +336,23 @@ func writeImage(w io.Writer, m image.Image, cb int, level int) error { // other PNG filter types. These buffers are allocated once and re-used for each row. // The +1 is for the per-row filter type, which is at cr[*][0]. b := m.Bounds() - var cr [nFilter][]uint8 - for i := range cr { - cr[i] = make([]uint8, 1+bpp*b.Dx()) - cr[i][0] = uint8(i) + sz := 1 + bpp*b.Dx() + for i := range e.cr { + if cap(e.cr[i]) < sz { + e.cr[i] = make([]uint8, sz) + } else { + e.cr[i] = e.cr[i][:sz] + } + e.cr[i][0] = uint8(i) + } + cr := e.cr + if cap(e.pr) < sz { + e.pr = make([]uint8, sz) + } else { + e.pr = e.pr[:sz] + zeroMemory(e.pr) } - pr := make([]uint8, 1+bpp*b.Dx()) + pr := e.pr gray, _ := m.(*image.Gray) rgba, _ := m.(*image.RGBA) @@ -429,7 +472,7 @@ func writeImage(w io.Writer, m image.Image, cb int, level int) error { } // Write the compressed bytes. - if _, err := zw.Write(cr[f]); err != nil { + if _, err := e.zw.Write(cr[f]); err != nil { return err } @@ -444,13 +487,16 @@ func (e *encoder) writeIDATs() { if e.err != nil { return } - var bw *bufio.Writer - bw = bufio.NewWriterSize(e, 1<<15) - e.err = writeImage(bw, e.m, e.cb, levelToZlib(e.enc.CompressionLevel)) + if e.bw == nil { + e.bw = bufio.NewWriterSize(e, 1<<15) + } else { + e.bw.Reset(e) + } + e.err = e.writeImage(e.bw, e.m, e.cb, levelToZlib(e.enc.CompressionLevel)) if e.err != nil { return } - e.err = bw.Flush() + e.err = e.bw.Flush() } // This function is required because we want the zero value of @@ -489,7 +535,19 @@ func (enc *Encoder) Encode(w io.Writer, m image.Image) error { return FormatError("invalid image size: " + strconv.FormatInt(mw, 10) + "x" + strconv.FormatInt(mh, 10)) } - var e encoder + var e *encoder + if enc.BufferPool != nil { + buffer := enc.BufferPool.Get() + e = (*encoder)(buffer) + + } + if e == nil { + e = &encoder{} + } + if enc.BufferPool != nil { + defer enc.BufferPool.Put((*EncoderBuffer)(e)) + } + e.enc = enc e.w = w e.m = m diff --git a/libgo/go/image/png/writer_test.go b/libgo/go/image/png/writer_test.go index d67a815698f..b1f97b1d7bf 100644 --- a/libgo/go/image/png/writer_test.go +++ b/libgo/go/image/png/writer_test.go @@ -130,6 +130,31 @@ func BenchmarkEncodeGray(b *testing.B) { } } +type pool struct { + b *EncoderBuffer +} + +func (p *pool) Get() *EncoderBuffer { + return p.b +} + +func (p *pool) Put(b *EncoderBuffer) { + p.b = b +} + +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() + 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)) |