huidong

只有枯燥的练习能收获极致的熟练,只有机械的重复才有资格拥抱唯美的创造。

保留透明(Alpha)信息地旋转图像 铜牌收录

前言

事实上我并未打算造这个轮子,因为 Lost 已经实现了一个相同功能的函数,见 文章

Lost 实现的函数确实可用,但存在致命 bug,因此我也在他的文章下留言回复,并私信了他,但由于他暂时没有回复,我便自行实现了一个。

原理

其实不用多说原理,只是基本的二维坐标旋转,此处不再赘述,如有兴趣,可以必应。

唯一需要注意的就是,映射坐标的时候,应该从旋转后的图像去对应原图像,否则反过来的话,由于像素密度不够,旋转出的图像会出现“空点”。

代码

注意:运行示例代码前,请在项目目录下放置一张 png 图像,并记得在 main 函数中确认您的图片路径。或者可以用下面这张,并命名为 sheep.png 放在项目目录下。

sheep.png

#include <easyx.h>
#include <math.h>

// 引用该库才能使用 AlphaBlend 函数
#pragma comment( lib, "MSIMG32.LIB")

// 圆周率
// 旋转函数未使用圆周率,只是在示例代码中用到
#define PI 3.1415926

// 旋转图像(保留透明信息,自适应大小)
// pImg 原图像
// radian 旋转弧度
// bkcolor 背景填充颜色
// 返回旋转后的图像
IMAGE RotateImage_Alpha(IMAGE* pImg, double radian, COLORREF bkcolor = BLACK)
{
	radian = -radian;														// 由于 y 轴翻转,旋转角度需要变负
	float fSin = (float)sin(radian), fCos = (float)cos(radian);				// 存储三角函数值
	float fNSin = (float)sin(-radian), fNCos = (float)cos(-radian);
	int left = 0, top = 0, right = 0, bottom = 0;							// 旋转后图像顶点
	int w = pImg->getwidth(), h = pImg->getheight();
	DWORD* pBuf = GetImageBuffer(pImg);
	POINT points[4] = { {0, 0}, {w, 0}, {0, h}, {w, h} };					// 存储图像顶点
	for (int j = 0; j < 4; j++)												// 旋转图像顶点,搜索旋转后的图像边界
	{
		points[j] = {
			(int)(points[j].x * fCos - points[j].y * fSin),
			(int)(points[j].x * fSin + points[j].y * fCos)
		};
		if (points[j].x < points[left].x)	left = j;
		if (points[j].y > points[top].y)	top = j;
		if (points[j].x > points[right].x)	right = j;
		if (points[j].y < points[bottom].y)	bottom = j;
	}

	int nw = points[right].x - points[left].x;								// 旋转后的图像尺寸
	int nh = points[top].y - points[bottom].y;
	int nSize = nw * nh;
	int offset_x = points[left].x < 0 ? points[left].x : 0;					// 旋转后图像超出第一象限的位移(据此调整图像位置)
	int offset_y = points[bottom].y < 0 ? points[bottom].y : 0;

	IMAGE img(nw, nh);
	DWORD* pNewBuf = GetImageBuffer(&img);
	if (bkcolor != BLACK)													// 设置图像背景色
		for (int i = 0; i < nSize; i++)
			pNewBuf[i] = BGR(bkcolor);

	for (int i = offset_x, ni = 0; ni < nw; i++, ni++)						// i 用于映射原图像坐标,ni 用于定位旋转后图像坐标
	{
		for (int j = offset_y, nj = 0; nj < nh; j++, nj++)
		{
			int nx = (int)(i * fNCos - j * fNSin);							// 从旋转后的图像坐标向原图像坐标映射
			int ny = (int)(i * fNSin + j * fNCos);
			if (nx >= 0 && nx < w && ny >= 0 && ny < h)						// 若目标映射在原图像范围内,则拷贝色值
				pNewBuf[nj * nw + ni] = pBuf[ny * w + nx];
		}
	}

	return img;
}

// 透明贴图(by 慢羊羊)
void transparentimage(IMAGE* dstimg, int x, int y, IMAGE* srcimg)
{
	HDC dstDC = GetImageHDC(dstimg);
	HDC srcDC = GetImageHDC(srcimg);
	int w = srcimg->getwidth();
	int h = srcimg->getheight();
	BLENDFUNCTION bf = { AC_SRC_OVER, 0, 255, AC_SRC_ALPHA };
	AlphaBlend(dstDC, x, y, w, h, srcDC, 0, 0, w, h, bf);
}

// 使用示例
int main()
{
	initgraph(320, 240);
	setbkcolor(YELLOW);
	setlinecolor(GREEN);
	BeginBatchDraw();

	IMAGE imgPng, imgRotate;
	loadimage(&imgPng, L"sheep.png");							// 加载透明图片

	for (double f = -2 * PI; f <= 2 * PI; f += 0.03)
	{
		cleardevice();											// 绘制背景
		for (int y = 0; y < 240; y += 10)
			line(0, y, 320, y);

		imgRotate = RotateImage_Alpha(&imgPng, f, 0x66AA0000);	// 图像旋转(设置了半透明的填充背景)
		transparentimage(NULL, 0, 0, &imgRotate);				// 透明贴图

		FlushBatchDraw();
		Sleep(10);
	}

	flushmessage();
	getmessage(EM_KEY);											// 按任意键退出

	closegraph();
	return 0;
}


运行截图

截图

添加评论