图形旋转好玩又有实用性, 这里介绍一种简单的图形旋转算法.
具体步骤如下:
1. 首先将原图和旋转图的坐标原点都变换到图形的中心位置处.
2. 历遍旋转图形中的每一个pixel, 将pixel的坐标(j,i)反向旋转映射到原图, 得到原图对应的坐标值(Xr,Yr).
3. 考虑到旋转图的尺寸可能大于原图,这时需要检测(Xr,Yr)是否在原图范围内,如果不是,则忽略下面步骤.
4. (Xr,Yr)通常并不是正好对应到原图中一个整点的像素, 而是会夹在四个像素中(如图), 对四个像素的位置进行插值计算(也即所谓的双线性插值), 可以得到对应位置的RGBA值.
5. 由于内存中的图形数据是以左上角为坐标原点逐行记录的, 我们需将(j,i)变换到此坐标系统下(步骤1的反向操作), 得到其对应的内存位置, 然后将RGBA数值存入此处.
6. 通过逐点计算,最终可以得到完整的旋转后的图形.
7. 待改进处: 这里由于对图形边缘点的采样不够密集,因此得到的旋转图形边缘会出现锯齿现象. 可以细化映射颗粒度,如以1/4像素为单位进行映射计算.
更普遍的情况是,我们需要对原图中的指定区域进行旋转操作, 即进行旋转抠图. 下面是这一算法的C语言实现:
/*-----------------------------------------------------------------
@eimg: 输入图像
@oimg: 输出图像(NULL忽略)
@height,width: 输出图像的尺寸
@px,py: 抠图相对原图的位置
@angle: 旋转角度
-------------------------------------------------------------------*/
EGI_IMGBUF* egi_imgbuf_rotBlockCopy2( EGI_IMGBUF *eimg, EGI_IMGBUF *oimg, int height, int width,
int px, int py, float angle)
{
int i,j;
float xr,yr;
int index_out;
EGI_IMGBUF *outimg=NULL;
float sina=sin(MATH_PI*angle/180);
float cosa=cos(MATH_PI*angle/180);
/* 1. Check input eimg */
if(eimg==NULL || eimg->imgbuf==NULL || eimg->height<=0 || eimg->width<=0 ) {
egi_dpstd("Input holding eimg is NULL or uninitiliazed!\n");
return NULL;
}
/* 2. Input oimg is NULL */
/* 注:这里将输出图像的长宽均设为奇数,这样中心点是个整点位置,便于计算. */
if(oimg==NULL) {
/* Make H/W an odd value, then it has a symmetrical center point. */
height |= 0x1;
width |= 0x1;
if(height<3)height=3;
if(width<3)width=3;
/* Create an imgbuf accordingly */
outimg=egi_imgbuf_create(height, width, 0, 0); /* H, W, alpah, color */
if(outimg==NULL) {
egi_dpstd("Fail to create outimg!\n");
return NULL;
}
/* Check ALPHA data */
if(eimg->alpha==NULL) {
free(outimg->alpha);
outimg->alpha=NULL;
}
}
/* Input oimg is NOT NULL */
else {
height=oimg->height;
width=oimg->width;
outimg=oimg;
}
/* 3. Clear outimg first. ALPHA to be 0, OR same as eimg. 如果是RGBA格式,可以在这里设置背景色. */
egi_imgbuf_resetColorAlpha(outimg, WEGI_COLOR_GRAY2, outimg->alpha==NULL ? -1:0 ); /* img, color, alpha */
/* 4. Map back point coordinates to eimg 以旋转图片中心为原点, 历遍旋转图形中的每一个像素点. 反向映射并插值计算得RGBA. */
int m=height>>1;
int n=width>>1;
for(i=-m; i<=m; i++) {
for(j=-n; j<=n; j++) {
/* 4.1 Map to original coordiante (xr,yr), Origin at center.
* 2D point rotation formula ( a positive: Right_hand Rule. ):
* x'=x*cos(a)-y*sin(a)
* y'=x*sin(a)+y*cos(a)
* Coord axis anti_clockwise, point colokwise rotate.
* points coordinates relative to up_right block coord.
* 旋转变换公式, 可以查数学手册.
*/
xr=cosa*j-sina*i;
yr=sina*j+cosa*i;
/* 4.2 Shift Origin to left_top, as of eimg->imgbuf 变换到原图坐标下 */
xr += px;
yr += py;
/* 4.3 Copy pixel alpha and color. Limit xr,yr, ignore if they are out of original imgbuf area. */
if( xr >= 0.0 && xr <= eimg->width-1 && yr >=0.0 && yr <= eimg->height-1) {
index_out=width*(i+m)+(j+n);
/* 4.3.1 的到相互邻的4个像数 */
int indx1 = eimg->width*floorf(yr)+floorf(xr);
int indx2 = eimg->width*floorf(yr)+ceilf(xr);
int indx3 = eimg->width*ceilf(yr)+floorf(xr);
int indx4 = eimg->width*ceilf(yr)+ceilf(xr);
EGI_16BIT_COLOR pcolor;
EGI_8BIT_ALPHA palpha;
float pft;
/* 4.3.2 Interpolate within 4 pixles 4点插值得到RGBA */
if(eimg->alpha!=NULL && outimg->alpha!=NULL) {
egi_16bitColor_interplt4p(eimg->imgbuf[indx1], eimg->imgbuf[indx2], /* color1, color2 */
eimg->imgbuf[indx3], eimg->imgbuf[indx4], /* color3, color4 */
eimg->alpha[indx1], eimg->alpha[indx2], /* alpha1, alpha2 */
eimg->alpha[indx3], eimg->alpha[indx4], /* alpha3, alpah4 */
modff(xr,&pft)*(1<<15), modff(yr, &pft)*(1<<15), /* f15_x, f15_y */
&pcolor, &palpha );
outimg->imgbuf[index_out]=pcolor;
outimg->alpha[index_out]=palpha;
}
else {
egi_16bitColor_interplt4p(eimg->imgbuf[indx1], eimg->imgbuf[indx2], /* color1, color2 */
eimg->imgbuf[indx3], eimg->imgbuf[indx4], /* color3, color4 */
0, 0, 0, 0, /* alpha1, alpha2, alpha3, alpha4 */
modff(xr,&pft)*(1<<15), modff(yr, &pft)*(1<<15), /* f15_x, f15_y */
&pcolor, NULL );
outimg->imgbuf[index_out]=pcolor;
}
}
}
}
return outimg;
}
(更多代码见 https://github.com/widora/wegi)
效果: