位图图像文件缩放(c++)
前言: c语言课程设计需要实现位图图像缩放,但并没有提供很详细的位图图像的理解,所以颇费了一番功夫,这里给大家整理下以供参考,如有不当之处,还希望纠正
理解位图图像(24位)
本篇文章只介绍biBitCount=24的位图图像,且代码也只适用于24位图像,且无压缩
定义
由多个像素点组成的图像
组成元素
共三部分
- 位图文件头数据结构,包含BMP图像文件的类型、显示内容等信息
- 位图信息数据结构,包含有BMP图像的宽、高、压缩方法,以及定义颜色等信息
- 每个像素点信息 (主要部分)
结构体定义
头文件
#include<windows.h>
- BITMAPFILEHEADER (对应文件头)
typedef struct tagBITMAPFILEHEADER {
WORD bfType;//固定为0x4d42
DWORD bfSize;//文件大小
WORD bfReserved1;//保留字
WORD bfReserved2;//保留字
DWORD bfOffBits;//实际位图数据的偏移字节数,即前两个部分长度之和
} BITMAPFILEHEADER,*LPBITMAPFILEHEADER,*PBITMAPFILEHEADER;
- BITMAPINFOHEADER (对应信息数据)
typedef struct tagBITMAPINFOHEADER {
DWORD biSize;//指定此结构体的长度,为40
LONG biWidth;//位图宽(单位:像素)
LONG biHeight;//位图高(单位:像素)
WORD biPlanes;//平面数,为1
WORD biBitCount;//采用颜色位数,可以是1 2 4 8 16 24 32
DWORD biCompression;//压缩方式,可以是0 1 2,其中0表示不压缩
DWORD biSizeImage;//实际位图数据占用的字节数
LONG biXPelsPerMeter;//X方向分辨率
LONG biYPelsPerMeter;//Y方向分辨率
DWORD biClrUsed;//使用的颜色数
DWORD biClrImportant;//重要颜色数
} BITMAPINFOHEADER,*LPBITMAPINFOHEADER,*PBITMAPINFOHEADER;
每个属性的具体意义可参考 这里
看起来属性很多,你只需要关注这些:
- biWidth
- biHeight
- biBitCount
下面主要介绍biBitCount和像素点的存储方式
- biBitCount 可以理解为每个像素点采用多少位二进制数来描述,例如biBitCount=24代表,每个像素点用24位二进制数描述,而每个像素点又由三个颜色通道组成(rgb),所以每个颜色通道由8位二进制数(1个字节)组成
- 像素点的存储方式 可以先想象成一个矩阵,开始是矩阵的左下角,然后按行存储
所以你只需要按照顺序依次存储新的像素单位即可,但这里有一个比较坑的地方,位图规定每一行元素的大小必须是4byte的整数倍,意思就是如果每一行有1个像素点,即24bit,需要在每一行结尾填充8bit,这8bit数据没有任何意义,只是为了凑齐4byte的整数倍
主要算法
- 计算出缩放后图像的height和width(单位:像素点数量)
- 遍历目标图像的每一个像素点,找到它在源图像的位置,根据双线性插值获得该像素点的三个颜色通道的值
上图为目标图像的一个像素点(E)在源图像的位置,其中x和y均为整数,E点必定落在源图像的一个像素格中,设 F(E) 代表E点一个颜色通道的值,那么根据双线性插值可以求得
F ( E ) = d x d y F ( B ) + ( 1 − d x ) ( 1 − d y ) F ( D ) + ( 1 − d x ) d y F ( A ) + d x ( 1 − d y ) F ( C ) F(E)=dxdyF(B) +(1-dx)(1-dy)F(D)+(1-dx)dyF(A)+dx(1-dy)F(C) F(E)=dxdyF(B)+(1−dx)(1−dy)F(D)+(1−dx)dyF(A)+dx(1−dy)F(C)
代码
运行方法
例如源程序名为 convert.cpp 源图像名 cat.bmp 且在同一路径下
目标图像名为 cat_a.bmp 需要放大1.5倍
编译好后,在命令行输入
convert cat.bmp 150 cat_a.bmp
/*@auther:gtyinstinct*/
/*该程序只能处理biBitCount为24的bmp图片*/
#include<bits/stdc++.h>
#include<windows.h>
#define WIDTHBYTES(bits) (((bits)+31)/32*4)//用于使图像宽度所占字节数为4byte的倍数
using namespace std;
int bmpwidth,bmpheight; //原图像的宽度和高度(单位:像素)
int src_width,dest_width; //实际存储每一行宽度(单位:字节)
int new_bmpwidth,new_bmpheight;//新图像的宽度和高度(单位:像素)
unsigned char *pBmpBuf; //存储原图像像素数据
unsigned char *new_pBmpBuf; //存储新图像像素数据
BITMAPFILEHEADER header;//位图文件头
BITMAPINFOHEADER info;//位图文件信息
int idx(int i,int j,int k){
return i*src_width + j*3 + k;
}
int idx_n(int i,int j,int k){
return i*dest_width + j*3 + k;
}
unsigned char process(int i1,int i2,int j1,int j2,int k,double i_t,double j_t){
unsigned int tmp[] = {pBmpBuf[idx(i1,j1,k)],
pBmpBuf[idx(i1,j2,k)],
pBmpBuf[idx(i2,j1,k)],
pBmpBuf[idx(i2,j2,k)]};
unsigned int ret = (unsigned int)(tmp[0] * (1-i_t) * (1-j_t) +
tmp[1] * (1-i_t) * j_t +
tmp[2] * i_t * (1-j_t) +
tmp[3] * i_t * j_t);
return (unsigned char) ret;
}
bool convert(char *bmpName,char *new_bmpName,double scale)
{
/*检查缩放比例是否正确*/
if(scale <=0){
cout << "scale is not correct" << endl;
return 0;
}
/*以二进制的方式打开文件*/
FILE *src,*dest;
dest = fopen(new_bmpName,"wb");
if( (src = fopen(bmpName,"rb")) == NULL)
{
cout<<"File "<< bmpName <<"failed to open"<<endl;
return 0;
}
/*读取文件头和位图信息*/
fread(&header,sizeof(BITMAPFILEHEADER),1,src);
fread(&info,sizeof(BITMAPINFOHEADER),1,src);
/*获取并打印位图大小*/
bmpwidth = info.biWidth;
bmpheight = info.biHeight;
if(info.biBitCount!=24){
cout << "biBitCount is not equal to 24" << endl;
return 0;
}
cout << "source image:" << endl;
cout << "width:" << bmpwidth << " height:" << bmpheight << endl;
/*计算缩放后的位图大小*/
new_bmpwidth = (int) (bmpwidth*scale);
new_bmpheight = (int) (bmpheight*scale);
cout << "dest image:" << endl;
cout << "width:" << new_bmpwidth << " height:" << new_bmpheight << endl;
/*修改位图信息的大小信息*/
info.biWidth = new_bmpwidth;
info.biHeight = new_bmpheight;
/*计算位图的实际宽度并确保是4byte的倍数*/
src_width = WIDTHBYTES(bmpwidth*info.biBitCount);
dest_width = WIDTHBYTES(new_bmpwidth*info.biBitCount);
/*写入新位图的位图信息和文件头*/
fwrite(&header,sizeof(BITMAPFILEHEADER),1,dest);
fwrite(&info,sizeof(BITMAPINFOHEADER),1,dest);
/*读取原位图的像素阵列*/
pBmpBuf = new unsigned char[bmpheight*src_width];
fread(pBmpBuf,sizeof(unsigned char),bmpheight*src_width,src);
/*计算新位图的像素阵列并写入*/
new_pBmpBuf = new unsigned char[dest_width*new_bmpheight];
for(int i=0;i<new_bmpheight;i++){
for(int j=0;j<new_bmpwidth;j++){
double i_,j_;
i_ = 1.0 * i * (bmpheight-1) / (new_bmpheight-1);
j_ = 1.0 * j * (bmpwidth-1) / (new_bmpwidth-1);
int i1,i2,j1,j2;
i1 = (int)i_;
j1 = (int)j_;
i2 = i1 + 1;
j2 = j1 + 1;
double i_t,j_t;
i_t = i_ - i1;
j_t = j_ - j1;
for(int k=0;k<3;k++){
new_pBmpBuf[idx_n(i,j,k)] = process(i1,i2,j1,j2,k,i_t,j_t);
}
}
}
fwrite(new_pBmpBuf,sizeof(unsigned char),dest_width*new_bmpheight,dest);
/*关闭文件*/
fclose(src);
fclose(dest);
return 1;
}
int main(int argc,char** argv)
{
if(convert(argv[1],argv[3],(double)atoi(argv[2])/100))
cout << "convert successfully";
else
cout << "fail to convert";
return 0;
}
/*@auther:gtyinstinct*/
ps:仅供参考,xder别直接拿源码交差