在前面的一篇文章中,作者详细介绍了利用GDI+的API实现缩放图像的方法。下面介绍另外一种可以实现图形缩放的方法。
在windows的绘图中,绘图的逻辑单位和视图的物理单位之间的对应关系是可以改变的,通过 CDC::SetMapMode可以改变这种关系。而本次实现图形缩放的功能原理就是通过改变这种映射关系来实现。
CDC::SetMapMode在MSDN中这样解释:
CDC::SetMapMode
virtual int SetMapMode( int nMapMode );
Return Value
The previous mapping mode.
Parameters
nMapMode
Specifies the new mapping mode. It can be any one of the following values:
MM_ANISOTROPIC Logical units are converted to arbitrary units with arbitrarily scaled axes. Setting the mapping mode to MM_ANISOTROPIC does not change the current window or viewport settings. To change the units, orientation, and scaling, call the SetWindowExt and SetViewportExt member functions.
MM_HIENGLISH Each logical unit is converted to 0.001 inch. Positive x is to the right; positive y is up.
MM_HIMETRIC Each logical unit is converted to 0.01 millimeter. Positive x is to the right; positive y is up.
MM_ISOTROPIC Logical units are converted to arbitrary units with equally scaled axes; that is, 1 unit along the x-axis is equal to 1 unit along the y-axis. Use the SetWindowExt and SetViewportExt member functions to specify the desired units and the orientation of the axes. GDI makes adjustments as necessary to ensure that the x and y units remain the same size.
MM_LOENGLISH Each logical unit is converted to 0.01 inch. Positive x is to the right; positive y is up.
MM_LOMETRIC Each logical unit is converted to 0.1 millimeter. Positive x is to the right; positive y is up.
MM_TEXT Each logical unit is converted to 1 device pixel. Positive x is to the right; positive y is down.
MM_TWIPS Each logical unit is converted to 1/20 of a point. (Because a point is 1/72 inch, a twip is 1/1440 inch.) Positive x is to the right; positive y is up.
Remarks
Sets the mapping mode. The mapping mode defines the unit of measure used to convert logical units to device units; it also defines the orientation of the device’s x- and y-axes. GDI uses the mapping mode to convert logical coordinates into the appropriate device coordinates. The MM_TEXT mode allows applications to work in device pixels, where 1 unit is equal to 1 pixel. The physical size of a pixel varies from device to device.
The MM_HIENGLISH, MM_HIMETRIC, MM_LOENGLISH, MM_LOMETRIC, and MM_TWIPS modes are useful for applications that must draw in physically meaningful units (such as inches or millimeters). The MM_ISOTROPIC mode ensures a 1:1 aspect ratio, which is useful when it is important to preserve the exact shape of an image. The MM_ANISOTROPIC mode allows the x- and y-coordinates to be adjusted independently.
这里主要介绍MM_ANISOTROPIC的映射模式,MSDN的介绍翻译成中文:
逻辑单位可以在任意坐标系中设置成任意的单位。设置该映射模式不能该表现有的窗口和视口设置。想要改变单位、方向、或者缩放比例,利用SetWindowExt和SetViewportExt功能。
那现在再看下MSDN中对SetWindowExt和SetViewportExt的解释,这里不再引用MSDN,只介绍下其中文解释:
对于SetWindowExt,MSDN中描述其功能:
设置窗口的相对于物理绘图设备x和y轴长度,窗口以及物理绘图设备决定了GDI+绘图中逻辑设备坐标和物理设备坐标的映射关系。
对于SetViewportExt,MSDN中描述其功能:
设置物理绘图设备的视口的x和y轴长度,视口以及绘图设备中的窗口决定了GDI+绘图中逻辑设备坐标和物理设备坐标的映射关系。换句话说,就是它们决定了逻辑单位和物理单位之间的映射关系。
下面,就是利用CDC::SetMapMode以及SetWindowExt、SetViewportExt实现图形的缩放功能。
首先,建立一个单文档MFC工程,名字就叫GDITest,在GDITestApp中添加GDI+的令牌作为成员变量用于初始化GDI+绘图环境。
private:
ULONG_PTR m_gdiplusToken;
分别在GDITestApp的InitInstance和ExitInstance中添加GDI+初始化和释放的代码:
BOOL CGDITestApp::InitInstance()
{
…
//初始化GDI+库
Gdiplus::GdiplusStartupInput gdiplusStartupInput;
Gdiplus::GdiplusStartup(&m_gdiplusToken, &gdiplusStartupInput, NULL);
…
}
int CGDITestApp::ExitInstance()
{
…
//关闭GDI+库
Gdiplus::GdiplusShutdown(m_gdiplusToken);
…
}
然后,在CGDITestView的OnDraw函数中,进行图形的绘制,这里面我们先做图形绘制的准备工作,具体绘制图形的任务,交给CGDITestDoc这个文档类完成。所以先在CGDITestDoc中添加两个成员函数,分别用于绘制绘图背景,和绘制图形,如下所示:
void CGDITestDoc::DrawBK(CDC* pDc, CRect& rect)
{
//定义背景画刷
CBrush brushOut(RGB(100,100,100));
//绘制背景色
pDc->FillRect(rect,&brushOut);
}
void CGDITestDoc::DrawShapes(CDC* pDc,CRect& rect)
{
Gdiplus::Graphics graphi(pDc->m_hDC);
Gdiplus::Pen pen(Gdiplus::Color(255,0,0,255));
graphi.DrawLine(&pen,0,0,200,100);
}
我们先在CGDITestView中添加一个double型的成员变量m_dbZoom作为控制画面缩放的缩放系数,并赋初值1.0,在CGDITestView的OnDraw函数中进行绘图的准备工作同时调用文档类的绘图函数进行绘图:
void CGDITestView::OnDraw(CDC* pDC)
{
CGDITestDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
if (!pDoc)
return;
//获取视图窗口大小
CRect clent;
this->GetClientRect(&clent);
//创建内存DC
CDC Memdc;
Memdc.CreateCompatibleDC(NULL);
//创建内存Bitmap
CBitmap bitmap;
CBitmap* pOldbitmap;
bitmap.CreateCompatibleBitmap(pDC,clent.Width(),clent.Height());
//将内存DC关联到内存Bitmap
pOldbitmap= Memdc.SelectObject(&bitmap);
//设置内存DC背景
Memdc.SetBkColor(TRANSPARENT);
//绘图
//1.设置缩放系数
CSize szViewPort;
pDC->SetMapMode(MM_ANISOTROPIC);
szViewPort.cx = m_dbZoom*1000;
szViewPort.cy = m_szViewPort.cx;
pDC->SetViewportExt(szViewPort);
pDC->SetWindowExt(1000,1000);
//1.绘制背景色
pDoc->DrawBK(&Memdc,clent);
//2.绘制图形
pDoc->DrawShapes(&Memdc,clent);
//从内存DC将内容拷贝的视图DC
pDC->BitBlt(0,0,clent.Width(),clent.Height(),&Memdc,0,0,SRCCOPY);
//还原内存DC的bitmap
Memdc.SelectObject(pOldbitmap);
}
最后重载BOOL CGDITestView::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)函数,这样就能通过鼠标滚轴控制图形的缩放。
BOOL CGDITestView::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
{
if (zDelta>0)
{
m_dbZoom*=1.2;
}
else
{
m_dbZoom/=1.2;
}
Invalidate(TRUE);
return CView::OnMouseWheel(nFlags, zDelta, pt);
}
以上的缩放是以原点为中心进行的缩放,若想要实现以鼠标所在点为中心进行缩放,要怎么实现?
其实原理和上述博客中所讲绘制的原理差不多,只不过这里需要先介绍下两个函数:
CDC::SetViewportOrg设置物理绘图设备的起始点;
CDC::SetWindowOrg设置窗口绘图设备的起始点;
我们就是利用SetViewportOrg以及缩放后的偏移距离,来设置开始绘图的坐标,以达到以鼠标为中心缩放的目的。
首先,计算偏移距离,在CGDITestView增加CSize型的成员变量m_csZoom.。修改 BOOL CGDITestView::OnMouseWheel函数如下:
BOOL CGDITestView::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
{
double PreZoom=m_dbZoom;
if (zDelta>0)
m_dbZoom*=1.2;
else
m_dbZoom/=1.2;
//获取视图所在的范围
CRect rect;
GetWindowRect(&rect);
//获取当前鼠标相对于视图所在的位置
CPoint point;
point.x=pt.x-rect.left;
point.y=pt.y-rect.top;
//计算偏移距离
m_csZoom.cx=(point.x * m_dbZoom) - (point.x * PreZoom);
m_csZoom.cy=(point.y * m_dbZoom) - (point.y * PreZoom);
Invalidate(TRUE);
return CView::OnMouseWheel(nFlags, zDelta, pt);
}
根据偏移距离,在OnDraw函数中重置绘图坐标原点,如下所示:
void CGDITestView::OnDraw(CDC* pDC)
{
CGDITestDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
if (!pDoc)
return;
//获取视图范围
CRect clent;
this->GetClientRect(&clent);
//创建内存DC
CDC Memdc;
Memdc.CreateCompatibleDC(NULL);
//创建内存bitmap
CBitmap bitmap;
CBitmap* pOldbitmap;
bitmap.CreateCompatibleBitmap(pDC,clent.Width(),clent.Height());
//将内存DC关联到bitmap
pOldbitmap= Memdc.SelectObject(&bitmap);
//设置内存DC背景
Memdc.SetBkColor(TRANSPARENT);
//绘图
//获取缩放距离
pDC->SetMapMode(MM_ANISOTROPIC);
m_szViewPort.cx = m_dbZoom*1000;
m_szViewPort.cy = m_szViewPort.cx;
pDC->SetViewportExt(m_szViewPort);
pDC->SetWindowExt(1000,1000);
//重新设置绘图原点
m_ViewOrgPoint.x-=m_csZoom.cx;
m_ViewOrgPoint.y-=m_csZoom.cy;
pDC->SetViewportOrg(m_ViewOrgPoint.x,m_ViewOrgPoint.y);
pDC->SetWindowOrg(0,0);
//1.绘制背景
pDoc->DrawBK(&Memdc,clent);
//2.绘制形状
pDoc->DrawShapes(&Memdc,m_csZoom);
//将内存DC拷贝到视图DC
pDC->BitBlt(0,0,clent.Width(),clent.Height(),&Memdc,0,0,SRCCOPY);
//还原内存bitmap
Memdc.SelectObject(pOldbitmap);
}