From 237ee3926906ad08a048e764920e036ecdb08b11 Mon Sep 17 00:00:00 2001 From: Nigel Tao Date: Thu, 13 Sep 2012 15:47:12 +1000 Subject: [PATCH] image/png: optimize encoding image.Gray and image.NRGBA images. benchmark old ns/op new ns/op delta BenchmarkEncodeGray 23616080 5624558 -76.18% BenchmarkEncodeNRGBOpaque 34181260 17144380 -49.84% BenchmarkEncodeNRGBA 41235820 20345990 -50.66% BenchmarkEncodePaletted 5594652 5620362 +0.46% BenchmarkEncodeRGBOpaque 17242210 17168820 -0.43% BenchmarkEncodeRGBA 66515720 67243560 +1.09% R=r CC=golang-dev https://golang.org/cl/6490099 --- src/pkg/image/png/writer.go | 61 +++++++++++++++++++++----------- src/pkg/image/png/writer_test.go | 45 ++++++++++++++++++++++- 2 files changed, 85 insertions(+), 21 deletions(-) diff --git a/src/pkg/image/png/writer.go b/src/pkg/image/png/writer.go index 88683a937b4..093d47193ba 100644 --- a/src/pkg/image/png/writer.go +++ b/src/pkg/image/png/writer.go @@ -290,26 +290,42 @@ func writeImage(w io.Writer, m image.Image, cb int) error { } pr := make([]uint8, 1+bpp*b.Dx()) + gray, _ := m.(*image.Gray) + rgba, _ := m.(*image.RGBA) + paletted, _ := m.(*image.Paletted) + nrgba, _ := m.(*image.NRGBA) + for y := b.Min.Y; y < b.Max.Y; y++ { // Convert from colors to bytes. i := 1 switch cb { case cbG8: - for x := b.Min.X; x < b.Max.X; x++ { - c := color.GrayModel.Convert(m.At(x, y)).(color.Gray) - cr[0][i] = c.Y - i++ + if gray != nil { + offset := (y - b.Min.Y) * gray.Stride + copy(cr[0][1:], gray.Pix[offset:offset+b.Dx()]) + } else { + for x := b.Min.X; x < b.Max.X; x++ { + c := color.GrayModel.Convert(m.At(x, y)).(color.Gray) + cr[0][i] = c.Y + i++ + } } case cbTC8: // We have previously verified that the alpha value is fully opaque. cr0 := cr[0] - if rgba, _ := m.(*image.RGBA); rgba != nil { - j0 := (y - b.Min.Y) * rgba.Stride + stride, pix := 0, []byte(nil) + if rgba != nil { + stride, pix = rgba.Stride, rgba.Pix + } else if nrgba != nil { + stride, pix = nrgba.Stride, nrgba.Pix + } + if stride != 0 { + j0 := (y - b.Min.Y) * stride j1 := j0 + b.Dx()*4 for j := j0; j < j1; j += 4 { - cr0[i+0] = rgba.Pix[j+0] - cr0[i+1] = rgba.Pix[j+1] - cr0[i+2] = rgba.Pix[j+2] + cr0[i+0] = pix[j+0] + cr0[i+1] = pix[j+1] + cr0[i+2] = pix[j+2] i += 3 } } else { @@ -322,9 +338,9 @@ func writeImage(w io.Writer, m image.Image, cb int) error { } } case cbP8: - if p, _ := m.(*image.Paletted); p != nil { - offset := (y - b.Min.Y) * p.Stride - copy(cr[0][1:], p.Pix[offset:offset+b.Dx()]) + if paletted != nil { + offset := (y - b.Min.Y) * paletted.Stride + copy(cr[0][1:], paletted.Pix[offset:offset+b.Dx()]) } else { pi := m.(image.PalettedImage) for x := b.Min.X; x < b.Max.X; x++ { @@ -333,14 +349,19 @@ func writeImage(w io.Writer, m image.Image, cb int) error { } } case cbTCA8: - // Convert from image.Image (which is alpha-premultiplied) to PNG's non-alpha-premultiplied. - for x := b.Min.X; x < b.Max.X; x++ { - c := color.NRGBAModel.Convert(m.At(x, y)).(color.NRGBA) - cr[0][i+0] = c.R - cr[0][i+1] = c.G - cr[0][i+2] = c.B - cr[0][i+3] = c.A - i += 4 + if nrgba != nil { + offset := (y - b.Min.Y) * nrgba.Stride + copy(cr[0][1:], nrgba.Pix[offset:offset+b.Dx()*4]) + } else { + // Convert from image.Image (which is alpha-premultiplied) to PNG's non-alpha-premultiplied. + for x := b.Min.X; x < b.Max.X; x++ { + c := color.NRGBAModel.Convert(m.At(x, y)).(color.NRGBA) + cr[0][i+0] = c.R + cr[0][i+1] = c.G + cr[0][i+2] = c.B + cr[0][i+3] = c.A + i += 4 + } } case cbG16: for x := b.Min.X; x < b.Max.X; x++ { diff --git a/src/pkg/image/png/writer_test.go b/src/pkg/image/png/writer_test.go index 644c4fb44b3..3116fc9ff94 100644 --- a/src/pkg/image/png/writer_test.go +++ b/src/pkg/image/png/writer_test.go @@ -101,6 +101,49 @@ 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() + for i := 0; i < b.N; i++ { + 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() + for y := bo.Min.Y; y < bo.Max.Y; y++ { + for x := bo.Min.X; x < bo.Max.X; x++ { + img.Set(x, y, color.NRGBA{0, 0, 0, 255}) + } + } + if !img.Opaque() { + b.Fatal("expected image to be opaque") + } + b.SetBytes(640 * 480 * 4) + b.StartTimer() + 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() + 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{ @@ -138,7 +181,7 @@ func BenchmarkEncodeRGBA(b *testing.B) { b.StopTimer() img := image.NewRGBA(image.Rect(0, 0, 640, 480)) if img.Opaque() { - b.Fatal("expected image to not be opaque") + b.Fatal("expected image not to be opaque") } b.SetBytes(640 * 480 * 4) b.StartTimer()