日期:2014-05-17  浏览次数:20681 次

BMP文件加载探讨(转http://blog.sina.com.cn/s/blog_5da62ae00100pyls.html)

BMP文件加载探讨(转http://blog.sina.com.cn/s/blog_5da62ae00100pyls.html)
2011年10月09日
  通常都是用loadimage把*.bmp文件加载到内存,然后进行处理。不过,这次在加载大的图形文件时候,却出现了像闹鬼式的失败,察看失败代码是8,ERROR_NOT_ENOUGH_MEMORY。而且是有时候失败,有时候又不失败。这才专门花上了些功夫,终于把原因自以为找了出来。
  简而言之,就是DDB和DIB图像处理上的区别。明显的区别就不多说了,造成上面描述问题的,是DDB资源管理方式造成的。加载到内存的DDB资源,特别是 bitmap,其实并不是放在应用程序能够申请到的空间。Loadimage把它load的东西据称放到一个desktop heap的地方。这我没有办法确认,只能够微软说啥就是啥吧。
  那我就要处理多个大的bmp图片怎么办?请看我下面描述的第三种方法吧。
  方法一:
  ////直接从外部文件加载图片
  HBITMAP bitmap;
  bitmap=(HBITMAP)::LoadImage(AfxGetInstanceHandle(),strFileName,IMAGE_BITMAP,0,0,LR_LOADFROMFILE);
  m_backBitmap.DeleteObject();
  if(!m_backBitmap.Attach(bitmap))
  {
  MessageBox("导入背景图失败!","提示",MB_OK);
  return;
  }
  ****************************************
  void CitemView::getBitMap( CDC *pDC )
  {
  CDC   MemDC; 
  HBITMAP hBmp;
  BITMAP   bm; 
  CBitmap Bitmap;
  CPoint point( 10, 10);
  CString cStr;
  //hBmp = (HBITMAP)::LoadImage(NULL,"BG.bmp",IMAGE_BITMAP,0,0,LR_LOADFROMFILE);
  hBmp = (HBITMAP)::LoadImage(AfxGetInstanceHandle(),"BG.bmp",IMAGE_BITMAP,0,0,LR_LOADFROMFILE);
  Bitmap.DeleteObject();
  Bitmap.Attach( hBmp );
  Bitmap.GetObject(sizeof(BITMAP),&bm); 
  MemDC.CreateCompatibleDC(pDC); 
  MemDC.SelectObject(&Bitmap); 
  pDC->BitBlt(point.x, point.y, bm.bmWidth, bm.bmHeight, &MemDC, 0, 0, SRCCOPY); 
  MemDC.DeleteDC();
  }
  方法二:
  用::CreateDIBitmap。呵呵,这个函数其实该叫Create DDB from DIBitmap更好。
  Step1:打开BMP文件
  CFile * t_pfile=new CFile;
  t_bool=t_pfile->Open(arg_mapfile , CFile::modeRead | CFile::typeBinary) ;
  m_vDIBDataLength=t_pfile->GetLength();
  m_pDIBData= new char [m_vDIBDataLength];
  t_dwCount=t_pfile->Read(m_pDIBData,m_vDIBDataLength);
  m_lpBMFH=(LPBITMAPFILEHEADER)m_pDIBData;
  m_lpBMIH=(LPBITMAPINFOHEADER)(m_pDIBData+sizeof(BITMAPFILEHEADER));
  m_lpBMI=(LPBITMAPINFO)m_lpBMIH;
  m_vBits=m_lpBMIH->biBitCount;
  m_lpData=(LPSTR)m_lpBMFH+m_lpBMFH->bfOffBits;
  上面这段伪代码就把BMP文件调入内存了,并把读入内存的文件依照结构分为BITMAP File Header,Bitmap Info Header,Bitmap Info,和真正的数据区m_lpData。
  Step2:由这个DIB的文件创建对应的DDB图片:
  ::CreateDIBitmap(pDc->GetSafeHdc(), m_lpBMIH, CBM_INIT, m_lpData, m_lpBMI, DIB_RGB_COLORS);
  说到这里,特别强调一下,上面这种方法,并不是打开一个BMP,或JPG的好方法。
  先说一下Windows里面图像的两种表示方法:DDB和DIB。
  DDB可以理解为设备相关,也就是windows在其特定设备上的内部的图像表示方法。有些地方也叫GDI图像,因为GDI相关的函数都是对DDB的图像直接进行操作。这原本也没有什么问题。最大的问题是:根据我的经验,系统内部的GDI资源(或DDB的图像资源),如icon,鼠标,toolbar,等,都是放在特定的内存区域。这个区域还不能够扩展到你所拥有的全部内存。于是,当你用上面的方法打开*.bmp文件,特别是文件还比较大的时候,没几个你就会发现向你报告内存不够。
  用GetLastError通常你会得到是8,ERROR_NOT_ENOUGH_MEMORY。可是你查看你的内存,还大大的空!
  这个问题不管你是用LoadImage,直接得到HBITMAP,还是打开文件,然后把文件读取到内存,再用转化为DDB,你都会遇到这个问题。更惨的是,如果调试或程序中有这样的DDB图片内存没有释放,那么以后你就再也没有足够内存来打开。LoadImage和CreateDIBitmap都会返回NULL。这时候,你只能够冲启机器了。
  具体原因我也说不清楚,但有一点可以确定,就是DDB的图像资源不是和程序资源放在一起,而是放在系统中的某个角落里。有一篇文件曾经提到过desktop heap,但我不能够确定。最明显的现象就是,当你把图片转化为DDB后,你可以查看你的程序内存占用情况,和系统总的内存被消耗情况,就能够发现区别了。
  总而言之,放置DDB(或GDI图像)的区域比较特殊,不是你有多少内存,就能够申请多少的。
  方法三:
  接下去的问题就是:我的程序就是需要打开多张大图,怎么办?其实在方法二中答案已经有了。
  图片文件不是都已经全部读到内存了么?这部分内存是程序申请的,有多大的内存,就可以申请多大。只不过,这时候的图片都是DIB形式,不能够直接赋值给CBitmap,也不能够直接被CDC对象使用。如果这些图片需要被显示出来,就直接用位图操作函数,把图片映射到device context上:
  ::SetDIBitsToDevice或:: StretchDIBits。
  SetDIBitsToDevice中有两个参数不是很清楚:
  UINT uStartScan,      // first scan line in array
  UINT cScanLines,      // number of scan lines
  但是第一个设置为0,第二个设置为位图高度就OK了。
  一小段代码例证如下:
  ::SetDIBitsToDevice(dc.GetSafeHdc(),UpdateRect.left,UpdateRect.top,UpdateRect.Width(),UpdateRect.Height(), UpdateRect.left, t_pdoc->m_vMapSize.cy-Upda