旋转(rotation)有一个绕着什么转的问题,通常的做法是以图象的中心为圆心旋转,类似下面这种情况:
可以看出,旋转后图象变大了。另一种做法是不让图象变大,转出的部分被裁剪掉如图2.9所示。
我们采用第一种做法,首先给出变换矩阵。在我们熟悉的坐标系中,将一个点顺时针旋转a角后的坐标变换公式,r为该点到原点的距离,在旋转过程中,r保持不变;b为r与x轴之间的夹角。
旋转前:x0=rcosb;y0=rsinb
旋转a角度后:
x1=rcos(b-a)=rcosbcosa+rsinbsina=x0cosa+y0sina;
y1=rsin(b-a)=rsinbcosa-rcosbsina=-x0sina+y0cosa;
以矩阵的形式表示:
上面的公式中,坐标系xoy是以图象的中心为原点,向右为x轴正方向,向上为y轴正方向。它和以图象左上角点为原点o’,向右为x’轴正方向,向下为y’轴正方向的坐标系x’o’y’之间的转换关系如何呢?
设图象的宽为w,高为h,容易得到:
逆变换为:
有了上面的公式,我们可以把变换分成三步:
1.将坐标系o’变成o;
2.将该点顺时针旋转a角;
3.将坐标系o变回o’,这样,我们就得到了变换矩阵,是上面三个矩阵的级联。
这样,对于新图中的每一点,我们就可以根据公式求出对应原图中的点,得到它的灰度。如果超出原图范围,则填成白色。要注意的是,由于有浮点运算,计算出来点的坐标可能不是整数,采用取整处理,即找最接近的点,这样会带来一些误差(图象可能会出现锯齿)。
这里的打开bmp格式图像代码就不再贴出来了,就贴出来旋转类(这个类是新加的,对话框里面可以输入旋转角度)里面的代码和调用代码。
View.h
void CMyView::OnXuanzhuan()
{
// TODO: Add your command handler code here
CMyDoc* pc=GetDocument();
CXuanzhuan dlg;
dlg.SetDocument(pc);
dlg.DoModal();
Invalidate();
}
Xuanzhuan.h
#if !defined(AFX_XUANZHUAN_H__B7192E70_1EA8_4EEF_BC0E_A30D3785DC93__INCLUDED_)
#define AFX_XUANZHUAN_H__B7192E70_1EA8_4EEF_BC0E_A30D3785DC93__INCLUDED_
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
// Xuanzhuan.h : header file
//
#include "图像旋转Doc.h"
/
// CXuanzhuan dialog
class CXuanzhuan : public CDialog
{
public:
int m_jiaodu;
void xuanzhuan(BYTE *&shuju,int width,int height,int du);
CMyDoc*poc;
void SetDocument(CMyDoc *m);
CMyDoc* GetDocument();
int GetData();
// Construction
public:
CXuanzhuan(CWnd* pParent = NULL); // standard constructor
// Dialog Data
//{{AFX_DATA(CXuanzhuan)
enum { IDD = IDD_DIALOG1 };
// NOTE: the ClassWizard will add data members here
//}}AFX_DATA
// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CXuanzhuan)
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support
//}}AFX_VIRTUAL
// Implementation
protected:
// Generated message map functions
//{{AFX_MSG(CXuanzhuan)
afx_msg void OnChangeJiaodu();
virtual void OnOK();
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately before the previous line.
#endif // !defined(AFX_XUANZHUAN_H__B7192E70_1EA8_4EEF_BC0E_A30D3785DC93__INCLUDED_)
Xuanzhuan.cpp:
// Xuanzhuan.cpp : implementation file
//
#include "stdafx.h"
#include "图像旋转.h"
#include "Xuanzhuan.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
#include "图像旋转Doc.h"
/
// CXuanzhuan dialog
CXuanzhuan::CXuanzhuan(CWnd* pParent /*=NULL*/)
: CDialog(CXuanzhuan::IDD, pParent)
{
//{{AFX_DATA_INIT(CXuanzhuan)
// NOTE: the ClassWizard will add member initialization here
//}}AFX_DATA_INIT
poc=NULL;
}
void CXuanzhuan::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CXuanzhuan)
// NOTE: the ClassWizard will add DDX and DDV calls here
//}}AFX_DATA_MAP
}
BEGIN_MESSAGE_MAP(CXuanzhuan, CDialog)
//{{AFX_MSG_MAP(CXuanzhuan)
ON_EN_CHANGE(IDC_JIAODU, OnChangeJiaodu)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
/
// CXuanzhuan message handlers
void CXuanzhuan::OnChangeJiaodu()
{
// TODO: If this is a RICHEDIT control, the control will not
// send this notification unless you override the CDialog::OnInitDialog()
// function and call CRichEditCtrl().SetEventMask()
// with the ENM_CHANGE flag ORed into the mask.
m_jiaodu=GetDlgItemInt(IDC_JIAODU);
// TODO: Add your control notification handler code here
}
void CXuanzhuan::OnOK()
{
// TODO: Add extra validation here
xuanzhuan(GetDocument()->bmpdata,GetDocument()->bmpinfo->bmiHeader.biWidth,GetDocument()->bmpinfo->bmiHeader.biHeight,GetData());
CDialog::OnOK();
}
void CXuanzhuan::xuanzhuan(BYTE *&shuju,int width,int height,int du)
{
float cosa;//余弦值
float sina;//正弦值
BYTE *data;//临时存储
BYTE *yuan;
BYTE *bian;
float srcx1,srcx2,srcx3,srcx4,srcy1,srcy2,srcy3,srcy4;//定义原图像的四个角的坐标
float dstx1,dstx2,dstx3,dstx4,dsty1,dsty2,dsty3,dsty4;//定义新图像的四个角的坐标
int newW;//新的宽度
int newH;//新的高度
int x0,y0;//原来的图像坐标
int x1,y1;//旋转后的图像坐标
float num1,num2;//常用值
float du1;
du1=(float)RADIAN(du);//角度到弧度的转化
cosa=(float)cos((double)du1);//计算cosa的值
sina=(float)sin((double)du1);//计算sina的值
计算出原来的坐标 以图像中心为原点 向右为x的正半轴 向下为y的正半轴
srcx1=(float)(-0.5*width); srcy1=(float)(0.5*height);
srcx2=(float)(0.5*width); srcy2=(float)(0.5*height);
srcx3=(float)(-0.5*width); srcy3=(float)(-0.5*height);
srcx4=(float)(0.5*width); srcy4=(float)(-0.5*height);
//计算出新图像的四个角的坐标 还是以图像中心为原点
dstx1=(float)(cosa*srcx1+sina*srcy1); dsty1=(float)(-sina*srcx1+cosa*srcy1);
dstx2=(float)(cosa*srcx2+sina*srcy2); dsty2=(float)(-sina*srcx2+cosa*srcy2);
dstx3=(float)(cosa*srcx3+sina*srcy3); dsty3=(float)(-sina*srcx3+cosa*srcy3);
dstx4=(float)(cosa*srcx4+sina*srcy4); dsty4=(float)(-sina*srcx4+cosa*srcy4);
///计算出新的宽度和高度/
newW=(int)(max(fabs(dstx4-dstx1),fabs(dstx3-dstx2))+0.5);
newH=(int)(max(fabs(dsty4-dsty1),fabs(dsty3-dsty2))+0.5);
//计算出常用值//
num1 = (float) (-0.5 * (newW - 1) * cosa - 0.5 * (newH - 1) * sina + 0.5 * (width - 1));
num2 = (float) ( 0.5 * (newW - 1) * sina - 0.5 * (newH - 1) * cosa + 0.5 * (height - 1));
//
LONG zijie;
LONG newzijie;
zijie = WIDTHBYTES(width * 8);//原来的一行字节数
newzijie=WIDTHBYTES(newW*8);//新的一行的字节数
GetDocument()->bmpinfo->bmiHeader.biWidth=newW;
GetDocument()->bmpinfo->bmiHeader.biHeight=newH;//把新的宽度和高度传给bmpinfo
data=(BYTE *)new BYTE[newzijie*newH];//给临时变量分配数据
for(x0=0;x0<newH;x0++)
{
for(y0=0;y0<newW;y0++)
{
bian = data + newzijie * (newH - 1 - x0) + y0;
//计算在原来图像的坐标
x1 = (LONG) (-(y0)*sina+(x0)*cosa+num2+0.5);
y1 = (LONG) ( (y0)*cosa+(x0)*sina+num1+0.5);
if( (y1 >= 0) && (y1 < width) && (x1 >= 0) && (x1 < height))//判断坐标是否在原图像内
{
yuan = shuju + zijie * (height - 1 - x1) + y1;
*bian = *yuan;
}
else
{
// 对于源图中没有的像素,直接赋值为0
* ((unsigned char*)bian) = 0;
}
}
}
if(shuju) delete [] shuju;
shuju=data;
Invalidate();
}
void CXuanzhuan::SetDocument(CMyDoc *m)
{
poc=m;
}
CMyDoc* CXuanzhuan::GetDocument()
{
return poc;
}
int CXuanzhuan::GetData()
{
return m_jiaodu;
}