Commit 13f08cd8 authored by Nigel Tao's avatar Nigel Tao

exp/draw: fast paths for drawing a YCbCr or an NRGBA onto an RGBA.

On my laptop, I had an 800x600 jpeg and an 800x600 png (with
transparency). I timed how long it took to draw each image onto an
equivalently sized, zeroed RGBA image.

Previously, the jpeg took 75ms and the png took 70ms, going through
the medium-fast path, i.e. func drawRGBA in draw.go.

After this CL, the jpeg took 14ms, and the png took 21ms with the
Over operator and 12ms with the Src operator.

It's only a rough estimate basd on one image file, but it should
give an idea of the order of magnitude of improvement.

R=rsc, r
CC=adg, golang-dev
https://golang.org/cl/4468044
parent e31272aa
...@@ -8,7 +8,10 @@ ...@@ -8,7 +8,10 @@
// and the X Render extension. // and the X Render extension.
package draw package draw
import "image" import (
"image"
"image/ycbcr"
)
// m is the maximum color value returned by image.Color.RGBA. // m is the maximum color value returned by image.Color.RGBA.
const m = 1<<16 - 1 const m = 1<<16 - 1
...@@ -65,29 +68,42 @@ func DrawMask(dst Image, r image.Rectangle, src image.Image, sp image.Point, mas ...@@ -65,29 +68,42 @@ func DrawMask(dst Image, r image.Rectangle, src image.Image, sp image.Point, mas
if dst0, ok := dst.(*image.RGBA); ok { if dst0, ok := dst.(*image.RGBA); ok {
if op == Over { if op == Over {
if mask == nil { if mask == nil {
if src0, ok := src.(*image.ColorImage); ok { switch src0 := src.(type) {
case *image.ColorImage:
drawFillOver(dst0, r, src0) drawFillOver(dst0, r, src0)
return return
} case *image.RGBA:
if src0, ok := src.(*image.RGBA); ok {
drawCopyOver(dst0, r, src0, sp) drawCopyOver(dst0, r, src0, sp)
return return
case *image.NRGBA:
drawNRGBAOver(dst0, r, src0, sp)
return
case *ycbcr.YCbCr:
drawYCbCr(dst0, r, src0, sp)
return
} }
} else if mask0, ok := mask.(*image.Alpha); ok { } else if mask0, ok := mask.(*image.Alpha); ok {
if src0, ok := src.(*image.ColorImage); ok { switch src0 := src.(type) {
case *image.ColorImage:
drawGlyphOver(dst0, r, src0, mask0, mp) drawGlyphOver(dst0, r, src0, mask0, mp)
return return
} }
} }
} else { } else {
if mask == nil { if mask == nil {
if src0, ok := src.(*image.ColorImage); ok { switch src0 := src.(type) {
case *image.ColorImage:
drawFillSrc(dst0, r, src0) drawFillSrc(dst0, r, src0)
return return
} case *image.RGBA:
if src0, ok := src.(*image.RGBA); ok {
drawCopySrc(dst0, r, src0, sp) drawCopySrc(dst0, r, src0, sp)
return return
case *image.NRGBA:
drawNRGBASrc(dst0, r, src0, sp)
return
case *ycbcr.YCbCr:
drawYCbCr(dst0, r, src0, sp)
return
} }
} }
} }
...@@ -224,6 +240,36 @@ func drawCopyOver(dst *image.RGBA, r image.Rectangle, src *image.RGBA, sp image. ...@@ -224,6 +240,36 @@ func drawCopyOver(dst *image.RGBA, r image.Rectangle, src *image.RGBA, sp image.
} }
} }
func drawNRGBAOver(dst *image.RGBA, r image.Rectangle, src *image.NRGBA, sp image.Point) {
for y, sy := r.Min.Y, sp.Y; y != r.Max.Y; y, sy = y+1, sy+1 {
dpix := dst.Pix[y*dst.Stride : (y+1)*dst.Stride]
spix := src.Pix[sy*src.Stride : (sy+1)*src.Stride]
for x, sx := r.Min.X, sp.X; x != r.Max.X; x, sx = x+1, sx+1 {
// Convert from non-premultiplied color to pre-multiplied color.
// The order of operations here is to match the NRGBAColor.RGBA
// method in image/color.go.
snrgba := spix[sx]
sa := uint32(snrgba.A)
sr := uint32(snrgba.R) * 0x101 * sa / 0xff
sg := uint32(snrgba.G) * 0x101 * sa / 0xff
sb := uint32(snrgba.B) * 0x101 * sa / 0xff
sa *= 0x101
rgba := dpix[x]
dr := uint32(rgba.R)
dg := uint32(rgba.G)
db := uint32(rgba.B)
da := uint32(rgba.A)
a := (m - sa) * 0x101
dr = (dr*a + sr*m) / m
dg = (dg*a + sg*m) / m
db = (db*a + sb*m) / m
da = (da*a + sa*m) / m
dpix[x] = image.RGBAColor{uint8(dr >> 8), uint8(dg >> 8), uint8(db >> 8), uint8(da >> 8)}
}
}
}
func drawGlyphOver(dst *image.RGBA, r image.Rectangle, src *image.ColorImage, mask *image.Alpha, mp image.Point) { func drawGlyphOver(dst *image.RGBA, r image.Rectangle, src *image.ColorImage, mask *image.Alpha, mp image.Point) {
x0, x1 := r.Min.X, r.Max.X x0, x1 := r.Min.X, r.Max.X
y0, y1 := r.Min.Y, r.Max.Y y0, y1 := r.Min.Y, r.Max.Y
...@@ -311,6 +357,73 @@ func drawCopySrc(dst *image.RGBA, r image.Rectangle, src *image.RGBA, sp image.P ...@@ -311,6 +357,73 @@ func drawCopySrc(dst *image.RGBA, r image.Rectangle, src *image.RGBA, sp image.P
} }
} }
func drawNRGBASrc(dst *image.RGBA, r image.Rectangle, src *image.NRGBA, sp image.Point) {
for y, sy := r.Min.Y, sp.Y; y != r.Max.Y; y, sy = y+1, sy+1 {
dpix := dst.Pix[y*dst.Stride : (y+1)*dst.Stride]
spix := src.Pix[sy*src.Stride : (sy+1)*src.Stride]
for x, sx := r.Min.X, sp.X; x != r.Max.X; x, sx = x+1, sx+1 {
// Convert from non-premultiplied color to pre-multiplied color.
// The order of operations here is to match the NRGBAColor.RGBA
// method in image/color.go.
snrgba := spix[sx]
sa := uint32(snrgba.A)
sr := uint32(snrgba.R) * 0x101 * sa / 0xff
sg := uint32(snrgba.G) * 0x101 * sa / 0xff
sb := uint32(snrgba.B) * 0x101 * sa / 0xff
sa *= 0x101
dpix[x] = image.RGBAColor{uint8(sr >> 8), uint8(sg >> 8), uint8(sb >> 8), uint8(sa >> 8)}
}
}
}
func drawYCbCr(dst *image.RGBA, r image.Rectangle, src *ycbcr.YCbCr, sp image.Point) {
// A YCbCr image is always fully opaque, and so if the mask is implicitly nil
// (i.e. fully opaque) then the op is effectively always Src.
var (
yy, cb, cr uint8
rr, gg, bb uint8
)
switch src.SubsampleRatio {
case ycbcr.SubsampleRatio422:
for y, sy := r.Min.Y, sp.Y; y != r.Max.Y; y, sy = y+1, sy+1 {
dpix := dst.Pix[y*dst.Stride : (y+1)*dst.Stride]
for x, sx := r.Min.X, sp.X; x != r.Max.X; x, sx = x+1, sx+1 {
i := sx / 2
yy = src.Y[sy*src.YStride+sx]
cb = src.Cb[sy*src.CStride+i]
cr = src.Cr[sy*src.CStride+i]
rr, gg, bb = ycbcr.YCbCrToRGB(yy, cb, cr)
dpix[x] = image.RGBAColor{rr, gg, bb, 255}
}
}
case ycbcr.SubsampleRatio420:
for y, sy := r.Min.Y, sp.Y; y != r.Max.Y; y, sy = y+1, sy+1 {
dpix := dst.Pix[y*dst.Stride : (y+1)*dst.Stride]
for x, sx := r.Min.X, sp.X; x != r.Max.X; x, sx = x+1, sx+1 {
i, j := sx/2, sy/2
yy = src.Y[sy*src.YStride+sx]
cb = src.Cb[j*src.CStride+i]
cr = src.Cr[j*src.CStride+i]
rr, gg, bb = ycbcr.YCbCrToRGB(yy, cb, cr)
dpix[x] = image.RGBAColor{rr, gg, bb, 255}
}
}
default:
// Default to 4:4:4 subsampling.
for y, sy := r.Min.Y, sp.Y; y != r.Max.Y; y, sy = y+1, sy+1 {
dpix := dst.Pix[y*dst.Stride : (y+1)*dst.Stride]
for x, sx := r.Min.X, sp.X; x != r.Max.X; x, sx = x+1, sx+1 {
yy = src.Y[sy*src.YStride+sx]
cb = src.Cb[sy*src.CStride+sx]
cr = src.Cr[sy*src.CStride+sx]
rr, gg, bb = ycbcr.YCbCrToRGB(yy, cb, cr)
dpix[x] = image.RGBAColor{rr, gg, bb, 255}
}
}
}
}
func drawRGBA(dst *image.RGBA, r image.Rectangle, src image.Image, sp image.Point, mask image.Image, mp image.Point, op Op) { func drawRGBA(dst *image.RGBA, r image.Rectangle, src image.Image, sp image.Point, mask image.Image, mp image.Point, op Op) {
x0, x1, dx := r.Min.X, r.Max.X, 1 x0, x1, dx := r.Min.X, r.Max.X, 1
y0, y1, dy := r.Min.Y, r.Max.Y, 1 y0, y1, dy := r.Min.Y, r.Max.Y, 1
......
...@@ -6,6 +6,7 @@ package draw ...@@ -6,6 +6,7 @@ package draw
import ( import (
"image" "image"
"image/ycbcr"
"testing" "testing"
) )
...@@ -43,6 +44,34 @@ func vgradAlpha(alpha int) image.Image { ...@@ -43,6 +44,34 @@ func vgradAlpha(alpha int) image.Image {
return m return m
} }
func vgradGreenNRGBA(alpha int) image.Image {
m := image.NewNRGBA(16, 16)
for y := 0; y < 16; y++ {
for x := 0; x < 16; x++ {
m.Set(x, y, image.RGBAColor{0, uint8(y * 0x11), 0, uint8(alpha)})
}
}
return m
}
func vgradCr() image.Image {
m := &ycbcr.YCbCr{
Y: make([]byte, 16*16),
Cb: make([]byte, 16*16),
Cr: make([]byte, 16*16),
YStride: 16,
CStride: 16,
SubsampleRatio: ycbcr.SubsampleRatio444,
Rect: image.Rect(0, 0, 16, 16),
}
for y := 0; y < 16; y++ {
for x := 0; x < 16; x++ {
m.Cr[y*m.CStride+x] = uint8(y * 0x11)
}
}
return m
}
func hgradRed(alpha int) Image { func hgradRed(alpha int) Image {
m := image.NewRGBA(16, 16) m := image.NewRGBA(16, 16)
for y := 0; y < 16; y++ { for y := 0; y < 16; y++ {
...@@ -95,6 +124,27 @@ var drawTests = []drawTest{ ...@@ -95,6 +124,27 @@ var drawTests = []drawTest{
{"copyAlphaSrc", vgradGreen(90), fillAlpha(192), Src, image.RGBAColor{0, 36, 0, 68}}, {"copyAlphaSrc", vgradGreen(90), fillAlpha(192), Src, image.RGBAColor{0, 36, 0, 68}},
{"copyNil", vgradGreen(90), nil, Over, image.RGBAColor{88, 48, 0, 255}}, {"copyNil", vgradGreen(90), nil, Over, image.RGBAColor{88, 48, 0, 255}},
{"copyNilSrc", vgradGreen(90), nil, Src, image.RGBAColor{0, 48, 0, 90}}, {"copyNilSrc", vgradGreen(90), nil, Src, image.RGBAColor{0, 48, 0, 90}},
// Uniform mask (100%, 75%, nil) and variable NRGBA source.
// At (x, y) == (8, 8):
// The destination pixel is {136, 0, 0, 255}.
// The source pixel is {0, 136, 0, 90} in NRGBA-space, which is {0, 48, 0, 90} in RGBA-space.
// The result pixel is different than in the "copy*" test cases because of rounding errors.
{"nrgba", vgradGreenNRGBA(90), fillAlpha(255), Over, image.RGBAColor{88, 46, 0, 255}},
{"nrgbaSrc", vgradGreenNRGBA(90), fillAlpha(255), Src, image.RGBAColor{0, 46, 0, 90}},
{"nrgbaAlpha", vgradGreenNRGBA(90), fillAlpha(192), Over, image.RGBAColor{100, 34, 0, 255}},
{"nrgbaAlphaSrc", vgradGreenNRGBA(90), fillAlpha(192), Src, image.RGBAColor{0, 34, 0, 68}},
{"nrgbaNil", vgradGreenNRGBA(90), nil, Over, image.RGBAColor{88, 46, 0, 255}},
{"nrgbaNilSrc", vgradGreenNRGBA(90), nil, Src, image.RGBAColor{0, 46, 0, 90}},
// Uniform mask (100%, 75%, nil) and variable YCbCr source.
// At (x, y) == (8, 8):
// The destination pixel is {136, 0, 0, 255}.
// The source pixel is {0, 0, 136} in YCbCr-space, which is {11, 38, 0, 255} in RGB-space.
{"ycbcr", vgradCr(), fillAlpha(255), Over, image.RGBAColor{11, 38, 0, 255}},
{"ycbcrSrc", vgradCr(), fillAlpha(255), Src, image.RGBAColor{11, 38, 0, 255}},
{"ycbcrAlpha", vgradCr(), fillAlpha(192), Over, image.RGBAColor{42, 28, 0, 255}},
{"ycbcrAlphaSrc", vgradCr(), fillAlpha(192), Src, image.RGBAColor{8, 28, 0, 192}},
{"ycbcrNil", vgradCr(), nil, Over, image.RGBAColor{11, 38, 0, 255}},
{"ycbcrNilSrc", vgradCr(), nil, Src, image.RGBAColor{11, 38, 0, 255}},
// Variable mask and variable source. // Variable mask and variable source.
// At (x, y) == (8, 8): // At (x, y) == (8, 8):
// The destination pixel is {136, 0, 0, 255}. // The destination pixel is {136, 0, 0, 255}.
......
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