Linux下图片处理

时间:2023-01-14 14:59:21

Linux下图片处理

      图片是指由图形、图像等构成的平面媒体。图片的格式很多,但总体上可以分为点阵图和矢量图两大类,我们常用BMP、JPG等格式都是点阵图形,而SWF、CDR、AI等格式的图形属于矢量图形。

有形式的事物,我们看到的,是图画、照片、拓片等的统称。图是技术制图中的基础术语,指用点、线、符号、文字和数字等描绘事物几何特征、形态、位置及大小的一种形式。随着数字采集技术和信号处理理论的发展,越来越多的图片以数字形式存储。

1.常见的图片格式

在嵌入式开发中,常用的图片格式有BMP、JPG/JPEG、PNG等。


  • BMP图片

位图(外语简称:BMP、外语全称:Bitmap),它是Windows操作系统中的标准图像文件格式,能够被多种Windows应用程序所支持。随着Windows操作系统的流行与丰富的Windows应用程序的发,BMP位图格式理所当然地被广泛应用。这种格式的特点是包含的图像信息较丰富,几乎不进行压缩,但由此导致了它与生俱来的缺点——占用磁盘空间过大,所以,BMP在单机上比较流行。

BMP图片格式简单,在不压缩处理下可以最好的保存图片原始颜色格式。


  • JPG图片

JPEG也是最常见的一种图像格式,它是由联合照片专家组(外语全称:Joint Photographic Experts Group)开发并以命名为“ISO 10918-1”,JPEG仅仅是一种俗称而已。JPEG文件的扩展名为.jpg或.jpeg,因其压缩技术十分先进,它用有损压缩方式去除冗余的图像和彩色数据,获取得极高的压缩率的同时能展现十分丰富生动的图像,换句话说,就是可以用最少的磁盘空间得到较好的图像质量。

同时JPEG还是一种很灵活的格式,具有调节图像质量的功能,允许你用不同的压缩比例对这种文件压缩,比如我们最高可以把1.38MB的BMP位图文件压缩至20.3KB。当然我们完全可以在图像质量和文件尺寸之间找到平衡点。

由于JPEG优异的品质和杰出的表现,特别是在网络和光盘读物上,肯定都能找到它的影子。各类浏览器均支持JPEG这种图像格式,因为JPEG格式的文件尺寸较小,下载速度快,使得Web页有可能以较短的下载时间提供大量美观的图像,JPEG同时也就顺理成章地成为网络上最受欢迎的图像格式。


PNG格式

便携式网络图形(外语简称PNG、外语全称:Portable Network Graphics)是一种新兴的网络图像格式。在1994年底,由于Unysis公司宣布GIF拥有专利的压缩方法,要求开发GIF软件的作者须缴交一定费用,由此促使免费的png图像格式的诞生。PNG一开始便结合GIF及JPG两家之长,打算一举取代这两种格式。1996年10月1日由PNG向国际网络联盟提出并得到推荐认可标准,并且大部分绘图软件和浏览器开始支持PNG图像浏览,从此PNG图像格式生机焕发。

PNG是保证最不失真的格式,它汲取了GIF和JPG二者的优点,存贮形式丰富,兼有GIF和JPG的色彩模式;它的另一个特点能把图像文件压缩到极限以利于网络传输,但又能保留所有与图像品质有关的信息,因为PNG是采用无损压缩方式来减少文件的大小,这一点与牺牲图像品质以换取高压缩率的JPG有所不同;它的第三个特点是显示速度很快,只需下载1/64的图像信息就可以显示出低分辨率的预览图像;第四,PNG同样支持透明图像的制作,透明图像在制作网页图像的时候很有用,我们可以把图像背景设为透明,用网页本身的颜色信息来代替设为透明的色彩,这样可让图像和网页背景很和谐地融合在一起。

PNG的缺点是不支持动画应用效果,如果在这方面能有所加强,简直就可以完全替代GIF和JPEG了。

2.BMP图片处理

BMP图片格式简单,可以不需要借助任何第三方库即可完成图片的各种操作处理,接下来将以C语言实现BMP图片的旋转。

2.1 BMP图片旋转处理

  • 原始图片如下

Linux下图片处理

  • 顺时针旋转90°效果:

Linux下图片处理

  • 逆时针旋转90°效果:

Linux下图片处理

  •  顺时针旋转90°示例
/***********************顺时针旋转90°**************************
**
**形参:const char *new_bmp -- 顺时针90°后图片
** const char *befor_bmp --原始图片
**返回值:0 -- 成功,其他值 -- 失败
**************************************************************/
int BMP_ClockWise_Revolve90(const char *new_bmp,const char *befor_bmp)
{
FILE *fp[2];
fp[0]=fopen(befor_bmp,"rb");
if(fp[0]==NULL)
{
printf("[%s line %d]文件打开失败",__FUNCTION__,__LINE__);
return 1;
}
fp[1]=fopen(new_bmp,"w+b");
if(fp[1]==NULL)
{
printf("[%s line %d]文件打开或创建失败",__FUNCTION__,__LINE__);
return 2;
}
BMP_HEADER bmp_head;
BMP_INFO bmp_info;
fread(&bmp_head,sizeof(BMP_HEADER),1,fp[0]);//读取头数据
if(bmp_head.bfType!=0x4d42)
{
printf("[%s line %d]图片格式错误\n",__FUNCTION__,__LINE__);
fclose(fp[0]);
fclose(fp[1]);
return 3;
}
fwrite(&bmp_head,sizeof(BMP_HEADER),1,fp[1]);//头数据写入到新的文件中
int w,h;//旋转90°宽和高需要互换
fread(&bmp_info,sizeof(BMP_INFO),1,fp[0]);//读取位图数据
h=bmp_info.biHeight;
w=bmp_info.biWidth;
bmp_info.biWidth=h;//旋转后图片宽度
bmp_info.biHeight=w;//旋转后图片高度
fwrite(&bmp_info,sizeof(BMP_INFO),1,fp[1]);//写入位图数据
printf("\n--------------------顺时针旋转90°----------------------\n");
printf("\t旋转后图片宽:%d\n",bmp_info.biWidth);
printf("\t旋转后图片高:%d\n",bmp_info.biHeight);
int befor_oneline_size=w*3;//之前图片一行的字节数
while(befor_oneline_size%4)befor_oneline_size++;//按4字节对齐
int new_oneline_size=bmp_info.biWidth*3;//旋转后图片一行字节数
while(new_oneline_size%4)new_oneline_size++;//按4字节对齐
int i,j;
int offset_count=0;
int rgb=0;
for(i=w-1;i>=0;i--)
{
for(j=0;j<h;j++)
{
offset_count=j*befor_oneline_size+i*3+bmp_head.bfOffBits;//从第一行的最后一个像素点开始
fseek(fp[0],offset_count,SEEK_SET);
fread(&rgb,bmp_info.biBitCount/8,1,fp[0]);
fwrite(&rgb,bmp_info.biBitCount/8,1,fp[1]);
}
if(new_oneline_size>bmp_info.biWidth*3)
{
fwrite(&rgb,new_oneline_size-bmp_info.biWidth*3,1,fp[1]);//补全为4的倍数
}
}
fclose(fp[0]);
fclose(fp[1]);
return 0;
}
  •  逆时针旋转90°示例
/***********************逆时针旋转90°**************************
**
**形参:const char *new_bmp -- 逆时针90°后图片
** const char *befor_bmp --原始图片
**返回值:0 -- 成功,其他值 -- 失败
**************************************************************/
int BMP_antiClockWise_Revolve90(const char *new_bmp,const char *befor_bmp)
{
FILE *fp[2];
fp[0]=fopen(befor_bmp,"rb");
if(fp[0]==NULL)
{
printf("[%s line %d]文件打开失败",__FUNCTION__,__LINE__);
return 1;
}
fp[1]=fopen(new_bmp,"w+b");
if(fp[1]==NULL)
{
printf("[%s line %d]文件打开或创建失败",__FUNCTION__,__LINE__);
return 2;
}
BMP_HEADER bmp_head;
BMP_INFO bmp_info;
fread(&bmp_head,sizeof(BMP_HEADER),1,fp[0]);//读取头数据
if(bmp_head.bfType!=0x4d42)
{
printf("[%s line %d]图片格式错误\n",__FUNCTION__,__LINE__);
fclose(fp[0]);
fclose(fp[1]);
return 3;
}
fwrite(&bmp_head,sizeof(BMP_HEADER),1,fp[1]);//头数据写入到新的文件中
int w,h;//旋转90°宽和高需要互换
fread(&bmp_info,sizeof(BMP_INFO),1,fp[0]);//读取位图数据
h=bmp_info.biHeight;
w=bmp_info.biWidth;
bmp_info.biWidth=h;//旋转后图片宽度
bmp_info.biHeight=w;//旋转后图片高度
fwrite(&bmp_info,sizeof(BMP_INFO),1,fp[1]);//写入位图数据
printf("\n--------------------逆时针旋转90°----------------------\n");
printf("\t旋转后图片宽:%d\n",bmp_info.biWidth);
printf("\t旋转后图片高:%d\n",bmp_info.biHeight);
int befor_oneline_size=w*3;//之前图片一行的字节数
while(befor_oneline_size%4)befor_oneline_size++;//按4字节对齐
int new_oneline_size=bmp_info.biWidth*3;//旋转后图片一行字节数
while(new_oneline_size%4)new_oneline_size++;//按4字节对齐
int i,j;
int offset_count=0;
int rgb=0;
int cnt=0;
unsigned char buff[new_oneline_size];//存放新图片一行字节数
for(i=0;i<w;i++)
{
cnt=0;
for(j=h-1;j>=0;j--)
{
//先读取最后一行的第一个像素点
offset_count=bmp_head.bfOffBits+i*3+j*befor_oneline_size;
fseek(fp[0],offset_count,SEEK_SET);
fread(&rgb,3,1,fp[0]);//读取一个像素点数据
buff[cnt++]=(rgb)&0xff;
buff[cnt++]=(rgb>>8)&0xff;
buff[cnt++]=(rgb>>16)&0xff;
}
fwrite(buff,cnt,1,fp[1]);//将一行颜色数据写入到新文件中
if(cnt!=new_oneline_size)//补全为4的整数倍
{
rgb=0;
fwrite(&rgb,new_oneline_size-cnt,1,fp[1]);
}
}
fclose(fp[0]);
fclose(fp[1]);
return 0;
}

2.2 BMP图片水印添加

  • 添加水印效果:

Linux下图片处理

水印添加实现原理:先获取图片大小,然后通过内存映射方式(mmap函数)将文件映射到内存中,接下来对内存的操作即为对文件的操作,内存地址偏移到BMP图的RGB颜色位置。将要显示的汉字加载到RGB颜色中即可。其中实现水印添加的核心函数为画点函数,实现如下:

/*
画点函数
形参:x,y --要绘制的位置
color --颜色值
*/
void Image_DrawPoint(int x,int y,int color)
{
unsigned char *p=(height-y-1)*width*3+x*3+image;//光标位置
/*写入rgb颜色*/
*p=color&0xff;
*(p+1)=(color>>8)&0xff;
*(p+2)=(color>>16)&0xff;
}

实现画点函数之后,即可调用汉字字库(点阵字库或者矢量字库均可),这里以点阵字库为例,按照点阵字库的算法将要显示的汉字点阵码取出,再调用画点函数完成汉字绘制,实现如下:

/*显示汉字*/
void Image_DisplayFont(int x,int y,int size,int color,const unsigned char *font)
{
u8 *p=NULL;
u8 H,L;
u32 addr=0;//汉字偏移地址
u16 font_size=size*size/8;//汉字点阵大小(宽度保证为8的倍数)
H=*font;//汉字高字节
L=*(font+1);//汉字的低字节
if(L<0x7F)L-=0x40;
else L-=0x41;
H-=0x81;
addr=(190*H+L)*font_size;//汉字所在点阵中的偏移地址
p=malloc(font_size);
if(p==NULL)
{
printf("申请空间失败\r\n");
return ;
}
if(size == 32)//GBK_32
{
fseek(font_fp, addr, SEEK_SET);//从文件头开始偏移
fread(p,font_size,1,font_fp);
}
else
{
free(p);//释放空间
return ;
}
int i=0,j=0;
int x0=x;
unsigned char temp;
for(i=0;i<size*size/8;i++)//一个汉字的字节数
{
temp=p[i];//取一个字节
for(j=0;j<8;j++)//一个字节8位
{
if(temp&0x80)
{
Image_DrawPoint(x0,y,color);
}
temp<<=1;//继续描绘下一个点
x0++;
}
if(x0-x == size)
{
x0=x;
y++;
}
}
free(p);//释放空间
}

有了上述函数,即可实现任意字符串的水印添加(要添加ascii码则需要进一步添加ascii的点阵码信息即可)。

2.3 BMP图片的结构体定义格式

BMP图片相比于其它格式图片要简单得多,格式定义为两个结构体,固定大小为54个字节,机构体定义如下:

#pragma pack(1)  /* 必须在结构体定义之前使用,这是为了让结构体中各成员按1字节对齐*/
/*图片头*/
typedef struct BitMapFileHEADER
{
unsigned short bfType; //保存图片类型。 'BM'
unsigned int bfSize; //图片文件的总大小,以字节为单位(3-6字节,低位在前)
unsigned short bfReserved1;//位图文件保留字,必须为0(7-8字节)
unsigned short bfReserved2;//位图文件保留字,必须为0(9-10字节)
unsigned int bfOffBits; //RGB数据偏移地址,位图数据的起始位置,以相对于位图(11-14字节,低位在前)//文件头的偏移量表示,以字节为单位
}BMP_HEADER;

/*图片信息*/
typedef struct BitMapFileInfo{
unsigned int biSize; //本结构所占用字节数(15-18字节)
unsigned int biWidth; //位图的宽度,以像素为单位(19-22字节)
unsigned int biHeight; //位图的高度,以像素为单位(23-26字节)
unsigned short biPlanes; //目标设备的级别,必须为1(27-28字节)
unsigned short biBitCount; //每个像素所需的位数,必须是1(双色)(29-30字节),4(16色),8(256色)16(高彩色)或24(真彩色)之一
unsigned int biCompression;//位图压缩类型,必须是0(不压缩),(31-34字节)
//1(BI_RLE8压缩类型)或2(BI_RLE4压缩类型)之一
unsigned int biSizeImage; //位图的大小(其中包含了为了补齐行数是4的倍数而添加的空字节),以字节为单位(35-38字节)
unsigned int biXPelsPerMeter;//位图水平分辨率,每米像素数(39-42字节)
unsigned int biYPelsPerMeter;//位图垂直分辨率,每米像素数(43-46字节)
unsigned int biClrUsed; //位图实际使用的颜色表中的颜色数(47-50字节)
unsigned int biClrImportant; //位图显示过程中重要的颜色数(51-54字节)
}BMP_INFO;
#pragma pack()