]> Cypherpunks repositories - gostls13.git/commitdiff
Fast-ish path for drawing onto an image.RGBA destination.
authorNigel Tao <nigeltao@golang.org>
Thu, 20 May 2010 20:57:18 +0000 (13:57 -0700)
committerNigel Tao <nigeltao@golang.org>
Thu, 20 May 2010 20:57:18 +0000 (13:57 -0700)
Time to draw.Draw a 200x200 image fell from 18.4ms (and 1 malloc) to
5.6ms (and 0 mallocs). It's still relatively slow since it assumes
nothing about the src or mask images, but it does remove the malloc.
There are existing faster, more specialized paths for copies, fills
and image glyph masks.

Also added a "compare to a slow but obviously correct implementation"
check to draw_test.go.

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

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

index dcafc893449ec297ee57a709b65b65c6fb36465a..41eaef4d4f3a24e09d1a8c4752b569a32260ebe8 100644 (file)
@@ -84,6 +84,8 @@ func DrawMask(dst Image, r Rectangle, src image.Image, sp Point, mask image.Imag
                                }
                        }
                }
+               drawRGBA(dst0, r, src, sp, mask, mp, op)
+               return
        }
 
        x0, x1, dx := r.Min.X, r.Max.X, 1
@@ -161,31 +163,26 @@ func drawGlyphOver(dst *image.RGBA, r Rectangle, src image.ColorImage, mask *ima
        cb >>= 16
        ca >>= 16
        for y, my := y0, mp.Y; y != y1; y, my = y+1, my+1 {
+               p := dst.Pixel[y]
                for x, mx := x0, mp.X; x != x1; x, mx = x+1, mx+1 {
                        ma := uint32(mask.Pixel[my][mx].A)
                        if ma == 0 {
                                continue
                        }
                        ma |= ma << 8
-                       rgba := dst.Pixel[y][x]
+                       rgba := p[x]
                        dr := uint32(rgba.R)
                        dg := uint32(rgba.G)
                        db := uint32(rgba.B)
                        da := uint32(rgba.A)
-                       // dr, dg, db and da are all 8-bit color at the moment, ranging in [0,255].
-                       // We work in 16-bit color, and so would normally do:
-                       // dr |= dr << 8
-                       // and similarly for dg, db and da, but instead we multiply a
-                       // (which is a 16-bit color, ranging in [0,65535]) by 0x101.
-                       // This yields the same result, but is fewer arithmetic operations.
                        const M = 1<<16 - 1
-                       a := M - (ca * ma / M)
-                       a *= 0x101
+                       // The 0x101 is here for the same reason as in drawRGBA.
+                       a := (M - (ca * ma / M)) * 0x101
                        dr = (dr*a + cr*ma) / M
                        dg = (dg*a + cg*ma) / M
                        db = (db*a + cb*ma) / M
                        da = (da*a + ca*ma) / M
-                       dst.Pixel[y][x] = image.RGBAColor{uint8(dr >> 8), uint8(dg >> 8), uint8(db >> 8), uint8(da >> 8)}
+                       p[x] = image.RGBAColor{uint8(dr >> 8), uint8(dg >> 8), uint8(db >> 8), uint8(da >> 8)}
                }
        }
 }
@@ -220,6 +217,63 @@ func drawCopy(dst *image.RGBA, r Rectangle, src *image.RGBA, sp Point) {
        }
 }
 
+func drawRGBA(dst *image.RGBA, r Rectangle, src image.Image, sp Point, mask image.Image, mp Point, op Op) {
+       x0, x1, dx := r.Min.X, r.Max.X, 1
+       y0, y1, dy := r.Min.Y, r.Max.Y, 1
+       if image.Image(dst) == src && r.Overlaps(r.Add(sp.Sub(r.Min))) {
+               if sp.Y < r.Min.Y || sp.Y == r.Min.Y && sp.X < r.Min.X {
+                       x0, x1, dx = x1-1, x0-1, -1
+                       y0, y1, dy = y1-1, y0-1, -1
+               }
+       }
+
+       sy := sp.Y + y0 - r.Min.Y
+       my := mp.Y + y0 - r.Min.Y
+       for y := y0; y != y1; y, sy, my = y+dy, sy+dy, my+dy {
+               sx := sp.X + x0 - r.Min.X
+               mx := mp.X + x0 - r.Min.X
+               p := dst.Pixel[y]
+               for x := x0; x != x1; x, sx, mx = x+dx, sx+dx, mx+dx {
+                       const M = 1<<16 - 1
+                       ma := uint32(M)
+                       if mask != nil {
+                               _, _, _, ma = mask.At(mx, my).RGBA()
+                               ma >>= 16
+                       }
+                       sr, sg, sb, sa := src.At(sx, sy).RGBA()
+                       sr >>= 16
+                       sg >>= 16
+                       sb >>= 16
+                       sa >>= 16
+                       var dr, dg, db, da uint32
+                       if op == Over {
+                               rgba := p[x]
+                               dr = uint32(rgba.R)
+                               dg = uint32(rgba.G)
+                               db = uint32(rgba.B)
+                               da = uint32(rgba.A)
+                               // dr, dg, db and da are all 8-bit color at the moment, ranging in [0,255].
+                               // We work in 16-bit color, and so would normally do:
+                               // dr |= dr << 8
+                               // and similarly for dg, db and da, but instead we multiply a
+                               // (which is a 16-bit color, ranging in [0,65535]) by 0x101.
+                               // This yields the same result, but is fewer arithmetic operations.
+                               a := (M - (sa * ma / M)) * 0x101
+                               dr = (dr*a + sr*ma) / M
+                               dg = (dg*a + sg*ma) / M
+                               db = (db*a + sb*ma) / M
+                               da = (da*a + sa*ma) / M
+                       } else {
+                               dr = sr * ma / M
+                               dg = sg * ma / M
+                               db = sb * ma / M
+                               da = sa * ma / M
+                       }
+                       p[x] = image.RGBAColor{uint8(dr >> 8), uint8(dg >> 8), uint8(db >> 8), uint8(da >> 8)}
+               }
+       }
+}
+
 // Border aligns r.Min in dst with sp in src and then replaces pixels
 // in a w-pixel border around r in dst with the result of the Porter-Duff compositing
 // operation ``src over dst.''  If w is positive, the border extends w pixels inside r.
index 64cf71cf618bbf1fc460d5a7b57c57ae6a90ebcf..5dc9d9593cb480072e98d08fef7c59a593e596e6 100644 (file)
@@ -94,12 +94,66 @@ var drawTests = []drawTest{
        drawTest{"genericSrc", fillBlue(255), vgradAlpha(192), Src, image.RGBAColor{0, 0, 102, 102}},
 }
 
+func makeGolden(dst image.Image, t drawTest) image.Image {
+       // Since golden is a newly allocated image, we don't have to check if the
+       // input source and mask images and the output golden image overlap.
+       golden := image.NewRGBA(dst.Width(), dst.Height())
+       for y := 0; y < golden.Height(); y++ {
+               my, sy := y, y
+               for x := 0; x < golden.Width(); x++ {
+                       mx, sx := x, x
+                       const M = 1<<16 - 1
+                       var dr, dg, db, da uint32
+                       if t.op == Over {
+                               dr, dg, db, da = dst.At(x, y).RGBA()
+                               dr >>= 16
+                               dg >>= 16
+                               db >>= 16
+                               da >>= 16
+                       }
+                       sr, sg, sb, sa := t.src.At(sx, sy).RGBA()
+                       sr >>= 16
+                       sg >>= 16
+                       sb >>= 16
+                       sa >>= 16
+                       ma := uint32(M)
+                       if t.mask != nil {
+                               _, _, _, ma = t.mask.At(mx, my).RGBA()
+                               ma >>= 16
+                       }
+                       a := M - (sa * ma / M)
+                       golden.Set(x, y, image.RGBA64Color{
+                               uint16((dr*a + sr*ma) / M),
+                               uint16((dg*a + sg*ma) / M),
+                               uint16((db*a + sb*ma) / M),
+                               uint16((da*a + sa*ma) / M),
+                       })
+               }
+       }
+       return golden
+}
+
 func TestDraw(t *testing.T) {
-       for _, test := range drawTests {
+loop: for _, test := range drawTests {
                dst := hgradRed(255)
-               DrawMask(dst, Rect(0, 0, 16, 16), test.src, ZP, test.mask, ZP, test.op)
+               // Draw the (src, mask, op) onto a copy of dst using a slow but obviously correct implementation.
+               golden := makeGolden(dst, test)
+               // Draw the same combination onto the actual dst using the optimized DrawMask implementation.
+               DrawMask(dst, Rect(0, 0, dst.Width(), dst.Height()), test.src, ZP, test.mask, ZP, test.op)
+               // Check that the resultant pixel at (8, 8) matches what we expect
+               // (the expected value can be verified by hand).
                if !eq(dst.At(8, 8), test.expected) {
-                       t.Errorf("draw %s: %v versus %v", test.desc, dst.At(8, 8), test.expected)
+                       t.Errorf("draw %s: at (8, 8) %v versus %v", test.desc, dst.At(8, 8), test.expected)
+                       continue
+               }
+               // Check that the resultant dst image matches the golden output.
+               for y := 0; y < golden.Height(); y++ {
+                       for x := 0; x < golden.Width(); x++ {
+                               if !eq(dst.At(x, y), golden.At(x, y)) {
+                                       t.Errorf("draw %s: at (%d, %d), %v versus golden %v", test.desc, x, y, dst.At(x, y), golden.At(x, y))
+                                       continue loop
+                               }
+                       }
                }
        }
 }