1.擦除画笔的痕迹
1.1 经过昨天的学习,我们已经可以进行截屏并在截到的屏幕上进行绘制图形了,但是我们绘制的图形有问题,问题在于我们回鼠标未松开时的痕迹也会被保留在截获的图像中,现在我们想将这些痕迹进行擦除。
1.我们在绘制曲线与其他图形之间进行擦除(因为绘制曲线时我们并不需要擦除痕迹,绘制非曲线时才需要进行擦除);
2.创建一个兼容性DC: CDC cdc ; cdc.CreateCompatibleDC(&dc) ;
3.选中栈顶元素到兼容性DC中: cdc.SelectObject(pFrame->btmp_box.top()) ;
4.拷贝栈顶元素到主DC上: dc.BitBlt(0, 0, pFrame->m_screen_cx, pFrame->m_screen_cy, &cdc, 0, 0, SRCCOPY) ;
void CShutScreenView::OnMouseMove(UINT nFlags, CPoint point)
{
// 先判断是否需要进行画图
if(is_draw)
{
// 需要画图则进行获取dc
CClientDC dc(this);
// 画曲线
if(m_draw_type == CURVE)
{
CPen pen(PS_DASH, 3, color); // color = RGB(255, 0, 0),写在了 View 的构造函数中
dc.SelectObject(pen);
dc.MoveTo(pointDown);
dc.LineTo(point);
pointDown = point;
return;
}
// 1. 擦除画笔痕迹
CDC cdc;
cdc.CreateCompatibleDC(&dc);
cdc.SelectObject(pFrame->btmp_box.top());
dc.BitBlt(0, 0, pFrame->m_screen_cx, pFrame->m_screen_cy, &cdc, 0, 0, SRCCOPY);
// 其他画图类型
switch (m_draw_type)
{
case LINE:
{
dc.MoveTo(pointDown);
dc.LineTo(point);
}
break;
case RECTANGLE:
{
dc.SelectStockObject(NULL_BRUSH);
dc.Rectangle(pointDown.x, pointDown.y, point.x, point.y);
}
break;
case TRIANGLE:
{
POINT pointArr[] = {
{(pointDown.x+point.x)/2, pointDown.y},
{point.x, point.y},
{pointDown.x, point.y}
}; // 设置一个三角形的三个角的数组
dc.Polygon(pointArr, 3); // 绘制一个多边形
}
break;
case CIRCLE:
{
dc.Ellipse(pointDown.x, pointDown.y, point.x, point.y);
}
break;
default:
break;
}
}
CView::OnMouseMove(nFlags, point);
}
1.2 经过上述步骤,我们完成了擦除多余的痕迹这个操作,但是我们发现在绘制图形的过程中出现了闪屏的问题,为了解决这个问题,我们使用双缓冲原理来解决闪屏问题。
1.创建一个第三方DC用来作为双缓冲的中间DC: CDC di_san_fang_dc ; di_san_fang_dc.CreateCompatibleDC(&dc) ;
2.为这个第三方DC选择一个空位图: CBitmap btmp ; btmp.CreateCompatibleBitmap(&dc,pFrame->m_screen_cx, pFrame->m_screen_cy) ; di_san_fang_dc.SelectObject(btmp) ;
3.将绘制非曲线操作中的所有主DC更改为 di_san_fang_dc ;
4.将第三方DC拷贝到主DC上: dc.BitBlt(0, 0, pFrame->m_screen_cx, pFrame->m_screen_cy, &di_san_fang_dc, 0, 0, SRCCOPY) ;
void CShutScreenView::OnMouseMove(UINT nFlags, CPoint point)
{
// 先判断是否需要进行画图
if(is_draw && m_draw_type != FULL)
{
// 需要画图则进行获取dc
CClientDC dc(this);
CMainFrame* pFrame = (CMainFrame*)AfxGetMainWnd();
CPen pen(PS_DASH, 3, color);
if(m_draw_type == CURVE)
{
dc.SelectObject(pen);
dc.MoveTo(pointDown);
dc.LineTo(point);
pointDown = point;
return;
}
// 1-17
//===========================// 2. 解决闪屏问题:双缓冲===================================================
CDC di_san_fang_dc;
di_san_fang_dc.CreateCompatibleDC(&dc);
CBitmap btmp;
btmp.CreateCompatibleBitmap(&dc,pFrame->m_screen_cx, pFrame->m_screen_cy); // 需要主dc,因为兼容性dc默认无法放颜色,只能是黑白的
di_san_fang_dc.SelectObject(btmp);
// 1. 擦除画笔痕迹
CDC cdc;
cdc.CreateCompatibleDC(&dc);
cdc.SelectObject(pFrame->btmp_box.top());
di_san_fang_dc.BitBlt(0, 0, pFrame->m_screen_cx, pFrame->m_screen_cy, &cdc, 0, 0, SRCCOPY);
// 其他画图类型
switch (m_draw_type)
{
case LINE:
{
di_san_fang_dc.MoveTo(pointDown);
di_san_fang_dc.LineTo(point);
}
break;
case RECTANGLE:
{
di_san_fang_dc.SelectObject(pen);
di_san_fang_dc.SelectStockObject(NULL_BRUSH);
di_san_fang_dc.Rectangle(pointDown.x, pointDown.y, point.x, point.y);
}
break;
case TRIANGLE:
{
POINT pointArr[] = {
{(pointDown.x+point.x)/2, pointDown.y},
{point.x, point.y},
{pointDown.x, point.y}
}; // 设置一个三角形的三个角的数组
di_san_fang_dc.Polygon(pointArr, 3); // 绘制一个多边形
}
break;
case CIRCLE:
{
di_san_fang_dc.Ellipse(pointDown.x, pointDown.y, point.x, point.y);
}
break;
default:
break;
}
//============================================================================
dc.BitBlt(0, 0, pFrame->m_screen_cx, pFrame->m_screen_cy, &di_san_fang_dc, 0, 0, SRCCOPY);
}
CView::OnMouseMove(nFlags, point);
}
1.3 每绘制一次就保存一次图像
1.每绘制一次的意思是鼠标每抬起一下,就将当前位图进行压栈;
2.压栈操作在 View 类中的 LButtonUp 函数中进行;
3.从主DC上向下拿图片,先获得主DC: CClientDC dc(this) ;再创建兼容性DC: CDC cdc ; cdc.CreateCompatibleDC(&dc) ;给兼容性DC创建空位图并放置到兼容性DC中: CBitmap *btmp = new CBitmap ; btmp->CreateCompatibleBitmap(&dc, pFrame->m_screen_cx, pFrame->m_screen_cy) ; cdc.SelectObject(btmp) ;从主DC上拿位图: cdc.BitBlt(0, 0, pFrame->m_screen_cx, pFrame->m_screen_cy, &dc, 0, 0, SRCCOPY) ;压栈: pFrame->btmp_box.push(btmp) ;
void CShutScreenView::OnLButtonUp(UINT nFlags, CPoint point)
{
// 抬起则取消画图
is_draw = false;
CMainFrame *pFrame = (CMainFrame*)AfxGetMainWnd();
// 3. 保存画好的位图
CClientDC dc(this);
CDC cdc;
cdc.CreateCompatibleDC(&dc);
CBitmap *btmp = new CBitmap;
btmp->CreateCompatibleBitmap(&dc, pFrame->m_screen_cx, pFrame->m_screen_cy);
cdc.SelectObject(btmp);
cdc.BitBlt(0, 0, pFrame->m_screen_cx, pFrame->m_screen_cy, &dc, 0, 0, SRCCOPY);
pFrame->btmp_box.push(btmp);
CView::OnLButtonUp(nFlags, point);
}
1.4 撤销绘制
1.撤销绘制的意思就是将栈顶位图删除,显示原来栈里的第二张位图;
2.使撤销操作作为快捷键,在 资源视图->Accelerator 中添加一个新的快捷键: ctrl+z ;
3.给这个快捷键添加消息处理函数 OnAccelerator32778 ;
4.在这个函数里,首先我们判断是否能够进行撤销,也就是栈里的元素个数是否大于1,若大于1则可以进行撤销操作;
5.先删除栈顶元素,再将空的栈顶元素出栈,最后调用重绘消息进行重绘就可完成撤销操作;
void CMainFrame::OnAccelerator32778()
{
// 撤销处理函数:Ctrl+Z
if(btmp_box.size() > 1)
{
// 删除栈顶
delete btmp_box.top();
btmp_box.pop();
// 通知视图重绘
GetActiveView()->SendMessage(WM_PAINT);
}
}
1.5 给工具条添加调色板按钮
1.对于调色板,我们在工具条上添加一个按钮用来点击后显示调色板,它是在 View 类中的;
2.给调色板按钮添加一个消息处理函数: afx_msg void OnChangeColor() ;
3.在该函数中创建一个调色板对象,并通过该对象来获得选中的颜色;
void CShutScreenView::OnChangeColor()
{
CColorDialog dlg; // 调色板对象
if(dlg.DoModal() == IDOK) // 显示调色板
{
color = dlg.GetColor(); // 获取选择的颜色
}
}
1.6 给工具条添加一个填充按钮
1.填充按钮就是将当前颜色填充到可以填充的区域中去;
2.给枚举类型添加一个 FULL ;
3.在鼠标按下 LButtonDown 函数中判断绘制风格 m_draw_type 是否为 FULL ;若是 FULL 则进行填充;
4.填充的步骤,首先获取主DC: CClientDC dc(this) ;其次创建一个画刷: CBrush brush(color) ;画刷的颜色由调色板(若使用了调色板)或默认颜色决定;将画刷选入主DC中: dc.SelectObject(brush) ;获取鼠标点击处的像素颜色: COLORREF pointColor = dc.GetPixel(point) ;进行颜色填充: dc.ExtFloodFill(point.x, point.y, pointColor, FLOODFILLSURFACE) ;
void CShutScreenView::OnLButtonDown(UINT nFlags, CPoint point)
{
// 记录按下的坐标并画图
pointDown = point;
is_draw = true;
// 判断是否要填充
if(m_draw_type == FULL)
{
CClientDC dc(this);
CBrush brush(color); // 创建一个画笔
dc.SelectObject(brush);
COLORREF pointColor = dc.GetPixel(point); // 获取光标像素点的颜色
dc.ExtFloodFill(point.x, point.y, pointColor, FLOODFILLSURFACE); // 填充
}
CView::OnLButtonDown(nFlags, point);
}
1.7 添加保存按钮
1.首先在工具条上新建一个按钮;
2.在 Frame 类中添加一个函数 OnSave 并与保存按钮进行 ON_COMMAND 消息映射;
3.在 OnSave 函数中保存图片,先定义一个图像对象: CImage img ;
4.关联图片: img.Attach((HBITMAP)(btmp_box.top()->m_hObject)) ;
5.保存: img.Save(L"aa.jpg") ;
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
... ...
ON_COMMAND(ID_SAVE, &CMainFrame::OnSave)
END_MESSAGE_MAP()
void CMainFrame::OnSave()
{
// 简单的保存一下
CImage img;
img.Attach((HBITMAP)(btmp_box.top()->m_hObject));
img.Save(L"aa.jpg");
}
6.上述保存方法无法使用户知道是否已经保存成功,因此一般使用下面这种方式进行保存;
void CMainFrame::OnSave()
{
// 简单的保存一下
CImage img;
img.Attach((HBITMAP)(btmp_box.top()->m_hObject));
if(img.Save(L"../../pictures/haha.jpg") == S_OK)
MessageBox(L"保存成功");
else
{
MessageBox(L"保存失败");
}
img.Detach();
}
1.8 我们对保存图片这一步进行升级:点击保存按钮之后弹出一个文件对话框来进行选择保存的路径
1.首先我们创建一个文件对话框对象: CFileDialog fileDlg(FALSE, L"jpg", L"未命名", OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, szFilter) (其中第二个参数是后缀名的默认值,第三个参数是文件名的默认值,第四个是风格的默认值,最后一个是显示参数的默认值,是一个 static TCHAR BASED_CODE 类型的变量);
2.我们从文件对话框中获取选择的保存路径: CString file_path = fileDlg.GetPathName() ;
3.再进行 1.7 的保存图片步骤即可;
void CMainFrame::OnSave()
{
// 弹出文件夹菜单保存文件
static TCHAR BASED_CODE szFilter[] = _T("jpg|*.jpg|")
_T("png|*.png|")
_T("*.bmp; *.bmp|All Files (*.*)|*.*||");
CFileDialog fileDlg(FALSE, L"jpg", L"未命名", OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, szFilter);
if(fileDlg.DoModal() == IDOK)
{
// 获取输入的路径
CString file_path = fileDlg.GetPathName();
CImage img;
img.Attach((HBITMAP)(btmp_box.top()->m_hObject));
if(img.Save(file_path) == S_OK)
MessageBox(L"保存成功!");
else
{
MessageBox(L"保存失败!");
}
img.Detach(); // 分离图片,若没有这一步,连续点两次保存会出现 Debug 错误
}
}