BMP格式图片的缩放

         绪论

        最近在着手用纯C语言打造一套自己的图像处理算法库——MyCV,在撸代码的时候突然想起我一直在处理固定大小的图片,而不能读取任意大小的图片,这就使我萌生了通过程序来改变图片的大小的想法。

        说干就干,想着我对bmp格式的图片最为了解就从把bmp格式的图片开始吧。经过我一顿猛如虎的操作成功地将代码雏形给敲了出来。来吧展示!

1.图片放缩的逻辑

        众所周知图像数据是由一个个像素点构成的,改变图像的大小无非就是根据一定的算法来改变像素的数量。如何才能改变像素数量还能使图像看起来不失真呢?这一下子好像还真没点思路。经过我三分钟的了解后,瞬间就使我来了灵感,开干!

        要使图像在经过放缩后不失真就必须使得到的新图片上的像素点与原图片上的像素点有一一对应的关系,新图像中的像素点数据要在源图像中找到,下面就列举一种颇为巧妙的方法。

                        \boldsymbol{1:} x' = x'' \div x_{2} \times x_{1} \boldsymbol \rightarrow {2:} y' = y'' \div x_{2} \times y_{1}

                两式中x1、y1表示源图像的宽和高,x2、y2表示目标图像的宽和高。

这样就找到了一种放缩算法的基本原理,下面开始进行代码实现。

         dwsrcY = (uint64_t)(((float)i + 0.5)  / dstHeight * srcHeight - 0.5); 

         dwsrcX = (uint64_t)(((float)j + 0.5)  / dstWidth * srcWidth - 0.5);

        为了使得图片在方所前后图像中心点的相对位置保持不变,因此需要在原始坐标上进行±0.5操作,具体为什么笔者在这里不做赘述。

2.bmp图像的读取与显示

        前面我们知道了图像是以像素点的形式保存在二进制文件中的,计算机是通过解析这些数据从而进行相应的显示的。 一个bmp格式的文件由以下几部分组成:1.bmp头文件、2.bmp头信息文件、3.调色板(位深度小于等于8位时才会使用)、4.像素数据。改变bmp图像的大小首先要改变头文件和头信息文件中的相应选项,否则bmp图片无法正常显示。

         

原图

 这是一张800 * 480的bmp图片,接下来将使用它来演示缩放效果。

2.1 bmp文件行四字节对齐问题

        在图片缩放过程中笔者发现当图像宽度为4的整数倍时图片可以正常显示,否则会出现图片倾斜问题。

                                                        803 * 480 bmp 读取效果图 

        不难发现当原图片的宽度不为4的倍数时图片会发生倾斜。我们转换思路将原图宽重新改成800,而将目标图片的宽度改成803看看又会发生什么。 

                                        803 * 480 bmp图片保存效果图 

        可以看出同样发生了倾斜,只是发生倾斜的方向不同。这是什么原因呢?经查阅相关文献得知,这是由于计算机内部数据读取方式造成的。计算机通常为提高工作效率会以四字节为单位进行数据读取,这就导致了在读取宽度不为4的整数倍的图像时会将上一行数据的结尾与下一行数据的开头读到一起,从而发生错误。解决办法就是在每行数据的结尾进项补充操作,将每行的像素数大小变成4的整数倍。

        srcRowSize = (((infoHeader.width * infoHeader.bit_count) + 31) / 32) * 4;

        在计算大小过程中需要将图像的行字节数进行向上取整,以解决4字节读取问题。

                                        修改后效果图 

        修改后注意在读取过程中跳过原图形每行末尾的0,而在新图像每行的末尾补充上相应字节数的0.(图像宽度不为4的整数倍时)。 

  • 14
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
好的,这里是一个用 C++ 实现的 BMP 图片缩放的示例代码,供你参考: ```cpp #include <iostream> #include <fstream> #include <cstring> #include <cmath> using namespace std; // BMP 文件头结构体 #pragma pack(push, 1) struct BMPFileHeader { uint16_t signature; // 文件类型标志 uint32_t file_size; // 文件大小 uint16_t reserved1; // 保留字段1 uint16_t reserved2; // 保留字段2 uint32_t offset; // 数据偏移量 uint32_t header_size; // 信息头大小 int32_t width; // 图像宽度 int32_t height; // 图像高度 uint16_t planes; // 颜色平面数 uint16_t bpp; // 每个像素的位数 uint32_t compression; // 压缩类型 uint32_t data_size; // 数据大小 int32_t x_resolution; // 水平分辨率 int32_t y_resolution; // 垂直分辨率 uint32_t color_used; // 颜色数 uint32_t color_important; // 重要颜色数 }; #pragma pack(pop) // BMP 图像像素数据结构体 struct BMPImageData { uint8_t r; uint8_t g; uint8_t b; }; // 缩放 BMP 图像 void scale_bmp_image(const char* input_file, const char* output_file, float scale) { BMPFileHeader header; BMPImageData* data = nullptr; // 读取 BMP 文件头和像素数据 ifstream fin(input_file, ios::binary); if (!fin.read(reinterpret_cast<char*>(&header), sizeof(header))) { cerr << "读取文件 " << input_file << " 失败!" << endl; return; } if (header.signature != 0x4D42) { cerr << "文件 " << input_file << " 不是 BMP 格式!" << endl; return; } data = new BMPImageData[header.width * header.height]; fin.read(reinterpret_cast<char*>(data), header.data_size); // 计算缩放后的图像大小 int new_width = static_cast<int>(round(header.width * scale)); int new_height = static_cast<int>(round(header.height * scale)); // 分配缩放后的像素数据内存 BMPImageData* new_data = new BMPImageData[new_width * new_height]; // 缩放 BMP 图像 for (int y = 0; y < new_height; y++) { for (int x = 0; x < new_width; x++) { // 计算缩放后的像素坐标 float src_x = static_cast<float>(x) / scale; float src_y = static_cast<float>(y) / scale; // 双线性插值计算缩放后的像素值 int x1 = static_cast<int>(floor(src_x)); int y1 = static_cast<int>(floor(src_y)); int x2 = static_cast<int>(ceil(src_x)); int y2 = static_cast<int>(ceil(src_y)); float f1 = (x2 - src_x) * (y2 - src_y); float f2 = (src_x - x1) * (y2 - src_y); float f3 = (x2 - src_x) * (src_y - y1); float f4 = (src_x - x1) * (src_y - y1); BMPImageData& p1 = data[y1 * header.width + x1]; BMPImageData& p2 = data[y1 * header.width + x2]; BMPImageData& p3 = data[y2 * header.width + x1]; BMPImageData& p4 = data[y2 * header.width + x2]; BMPImageData& new_p = new_data[y * new_width + x]; new_p.r = static_cast<uint8_t>(round(p1.r * f1 + p2.r * f2 + p3.r * f3 + p4.r * f4)); new_p.g = static_cast<uint8_t>(round(p1.g * f1 + p2.g * f2 + p3.g * f3 + p4.g * f4)); new_p.b = static_cast<uint8_t>(round(p1.b * f1 + p2.b * f2 + p3.b * f3 + p4.b * f4)); } } // 更新 BMP 文件头信息 header.width = new_width; header.height = new_height; header.file_size = header.offset + new_width * new_height * sizeof(BMPImageData); header.data_size = new_width * new_height * sizeof(BMPImageData); // 写入缩放后的 BMP 图像 ofstream fout(output_file, ios::binary); fout.write(reinterpret_cast<const char*>(&header), sizeof(header)); fout.write(reinterpret_cast<const char*>(new_data), header.data_size); // 释放内存 delete[] data; delete[] new_data; cout << "文件 " << input_file << " 缩放完成,输出到 " << output_file << "。" << endl; } int main(int argc, char* argv[]) { if (argc != 4) { cerr << "用法:" << argv[0] << " <输入文件> <输出文件> <缩放比例>" << endl; return 1; } float scale = atof(argv[3]); if (scale <= 0) { cerr << "缩放比例必须大于零!" << endl; return 1; } scale_bmp_image(argv[1], argv[2], scale); return 0; } ``` 这段代码实现了 BMP 图片缩放功能,使用双线性插值算法进行缩放。你可以通过命令行参数指定输入文件、输出文件和缩放比例,例如: ```bash ./scale_bmp_image input.bmp output.bmp 0.5 ``` 这条命令会将输入文件 `input.bmp` 缩小一半,输出到文件 `output.bmp` 中。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值