C++学习 2019-1-17

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 错误
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值