Linux开发_BMP图片编程(翻转、添加水印)

237 阅读6分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第31天,点击查看活动详情

这篇文章主要是以实际案例拓展来理解BMP图片编程,理解BMP图片如何进行编程操作。实现文字水印添加、图像翻转、图像缩放等操作。

一、摄像头编程扩展

【1】改进拍照程序,至少BMP图片数据要正常,还可以将.c文件分隔成3个.c文件。

24位的真彩色BMP图片的构造:分为3个部分。

(1) 存放BMP头数据—主要存放BMP属性,RGB数据偏移量

(2) 存放BMP信息数据—存放宽度、高度、颜色位数

(3) 存放BMP图片源数据—RGB888

注意:

(1) BMP图片一行(宽度)的数据必须是4的倍数。

(2) BMP图片的源数据是从下到上,从左到右

BMP、JPG、PNG、MP4、MP3

【2】扩展案例: 拍照图片上加水印。比如:时间水印、或者自己的名字

(1) 第一种图片水印: 图片重叠。

(2) 在控制台终端屏幕上使用*号打印自己名字。

要求:

(1) 添加文字水印和数字水印。比如: 添加自己的名字和日期

(2) 实现任意位置添加任意的水印。

(3) 实现缩放功能,将图片可以任意放大和缩小。

【3】精简远程摄像头监控的代码文件。

将网页监控相关的代码文件单独提取出来进行编译,实现远程监控的效果。

使用gcc单独编译实现。(uvc)

分析: 程序是依靠Makefile文件已经编译。首先从Makefile文件入手。

主要修改的文件:mjpg_streamer.c

【A】改进拍照程序,至少BMP图片数据要正常,还可以将.c文件分隔成3个.c文件。

24位的真彩色BMP图片的构造:分为3个部分。

(1) 存放BMP头数据—主要存放BMP属性,RGB数据偏移量

(2) 存放BMP信息数据—存放宽度、高度、颜色位数

(3) 存放BMP图片源数据—RGB888

注意:

(1) BMP图片一行(宽度)的数据必须是4的倍数。

(2) BMP图片的源数据是从下到上,从左到右

BMP、JPG、PNG、MP4、MP3

【B】扩展知识: 拍照图片上加水印。比如:时间水印、或者自己的名字

(1) 第一种图片水印: 图片重叠。

(2) 在控制台终端屏幕上使用*号打印自己名字。

img

要求:

(1) 添加文字水印和数字水印。比如: 添加自己的名字和日期

(2) 实现任意位置添加任意的水印。

(3) 实现缩放功能,将图片可以任意放大和缩小。

【C】精简远程摄像头监控的代码文件。

将网页监控相关的代码文件单独提取出来进行编译,实现远程监控的效果。

使用gcc单独编译实现。(uvc)

分析: 程序是依靠Makefile文件已经编译。首先从Makefile文件入手。

二、汉字库的制作与使用

img

需要解决的问题:

【1】如何从汉字库里提取自己想要的点阵数据 。比如: 小龙哥

汉字库编码还是GBK编码: 有特定的公式可以计算点阵码的位置。

【2】如何区分中文还有英文

数据大于0x80就是中文,其他就是英文数据。 变量必须是: 无符号类型

注意: 写代码需要在中文编码的情况下编写!

【3】英文数据该如何取模?

从空格开始到~号结束 ,连续95个数据。

三、BMP图片案例代码

3.1 BMP图片缩放

 #include <stdio.h>
 #include <string.h>
 ​
 int PicZoom(unsigned char *s_buff,unsigned int s_width,unsigned int s_height,unsigned char *buff,unsigned int width,unsigned int height);
 void *my_memcpy(void *v_dst,const void *v_src,unsigned char c);
 ​
 #pragma pack(1)   /* 必须在结构体定义之前使用,这是为了让结构体中各成员按1字节对齐 */
 /*需要文件信息头:14个字节 */
 struct BITMAPFILEHEADER
 {
     unsigned short bfType;      //保存图片类似。 'BM'
     unsigned long  bfSize;      //图片的大小
     unsigned short bfReserved1;
     unsigned short bfReserved2;
     unsigned long  bfOffBits;  //RGB数据偏移地址
 };
 ​
 /* 位图信息头 */
 struct BITMAPINFOHEADER { /* bmih */
     unsigned long  biSize;      //结构体大小
     unsigned long  biWidth;     //宽度
     unsigned long  biHeight;    //高度
     unsigned short biPlanes;
     unsigned short biBitCount;  //颜色位数
     unsigned long  biCompression;
     unsigned long  biSizeImage;
     unsigned long  biXPelsPerMeter;
     unsigned long  biYPelsPerMeter;
     unsigned long  biClrUsed;
     unsigned long  biClrImportant;
 };
 ​
 #define NEW_FILE_NAME "new.bmp" //缩放后的新图片名称
 #define SRC_FILE_NAME "src.bmp" //源图片名称
 ​
 ​
 /*
 图片放大与缩小示例
 */
 int main()
 {
     struct BITMAPFILEHEADER src_head;  //源文件头数据
     struct BITMAPINFOHEADER src_info;  //源文件参数结构
     struct BITMAPFILEHEADER new_head;  //新文件头数据
     struct BITMAPINFOHEADER new_info;  //新文件参数结构
     unsigned int new_Width;            //缩放后的宽度
     unsigned int new_Height;           //缩放后的高度
     unsigned char *new_buff;           //存放新图片的数据
     unsigned char *src_buff;           //存放源图片的数据
     unsigned int cnt=0;
 ​
     /*1. 打开图片文件*/
     FILE *src_file=fopen(SRC_FILE_NAME,"rb");
     FILE *new_file=fopen(NEW_FILE_NAME,"wb");
     if(src_file==NULL||new_file==NULL)
     {
         printf("%s 源文件打开失败!\r\n",SRC_FILE_NAME);
         return;
     }
     
     /*2. 读取源图片参数*/
     fread(&src_head,sizeof(struct BITMAPFILEHEADER),1,src_file);
     fread(&src_info,sizeof(struct BITMAPINFOHEADER),1,src_file);
     printf("源图片尺寸:w=%d h=%d\r\n",src_info.biWidth,src_info.biHeight);
 ​
     /*3. 获取新图片的尺寸*/
     printf("输入新图片的宽度: ");
     scanf("%d",&new_Width);
     printf("输入新图片的高度: ");
     scanf("%d",&new_Height);
     printf("新图片尺寸:w=%d h=%d\r\n",new_Width,new_Height);
 ​
     /*4. 申请存放图片数据的空间*/
     src_buff=malloc(src_info.biWidth*src_info.biHeight*3);
     new_buff=malloc(new_Width*new_Height*3);
     if(new_buff==NULL||src_buff==NULL)
     {
         printf("malloc申请空间失败!\r\n");
         return -1;
     }
 ​
     /*5. 读取源图片RGB数据*/
     fseek(src_file,src_head.bfOffBits,SEEK_SET); //移动文件指针到RGB数据位置
     fread(src_buff,1,src_info.biWidth*src_info.biHeight*3,src_file); //读取源数据
 ​
     /*6. 缩放图片*/
     if(PicZoom(src_buff,src_info.biWidth,src_info.biHeight,new_buff,new_Width,new_Height))
     {
         printf("图片缩放处理失败!\r\n");
         return -1;
     }
 ​
     /*7. 写入新图片数据*/
     //填充文件头
     memset(&new_head,0,sizeof(struct BITMAPFILEHEADER));
     new_head.bfType=0x4d42;
     new_head.bfSize=54+new_Width*new_Height*3;
     new_head.bfOffBits=54;
     //填充文件参数
     memset(&new_info,0,sizeof(struct BITMAPINFOHEADER));
     new_info.biSize=sizeof(struct BITMAPINFOHEADER);
     new_info.biWidth=new_Width;
     new_info.biHeight=new_Height;
     new_info.biPlanes=1;
     new_info.biBitCount=24;
     //写入文件数据
     fwrite(&new_head,sizeof(struct BITMAPFILEHEADER),1,new_file);
     fwrite(&new_info,sizeof(struct BITMAPINFOHEADER),1,new_file);
     fseek(new_file,new_head.bfOffBits,SEEK_SET); //移动文件指针到RGB数据位置
     cnt=fwrite(new_buff,1,new_info.biWidth*new_info.biHeight*3,new_file); //写数据
 ​
     /*8. 关闭图片文件*/
     fclose(new_file);
     fclose(src_file);
 ​
     printf("%s 新图片创建成功! 路径:程序运行路径下\r\n",NEW_FILE_NAME);
     return 0;
 }
 ​
 ​
 /**********************************************************************
 * 函数名称: PicZoom
 * 功能描述: 近邻取样插值方法缩放图片
 *            注意该函数会分配内存来存放缩放后的图片,用完后要用free函数释放掉
 *            "近邻取样插值"的原理请参考网友"lantianyu520"所著的"图像缩放算法"
 * 输入参数:  ptOriginPic - 内含原始图片的象素数据
 *             ptZoomPic    - 内含缩放后的图片的象素数据
 * 输出参数: 无
 * 返 回 值: 0 - 成功, 其他值 - 失败
 ***********************************************************************/
 int PicZoom(unsigned char *ptOriginPic_aucPixelDatas,unsigned int ptOriginPic_iWidth,unsigned int ptOriginPic_iHeight,unsigned char *ptZoomPic_aucPixelDatas,unsigned int ptZoomPic_iWidth,unsigned int ptZoomPic_iHeight)
 {
     unsigned int ptOriginPic_iLineBytes=ptOriginPic_iWidth*3; //一行的字节数
     unsigned int ptZoomPic_iLineBytes=ptZoomPic_iWidth*3;  //一行的字节数
 ​
     unsigned long dwDstWidth=ptZoomPic_iWidth;
     unsigned long* pdwSrcXTable;
     unsigned long x;
     unsigned long y;
     unsigned long dwSrcY;
     unsigned char *pucDest;
     unsigned char *pucSrc;
     unsigned long dwPixelBytes=3; //像素字节
     pdwSrcXTable=malloc(sizeof(unsigned long) * dwDstWidth);
     if(NULL==pdwSrcXTable)
     {
         return -1;
     }
 ​
     for(x=0; x < dwDstWidth; x++)//生成表 pdwSrcXTable
     {
         pdwSrcXTable[x]=(x*ptOriginPic_iWidth/ptZoomPic_iWidth);
     }
 ​
     for(y=0; y < ptZoomPic_iHeight; y++)
     {
         dwSrcY=(y * ptOriginPic_iHeight/ptZoomPic_iHeight);
 ​
         pucDest=ptZoomPic_aucPixelDatas + y * ptZoomPic_iLineBytes;
         pucSrc=ptOriginPic_aucPixelDatas+dwSrcY * ptOriginPic_iLineBytes;
 ​
         for(x=0; x <dwDstWidth; x++)
         {
             my_memcpy(pucDest+x*dwPixelBytes,pucSrc+pdwSrcXTable[x]*dwPixelBytes,dwPixelBytes);
         }
     }
 ​
     free(pdwSrcXTable);
     return 0;
 }
 ​
 /*
 函数功能:内存拷贝函数
 */
 void *my_memcpy(void *v_dst,const void *v_src,unsigned char c)
 {
     const char *src=v_src;
     char *dst=v_dst;
     while(c--)
         *dst++=*src++;
     return v_dst;
 }

3.2 BMP图片添加水印

 #include "savebmp.h"
 #include "yuvtorgb.h"
 ​
 /* 图片的象素数据 */
 typedef struct PixelDatas {
     int iWidth;      /* 宽度: 一行有多少个象素 */
     int iHeight;     /* 高度: 一列有多少个象素 */
     int iBpp;        /* 一个象素用多少位来表示 */
     int iLineBytes;  /* 一行数据有多少字节 */
     int iTotalBytes; /* 所有字节数 */ 
     unsigned char *VideoBuf; //存放一帧摄像头的数据
     //指向了存放摄像头数据的空间地址
 }T_PixelDatas;
 ​
 T_PixelDatas Pixedata; //存放实际的图像数据
 ​
 /*
         USB摄像头相关参数定义
 */
 struct v4l2_buffer tV4l2Buf;
 int iFd;
 int ListNum;
 unsigned char* pucVideBuf[4];  // 视频BUFF空间地址
 void camera_pthread(void);
 ​
 int main(int argc ,char *argv[])
 {
     if(argc!=2)
     {
         printf("./app /dev/videoX\n");
         return -1;
     }
     
     camera_init(argv[1]);  //摄像头设备初始化
 ​
     //开始采集摄像头数据,并编码保存为BMP图片
     camera_pthread();
     return 0;
 }
 ​
 ​
 ​
 ​
 //摄像头设备的初始化
 int camera_init(char *video)
 {
     int i=0;
     int cnt=0;
     //定义摄像头驱动的BUF的功能捕获视频
     int iType = V4L2_BUF_TYPE_VIDEO_CAPTURE;
     
     /* 1、打开视频设备 */
     iFd = open(video,O_RDWR);
     if(iFd < 0)
     {
         printf("摄像头设备打开失败!\n");
         return 0;
     }
     
     struct v4l2_format  tV4l2Fmt;
     
     /* 2、 VIDIOC_S_FMT 设置摄像头使用哪种格式 */
     memset(&tV4l2Fmt, 0, sizeof(struct v4l2_format));
     tV4l2Fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; //视频捕获
 ​
     //设置摄像头输出的图像格式
     tV4l2Fmt.fmt.pix.pixelformat=V4L2_PIX_FMT_YUYV;
 ​
     /*设置输出的尺寸*/
     tV4l2Fmt.fmt.pix.width       = 640;
     tV4l2Fmt.fmt.pix.height      = 480;
     tV4l2Fmt.fmt.pix.field       = V4L2_FIELD_ANY;
     
     //VIDIOC_S_FMT 设置摄像头的输出参数
     ioctl(iFd, VIDIOC_S_FMT, &tV4l2Fmt); 
 ​
     //打印摄像头实际的输出参数
     printf("Support Format:%d\n",tV4l2Fmt.fmt.pix.pixelformat);
     printf("Support width:%d\n",tV4l2Fmt.fmt.pix.width);
     printf("Support height:%d\n",tV4l2Fmt.fmt.pix.height);
 ​
     /* 3、VIDIOC_REQBUFS  申请buffer */
     /* 初始化Pixedata结构体,为转化做准备 */
     Pixedata.iBpp =24;                              
     //高度 和宽度的赋值
     Pixedata.iHeight = tV4l2Fmt.fmt.pix.height;
     Pixedata.iWidth = tV4l2Fmt.fmt.pix.width;
 ​
     //一行所需要的字节数
     Pixedata.iLineBytes = Pixedata.iWidth*Pixedata.iBpp/8;
     //一帧图像的字节数
     Pixedata.iTotalBytes = Pixedata.iLineBytes * Pixedata.iHeight;
     
     Pixedata.VideoBuf=malloc(Pixedata.iTotalBytes); //申请存放图片数据空间
 ​
     //v412请求命令
     struct v4l2_requestbuffers tV4l2ReqBuffs;
     
     memset(&tV4l2ReqBuffs, 0, sizeof(struct v4l2_requestbuffers));
 ​
     /* 分配4个buffer:实际上由VIDIOC_REQBUFS获取到的信息来决定 */
     tV4l2ReqBuffs.count   = 4; /*在内核空间里开辟4个空间*/
     
     /*支持视频捕获功能*/
     tV4l2ReqBuffs.type    = V4L2_BUF_TYPE_VIDEO_CAPTURE;
     
     /* 表示申请的缓冲是支持MMAP(内存映射) */
     tV4l2ReqBuffs.memory  = V4L2_MEMORY_MMAP;
     
     /* 为分配buffer做准备 */
     ioctl(iFd, VIDIOC_REQBUFS, &tV4l2ReqBuffs);
     
     for (i = 0; i < tV4l2ReqBuffs.count; i++) 
     {
         memset(&tV4l2Buf, 0, sizeof(struct v4l2_buffer));
         tV4l2Buf.index = i; // 0  1 2 3
         tV4l2Buf.type   = V4L2_BUF_TYPE_VIDEO_CAPTURE;  /*支持视频捕获*/
         tV4l2Buf.memory = V4L2_MEMORY_MMAP;  /*支持内存映射*/
 ​
         /* 6、VIDIOC_QUERYBUF 确定每一个buffer的信息 并且 mmap */
         ioctl(iFd, VIDIOC_QUERYBUF, &tV4l2Buf);
         //映射空间地址
         pucVideBuf[i] = mmap(         /*返回用户空间的地址*/
                   0,                  /*表示系统自己制定地址*/
                   tV4l2Buf.length,   /*映射的长度*/
                   PROT_READ,          /*空间数据只读*/
                   MAP_SHARED,         /*空间支持共享*/
                   iFd,                /*将要映射的文件描述符*/
                   tV4l2Buf.m.offset  /*从哪个位置开始映射,表示起始位置*/
                   );
         printf("mmap %d addr:%p\n",i,pucVideBuf[i]);
     }
 ​
     /* 4、VIDIOC_QBUF  放入队列*/
     for (i = 0; i <tV4l2ReqBuffs.count; i++) 
     {
         memset(&tV4l2Buf, 0, sizeof(struct v4l2_buffer));
         tV4l2Buf.index = i;
         tV4l2Buf.type  = V4L2_BUF_TYPE_VIDEO_CAPTURE;
         tV4l2Buf.memory = V4L2_MEMORY_MMAP;
         ioctl(iFd, VIDIOC_QBUF, &tV4l2Buf); //放入队列   queue
     }
     
     /*5、启动摄像头开始读数据*/
     ioctl(iFd, VIDIOC_STREAMON, &iType);
     return 0;
 }
 ​
 ​
 /*采集摄像头数据*/
 void camera_pthread(void)
 {
     int error;
     int cnt=0;
     int i=0;
     int ListNum;
     
     /* 8.1、等待是否有数据 */
     fd_set readfds;
      
     /* YUV格式的数据<------>在LCD上显示:rgb888 */
     initLut();
     printf("开始采集数据.......\n");
 //  while(1)
 //  {
         FD_ZERO(&readfds);
         FD_SET(iFd,&readfds);
         select(iFd+1,&readfds,NULL,NULL,NULL);  /*检测文件描述符是否发生了读写事件*/
       
         memset(&tV4l2Buf, 0, sizeof(struct v4l2_buffer));
         tV4l2Buf.type    = V4L2_BUF_TYPE_VIDEO_CAPTURE; //类型
         tV4l2Buf.memory  = V4L2_MEMORY_MMAP; //存储空间类型
 ​
         /* 9、VIDIOC_DQBUF    从队列中取出 */
         error = ioctl(iFd, VIDIOC_DQBUF, &tV4l2Buf); //取出一帧数据
         ListNum = tV4l2Buf.index; //索引编号   0
         
         //将YUV转换为RGB
         Pyuv422torgb32(pucVideBuf[ListNum],Pixedata.iWidth,Pixedata.iHeight,Pixedata.VideoBuf);
         
         //保存BMP
         save_bmp(Pixedata.VideoBuf,Pixedata.iWidth,Pixedata.iHeight); 
         
         memset(&tV4l2Buf, 0, sizeof(struct v4l2_buffer));
         tV4l2Buf.index  = ListNum;
         tV4l2Buf.type   = V4L2_BUF_TYPE_VIDEO_CAPTURE;
         tV4l2Buf.memory = V4L2_MEMORY_MMAP;
         error = ioctl(iFd, VIDIOC_QBUF, &tV4l2Buf);  /*将缓冲区再次放入队列*/
 //  }
 }