旋转图像最关键的就是坐标旋转公式,其实这个东西很简单,可以了解:http://huidong.xyz/index.php?mode=2&id=406
之前我也写过类似的东西,不过是单个点的,见 http://huidong.xyz/?mode=2&id=365
那个里面有一些小错误,不过用在那个需求背景下是没有问题的。
这回我本来是不打算自己来写这个旋转函数的,但是无奈 EasyX 的 rotateimage 不支持含有透明信息的图像的旋转,要是旋转了会失真,导致奇妙的色彩效果。而 Codebus 上面有位作者(Lost)写了个支持透明图像的旋转函数,但是据我测试有很多 bug(见 https://codebus.cn/lostperson/rotate ),所以没办法,只好自己写一个了。其实在写之前我告诉了村长 Codebus 上面那个有 bug,村长建议我写一个发上去,在这样的怂恿下我于是才真正动手。
这一回其实也踩了点坑,算是还可以吧,也复习了一下旋转。我收获最大的就是发现要换个角度想问题,一开始我是从原图像映射到旋转后的图像上,这样会导致很多空点,效果不好,后来我还在想怎么通过附近的点去补这些空点……有点可笑。后来我上网查了才恍然大悟,可以从旋转后的图像往回找,反向映射到原图像上,这样就确保旋转后的图像中每个像素(除了多边形外面的补充像素)都可以在原图像中进行对应了,就解决了空点的问题。
好了,看看代码吧:
#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;
}