summaryrefslogtreecommitdiff
path: root/libgo/go/image
diff options
context:
space:
mode:
authorIan Lance Taylor <iant@golang.org>2017-09-14 17:11:35 +0000
committerIan Lance Taylor <ian@gcc.gnu.org>2017-09-14 17:11:35 +0000
commitbc998d034f45d1828a8663b2eed928faf22a7d01 (patch)
tree8d262a22ca7318f4bcd64269fe8fe9e45bcf8d0f /libgo/go/image
parenta41a6142df74219f596e612d3a7775f68ca6e96f (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.go58
-rw-r--r--libgo/go/image/geom.go6
-rw-r--r--libgo/go/image/geom_test.go7
-rw-r--r--libgo/go/image/gif/reader.go45
-rw-r--r--libgo/go/image/gif/reader_test.go63
-rw-r--r--libgo/go/image/gif/writer.go39
-rw-r--r--libgo/go/image/gif/writer_test.go61
-rw-r--r--libgo/go/image/image_test.go12
-rw-r--r--libgo/go/image/internal/imageutil/gen.go2
-rw-r--r--libgo/go/image/internal/imageutil/impl.go8
-rw-r--r--libgo/go/image/jpeg/huffman.go3
-rw-r--r--libgo/go/image/jpeg/reader.go17
-rw-r--r--libgo/go/image/jpeg/scan.go10
-rw-r--r--libgo/go/image/jpeg/writer.go29
-rw-r--r--libgo/go/image/jpeg/writer_test.go58
-rw-r--r--libgo/go/image/png/reader.go17
-rw-r--r--libgo/go/image/png/reader_test.go71
-rw-r--r--libgo/go/image/png/writer.go106
-rw-r--r--libgo/go/image/png/writer_test.go25
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))