引言
学校要求用C做个项目,我选择了BMP文件处理,BMP基本的文件格式我就不讲解了,网上都可以查到详细资料,现直接展示我的程序。
程序基本框架
先上个图,分为三个层
有两个.h头文件,一个是对BMP文件进行处理,一个是控制层代码
bmp.h
typedef struct
{
/*unsigned short bfType; */ //文件标识符
unsigned long bfSize; //文件大小
unsigned short bfReserved1; //保留值
unsigned short bfReserved2; //保留值
unsigned long bfOffBits; //文件头最后到图像数据的偏移量
} BitMapFileHeader;
/**
* 位图信息头
*/
typedef struct
{
unsigned long biSize; //信息头大小
long biWidth; //图像宽度
long biHeight; //图像高度
unsigned short biPlanes; //图像的位面数
unsigned short biBitCount; //每个像素的位数
unsigned long biCompression; //压缩类型
unsigned long biSizeImage; //图像的大小
long biXPelsPerMeter; //水平分辨率
long biYPelsPerMeter; //垂直分辨率
unsigned long biClrUsed; //使用的色彩数
unsigned long biClrImportant; //重要的颜色数
} BitMapInfoHeader;
/**
* 调色板
*/
typedef struct
{
unsigned char rgbBlue; //该颜色的蓝色分量
unsigned char rgbGreen; //该颜色的绿色分量
unsigned char rgbRed; //该颜色的红色分量
unsigned char rgbReserved; //保留值
} RgbQuad;
typedef struct
{
int width;
int height;
int channels;
unsigned long size;
unsigned char* data;
}Image;
Image *LoadingImage(const char* path); //加载图片
bool SaveImage(const char* path,Image* bmpImg); //保存图片
bool AddWaterMark(const char *path, Image *bmpImg); //加水印
bool Grayed(const char *path, Image *bmpImg); //变成灰白色
bool SharpenOrSmooth(int template[][3], const char *path, Image *bmpImg); //锐化或平移
control,h
char *UpdatePath(char *path, char *sub); //修改路径
void Menu(); //菜单
void AddWaterMarkControl(); //增加水印控制台
void GrayedControl(); //图片转换为黑白
void BasicInformation(); //图片基本信息
void SharpenControl(); //锐化图片
void SmoothControl(); //平移图片
读写
这个就是对BMP结构进行读写,要先了解下BMP的结构
Image *LoadingImage(const char *path) {
Image *img;
FILE *pf;
short fileType;
BitMapFileHeader bitMapFileHeader;
BitMapInfoHeader bitMapInfoHeader;
int channels = 1;
int width = 0;
int height = 0;
img = (Image *) malloc(sizeof(Image));
pf = fopen(path, "rb");
if (!pf) {
free(img);
fclose(pf);
return NULL;
}
//读取头文件名
fread(&fileType, sizeof(unsigned short), 1, pf);
//判断是否为24位文件
if (fileType == 0x4D42) {
fread(&bitMapFileHeader, sizeof(BitMapFileHeader), 1, pf);
fread(&bitMapInfoHeader, sizeof(BitMapInfoHeader), 1, pf);
channels = bitMapInfoHeader.biBitCount / 8;
width = bitMapInfoHeader.biWidth;
height = bitMapInfoHeader.biHeight;
img->width = width;
img->height = height;
img->channels = channels;
img->size = bitMapFileHeader.bfSize;
int line = (width * channels + 3) / 4 * 4;
if (channels == 1) {
RgbQuad *rgbQuad = (RgbQuad *) malloc(sizeof(RgbQuad) * 256);
fread(rgbQuad, sizeof(RgbQuad), 256, pf);
}
img->data = (unsigned char *) malloc(sizeof(unsigned char) * line * height);
fread(img->data, 1, line * height, pf);
fflush(pf);
fclose(pf);
}
return img;
}
bool SaveImage(const char *path, Image *bmpImg) {
if (!bmpImg)
return false;
FILE *pf;
unsigned short fileType;
BitMapFileHeader bitMapFileHeader;
BitMapInfoHeader bitMapInfoHeader;
int colorSize = 0;
if (bmpImg->channels == 1)
colorSize = 1024;
RgbQuad *rgbQuad;
pf = fopen(path, "wb");
if (!pf) {
fclose(pf);
return false;
}
fileType = 0x4D42;
fwrite(&fileType, sizeof(unsigned short), 1, pf);
int line = (bmpImg->width * bmpImg->channels + 3) / 4 * 4;
bitMapFileHeader.bfSize =
sizeof(BitMapInfoHeader) + sizeof(BitMapFileHeader) + colorSize + line * bmpImg->height + colorSize;
bitMapFileHeader.bfReserved1 = 0;
bitMapFileHeader.bfReserved2 = 0;
bitMapFileHeader.bfOffBits = 54 + colorSize;
fwrite(&bitMapFileHeader, sizeof(bitMapFileHeader), 1, pf);
bitMapInfoHeader.biSize = 40;
bitMapInfoHeader.biWidth = bmpImg->width;
bitMapInfoHeader.biHeight = bmpImg->height;
bitMapInfoHeader.biPlanes = 1;
bitMapInfoHeader.biBitCount = bmpImg->channels * 8;
bitMapInfoHeader.biSizeImage = line * bmpImg->height;
bitMapInfoHeader.biXPelsPerMeter = 0;
bitMapInfoHeader.biYPelsPerMeter = 0;
bitMapInfoHeader.biClrImportant = 0;
bitMapInfoHeader.biClrUsed = 0;
fwrite(&bitMapInfoHeader, sizeof(bitMapInfoHeader), 1, pf);
if (bitMapInfoHeader.biBitCount == 8)
fwrite(rgbQuad, sizeof(RgbQuad), 256, pf);
fwrite(bmpImg->data, line * bmpImg->height, 1, pf);
fclose(pf);
return true;
}
增加水印
我这里采取的方式是用水印图片的像素去替换要增加水印图片的像素。我选择的水印图片是白底黑字的,所以采取逻辑与运算,想水印的图片变为透明的。上代码
bool AddWaterMark(const char *path, Image *bmpImg) {
Image *water = LoadingImage("waterMark.bmp");
int step = (bmpImg->width * 24 / 8 + 3) / 4 * 4;
int Wstep = (water->width * 24 / 8 + 3) / 4 * 4;
for (int i = 0; i < water->height; ++i) {
for (int j = 0; j < water->width; ++j) {
//根据原图的长宽计算要插入水印的位置
int a = (int) (i + bmpImg->height / 1.2);
int b = (int) (j + bmpImg->width / 5.8);
for (int k = 0; k < 3; ++k) {
bmpImg->data[a * step + b * 3 + k] =
water->data[(i) * Wstep + j * 3 + k] & bmpImg->data[a * step + b * 3 + k];
}
}
}
SaveImage(path, bmpImg);
return true;
}
图片灰度
这里我是用的是:Gray=R0.299+G0.587+B*0.114。这个公式,不过也可以使用位运算。我是直接把Gray替换RGB数据,也可以写一个调色板然后直接把Gray的数据写入。
bool Grayed(const char *path, Image *bmpImg) {
Image *water = LoadingImage("waterMark.bmp");
int line = (bmpImg->width * bmpImg->channels + 3) / 4 * 4;
for (int i = 0; i < bmpImg->height; ++i) {
for (int j = 0; j < bmpImg->width; ++j) {
int t = bmpImg->data[i * line + j * 3 + 2] * 0.299 +
bmpImg->data[i * line + j * 3] * 0.114 +
bmpImg->data[i * line + j * 3 + 1] * 0.587;
for (int k = 0; k < 3; ++k) {
bmpImg->data[i * line + j * 3 + k] = t;
}
}
}
SaveImage(path, bmpImg);
return true;
}
平滑与锐化
平滑我用了平滑模板,锐化用了laplace算子 8 领域模板,控制层代码会看到
bool SharpenOrSmooth(int template[][3], const char *path, Image *bmpImg) {
Image *water = LoadingImage("waterMark.bmp");
int line = (bmpImg->width * bmpImg->channels + 3) / 4 * 4;
for (int i = 2; i < bmpImg->height - 1; ++i) {
for (int j = 2; j < bmpImg->width - 1; ++j) {
for (int k = 0; k < 3; ++k) {
int sum = 0;
for (int m = i - 1; m < i + 2; ++m) {
for (int n = j - 1; n < j + 2; ++n) {
sum += bmpImg->data[m * line + n * 3 + k] * template[n - j + 1][m - i + 1];
}
}
sum > 0 ? sum : 0;
sum > 255 ? 255 : sum;
bmpImg->data[i * line + j * 3 + k] = sum / 9;
}
}
}
SaveImage(path, bmpImg);
return true;
}
控制层
菜单
void Menu() {
printf(" ★☆★☆★☆★☆★☆★☆★☆★☆★☆★☆★☆★☆★☆★☆★☆★\n");
printf(" ☆**************************************************************************☆\n");
printf(" ★ BMP文件处理 ★\n");
printf(" ☆ ☆\n");
printf(" ★ 1.读取文件基本信息 ★ \n");
printf(" ☆ ☆\n");
printf(" ★ 2.向文件加水印 ★\n");
printf(" ☆ ☆\n");
printf(" ★ 3.将图片转换为黑白图片 ★\n");
printf(" ☆ ☆\n");
printf(" ★ 4.将图片平移 ★\n");
printf(" ☆ ☆\n");
printf(" ★ 5.将图片锐化 ★\n");
printf(" ☆ ☆\n");
printf(" ★***************************************************************************★\n");
printf(" ☆ 输入0退出系统 ☆\n");
printf(" ★---------------------------------------------------------------------------★\n\n");
}
更新路径
这个函数的作用是进行相应操作后生成图片的命名格式的更新。格式:原文件名+相应操作名
char *UpdatePath(char *path, char *sub) {
char *new = (char *) calloc(sizeof(char), 50);
char *cur = new;
while (*path != '\0') {
if (*(path + 1) == '.') {
*cur = *path;
path++;
cur++;
while (*sub != '\0') {
*cur = *sub;
sub++;
cur++;
}
}
*cur = *path;
path++;
cur++;
}
return new;
}
增加水印控制层
void AddWaterMarkControl() {
char path[50];
char *nPath;
printf("请输入文件路径:");
scanf("%s", path);
nPath = UpdatePath(path, "_mark");
Image *image = LoadingImage(path);
if (image == NULL)
printf("输入文件路径有误!\n");
else {
if (AddWaterMark(nPath, image))
printf("水印添加成功!\n");
else
printf("水印添加失败!\n");
}
Sleep(1500);
system("cls");
}
灰度控制层
void GrayedControl() {
char path[50];
char *nPath;
printf("请输入文件路径:");
scanf("%s", path);
nPath = UpdatePath(path, "_grayed");
Image *image = LoadingImage(path);
if (image == NULL)
printf("输入文件路径有误!\n");
else {
if (Grayed(nPath, image))
printf("图片转换为黑白成功\n");
else
printf("图片转化为黑白失败\n");
}
Sleep(1500);
system("cls");
}
锐化控制层
void SharpenControl() {
char path[50];
char *nPath;
int Template[3][3] = {1, 1, 1, 1, -9, 1, 1, 1, 1};
printf("请输入文件路径:");
scanf("%s", path);
nPath = UpdatePath(path, "_sharpen");
Image *image = LoadingImage(path);
if (image == NULL)
printf("输入文件路径有误!\n");
else {
if (SharpenOrSmooth(Template, nPath, image))
printf("图片已完成锐化\n");
else
printf("图片锐化失败\n");
}
Sleep(1500);
system("cls");
}
平移控制层
void SmoothControl() {
char path[50];
char *nPath;
int Template[3][3] = {1, 1, 1, 1, 1, 1, 1, 1, 1};
printf("请输入文件路径:");
scanf("%s", path);
nPath = UpdatePath(path, "_smooth");
Image *image = LoadingImage(path);
if (image == NULL)
printf("输入文件路径有误!\n");
else {
if (SharpenOrSmooth(Template, nPath, image))
printf("图片已完成平移\n");
else
printf("图片平移失败失败\n");
}
Sleep(1500);
system("cls");
}
基本信息控制层
打印图片的基本数据,大小,分辨率,为主。大小可以自动调整为kb,mb
void BasicInformation() {
char path[50];
printf("请输入文件路径:");
scanf("%s", path);
Image *image = LoadingImage(path);
if (image == NULL)
printf("\n输入文件路径有误!\n\n");
else {
double size = (double) image->size;
if (size > 1024 && size < 1024 * 1024) {
size = (double) image->size / 1024;
printf("\n图片大小:%.2lf KB\n\n", size);
} else {
size = (double) image->size / 1024 / 1024;
printf("\n图片大小:%.2lf MB\n\n", size);
}
printf("图片分辨率:%d*%d\n\n", image->width, image->height);
printf("图片位数:%d位\n\n", image->channels * 8);
}
system("pause");
system("cls");
}
main 函数
基本代码写完了,下面附上主函数
int main() {
system("mode con cols=100 lines=25");
int Input;
do {
Menu();
printf(" ");
scanf("%d", &Input);
switch (Input) {
case 1:
system("cls");
BasicInformation();
break;
case 2:
system("cls");
AddWaterMarkControl();
break;
case 3:
system("cls");
GrayedControl();
break;
case 4:
system("cls");
SmoothControl();
break;
case 5:
system("cls");
SharpenControl();
break;
case 0:
return 0;
default:
printf("请输入合适的数字!\n");
Sleep(1500);
system("cls");
}
} while (1);
}
效果
下面附一下效果图
原图
灰度
平滑
加水印
文件下的生成的图片
总结
好了,就分享到这里了。这个小项目也学到了很多东西。
1、学习了BMP文件的格式,对其内部有了深入的了解。
2、学习了两个图片进行逻辑运算可以得到不同的效果。这样大大加快了转换速率。
3、了解了laplace算子,使用其模板,从而实现锐化、平移等效果。