版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
目标
实现滚轮的缩放:滚轮向上,以鼠标当前在绘图区的位置进行放大;滚轮向下,以绘图去的中心进行缩小。
核心
1、OnDraw函数
2、CImage
3、CImage::StretchBlt方法【缩放原理】
4、滚轮的消息响应函数
5、工作区Client坐标、屏幕Screen坐标
核心概述
1、OnDraw函数
- 应用窗口的工作区Client进行绘图的代码都必须写在这个函数当中
2、CImage
- 首先需要用一个CImage的对象去加载一个地址的图片,也就是我们要显示的图片
- 如果之前有图片对象,则销毁对象,重新创建
- 然后获取相关的图片参数:宽、高
//判断是否加载过图片
if (!cimgImage.IsNull())
{
//加载过,销毁
cimgImage.Destroy();
}
//加载图片
cimgImage.Load(cstrLoadPathName);
//放大倍数清零
m_fMultiple = 0;
//原图的宽度
m_nWidthSrc = cimgImage.GetWidth();
//原图的高度
m_nHeightSrc = cimgImage.GetHeight();
//源的原点清零
m_OriginSrcPoint = (0, 0);
//使当前的窗口无效:让Windows知道这个窗口现在该重新绘制一下了
Invalidate();
3、CImage::StretchBlt()方法【缩放原理】
- 显示图片的核心方法
- 主要是通过改变源Src的坐标原点xSrc、ySrc以及宽度nSrcWidth和高度nSrcHeight进行缩放的操作
- 坐标原点影响缩放的位置
- 宽度nSrcWidth、高度nSrcHeight影响缩放的大小(小于原图的宽高:相当于放大原图的某个区域)
BOOL StretchBlt(
//目标
HDC hDestDC, //对目标设备上下文的句柄。【pDC->m_hDC】pDC是画布的对象指针,m_hDC是对象的成员,画布的句柄
int xDest, //x坐标,在逻辑单位,目标矩形的左上角。
int yDest, //y坐标,在逻辑单位,目标矩形的左上角。
int nDestWidth, //宽度,在逻辑单位,目标矩形。
int nDestHeight, //高度,在逻辑单位,目标矩形。
//源:不需要源的DC句柄,加载的时候已经获取
int xSrc, //x坐标,在逻辑单位,源矩形的左上角。
int ySrc, //y坐标,在逻辑单位,源矩形的左上角。
int nSrcWidth, //宽度,在逻辑单位,源矩形。
int nSrcHeight, //高度,在逻辑单位,源矩形。
DWORD dwROP = SRCCOPY
) const throw( );
4、滚轮的消息响应函数
- 类视图》CxxxView类》右键》属性》消息》WM_MOUSEWHEEL:添加
- 主要有滚轮上/下的标识为zDelta,屏幕坐标系的坐标点pt
- BOOL CMyRipView::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
5、工作区Client坐标、屏幕Screen坐标
- 屏幕坐标系转工作区坐标系:ScreenToClient(&pt)
代码实现
1、View.h定义相关的变量+初始化
//缩放功能
//放大的倍数
float m_fMultiple;
//原图坐标原点
CPoint m_OriginSrcPoint;
//原图的宽度
int m_nWidthSrc;
//原图的高度
int m_nHeightSrc;
//滚轮滚动时工作区坐标点
CPoint m_ptRollPointClient;
//滚轮滚动时屏幕坐标点
CPoint m_ptRollPointScreen;
//绘图区的矩形区域
CRect m_rectDraw;
CMyRipView::CMyRipView() noexcept
{
// TODO: 在此处添加构造代码
//窗口类指针初始化
m_pDitherDlg = NULL;
m_pICCDlg = NULL;
//放大倍数初始化
m_fMultiple = 0.0;
//原图坐标原点
m_OriginSrcPoint = (0, 0);
//原图的宽度
m_nWidthSrc = 0;
//原图的高度
m_nWidthSrc = 0;
//滚轮滚动时工作区坐标点
m_ptRollPointClient = (0, 0);
//滚轮滚动时屏幕坐标点
m_ptRollPointScreen = (0, 0);
}
2、添加滚轮消息响应函数
- 类视图》CMyRipView》右键》属性》消息》WM_MOUSEWHEEL
- 放大:以鼠标的位置为中心
- 缩小:以绘图区为中心
BOOL CMyRipView::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
//pt是Screen显示器坐标系的坐标
//将OnMouseWheel的左边传到当前位置坐标变量当中
m_ptRollPointScreen = pt;
//将pt从屏幕坐标系转换工作区坐标系
//ScreenToClient传入的是坐标点的指针
m_ptRollPointClient = pt;
ScreenToClient(&m_ptRollPointClient);
//如果存在加载的图像,改变原点和宽、高
if (!cimgImage.IsNull())
{
//设立一个tmp暂存正常变量
//保存上一次的结果
CPoint ptTmp = m_OriginSrcPoint;
int nTmpWidth = m_nWidthSrc;
int nTmpHeight = m_nHeightSrc;
//放大操作
if (zDelta > 0)
{
//判断滚轮坐标是否在绘图区当中:在左侧的右边,在右侧的左边
if (m_ptRollPointClient.x < m_rectDraw.TopLeft().x || m_ptRollPointClient.x > m_rectDraw.BottomRight().x)
{
return CView::OnMouseWheel(nFlags, zDelta, pt);
}
//防止缩放过度
//如果宽、高<50,则缩放过度
else if (m_nWidthSrc < 50 || m_nHeightSrc < 50)
{
return CView::OnMouseWheel(nFlags, zDelta, pt);
}
//设置放大倍数
m_fMultiple = 0.1;
//改变原图的宽、高:
//实现放大的过程其实是获取原图某个【区域】的过程
//这个【区域】对比【源图区域】是小的
m_nWidthSrc = m_nWidthSrc * (1 - m_fMultiple);
m_nHeightSrc = m_nHeightSrc * (1 - m_fMultiple);
/*(计算缩放前在Rect的宽和高的比例)、然后用比例乘以Delta(缩放前后宽、高的差值)*/
float nDeltaX = m_ptRollPointClient.x - float(m_rectDraw.TopLeft().x);
float fPosXRatio = nDeltaX / float(m_rectDraw.Width());
float nDeltaY = m_ptRollPointClient.y - float(m_rectDraw.TopLeft().y);
float fPosYRatio = nDeltaY / float(m_rectDraw.Height());
//改变显示区域的原点位置
//改变原点的位置,往右、下移动
//1、以鼠标为中心的缩放(放大)
m_OriginSrcPoint.x += (nTmpWidth - m_nWidthSrc) * fPosXRatio;
m_OriginSrcPoint.y += (nTmpHeight - m_nHeightSrc) * fPosYRatio;
}
//缩小操作
if (zDelta < 0)
{
//判断滚轮坐标是否在绘图区当中
if (m_ptRollPointClient.x < m_rectDraw.TopLeft().x || m_ptRollPointClient.x > m_rectDraw.BottomRight().x)
{
return CView::OnMouseWheel(nFlags, zDelta, pt);
}
//如果宽、高到为3000,则缩小过度
else if (m_nWidthSrc > 3000 || m_nHeightSrc > 3000)
{
return CView::OnMouseWheel(nFlags, zDelta, pt);
}
//设置缩小倍数
m_fMultiple = -0.1;
//改变原图的宽、高:
//实现放大的过程其实是获取原图某个【区域】的过程
//这个【区域】对比【源图区域】是小的
m_nWidthSrc = m_nWidthSrc * (1 - m_fMultiple);
m_nHeightSrc = m_nHeightSrc * (1 - m_fMultiple);
//改变显示区域的原点位置
//改变原点的位置,往右、下移动
//2、以工作区的中心进行缩放(缩小)fPosXRatio = fPosYRatio = 0.5
m_OriginSrcPoint.x += (nTmpWidth - m_nWidthSrc) * 0.5;
m_OriginSrcPoint.y += (nTmpHeight - m_nHeightSrc) * 0.5;
}
//使当前的窗口无效:让Windows知道这个窗口现在该重新绘制一下了
Invalidate();
}
return CView::OnMouseWheel(nFlags, zDelta, pt);
}
3、修改OnDraw函数
void CMyRipView::OnDraw(CDC* pDC)
{
CMyRipDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
if (!pDoc)
return;
// TODO: 在此处为本机数据添加绘制代码
//如果CImage对象不为空
if (!cimgImage.IsNull())
{
//矩形对象rectControl:客户区的矩形区域
CRect rectClient;
//获取工作区Client的矩形区域
GetClientRect(rectClient);
//每次显示前先将原来的界面刷新称白色
CBrush br(0xffffff);//刷成白色(255,255,255)
pDC->FillRect(rectClient, &br);
//获取图片的宽、高
double dWidthOrigin = cimgImage.GetWidth();
double dHeightOrigin = cimgImage.GetHeight();
//定义拉伸后图片的宽、高
double dWidthStrectch;
double dHeightStrectch;
//显示方法二:使原图最大地显示在客户区当中
if (dWidthOrigin > dHeightOrigin)
{
//原图的宽比高大:处理后的宽度,让它和显示框等宽
dWidthStrectch = rectClient.Width();
//处理后的高度
dHeightStrectch = dWidthStrectch / dWidthOrigin * dHeightOrigin;
}
else
{
//原图的高比宽大:处理后的高度,让它和显示框等高
dHeightStrectch = rectClient.Height();
dWidthStrectch = dHeightStrectch / dHeightOrigin * dWidthOrigin;
}
//绘图区域在客户区Client的位置
m_rectDraw = CRect(CPoint((rectClient.Width() - dWidthStrectch) / 2, (rectClient.Height() - dHeightStrectch) / 2), CSize(dWidthStrectch, dHeightStrectch));
//绘制图片到控件表示的区域:CImage::StretchBlt
//句柄+尺寸参数
//SRCCOPY:将源矩形区直接拷贝到目标矩形区域pDC
//handle to destination DC:pDC->m_hDC
/*方法1*/
//cimgImage.StretchBlt(pDC->m_hDC, rectDraw, SRCCOPY);
/*方法2*/
//目标区域Dst
int xDest = m_rectDraw.TopLeft().x;
int yDest = m_rectDraw.TopLeft().y;
int nDestWidth = m_rectDraw.Width();
int nDestHeight = m_rectDraw.Height();
//源图片Src
int xSrc = m_OriginSrcPoint.x;
int ySrc = m_OriginSrcPoint.y;
int nSrcWidth = m_nWidthSrc;
int nSrcHeight = m_nHeightSrc;
//光栅操作
DWORD dwROP = SRCCOPY;
//缩放操作
cimgImage.StretchBlt(pDC->m_hDC, xDest, yDest, nDestWidth, dHeightStrectch, xSrc, ySrc, nSrcWidth, nSrcHeight,dwROP);
//image.Draw(pDC->GetSafeHdc(), 0, 0);
}
//在工作区Client中显示相关的变量
//在(0,0)的位置中输出加载地址
CString cstr_text1 = _T("Load Path:");
cstr_text1 += cstrLoadPathName;
pDC->TextOut(0, 0, cstr_text1);
//输出显示区域的坐标原点
CString cstr_OriginPoint;
cstr_OriginPoint.Format(_T("Origin Point:(%d,%d)"), m_OriginSrcPoint.x, m_OriginSrcPoint.y);
pDC->TextOut(0, 20, cstr_OriginPoint);
//输出屏幕坐标:
CString cstr_PointScreen;
cstr_PointScreen.Format(_T("Screen Point:(%d,%d)"), m_ptRollPointScreen.x, m_ptRollPointScreen.y);
pDC->TextOut(0, 40, cstr_PointScreen);
//输出滚轮坐标位置
CString cstr_PointClient;
cstr_PointClient.Format(_T("Client Point:(%d,%d)"), m_ptRollPointClient.x, m_ptRollPointClient.y);
pDC->TextOut(0, 60, cstr_PointClient);
}
4、修改加载图片的函数
void CMyRipView::OnLoadButton()
{
// TODO: 在此添加命令处理程序代码
//设置打开文件类型的滤波器
TCHAR szFilter[] = _T("Tiff文件(*.tif)|*.tif|位图文件(*.bmp)|*.bmp|所有文件(*.*)|*.*||");
//构造加载文件的文件对话框
CFileDialog fileDlg(TRUE, _T("*.*"), NULL, 0, szFilter, this);
//打开对话框
if (IDOK == fileDlg.DoModal()) //.DoModal()显示一个模态对话框,成功返回IDOK
{
//成功显示打开加载图片的对话框
//获取加载的相关信息:路径名字、名字、路径
cstrLoadPathName = fileDlg.GetPathName();
//获取格式化的图片的名字
cstrLoadName = fileDlg.GetFileName();
//去除路径当中的名字:
//找到路径名字从左往右是第几个索引值
int index = cstrLoadPathName.Find(cstrLoadName);
cstrLoadPath = cstrLoadPathName.Left(index);
//判断是否加载过图片
if (!cimgImage.IsNull())
{
//加载过,销毁
cimgImage.Destroy();
}
//加载图片
cimgImage.Load(cstrLoadPathName);
//放大倍数清零
m_fMultiple = 0;
//原图的宽度
m_nWidthSrc = cimgImage.GetWidth();
//原图的高度
m_nHeightSrc = cimgImage.GetHeight();
//源的原点清零
m_OriginSrcPoint = (0, 0);
//使当前的窗口无效:让Windows知道这个窗口现在该重新绘制一下了
Invalidate();
}
else if (IDCANCEL == fileDlg.DoModal())
{
MessageBox(_T("没有选择加载图片"));
return;
}
}