使用标准IO对bitmap打马赛克
了解bmp格式图片的存储格式
bmp文件头(bmp file header): 提供文件的格式、大小等信息(14字节)
位图信息头(bitmap information): 提供图像数据的尺寸、位平面数、压缩方式、颜色索引等信息(40字节)
我们需要从图片文件中获取
bfOffBits :图片像素数据距离文件开头的偏移量,找到(0,0)位置的像素点数据的位置
biWidth :图片的宽度,以像素点为单位
biHeigh :图片的高度,以像素点为单位
biBitCount : 每个像素点数据的大小,以位为单位,因为下述程序只能处理,rgb888的图片,所以可以使用这个判断图片是否是rgb888的图片
实现过程
1. 打开文件
FILE* fp;
****
//以读和追加的方式打开文件
//open 打开成功则返回文件指针,失败返回NULL,并置位错误码
//若失败则使用perror打印错误信息,并return -1
if ((fp = fopen(argv[1], "r+")) == NULL){
perror("fopen error\n");
return -1;
}
2. 读取文件头的信息
//printf是用来查看数据有没有正常读出
//根据文件头数据格式读取实际像素数据的偏移量
fseek(fp, 4, SEEK_CUR);
fread(&bfOffBits, 4, 1, fp);
//printf("bfOffBit = %d\n", bfOffBits);
//由文件头格式读取图片的宽度
fseek(fp, 18, SEEK_SET);
fread(&biWidth, 4, 1, fp);
//printf("biWidth = %d\n", biWidth);
//由文件头格式读取图片的高度,因为宽度和高度存储的位置相邻,所有就不用移动光标的数据了
fread(&biHeigh, 4, 1, fp);
//printf("biHeigh = %d\n", biHeigh);
//由文件头格式读取图片像素点存储宽度
fseek(fp, 2, SEEK_CUR);
fread(&biBitCount, 2, 1, fp);
//printf("biBitCount = %d\n", biBitCount);
3. 将图片划分为若干方格
//将光标定位到图片像素数据
fseek(fp, bfOffBits, SEEK_SET);
//将图片划分为n*n的小格子
for (int i = 0; i < biHeigh / n ; i++) {
for (int j = 0; j < biWidth / n; j++) {
//将光标定位在第i行第j列的格子中第一像素点在文件中的存储位置
fseek(fp, (biWidth * i + j) * n * 3 + bfOffBits, SEEK_SET);
}
}
4. 读取方格中第一个像素点的信息
//像素点信息的结构体,本程序只能处理rgb888的bitmap
//b代表blue,g代表green,r代表red,分别用来存储对应颜色的值
// typedef struct {
// unsigned char b, g, r;
// } rgb_t;
rgb_t rgb = { 255, 255,255};
fread(&rgb, sizeo(rgb), 1, fp);
5. 将方格中第一个像素点的信息覆盖方格中其它像素点的信息
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
//第一个元素不用处理,且在读的时候光标向后移动了,因此不能要跳过第一个像素点
if (i == 0 && j == 0)
continue;
fwrite(&rgb, 3, 1, fp); //将光标所在的像素点置为第一个像素点
}
fseek(fp, (biWidth - n) * 3, SEEK_CUR); //将光标移动到下一行
}
效果展示
原图:
打过马赛克后:
源码
#include <head.h>
int bfOffBits = 0;
int biWidth = 0;
int biHeigh = 0;
int biBitCount = 0;
typedef struct {
unsigned char b, g, r;
} rgb_t;
int get_value(FILE* fp)
{
fseek(fp, 4, SEEK_CUR);
fread(&bfOffBits, 4, 1, fp);
//printf("bfOffBit = %d\n", bfOffBits);
fseek(fp, 18, SEEK_SET);
fread(&biWidth, 4, 1, fp);
//printf("biWidth = %d\n", biWidth);
fread(&biHeigh, 4, 1, fp);
//printf("biHeigh = %d\n", biHeigh);
fseek(fp, 2, SEEK_CUR);
fread(&biBitCount, 2, 1, fp);
//printf("biBitCount = %d\n", biBitCount);
if(biBitCount != 24){
printf("the bitmap is not rgb888\n");
return -1;
}
return 0;
}
//获取blocksize * blocksize方格中第一个像素点,然后将其它像素点都替换
void set_block_mosic(FILE* fp, int n, int m, int blocksize)
{
rgb_t rgb = {0,0,0};
fread(&rgb, sizeof(rgb), 1, fp);
for (int i = 0; i < blocksize; i++) {
for (int j = 0; j < blocksize; j++) {
if (i == 0 && j == 0)
continue;
fwrite(&rgb, sizeof(rgb), 1, fp); //将光标所在的像素点置为第一个像素点
}
fseek(fp, (biWidth - blocksize) * 3, SEEK_CUR); //将光标移动到下一行
}
}
void set_mosic(FILE* fp,int n)
{
fseek(fp, bfOffBits, SEEK_SET);
//将图片划分成一个个n * n 的小格
for (int i = 0; i < biHeigh / n ; i++) {
for (int j = 0; j < biWidth / n; j++) {
fseek(fp, (biWidth * i + j) * n * 3 + bfOffBits, SEEK_SET);
set_block_mosic(fp, i, j, n);
}
}
}
int main(int argc, const char* argv[])
{
FILE* fp;
int n = 1;
//以读和追加的方式打开文件
//open 打开成功则返回文件指针,失败返回NULL,并置位错误码
//若失败则使用perror打印错误信息,并return -1
if ((fp = fopen(argv[1], "r+")) == NULL)
PRINT_ERR("fopen error");
//若图片不是rgb888
if(get_value(fp))
return -1;
printf("请输入马赛克的宽度(以像素点为单位):");
scanf("%d",&n);
set_mosic(fp, n);
return 0;
}