对慢羊羊的半透明贴图函数进行改良
对慢羊羊的半透明贴图函数改良了一下,效率 * 2
(其实是看贴图透明区域大小)
思路是将贴图分三部分
1:完全透明的
2:半透明的
3:完全不透明的
完全透明的直接跳过计算(主要加速在这)
完全不透明的直接拷贝(但还是好慢)
减少赋值而直接将获取 RGB 值的函数放到阿尔法混合函数中
(有没有效果我不知道,但可读性差了)
先上原地址
https://codebus.cn/yangw/transparent-putimage
这是村长写的代码,明显有很多地方可以优化
// 半透明贴图函数
// 参数:
// dstimg:目标 IMAGE(NULL 表示默认窗体)
// x, y: 目标贴图位置
// srcimg: 源 IMAGE 对象指针
void transparentimage(IMAGE *dstimg, int x, int y, IMAGE *srcimg)
{
// 变量初始化
DWORD *dst = GetImageBuffer(dstimg);
DWORD *src = GetImageBuffer(srcimg);
int src_width = srcimg->getwidth();
int src_height = srcimg->getheight();
int dst_width = (dstimg == NULL ? getwidth() : dstimg->getwidth());
int dst_height = (dstimg == NULL ? getheight() : dstimg->getheight());
// 计算贴图的实际长宽
int iwidth = (x + src_width > dst_width) ? dst_width - x : src_width; // 处理超出右边界
int iheight = (y + src_height > dst_height) ? dst_height - y : src_height; // 处理超出下边界
if (x < 0) { src += -x; iwidth -= -x; x = 0; } // 处理超出左边界
if (y < 0) { src += src_width * -y; iheight -= -y; y = 0; } // 处理超出上边界
// 修正贴图起始位置
dst += dst_width * y + x;
// 实现透明贴图
for (int iy = 0; iy < iheight; iy++)
{
for (int ix = 0; ix < iwidth; ix++)
{
int sa = ((src[ix] & 0xff000000) >> 24);
int sr = ((src[ix] & 0xff0000) >> 16); // 源值已经乘过了透明系数
int sg = ((src[ix] & 0xff00) >> 8); // 源值已经乘过了透明系数
int sb = src[ix] & 0xff; // 源值已经乘过了透明系数
int dr = ((dst[ix] & 0xff0000) >> 16);
int dg = ((dst[ix] & 0xff00) >> 8);
int db = dst[ix] & 0xff;
dst[ix] = ((sr + dr * (255 - sa) / 255) << 16)
| ((sg + dg * (255 - sa) / 255) << 8)
| (sb + db * (255 - sa) / 255);
}
dst += dst_width;
src += src_width;
}
}
首先,我们完全不用每个像素点都循环执行一次,只要算需要算的像素点就行。
比如完全透明的像素点
也不必每个像素点都执行混合,只要算需要算的像素点就行。
比如完全不透明的像素
所以可以改成这样
// 半透明贴图函数
// 参数:
// dstimg:目标 IMAGE(NULL 表示默认窗体)
// x, y: 目标贴图位置
// srcimg: 源 IMAGE 对象指针
void transparentimage(IMAGE *dstimg, int x, int y, IMAGE *srcimg)
{
// 变量初始化
DWORD *dst = GetImageBuffer(dstimg);
DWORD *src = GetImageBuffer(srcimg);
int src_width = srcimg->getwidth();
int src_height = srcimg->getheight();
int dst_width = (dstimg == NULL ? getwidth() : dstimg->getwidth());
int dst_height = (dstimg == NULL ? getheight() : dstimg->getheight());
// 计算贴图的实际长宽
int iwidth = (x + src_width > dst_width) ? dst_width - x : src_width; // 处理超出右边界
int iheight = (y + src_height > dst_height) ? dst_height - y : src_height; // 处理超出下边界
if (x < 0) { src += -x; iwidth -= -x; x = 0; } // 处理超出左边界
if (y < 0) { src += src_width * -y; iheight -= -y; y = 0; } // 处理超出上边界
// 修正贴图起始位置
dst += dst_width * y + x;
// 实现透明贴图
for (int iy = 0; iy < iheight; iy++)
{
////////////////////////////////修改开始////////////////////////////////////
for (int ix = 0; ix < iwidth; ix++)
{
int sa = ((src[ix] & 0xff000000) >> 24);
//假如完全透明则不处理
if (sa != 0)
{
//假如完全不透明则直接拷贝
if (sa == 255)
{
dst[i] = src[i];
}
//真正需要阿尔法混合计算的图像边界才进行混合
else
{
int sr = ((src[ix] & 0xff0000) >> 16); // 源值已经乘过了透明系数
int sg = ((src[ix] & 0xff00) >> 8); // 源值已经乘过了透明系数
int sb = src[ix] & 0xff; // 源值已经乘过了透明系数
int dr = ((dst[ix] & 0xff0000) >> 16);
int dg = ((dst[ix] & 0xff00) >> 8);
int db = dst[ix] & 0xff;
dst[ix] = ((sr + dr * (255 - sa) / 255) << 16)
| ((sg + dg * (255 - sa) / 255) << 8)
| (sb + db * (255 - sa) / 255);
}
}
}
////////////////////////////////修改结束////////////////////////////////////
dst += dst_width;
src += src_width;
}
}
现在运行看看,效率暴涨。
不过还有优化余地(不过只对 debug 模式加速)
由于变量赋值也要时间,减少赋值次数也可以有效加速
最简单的办法就是尽可能让 CPU 一次性算完,不要分步骤
(但编译器有时会帮你优化)
于是可以改成这样
// 半透明贴图函数
// 参数:
// dstimg:目标 IMAGE(NULL 表示默认窗体)
// x, y: 目标贴图位置
// srcimg: 源 IMAGE 对象指针
void transparentimage(IMAGE *dstimg, int x, int y, IMAGE *srcimg)
{
// 变量初始化
DWORD *dst = GetImageBuffer(dstimg);
DWORD *src = GetImageBuffer(srcimg);
int src_width = srcimg->getwidth();
int src_height = srcimg->getheight();
int dst_width = (dstimg == NULL ? getwidth() : dstimg->getwidth());
int dst_height = (dstimg == NULL ? getheight() : dstimg->getheight());
// 计算贴图的实际长宽
int iwidth = (x + src_width > dst_width) ? dst_width - x : src_width; // 处理超出右边界
int iheight = (y + src_height > dst_height) ? dst_height - y : src_height; // 处理超出下边界
if (x < 0) { src += -x; iwidth -= -x; x = 0; } // 处理超出左边界
if (y < 0) { src += src_width * -y; iheight -= -y; y = 0; } // 处理超出上边界
// 修正贴图起始位置
dst += dst_width * y + x;
// 实现透明贴图
for (int iy = 0; iy < iheight; ++iy)
{
for (int i = 0; i < iwidth; ++i)
{
int sa = ((src[i] & 0xff000000) >> 24);//获取阿尔法值
if (sa != 0)//假如是完全透明就不处理
if (sa == 255)//假如完全不透明则直接拷贝
dst[i] = src[i];
else//真正需要阿尔法混合计算的图像边界才进行混合
dst[i] = ((((src[i] & 0xff0000) >> 16) + ((dst[i] & 0xff0000) >> 16) * (255 - sa) / 255) << 16) |((((src[i] & 0xff00) >> 8) + ((dst[i] & 0xff00) >> 8) * (255 - sa) / 255) << 8) | ((src[i] & 0xff) + (dst[i] & 0xff) * (255 - sa) / 255);
}
dst += dst_width;
src += src_width;
}
}
没错,就是把
int sr = ((src[ix] & 0xff0000) >> 16); // 源值已经乘过了透明系数
int sg = ((src[ix] & 0xff00) >> 8); // 源值已经乘过了透明系数
int sb = src[ix] & 0xff; // 源值已经乘过了透明系数
int dr = ((dst[ix] & 0xff0000) >> 16);
int dg = ((dst[ix] & 0xff00) >> 8);
int db = dst[ix] & 0xff;
dst[ix] = ((sr + dr * (255 - sa) / 255) << 16)
| ((sg + dg * (255 - sa) / 255) << 8)
| (sb + db * (255 - sa) / 255);
这一段给压缩成一行了
好像编译器也会进行这种优化,而且代码可读性也变差了。
是否使用这招就看自己决定(建议不要用)
接下来就看看效率。
我用自己写的游戏主界面做测试
游戏均在 30FPS 下运行
在我的 CPU 下
=================================
原版
CPU:26% 到 35% 左右
满载极限 FPS 测试:28 到 32 左右
=================================
改良版
CPU:13% 到 19% 左右
满载极限 FPS 测试:43 到 52 左右
=================================
这优化。。。。。。。。
其实我还想做多线程优化的,但不太会。。。。。
但其实这优化还是有缺点的
如果都是半透明像素的话有可能会负优化。
不过一般也不会出现这种情况吧。
就这样吧