最近需要写一段位图操作的代码,功能是将一幅特定的Logo位图贴到视频中的每
一帧上,实现类似电视台台标的效果,限制就是要过滤小位图中的特定颜色,避
免出现难看的矩形块。
如果用 GDI来做,用 TransparentBlt可以做到,可实际环境不允许,只能直接操作
位图数据。
最先在网上找了一些位图透明叠加的实现,多数是采用mask的方法,也就是针对
Logo位图,设计一张同样大小和颜色深度的mask位图,将需要过滤的颜色置为黑
色,然后用bitblt两次,第一次将Logo位图和mask进行and,第二次将and过的结
果copy到视频帧上。
这个方法比较通用,但是也比较烦琐,如果不考虑bitblt,完全可以自己写位图
操作函数,凡是遇到特定颜色就跳过,假设需要过滤的颜色为黑色(RGB值为0),
也假设位图是256位的,也就是每个像素的颜色用一个字节就可以表示。
用以下代码可以实现位图叠加的基本功能:
void BitmapOverlay(...)
{
BYTE *pSource = ... /* 来源位图数据,也即Logo位图的数据 */
DWORD dwSourceHeight = ... /* Logo位图的高度 */
DWORD dwSourceWidth = ... /* Logo位图的宽度 */
BYTE *pTarget = ... /* 目标位图数据,也即视频帧的位图数据 */
DWORD dwTargetHeight = ... /* 目标位图的高度 */
DWORD dwTargetWidth = ... /* 目标位图的宽度 */
DWORD dwOffsetX = ... /* Logo位图在目标位图上的X偏移位置 */
DWORD dwOffsetY = ... /* Logo位图在目标位图上的Y偏移位置 */
for (DWORD dwSourceLine = 0; dwSourceLine < dwSourceHeight; dwLine++) {
for (DWORD dwSourcePixel = 0; dwSourcePixel < dwSourceWidth; dwSourcePixel++) {
DWORD dwSourcePos = dwSourceLine*dwSourceWidth + dwSourcePixel;
if (pSource[dwSourcePos]) {
DWORD dwTargetLine = dwSourceLine + dwOffsetY;
DWORD dwTargetPixel = dwSourcePixel + dwOffsetX;
DWORD dwTargetPos = dwTargetLine*dwTargetWidth + dwTargetPixel;
pTarget[dwTargetPos] = pSource[dwSourcePos];
}
}
}
}
上面的代码实现了位图叠加的功能,但是性能...实在太低了。
很明显需要优化的地方就是循环中的乘法,将其修改为加法实现,
void BitmapOverlay(...)
{
...
DWORD dwSourcePos = 0; /* Logo位图当前待处理像素的位置 */
DWORD dwTargetPos = 0; /* 目标位图当前待处理像素的位置 */
dwTargetPos = dwOffsetY * dwTargetWidth + dwOffsetX;
for (DWORD dwSourceLine = 0; dwSourceLine < dwSourceHeight; dwLine++) {
for (DWORD dwSourcePixel = 0; dwSourcePixel < dwSourceWidth; dwSourcePixel++) {
if (pSource[dwSourcePos]) {
pTarget[dwTargetPos] = pSource[dwSourcePos];
}
dwSourcePos++;
dwTargetPos++;
}
/* 跳到目标位图下一行的对应位置 */
dwTargetPos += (dwTargetWidth - dwSourceWidth + dwOffsetX);
}
}
上面代码的性能,比优化前提高了很多,但这仍然是在Bitblt的思路上,
Bitblt本身是一个通用的GDI函数,而我们要处理的是一个特定的场景,
其中Logo位图的数据是恒定的,只是目标位图的数据在不断变化。
既然存在恒定的数据,那就可以采用预处理的方法,减少后期的计算量。
t ypedef tagLogoData {
BYTE byColor;
DWORD dwPos;
} LogoData;
void PreProcessSource(...)
{
BYTE *pSource = ... /* 来源位图数据,也即Logo位图的数据 */
DWORD dwSourceHeight = ... /* Logo位图的高度 */
DWORD dwSourceWidth = ... /* Logo位图的宽度 */
DWORD dwTargetHeight = ... /* 目标位图的高度 */
DWORD dwTargetWidth = ... /* 目标位图的宽度 */
DWORD dwOffsetX = ... /* Logo位图在目标位图上的X偏移位置 */
DWORD dwOffsetY = ... /* Logo位图在目标位图上的Y偏移位置 */
DWORD dwSourcePos = 0; /* Logo位图当前待处理像素的位置 */
DWORD dwTargetPos = 0; /* 目标位图当前待处理像素的位置 */
dwTargetPos = dwOffsetY * dwTargetWidth + dwOffsetX;
LogoData *pLogoData = new .../* 分配足够的内存给Logo数据的数组 */;
DWORD dwLogoDataSize = 0;
for (DWORD dwSourceLine = 0; dwSourceLine < dwSourceHeight; dwLine++) {
for (DWORD dwSourcePixel = 0; dwSourcePixel < dwSourceWidth; dwSourcePixel++) {
if (pSource[dwSourcePos]) {
pLogoData->byColor = pSource[dwSourcePos];
pLogoData->dwPos = dwTargetPos;
pLogoData++;
dwLogoDataSize++;
}
dwSourcePos++;
dwTargetPos++;
}
/* 跳到目标位图下一行的对应位置 */
dwTargetPos += (dwTargetWidth - dwSourceWidth + dwOffsetX);
}
}
此时的位图叠加函数就变得非常简单
void BitmapOverlay(...)
{
LogoData *pLogoData = ... /* Logo数据的数组 */;
DWORD dwLogoDataSize = ... /* Logo数据的数组的长度 */;
for (DWORD dwIndex = 0; dwIndex < dwLogoDataSize; dwIndex++) {
pTarget[pLogoData->dwPos] = pLogoData->byColor;
}
}
如果Logo位图中的有效数据较少(比如是文字),那采用预处理的做法就能
有效提高性能。
如果是较大块的图片,则还有进一步优化的余地——上面所有的实现中,都采
取了按字节赋值的方法,实际上对于较大块的图片来说,连续的有效的位图数
据占绝大多数,只需要在预处理时,将有效的联系位图数据组成一个个内存块,
然后在在叠加时就可以CopyMemory来进一步提高性能。