X Tutup
Skip to content

Commit fcd2d9c

Browse files
owulverycknigeltao
authored andcommitted
image/draw: improve performances if mask is *image.Alpha
The existing DrawMask method is generic and is therefore calling the At().RGBA() method for every pixel of the mask and the source. Do a specific implementation when the mask is *image.Alpha (which is common) and use use the PixOffset method to increase performances. name old time/op new time/op delta RGBA2-12 1.60ms ± 0% 1.13ms ± 1% -29.16% (p=0.008 n=5+5) GenericMaskOver-12 915µs ± 4% 926µs ± 1% ~ (p=0.190 n=5+4) RGBA64Over-12 1.53ms ± 3% 1.21ms ± 2% -20.74% (p=0.008 n=5+5) GrayOver-12 1.36ms ± 2% 1.01ms ± 7% -26.27% (p=0.008 n=5+5) Fixes: golang#46395 Change-Id: Iaeaa8cfcc6a3fe93eb19b361f3bf076e41cac5b6 Reviewed-on: https://go-review.googlesource.com/c/go/+/323749 Reviewed-by: Nigel Tao <nigeltao@golang.org> Trust: Nigel Tao <nigeltao@golang.org> Trust: Andrew Gerrand <adg@golang.org> Run-TryBot: Nigel Tao <nigeltao@golang.org>
1 parent 7b55457 commit fcd2d9c

File tree

3 files changed

+171
-0
lines changed

3 files changed

+171
-0
lines changed

src/image/draw/bench_test.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,3 +266,11 @@ func BenchmarkGenericSrc(b *testing.B) {
266266
func BenchmarkGenericMaskSrc(b *testing.B) {
267267
bench(b, color.RGBA64Model, color.RGBA64Model, color.AlphaModel, Src)
268268
}
269+
270+
func BenchmarkRGBA64Over(b *testing.B) {
271+
bench(b, color.RGBAModel, color.RGBA64Model, color.AlphaModel, Over)
272+
}
273+
274+
func BenchmarkGrayOver(b *testing.B) {
275+
bench(b, color.RGBAModel, color.GrayModel, color.AlphaModel, Over)
276+
}

src/image/draw/draw.go

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,15 @@ func DrawMask(dst Image, r image.Rectangle, src image.Image, sp image.Point, mas
160160
case *image.Uniform:
161161
drawGlyphOver(dst0, r, src0, mask0, mp)
162162
return
163+
case *image.RGBA:
164+
drawRGBAMaskOver(dst0, r, src0, sp, mask0, mp)
165+
return
166+
case *image.Gray:
167+
drawGrayMaskOver(dst0, r, src0, sp, mask0, mp)
168+
return
169+
case image.RGBA64Image:
170+
drawRGBA64ImageMaskOver(dst0, r, src0, sp, mask0, mp)
171+
return
163172
}
164173
}
165174
} else {
@@ -602,6 +611,156 @@ func drawGlyphOver(dst *image.RGBA, r image.Rectangle, src *image.Uniform, mask
602611
}
603612
}
604613

614+
func drawGrayMaskOver(dst *image.RGBA, r image.Rectangle, src *image.Gray, sp image.Point, mask *image.Alpha, mp image.Point) {
615+
x0, x1, dx := r.Min.X, r.Max.X, 1
616+
y0, y1, dy := r.Min.Y, r.Max.Y, 1
617+
if r.Overlaps(r.Add(sp.Sub(r.Min))) {
618+
if sp.Y < r.Min.Y || sp.Y == r.Min.Y && sp.X < r.Min.X {
619+
x0, x1, dx = x1-1, x0-1, -1
620+
y0, y1, dy = y1-1, y0-1, -1
621+
}
622+
}
623+
624+
sy := sp.Y + y0 - r.Min.Y
625+
my := mp.Y + y0 - r.Min.Y
626+
sx0 := sp.X + x0 - r.Min.X
627+
mx0 := mp.X + x0 - r.Min.X
628+
sx1 := sx0 + (x1 - x0)
629+
i0 := dst.PixOffset(x0, y0)
630+
di := dx * 4
631+
for y := y0; y != y1; y, sy, my = y+dy, sy+dy, my+dy {
632+
for i, sx, mx := i0, sx0, mx0; sx != sx1; i, sx, mx = i+di, sx+dx, mx+dx {
633+
mi := mask.PixOffset(mx, my)
634+
ma := uint32(mask.Pix[mi])
635+
ma |= ma << 8
636+
si := src.PixOffset(sx, sy)
637+
sy := uint32(src.Pix[si])
638+
sy |= sy << 8
639+
sa := uint32(0xffff)
640+
641+
d := dst.Pix[i : i+4 : i+4] // Small cap improves performance, see https://golang.org/issue/27857
642+
dr := uint32(d[0])
643+
dg := uint32(d[1])
644+
db := uint32(d[2])
645+
da := uint32(d[3])
646+
647+
// dr, dg, db and da are all 8-bit color at the moment, ranging in [0,255].
648+
// We work in 16-bit color, and so would normally do:
649+
// dr |= dr << 8
650+
// and similarly for dg, db and da, but instead we multiply a
651+
// (which is a 16-bit color, ranging in [0,65535]) by 0x101.
652+
// This yields the same result, but is fewer arithmetic operations.
653+
a := (m - (sa * ma / m)) * 0x101
654+
655+
d[0] = uint8((dr*a + sy*ma) / m >> 8)
656+
d[1] = uint8((dg*a + sy*ma) / m >> 8)
657+
d[2] = uint8((db*a + sy*ma) / m >> 8)
658+
d[3] = uint8((da*a + sa*ma) / m >> 8)
659+
}
660+
i0 += dy * dst.Stride
661+
}
662+
}
663+
664+
func drawRGBAMaskOver(dst *image.RGBA, r image.Rectangle, src *image.RGBA, sp image.Point, mask *image.Alpha, mp image.Point) {
665+
x0, x1, dx := r.Min.X, r.Max.X, 1
666+
y0, y1, dy := r.Min.Y, r.Max.Y, 1
667+
if dst == src && r.Overlaps(r.Add(sp.Sub(r.Min))) {
668+
if sp.Y < r.Min.Y || sp.Y == r.Min.Y && sp.X < r.Min.X {
669+
x0, x1, dx = x1-1, x0-1, -1
670+
y0, y1, dy = y1-1, y0-1, -1
671+
}
672+
}
673+
674+
sy := sp.Y + y0 - r.Min.Y
675+
my := mp.Y + y0 - r.Min.Y
676+
sx0 := sp.X + x0 - r.Min.X
677+
mx0 := mp.X + x0 - r.Min.X
678+
sx1 := sx0 + (x1 - x0)
679+
i0 := dst.PixOffset(x0, y0)
680+
di := dx * 4
681+
for y := y0; y != y1; y, sy, my = y+dy, sy+dy, my+dy {
682+
for i, sx, mx := i0, sx0, mx0; sx != sx1; i, sx, mx = i+di, sx+dx, mx+dx {
683+
mi := mask.PixOffset(mx, my)
684+
ma := uint32(mask.Pix[mi])
685+
ma |= ma << 8
686+
si := src.PixOffset(sx, sy)
687+
sr := uint32(src.Pix[si+0])
688+
sg := uint32(src.Pix[si+1])
689+
sb := uint32(src.Pix[si+2])
690+
sa := uint32(src.Pix[si+3])
691+
sr |= sr << 8
692+
sg |= sg << 8
693+
sb |= sb << 8
694+
sa |= sa << 8
695+
d := dst.Pix[i : i+4 : i+4] // Small cap improves performance, see https://golang.org/issue/27857
696+
dr := uint32(d[0])
697+
dg := uint32(d[1])
698+
db := uint32(d[2])
699+
da := uint32(d[3])
700+
701+
// dr, dg, db and da are all 8-bit color at the moment, ranging in [0,255].
702+
// We work in 16-bit color, and so would normally do:
703+
// dr |= dr << 8
704+
// and similarly for dg, db and da, but instead we multiply a
705+
// (which is a 16-bit color, ranging in [0,65535]) by 0x101.
706+
// This yields the same result, but is fewer arithmetic operations.
707+
a := (m - (sa * ma / m)) * 0x101
708+
709+
d[0] = uint8((dr*a + sr*ma) / m >> 8)
710+
d[1] = uint8((dg*a + sg*ma) / m >> 8)
711+
d[2] = uint8((db*a + sb*ma) / m >> 8)
712+
d[3] = uint8((da*a + sa*ma) / m >> 8)
713+
}
714+
i0 += dy * dst.Stride
715+
}
716+
}
717+
718+
func drawRGBA64ImageMaskOver(dst *image.RGBA, r image.Rectangle, src image.RGBA64Image, sp image.Point, mask *image.Alpha, mp image.Point) {
719+
x0, x1, dx := r.Min.X, r.Max.X, 1
720+
y0, y1, dy := r.Min.Y, r.Max.Y, 1
721+
if image.Image(dst) == src && r.Overlaps(r.Add(sp.Sub(r.Min))) {
722+
if sp.Y < r.Min.Y || sp.Y == r.Min.Y && sp.X < r.Min.X {
723+
x0, x1, dx = x1-1, x0-1, -1
724+
y0, y1, dy = y1-1, y0-1, -1
725+
}
726+
}
727+
728+
sy := sp.Y + y0 - r.Min.Y
729+
my := mp.Y + y0 - r.Min.Y
730+
sx0 := sp.X + x0 - r.Min.X
731+
mx0 := mp.X + x0 - r.Min.X
732+
sx1 := sx0 + (x1 - x0)
733+
i0 := dst.PixOffset(x0, y0)
734+
di := dx * 4
735+
for y := y0; y != y1; y, sy, my = y+dy, sy+dy, my+dy {
736+
for i, sx, mx := i0, sx0, mx0; sx != sx1; i, sx, mx = i+di, sx+dx, mx+dx {
737+
mi := mask.PixOffset(mx, my)
738+
ma := uint32(mask.Pix[mi])
739+
ma |= ma << 8
740+
srgba := src.RGBA64At(sx, sy)
741+
d := dst.Pix[i : i+4 : i+4] // Small cap improves performance, see https://golang.org/issue/27857
742+
dr := uint32(d[0])
743+
dg := uint32(d[1])
744+
db := uint32(d[2])
745+
da := uint32(d[3])
746+
747+
// dr, dg, db and da are all 8-bit color at the moment, ranging in [0,255].
748+
// We work in 16-bit color, and so would normally do:
749+
// dr |= dr << 8
750+
// and similarly for dg, db and da, but instead we multiply a
751+
// (which is a 16-bit color, ranging in [0,65535]) by 0x101.
752+
// This yields the same result, but is fewer arithmetic operations.
753+
a := (m - (uint32(srgba.A) * ma / m)) * 0x101
754+
755+
d[0] = uint8((dr*a + uint32(srgba.R)*ma) / m >> 8)
756+
d[1] = uint8((dg*a + uint32(srgba.G)*ma) / m >> 8)
757+
d[2] = uint8((db*a + uint32(srgba.B)*ma) / m >> 8)
758+
d[3] = uint8((da*a + uint32(srgba.A)*ma) / m >> 8)
759+
}
760+
i0 += dy * dst.Stride
761+
}
762+
}
763+
605764
func drawRGBA(dst *image.RGBA, r image.Rectangle, src image.Image, sp image.Point, mask image.Image, mp image.Point, op Op) {
606765
x0, x1, dx := r.Min.X, r.Max.X, 1
607766
y0, y1, dy := r.Min.Y, r.Max.Y, 1

src/image/draw/draw_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,10 @@ var drawTests = []drawTest{
397397
Over, color.RGBA{81, 0, 102, 255}},
398398
{"genericSrcSlowest", fillBlue(255), convertToSlowestRGBA(vgradAlpha(192)),
399399
Src, color.RGBA{0, 0, 102, 102}},
400+
// The source pixel is {0, 48, 0, 90}.
401+
{"rgbaVariableMaskOver", vgradGreen(255), vgradAlpha(192), Over, color.RGBA{81, 54, 0, 255}},
402+
// The source pixel is {136} in Gray-space, which is {136, 136, 136, 255} in RGBA-space.
403+
{"grayVariableMaskOver", vgradGray(), vgradAlpha(192), Over, color.RGBA{136, 54, 54, 255}},
400404
}
401405

402406
func makeGolden(dst image.Image, r image.Rectangle, src image.Image, sp image.Point, mask image.Image, mp image.Point, op Op) image.Image {

0 commit comments

Comments
 (0)
X Tutup