Lost person

幸甚至哉,码以咏志。

支持 Alpha 信息图片旋转(旋转后自动适应大小) 铜牌收录

由于 EasyX 图形库中的 rotateimage 函数没有考虑关于 alpha 信息的问题,使旋转后的贴图 alpha 信息失真。

为了满足自己的需求,本人手动写 rotateimage 函数,解决了该问题。

本文参考:

要实现图像旋转首先要搞懂旋转的坐标变换。

如图,在平面直角坐标系中,以原点为中心,一个点由一个位置逆时针旋转到另一个点,转角为θ。

根据坐标关系,可以求出两点的坐标关系,推算过程如下:

如图是一个单位圆的 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;
}

执行效果:

(完)

评论 (2) -

  • 我用了您的函数,确实可用!但您的代码中还存在一些问题:

    1. SetWorkingImage 和 setorigin 都是不必要的。
    2. 传入负弧度会崩溃。

    所以我重新写了一个函数,见文章:
    https://codebus.cn/huidong/rotateimage-alpha
  • 你好,我想问一下有没有让图片镜像翻转的方法

添加评论