文章目录
- 问题
- 对策
- 代码
- 完整工程
- 结果
- 使用Picture控件的RedrawWindow()的效果
- 使用Dialog的RedrawWindow()的效果
- 使用Picture控件的RedrawWindow(),ROI绘制到图像外的效果
- 结论
问题
最近想通过业余时间,写一个简单的图像处理软件,一点点学习图像处理的基础知识。
目前想实现一个在图像上鼠标选取ROI矩形区域的功能。
想通过OnMouseMove()绘制一个随着鼠标拖动实时显示位置的矩形。
网上推荐双缓冲方案,但是怎么用MFC Picture控件和CImage实现双缓冲,还没查到。
目前采用了一个折中策略,虽然没有完全解决闪烁的问题,但是还可以接收。
对策
每次走进OnMouseMove(),调用Picture控件的RedrawWindow(),清空Piture控件区域,连同Image和ROI矩形一起清除。
重新把存在程序中的CImage Draw到Picture控件中。
通过OnMouseMove()传入的坐标,重画Roi矩形。
==不要调用Dialog的RedrawWindow,否则会严重闪烁。 ==
代码
void CGDITESTDlg::OnLButtonDown(UINT nFlags, CPoint point)
{
ClientToScreen(&point);
GetDlgItem(IDC_PIC_DISPLAY)->GetWindowRect(&rc_dsp);
if (rc_dsp.PtInRect(point)) //判断鼠标左键落下时,point是否在控件的rect范围内
{
m_startPt = point;
isStartPtValid = true;
isDrawingRoi = true;
isDrawRoiFinish = false;
}
CDialogEx::OnLButtonDown(nFlags, point);
}
void CGDITESTDlg::OnLButtonUp(UINT nFlags, CPoint point)
{
ClientToScreen(&point);
GetDlgItem(IDC_PIC_DISPLAY)->GetWindowRect(&rc_dsp);
if (rc_dsp.PtInRect(point) && m_IsChoose)//判断鼠标左键抬起时,point是否在控件的rect范围内
{
if (isDrawingRoi && isStartPtValid)
{
isDrawRoiFinish = false;
isDrawingRoi = false;
isStartPtValid = false;
}
}
CDialogEx::OnLButtonUp(nFlags, point);
}
void CGDITESTDlg::OnMouseMove(UINT nFlags, CPoint point)
{
if ((nFlags & MK_LBUTTON) == MK_LBUTTON) //判断鼠标move的时候,鼠标左键是否按下
{
ClientToScreen(&point);
GetDlgItem(IDC_PIC_DISPLAY)->GetWindowRect(&rc_dsp);
if (rc_dsp.PtInRect(point) && isDrawingRoi) //判断鼠标左键落下时,point是否在控件的rect范围内
{
//就清空picture控件区域,重新画图片,这样就把原来的ROI消除了
{
CRect rc_dsp;
m_ctl_pic_dsp.SetBitmap(NULL);
GetDlgItem(IDC_PIC_DISPLAY)->GetWindowRect(&rc_dsp);
ScreenToClient(&rc_dsp);
//this->RedrawWindow(rc_dsp); //重绘主窗口指定区域 //使用这个函数,会重绘整个picture控件的rc_dsp区域,但是会引起严重闪烁
m_ctl_pic_dsp.RedrawWindow(); //重绘主窗口指定区域 //使用这个控件,只会重绘图像区域,但是闪烁程度相对温柔(观察图片边缘仍可发现闪烁)
//如果有绘制在picture控件内但是在图像外的线条,则不会被重绘
CDC* pdc = nullptr;
switch (m_IsChoose) //重绘image
{
case PIC_ORIGIN:
pdc = m_ctl_pic_dsp.GetDC();
image_origin.Draw(pdc->m_hDC, rc_convert);
m_ctl_pic_dsp.ReleaseDC(pdc);
break;
case PIC_BINARIZATION:
pdc = m_ctl_pic_dsp.GetDC();
image_binarization.Draw(pdc->m_hDC, rc_convert);
m_ctl_pic_dsp.ReleaseDC(pdc);
break;
case PIC_GREY:
pdc = m_ctl_pic_dsp.GetDC();
image_grey.Draw(pdc->m_hDC, rc_convert);
m_ctl_pic_dsp.ReleaseDC(pdc);
break;
default:
break;
}
}
//画Rect
{
CRect rect;
int rect_width = 0;
int rect_height = 0;
CPoint sel_pos;
//GetCursorPos(&sel_pos);
GetDlgItem(IDC_PIC_DISPLAY)->GetWindowRect(&rc_dsp);
if (rc_dsp.PtInRect(point) && m_IsChoose && isStartPtValid && !isDrawRoiFinish)//判断鼠标左键抬起时,point是否在控件的rect范围内
{
//ClipCursor(&rc_dsp);
//计算ROI的topleft和rightbottom坐标
if (point.x > m_startPt.x)
m_endPt.x = point.x < rc_dsp.right ? point.x : rc_dsp.right;
else
m_endPt.x = point.x > rc_dsp.left ? point.x : rc_dsp.left;
if (point.y > m_startPt.y)
m_endPt.y = point.y < rc_dsp.bottom ? point.y : rc_dsp.bottom;
else
m_endPt.y = point.y > rc_dsp.top ? point.y : rc_dsp.top;
CPoint tplt, rtbt;
tplt.x = m_startPt.x < m_endPt.x ? m_startPt.x : m_endPt.x;
tplt.y = m_startPt.y < m_endPt.y ? m_startPt.y : m_endPt.y;
rtbt.x = m_startPt.x > m_endPt.x ? m_startPt.x : m_endPt.x;
rtbt.y = m_startPt.y > m_endPt.y ? m_startPt.y : m_endPt.y;
m_ctl_pic_dsp.ScreenToClient(&tplt);
m_ctl_pic_dsp.ScreenToClient(&rtbt);
CDC* pDC = m_ctl_pic_dsp.GetWindowDC();
CPen pen(PS_SOLID, 2, RGB(255, 0, 0));
pDC->SelectStockObject(NULL_BRUSH);
pDC->SelectObject(&pen);
//在重新draw的image上再画矩形
m_rc = CRect(tplt, rtbt);
pDC->Rectangle(m_rc);
}
}
}
}
CDialogEx::OnMouseMove(nFlags, point);
}
完整工程
MFC简单的图片处理工程
结果
使用Picture控件的RedrawWindow()的效果
可以看到,和图片接触的Picture控件边框会有闪烁,而图片部分本身没什么闪烁。
如果调整图像的Rect坐标,使其离开Picture控件的边框一点距离,会不会就没有闪烁了?
使用Dialog的RedrawWindow()的效果
这里是由于我采集GIf图的帧率太低(CSDN不能传超过5m的图),导致无法还原实际的闪烁效果。
实际上闪烁非常明显,但是没有这样大面积空白的情况。
使用Picture控件的RedrawWindow(),ROI绘制到图像外的效果
当ROI矩形,拉倒图像外但是还在Picture控件内的区域时,可以看到出现了异常的重叠矩形框,这些异常的矩形框无法随着新绘制ROI刷新掉。
我感觉Picture控件的RedrawWindow()只能重绘图像区。
这个问题可以通过控制ROI的坐标范围解决。
结论
仍然在探索双缓冲方案的实现。