现在的位置: 首页 > 综合 > 正文

VC实现生成BMP文件(DDA算法画直线)

2013年08月16日 ⁄ 综合 ⁄ 共 6644字 ⁄ 字号 评论关闭
最近老师布置一道作业,用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日

抱歉!评论已关闭.