BMP-FS

 BMP 󰈭 1892字

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(逃

嗨! 这里是 rqdmap 的个人博客, 我正关注 GNU/Linux 桌面系统, Linux 内核 以及一切有趣的计算机技术! 希望我的内容能对你有所帮助~
如果你遇到了任何问题, 包括但不限于: 博客内容说明不清楚或错误; 样式版面混乱; 加密博客访问请求等问题, 请通过邮箱 rqdmap@gmail.com 联系我!
修改日志
  • 2023-05-29 23:05:14 博客结构与操作脚本重构
  • 2023-05-08 21:44:36 博客架构修改升级
  • 2022-11-16 01:27:34 迁移老博客文章内容