【MFC】绘制自定义控件-显示图片(支持放大操作)

发布于:2025-06-21 ⋅ 阅读:(13) ⋅ 点赞:(0)

一、CDC类(二级缓存)

CDC类是设备上下文的核心类,它的作用是抽象化对图形输出设备(像屏幕、打印机、内存位图等)的访问,借助CDC我们可以在屏幕上绘制图像。

自定义控件中采用了双缓冲技术,防止在绘制图像时屏幕闪烁。其中用到了关键的几个类和函数,如下。

//成员变量
CDC m_ImageDC;								//	源图片DC
HBITMAP m_hImageBitmap;						//	位图
BITMAPINFO* m_pBitmapInfo;
void* m_pBuffdata;							//	图像数据

CDC m_buffDC_image;							// 二级缓存
CBitmap m_cBitmap;

图像放在控件上,图像的大小和控件的大小可能不一样,这时需要将图像进行压缩或放绘制到控件上(需要直接操作数据,调用Windows API),故在第一个CDC中使用了HBITMAP位图。创建位图调用Windows API接口函数 CreateDIBSection()函数。该函数直接绑定数据指针m_pBuffdata
,可以直接操作数据。

计算好压缩因子、视口、窗口显示区域,通过StretchBlt()函数进行压缩到m_ImageDC中,再通过BitBlt()函数将m_ImageDC复制到m_buffDC_image,再将m_buffDC_image复制到屏幕上。

进行放大操作就是调节视口区域,再计算压缩因子进而计算窗口显示区域,再把图像放到指定区域。

二、计算视口

//成员变量
CSize m_tSizeWnd;							//	控件显示窗口的尺寸
CSize m_tSizeImg;							//	图片的尺寸
int m_iImageSize;							//	图片的字节数

CRect m_reShowImage;						//	显示图像区域(视口)
CRect m_reShowWnd;							//	显示窗口区域
					
double m_dChangeNum;						//	压缩因子(变化因子)

要求:将图像显示在控件上,并且保证在控件上居中。

计算缩放因子

图像不一定是完美契合控件大小,要等比例缩放需计算缩放因子。
计算窗口的宽高和图像宽高的比例,为了图像全部显示在控件上,我们选择最小的比例当缩放因子。

//计算缩放因子
double dChangeX = (double)m_tSizeWnd.cx / m_tSizeImg.cx;
double dChangeY = (double)m_tSizeWnd.cy / m_tSizeImg.cy;

m_dChangeNum = min(dChangeX, dChangeY);

计算视口(图像全部显示在空间上的视口大小)

图像的显示区域就是视口,图像完全显示到控件上,及视口就是整个图像的区域。

//视口大小(相对于图像的坐标)
m_reShowImage.left = 0;
m_reShowImage.top = 0;
m_reShowImage.right = m_reShowImage.left + m_tSizeImg.cx;
m_reShowImage.bottom = m_reShowImage.top + m_tSizeImg.cy;

计算控件显示图像的区域(居中)

第一步:通过压缩因子计算压缩后图像的宽高
第二步:通过控件的宽高-压缩后图像的宽高再除以2,得到居中后的坐标

double dWid = static_cast<double>(m_reShowImage.right - m_reShowImage.left) * m_dChangeNum;
double dHit = static_cast<double>(m_reShowImage.bottom - m_reShowImage.top) * m_dChangeNum;

int iWid = static_cast<int>(dWid + 0.5);  // 四舍五入
int iHit = static_cast<int>(dHit + 0.5);

m_reShowWnd.left = (m_tSizeWnd.cx - iWid) >> 1;
m_reShowWnd.top = (m_tSizeWnd.cy - iHit) >> 1;
m_reShowWnd.right = m_reShowWnd.left + iWid;
m_reShowWnd.bottom = m_reShowWnd.top + iHit;

再调用第一节说的StretchBlt()函数将图像缩放复制到二级缓存中

::StretchBlt(m_buffDC_image.GetSafeHdc(), 
			 	m_reShowWnd.left,
 			 	m_reShowWnd.top,
 			 	m_reShowWnd.Width(),
   			 	m_reShowWnd.Height(),
			m_ImageDC.GetSafeHdc(),
				m_reShowImage.left,
			    m_reShowImage.top,
			    m_reShowImage.Width(), 
			    m_reShowImage.Height(),
			SRCCOPY);

三、放大操作

放大操作:计算新视口

当鼠标点击图像时,图像放大。鼠标指向的区域是相对图像坐标是不变的,通过这个坐标计算新的视口。

补充:当图像需要放大两倍时,就是视口区域缩小两倍,再显示控件上就是放大两倍的效果。

在这里插入图片描述
重写鼠标点击事件,我们可以得到鼠标点击的坐标(x,y)。因为是等比例缩放,通过缩放因子计算可以得到W2和H2。及可以算出坐标到新视口边的距离。

再计算之前需要将坐标转换为图像坐标系上的坐标。(图上画的两个矩形都是图像)

坐标转换(控件上的坐标转换成图像上的坐标)

//将窗口的坐标转化为图像上的坐标
//因为图像是通过缩放因子等比例缩放显示在控件上的,故可以通过这层关系
//计算相对于图像上的坐标
void MyImageView::OnLButtonDblClk(UINT nFlags, CPoint point)
{
	point.x /= m_dChangeNum;
	point.y /= m_dChangeNum;

计算新的缩放因子、计算新的视口大小

double dOldChangeNum = m_dChangeNum;
double newScale = 2 * m_dChangeNum;
newScale = max(m_dMinScale, min(m_dMaxScale, newScale));
double scaleRatio = dOldChangeNum / newScale;  //	1/2

//通过新的缩放因子得出新视口的宽高
int iWid = m_reShowImage.Width() * scaleRatio;
int iHit = m_reShowImage.Height() * scaleRatio;

//通过比例关系 计算出视口的区域坐标
m_reShowImage.left = point.x - (point.x * iWid / m_tSizeImg.cx);
m_reShowImage.top = point.y - (point.y * iHit / m_tSizeImg.cy);
m_reShowImage.right = m_reShowImage.left + iWid;
m_reShowImage.bottom = m_reShowImage.top + iHit;

至此计算出新视口的区域,再进行计算新显示窗口区域就可以完成放大操作。

代码中初始化操作(方便以后cv)

	CDC* pDC = pWnd->GetDC();

	m_pBitmapInfo = (BITMAPINFO*)malloc(sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * 255);

	if (NULL != m_pBitmapInfo)
	{
		for (int i = 0; i < 256; i++)
		{
			m_pBitmapInfo->bmiColors[i].rgbRed = i;
			m_pBitmapInfo->bmiColors[i].rgbGreen = i;
			m_pBitmapInfo->bmiColors[i].rgbBlue = i;
		}

		m_pBitmapInfo->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
		m_pBitmapInfo->bmiHeader.biBitCount = (model == false) ? 8 : 24;
		m_pBitmapInfo->bmiHeader.biPlanes = 1;
		m_pBitmapInfo->bmiHeader.biCompression = 0;
		m_pBitmapInfo->bmiHeader.biXPelsPerMeter = 0;
		m_pBitmapInfo->bmiHeader.biYPelsPerMeter = 0;
		m_pBitmapInfo->bmiHeader.biClrImportant = 0;
		m_pBitmapInfo->bmiHeader.biClrUsed = 0;
		m_pBitmapInfo->bmiHeader.biHeight = tSizeImg.cy;
		m_pBitmapInfo->bmiHeader.biWidth = tSizeImg.cx;

		int bitCount = (model == false) ? 8 : 24;
		m_iImageWidthStep = ((tSizeImg.cx * bitCount / 8) + 3) & ~3;
		m_pBitmapInfo->bmiHeader.biSizeImage = m_iImageWidthStep * tSizeImg.cy;

		m_ImageDC.CreateCompatibleDC(pDC);
		m_ImageDC.SetStretchBltMode(HALFTONE); //( COLORONCOLOR ); //(HALFTONE);
		m_ImageDC.SetBkMode(TRANSPARENT);

		if (m_hImageBitmap)
		{
			DeleteObject(m_hImageBitmap);
			m_hImageBitmap = NULL;
		}

		m_hImageBitmap = CreateDIBSection(m_ImageDC.m_hDC, m_pBitmapInfo, DIB_RGB_COLORS, &m_pBuffdata, NULL, 0);
		m_iImageSize = m_iImageWidthStep * tSizeImg.cy * sizeof(unsigned char);
		memset(m_pBuffdata, 0, m_iImageSize);

		m_ImageDC.SelectObject(m_hImageBitmap);
	}
	//二级缓存初始化
	m_cBitmap.CreateCompatibleBitmap(pDC, m_tSizeWnd.cx, m_tSizeWnd.cy);
	m_buffDC_image.CreateCompatibleDC(pDC);
	m_buffDC_image.SetStretchBltMode(HALFTONE); //( COLORONCOLOR ); //(HALFTONE);
	m_buffDC_image.SetBkMode(TRANSPARENT);
	m_buffDC_image.SelectObject(&m_cBitmap);

注:需要重写这两个函数

virtual void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct);

//这个函数中 设置格式SS_OWNERDRAW 之后 ,调用Invalidate(false)
//才会去执行DrawItem()函数
virtual void PreSubclassWindow()
{
	ModifyStyle(SS_TYPEMASK,
	 	SS_OWNERDRAW | SS_NOTIFY, 
	 	SWP_FRAMECHANGED);
	 	
	CStatic::PreSubclassWindow();

网站公告

今日签到

点亮在社区的每一天
去签到