支持 Alpha 信息图片旋转(旋转后自动适应大小)
由于 EasyX 图形库中的 rotateimage 函数没有考虑关于 alpha 信息的问题,使旋转后的贴图 alpha 信息失真。
为了满足自己的需求,本人手动写 rotateimage 函数,解决了该问题。
本文参考:
- http://tieba.baidu.com/p/1490993926(图像任意角度旋转方法)
- https://codebus.cn/yangw/transparent-putimage(详解透明贴图和三元光栅操作)
要实现图像旋转首先要搞懂旋转的坐标变换。
如图,在平面直角坐标系中,以原点为中心,一个点由一个位置逆时针旋转到另一个点,转角为θ。
根据坐标关系,可以求出两点的坐标关系,推算过程如下:
如图是一个单位圆的 1/4 圆弧,圆心在原点上,点 (x, y) 和 (x1, y1) 在圆弧上,则由三角函数的和差角公式可以推出式子
y = rsin(θ + α) = rsinθcosα + rcosθsinα = y1cosθ + x1sinθ
x = rcos(θ + α) = rcosθcosα - rsinθsinα = y1cosθ + x1sinθ
这就是逆时针的坐标变换。由于通过原图坐标映射出新图坐标就会失真(图片中会有点),所以我们就要通过新图坐标映射出原图坐标,即原来是看原图的 (x1, y1) 在新图的 (x, y) 位置,现在是看新图的 (x1, y1) 在原图的 (x, y) 位置,顺便将逆时针旋转转变为顺时针旋转。
图片旋转解决了,我们还得实现旋转后的自动适应图片大小。
如上图,设图片转过θ角,即∠EOD = θ
∵DO//AB,∴∠ABC = ∠BFO
又∵∠CAB = Rt∠ = ∠FEO,∴∠ACB = ∠EOF = θ
则上图可转化为下图,已知 a, b, θ,求 x, y。
易证各个小三角形相似,则有
x = acosθ + bsinθ
y = asinθ + bcosθ
之后处理下和弧度有关的问题即可,这里就不再赘述了。
下面给出代码(结合透明贴图函数使用效果更佳):
图片 src4(半透明):
代码:
#include <graphics.h>
#include <conio.h>
#include <cmath>
#include <iostream>
#define PI 3.14159265
// Rotate_Image 函数:
// 本函数参考 https://codebus.cn/yangw/transparent-putimage
// 本函数参考 http://tieba.baidu.com/p/1490993926
// IMAGE * pTo : 新图
// IMAGE * pFrom : 原图
// double rad : 旋转的弧度
void RotateImage(IMAGE* pTo, IMAGE* pFrom, double rad)
{
IMAGE* pWorking = GetWorkingImage();
SetWorkingImage(pFrom);
int iWidth = getwidth();
int iHeight = getheight(); // 获取原图长宽
while (rad > 2 * PI) // 化简弧度
rad -= 2 * PI;
double pad = rad; // 处理弧度
if (pad > PI / 2 && pad <= PI)
{
pad -= PI / 2;
pad = PI / 2 - pad;
}
else if (pad > PI && pad <= PI / 2 * 3)
{
pad -= PI;
}
else if (pad > PI / 2 * 3 && pad <= PI * 2)
{
pad -= PI / 2 * 3;
pad = PI / 2 - pad;
}
int tWidth = int(iWidth * cos(pad) + iHeight * sin(pad));
int tHeight = int(iHeight * cos(pad) + iWidth * sin(pad)); // 计算新图大小
int iMinX = -(iWidth / 2), iMinY = -(iHeight / 2);
int iMaxX = iMinX + iWidth, iMaxY = iMinY + iHeight; // 计算原图最小(大)坐标
int tMinX = -(tWidth / 2), tMinY = -(tHeight / 2);
int tMaxX = tMinX + tWidth, tMaxY = tMinY + tHeight; // 计算新图最小(大)坐标
setorigin(-iMinX, -iMinY); // 设置图片中心为原点
SetWorkingImage(NULL);
pTo->Resize(tWidth, tHeight); // 初始化新图
DWORD* dst = GetImageBuffer(pTo);
DWORD* src = GetImageBuffer(pFrom); // 获取新图、原图的缓冲区
SetWorkingImage(pTo);
for (int y1 = 0; y1 < tHeight; y1++)
{
for (int x1 = 0; x1 < tWidth; x1++)
dst[x1] = 0x00000000;
dst += tWidth;
}
SetWorkingImage(pWorking);
for (int y1 = 0; y1 < tHeight; y1++) // 初始化新图
dst -= tWidth;
for (int y1 = tMinY; y1 < tMaxY; y1++)
{
for (int x1 = tMinX; x1 < tMaxX; x1++)
{
int x = int(x1 * cos(rad) - y1 * sin(rad));
int y = int(x1 * sin(rad) + y1 * cos(rad)); // 计算变换后坐标
int sxy = (iHeight - (y - iMinY) - 1) * iWidth + (x - iMinX);
int dxy = (tHeight - (y1 - tMinY) - 1) * tWidth + (x1 - tMinX); // 计算坐标在缓冲区的位置
if (x >= iMinX && x < iMaxX && y >= iMinY && y < iMaxY) // 越界特判
dst[dxy] = src[sxy];
}
}
SetWorkingImage(pFrom);
setorigin(0, 0);
SetWorkingImage(pWorking); // 还原原图坐标
}
// transparentimage 函数:
// 请参考 https://codebus.cn/yangw/transparent-putimage 中的第五项:
// 根据 png 的 alpha 信息实现半透明贴图(基于直接操作显示缓冲区)
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;
}
}
int main()
{
initgraph(600, 600); // 初始化图形窗口
BeginBatchDraw();
IMAGE src, yyy;
loadimage(&src, _T("rotate-src4.png"), 180, 180); // 加载图片
double i = 0;
while (1)
{
RotateImage(&yyy, &src, PI / 180 * i); // 旋转图片
i += 0.1;
setbkcolor(BLACK);
cleardevice();
setlinecolor(GREEN);
for (int y = 0; y < 600; y += 3)
line(0, y, 639, y); // 绘制背景
putimage(0, 0, &src);
transparentimage(NULL, 300, 0, &src);
putimage(0, 200, &yyy);
transparentimage(NULL, 300, 200, &yyy); // 贴图
FlushBatchDraw();
}
EndBatchDraw();
closegraph();
return 0;
}
执行效果:
(完)
1. SetWorkingImage 和 setorigin 都是不必要的。
2. 传入负弧度会崩溃。
所以我重新写了一个函数,见文章:
https://codebus.cn/huidong/rotateimage-alpha