MFC刷新图片以及防止窗口抖动的方法
背景
前置文章:MFC鼠标滚轮缩放OpenCV图片
使用滚轮缩小图片,使其小于MFC的PictrueContral控件大小时,发现缩小前的图片依旧存在,缩小后的图片重叠在其上。具体错误图片如下所示:
故缩放前应先刷新图片。
刷新方法
一、先使PictrueContral控件不可见,后使其重新可见。
实现方法
在显示图片之前添加如下代码刷新MFC的PictrueContral控件,其中IDC_STATIC为PictrueContral控件ID:
GetDlgItem(IDC_STATIC)->ShowWindow(FALSE);
GetDlgItem(IDC_STATIC)->ShowWindow(TRUE);
MFCApplication1Dlg.cpp全部代码如下:
// MFCApplication1Dlg.cpp: 实现文件
//
#include "stdafx.h"
#include "MFCApplication1.h"
#include "MFCApplication1Dlg.h"
#include "afxdialogex.h"
#include "opencv2/opencv.hpp"
using namespace cv;
double g_iZoom = 1;//初始缩放值
double g_iRadio = 0.1;//缩放步距
Mat g_matOriginal;//原始图片
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
// CMFCApplication1Dlg 对话框
CMFCApplication1Dlg::CMFCApplication1Dlg(CWnd* pParent /*=nullptr*/)
: CDialogEx(IDD_MFCAPPLICATION1_DIALOG, pParent)
{
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}
void CMFCApplication1Dlg::DoDataExchange(CDataExchange* pDX)
{
CDialogEx::DoDataExchange(pDX);
}
BEGIN_MESSAGE_MAP(CMFCApplication1Dlg, CDialogEx)
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_WM_MOUSEWHEEL()
END_MESSAGE_MAP()
// CMFCApplication1Dlg 消息处理程序
BOOL CMFCApplication1Dlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
// 设置此对话框的图标。 当应用程序主窗口不是对话框时,框架将自动
// 执行此操作
SetIcon(m_hIcon, TRUE); // 设置大图标
SetIcon(m_hIcon, FALSE); // 设置小图标
namedWindow("ImageShow");//创建OpenCV窗口
HWND hWnd = (HWND)cvGetWindowHandle("ImageShow");//嵌套opencv窗口
HWND hParent = ::GetParent(hWnd);
::SetParent(hWnd, GetDlgItem(IDC_STATIC)->m_hWnd);
::ShowWindow(hParent, SW_HIDE);
g_iZoom = 1;//初始缩放值为1,表示原图
g_matOriginal = imread("E:\\vs\\image\\11.png");//opencv读取图片
imshow("ImageShow", g_matOriginal);//opencv显示图片
waitKey(1);
return TRUE; // 除非将焦点设置到控件,否则返回 TRUE
}
// 如果向对话框添加最小化按钮,则需要下面的代码
// 来绘制该图标。 对于使用文档/视图模型的 MFC 应用程序,
// 这将由框架自动完成。
void CMFCApplication1Dlg::OnPaint()
{
if (IsIconic())
{
CPaintDC dc(this); // 用于绘制的设备上下文
SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);
// 使图标在工作区矩形中居中
int cxIcon = GetSystemMetrics(SM_CXICON);
int cyIcon = GetSystemMetrics(SM_CYICON);
CRect rect;
GetClientRect(&rect);
int x = (rect.Width() - cxIcon + 1) / 2;
int y = (rect.Height() - cyIcon + 1) / 2;
// 绘制图标
dc.DrawIcon(x, y, m_hIcon);
}
else
{
CDialogEx::OnPaint();
}
}
//当用户拖动最小化窗口时系统调用此函数取得光标
//显示。
HCURSOR CMFCApplication1Dlg::OnQueryDragIcon()
{
return static_cast<HCURSOR>(m_hIcon);
}
BOOL CMFCApplication1Dlg::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
GetDlgItem(IDC_STATIC)->ShowWindow(FALSE);
GetDlgItem(IDC_STATIC)->ShowWindow(TRUE);
CRect rect;
GetDlgItem(IDC_STATIC)->GetWindowRect(&rect);//获取MFC图像显示区域
Point p(pt.x, pt.y);//opencv鼠标坐标
Rect r(rect.left, rect.top, rect.right, rect.bottom);//opencv窗口显示区域
if (r.contains(p)) //鼠标放在图片窗口上
{
int w = g_matOriginal.cols, h =g_matOriginal.rows;//原图宽高
Mat mat;
if (zDelta > 0)//放大图片
{
double zoom = g_iZoom + g_iRadio;
if (w * zoom * h * zoom < 3000 * 3000)//图片宽高<3000*3000时缩放值才会变大
{
g_iZoom = zoom;
}
resize(g_matOriginal, mat, Size(), g_iZoom, g_iZoom, INTER_LINEAR); //按比例放大图片
}
else if (zDelta < 0)//缩小图片
{
double zoom = g_iZoom - g_iRadio;
if (w * zoom * h * zoom > 100 * 100)//图片宽高>100*100时缩放值才会变小
{
g_iZoom = zoom;
}
resize(g_matOriginal, mat, Size(), g_iZoom, g_iZoom, INTER_AREA); //按比例缩小图片
}
imshow("ImageShow", mat);//显示缩放后图片
waitKey(1);
}
return CDialogEx::OnMouseWheel(nFlags, zDelta, pt);
}
运行结果
添加刷新代码后结果如下图所示。此方法虽然代码简单,最终结果也正确,但刷新图片时MFC图像窗口出现抖动,即刷新图片时间过长导致刷新时出现短暂空白。
二、填充与MFC图像窗口背景相同的颜色。
实现方法
在显示图片前添加如下代码:
CStatic* pStatic = (CStatic*)GetDlgItem(IDC_STATIC);
CRect rect1;
pStatic->GetClientRect(&rect1);//获取MFC图像窗口相对于窗口用户区左上角的坐标
pStatic->GetDC()->FillSolidRect(rect1.left, rect1.top, rect1.Width(), rect1.Height(), RGB(255, 255, 255));//填充白色
MFCApplication1Dlg.cpp全部代码如下:
// MFCApplication1Dlg.cpp: 实现文件
//
#include "stdafx.h"
#include "MFCApplication1.h"
#include "MFCApplication1Dlg.h"
#include "afxdialogex.h"
#include "opencv2/opencv.hpp"
using namespace cv;
double g_iZoom = 1;//初始缩放值
double g_iRadio = 0.1;//缩放步距
Mat g_matOriginal;//原始图片
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
// CMFCApplication1Dlg 对话框
CMFCApplication1Dlg::CMFCApplication1Dlg(CWnd* pParent /*=nullptr*/)
: CDialogEx(IDD_MFCAPPLICATION1_DIALOG, pParent)
{
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}
void CMFCApplication1Dlg::DoDataExchange(CDataExchange* pDX)
{
CDialogEx::DoDataExchange(pDX);
}
BEGIN_MESSAGE_MAP(CMFCApplication1Dlg, CDialogEx)
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_WM_MOUSEWHEEL()
END_MESSAGE_MAP()
// CMFCApplication1Dlg 消息处理程序
BOOL CMFCApplication1Dlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
// 设置此对话框的图标。 当应用程序主窗口不是对话框时,框架将自动
// 执行此操作
SetIcon(m_hIcon, TRUE); // 设置大图标
SetIcon(m_hIcon, FALSE); // 设置小图标
namedWindow("ImageShow");//创建OpenCV窗口
HWND hWnd = (HWND)cvGetWindowHandle("ImageShow");//嵌套opencv窗口
HWND hParent = ::GetParent(hWnd);
::SetParent(hWnd, GetDlgItem(IDC_STATIC)->m_hWnd);
::ShowWindow(hParent, SW_HIDE);
g_iZoom = 1;//初始缩放值为1,表示原图
g_matOriginal = imread("E:\\vs\\image\\11.png");//opencv读取图片
imshow("ImageShow", g_matOriginal);//opencv显示图片
waitKey(1);
return TRUE; // 除非将焦点设置到控件,否则返回 TRUE
}
// 如果向对话框添加最小化按钮,则需要下面的代码
// 来绘制该图标。 对于使用文档/视图模型的 MFC 应用程序,
// 这将由框架自动完成。
void CMFCApplication1Dlg::OnPaint()
{
if (IsIconic())
{
CPaintDC dc(this); // 用于绘制的设备上下文
SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);
// 使图标在工作区矩形中居中
int cxIcon = GetSystemMetrics(SM_CXICON);
int cyIcon = GetSystemMetrics(SM_CYICON);
CRect rect;
GetClientRect(&rect);
int x = (rect.Width() - cxIcon + 1) / 2;
int y = (rect.Height() - cyIcon + 1) / 2;
// 绘制图标
dc.DrawIcon(x, y, m_hIcon);
}
else
{
CDialogEx::OnPaint();
}
}
//当用户拖动最小化窗口时系统调用此函数取得光标
//显示。
HCURSOR CMFCApplication1Dlg::OnQueryDragIcon()
{
return static_cast<HCURSOR>(m_hIcon);
}
BOOL CMFCApplication1Dlg::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
CStatic* pStatic = (CStatic*)GetDlgItem(IDC_STATIC);
CRect rect1;
pStatic->GetClientRect(&rect1);//获取MFC图像窗口相对于窗口用户区左上角的坐标
pStatic->GetDC()->FillSolidRect(rect1.left, rect1.top, rect1.Width(), rect1.Height(), RGB(255, 255, 255));//填充白色
CRect rect;
GetDlgItem(IDC_STATIC)->GetWindowRect(&rect);//获取MFC图像显示区域
Point p(pt.x, pt.y);//opencv鼠标坐标
Rect r(rect.left, rect.top, rect.right, rect.bottom);//opencv窗口显示区域
if (r.contains(p)) //鼠标放在图片窗口上
{
int w = g_matOriginal.cols, h =g_matOriginal.rows;//原图宽高
Mat mat;
if (zDelta > 0)//放大图片
{
double zoom = g_iZoom + g_iRadio;
if (w * zoom * h * zoom < 3000 * 3000)//图片宽高<3000*3000时缩放值才会变大
{
g_iZoom = zoom;
}
resize(g_matOriginal, mat, Size(), g_iZoom, g_iZoom, INTER_LINEAR); //按比例放大图片
}
else if (zDelta < 0)//缩小图片
{
double zoom = g_iZoom - g_iRadio;
if (w * zoom * h * zoom > 100 * 100)//图片宽高>100*100时缩放值才会变小
{
g_iZoom = zoom;
}
resize(g_matOriginal, mat, Size(), g_iZoom, g_iZoom, INTER_AREA); //按比例缩小图片
}
imshow("ImageShow", mat);//显示缩放后图片
waitKey(1);
}
return CDialogEx::OnMouseWheel(nFlags, zDelta, pt);
}
运行结果
此方法同样能够刷新图片。针对小图片刷新、刷新操作速度慢、刷新频繁的情况,此方法基本可以避免窗口刷新造成抖动的情况。若快速且频繁刷新则会出现窗口抖动,图片闪烁的情况。
三、填充图片
实现方法:
既然MFC刷新图片必然会出现图像窗口短暂空白的情况,若此段时间短到人眼无法识别,则无抖动闪烁情况。若时间过长则会出现窗口抖动、图片闪烁情况。
那么可以新建一张与背景色相同的空白图片,然后在此空白图片上填充缩放后的图片。每次显示此图片时,其总能完整覆盖图片窗口而不再需要刷新图片。
实现步骤
1、添加全局变量和函数声明
Mat g_matOriginal;//原始图片
Mat FileImage(Mat mat);
2、函数定义
Mat FileImage(Mat mat)
{
Mat zero = Mat::Mat(g_szWin, CV_8UC3, Scalar(255, 255, 255));//定义一个与图片控件一样大,与背景色相同的空白图片
Mat roi(zero, Rect(0, 0, mat.cols, mat.rows));//新建一个与mat相同大小的感兴趣区域
mat.copyTo(roi);//将mat拷贝到感兴趣区域
return zero;
}
3、显示图片判断
if (w*g_iZoom < g_szWin.width || h*g_iZoom < g_szWin.height)//图片小于MFC图片窗口时
{
imshow("ImageShow", FileImage(mat));//显示缩放后图片
}
else
{
imshow("ImageShow", mat);//显示缩放后图片
}
4、MFCApplication1Dlg.cpp全部代码
// MFCApplication1Dlg.cpp: 实现文件
//
#include "stdafx.h"
#include "MFCApplication1.h"
#include "MFCApplication1Dlg.h"
#include "afxdialogex.h"
#include "opencv2/opencv.hpp"
using namespace cv;
double g_iZoom = 1;//初始缩放值
double g_iRadio = 0.1;//缩放步距
Size g_szWin = Size(0, 0);//图像窗口大小
Mat g_matOriginal;//原始图片
Mat FileImage(Mat mat);
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
// CMFCApplication1Dlg 对话框
CMFCApplication1Dlg::CMFCApplication1Dlg(CWnd* pParent /*=nullptr*/)
: CDialogEx(IDD_MFCAPPLICATION1_DIALOG, pParent)
{
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}
void CMFCApplication1Dlg::DoDataExchange(CDataExchange* pDX)
{
CDialogEx::DoDataExchange(pDX);
}
BEGIN_MESSAGE_MAP(CMFCApplication1Dlg, CDialogEx)
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_WM_MOUSEWHEEL()
END_MESSAGE_MAP()
// CMFCApplication1Dlg 消息处理程序
BOOL CMFCApplication1Dlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
// 设置此对话框的图标。 当应用程序主窗口不是对话框时,框架将自动
// 执行此操作
SetIcon(m_hIcon, TRUE); // 设置大图标
SetIcon(m_hIcon, FALSE); // 设置小图标
namedWindow("ImageShow");//创建OpenCV窗口
HWND hWnd = (HWND)cvGetWindowHandle("ImageShow");//嵌套opencv窗口
HWND hParent = ::GetParent(hWnd);
::SetParent(hWnd, GetDlgItem(IDC_STATIC)->m_hWnd);
::ShowWindow(hParent, SW_HIDE);
g_iZoom = 1;//初始缩放值为1,表示原图
g_matOriginal = imread("E:\\vs\\image\\11.png");//opencv读取图片
imshow("ImageShow", g_matOriginal);//opencv显示图片
waitKey(1);
CRect rect;
GetDlgItem(IDC_STATIC)->GetClientRect(&rect);//获取句柄
g_szWin = Size(rect.right - rect.left - 2, rect.bottom - rect.top - 2);//获取图像窗口大小
return TRUE; // 除非将焦点设置到控件,否则返回 TRUE
}
// 如果向对话框添加最小化按钮,则需要下面的代码
// 来绘制该图标。 对于使用文档/视图模型的 MFC 应用程序,
// 这将由框架自动完成。
void CMFCApplication1Dlg::OnPaint()
{
if (IsIconic())
{
CPaintDC dc(this); // 用于绘制的设备上下文
SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);
// 使图标在工作区矩形中居中
int cxIcon = GetSystemMetrics(SM_CXICON);
int cyIcon = GetSystemMetrics(SM_CYICON);
CRect rect;
GetClientRect(&rect);
int x = (rect.Width() - cxIcon + 1) / 2;
int y = (rect.Height() - cyIcon + 1) / 2;
// 绘制图标
dc.DrawIcon(x, y, m_hIcon);
}
else
{
CDialogEx::OnPaint();
}
}
//当用户拖动最小化窗口时系统调用此函数取得光标
//显示。
HCURSOR CMFCApplication1Dlg::OnQueryDragIcon()
{
return static_cast<HCURSOR>(m_hIcon);
}
BOOL CMFCApplication1Dlg::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
CRect rect;
GetDlgItem(IDC_STATIC)->GetWindowRect(&rect);//获取MFC图像显示区域
Point p(pt.x, pt.y);//opencv鼠标坐标
Rect r(rect.left, rect.top, rect.right, rect.bottom);//opencv窗口显示区域
if (r.contains(p)) //鼠标放在图片窗口上
{
int w = g_matOriginal.cols, h =g_matOriginal.rows;//原图宽高
Mat mat;
if (zDelta > 0)//放大图片
{
double zoom = g_iZoom + g_iRadio;
if (w * zoom * h * zoom < 3000 * 3000)//图片宽高<3000*3000时缩放值才会变大
{
g_iZoom = zoom;
}
resize(g_matOriginal, mat, Size(), g_iZoom, g_iZoom, INTER_LINEAR); //按比例放大图片
}
else if (zDelta < 0)//缩小图片
{
double zoom = g_iZoom - g_iRadio;
if (w * zoom * h * zoom > 100 * 100)//图片宽高>100*100时缩放值才会变小
{
g_iZoom = zoom;
}
resize(g_matOriginal, mat, Size(), g_iZoom, g_iZoom, INTER_AREA); //按比例缩小图片
}
if (w*g_iZoom < g_szWin.width || h*g_iZoom < g_szWin.height)//图片小于MFC图片窗口时
{
imshow("ImageShow", FileImage(mat));//显示缩放后图片
}
else
{
imshow("ImageShow", mat);//显示缩放后图片
}
waitKey(1);
}
return CDialogEx::OnMouseWheel(nFlags, zDelta, pt);
}
Mat FileImage(Mat mat)
{
Mat zero = Mat::Mat(g_szWin, CV_8UC3, Scalar(255, 255, 255));//定义一个与图片控件一样大,与背景色相同的空白图片
Mat roi(zero, Rect(0, 0, mat.cols, mat.rows));//新建一个与mat相同大小的感兴趣区域
mat.copyTo(roi);//将mat拷贝到感兴趣区域
return zero;
}
5、运行结果