mirror of
https://github.com/golang/go
synced 2024-11-24 08:10:09 -07:00
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
This commit is contained in:
parent
e31272aa05
commit
13f08cd807
@ -8,7 +8,10 @@
|
||||
// and the X Render extension.
|
||||
package draw
|
||||
|
||||
import "image"
|
||||
import (
|
||||
"image"
|
||||
"image/ycbcr"
|
||||
)
|
||||
|
||||
// m is the maximum color value returned by image.Color.RGBA.
|
||||
const m = 1<<16 - 1
|
||||
@ -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 op == Over {
|
||||
if mask == nil {
|
||||
if src0, ok := src.(*image.ColorImage); ok {
|
||||
switch src0 := src.(type) {
|
||||
case *image.ColorImage:
|
||||
drawFillOver(dst0, r, src0)
|
||||
return
|
||||
}
|
||||
if src0, ok := src.(*image.RGBA); ok {
|
||||
case *image.RGBA:
|
||||
drawCopyOver(dst0, r, src0, sp)
|
||||
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 {
|
||||
if src0, ok := src.(*image.ColorImage); ok {
|
||||
switch src0 := src.(type) {
|
||||
case *image.ColorImage:
|
||||
drawGlyphOver(dst0, r, src0, mask0, mp)
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if mask == nil {
|
||||
if src0, ok := src.(*image.ColorImage); ok {
|
||||
switch src0 := src.(type) {
|
||||
case *image.ColorImage:
|
||||
drawFillSrc(dst0, r, src0)
|
||||
return
|
||||
}
|
||||
if src0, ok := src.(*image.RGBA); ok {
|
||||
case *image.RGBA:
|
||||
drawCopySrc(dst0, r, src0, sp)
|
||||
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.
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
x0, x1 := r.Min.X, r.Max.X
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
x0, x1, dx := r.Min.X, r.Max.X, 1
|
||||
y0, y1, dy := r.Min.Y, r.Max.Y, 1
|
||||
|
@ -6,6 +6,7 @@ package draw
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/ycbcr"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@ -43,6 +44,34 @@ func vgradAlpha(alpha int) image.Image {
|
||||
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 {
|
||||
m := image.NewRGBA(16, 16)
|
||||
for y := 0; y < 16; y++ {
|
||||
@ -95,6 +124,27 @@ var drawTests = []drawTest{
|
||||
{"copyAlphaSrc", vgradGreen(90), fillAlpha(192), Src, image.RGBAColor{0, 36, 0, 68}},
|
||||
{"copyNil", vgradGreen(90), nil, Over, image.RGBAColor{88, 48, 0, 255}},
|
||||
{"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.
|
||||
// At (x, y) == (8, 8):
|
||||
// The destination pixel is {136, 0, 0, 255}.
|
||||
|
Loading…
Reference in New Issue
Block a user