]> Cypherpunks repositories - gostls13.git/commitdiff
exp/draw: fast paths for drawing a YCbCr or an NRGBA onto an RGBA.
authorNigel Tao <nigeltao@golang.org>
Wed, 4 May 2011 17:17:53 +0000 (10:17 -0700)
committerNigel Tao <nigeltao@golang.org>
Wed, 4 May 2011 17:17:53 +0000 (10:17 -0700)
On my laptop, I had an 800x600 jpeg and an 800x600 png (with
transparency). I timed how long it took to draw each image onto an
equivalently sized, zeroed RGBA image.

Previously, the jpeg took 75ms and the png took 70ms, going through
the medium-fast path, i.e. func drawRGBA in draw.go.

After this CL, the jpeg took 14ms, and the png took 21ms with the
Over operator and 12ms with the Src operator.

It's only a rough estimate basd on one image file, but it should
give an idea of the order of magnitude of improvement.

R=rsc, r
CC=adg, golang-dev
https://golang.org/cl/4468044

src/pkg/exp/draw/draw.go
src/pkg/exp/draw/draw_test.go

index 1d0729d922c89f2a426d3353e23112eb664053d4..f98e24618946602a325d14d45991c385bac8e718 100644 (file)
@@ -8,7 +8,10 @@
 // and the X Render extension.
 package draw
 
-import "image"
+import (
+       "image"
+       "image/ycbcr"
+)
 
 // m is the maximum color value returned by image.Color.RGBA.
 const m = 1<<16 - 1
@@ -65,29 +68,42 @@ func DrawMask(dst Image, r image.Rectangle, src image.Image, sp image.Point, mas
        if dst0, ok := dst.(*image.RGBA); ok {
                if op == Over {
                        if mask == nil {
-                               if src0, ok := src.(*image.ColorImage); ok {
+                               switch src0 := src.(type) {
+                               case *image.ColorImage:
                                        drawFillOver(dst0, r, src0)
                                        return
-                               }
-                               if src0, ok := src.(*image.RGBA); ok {
+                               case *image.RGBA:
                                        drawCopyOver(dst0, r, src0, sp)
                                        return
+                               case *image.NRGBA:
+                                       drawNRGBAOver(dst0, r, src0, sp)
+                                       return
+                               case *ycbcr.YCbCr:
+                                       drawYCbCr(dst0, r, src0, sp)
+                                       return
                                }
                        } else if mask0, ok := mask.(*image.Alpha); ok {
-                               if src0, ok := src.(*image.ColorImage); ok {
+                               switch src0 := src.(type) {
+                               case *image.ColorImage:
                                        drawGlyphOver(dst0, r, src0, mask0, mp)
                                        return
                                }
                        }
                } else {
                        if mask == nil {
-                               if src0, ok := src.(*image.ColorImage); ok {
+                               switch src0 := src.(type) {
+                               case *image.ColorImage:
                                        drawFillSrc(dst0, r, src0)
                                        return
-                               }
-                               if src0, ok := src.(*image.RGBA); ok {
+                               case *image.RGBA:
                                        drawCopySrc(dst0, r, src0, sp)
                                        return
+                               case *image.NRGBA:
+                                       drawNRGBASrc(dst0, r, src0, sp)
+                                       return
+                               case *ycbcr.YCbCr:
+                                       drawYCbCr(dst0, r, src0, sp)
+                                       return
                                }
                        }
                }
@@ -224,6 +240,36 @@ func drawCopyOver(dst *image.RGBA, r image.Rectangle, src *image.RGBA, sp image.
        }
 }
 
+func drawNRGBAOver(dst *image.RGBA, r image.Rectangle, src *image.NRGBA, sp image.Point) {
+       for y, sy := r.Min.Y, sp.Y; y != r.Max.Y; y, sy = y+1, sy+1 {
+               dpix := dst.Pix[y*dst.Stride : (y+1)*dst.Stride]
+               spix := src.Pix[sy*src.Stride : (sy+1)*src.Stride]
+               for x, sx := r.Min.X, sp.X; x != r.Max.X; x, sx = x+1, sx+1 {
+                       // Convert from non-premultiplied color to pre-multiplied color.
+                       // The order of operations here is to match the NRGBAColor.RGBA
+                       // method in image/color.go.
+                       snrgba := spix[sx]
+                       sa := uint32(snrgba.A)
+                       sr := uint32(snrgba.R) * 0x101 * sa / 0xff
+                       sg := uint32(snrgba.G) * 0x101 * sa / 0xff
+                       sb := uint32(snrgba.B) * 0x101 * sa / 0xff
+                       sa *= 0x101
+
+                       rgba := dpix[x]
+                       dr := uint32(rgba.R)
+                       dg := uint32(rgba.G)
+                       db := uint32(rgba.B)
+                       da := uint32(rgba.A)
+                       a := (m - sa) * 0x101
+                       dr = (dr*a + sr*m) / m
+                       dg = (dg*a + sg*m) / m
+                       db = (db*a + sb*m) / m
+                       da = (da*a + sa*m) / m
+                       dpix[x] = image.RGBAColor{uint8(dr >> 8), uint8(dg >> 8), uint8(db >> 8), uint8(da >> 8)}
+               }
+       }
+}
+
 func drawGlyphOver(dst *image.RGBA, r image.Rectangle, src *image.ColorImage, mask *image.Alpha, mp image.Point) {
        x0, x1 := r.Min.X, r.Max.X
        y0, y1 := r.Min.Y, r.Max.Y
@@ -311,6 +357,73 @@ func drawCopySrc(dst *image.RGBA, r image.Rectangle, src *image.RGBA, sp image.P
        }
 }
 
+func drawNRGBASrc(dst *image.RGBA, r image.Rectangle, src *image.NRGBA, sp image.Point) {
+       for y, sy := r.Min.Y, sp.Y; y != r.Max.Y; y, sy = y+1, sy+1 {
+               dpix := dst.Pix[y*dst.Stride : (y+1)*dst.Stride]
+               spix := src.Pix[sy*src.Stride : (sy+1)*src.Stride]
+               for x, sx := r.Min.X, sp.X; x != r.Max.X; x, sx = x+1, sx+1 {
+                       // Convert from non-premultiplied color to pre-multiplied color.
+                       // The order of operations here is to match the NRGBAColor.RGBA
+                       // method in image/color.go.
+                       snrgba := spix[sx]
+                       sa := uint32(snrgba.A)
+                       sr := uint32(snrgba.R) * 0x101 * sa / 0xff
+                       sg := uint32(snrgba.G) * 0x101 * sa / 0xff
+                       sb := uint32(snrgba.B) * 0x101 * sa / 0xff
+                       sa *= 0x101
+
+                       dpix[x] = image.RGBAColor{uint8(sr >> 8), uint8(sg >> 8), uint8(sb >> 8), uint8(sa >> 8)}
+               }
+       }
+}
+
+func drawYCbCr(dst *image.RGBA, r image.Rectangle, src *ycbcr.YCbCr, sp image.Point) {
+       // A YCbCr image is always fully opaque, and so if the mask is implicitly nil
+       // (i.e. fully opaque) then the op is effectively always Src.
+       var (
+               yy, cb, cr uint8
+               rr, gg, bb uint8
+       )
+       switch src.SubsampleRatio {
+       case ycbcr.SubsampleRatio422:
+               for y, sy := r.Min.Y, sp.Y; y != r.Max.Y; y, sy = y+1, sy+1 {
+                       dpix := dst.Pix[y*dst.Stride : (y+1)*dst.Stride]
+                       for x, sx := r.Min.X, sp.X; x != r.Max.X; x, sx = x+1, sx+1 {
+                               i := sx / 2
+                               yy = src.Y[sy*src.YStride+sx]
+                               cb = src.Cb[sy*src.CStride+i]
+                               cr = src.Cr[sy*src.CStride+i]
+                               rr, gg, bb = ycbcr.YCbCrToRGB(yy, cb, cr)
+                               dpix[x] = image.RGBAColor{rr, gg, bb, 255}
+                       }
+               }
+       case ycbcr.SubsampleRatio420:
+               for y, sy := r.Min.Y, sp.Y; y != r.Max.Y; y, sy = y+1, sy+1 {
+                       dpix := dst.Pix[y*dst.Stride : (y+1)*dst.Stride]
+                       for x, sx := r.Min.X, sp.X; x != r.Max.X; x, sx = x+1, sx+1 {
+                               i, j := sx/2, sy/2
+                               yy = src.Y[sy*src.YStride+sx]
+                               cb = src.Cb[j*src.CStride+i]
+                               cr = src.Cr[j*src.CStride+i]
+                               rr, gg, bb = ycbcr.YCbCrToRGB(yy, cb, cr)
+                               dpix[x] = image.RGBAColor{rr, gg, bb, 255}
+                       }
+               }
+       default:
+               // Default to 4:4:4 subsampling.
+               for y, sy := r.Min.Y, sp.Y; y != r.Max.Y; y, sy = y+1, sy+1 {
+                       dpix := dst.Pix[y*dst.Stride : (y+1)*dst.Stride]
+                       for x, sx := r.Min.X, sp.X; x != r.Max.X; x, sx = x+1, sx+1 {
+                               yy = src.Y[sy*src.YStride+sx]
+                               cb = src.Cb[sy*src.CStride+sx]
+                               cr = src.Cr[sy*src.CStride+sx]
+                               rr, gg, bb = ycbcr.YCbCrToRGB(yy, cb, cr)
+                               dpix[x] = image.RGBAColor{rr, gg, bb, 255}
+                       }
+               }
+       }
+}
+
 func drawRGBA(dst *image.RGBA, r image.Rectangle, src image.Image, sp image.Point, mask image.Image, mp image.Point, op Op) {
        x0, x1, dx := r.Min.X, r.Max.X, 1
        y0, y1, dy := r.Min.Y, r.Max.Y, 1
index 90c9e823d3e834fbc129e2ae9811d79ad463fd9e..873a2f24a40bedf26c69e2900d4d1fdd53d8b216 100644 (file)
@@ -6,6 +6,7 @@ package draw
 
 import (
        "image"
+       "image/ycbcr"
        "testing"
 )
 
@@ -43,6 +44,34 @@ func vgradAlpha(alpha int) image.Image {
        return m
 }
 
+func vgradGreenNRGBA(alpha int) image.Image {
+       m := image.NewNRGBA(16, 16)
+       for y := 0; y < 16; y++ {
+               for x := 0; x < 16; x++ {
+                       m.Set(x, y, image.RGBAColor{0, uint8(y * 0x11), 0, uint8(alpha)})
+               }
+       }
+       return m
+}
+
+func vgradCr() image.Image {
+       m := &ycbcr.YCbCr{
+               Y:              make([]byte, 16*16),
+               Cb:             make([]byte, 16*16),
+               Cr:             make([]byte, 16*16),
+               YStride:        16,
+               CStride:        16,
+               SubsampleRatio: ycbcr.SubsampleRatio444,
+               Rect:           image.Rect(0, 0, 16, 16),
+       }
+       for y := 0; y < 16; y++ {
+               for x := 0; x < 16; x++ {
+                       m.Cr[y*m.CStride+x] = uint8(y * 0x11)
+               }
+       }
+       return m
+}
+
 func hgradRed(alpha int) Image {
        m := image.NewRGBA(16, 16)
        for y := 0; y < 16; y++ {
@@ -95,6 +124,27 @@ var drawTests = []drawTest{
        {"copyAlphaSrc", vgradGreen(90), fillAlpha(192), Src, image.RGBAColor{0, 36, 0, 68}},
        {"copyNil", vgradGreen(90), nil, Over, image.RGBAColor{88, 48, 0, 255}},
        {"copyNilSrc", vgradGreen(90), nil, Src, image.RGBAColor{0, 48, 0, 90}},
+       // Uniform mask (100%, 75%, nil) and variable NRGBA source.
+       // At (x, y) == (8, 8):
+       // The destination pixel is {136, 0, 0, 255}.
+       // The source pixel is {0, 136, 0, 90} in NRGBA-space, which is {0, 48, 0, 90} in RGBA-space.
+       // The result pixel is different than in the "copy*" test cases because of rounding errors.
+       {"nrgba", vgradGreenNRGBA(90), fillAlpha(255), Over, image.RGBAColor{88, 46, 0, 255}},
+       {"nrgbaSrc", vgradGreenNRGBA(90), fillAlpha(255), Src, image.RGBAColor{0, 46, 0, 90}},
+       {"nrgbaAlpha", vgradGreenNRGBA(90), fillAlpha(192), Over, image.RGBAColor{100, 34, 0, 255}},
+       {"nrgbaAlphaSrc", vgradGreenNRGBA(90), fillAlpha(192), Src, image.RGBAColor{0, 34, 0, 68}},
+       {"nrgbaNil", vgradGreenNRGBA(90), nil, Over, image.RGBAColor{88, 46, 0, 255}},
+       {"nrgbaNilSrc", vgradGreenNRGBA(90), nil, Src, image.RGBAColor{0, 46, 0, 90}},
+       // Uniform mask (100%, 75%, nil) and variable YCbCr source.
+       // At (x, y) == (8, 8):
+       // The destination pixel is {136, 0, 0, 255}.
+       // The source pixel is {0, 0, 136} in YCbCr-space, which is {11, 38, 0, 255} in RGB-space.
+       {"ycbcr", vgradCr(), fillAlpha(255), Over, image.RGBAColor{11, 38, 0, 255}},
+       {"ycbcrSrc", vgradCr(), fillAlpha(255), Src, image.RGBAColor{11, 38, 0, 255}},
+       {"ycbcrAlpha", vgradCr(), fillAlpha(192), Over, image.RGBAColor{42, 28, 0, 255}},
+       {"ycbcrAlphaSrc", vgradCr(), fillAlpha(192), Src, image.RGBAColor{8, 28, 0, 192}},
+       {"ycbcrNil", vgradCr(), nil, Over, image.RGBAColor{11, 38, 0, 255}},
+       {"ycbcrNilSrc", vgradCr(), nil, Src, image.RGBAColor{11, 38, 0, 255}},
        // Variable mask and variable source.
        // At (x, y) == (8, 8):
        // The destination pixel is {136, 0, 0, 255}.