图形学与二维图像处理技术是很有关联的(要不,CSDN的论坛怎么把它们归一块去了?呵呵,说笑),而学计算机图像处理首先要解决的——图片怎么读入程序呀?——ZwqXin.com
本文来源于 ZwqXin (http://www.zwqxin.cn/), 转载请注明
原文地址:http://www.zwqxin.cn/archives/image-processing/bmp-operate-print-pixel.html
BMP格式位图是WINDOWS搞出来的,因此WIN32里有LoadImage, LoadBitmap等函数给你直接载入图片。但是,想真正了解BMP这种图片格式的话,还是自己对BMP从头读入开始吧。根据WINDOWS游戏编程大师技巧第7章,一个BMP文件可以用如下结构描述:
- typedef struct tagBITMAP_FILE{
- BITMAPFILEHEADER bitmapheader;
- BITMAPINFOHEADER bitmapinfoheader;
- PALETTEENTRY palette[256];
- UCHAR *buffer; //UCHAR 大小1字节(同BYTE), 在VC6下
- } 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范围(?)比较大,也就有了上述的压缩模式,根据掩码判别。当然还有其他的。
好了,初步了解以上,读吧!
- COLORREF *PixelsArray;
- int IMAGE_Width;
- int IMAGE_Height;
- typedef struct tagBITMAP_FILE{
- BITMAPFILEHEADER bitmapheader;
- BITMAPINFOHEADER bitmapinfoheader;
- PALETTEENTRY palette[256];
- UCHAR *buffer; //UCHAR 大小 同 INT (16BYTES)在VC6下
- } *BITMAP_FILE_PTR, BITMAP_FILE;
- bool Load_Bitmap_File(BITMAP_FILE_PTR bitmap, char *filename)
- {
- FILE *filepointer;
- filepointer = fopen(filename, "rb");
- if(filepointer == NULL)
- MessageBox(NULL, "无法打开喔", "My WinMain", MB_OK | MB_ICONINFORMATION);
- fread(&bitmap->bitmapheader, sizeof(BITMAPFILEHEADER), 1, filepointer);
- if(bitmap->bitmapheader.bfType != 0x4D42)
- {
- fclose(filepointer);
- MessageBox(NULL, "不是0x4D42", "My WinMain", MB_OK | MB_ICONINFORMATION);
- return 0;
- }
- fread(&bitmap->bitmapinfoheader, sizeof(BITMAPINFOHEADER), 1, filepointer);
- int BmpDataSize = 0;
- if(bitmap->bitmapinfoheader.biSizeImage > 0)
- BmpDataSize = bitmap->bitmapinfoheader.biSizeImage;
- else
- BmpDataSize = bitmap->bitmapinfoheader.biWidth * bitmap->bitmapinfoheader.biBitCount * bitmap->bitmapinfoheader.biHeight;
- //int fseek( FILE *stream, long offset, int origin );
- //-----origin:
- //SEEK_CUR Current position of file pointer
- //SEEK_END End of file
- //SEEK_SET Beginning of file
- fseek(filepointer, (BmpDataSize)*(-1), SEEK_END);
- bitmap->buffer = new UCHAR[BmpDataSize];
- memset(bitmap->buffer,0,BmpDataSize);
- fread(bitmap->buffer, BmpDataSize, 1, filepointer);
- fclose(filepointer);
- if(bitmap->bitmapinfoheader.biHeight >= 0)
- {
- IMAGE_Width = bitmap->bitmapinfoheader.biWidth;
- IMAGE_Height = bitmap->bitmapinfoheader.biHeight;
- //biHeight >= 0, 图像数据上下倒置(如图片左上角的像素颜色值在图像数据的左下角),因此翻转图像
- UCHAR *temp;
- temp = new UCHAR[BmpDataSize];
- memcpy(temp, bitmap->buffer, BmpDataSize);
- // Windows规定一个扫描行所占的字节数必须是4的倍数(即以long为单位),不足的以0填充
- int dataline=(IMAGE_Width * bitmap->bitmapinfoheader.biBitCount+31)/32*4;
- //int del;
- //int dataline = bitmap->bitmapinfoheader.biWidth * bitmap->bitmapinfoheader.biBitCount /8;
- //if( (del = dataline%4) != 0 )dataline += 4 - del;
- //翻转
- for(int i = 0; i < IMAGE_Height; i++)
- memcpy(&bitmap->buffer[(IMAGE_Height-1 -i)*(long)dataline], &temp[i*(long)dataline], dataline);//memcpy单位:字节,扫描线必须为4BYTES倍数
- delete []temp;
- }
- else
- {
- IMAGE_Width = bitmap->bitmapinfoheader.biWidth;
- IMAGE_Height = -bitmap->bitmapinfoheader.biHeight;
- }
- //转化为WIN32下的COLORREF(DWORD,4BYTES)类型
- int bmpPixelNum = bitmap->bitmapinfoheader.biWidth * bitmap->bitmapinfoheader.biHeight;
- PixelsArray = new COLORREF[bmpPixelNum];
- UCHAR blue, green, red;
- if(bitmap->bitmapinfoheader.biBitCount == 24)
- for(int j = 0; j < (IMAGE_Height-1)*3; j++)
- for(int i = 0; i < IMAGE_Width; i++)
- {
- blue = bitmap->buffer[i*3 + 0 + IMAGE_Width * j];
- green = bitmap->buffer[i*3 + 1 + IMAGE_Width * j];
- red = bitmap->buffer[i*3 + 2 + IMAGE_Width * j];
- PixelsArray[i + IMAGE_Width * j / 3] = RGB(red, green, blue);
- }
- return true;
- }
当然,从最后转化为COLORREF数组可知这个是24BIT的,32位的也类似设置(改步长为4)。但因为接下来后面的日志会谈到直接打印像素到屏幕的效率问题(真的很低),所以8位和16位版本的我就没做下去了(事实上在二值化等“读-改”像素的操作中我们都会再遇到这种操作)。
直接逐个打印像素到屏幕来显示就这样了:
- void ShowBMP(HWND hwnd, HDC mydc, BITMAP_FILE_PTR bitmap, int width, int height)
- {
- for(int j = 0; j < height; j++)
- for(int i = 0; i < width; i++)
- SetPixel(mydc, i, j, PixelsArray[i + j * width]);
- }
保存到文件.....就是从文件读入的反操作:
- void SaveBMP(BITMAP_FILE_PTR bitmap, char *filename)
- {
- FILE *file_pointer;
- file_pointer = fopen(filename, "wb");
- fwrite(&bitmap->bitmapheader,sizeof(bitmap->bitmapheader), 1, file_pointer);
- fwrite(&bitmap->bitmapinfoheader,sizeof(bitmap->bitmapinfoheader), 1, file_pointer);
- //上面最后没涉及8位载入,调色板无用
- // fwrite(&bitmap->palette,sizeof(bitmap->palette), 1, file_pointer);//
- fwrite(bitmap->buffer,bitmap->bitmapinfoheader.biSizeImage, 1, file_pointer);
- fclose(file_pointer);
- }
接下来的整内存拷贝版使BMP图片在WINDOWS窗口内的显示达到正常水平。但是,得学习更多WIN32的API了~
认识HBITMAP与Bmp操作(整内存拷贝版)
本文来源于 ZwqXin (http://www.zwqxin.cn/), 转载请注明
原文地址:http://www.zwqxin.cn/archives/image-processing/bmp-operate-print-pixel.html