bmp是BitMap(位图)的简称,也是所有windows上图片显示的基础。所有的图片格式,都必须转换成bmp才能进行最终的显示。所以,bmp文件的读写,就变得非常重要了。然而,很多人是借助于MFC类,C# 库函数,OpenCV,OpenGL等库函数进行bmp文件的读写。试想一下,如果你要在诸如DSP、FPGA之类的嵌入式设备上进行bmp文件的读写,总不能去安装一个庞大的MFC,C#类库吧?其实,我们完全可以抛开这些庞杂繁琐的类库和API函数,仅仅利用C语言,编写几个函数,就完全可以实现bmp文件的读写了。本文的意图正在于此。
一个完整的bmp位图文件,可以分为文件信息头,位图信息头和RGB颜色阵列三个部分。文件信息头主要包含“是否是BMP文件”,文件的大小等信息。而位图信息头则主要包含bmp文件的位图宽度,高度,位平面,通道数等信息。而RGB颜色阵列,里面才真正包含我们所需要的bmp位图的像素数据。需要提醒的是,bmp位图的颜色阵列部分,像素数据的存储是以左下角为原点。也就是说,当你打开一个bmp图片并显示在电脑屏幕上的时,实际在存储的时候,这个图片的最左下角的像素是首先被存储在bmp文件中的。之后,按照从左到右,从下到上的顺序,依次进行像素数据的存储。如果,你存储的是3通道的位图数据(也就是我们通常说的彩图),那么它是按照B0G0R0B1G1R1B2G2R2...的顺序进行存储的,同时,还要考虑到4字节对齐的问题。OK,了解了这些基本概念,相信,自己编程实现一些bmp文件的读写函数并非难事。这里,我给出C语言的版本,仅供参考,如有错误,欢迎指正。
chenLeeCV.h #ifndef CHENLEECV_H #define CHENLEECV_H typedef struct { //unsigned short bfType; unsigned long bfSize; unsigned short bfReserved1; unsigned short bfReserved2; unsigned long bfOffBits; } ClBitMapFileHeader; typedef struct { unsigned long biSize; long biWidth; long biHeight; unsigned short biPlanes; unsigned short biBitCount; unsigned long biCompression; unsigned long biSizeImage; long biXPelsPerMeter; long biYPelsPerMeter; unsigned long biClrUsed; unsigned long biClrImportant; } ClBitMapInfoHeader; typedef struct { unsigned char rgbBlue; //该颜色的蓝色分量 unsigned char rgbGreen; //该颜色的绿色分量 unsigned char rgbRed; //该颜色的红色分量 unsigned char rgbReserved; //保留值 } ClRgbQuad; typedef struct { int width; int height; int channels; unsigned char* imageData; }ClImage; ClImage* clLoadImage(char* path); bool clSaveImage(char* path, ClImage* bmpImg); #endif chenLeeCV.cpp #include "chenLeeCV.h" #include <stdio.h> #include <stdlib.h> ClImage* clLoadImage(char* path) { ClImage* bmpImg; FILE* pFile; unsigned short fileType; ClBitMapFileHeader bmpFileHeader; ClBitMapInfoHeader bmpInfoHeader; int channels = 1; int width = 0; int height = 0; int step = 0; int offset = 0; unsigned char pixVal; ClRgbQuad* quad; int i, j, k; bmpImg = (ClImage*)malloc(sizeof(ClImage)); pFile = fopen(path, "rb"); if (!pFile) { free(bmpImg); return NULL; } fread(&fileType, sizeof(unsigned short), 1, pFile); if (fileType == 0x4D42) { //printf("bmp file! \n"); fread(&bmpFileHeader, sizeof(ClBitMapFileHeader), 1, pFile); /*printf("\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\n"); printf("bmp文件头信息:\n"); printf("文件大小:%d \n", bmpFileHeader.bfSize); printf("保留字:%d \n", bmpFileHeader.bfReserved1); printf("保留字:%d \n", bmpFileHeader.bfReserved2); printf("位图数据偏移字节数:%d \n", bmpFileHeader.bfOffBits);*/ fread(&bmpInfoHeader, sizeof(ClBitMapInfoHeader), 1, pFile); /*printf("\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\n"); printf("bmp文件信息头\n"); printf("结构体长度:%d \n", bmpInfoHeader.biSize); printf("位图宽度:%d \n", bmpInfoHeader.biWidth); printf("位图高度:%d \n", bmpInfoHeader.biHeight); printf("位图平面数:%d \n", bmpInfoHeader.biPlanes); printf("颜色位数:%d \n", bmpInfoHeader.biBitCount); printf("压缩方式:%d \n", bmpInfoHeader.biCompression); printf("实际位图数据占用的字节数:%d \n", bmpInfoHeader.biSizeImage); printf("X方向分辨率:%d \n", bmpInfoHeader.biXPelsPerMeter); printf("Y方向分辨率:%d \n", bmpInfoHeader.biYPelsPerMeter); printf("使用的颜色数:%d \n", bmpInfoHeader.biClrUsed); printf("重要颜色数:%d \n", bmpInfoHeader.biClrImportant); printf("\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\n");*/ if (bmpInfoHeader.biBitCount == 8) { //printf("该文件有调色板,即该位图为非真彩色\n\n"); channels = 1; width = bmpInfoHeader.biWidth; height = bmpInfoHeader.biHeight; offset = (channels*width)%4; if (offset != 0) { offset = 4 - offset; } //bmpImg->mat = kzCreateMat(height, width, 1, 0); bmpImg->width = width; bmpImg->height = height; bmpImg->channels = 1; bmpImg->imageData = (unsigned char*)malloc(sizeof(unsigned char)*width*height); step = channels*width; quad = (ClRgbQuad*)malloc(sizeof(ClRgbQuad)*256); fread(quad, sizeof(ClRgbQuad), 256, pFile); free(quad); for (i=0; i<height; i++) { for (j=0; j<width; j++) { fread(&pixVal, sizeof(unsigned char), 1, pFile); bmpImg->imageData[(height-1-i)*step+j] = pixVal; } if (offset != 0) { for (j=0; j<offset; j++) { fread(&pixVal, sizeof(unsigned char), 1, pFile); } } } } else if (bmpInfoHeader.biBitCount == 24) { //printf("该位图为位真彩色\n\n"); channels = 3; width = bmpInfoHeader.biWidth; height = bmpInfoHeader.biHeight; bmpImg->width = width; bmpImg->height = height; bmpImg->channels = 3; bmpImg->imageData = (unsigned char*)malloc(sizeof(unsigned char)*width*3*height); step = channels*width; offset = (channels*width)%4; if (offset != 0) { offset = 4 - offset; } for (i=0; i<height; i++) { for (j=0; j<width; j++) { for (k=0; k<3; k++) { fread(&pixVal, sizeof(unsigned char), 1, pFile); bmpImg->imageData[(height-1-i)*step+j*3+k] = pixVal; } //kzSetMat(bmpImg->mat, height-1-i, j, kzScalar(pixVal[0], pixVal[1], pixVal[2])); } if (offset != 0) { for (j=0; j<offset; j++) { fread(&pixVal, sizeof(unsigned char), 1, pFile); } } } } } return bmpImg; } bool clSaveImage(char* path, ClImage* bmpImg) { FILE *pFile; unsigned short fileType; ClBitMapFileHeader bmpFileHeader; ClBitMapInfoHeader bmpInfoHeader; int step; int offset; unsigned char pixVal = '\0'; int i, j; ClRgbQuad* quad; pFile = fopen(path, "wb"); if (!pFile) { return false; } fileType = 0x4D42; fwrite(&fileType, sizeof(unsigned short), 1, pFile); if (bmpImg->channels == 3)//24位,通道,彩图 { step = bmpImg->channels*bmpImg->width; offset = step%4; if (offset != 4) { step += 4-offset; } bmpFileHeader.bfSize = bmpImg->height*step + 54; bmpFileHeader.bfReserved1 = 0; bmpFileHeader.bfReserved2 = 0; bmpFileHeader.bfOffBits = 54; fwrite(&bmpFileHeader, sizeof(ClBitMapFileHeader), 1, pFile); bmpInfoHeader.biSize = 40; bmpInfoHeader.biWidth = bmpImg->width; bmpInfoHeader.biHeight = bmpImg->height; bmpInfoHeader.biPlanes = 1; bmpInfoHeader.biBitCount = 24; bmpInfoHeader.biCompression = 0; bmpInfoHeader.biSizeImage = bmpImg->height*step; bmpInfoHeader.biXPelsPerMeter = 0; bmpInfoHeader.biYPelsPerMeter = 0; bmpInfoHeader.biClrUsed = 0; bmpInfoHeader.biClrImportant = 0; fwrite(&bmpInfoHeader, sizeof(ClBitMapInfoHeader), 1, pFile); for (i=bmpImg->height-1; i>-1; i--) { for (j=0; j<bmpImg->width; j++) { pixVal = bmpImg->imageData[i*bmpImg->width*3+j*3]; fwrite(&pixVal, sizeof(unsigned char), 1, pFile); pixVal = bmpImg->imageData[i*bmpImg->width*3+j*3+1]; fwrite(&pixVal, sizeof(unsigned char), 1, pFile); pixVal = bmpImg->imageData[i*bmpImg->width*3+j*3+2]; fwrite(&pixVal, sizeof(unsigned char), 1, pFile); } if (offset!=0) { for (j=0; j<offset; j++) { pixVal = 0; fwrite(&pixVal, sizeof(unsigned char), 1, pFile); } } } } else if (bmpImg->channels == 1)//8位,单通道,灰度图 { step = bmpImg->width; offset = step%4; if (offset != 4) { step += 4-offset; } bmpFileHeader.bfSize = 54 + 256*4 + bmpImg->width; bmpFileHeader.bfReserved1 = 0; bmpFileHeader.bfReserved2 = 0; bmpFileHeader.bfOffBits = 54 + 256*4; fwrite(&bmpFileHeader, sizeof(ClBitMapFileHeader), 1, pFile); bmpInfoHeader.biSize = 40; bmpInfoHeader.biWidth = bmpImg->width; bmpInfoHeader.biHeight = bmpImg->height; bmpInfoHeader.biPlanes = 1; bmpInfoHeader.biBitCount = 8; bmpInfoHeader.biCompression = 0; bmpInfoHeader.biSizeImage = bmpImg->height*step; bmpInfoHeader.biXPelsPerMeter = 0; bmpInfoHeader.biYPelsPerMeter = 0; bmpInfoHeader.biClrUsed = 256; bmpInfoHeader.biClrImportant = 256; fwrite(&bmpInfoHeader, sizeof(ClBitMapInfoHeader), 1, pFile); quad = (ClRgbQuad*)malloc(sizeof(ClRgbQuad)*256); for (i=0; i<256; i++) { quad[i].rgbBlue = i; quad[i].rgbGreen = i; quad[i].rgbRed = i; quad[i].rgbReserved = 0; } fwrite(quad, sizeof(ClRgbQuad), 256, pFile); free(quad); for (i=bmpImg->height-1; i>-1; i--) { for (j=0; j<bmpImg->width; j++) { pixVal = bmpImg->imageData[i*bmpImg->width+j]; fwrite(&pixVal, sizeof(unsigned char), 1, pFile); } if (offset!=0) { for (j=0; j<offset; j++) { pixVal = 0; fwrite(&pixVal, sizeof(unsigned char), 1, pFile); } } } } fclose(pFile); return true; } Main.cpp #include "stdafx.h" #include "chenLeeCV.h" int _tmain(int argc, _TCHAR* argv[]) { ClImage* img = clLoadImage("c:/test.bmp"); bool flag = clSaveImage("c:/result.bmp", img); if(flag) { printf("save ok... \n"); } while(1); return 0; }