// 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
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.
// 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)
}
// Write the compressed bytes.
- if _, err := zw.Write(cr[f]); err != nil {
+ if _, err := e.zw.Write(cr[f]); err != nil {
return err
}
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
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
}
}
+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))