New Document
 
美丽心灵


时 间 记 忆
<<  < 2006 - 11 >  >>
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30

我的相册

最 新 评 论

最 新 日 志

最 新 留 言

搜 索

最 新 留 言

友 情 连 接
处理 SSI 文件时出错
  • 日志:8
  • 评论:7
  • 留言:1
  • 访问:


 
 
 
 
 
 
 
 
 
 
 
 
 
读写位图程序调试日记
[ 2006-11-12 16:38:00 | By: dragonflier ]
 
这几天写读写位图的程序,看得头都大了。先是看BMP文件的基本格式,然后又参考了n个版本的代码,最后自己copy了读位图的代码段,觉得这个版本写得很简单、条理清楚。不妨拿出来跟大家分享一下:
/*
代码来源:
《Visual C++  图形编程技巧与实例》
 谭明金,人民邮电出版社
*/
 
//自定义位图类型
typedef struct _MYBITMAP {
  COLORREF clrRGBA;
}MYBITMAP;
 
#define DIB_HEADER_MARKER   ((WORD) ('M' << 8) | 'B')
BOOL CImage::LoadImage(CFile& file)
{
 //读取位图文件头
file.Read (&bmfHead,sizeof(BITMAPFILEHEADER));
if(bmfHead.bfType!=DIB_HEADER_MARKER) 
{
 MessageBox("File is not BMP!",NULL,MB_OK); 
 exit(1);
}
 
//读取位图信息头
file.Seek(14,CFile::begin);
file.Read (&bmHead,sizeof(BITMAPINFOHEADER));
if(bmHead.biCompression)
{
 MessageBox("cannot open this compressed file!",NULL,MB_OK); 
 return;
}
 
//从堆中分配逐像素缓冲所需要容量的内存
tmp=bmHead.biWidth*bmHead.biBitCount;
nScan=tmp%32?(tmp/32*4+(tmp%32+32)/32*4):tmp/8;
rdBytes=nScan*bmHead.biHeight;
svBytes=bmHead.biWidth*bmHead.biHeight;
pTemp=new BYTE[rdBytes];
if(pBuff)   delete pBuff;
pBuff=new MYBITMAP[svBytes];
if(!pBuff||!pTemp)
{
 MessageBox("Cannot allocate memory","Memory allocate",MB_OK);
 exit(0);
}
 
//读取位图数据
lOffset=bmfHead.bfOffBits;
file.Seek (lOffset,CFile::begin);
file.Read (pTemp,rdBytes);
 
//读取调色板
if(bmHead.biBitCount<24)
{
 nEntries=Pow2(bmHead.biBitCount);
 pRGBQUARD=new BYTE[nEntries*4];
 file.Seek (54l,CFile::begin);
 file.Read (pRGBQUARD,nEntries*4);
}
 
//进行数据转换
switch(bmHead.biBitCount)
{
 case 1:nPixel=0;
   for(i=bmHead.biHeight-1;i>=0;i--)
    for(index=i*nScan,j=0;j<bmHead.biWidth;j++)
     {
      OneByte=pTemp[index+j/8];
      iRGB=(OneByte&Pow2(7-j%8))?1:0;
      pBuff[nPixel++].clrRGBA =RGB(pRGBQUARD[iRGB*4+2], 
                        pRGBQUARD[iRGB*4+1],pRGBQUARD[iRGB*4]);
     } 
   break;
 case 4:nPixel=0;
    for(i=bmHead.biHeight-1;i>=0;i--)
     for(index=i*nScan,j=0;j<bmHead.biWidth;j++)
     {      OneByte=pTemp[index+j*4/8];
            iRGB=j%2?OneByte&0x0f:OneByte>>4;
            pBuff[nPixel++].clrRGBA =RGB(pRGBQUARD[iRGB*4+2],  
                           pRGBQUARD[iRGB*4+1],pRGBQUARD[iRGB*4]);
     }
    break;
 case 8:nPixel=0;
    for(i=bmHead.biHeight-1;i>=0;i--)
     for(index=i*nScan,j=0;j<bmHead.biWidth;j++)
     {     OneByte=pTemp[index+j];
           iRGB=OneByte;
           pBuff[nPixel++].clrRGBA =RGB(pRGBQUARD[iRGB*4+2], 
                           pRGBQUARD[iRGB*4+1],pRGBQUARD[iRGB*4]);
    }
    break;
 case 24:nPixel=0;
   for(i=bmHead.biHeight-1;i>=0;i--)
     for(index=i*nScan,j=0;j<bmHead.biWidth;j++)
     {      pBuff[nPixel++].clrRGBA =RGB(pTemp[index+2],
                            pTemp[index+1],pTemp[index]);
           index+=3;
     }
     break;
 default:
    MessageBox("File not supported.",NULL,MB_OK);
    exit(1);
}
 
 //释放临时资源
if(pTemp)   delete pTemp;
if(pRGBQUARD)   delete pRGBQUARD;
}
 
至于写图像的代码是自己瞎写的,一开始怎么也不能正常工作,后来经过一个晚上和一个上午的无聊的调试工作,终于写好了。现在,就总结一下我找出的几个BUG。
1、内存数据对齐问题
内存对齐是说数据存放的地址不是任意的,它一般是4的整数倍,原因是考虑CPU读取数据的效率。像上面定义的结构体MYBITMAP表面看起来是3个字节,但实际上编译器给它分配的字节数是4。这可以通过比较MYBITMAP数组前后两个元素的地址值得到验证。
由于没有想到数据对齐问题,出现了下面的错误。开始的时候为了图省事,就用了库函数memcpy,传给的第三个参数是bmiHead.biHeight*bmiHead.biWidth*3。可想而知程序运行有问题,只是开始的时候不知道是什么原因,只是一个很偶然的机会,我试着用for循环重写才惊诧的发现,程序竟然没有出问题。后来,我才猛然想起数据对齐的问题,然后又用参数bmiHead.biHeight*bmiHead.biWidth*4重新调用了memcpy,程序还是对的。
总结:以后再遇到拷贝自定义类型的数组时,尽量不要使用memcpy,而是自己重新写一个,因为它的参数时void型指针,把自定义类型的指针传给给它会产生信息丢失。
/*
函数名称:CopyBuffer
函数功能:把数组src中的元素拷贝到数组dest
*/
void CImage::CopyBuffer(MYBITMAP* dest, MYBITMAP* src)
{
// memcpy(dest, src, bmiHead.biHeight*bmiHead.biWidth*4);
// Not surprised. It's 4 not 3. Consider alignment!
 for (int i = 0; i < bmiHead.biWidth; i++)
  for (int j = 0; j < bmiHead.biHeight; j++)
  {
   dest[j*bmiHead.biWidth+i].clrRGBA = src[j*bmiHead.biWidth+i].clrRGBA;
  }
}
 
2、new/delete相关
说来惭愧,这个简单的bug花了很长时间才找出来。我在一个if语句里面使用了new分配了一段空间并赋值给一个指针,但在使用以后并没有用delete释放空间。不过,程序运行还是对的。后来,我发现了这个错误,就顺手补上了,如下面的代码所示。
int* padding;
if(some_condition)
{
   padding = new int[4];
}
...
delete[] padding; // 后来顺手添加上的
显然,这段代码是有问题的:如果if语句没有执行,padding指向不确定的内存空间,然后delete就会出问题。但是修改完成之后,认为这不会出错的,就没有再调试。后来,我又对程序的其他部分做了修改,然后在某次程序运行中发现了很奇怪的指针错误。调试,调试...都会郁闷死了,也是一霎那我才发现了这个低级的错误。
总结:在每次对代码进行修改后,要养成随时调试的好习惯。
3、"对一未命名文件进行查找失败"
说起这个错误,就想生气。这是在打开文件的时候出现的,微软给出的错误信息实在是太虚无缥缈了,看了几遍都不知道哪里出错了,明明我打开的文件是存在的啊。也曾在网上搜索过这个错误,发现以前有人就碰到过了,但觉得没有可借鉴之处。最后,我把BMP文件的文件头给打出来,才发现保存图像时数据偏移量bfOffBits计算有误,是一个很大的数,怪不得会出错。在写这篇总结的时候,我又一次baidu了下这个错误,才发现别人的情况是差不多的,都是对一个文件进行读操作,而文件指针已到或超出文件尾还进行读操作时才出现的。
总结:遇到问题,先baidu一下^_^
遇到计算文件大小之类的问题,能用sizeof就用,尽量少用变量,因为你不知道它是不是被正确的初始化了,或者是不是被意外修改了。
 
好了,就写这么多了。
PS: 调试过程又耗时又枯燥,但最终这些bug都被我一个个挖出来了,心情还是蛮高兴的^_^
 
 
  • 标签:debug 位图 
  •  
    Re:读写位图程序调试日记
    [ 2007-4-10 10:22:00 | By: mingzihua(游客) ]
     
    CFile~~~“对一未命名文件进行查找失败”的总结给了我很好的提示,谢谢!
     
    个人主页 | 引用 | 返回 | 删除 | 回复
     
    处理 SSI 文件时出错

    发表评论:

      大名:
      密码: (游客无须输入密码)
      主页:
      标题:
      教育人博客页面数据载入,请耐心等待