From: Nigel Tao Date: Thu, 20 May 2010 20:57:18 +0000 (-0700) Subject: Fast-ish path for drawing onto an image.RGBA destination. X-Git-Tag: weekly.2010-05-27~62 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=efda3aba10305754ddbf72ba85a7b6ff05c1c9d1;p=gostls13.git Fast-ish path for drawing onto an image.RGBA destination. 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 --- diff --git a/src/pkg/exp/draw/draw.go b/src/pkg/exp/draw/draw.go index dcafc89344..41eaef4d4f 100644 --- a/src/pkg/exp/draw/draw.go +++ b/src/pkg/exp/draw/draw.go @@ -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. diff --git a/src/pkg/exp/draw/draw_test.go b/src/pkg/exp/draw/draw_test.go index 64cf71cf61..5dc9d9593c 100644 --- a/src/pkg/exp/draw/draw_test.go +++ b/src/pkg/exp/draw/draw_test.go @@ -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 + } + } } } }