Commit efda3aba authored by Nigel Tao's avatar Nigel Tao

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
parent 37a6adf1
......@@ -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.
......
......@@ -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
}
}
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment