您的位置:首页 > 其它

图像透明拷贝--不同方法的效率比较

2010-07-14 10:24 148 查看
基于 Windows GDI 的图像透明拷贝,我知道的有三种方法。第一种方法是用 MaskBlt() 函数,第二种方法是我从网上看来的,用 SRCINVERT 的 DC 拷贝 (BitBlt) 的方法。第三种方法是直接处理像素。

先晾一下我的测试结论:

方法 每像素处理所花时间 备注
MaskBlt 22 Clk 无
DC 辗转拷贝的方法 34~35 Clk 无
自己拷贝像素 19 Clk 仅仅直接拷贝像素
自己处理像素 38~40 Clk 除了拷贝以外,还跟背景进行了 Alpha 混合
自己处理像素并 MMX 优化 26 Clk 自己拷贝像素,在跟背景进行 Alpha 混合时用 MMX 指令进行优化

这里时间单位是 Clk,也就是 CPU 指令周期,它一般等于主频的倒数。例如你的主频是 1GHz,那么 1 Clk 大概相当于 1 纳秒。(注意:CPU 不管执行什么指令最少也需要 1 Clk。)
我的测试平台是 Windows XP + Intel Centrino 3。

这三种方法对像素的处理代码如下。其中 TOP_ 是前景图 hDcImg 的高、宽参数,BOTTOM_ 是背景图片 hDcPic 的高、宽参数。图片都是 24 色 Bitmap,处理的目的是把前景图透明地(即有些地方镂空)拷到背景上。
1、MaskBlt:
MaskBlt(hDcPic,0,0,TOP_WIDTH,TOP_HEIGHT,hDcImg,0,0,hPic/*这就是那个 Mask Bitmap*/,0,0,MAKEROP4(SRCCOPY,SRCAND));
2、DC 拷贝方法:
//hTrans 是原前景图的对应的单色图:
// hDcTrans=CreateCompatibleDC(hDcScreen);
// bmptrans=CreateBitmap(TOP_WIDTH,TOP_HEIGHT,1,1,NULL);
// SelectObject(hDcTrans,bmptrans);
// BitBlt(hDcTrans,0,0,TOP_WIDTH,TOP_HEIGHT,hDcImg,0,0,SRCCOPY);
BitBlt(hDcPic,0,0,TOP_WIDTH,TOP_HEIGHT,hDcImg,0,0,SRCINVERT);
BitBlt(hDcPic,0,0,TOP_WIDTH,TOP_HEIGHT,hDcTrans,0,0,SRCAND);
BitBlt(hDcPic,0,0,TOP_WIDTH,TOP_HEIGHT,hDcImg,0,0,SRCINVERT);
3、直接像素的处理方法:
这种方法是获取图像的 RGB 数据,然后自己手动处理。操作步骤是:先用 GetDIBits() 获取图片和背景的图像数据,处理完了后再用 SetDIBits() 设 Bitmap,如果 Bitmap 和 DC 绑定的话那么 DC 也会同步改变的,因此直接把 hDcPic 拷到屏幕上就行了。像素的处理过程如下:
//BYTE * nPic:背景图数据的内存指针
//BYTE * nTop:前景图数据的内存指针
//int linebyteTop,linebytePic: 分别是前景图与背景图一根扫描线所对应的数据量
int base, base1; //分别指向当前处理的前景图、背景图的像素
int _base1 = t * linebytePic + l + l + l; int _base = 0; //这个表达式可以把图片拷到背景图片 (l,t) 的位置
for (j=0; j<TOP_HEIGHT; j++){
for (i=0; i<TOP_WIDTH; i++){
base = _base + i + i + i; base1 = _base1 + i + i + i; //用加不用乘,可以节省少许时间
if (pTop[base+0]!=0xFF && pTop[base+1]!=0xFF && pTop[base+2]!=0xFF){ //这一行判断透明色,这里取白色为透明色
pPic[base1+2] = pTop[base+2];
pPic[base1+1] = pTop[base+1];
pPic[base1+0] = pTop[base+0];
}
}
_base += linebyteTop; _base1 += linebytePic;
}
这三种方法各有各的好处。MaskBlt 虽然最简单,但需要一张 Mask Bitmap;DC 方法麻烦一些,但不需要这张 Mask Bitmap 了;直接像素处理代码比较长,还要申请一大堆内存。当然,内存效率当然只是一个方面,当我们有一个足够大的内存时,CPU 效率便显得举足轻重了。
我对这三种方法进行了效率测试,代码是用 VC6 编译的,测试平台是 Windows XP + Intel Centrino 3。测试代码如下。(注:因为使用了 Pentium (586) 指令,所以只能在兼容 Pentium 指令系统的机器上执行。)
int l_old,h_old;
__asm {
rdtsc
mov l_old,eax
mov h_old,edx
}
//这里放要测试的代码
__asm {
rdtsc
sub eax,l_old
sbb edx,h_old
mov l_old,eax
mov h_old,edx
}
timecost = ((INT64)h_old*65536*65536+l_old);
timecost 中就是代码的执行时间,单位是 CPU Clock,能够精确到微秒。
这里是我的测试结果:
方法 每像素系统耗费 备注
MaskBlt 22 Clk
DC 辗转拷贝的方法 34 ~ 35 Clk
直接像素处理 19 Clk 其中处理像素花了 17 Clk,SetDIBits() 花了 2 Clk

由此可见,用 Windows 提供的 API 函数进行透明拷贝是非常糟糕的。虽然代码非常简洁,但却耗费了大量的 CPU。同时,用 Windows API 几乎没法进行 Alpha 混合,而 Alpha 混合本身是包含透明拷贝的(如果把 Alpha 设为 0 的话)。

如果在上面直接像素的处理中引入 Alpha 混合,则那三行像素赋值语句应该被改作:
pPic[base1+2] = pTop[base+2]*a + pBottom[base1+2]*(1-a);
pPic[base1+1] = pTop[base+1]*a + pBottom[base1+1]*(1-a);
pPic[base1+0] = pTop[base+0]*a + pBottom[base1+0]*(1-a);
这种 Alpha 混合算法其实是非常费时的,因为牵涉到两个乘法。我设 a 为 float 型数,结果这段代码引入后,平均处理每个像素系统要花 38~40 Clk,也就是说这段代码耗费了 20 Clk。
为了提高效率,我尝试用 MMX 指令来优化这段代码。优化那三行赋值语句被改成如下这个样子:
alpha.i16[0] = ialpha; alpha.i16[1] = ialpha; alpha.i16[2] = ialpha; alpha.i16[3] = ialpha;
forgnd.i16[0] = pTop[base]; forgnd.i16[1] = pTop[base+1]; forgnd.i16[2] = pTop[base+2];
bkgnd.i16[0] = pBottom[base1]; bkgnd.i16[1] = pBottom[base1+1]; bkgnd.i16[2] = pBottom[base1+2];
__asm {
movq mm0,i64100.i64
psubusw mm0,alpha.i64 //mm0 = 1 - alpha
pmullw mm0,bkgnd.i64
movq mm1,alpha.i64
pmullw mm1,forgnd.i64
paddusw mm0,mm1
movq result.i64,mm0
}
pPic[base1+2] = result.i16[2]>>7;
pPic[base1+1] = result.i16[1]>>7;
pPic[base1+0] = result.i16[0]>>7;
这段代码所要用到的一些变量需要在处理像素的循环以外定义:
typedef union __uuint64__ {
UINT64 i64;
unsigned int i32[2];
unsigned short i16[4];
unsigned char i8[8];
} uuint64;
uuint64 forgnd, bkgnd, result, alpha;
uuint64 i64100 = { 0x008000800080 };
int ialpha = a*128; //a 便是 Alpha。如果这行代码要反复执行,建议写成 ialpha = a<<7。
因为 MMX 指令占用了 FPU,所以这里不能用浮点乘法了,因此采用了个变通的办法:把 alpha 乘上 128,算完了以后再把这个 128 给除回来(即右移 7 位)。之所以采用 128,是因为 MMX 乘法指令只针对有符号数,而像素值是无符号的。
经测试,这段代码的效率是:平均每像素 26 Clk,比直接用浮点乘法快了近一倍。虽然还是比 MaskBlt 慢一点,但这里毕竟附带了 alpha 拷贝。

至此,我得出的结论是:在进行 DC 的透明拷贝的时候,如果对效率特别在意,则应当自己处理像素。虽然麻烦一点,但一来可以做很多 Windows 没有的功能(如 Gamma 变换、YUV变换、亮度变换等等),二来效率最高。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: