« 遇上OpenGL,一年认识HBITMAP与Bmp操作(整内存拷贝版) »

Bmp文件的结构与基本操作(逐像素印屏版)

 图形学与二维图像处理技术是很有关联的(要不,CSDN的论坛怎么把它们归一块去了?呵呵,说笑),而学计算机图像处理首先要解决的——图片怎么读入程序呀?——ZwqXin.com

本文来源于 ZwqXin (http://www.zwqxin.com/), 转载请注明
      原文地址:http://www.zwqxin.com/archives/image-processing/bmp-operate-print-pixel.html

BMP格式位图是WINDOWS搞出来的,因此WIN32里有LoadImage, LoadBitmap等函数给你直接载入图片。但是,想真正了解BMP这种图片格式的话,还是自己对BMP从头读入开始吧。根据WINDOWS游戏编程大师技巧第7章,一个BMP文件可以用如下结构描述:

  1. typedef struct tagBITMAP_FILE{
  2.  
  3.       BITMAPFILEHEADER bitmapheader;
  4.       BITMAPINFOHEADER bitmapinfoheader;
  5.       PALETTEENTRY palette[256];
  6.       UCHAR *buffer;   //UCHAR 大小1字节(同BYTE), 在VC6下
  7.  
  8. } BITMAP_FILE;

这里一个BITMAP_FILE就能表示一个BMP文件啦。首先是最开头的BITMAPFILEHEADER描述文件头,接下来BITMAPINFOHEADER描述了BMP文件的信息,PALETTEENTRY是调色板,最后是指向数据的指针。具体的含义,你上网GOOGLE一下“Bmp文件结构”就有一大把抓一大把了。其中重要的信息是:

 1.BITMAPFILEHEADER下的bfType。它是文件标识,如果值不是0x4D42,就表明这张根本不是BMP。

    2.BITMAPINFOHEADER下的biBitCount。它代表了BMP的位数。常用的BMP有8位,16位,24位(RGB),32位(RGBA)。

    3.BITMAPINFOHEADER下的biCompression。它表示BMP图片的压缩方式。是的,BMP也能压缩,只不过可能跟你想象的那个“压缩”有点不一样。方式(BI_RGB   BI_RLE8   BI_RLE4  BI_BITFIELDS)中最前一个表示无压缩,最后一个在16位时表示RGB的565存放,我就知道这么多。

    4.BITMAPINFOHEADER下的biSizeImage,biWidth,biHeight;biSizeImage理应=biWidth*biHeight*biBitCount,但是有些图片直接读下来这个值是0的。所以数据大小还是用等式右边得出比较好。另外,BMP数据默认是按行倒置的(biHeight>0),但偶尔也会出现biHeight<0,图片数据无倒置的情况。

另外不得不提的是PALETTEENTRY调色板。8位模式下是调色板(颜色索引);在16位模式下,在biCompression为BI_BITFIELDS的时候存储的是565的16位BMP的掩码(测试过),在biCompression为BI_BI_RGB 的时候存555的16位BMP掩码。

数据是以UCHAR(BYTE)来存储的。而 1 BYTE = 8BIT。所以很明显地,24BIT = 3 * 8BIT,也就是说数据里头每三个数据为一组,分别描述一个B,G,R的值(注意,BMP里面都是BGR顺序),每个分量的范围是0~2^8-1,也就是0~255,跟我们日常所见一致。32BIT多出的8BIT就存ALPHA值了(注意是BGRA顺序)。而8BIT就是说每字节(8BIT)就描述一种颜色,共256“色”了。那怎么知道某个值(譬如120)代表什么颜色呢?调色板起作用了。调色板PALETTEENTRY结构就是一个RGB结构(外加一个保留字,有时用作ALPHA),如果对应的palette[120] = (255,255,0,*)的话,数据区内那个值为120的数据就表示黄色了。可见256“色”不是真的256色(当然,8位的灰度图就真的是256色了)。问题是麻烦的16位(2BYTE),它又怎样呢?在这16个位里,可以是第1位保留,其余15位均分给BGR(传说中的555,每个分量可为0~2^5-1,即0~31,比24位差远了,可见8位也可以比16位“高级”的嘛);也可以是按5-6-5分配给BGR,因为人对绿色的XXX范围(?)比较大,也就有了上述的压缩模式,根据掩码判别。当然还有其他的。

好了,初步了解以上,读吧!

  1. COLORREF *PixelsArray;
  2. int IMAGE_Width;
  3. int IMAGE_Height;
  4.  
  5. typedef struct tagBITMAP_FILE{
  6.  
  7.       BITMAPFILEHEADER bitmapheader;
  8.       BITMAPINFOHEADER bitmapinfoheader;
  9.       PALETTEENTRY palette[256];
  10.       UCHAR *buffer;   //UCHAR 大小 同 INT (16BYTES)在VC6下
  11.  
  12. } *BITMAP_FILE_PTR, BITMAP_FILE;
  13.  
  14. bool Load_Bitmap_File(BITMAP_FILE_PTR bitmap, char *filename)
  15. {
  16.     FILE *filepointer;
  17.  
  18.     filepointer = fopen(filename, "rb");
  19.     if(filepointer == NULL)
  20.         MessageBox(NULL, "无法打开喔", "My WinMain", MB_OK | MB_ICONINFORMATION);
  21.     
  22.     fread(&bitmap->bitmapheader, sizeof(BITMAPFILEHEADER), 1, filepointer);
  23.  
  24.    if(bitmap->bitmapheader.bfType != 0x4D42)
  25.    {
  26.        fclose(filepointer);
  27.        MessageBox(NULL, "不是0x4D42", "My WinMain", MB_OK | MB_ICONINFORMATION);
  28.         return 0;
  29.    }
  30.  
  31.    fread(&bitmap->bitmapinfoheader, sizeof(BITMAPINFOHEADER), 1, filepointer);
  32.  
  33.    int BmpDataSize = 0;
  34.  
  35.    if(bitmap->bitmapinfoheader.biSizeImage > 0)
  36.        BmpDataSize = bitmap->bitmapinfoheader.biSizeImage;
  37.    else
  38.        BmpDataSize = bitmap->bitmapinfoheader.biWidth * bitmap->bitmapinfoheader.biBitCount * bitmap->bitmapinfoheader.biHeight;
  39.  
  40.    //int fseek( FILE *stream, long offset, int origin );
  41.    //-----origin:
  42.    //SEEK_CUR Current position of file pointer 
  43.    //SEEK_END End of file 
  44.    //SEEK_SET Beginning of file 
  45.  
  46.    fseek(filepointer, (BmpDataSize)*(-1), SEEK_END);
  47.  
  48.    bitmap->buffer = new UCHAR[BmpDataSize];
  49.    memset(bitmap->buffer,0,BmpDataSize);
  50.  
  51.  fread(bitmap->buffer, BmpDataSize, 1, filepointer);
  52.  
  53.    fclose(filepointer);
  54.  
  55.    if(bitmap->bitmapinfoheader.biHeight >= 0)
  56.    {
  57.      IMAGE_Width = bitmap->bitmapinfoheader.biWidth;
  58.      IMAGE_Height = bitmap->bitmapinfoheader.biHeight;
  59.  
  60. //biHeight >= 0, 图像数据上下倒置(如图片左上角的像素颜色值在图像数据的左下角),因此翻转图像   
  61.      UCHAR *temp;
  62.      temp = new UCHAR[BmpDataSize];
  63.      memcpy(temp, bitmap->buffer, BmpDataSize);
  64.  
  65.      // Windows规定一个扫描行所占的字节数必须是4的倍数(即以long为单位),不足的以0填充
  66.      int dataline=(IMAGE_Width * bitmap->bitmapinfoheader.biBitCount+31)/32*4;  
  67.  
  68.      //int del;
  69.      //int dataline = bitmap->bitmapinfoheader.biWidth *  bitmap->bitmapinfoheader.biBitCount /8; 
  70.      //if( (del = dataline%4) != 0 )dataline += 4 - del;
  71.      
  72.     //翻转    
  73.     for(int i = 0; i < IMAGE_Height; i++)
  74.          memcpy(&bitmap->buffer[(IMAGE_Height-1 -i)*(long)dataline], &temp[i*(long)dataline], dataline);//memcpy单位:字节,扫描线必须为4BYTES倍数
  75.  
  76.      delete []temp;
  77.    }
  78.    else
  79.    {
  80.      IMAGE_Width = bitmap->bitmapinfoheader.biWidth;
  81.      IMAGE_Height = -bitmap->bitmapinfoheader.biHeight;
  82.    }
  83.  
  84.  
  85.  
  86.    //转化为WIN32下的COLORREF(DWORD,4BYTES)类型
  87.   
  88.   int bmpPixelNum = bitmap->bitmapinfoheader.biWidth * bitmap->bitmapinfoheader.biHeight;
  89.  
  90.   PixelsArray = new COLORREF[bmpPixelNum];
  91.  
  92.     UCHAR blue, green, red;
  93.    if(bitmap->bitmapinfoheader.biBitCount == 24)
  94.         for(int j = 0; j < (IMAGE_Height-1)*3; j++)
  95.         for(int i = 0; i < IMAGE_Width; i++)
  96.         {
  97.             blue =  bitmap->buffer[i*3 + 0 + IMAGE_Width * j];
  98.             green = bitmap->buffer[i*3 + 1 + IMAGE_Width * j];
  99.             red =   bitmap->buffer[i*3 + 2 + IMAGE_Width * j];
  100.  
  101.            PixelsArray[i + IMAGE_Width * j / 3] = RGB(red, green, blue);
  102.         }
  103.  
  104.  
  105.     return true;
  106. }

当然,从最后转化为COLORREF数组可知这个是24BIT的,32位的也类似设置(改步长为4)。但因为接下来后面的日志会谈到直接打印像素到屏幕的效率问题(真的很低),所以8位和16位版本的我就没做下去了(事实上在二值化等“读-改”像素的操作中我们都会再遇到这种操作)。

直接逐个打印像素到屏幕来显示就这样了:

  1. void ShowBMP(HWND hwnd, HDC mydc, BITMAP_FILE_PTR bitmap, int width, int height)
  2. {
  3.   for(int j = 0; j < height; j++)
  4.     for(int i = 0; i < width; i++)
  5.       SetPixel(mydc, i, j, PixelsArray[i + j * width]);
  6. }

保存到文件.....就是从文件读入的反操作:

  1. void SaveBMP(BITMAP_FILE_PTR bitmap, char *filename)
  2. {
  3.   FILE *file_pointer;
  4.   file_pointer = fopen(filename, "wb");
  5.  
  6.   fwrite(&bitmap->bitmapheader,sizeof(bitmap->bitmapheader), 1, file_pointer);
  7.   fwrite(&bitmap->bitmapinfoheader,sizeof(bitmap->bitmapinfoheader), 1, file_pointer);
  8.  
  9. //上面最后没涉及8位载入,调色板无用
  10.  // fwrite(&bitmap->palette,sizeof(bitmap->palette), 1, file_pointer);//
  11.  
  12.   fwrite(bitmap->buffer,bitmap->bitmapinfoheader.biSizeImage, 1, file_pointer);
  13.   fclose(file_pointer);
  14.  
  15. }

 接下来的整内存拷贝版使BMP图片在WINDOWS窗口内的显示达到正常水平。但是,得学习更多WIN32的API了~
认识HBITMAP与Bmp操作(整内存拷贝版)

本文来源于 ZwqXin (http://www.zwqxin.com/), 转载请注明
      原文地址:http://www.zwqxin.com/archives/image-processing/bmp-operate-print-pixel.html

  • quote 1.胡彦春
  • 这篇文章对我帮助太大了,感谢楼主分享精神。
    充分的理解啊,让我接收网络端的数据(一个大int数组)后直接存成bmp文件的想法

    强烈建议耐下心看完,自己再写出来实现一遍
  • 2013-3-14 21:31:33 回复该留言

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

IE下本页面显示有问题?

→点击地址栏右侧【兼容视图】←

日历

Search

网站分类

最新评论及回复

最近发表

Powered By Z-Blog 1.8 Walle Build 100427

Copyright 2008-2013 ZwqXin. All Rights Reserved. Theme edited from ipati.