最近老师布置一道作业,用DDA算法画出直线,本人在此基础上实现了生成客户区内容为BMP文件的类。
其中一些细节问题值得注意,所以贴出来分享~~
DDA算法实现画直线
中山大学 叶新州
一. 实验内容
用DDA算法思想画出一条线段.
本程序开发于VC.Net平台,基于MFC框架,实现了根据起始坐标(有平面坐标)来画出线段、并且能保存客户区图片位未压缩BMP文件.
二. 算法思想
本程序的DDA与常见的DDA算法有些不同:
采用X方向逐1递增,然后判断上个点的y坐标(OldY)和当前点的y坐标之差是否大于1,是则链接在中间链接两点(此时两点之间所有点x坐标相等)。
三. 算法核心代码
void CDrawLineView::DrawLineByDDA(int nStartX, int nStartY, int nEndX, int nEndY)
{
int i;
//定义坐标系转换
CRect crect;
GetClientRect(crect);
m_nOriginY=crect.Height()-50;
m_nOriginX=50;
DrawCoordinate(m_nOriginX,m_nOriginY); //画出直角坐标系
CClientDC dc(this);
nStartX=m_nOriginX+nStartX;
nEndX=m_nOriginX+nEndX;
nStartY=m_nOriginY-nStartY;
nEndY=m_nOriginY-nEndY;
//end of 定义坐标系转换
if (nStartX==nEndX) //若为垂直线
{
if (nStartY>nEndY) //为了保证nStartY小于nEndY
{
int t;
t=nStartY;
nStartY=nEndY;
nEndY=t;
}
for (i=nStartY;i<=nEndY;i++)
{
dc.SetPixel(nStartX,i,m_cLineColor);
}
}
else //不为垂直线
{
int x;
int nBeginX,nStopX,nBeginY,nStopY;
nBeginX=nStartX;
nStopX=nEndX;
nBeginY=nStartY;
nStopY=nEndY;
if (nStartX>nEndX) //确保(nBeginX,nBeginY)在左边
{
nBeginX=nEndX;
nStopX=nStartX;
nBeginY=nEndY;
nStopY=nStartY;
}
double k=(nStopY*1.0-nBeginY)/(nStopX-nBeginX);
int OldY=(int)((k*(nBeginX-nBeginX)+nBeginY));//上次y的位置
for (x=nBeginX;x<=nStopX;x++)
{
dc.SetPixel(x,k*(x-nBeginX)+nBeginY,m_cLineColor);
if (OldY-(k*(x-nBeginX)+nBeginY)>1) //表明y方向有没有填充的点
{
for (int j=(k*(x-nBeginX)+nBeginY);j<=OldY;j++)
dc.SetPixel(x,j,m_cLineColor);
}
if (OldY-(k*(x-nBeginX)+nBeginY)<-1) //表明y方向有没有填充的点
{
for (int j=OldY;j<=(k*(x-nBeginX)+nBeginY);j++)
dc.SetPixel(x,j,m_cLineColor);
}
OldY=(int)((k*(x-nBeginX)+nBeginY));
}
}
ReleaseDC(&dc);
}
四. 重要功能说明
本程序最大特点是:实现了把客户区内容转换成BMP文件保存。为了实现这以功能,本人仔细研究了BMP文件结构,并在VC.NET下实现了组装BMP文件的功能。
为了实现BMP文件生成,特别生成了一个独立类CMyBMP,如下:
//MyBMP.h
#pragma once
#include "afx.h"
// CMyBMP
class CMyBMP : public CWnd
{
DECLARE_DYNAMIC(CMyBMP)
public:
CMyBMP(CString FileName);
virtual ~CMyBMP();
protected:
DECLARE_MESSAGE_MAP()
BITMAPFILEHEADER m_BMPHeader; //BMP文件头
BITMAPINFO m_BMPInfo; //BMP信息块
BITMAPINFOHEADER m_BMPInfoHeader; //BMP信息头(即包含在BMP信息块的 信息头)
RGBQUAD m_BMPRgbQuad; //BMP色彩表(即包含在BMP信息块的色彩表)
public:
int SetBMPFileHeader(int width, int height);
CFile m_BMPFile;
void SaveToBMPFile(int Red,int Green, int Blue);
struct MyPixel
{//注意这些域的类型,
public:
BYTE b; //代表blue
BYTE g; //代表green
BYTE r; //代表red
};
MyPixel c1; //定义了一个象素点的结构
};
//MyBMP.cpp
// MyBMP.cpp : 实现文件
//
#include "stdafx.h"
#include "DrawLine.h"
#include "MyBMP.h"
#include "./mybmp.h"
// CMyBMP
IMPLEMENT_DYNAMIC(CMyBMP, CWnd)
CMyBMP::CMyBMP(CString FileName)
{
m_BMPFile.Open(FileName,CFile::modeCreate|CFile::modeWrite); //创建BMP文件
}
CMyBMP::~CMyBMP()
{
//关闭BMP文件
m_BMPFile.Flush();
m_BMPFile.Close();
}
BEGIN_MESSAGE_MAP(CMyBMP, CWnd)
END_MESSAGE_MAP()
// CMyBMP 消息处理程序
int CMyBMP::SetBMPFileHeader(int width, int height)
{
m_BMPHeader.bfType=0x4D42;
m_BMPHeader.bfSize=3*width*height+0x36; //指示 整个BMP文件字节数,其中0x36是文件头本身的长度
m_BMPHeader.bfReserved1=0x0;
m_BMPHeader.bfReserved2=0x0;
m_BMPHeader.bfOffBits=0x36; //x36是文件头本身的长度
//以上共占据14个字节
m_BMPInfoHeader.biSize=sizeof(BITMAPINFOHEADER); //指示 文件信息头大小
m_BMPInfoHeader.biWidth=width; //图片宽度
m_BMPInfoHeader.biHeight=height; //图片高度
m_BMPInfoHeader.biPlanes=1;
m_BMPInfoHeader.biBitCount=24; //图片位数,位24位图
//以上共占据14+16个字节
m_BMPInfoHeader.biCompression=0; //表示没有压缩
m_BMPInfoHeader.biSizeImage=0x30; //因为没有压缩,所以可以设置为0
m_BMPInfoHeader.biXPelsPerMeter=0x0;
m_BMPInfoHeader.biYPelsPerMeter=0x0;
m_BMPInfoHeader.biClrUsed=0; //表明使用所有索引色
m_BMPInfoHeader.biClrImportant=0; //说明对图象显示有重要影响的颜色索引的数目,0表示都重要。
//以上共占据14+16+24个字节
/*m_BMPRgbQuad.rgbBlue=0x0;
m_BMPRgbQuad.rgbGreen=0x0;
m_BMPRgbQuad.rgbRed=0x0;
m_BMPRgbQuad.rgbReserved=0x0;
*/
// m_BMPInfo.bmiColors[1]=NULL;
m_BMPInfo.bmiHeader=m_BMPInfoHeader;
m_BMPFile.Write(&(m_BMPHeader),sizeof(m_BMPHeader));
m_BMPFile.Write(&m_BMPInfoHeader,sizeof(m_BMPInfo)-sizeof(m_BMPRgbQuad));
return 1;//表示成功创建BMP文件头
}
void CMyBMP::SaveToBMPFile(int Red,int Green, int Blue)
{
//保存每个象素点的值
c1.b=BYTE(Blue);
c1.g=BYTE(Green);
c1.r=BYTE(Red);
m_BMPFile.Write(&c1,sizeof(MyPixel));
}
另外附上一个在CdrawLineView类中调用CMyBMP类相关的函数
void CDrawLineView::OnSaveLine()
{
//此函数保存客户去的制图内容,存为未经压缩的bmp文件
//技术关键点:图的存储从坐下角开始,每个象素由三个字节表达,分别位BGR(注意顺序),
//每行的字节数必须是4的倍数,否则出错。。。
CRect rect;
GetClientRect(&rect); //获得客户区
CFileDialog fDlg(FALSE,"bmp",0,OFN_HIDEREADONLY|OFN_OVERWRITEPROMPT,"*.bmp");
CString sFileName("");
if (fDlg.DoModal()==IDOK)
{
sFileName=fDlg.GetFileName();
}
if (sFileName!="")
{
OnDraw(GetDC()); //强行刷新客户区
CMyBMP bmp1(fDlg.GetFileName());
bmp1.SetBMPFileHeader(rect.Width()-(rect.Width()%4),rect.Height()); //特别注意!!
int red,green,blue;
CDC *pDC;
pDC=GetDC();
for (int j=rect.Height();j>=1;j--)
for (int i=1;i<=rect.Width()-(rect.Width()%4);i++)
{
//扫描客户区每个点,注意从坐下角 开始最后到 右上角
blue=(pDC->GetPixel(i,j))/(256*256);
green=(pDC->GetPixel(i,j)-blue*(256*256))/256;
red=pDC->GetPixel(i,j)-blue*(256*256)-green*(256);
bmp1.SaveToBMPFile(BYTE(red),BYTE(green),BYTE(blue));
}
//注意RGB(a,b,c)中得到的值为a*1+b*256+c*256*256,在BMP文件内部,排列是BGR(地址低->高)
AfxMessageBox("成功创建文件!");
}
}
五、程序运行示例
(建立直线)
(设置背景/线条颜色)
(保存图片)
(本程序生成的未压缩BMP图示例)
六、实验总结
通过这个程序的开发,我比较深刻的理解了DDA算法的思想,由于开始我并没参考任何DDA算法,只是在课堂上听到一些,故这个DDA算法可能与网上大多数不大一样。
此外,由于见了璐恺同学的作品,感到深深内疚,原来那个程序实在是没有认真对待,故在原有程序基础上,增添了一些实用的功能,特别是实现了保存BMP文件的功能。下面重点总结BMP文件生成类开发的心得:
1. 通过这个实验,我查阅了相关书籍和网上资料,比较弄明白了BMP文件的完整结构,也弄清了一些细节问题,比如每行字节扫描数必为4的整数(即与DWORD对齐),RGB在BMP文件中的表达等。
2. 尽管我实现了BMP文件生成,但是由一些字段还是不大明白,主要是压缩部分biCompression,故我这里设置为0,不压缩。此外垂直水平象素值(即biXPelsPerMeter和biYPelsPerMeter)也不大明白指的是什么,参照资料干脆设置为0。索引色biClrUsed及其重要程度biClrImportant我还是不大明白有什么用途,不过这些应该可以在网上找到答案。
3. 在写这部分功能时,我始终用Debug工具查看每个字节的值,试图分析出它们的含义,事实证明这种方法还是有效的。
4. 这个实验我还发现,有些参考书上说的接在BITMAPINFOHEADER(同样在BITMAPINFO中)后面的RGBQUAD(色彩表,同样包含在BITMAPINFO中)并没有用上,但是有些资料上却说有,不过相应的bfOffBits必须设置为0x3E,我发现有写BMP图片确实有这样的值,但是我这个程序设置为bfOffBits=0x36,而且没有色彩表。估计这个可能是版本问题?
5. 由于生成BMP过程是采集客户区每个象素的值,所以必须每点扫描,速度真的很慢,这个问题还没有解决。
七、参考资料
互联网资料,《BMP文件格式分析》,http://blog.csdn.net/yxz149
2006年3月15日