BMP文件格式初探
实现BMP文件的放缩
搜索资料时找到,笑死。
此为课程设计的第四题,这道题体现了大作业的精髓:在抄中知道,在抄中学习,在抄中苟且。
BMP 文件结构
一般的情况如下所示,摘自 wiki
Structure name | Optional | Size | Purpose | Comments |
---|---|---|---|---|
Bitmap file header | No | 14 bytes | To store general information about the bitmap image file | Not needed after the file is loaded in memory |
DIB header | No | Fixed-size (7 different versions exist) | To store detailed information about the bitmap image and define the pixel format | Immediately follows the Bitmap file header |
Extra bit masks | Yes | 3 or 4 DWORDs[ 6] (12 or 16 bytes) | To define the pixel format | Present only in case the DIB header is the BITMAPINFOHEADER and the Compression Method member is set to either BI_BITFIELDS or BI_ALPHABITFIELDS |
Color table | Semi-optional | Variable size | To define colors used by the bitmap image data (Pixel array) | Mandatory for color depths ≤ 8 bits |
Gap1 | Yes | Variable size | Structure alignment | An artifact of the File offset to Pixel array in the Bitmap file header |
Pixel array | No | Variable size | To define the actual values of the pixels | The pixel format is defined by the DIB header or Extra bit masks. Each row in the Pixel array is padded to a multiple of 4 bytes in size |
Gap2 | Yes | Variable size | Structure alignment | An artifact of the ICC profile data offset field in the DIB header |
ICC color profile | Yes | Variable size | To define the color profile for color management | Can also contain a path to an external file containing the color profile. When loaded in memory as “non-packed DIB”, it is located between the color table and Gap1.[ 7] |
一般来说,现在获取到的BMP文件的DIB头都是第五代(BITMAPV5HEADER),长124字节;但是针对本实验而言,只需要极少的一些信息,因而只保留40字节,剩余84字节读进来不与使用。
调色盘在24位(RGB各占一个字节)时不需要,因而像素矩阵的偏移就是14 + 124
因而在本实验中,BMP结构仅有三部分:
- BMP file header
- DIB header
- pixel array
BMP file header
cpp
1struct{
2 u_int16_t bfType;
3 u_int32_t bfSize;
4 u_int16_t bfReserved1;
5 u_int16_t bfReserved2;
6 u_int32_t bfOffBits;
7 void show(){
8 PRINT_FONT_GRE;
9 printf("BmpFileHeader\n");
10 PRINT_ATTR_REC;
11 fflush(stdout);
12 printf("bfType: %d\n", bfType);
13 printf("bfSize: %d kb\n", bfSize);
14 printf("bfReservered1, 2: %d %d\n", bfReserved1, bfReserved2);
15 printf("bfOffBits: %d\n", bfOffBits);
16
17 puts("");
18 }
19}BmpFileHeader;
该头固定14字节
DIB header
cpp
1struct{
2 u_int32_t biSize;
3 u_int32_t biWidth;
4 u_int32_t biHeight;
5 u_int16_t biPlanes;
6 u_int16_t biBitCount;
7 u_int32_t biCompression;
8 u_int32_t biSizeImage;
9 u_int32_t biXPelsPerMeter;
10 u_int32_t biYPelsPerMeter;
11 u_int32_t biClrUsed;
12 u_int32_t biClrImportant;
13
14 /**
15 * 由于目前一般dedaodeBMP文件均采用第5代DIBHEADER格式,因而DIBHEADER信息长为124字节
16 * 我们额外吸收掉这些额外的信息,在本实验中不予考虑它们。
17 * */
18 u_int8_t foo[84];
19 void show(){
20 PRINT_FONT_GRE;
21 printf("DibHeader\n");
22 PRINT_ATTR_REC;
23 fflush(stdout);
24 printf("biSize: %d\n", biSize);
25 printf("biWidth: %d\n", biWidth);
26 printf("biHeight: %d\n", biHeight);
27 printf("biPlanes(always 1): %d\n", biPlanes);
28 printf("biBitCount: %d\n", biBitCount);
29 printf("biCompression: %d\n", biCompression);
30 printf("biSizeImage: %d\n", biSizeImage);
31 printf("biXPelsPerMeter: %d\n", biXPelsPerMeter);
32 printf("biYPelsPerMeter: %d\n", biYPelsPerMeter);
33 printf("biClrUsed(0 if 2^biBitCount): %d\n", biClrUsed);
34 printf("biClrImportant(0 if all important): %d\n", biClrImportant);
35
36 puts("");
37 }
38}DibHeader;
pixel array
对于缩放后的图片的每一个像素点,将其按对应比例映射回原图,在原图中假设该投影点被四个点$A,B,C,D$构成的矩形包围,那么就利用这四个点的颜色构造出该点的颜色。
使用博客 位图图像文件缩放(c++)提及的双线性插值: $$ F(X) = dxdyF(B)+(1-dx)(1-dy)F(D)+(1-dx)dyF(a)+dx(1-dy)F(C) $$
实现
坑点: ** BMP文件的width**应当是4的倍数!
最开始虽然注意到了4的倍数,但是是使得height是4的倍数,导致最后的satou酱不仅是灰色的,还是倾斜的。经过好一番调试才发现是width,也就是列。
cpp
1#include <stdio.h>
2#include <string.h>
3#include <sys/types.h>
4#include "rqdmap.h"
5
6#include <iostream>
7#pragma pack(2) //对齐
8using namespace std;
9
10struct{
11 u_int16_t bfType;
12 u_int32_t bfSize;
13 u_int16_t bfReserved1;
14 u_int16_t bfReserved2;
15 u_int32_t bfOffBits;
16 void show(){
17 PRINT_FONT_GRE;
18 printf("BmpFileHeader\n");
19 PRINT_ATTR_REC;
20 fflush(stdout);
21 printf("bfType: %d\n", bfType);
22 printf("bfSize: %d kb\n", bfSize);
23 printf("bfReservered1, 2: %d %d\n", bfReserved1, bfReserved2);
24 printf("bfOffBits: %d\n", bfOffBits);
25
26 puts("");
27 }
28}BmpFileHeader;
29
30struct{
31 u_int32_t biSize;
32 u_int32_t biWidth;
33 u_int32_t biHeight;
34 u_int16_t biPlanes;
35 u_int16_t biBitCount;
36 u_int32_t biCompression;
37 u_int32_t biSizeImage;
38 u_int32_t biXPelsPerMeter;
39 u_int32_t biYPelsPerMeter;
40 u_int32_t biClrUsed;
41 u_int32_t biClrImportant;
42
43 /**
44 * 由于目前一般dedaodeBMP文件均采用第5代DIBHEADER格式,因而DIBHEADER信息长为124字节
45 * 我们额外吸收掉这些额外的信息,在本实验中不予考虑它们。
46 * */
47 u_int8_t foo[84];
48 void show(){
49 PRINT_FONT_GRE;
50 printf("DibHeader\n");
51 PRINT_ATTR_REC;
52 fflush(stdout);
53 printf("biSize: %d\n", biSize);
54 printf("biWidth: %d\n", biWidth);
55 printf("biHeight: %d\n", biHeight);
56 printf("biPlanes(always 1): %d\n", biPlanes);
57 printf("biBitCount: %d\n", biBitCount);
58 printf("biCompression: %d\n", biCompression);
59 printf("biSizeImage: %d\n", biSizeImage);
60 printf("biXPelsPerMeter: %d\n", biXPelsPerMeter);
61 printf("biYPelsPerMeter: %d\n", biYPelsPerMeter);
62 printf("biClrUsed(0 if 2^biBitCount): %d\n", biClrUsed);
63 printf("biClrImportant(0 if all important): %d\n", biClrImportant);
64
65 puts("");
66 }
67}DibHeader;
68
69struct pixel{
70 u_int8_t c[3];
71 pixel operator * (double x){
72 pixel res; res.c[0] = res.c[1] = res.c[2] = 0;
73 for(int i = 0; i < 3; i++) res.c[i] = c[i] * x;
74 return res;
75 }
76 pixel operator +(const pixel &b){
77 pixel res; res.c[0] = res.c[1] = res.c[2] = 0;
78 for(int i = 0; i < 3; i++) res.c[i] = c[i] + b.c[i];
79 return res;
80 }
81 void prt(){
82 printf("<%d, %d, %d>\n", c[0], c[1], c[2]);
83 }
84};
85
86int height, width;
87pixel *rawPic;
88
89int newHeight, newWidth;
90pixel *toPic;
91
92int convert(int i, int j, int n, int m){
93 i = max(0, min(i, n));
94 j = max(0, min(j, m));
95 return i * m + j;
96}
97
98void prt(const char *to){
99 FILE *out = fopen(to, "wb+");
100 BmpFileHeader.bfOffBits = 14 + 40;
101 BmpFileHeader.bfSize = BmpFileHeader.bfOffBits + newHeight * newWidth * 3;
102
103 DibHeader.biSize = 40;
104 DibHeader.biWidth = newWidth;
105 DibHeader.biHeight = newHeight;
106 fwrite(&BmpFileHeader, sizeof(BmpFileHeader), 1, out);
107 fwrite(&DibHeader, DibHeader.biSize, 1, out);
108
109 for(int i = 0; i < newHeight; i++) for(int j = 0; j < newWidth; j++)
110 fwrite(&toPic[convert(i, j, newHeight, newWidth)], sizeof(pixel), 1, out);
111}
112
113
114int main(int argc, char *argv[]){
115
116 if(argc != 4 && argc != 2){
117 PRINT_FONT_RED;
118 puts(" usage: ./main bmpFrom ratio bmpto");
119 puts(" usage: ./main bmpFile");
120 PRINT_ATTR_REC;
121 fflush(stdout);
122 return 0;
123 }
124
125 FILE *in = fopen(argv[1], "rb");
126
127 fread(&BmpFileHeader, 1, sizeof(BmpFileHeader), in);
128 fread(&DibHeader, 1, sizeof(DibHeader), in);
129
130
131 if(argc == 2){
132 BmpFileHeader.show();
133 DibHeader.show();
134 return 0;
135 }
136
137 /**
138 * 考虑到24色RGB颜色无调色盘,接下来的信息就是像素点信息
139 * 这一点也可以从:
140 * bfOffBits(138) = BmpFileHeader(14) + biSize(124)得以印证
141 * */
142
143 height = DibHeader.biHeight;
144 width = DibHeader.biWidth;
145 rawPic = (pixel*)malloc(height * width * sizeof(pixel));
146 for(int i = 0; i < height; i++) for(int j = 0; j < width; j++)
147 fread(rawPic + convert(i, j, height, width), 1, sizeof(pixel), in);
148
149
150
151 double ratio; sscanf(argv[2], "%lf", &ratio);
152 ratio /= 100;
153
154
155 newHeight = ratio * height;
156 // newHeight = (newHeight + 3) / 4 * 4;
157 newWidth = ratio * width;
158 newWidth = (newWidth + 3) / 4 * 4;
159
160 // newWidth = 600;
161 toPic = (pixel*)malloc(newHeight * newWidth * sizeof(pixel));
162 memset(toPic, 0, newHeight * newWidth * sizeof(pixel));
163
164 for(int i = 0; i < newHeight; i++) for(int j = 0; j < newWidth; j++){
165 // double _i = i * height / newHeight, _j = j * width / newWidth;
166 double _i = i / ratio, _j = j / ratio;
167 int ni = _i, nj = _j;
168 double dx = _i - ni, dy = _j - nj;
169
170 pixel B = rawPic[convert(ni + 1, nj + 1, height, width)];
171 pixel D = rawPic[convert(ni, nj, height, width)];
172 pixel A = rawPic[convert(ni + 1, nj, height, width)];
173 pixel C = rawPic[convert(ni, nj + 1, height, width)];
174
175
176 toPic[convert(i, j, newHeight, newWidth)] = \
177 (B * dx * dy) + (D * (1 - dx) * (1 - dy)) + (A * (1 - dx) * dy) + (C * dx * (1 - dy));
178
179 }
180
181
182 prt(argv[3]);
183 fclose(in);
184 return 0;
185}
最后放一张satou(逃