对慢羊羊的半透明贴图函数进行改良 铜牌收录

对慢羊羊的半透明贴图函数改良了一下,效率 * 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 左右

=================================

这优化。。。。。。。。

其实我还想做多线程优化的,但不太会。。。。。

但其实这优化还是有缺点的

如果都是半透明像素的话有可能会负优化。

不过一般也不会出现这种情况吧。

就这样吧

评论 (2) -

  • 可以利用在循环中直接让指针指向下一个数组元素(++),对于大图片输出应该会有部分性能提升
  • (255 - sa) / 255)的除数可以近似为256,也就是2^8,就可以改为(255 - sa)<<8,虽然精准度少了一点,不清楚能不能增加性能,听说移位的速度比除法快

添加评论