双缓冲绘图算法C++例子实现


前言

提示:这里可以添加本文要记录的大概内容:
本人最近在学习计算机视觉的东西,然后在B站发现一个挺不错的课程计算机图形学全套算法讲解,于是想着每看一节课,便来总结一次,写一下博客,方便自己与后人的参考。


一、双缓冲绘图算法?

双缓冲是指一个显示缓冲区(显示设备上下文)和一个那日村缓冲区(内存设备上下文)。双缓冲机制是一种基本的动画技术,常用于解决单缓冲擦除图像时带来的屏幕闪烁问题。
本博客将以小球碰撞案例来展现该算法。
先上效果图:在这里插入图片描述

二、使用步骤

1.准备环境

该项目使用的编辑器是VS 2017

2.创建项目

打开VS2017,选择 新建项目 -> MFC 应用, 然后将项目名命名为:CV_ClassOne,点击确定,再选择 单个文档 -> MFC Standard
在这里插入图片描述

3. 创建小球类

# CSphere.h 头文件

#pragma once
#include <atltypes.h>

class CSphere
{
    
    
public:
	CSphere();
	~CSphere();

public:
	void SetParameter(int R, CPoint point);		// 初始化小球
	void Draw(CDC* pDC);						// 绘制小球
	void MoveCenterPoint(CPoint distance);		// 移动小球
	int GetRadius();							// 得到小球半径大小
	CPoint GetCenterPoint();					// 得到小球圆心位置

private:
	int radius;				// 小球半径
	CPoint centerPoint;		// 小球圆心
};
# CSphere.cpp 源文件
#include "pch.h"
#include "CSphere.h"

CSphere::CSphere()
{
    
    
	radius = 10;
	centerPoint = CPoint(200, 100);
}

CSphere::~CSphere()
{
    
    
	
}

void CSphere::SetParameter(int R, CPoint point)
{
    
    
	radius = R;
	centerPoint = point;
}

void CSphere::Draw(CDC * pDC)
{
    
    
	// 设置圆的范围
	CPoint leftTop = CPoint(centerPoint.x - radius, centerPoint.y - radius);		// 左上角
	CPoint rightBottom = CPoint(centerPoint.x + radius, centerPoint.y + radius);	// 右下角

	// 设置画笔,将圆的边缘设为红色
	CPen pen(PS_SOLID, 2, RGB(255, 0, 0));
	pDC->SelectObject(&pen);

	// 设置画刷,将圆的内部填充为红色
	CBrush brush(RGB(255, 0, 0));
	pDC->SelectObject(&brush);

	// 绘制圆形
	pDC->Ellipse(leftTop.x, leftTop.y, rightBottom.x, rightBottom.y);

}

void CSphere::MoveCenterPoint(CPoint distance)
{
    
    
	centerPoint.x += distance.x;
	centerPoint.y += distance.y;
}

int CSphere::GetRadius()
{
    
    
	return radius;
}

CPoint CSphere::GetCenterPoint()
{
    
    
	return centerPoint;
}

4. 在view文件中设置所需参数及函数

先理清一下逻辑:
1. 需要添加一个小球对象,还需要添加小球移动的距离变量;
2. 需要添加一个定时器,用以更新小球的位置,同时调用绘图函数显示图像;
3.在绘图函数中调用双缓冲绘图算法函数;
4. 在双缓冲绘图算法函数中,实现将小球图像保存至内存DC中,再复制到显示DC中显示。

相关代码如下:

// CV_ClassOneView.h: CCVClassOneView 类的接口
//

#pragma once
#include "CSphere.h"

class CCVClassOneView : public CView
{
    
    
protected: // 仅从序列化创建
	CCVClassOneView() noexcept;
	DECLARE_DYNCREATE(CCVClassOneView)

// 特性
public:
	CCVClassOneDoc* GetDocument() const;

// 重写
public:
	virtual void OnDraw(CDC* pDC);  // 重写以绘制该视图
	virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
protected:
	virtual BOOL OnPreparePrinting(CPrintInfo* pInfo);
	virtual void OnBeginPrinting(CDC* pDC, CPrintInfo* pInfo);
	virtual void OnEndPrinting(CDC* pDC, CPrintInfo* pInfo);

// 实现
public:
	virtual ~CCVClassOneView();
#ifdef _DEBUG
	virtual void AssertValid() const;
	virtual void Dump(CDumpContext& dc) const;
#endif


// 生成的消息映射函数
protected:
	DECLARE_MESSAGE_MAP()

// 我添加的东西如下,其它都是创建项目时就自动创建的
protected:
	CSphere sphere;		// 创建小球对象
	CPoint distance;	// 定义球的移动距离
	bool displaying;	// 用于判定小球的运动状态

public:
	afx_msg void OnTimer(UINT_PTR nIDEvent);					// 定时器,用于更新球的位置
	afx_msg void OnLButtonDblClk(UINT nFlags, CPoint point);	// 双击鼠标左键,播放或停止小球运动
	// 操作
public:
	void DoubleBuffer(CDC* pDC);
	void CollisionDetection();
};

#ifndef _DEBUG  // CV_ClassOneView.cpp 中的调试版本
inline CCVClassOneDoc* CCVClassOneView::GetDocument() const
   {
    
     return reinterpret_cast<CCVClassOneDoc*>(m_pDocument); }
#endif
// CV_ClassOneView.cpp: CCVClassOneView 类的实现
// 函数体前注明 Modify 的即为我修改过的函数,注明 New 的即为我新添加的函数

#include "pch.h"
#include "framework.h"
// SHARED_HANDLERS 可以在实现预览、缩略图和搜索筛选器句柄的
// ATL 项目中进行定义,并允许与该项目共享文档代码。
#ifndef SHARED_HANDLERS
#include "CV_ClassOne.h"
#endif

#include "CV_ClassOneDoc.h"
#include "CV_ClassOneView.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#endif


// CCVClassOneView

IMPLEMENT_DYNCREATE(CCVClassOneView, CView)

BEGIN_MESSAGE_MAP(CCVClassOneView, CView)
	// 标准打印命令
	ON_COMMAND(ID_FILE_PRINT, &CView::OnFilePrint)
	ON_COMMAND(ID_FILE_PRINT_DIRECT, &CView::OnFilePrint)
	ON_COMMAND(ID_FILE_PRINT_PREVIEW, &CView::OnFilePrintPreview)
	ON_WM_TIMER()
	ON_WM_LBUTTONDBLCLK()
END_MESSAGE_MAP()

// CCVClassOneView 构造/析构

/*
	Modify
*/
CCVClassOneView::CCVClassOneView() noexcept
{
    
    
	int R = GetSystemMetrics(SM_CXFULLSCREEN) / 20;		// 将小球半径设置为 最大化窗体的显示区域宽度的 1/20
	sphere.SetParameter(R, CPoint(200, 100));			// 将小球的起始点设置为(200,100)

	distance = CPoint(1, 1);							// 将小球x、y方向的移动距离都定为1
	displaying = false;									// 初始化未播放状态
}

CCVClassOneView::~CCVClassOneView()
{
    
    
}

BOOL CCVClassOneView::PreCreateWindow(CREATESTRUCT& cs)
{
    
    
	// TODO: 在此处通过修改
	//  CREATESTRUCT cs 来修改窗口类或样式

	return CView::PreCreateWindow(cs);
}

// CCVClassOneView 绘图

/*
	New
*/
void CCVClassOneView::DoubleBuffer(CDC* pDC)
{
    
    
	// 获取当前窗口客户区(即图片中的黑色区域)的大小
	CRect rect;
	GetClientRect(&rect);

	// 创建一个与显示DC相兼容的内存DC
	CDC memDC;
	memDC.CreateCompatibleDC(pDC);	
	
	CBitmap newBitMap, *oldBitMap;
	newBitMap.CreateCompatibleBitmap(pDC, rect.Width(), rect.Height());		// 创建兼容内存位图,用以存储更新后的图像
	oldBitMap = memDC.SelectObject(&newBitMap);								// 将新位图放进内存DC中,并返回旧位图

	sphere.Draw(&memDC);													// 绘制新位图,并存到内存DC中

	pDC->BitBlt(0, 0, rect.Width(), rect.Height(), &memDC, 0, 0, SRCCOPY);	// 将内存DC的位图复制到显示DC用以显示在屏幕上

	memDC.SelectObject(oldBitMap);											// 将旧位图归还给内存DC
	newBitMap.DeleteObject();												// 释放新位图资源
	memDC.DeleteDC();														// 释放内存DC
}

/*
	New
*/
void CCVClassOneView::CollisionDetection()
{
    
    
	// 获取当前窗口客户区(即图片中的黑色区域)的大小
	CRect rect;
	GetClientRect(&rect);

	// 获取当前小球位置信息
	CPoint centerPoint = sphere.GetCenterPoint();
	int radius = sphere.GetRadius();

	// 根据小球边缘坐标与窗口大小位置,更新移动距离
	// 注意:在窗口中,左顶点为原点,从左到右为X轴正方向,从上到下是Y轴正方向
	if (centerPoint.x - radius < 0)
		distance.x = 1;
	if (centerPoint.x + radius > rect.Width())
		distance.x = -1;
	if (centerPoint.y - radius < 0)
		distance.y = 1;
	if (centerPoint.y + radius > rect.Height())
		distance.y = -1;
		
}

/*
	Modify
*/
void CCVClassOneView::OnDraw(CDC* pDC)
{
    
    
	CCVClassOneDoc* pDoc = GetDocument();
	ASSERT_VALID(pDoc);
	if (!pDoc)
		return;

	// 使用双缓冲绘图算法
	DoubleBuffer(pDC);
}


// CCVClassOneView 打印

BOOL CCVClassOneView::OnPreparePrinting(CPrintInfo* pInfo)
{
    
    
	// 默认准备
	return DoPreparePrinting(pInfo);
}

void CCVClassOneView::OnBeginPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)
{
    
    
	// TODO: 添加额外的打印前进行的初始化过程
}

void CCVClassOneView::OnEndPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)
{
    
    
	// TODO: 添加打印后进行的清理过程
}


// CCVClassOneView 诊断

#ifdef _DEBUG
void CCVClassOneView::AssertValid() const
{
    
    
	CView::AssertValid();
}

void CCVClassOneView::Dump(CDumpContext& dc) const
{
    
    
	CView::Dump(dc);
}

CCVClassOneDoc* CCVClassOneView::GetDocument() const // 非调试版本是内联的
{
    
    
	ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CCVClassOneDoc)));
	return (CCVClassOneDoc*)m_pDocument;
}
#endif //_DEBUG


// CCVClassOneView 消息处理程序

/*
	New
*/
void CCVClassOneView::OnTimer(UINT_PTR nIDEvent)
{
    
    
	CollisionDetection();				// 检测小球当前位置是否发生碰撞,然后更新相应移动距离
	sphere.MoveCenterPoint(distance);	// 更新小球位置
	Invalidate(FALSE);					// 调用OnDraw()函数

	CView::OnTimer(nIDEvent);
}

/*
	New
*/
void CCVClassOneView::OnLButtonDblClk(UINT nFlags, CPoint point)
{
    
    
	displaying = !displaying;	// 鼠标左键双击,即改变播放状态

	if (displaying)
		SetTimer(1, 10, NULL);	// 如果 displaying = true, 则设置定时器开始更新小球位置,进而显示出小球的运动
	else
		KillTimer(1);			// 如果 displaying = false,则关闭该定时器,小球不再运动

	CView::OnLButtonDblClk(nFlags, point);
}

总结

以上就是所有相关代码。但是我仍有一个疑问:

在 void CCVClassOneView::DoubleBuffer(CDC* pDC) 函数最后,有将旧位图重新设置进内存DC中然后再释放内存DC这一步。但是我总感觉这一步是多余的,请问这一步是必须的嘛?希望有人能回答我!

如果其他人有什么问题,或者需要我整个项目的代码,也可以评论或者私信我。我会经常看的!

猜你喜欢

转载自blog.csdn.net/A_water_/article/details/121311386
今日推荐