Skip to content

Commit dac89a9

Browse files
committed
image/draw: add RGBA64Image fast path for RGBA dst
This should have been part of https://golang.org/cl/340049 but I overlooked it. That commit added fast path code when the destination image was *not* an *image.RGBA. This commit edits func drawRGBA. name old time/op new time/op delta RGBA1-4 5.11ms ± 1% 1.12ms ± 1% -78.01% (p=0.008 n=5+5) RGBA2-4 8.69ms ± 1% 2.98ms ± 1% -65.77% (p=0.008 n=5+5) Updates #44808. Updates #46395. Change-Id: I899d46d985634fc81ea47ff4f0d436630e8a961c Reviewed-on: https://go-review.googlesource.com/c/go/+/351852 Trust: Nigel Tao <[email protected]> Reviewed-by: Dmitri Shuralyov <[email protected]>
1 parent 54079df commit dac89a9

File tree

3 files changed

+166
-14
lines changed

3 files changed

+166
-14
lines changed

src/image/draw/bench_test.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,8 @@ func bench(b *testing.B, dcm, scm, mcm color.Model, op Op) {
190190
}
191191
}
192192

193-
// The BenchmarkFoo functions exercise a drawFoo fast-path function in draw.go.
193+
// The BenchmarkFoo and BenchmarkFooN functions exercise a drawFoo fast-path
194+
// function in draw.go.
194195

195196
func BenchmarkFillOver(b *testing.B) {
196197
bench(b, color.RGBAModel, nil, nil, Over)
@@ -232,10 +233,14 @@ func BenchmarkGlyphOver(b *testing.B) {
232233
bench(b, color.RGBAModel, nil, color.AlphaModel, Over)
233234
}
234235

235-
func BenchmarkRGBA(b *testing.B) {
236+
func BenchmarkRGBA1(b *testing.B) {
236237
bench(b, color.RGBAModel, color.RGBA64Model, nil, Src)
237238
}
238239

240+
func BenchmarkRGBA2(b *testing.B) {
241+
bench(b, color.RGBAModel, color.RGBAModel, color.AlphaModel, Over)
242+
}
243+
239244
func BenchmarkPalettedFill(b *testing.B) {
240245
bench(b, palette, nil, nil, Src)
241246
}

src/image/draw/draw.go

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,8 @@ func DrawMask(dst Image, r image.Rectangle, src image.Image, sp image.Point, mas
220220
y0, y1, dy = y1-1, y0-1, -1
221221
}
222222

223+
// FALLBACK1.17
224+
//
223225
// Try the draw.RGBA64Image and image.RGBA64Image interfaces, part of the
224226
// standard library since Go 1.17. These are like the draw.Image and
225227
// image.Image interfaces but they can avoid allocations from converting
@@ -295,6 +297,8 @@ func DrawMask(dst Image, r image.Rectangle, src image.Image, sp image.Point, mas
295297
}
296298
}
297299

300+
// FALLBACK1.0
301+
//
298302
// If none of the faster code paths above apply, use the draw.Image and
299303
// image.Image interfaces, part of the standard library since Go 1.0.
300304

@@ -615,6 +619,89 @@ func drawRGBA(dst *image.RGBA, r image.Rectangle, src image.Image, sp image.Poin
615619
sx1 := sx0 + (x1 - x0)
616620
i0 := dst.PixOffset(x0, y0)
617621
di := dx * 4
622+
623+
// Try the image.RGBA64Image interface, part of the standard library since
624+
// Go 1.17.
625+
//
626+
// This optimization is similar to how FALLBACK1.17 optimizes FALLBACK1.0
627+
// in DrawMask, except here the concrete type of dst is known to be
628+
// *image.RGBA.
629+
if src0, _ := src.(image.RGBA64Image); src0 != nil {
630+
if mask == nil {
631+
if op == Over {
632+
for y := y0; y != y1; y, sy, my = y+dy, sy+dy, my+dy {
633+
for i, sx, mx := i0, sx0, mx0; sx != sx1; i, sx, mx = i+di, sx+dx, mx+dx {
634+
srgba := src0.RGBA64At(sx, sy)
635+
d := dst.Pix[i : i+4 : i+4]
636+
dr := uint32(d[0])
637+
dg := uint32(d[1])
638+
db := uint32(d[2])
639+
da := uint32(d[3])
640+
a := (m - uint32(srgba.A)) * 0x101
641+
d[0] = uint8((dr*a/m + uint32(srgba.R)) >> 8)
642+
d[1] = uint8((dg*a/m + uint32(srgba.G)) >> 8)
643+
d[2] = uint8((db*a/m + uint32(srgba.B)) >> 8)
644+
d[3] = uint8((da*a/m + uint32(srgba.A)) >> 8)
645+
}
646+
i0 += dy * dst.Stride
647+
}
648+
} else {
649+
for y := y0; y != y1; y, sy, my = y+dy, sy+dy, my+dy {
650+
for i, sx, mx := i0, sx0, mx0; sx != sx1; i, sx, mx = i+di, sx+dx, mx+dx {
651+
srgba := src0.RGBA64At(sx, sy)
652+
d := dst.Pix[i : i+4 : i+4]
653+
d[0] = uint8(srgba.R >> 8)
654+
d[1] = uint8(srgba.G >> 8)
655+
d[2] = uint8(srgba.B >> 8)
656+
d[3] = uint8(srgba.A >> 8)
657+
}
658+
i0 += dy * dst.Stride
659+
}
660+
}
661+
return
662+
663+
} else if mask0, _ := mask.(image.RGBA64Image); mask0 != nil {
664+
if op == Over {
665+
for y := y0; y != y1; y, sy, my = y+dy, sy+dy, my+dy {
666+
for i, sx, mx := i0, sx0, mx0; sx != sx1; i, sx, mx = i+di, sx+dx, mx+dx {
667+
ma := uint32(mask0.RGBA64At(mx, my).A)
668+
srgba := src0.RGBA64At(sx, sy)
669+
d := dst.Pix[i : i+4 : i+4]
670+
dr := uint32(d[0])
671+
dg := uint32(d[1])
672+
db := uint32(d[2])
673+
da := uint32(d[3])
674+
a := (m - (uint32(srgba.A) * ma / m)) * 0x101
675+
d[0] = uint8((dr*a + uint32(srgba.R)*ma) / m >> 8)
676+
d[1] = uint8((dg*a + uint32(srgba.G)*ma) / m >> 8)
677+
d[2] = uint8((db*a + uint32(srgba.B)*ma) / m >> 8)
678+
d[3] = uint8((da*a + uint32(srgba.A)*ma) / m >> 8)
679+
}
680+
i0 += dy * dst.Stride
681+
}
682+
} else {
683+
for y := y0; y != y1; y, sy, my = y+dy, sy+dy, my+dy {
684+
for i, sx, mx := i0, sx0, mx0; sx != sx1; i, sx, mx = i+di, sx+dx, mx+dx {
685+
ma := uint32(mask0.RGBA64At(mx, my).A)
686+
srgba := src0.RGBA64At(sx, sy)
687+
d := dst.Pix[i : i+4 : i+4]
688+
d[0] = uint8(uint32(srgba.R) * ma / m >> 8)
689+
d[1] = uint8(uint32(srgba.G) * ma / m >> 8)
690+
d[2] = uint8(uint32(srgba.B) * ma / m >> 8)
691+
d[3] = uint8(uint32(srgba.A) * ma / m >> 8)
692+
}
693+
i0 += dy * dst.Stride
694+
}
695+
}
696+
return
697+
}
698+
}
699+
700+
// Use the image.Image interface, part of the standard library since Go
701+
// 1.0.
702+
//
703+
// This is similar to FALLBACK1.0 in DrawMask, except here the concrete
704+
// type of dst is known to be *image.RGBA.
618705
for y := y0; y != y1; y, sy, my = y+dy, sy+dy, my+dy {
619706
for i, sx, mx := i0, sx0, mx0; sx != sx1; i, sx, mx = i+di, sx+dx, mx+dx {
620707
ma := uint32(m)

src/image/draw/draw_test.go

Lines changed: 72 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,23 @@ func (p *slowestRGBA) PixOffset(x, y int) int {
6666
return (y-p.Rect.Min.Y)*p.Stride + (x-p.Rect.Min.X)*4
6767
}
6868

69+
func convertToSlowestRGBA(m image.Image) *slowestRGBA {
70+
if rgba, ok := m.(*image.RGBA); ok {
71+
return &slowestRGBA{
72+
Pix: append([]byte(nil), rgba.Pix...),
73+
Stride: rgba.Stride,
74+
Rect: rgba.Rect,
75+
}
76+
}
77+
rgba := image.NewRGBA(m.Bounds())
78+
Draw(rgba, rgba.Bounds(), m, m.Bounds().Min, Src)
79+
return &slowestRGBA{
80+
Pix: rgba.Pix,
81+
Stride: rgba.Stride,
82+
Rect: rgba.Rect,
83+
}
84+
}
85+
6986
func init() {
7087
var p interface{} = (*slowestRGBA)(nil)
7188
if _, ok := p.(RGBA64Image); ok {
@@ -138,6 +155,23 @@ func (p *slowerRGBA) PixOffset(x, y int) int {
138155
return (y-p.Rect.Min.Y)*p.Stride + (x-p.Rect.Min.X)*4
139156
}
140157

158+
func convertToSlowerRGBA(m image.Image) *slowerRGBA {
159+
if rgba, ok := m.(*image.RGBA); ok {
160+
return &slowerRGBA{
161+
Pix: append([]byte(nil), rgba.Pix...),
162+
Stride: rgba.Stride,
163+
Rect: rgba.Rect,
164+
}
165+
}
166+
rgba := image.NewRGBA(m.Bounds())
167+
Draw(rgba, rgba.Bounds(), m, m.Bounds().Min, Src)
168+
return &slowerRGBA{
169+
Pix: rgba.Pix,
170+
Stride: rgba.Stride,
171+
Rect: rgba.Rect,
172+
}
173+
}
174+
141175
func init() {
142176
var p interface{} = (*slowerRGBA)(nil)
143177
if _, ok := p.(RGBA64Image); !ok {
@@ -310,6 +344,32 @@ var drawTests = []drawTest{
310344
{"grayAlphaSrc", vgradGray(), fillAlpha(192), Src, color.RGBA{102, 102, 102, 192}},
311345
{"grayNil", vgradGray(), nil, Over, color.RGBA{136, 136, 136, 255}},
312346
{"grayNilSrc", vgradGray(), nil, Src, color.RGBA{136, 136, 136, 255}},
347+
// Same again, but with a slowerRGBA source.
348+
{"graySlower", convertToSlowerRGBA(vgradGray()), fillAlpha(255),
349+
Over, color.RGBA{136, 136, 136, 255}},
350+
{"graySrcSlower", convertToSlowerRGBA(vgradGray()), fillAlpha(255),
351+
Src, color.RGBA{136, 136, 136, 255}},
352+
{"grayAlphaSlower", convertToSlowerRGBA(vgradGray()), fillAlpha(192),
353+
Over, color.RGBA{136, 102, 102, 255}},
354+
{"grayAlphaSrcSlower", convertToSlowerRGBA(vgradGray()), fillAlpha(192),
355+
Src, color.RGBA{102, 102, 102, 192}},
356+
{"grayNilSlower", convertToSlowerRGBA(vgradGray()), nil,
357+
Over, color.RGBA{136, 136, 136, 255}},
358+
{"grayNilSrcSlower", convertToSlowerRGBA(vgradGray()), nil,
359+
Src, color.RGBA{136, 136, 136, 255}},
360+
// Same again, but with a slowestRGBA source.
361+
{"graySlowest", convertToSlowestRGBA(vgradGray()), fillAlpha(255),
362+
Over, color.RGBA{136, 136, 136, 255}},
363+
{"graySrcSlowest", convertToSlowestRGBA(vgradGray()), fillAlpha(255),
364+
Src, color.RGBA{136, 136, 136, 255}},
365+
{"grayAlphaSlowest", convertToSlowestRGBA(vgradGray()), fillAlpha(192),
366+
Over, color.RGBA{136, 102, 102, 255}},
367+
{"grayAlphaSrcSlowest", convertToSlowestRGBA(vgradGray()), fillAlpha(192),
368+
Src, color.RGBA{102, 102, 102, 192}},
369+
{"grayNilSlowest", convertToSlowestRGBA(vgradGray()), nil,
370+
Over, color.RGBA{136, 136, 136, 255}},
371+
{"grayNilSrcSlowest", convertToSlowestRGBA(vgradGray()), nil,
372+
Src, color.RGBA{136, 136, 136, 255}},
313373
// Uniform mask (100%, 75%, nil) and variable CMYK source.
314374
// At (x, y) == (8, 8):
315375
// The destination pixel is {136, 0, 0, 255}.
@@ -327,6 +387,16 @@ var drawTests = []drawTest{
327387
// The mask pixel's alpha is 102, or 40%.
328388
{"generic", fillBlue(255), vgradAlpha(192), Over, color.RGBA{81, 0, 102, 255}},
329389
{"genericSrc", fillBlue(255), vgradAlpha(192), Src, color.RGBA{0, 0, 102, 102}},
390+
// Same again, but with a slowerRGBA mask.
391+
{"genericSlower", fillBlue(255), convertToSlowerRGBA(vgradAlpha(192)),
392+
Over, color.RGBA{81, 0, 102, 255}},
393+
{"genericSrcSlower", fillBlue(255), convertToSlowerRGBA(vgradAlpha(192)),
394+
Src, color.RGBA{0, 0, 102, 102}},
395+
// Same again, but with a slowestRGBA mask.
396+
{"genericSlowest", fillBlue(255), convertToSlowestRGBA(vgradAlpha(192)),
397+
Over, color.RGBA{81, 0, 102, 255}},
398+
{"genericSrcSlowest", fillBlue(255), convertToSlowestRGBA(vgradAlpha(192)),
399+
Src, color.RGBA{0, 0, 102, 102}},
330400
}
331401

332402
func makeGolden(dst image.Image, r image.Rectangle, src image.Image, sp image.Point, mask image.Image, mp image.Point, op Op) image.Image {
@@ -399,19 +469,9 @@ func TestDraw(t *testing.T) {
399469
// result, in terms of final pixel RGBA values.
400470
switch i {
401471
case 1:
402-
d := dst.(*image.RGBA)
403-
dst = &slowerRGBA{
404-
Pix: d.Pix,
405-
Stride: d.Stride,
406-
Rect: d.Rect,
407-
}
472+
dst = convertToSlowerRGBA(dst)
408473
case 2:
409-
d := dst.(*image.RGBA)
410-
dst = &slowestRGBA{
411-
Pix: d.Pix,
412-
Stride: d.Stride,
413-
Rect: d.Rect,
414-
}
474+
dst = convertToSlowestRGBA(dst)
415475
}
416476

417477
// Draw the (src, mask, op) onto a copy of dst using a slow but obviously correct implementation.

0 commit comments

Comments
 (0)