2018-09-11 10:40:26 Meteor_s 阅读数 487
  • 6.小项目.图片解码播放器

    本课程是《朱有鹏老师嵌入式linux核心课程》第6部分,是一个课程后的小项目。用开发板本身自带的硬件完成一个基于linux API开发的图片解码播放器,实现了对BMP、JPG、PNG等格式图片进行解码播放的功能。

    7696 人正在学习 去看看 朱有鹏

libjpeg版本:v6b
运行环境:arm

/*
 *函数功能:解码jpg图片,存储解码出来的数据
 *函数参数:pPic记录源jpg图片,解码得到图片宽高、位深度、显示数据
 *返回值  :解码正确返回0,错误返回-1
 */
int jpg_analyze (struct pic_info *pPic)
{
	struct jpeg_decompress_struct cinfo; //解压参数
	struct my_error_mgr jerr;
	FILE * infile;				//指向打开图片的指针	
 	char * buffer = NULL;       //指向解码行数据的指针
 	int row_stride;				//解码出来的一行图片信息的字节数
	
	if ((infile = fopen(pPic->pathname, "rb")) == NULL) {
  	  	fprintf(stderr, "can't open %s\n", pPic->pathname);
    	return -1;
  	}

	//第1步:申请和初始化JPEG解压目标
	cinfo.err = jpeg_std_error(&jerr.pub); //输出型参数
  	jerr.pub.error_exit = my_error_exit;	
	if (setjmp(jerr.setjmp_buffer)) {
    	jpeg_destroy_decompress(&cinfo);
    	fclose(infile);
    	return -1;
 	}
	//给解码器作必要的内存分配和数据结构的初始化
	jpeg_create_decompress(&cinfo); 
	
	//第2步:将fopen打开的源jpg图片和解码器相关联
	jpeg_stdio_src(&cinfo, infile);

	//第3步:读取图片头信息
	(void) jpeg_read_header(&cinfo, TRUE);
	
	//第4步:开始解码
	(void) jpeg_start_decompress(&cinfo);
	row_stride = cinfo.output_width * cinfo.output_components; //一行的字节数
	buffer = (char *)malloc(row_stride); 	//申请一行的空间
	if (NULL == buffer) {
		fprintf(stderr, "cinfo.mem->alloc_sarray error.\n");
		return -1;
	}

	//第5步:逐行解码,并将解码出的数据存到buffer中
	while (cinfo.output_scanline < cinfo.output_height) {
		jpeg_read_scanlines(&cinfo, &buffer, 1);
		//将buffer中的一行数据移到数据缓冲区中
		memcpy(pPic->pData + (cinfo.output_scanline-1*row_stride, buffer, row_stride);
	}

	pPic->bpp = cinfo.output_components*8; 
	pPic->height = cinfo.image_height;
	pPic->width = cinfo.image_width;	

	//第6步:结束解码
	(void) jpeg_finish_decompress(&cinfo);

	//第7步:释放解码目标
	jpeg_destroy_decompress(&cinfo);
	fclose(infile);

	return 0;
}

typedef struct pic_info {
	char *pathname; 	  //图片在系统中的文件名加路径名
	unsigned int width;   //宽
	unsigned int height;  //高
	unsigned int bpp;     //位深度
	unsigned char *pData; //指向图片数据缓冲区
} pic_info;

note:执行读取函数jpeg_read_scanlines(),第一次读取出来的数据全是0000,所以存储数据的时候需要丢掉第一次读取的数据,操作就是pPic->pData + (cinfo.output_scanline-1)*row_stride,更改指针指向。

数据处理过程:

2019-05-05 17:35:29 qq_31878855 阅读数 105
  • 6.小项目.图片解码播放器

    本课程是《朱有鹏老师嵌入式linux核心课程》第6部分,是一个课程后的小项目。用开发板本身自带的硬件完成一个基于linux API开发的图片解码播放器,实现了对BMP、JPG、PNG等格式图片进行解码播放的功能。

    7696 人正在学习 去看看 朱有鹏

JPEG图片叠加PNG图片水印

本文的主要目的是将一张JPEG的图片叠加一张PNG格式的水印,很多基础理论知识本文并没有涉及,博主也不是很懂。但是JPEG的编解码,PNG的解码大家还是可以参照完成的。本文的代码上传到了CSDN,基础好的同学可以直接下载使用就好了,积分有点多,要5积分,手快了没有看到在哪里设置,上传到这里主要是想搞点积分。
https://download.csdn.net/download/qq_31878855/11160413
实在没有积分的可以私聊我。

一、JEPG图片解码

JPEG图像解码需要用到开源的解码库,IJG是一个非正式的组,它为JPEG图像压缩编写和发布一个广泛使用的免费库。目前最新的版本是2018年1月14日发布的9c版本。Linux下载地址
http://www.ijg.org/files/jpegsrc.v9c.tar.gz
本文所使用的是8c版本,
http://www.ijg.org/files/jpegsrc.v8c.tar.gz

1.解压

tar -zxvf jpegsrc.v8c.tar.gz

2.编译JPEG库

cd jpeg-8c/ #cd到解压目录
mkdir tmp #创建一个临时文件夹,用于存放安装文件
./configure --prefix=$(pwd)/tmp #配置并生成Makefile
make && make install #编译并安装
生成文件如下图所示:
在这里插入图片描述

3.编写解码JPEG图片代码。

①函数介绍
//写输出bmp文件的头部分,文件信息由库中解码函数提供
void write_bmp_header(j_decompress_ptr cinfo, FILE *output_file)
{
        char bmpfileheader[14];
        char bmpinfoheader[40];
        long headersize, bfSize;
        int bits_per_pixel, cmap_entries;
        int step;
        /* Compute colormap size and total file size */
        if (cinfo->out_color_space == JCS_RGB) {
                if (cinfo->quantize_colors) {
                        /* Colormapped RGB */
                        bits_per_pixel = 8;
                        cmap_entries = 256;
                } else {
                        /* Unquantized, full color RGB */
                        bits_per_pixel = 24;
                        cmap_entries = 0;
                }
        } else {
                /* Grayscale output.  We need to fake a 256-entry colormap. */
                bits_per_pixel = 8;
                cmap_entries = 256;
        }
        step = cinfo->output_width * cinfo->output_components;
        while ((step & 3) != 0) step++;
        /* File size */
        headersize = 14 + 40 + cmap_entries * 4; /* Header and colormap */

        bfSize = headersize + (long) step * (long) cinfo->output_height;

        /* Set unused fields of header to 0 */
        memset(bmpfileheader, 0, sizeof(bmpfileheader));
        memset(bmpinfoheader, 0 ,sizeof(bmpinfoheader));

        /* Fill the file header */
        bmpfileheader[0] = 0x42;/* first 2 bytes are ASCII 'B', 'M' */
        bmpfileheader[1] = 0x4D;
        PUT_4B(bmpfileheader, 2, bfSize); /* bfSize */
        /* we leave bfReserved1 & bfReserved2 = 0 */
        PUT_4B(bmpfileheader, 10, headersize); /* bfOffBits */

        /* Fill the info header (Microsoft calls this a BITMAPINFOHEADER) */
        PUT_2B(bmpinfoheader, 0, 40);   /* biSize */
        PUT_4B(bmpinfoheader, 4, cinfo->output_width); /* biWidth */
        PUT_4B(bmpinfoheader, 8, cinfo->output_height); /* biHeight */
        PUT_2B(bmpinfoheader, 12, 1);   /* biPlanes - must be 1 */
        PUT_2B(bmpinfoheader, 14, bits_per_pixel); /* biBitCount */
        /* we leave biCompression = 0, for none */
        /* we leave biSizeImage = 0; this is correct for uncompressed data */
        if (cinfo->density_unit == 2) { /* if have density in dots/cm, then */
                PUT_4B(bmpinfoheader, 26, (INT32) (cinfo->X_density*100)); /* XPels/M */
                PUT_4B(bmpinfoheader, 30, (INT32) (cinfo->Y_density*100)); /* XPels/M */
        }
        PUT_2B(bmpinfoheader, 32, cmap_entries); /* biClrUsed */
        /* we leave biClrImportant = 0 */
        if (fwrite(bmpfileheader, 1, 14, output_file) != (size_t) 14) {
                printf("write bmpfileheader error\n");
        }
        if (fwrite(bmpinfoheader, 1, 40, output_file) != (size_t) 40) {
                printf("write bmpinfoheader error\n");
        }
        if (cmap_entries > 0) {
        }
}
// 写入bmp图像 rgb 数据
void write_pixel_data(j_decompress_ptr cinfo, unsigned char *output_buffer, FILE *output_file)
{
        int rows, cols;
        int row_width;
        int step;
        
        int x=20,y=cinfo->output_height -watermask_info.height -20 ;
        unsigned char *tmp = NULL;
unsigned char *pdata;

        row_width = cinfo->output_width * cinfo->output_components;
        step = row_width;
        while ((step & 3) != 0) step++;

        pdata = (unsigned char *)malloc(step);
        memset(pdata, 0, step);
				printf("cinfo->output_components=%d,\n",cinfo->output_components);
        tmp = output_buffer + row_width * (cinfo->output_height - 1);
        
      
        int i;
        
        for (rows = 0; rows < cinfo->output_height; rows++) {
                for (cols = 0; cols < row_width; cols += 3) {
                        pdata[cols + 2] = tmp[cols + 0];
                        pdata[cols + 1] = tmp[cols + 1];
                        pdata[cols + 0] = tmp[cols + 2];
                  //       pdata[cols + 0] = tmp[cols + 0];
                   //     pdata[cols + 1] = tmp[cols + 1];
                   //     pdata[cols + 2] = tmp[cols + 2];
                }
                tmp -= row_width;
 #if 0     // 这里是叠加一个bmp图片水印,bmp的图片不是透明的叠加出来效果不好 //watermask_info 就是水印的数据
                if( rows >= y  &&  rows <y+watermask_info.height  ){
                	for( i=0;i<watermask_info.width*3;i++)	
                	{
                		pdata[ x*3+i]  = watermask_info.data[i+(rows - y )*watermask_info.width*3];
                	}
                }
#endif                
                fwrite(pdata, 1, step, output_file);
        }
				free(watermask_info.data);
        free(pdata);
}
// 解码函数
int decode_jpeg_file(const char *input_filename, const char *output_filename)
{
        struct jpeg_decompress_struct cinfo; //jpeg使用的对象结构体
        struct jpeg_error_mgr jerr;       // jpeg错误处理结构体
        FILE *input_file;
        FILE *output_file;
        JSAMPARRAY buffer;//IJG还定义了JSAMPROW和JSAMPARRAY,分别表示一行JSAMPLE和一个2D的JSAMPLE数组
        int row_width;

        unsigned char *output_buffer;
        unsigned char *tmp = NULL;

        cinfo.err = jpeg_std_error(&jerr); //将错误结构体绑定到jpeg对象上
// 输入文件 需要解码的jpg图片
        if ((input_file = fopen(input_filename, "rb")) == NULL) {
                fprintf(stderr, "can't open %s\n", input_filename);
                return -1;
        }
//输出文件,解码后的bmp文件
        if ((output_file = fopen(output_filename, "wb")) == NULL) {
                fprintf(stderr, "can't open %s\n", output_filename);
                return -1;
        }

        jpeg_create_decompress(&cinfo);  //初始化jpeg 对象

        /* Specify data source for decompression */
        jpeg_stdio_src(&cinfo, input_file); //利用标准C中的文件指针传递要打开的jpg文件
        /* Read file header, set default decompression parameters */
        (void) jpeg_read_header(&cinfo, TRUE); //IJG将图像的缺省信息填充到cinfo结构中以便程序使用。

        /* Start decompressor */
        (void) jpeg_start_decompress(&cinfo);

        row_width = cinfo.output_width * cinfo.output_components;//每个像素中的颜色通道数cinfo.output_components(比如灰度为1,全彩色为3)等。

/*JPOOL_IMAGE表示分配的内存空间将在调用jpeg_finish_compress,jpeg_finish_decompress,jpeg_abort后被释放,而如果此参数改为JPOOL_PERMANENT则表示内存将一直到JPEG对象被销毁时才被释放。row_stride如上所说,是每行数据的实际大小。最后一个参数是要分配多少行数据。此处只分配了一行。*/
        buffer = (*cinfo.mem->alloc_sarray)
                ((j_common_ptr) &cinfo, JPOOL_IMAGE, row_width, 1);

        write_bmp_header(&cinfo, output_file); //填充bmp 的头,输出的bmp文件

        output_buffer = (unsigned char *)malloc(row_width * cinfo.output_height);
        memset(output_buffer, 0, row_width * cinfo.output_height);
        tmp = output_buffer;
				/*output_scanline表示当前已经读取的行数,如此即可依次读出图像的所有数据,并填充到缓冲区中,参数1表示的是每次读取的行数。*/
        /* Process data */
        while (cinfo.output_scanline < cinfo.output_height) {
                (void) jpeg_read_scanlines(&cinfo, buffer, 1);// 读出解码后的数据

                memcpy(tmp, *buffer, row_width);// 保存到输出buff中
                tmp += row_width;
        }
        write_pixel_data(&cinfo, output_buffer, output_file); //写入bmp中
        free(output_buffer);
        (void) jpeg_finish_decompress(&cinfo); //解压缩完毕
        jpeg_destroy_decompress(&cinfo);//释放资源
        /* Close files, if we opened them */
        fclose(input_file);
        fclose(output_file);
        return 0;
}
//工具函数,宏
#define PUT_2B(array,offset,value)  \
        (array[offset] = (char) ((value) & 0xFF), \
         array[offset+1] = (char) (((value) >> 8) & 0xFF))
#define PUT_4B(array,offset,value)  \
        (array[offset] = (char) ((value) & 0xFF), \
         array[offset+1] = (char) (((value) >> 8) & 0xFF), \
         array[offset+2] = (char) (((value) >> 16) & 0xFF), \
         array[offset+3] = (char) (((value) >> 24) & 0xFF))
int get_2b(unsigned char*a,int offset)
{
    return a[offset+1]<<8|a[offset];
}
int get_4b(unsigned char*a,int offset)
{
    return (a[offset+3]<<24)|(a[offset+2]<<16)|(a[offset+1]<<8)|a[offset];
} 
②调用上面的函数

int decode_jpeg_file(const char *input_filename, const char *output_filename);
input_filename // 为需要解码的图像文件名
output_filename //输出解码后bmp文件的文件名,会自动创建

③编译

gcc jpeg_decode.c -l jpeg -L jpeg-8c/tmp/lib/ -o jpeg_decode.app

4.成功解码

没有运行之前
在这里插入图片描述
在这里插入图片描述
运行./jpeg_decode.app,之后生成tt.bmp文件。
在这里插入图片描述
打开tt.bmp
在这里插入图片描述
成功的将jpeg图片解码成了bmp图片。
大小对比:
在这里插入图片描述
解码成功后的BMP文件比JPEG格式的文件大了许多。

二、JEPG图片编码

现在找一张bmp图片,把它编码成jpeg文件格式。

1.代码编写

①解析原始的bmp图片,获取图像高度,宽度等信息。
void read_bmp_header(char *bmpfilename)
{
    unsigned char bmpfileheader[14];//¿¿¿
    unsigned char bmpinfoheader[40];//¿¿¿
    bmpfile=fopen(bmpfilename,"r");//
    if(bmpfile<0)
    printf("open bmp file error!\n");
    printf("open bmp file success!\n");
    fread(bmpfileheader,14,1,bmpfile);
    int type=get_2b(bmpfileheader,0);
    printf("type=0x%x\n",type);
    int filesize=get_4b(bmpfileheader,2);
    printf("filesize=%d bytes\n",filesize);
    headersize=get_4b(bmpfileheader,10);
    printf("headersize=%d bytes\n",headersize);
    if(headersize>54)
        printf("colormap size=%d bytes\n",headersize-54);
    fseek(bmpfile,14,SEEK_SET);
    fread(bmpinfoheader,40,1,bmpfile);
    image_width=get_4b(bmpinfoheader,4);
    while (image_width%4!=0)
        image_width++;
    printf("weight=%d\n",image_width);
    image_height=get_4b(bmpinfoheader,8);
    printf("height=%d\n",image_height);
    bits_per_pixel=get_2b(bmpinfoheader,14);
    printf("bits_per_pixel=%d\n",bits_per_pixel);
    depth=bits_per_pixel/8;
    image_size=image_width*image_height*depth;
    src_data=(unsigned char *)malloc(image_size);
    fseek(bmpfile,headersize,SEEK_SET);
    fread(src_data,sizeof(unsigned char)*image_size,1,bmpfile);
    fclose(bmpfile);
}
② 编码JEPG图片,将bmp中的rgb原始数据编码成jpeg格式。
void encode_jpeg_file (char * outfilename, unsigned char * buffer,int quality)
{
    struct jpeg_compress_struct cinfo;
    struct jpeg_error_mgr jerr;
    FILE * outfile;
    unsigned char *dst_data;
    int i,j;
    //char *point;
    JSAMPROW  row_pointer[1];
    //js amparray buffer;
    int row_stride;
    cinfo.err = jpeg_std_error(&jerr);
    jpeg_create_compress(&cinfo);
    if ((outfile = fopen(outfilename, "wb")) == NULL) {
    fprintf(stderr, "can't open %s\n", outfilename);
    exit(1);
    }
    jpeg_stdio_dest(&cinfo, outfile);
    cinfo.image_width = image_width;     /* image width and height, in pixels */
    cinfo.image_height = image_height;
    cinfo.input_components =  depth;        /* # of color components per pixel */
   // cinfo.in_color_space = (depth==3) ? jcs_rgb : jcs_grayscale;     /* colorspace of input image */
   cinfo.in_color_space = (depth==3) ? JCS_RGB : JCS_GRAYSCALE; 
   	printf("in_color_space = %d ,input_components =%d JCS_RGB= %d \n",cinfo.in_color_space, cinfo.input_components,JCS_RGB);
    jpeg_set_defaults(&cinfo);
    jpeg_set_quality(&cinfo, quality, TRUE /* limit to baseline-jpeg values */);
    dst_data=(unsigned char *)malloc(image_size*sizeof(unsigned char));
    //bgr->rgb
    #if 1
    for(i=0;i<image_height;i++){
        for(j=0;j<image_width;j++)
        {
            if(depth==1)//¿¿¿
                *(dst_data+i*image_width+j)=*(src_data+i*image_width+j);
            else //¿¿¿
                {
            *(dst_data+i*image_width*depth+j*3+0)=*(src_data+i*image_width*depth+j*3+2);
                    *(dst_data+i*image_width*depth+j*3+1)=*(src_data+i*image_width*depth+j*3+1);
                   *(dst_data+i*image_width*depth+j*3+2)=*(src_data+i*image_width*depth+j*3+0);
                   // dst_data[j + 2] = src_data[j + 0];
                   // dst_data[j + 1] = src_data[j + 1];
                    //dst_data[j + 0] = src_data[j + 2];
                }
        }
    }
    #endif
    //dst_data=src_data;
    jpeg_start_compress(&cinfo, TRUE);

    row_stride = image_width * cinfo.input_components;    /* js amples per row in image_buffer */
    
    while (cinfo.next_scanline < cinfo.image_height) {
         row_pointer[0] = & dst_data[(cinfo.image_height - cinfo.next_scanline - 1) * row_stride];//cinfo.next_scanline * row_stride
        // row_pointer[0] = & dst_data[cinfo.next_scanline  * row_stride];
         (void) jpeg_write_scanlines(&cinfo, row_pointer, 1);

    }

    jpeg_finish_compress(&cinfo);
    fclose(outfile);
    jpeg_destroy_compress(&cinfo);
    free(src_data);
    free(dst_data);
}
③ 调用上诉函数

先调用 read_bmp_header()获取需要编码的bmp图片信息,在调用encode_jpeg_file函数编码jpeg数据并保存为jeg格式的图片。

2.编译

gcc jpeg_encode.c -l jpeg -L jpeg-8c/tmp/lib/ -o jpeg_encode.app

3.成功编码

没运行之前文件如下:
在这里插入图片描述

运行app(如果出现找不到共享库的错误,参照下PNG那节,导出共享库路径)
在这里插入图片描述
打开文件对比,
在这里插入图片描述在这里插入图片描述
可以发现上面两张照片效果无明显差别但是文件大小确小了很多。

三、PNG解码

水印文件一般为PNG格式的文件,32bit带透明数据。想要叠加水印还的解码PNG格式的图片。

1.下载库

解码PNG格式的文件需要用到libpng库,网上搜索
libpng download 。
又升级了libpng-1.6.36.tar.xz ,下载地址 https://downloads.sourceforge.net/libpng/libpng-1.6.36.tar.xz。
不过我用的是1.6.34版本。

2.解压并编译

tar -zxvf libpng-1.6.34.tar.gz
cd libpng-1.6.34
mkdir tmp
./configure --prefix=KaTeX parse error: Expected 'EOF', got '&' at position 18: …wd)/tmp make &̲& make install …(pwd)/tmp --host=arm-linux 也可以使用CC变量。
生成目录如下:
在这里插入图片描述

3.编写代码

① 解码函数

偷了个懒没有保存为bmp文件,直接保存为rgb的裸数据流文件,后面图片叠加的时候也不用解析bmp文件了。水印文件也不用经常换,后面直接放到h文件了。

char*  decode_png(char* name)
{
 int i, j;
 int m_width, m_height;
 png_infop info_ptr;             //图片信息的结构体
 png_structp png_ptr;         //初始化结构体,初始生成,调用api时注意传入

 FILE* file = fopen(name, "rb");    //打开的文件名
 printf("%s, %d\n", __FUNCTION__, __LINE__);

 png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);   //创建初始化libpng库结构体
  info_ptr = png_create_info_struct(png_ptr);                                                 //创建图片信息结构体
 setjmp(png_jmpbuf(png_ptr));                              //设置错误的返回点
 // 这句很重要
 png_init_io(png_ptr, file);         //把文件加载到libpng库结构体中
 // 读文件了
 png_read_png(png_ptr, info_ptr, PNG_TRANSFORM_EXPAND, 0);        //读文件内容到info_ptr中
 // 得到文件的宽高色深
 if ((png_ptr != NULL) && (info_ptr != NULL))
 {
  m_width = png_get_image_width(png_ptr, info_ptr);             
  m_height = png_get_image_height(png_ptr, info_ptr);                          //通过png库中的api获取图片的宽度和高度
  printf("%s, %d, m_width =%d, m_height = %d\n", __FUNCTION__, __LINE__, m_width, m_height);
 }
 int color_type = png_get_color_type(png_ptr, info_ptr);                          //通过api获取color_type
 printf("%s, %d, color_type = %d\n", __FUNCTION__, __LINE__, color_type);


 int size = m_height * m_width * 4;
 unsigned char *bgra = NULL;
 bgra = malloc(size);
 if (NULL == bgra)
 {
  printf("%s, %d, bgra == NULL\n", __FUNCTION__, __LINE__);
  return;
 }
 int pos = 0;
 // row_pointers里边就是传说中的rgb数据了
 png_bytep* row_pointers = png_get_rows(png_ptr, info_ptr);
 // 拷贝!!注意,如果你读取的png没有A通道,就要3位3位的读。还有就是注意字节对其的问题,最简单的就是别用不能被4整除的宽度就行了。读过你实在想用,就要在这里加上相关的对齐处理。
 for(i = 0; i < m_height; i++)
 {
    for(j = 0; j < (4 * m_width); j += 4)
    {
     bgra[pos++] = row_pointers[i][j + 2]; // blue
     bgra[pos++] = row_pointers[i][j + 1]; // green
     bgra[pos++] = row_pointers[i][j];   // red
     bgra[pos++] = row_pointers[i][j + 3]; // alpha
    }
 }
 // 好了,你可以用这个数据作任何的事情了。。。把它显示出来或者打印出来都行。
/* for (i = 0; i < size; i++ )
 {
  printf("%s, %d, bgra[%d] = %d\n", __FUNCTION__, __LINE__,  i, bgra[i]);
 }
 */
 char tmp[10]={0};
//保存rgb裸流文件咯,省时省力
 FILE * f = fopen("savepng.bin","wb");
 for (i = 0; i < size; i++ )
 {
 // printf("%s, %d, bgra[%d] = %d\n", __FUNCTION__, __LINE__,  i, bgra[i]);
  	sprintf(tmp,"0x%x,",bgra[i]);
  	fwrite(tmp,strlen(tmp),1,f);
  	
 }
 	fwrite(bgra,size,1,f);
 	fclose(f);
 png_destroy_read_struct(&png_ptr, &info_ptr, 0);
 fclose(file);
 return   bgra;
}
② 编译

gcc png_decode.c -l png -L libpng-1.6.34/tmp/lib/ -o png_decode.app

4.成功解码

运行第一次出错
在这里插入图片描述
找不到共享库。
导出库路径,具体路径看实际的。

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/work/ibpng-1.6.34/tmp/lib

再次运行,成功,分辨率信息没有保存,但是后续还是要用到。
在这里插入图片描述
解码的文件名为LOGO.png.
图中出现警告 libpng warning: iCCP: known incorrect sRGB profile可无需体会,如果觉得碍眼可以百度解决下,当前不影响我们叠加水印。
生成文件
在这里插入图片描述
至于解码出来的数据对不对还的看下节了。

四、叠加水印

叠加水印首先我们将JPEG格式的文件解码成RGB原始数据,再将上面解码出来的PNG图片的RGBA数据取出,然后将两种数据按比例混合。由于JPEG是有损压缩,会产生迭代有损,在重复压缩和解码的过程中会不断丢失信息使图像质量下降。

1、编写代码

因为后面需要移植到嵌入式平台上,为了方便使用,我将PNG的数据保存到H文件中,建立了一个常量数组来保存PNG数据,便于使用。当然这种方法不适合通用,将PNG解码和JPEG编解码结合起来实在点,不过到嵌入式设备了就得连带将PNG和JPEG的库文件都移植进去空间会变大,可以根据情况选取。

①填充PNG数据
void fill_png_info()
{
	watermask_info.width=138;
	watermask_info.height=45;
	watermask_info.depth=4;
	watermask_info.image_size=watermask_info.width * watermask_info.height *watermask_info.depth ;
	watermask_info.bits_per_pixel=watermask_info.depth * 8;
	watermask_info.data=(char *)png_watermark_data;
}

这就是自己写的一个结构体,保存一些后面要用的信息,png_watermark_data这是个const char 类型的数字放的就是PNG解码出来的纯RBGA数据流,后面叠加的时候需要用到。

②叠加水印函数

整体思想就是先按上文的方式解码JPEG文件,再将PNG解码后的数据叠加到JPEG解码数据上,整合后按JPEG格式编码。

int read_jpeg_file(const char *input_filename, const char *output_filename)
{
        struct jpeg_decompress_struct cinfo; //jpeg使用的对象结构体
        struct jpeg_error_mgr jerr;       // jpeg错误处理结构体
        FILE *input_file;
        FILE *output_file;
        JSAMPARRAY buffer;//IJG还定义了JSAMPROW和JSAMPARRAY,分别表示一行JSAMPLE和一个2D的JSAMPLE数组
        int row_width;

        unsigned char *output_buffer;
        unsigned char *tmp = NULL;
        cinfo.err = jpeg_std_error(&jerr); //将错误结构体绑定到jpeg对象上
        if ((input_file = fopen(input_filename, "rb")) == NULL) {
                fprintf(stderr, "can't open %s\n", input_filename);
                return -1;
        }
        jpeg_create_decompress(&cinfo);  //初始化jpeg 对象
        /* Specify data source for decompression */
        jpeg_stdio_src(&cinfo, input_file); //利用标准C中的文件指针传递要打开的jpg文件
        /* Read file header, set default decompression parameters */
        (void) jpeg_read_header(&cinfo, TRUE); //IJG将图像的缺省信息填充到cinfo结构中以便程序使用。
        /* Start decompressor */
        (void) jpeg_start_decompress(&cinfo);
        row_width = cinfo.output_width * cinfo.output_components;//每个像素中的颜色通道数cinfo.output_components(比如灰度为1,全彩色为3)等。
        buffer = (*cinfo.mem->alloc_sarray)
                ((j_common_ptr) &cinfo, JPOOL_IMAGE, row_width, 1);

        output_buffer = (unsigned char *)malloc(row_width * cinfo.output_height);
        memset(output_buffer, 0, row_width * cinfo.output_height);
        tmp = output_buffer;
				/*output_scanline表示当前已经读取的行数,如此即可依次读出图像的所有数据,并填充到缓冲区中,参数1表示的是每次读取的行数。*/
        /* Process data */
        while (cinfo.output_scanline < cinfo.output_height) {
                (void) jpeg_read_scanlines(&cinfo, buffer, 1);

                memcpy(tmp, *buffer, row_width);
                tmp += row_width;
        }
		if(	png_add_watermark(&cinfo, output_buffer,10,10) <0)
		{
			printf("add watermark failed \n");
			return -1;
		}

	jpeg_finish_decompress(&cinfo); //解压缩完毕
	encode_jpeg(&cinfo,output_filename, output_buffer,80);
		
	jpeg_destroy_decompress(&cinfo);//释放资源
	free(output_buffer);
	/* Close files, if we opened them */
	fclose(input_file);
	return 0;
}
②整合数据函数

PNG格式解码后的RGBA数据当中的A数据包含着该像素点在图像当中的比列,我们根据这个比列进行融合。融合在for循环中,最关键的是要理解像素点数据在数组当中的位置进行偏移融合,JPEG解码的是BGR数据,然PNG数组当中的数据我在解码的时候为了方便就按BRGA的顺序存放,所以就无需进行数据顺序的交换,按实际顺序即可。JPEG解码后的数据是按三个字节一像素偏移,PNG数据是按四字节一像素偏移。

int	png_add_watermark(struct jpeg_decompress_struct *cinfo, unsigned char *output_buffer,int x ,int y)
{
    int rows, cols ,i,j;
    int row_width;
    int step;
    unsigned char *tmp = NULL;
    if( (x+watermask_info.width  )  > cinfo->output_width || (y+watermask_info.height)>cinfo->output_height )
    	return -1;
	row_width = watermask_info.width*watermask_info.depth;
/*rgb - bgr*/
	step = cinfo->output_width * cinfo->output_components;
	//printf(   " output_width =%d,output_components=%d, output_height  =%d \n",cinfo->output_width,cinfo->output_components,cinfo->output_height  );
	//printf("output_buffer = %p  \n",output_buffer);
	tmp = output_buffer + (step) * (y); // 得到当前的偏移行地址
	//char * wm_src_data = dst_data + ( watermask_info.height-1  )* row_width;
	int cnt;
	int num;
	  for(rows = 0 ; rows <watermask_info.height ; rows++ )
	  {
		  for(cols = 0,cnt=0 ; cols <watermask_info.width *(watermask_info.depth ) ; cols+=4 )
		  {
		  		num = watermask_info.data[ cols+3  +  rows* row_width];
		  		//output_buffer[(y*row_width)+rows] = wm_src_data[   ]
				tmp[ cinfo->output_components*x+cnt] = tmp[ cinfo->output_components*x+cnt++]*(100-num)/100  + watermask_info.data[ cols+0  +  rows* row_width] *(num) /100 ;  
				tmp[ cinfo->output_components*x+cnt] = tmp[ cinfo->output_components*x+cnt++]*(100-num)/100 + watermask_info.data[ cols+1  +  rows* row_width] *(num) / 100;  
				tmp[ cinfo->output_components*x+cnt] = tmp[ cinfo->output_components*x+cnt++]*(100-num)/100  + watermask_info.data[ cols+2  +  rows* row_width] *(num ) / 100;  
		  }
		  tmp+=step; //移到下一行
	  }
	//  free(watermask_info.data);
	  return 0;
}

2、编译代码

gcc JpegAddWatermark.c -l jpeg -L jpeg-8c/tmp/lib/ -o AddWm.app

3、运行结果

成功的将水印叠加了上去。
在这里插入图片描述

结束语

至此本文就编写完成,该项目已经完成快一年半了,很多细节知识都忘记了,当中如果有什么错误还望各位能够在评论区指正,有什么不明白的地方也欢迎在评论区中讨论,能给个赞最好了/wx。

2016-12-16 16:12:40 astonqa 阅读数 172
  • 6.小项目.图片解码播放器

    本课程是《朱有鹏老师嵌入式linux核心课程》第6部分,是一个课程后的小项目。用开发板本身自带的硬件完成一个基于linux API开发的图片解码播放器,实现了对BMP、JPG、PNG等格式图片进行解码播放的功能。

    7696 人正在学习 去看看 朱有鹏
6.小项目.图片解码播放器—6707人已学习
课程介绍    
201612161601076574.jpg
    本课程是《朱有鹏老师嵌入式linux核心课程》第6部分,是一个课程后的小项目。用开发板本身自带的硬件完成一个基于linux API开发的图片解码播放器,实现了对BMP、JPG、PNG等格式图片进行解码播放的功能。
课程收益
    本课程是《朱有鹏老师嵌入式linux核心课程》第6部分,适合学习完了整个课程后作为实训项目来检验所学、提升项目经验、进一步锻炼自己所用。
讲师介绍
    朱有鹏更多讲师课程
    互联网课程品牌《朱老师物联网大讲堂》创始人。精通U-Boot、Linux kernel移植及驱动程序开发;精通C、C++、Java、C#等语言,熟悉ARM Cortex-A、Cortex-M3/M4等体系结构;熟悉三星平台S3C2440、S3C6410、S5PV210等处理器系列的linux、WinCE下的开发流程;授课风趣幽默,讲解条理清晰,对知识有自己独到见解,善于发散学生的思维。
课程大纲
    1.6.1.项目展示与整体规划  37:01
    2.6.2.环境搭建和基础确认  18:46
    3.6.3.开始动手写代码  34:02
    4.6.4.framebuffer基本操作代码  37:17
    5.6.5.图片显示原理和实践  36:45
    6.6.6.图片数据提取和显示  39:02
    7.6.7.图片显示的高级话题  31:53
    8.6.8.其他显示细节问题  27:07
    9.6.9.任意起点位置图片显示1  26:12
    10.6.10.任意起点位置图片显示2  28:02
    11.6.11.BMP图片的显示1  33:00
    12.6.12.BMP图片的显示2  30:05
    13.6.13.BMP图片的显示3  30:34
    14.6.14.BMP图片的显示4  34:54
    15.6.15.BMP图片的显示5  29:24
    16.6.16.及时规整才能写出好项目1  32:37
    17.6.17.及时规整才能写出好项目2  30:02
    18.6.18.及时规整才能写出好项目3  28:52
    19.6.19.jpg图片的显示原理分析1  20:05
    20.6.20.jpg图片的显示原理分析2  18:55
    21.6.21.libjpeg介绍及开源库的使用方法  37:54
    22.6.22.libjpeg的移植实战1  22:08
    23.6.23.libjpeg的移植实战2  22:54
    24.6.24.使用libjpeg解码显示jpg图片1  28:00
    25.6.25.使用libjpeg解码显示jpg图片2  26:08
    26.6.26.使用libjpeg解码显示jpg图片3  27:45
    27.6.27.使用libjpeg解码显示jpg图片4  34:23
    28.6.28.使用libjpeg解码显示jpg图片5  36:24
    29.6.29.解决解码显示中的问题1  24:09
    30.6.30.解决解码显示中的问题2  25:17
    31.6.31.结束jpg图片部分  37:18
    32.6.32.解码显示png图片1  28:46
    33.6.33.解码显示png图片2  27:04
    34.6.34.解码显示png图片3  29:09
    35.6.35.解码显示png图片4  34:48
    36.6.36.解码显示png图片5  26:34
    37.6.37.图片文件的管理和检索1  30:16
    38.6.38.图片文件的管理和检索2  27:09
    39.6.39.图片文件的管理和检索3  26:45
    40.6.40.添加触摸翻页功能  34:40
    41.6.41.总结与回顾  26:16
大家可以点击【查看详情】查看我的课程
2018-09-13 14:17:48 Meteor_s 阅读数 1500
  • 6.小项目.图片解码播放器

    本课程是《朱有鹏老师嵌入式linux核心课程》第6部分,是一个课程后的小项目。用开发板本身自带的硬件完成一个基于linux API开发的图片解码播放器,实现了对BMP、JPG、PNG等格式图片进行解码播放的功能。

    7696 人正在学习 去看看 朱有鹏

解码库版本:libpng-1.6.35

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <png.h>        //解码库文件
#include <pngstruct.h>  //解码库文件
#include <pnginfo.h>    //解码库文件
#include <config.h>     //自己定义的头文件

#define PNG_BYTES_TO_CHECK 	 8  
/*
 *函数功能 :判断一个图片数是不是png图片
 *函数参数 :path是图片文件的路径名加文件名
 *返回值   :如果是png则返回0,不是或出错返回-1
 */
static int is_png(char *path) {
	FILE *fp = NULL;
	char buf[PNG_BYTES_TO_CHECK] = {0}; 

	/* Open the prospective PNG file. */   
	if ((fp = fopen(path, "rb")) == NULL)		
		return -1;	

	/* Read in some of the signature bytes */	
	if (fread(buf, 1, PNG_BYTES_TO_CHECK, fp) != PNG_BYTES_TO_CHECK)	   
		return -1; 

	//比较头文件标识,是png格式则返回0,不是返回非0
	//png 文件头标识 (8 bytes)   89 50 4E 47 0D 0A 1A 0A	 
	return(png_sig_cmp(buf, (png_size_t)0, PNG_BYTES_TO_CHECK));
}

/*
 *函数功能:解码png图片,并将解码出来的数据存储起来
 *函数参数:pPic记录源png图片,解码出来图片宽高、位深度等
 *返回值  :解码正确返回0,错误返回-1
 */
static int png_analyze (struct pic_info *pPic)
{
	int i, j, pos=0;
	png_structp png_ptr; //定义一个png指针
	png_infop info_ptr;
	png_bytep* row_ptr;  //实际存储rgb数据的buf,二维指针
	png_byte color_type;
	FILE *fp = NULL; 

	if ((fp = fopen(pPic->pathname, "rb")) == NULL) {
		fprintf(stderr, "fopen %s error.\n", pPic->pathname);
		return -1;
	}
			
	//第1步:初始化相关结构体
	png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
	if (png_ptr == NULL)
	{
		fprintf(stderr, "init %s error.\n", pPic->pathname);
		fclose(fp);
		return -1;
	}
	info_ptr = png_create_info_struct(png_ptr);
	if (info_ptr == NULL)
	{
		fprintf(stderr, "create %s error.\n", pPic->pathname);
		fclose(fp);
		png_destroy_read_struct(&png_ptr, NULL, NULL);
		return -1;
	}

	//第2步:设置错误返回点	
	if (setjmp(png_jmpbuf(png_ptr)))
	{
		png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
		fclose(fp);
		return -1;
	}

	//第3步:将解码的结构体和图片文件绑定
	png_init_io(png_ptr, fp);
	
	//第4步:读文件
	/*当内存足够大可以一次性读入所有的png数据,可以使用高层函数
	该函数将会把所有的图片数据解码到info_ptr数据结构中*/	 
	png_read_png(png_ptr, info_ptr, PNG_TRANSFORM_EXPAND | PNG_TRANSFORM_STRIP_ALPHA, NULL); 

	//第5步:提取打印文件信息
	pPic->width = info_ptr->width;
	pPic->height = info_ptr->height;
	pPic->bpp = info_ptr->pixel_depth;
	color_type = info_ptr->color_type; 	
	printf("picture resolution: %d*%d\n", pPic->width, pPic->height);
	printf("picture bpp: %d\n", pPic->bpp);
	printf("picture colortype: %d\n", color_type);
	
	// 第6步: 读取真正的图像信息
	row_ptr = png_get_rows(png_ptr, info_ptr);
	switch (color_type) {
		case PNG_COLOR_TYPE_RGB_ALPHA:
			for (i = 0; i < pPic->height; i++) {
				for (j = 0; j < pPic->width*4; j+=4) {
					pPic->pData[pos++] = row_ptr[i][j+0]; // red
					pPic->pData[pos++] = row_ptr[i][j+1]; // green
					pPic->pData[pos++] = row_ptr[i][j+2]; // blue
				}
			}
			break;
		case PNG_COLOR_TYPE_RGB:
			for (i = 0; i < pPic->height; i++) {
				for (j = 0; j < pPic->width*3; j+=3) {
					pPic->pData[pos++] = row_ptr[i][j+0]; // red
					pPic->pData[pos++] = row_ptr[i][j+1]; // green
					pPic->pData[pos++] = row_ptr[i][j+2]; // blue	
				}
			}
			break;
		default:
			break;	
	}
	
	//第7步:扫尾
	png_destroy_read_struct(&png_ptr, &info_ptr, 0);
	fclose(fp);
	return 0;	
}

int png_display (char *pathname) 
{
	int ret = -1;
	struct pic_info pic;

	//第1步:判断图片是不是jpg图片
	ret = is_png(pathname);
	if (ret != 0) {
		return -1;
	}
	
	//第2步:显示jpg图片
	pic.pathname = pathname;
	pic.pData = rgb_buf;
	ret = png_analyze(&pic);
	if (ret != 0) {
		return -1;
	}
	fb_draw_jpg(0, 0, &pic);
	return 0;
}

下面分析一下解码函数

1.png_infop 结构体在哪儿

在png.h的484行有如下内容,png_infop被声明为了png_info 的一维指针,而png_info又是png_info_def 的声明。

typedef struct png_info_def png_info;
typedef png_info * png_infop;
typedef const png_info * png_const_infop;
typedef png_info * * png_infopp;

得到了png_info_def后,接下来追根溯源,可以找到png_info_def定义在了pnginfo.h文件中:

struct png_info_def
{
   /* The following are necessary for every PNG file */
   png_uint_32 width;       /* width of image in pixels (from IHDR) */
   png_uint_32 height;      /* height of image in pixels (from IHDR) */
   png_uint_32 valid;       /* valid chunk data (see PNG_INFO_ below) */
   size_t rowbytes;         /* bytes needed to hold an untransformed row */
   png_colorp palette;      /* array of color values (valid & PNG_INFO_PLTE) */
   png_uint_16 num_palette; /* number of color entries in "palette" (PLTE) */
   png_uint_16 num_trans;   /* number of transparent palette color (tRNS) */
   png_byte bit_depth;      /* 1, 2, 4, 8, or 16 bits/channel (from IHDR) */
   png_byte color_type;     /* see PNG_COLOR_TYPE_ below (from IHDR) */
   /* The following three should have been named *_method not *_type */
   png_byte compression_type; /* must be PNG_COMPRESSION_TYPE_BASE (IHDR) */
   png_byte filter_type;    /* must be PNG_FILTER_TYPE_BASE (from IHDR) */
   png_byte interlace_type; /* One of PNG_INTERLACE_NONE, PNG_INTERLACE_ADAM7 */

   /* The following are set by png_set_IHDR, called from the application on
    * write, but the are never actually used by the write code.
    */
   png_byte channels;       /* number of data channels per pixel (1, 2, 3, 4) */
   png_byte pixel_depth;    /* number of bits per pixel */
   png_byte spare_byte;     /* to align the data, and for future use */
..................还有很长........
}

2.png_read_png()如何传参

函数原型:png_read_png(png_ptr, info_ptr, png_transforms, NULL)
读取图片文件信息有两种方法可以使用,一种是使用高层函数(high-level)读取,png_read_png()即为高层函数;另外一种是通过一系列的低层函数(low-level)接口进行读取操作,用起来相对麻烦。使用高层函数的前提是有足够的内存去存储整个图片的信息,因为高层函数内部进行了封装,所以使用起来很方便。
参数 png_transforms:可选用的值如下,如果只填参数PNG_TRANSFORM_EXPAND,那么转换结果为四通道,包含了RGB通道和ALPHA通道;参数改为PNG_TRANSFORM_EXPAND | PNG_TRANSFORM_STRIP_ALPHA后,就可以把ALPHA通道屏蔽掉,转换之后即为RGB三通道。

/* Transform masks for the high-level interface */
#define PNG_TRANSFORM_IDENTITY       0x0000    /* read and write */
#define PNG_TRANSFORM_STRIP_16       0x0001    /* read only */
#define PNG_TRANSFORM_STRIP_ALPHA    0x0002    /* read only */
#define PNG_TRANSFORM_PACKING        0x0004    /* read and write */
#define PNG_TRANSFORM_PACKSWAP       0x0008    /* read and write */
#define PNG_TRANSFORM_EXPAND         0x0010    /* read only */
#define PNG_TRANSFORM_INVERT_MONO    0x0020    /* read and write */
#define PNG_TRANSFORM_SHIFT          0x0040    /* read and write */
#define PNG_TRANSFORM_BGR            0x0080    /* read and write */
#define PNG_TRANSFORM_SWAP_ALPHA     0x0100    /* read and write */
#define PNG_TRANSFORM_SWAP_ENDIAN    0x0200    /* read and write */
#define PNG_TRANSFORM_INVERT_ALPHA   0x0400    /* read and write */
#define PNG_TRANSFORM_STRIP_FILLER   0x0800    /* write only */
/* Added to libpng-1.2.34 */
#define PNG_TRANSFORM_STRIP_FILLER_BEFORE PNG_TRANSFORM_STRIP_FILLER
#define PNG_TRANSFORM_STRIP_FILLER_AFTER 0x1000 /* write only */
/* Added to libpng-1.4.0 */
#define PNG_TRANSFORM_GRAY_TO_RGB    0x2000     /* read only */
/* Added to libpng-1.5.4 */
#define PNG_TRANSFORM_EXPAND_16      0x4000     /* read only */

3.color_type的值有哪些

color_type的可能等于的值是由宏定义来决定的,switch判断的时候,我只判断了PNG_COLOR_TYPE_RGB_ALPHA和PNG_COLOR_TYPE_RGB两种情况,其他情况没有考虑,PNG_COLOR_TYPE_GRAY_ALPHA代表解码后的颜色类型为四通道,PNG_COLOR_TYPE_RGB代表三通道,转移数据的时候要注意这一点。

#define PNG_COLOR_TYPE_GRAY			0
#define PNG_COLOR_TYPE_RGB 			2
#define PNG_COLOR_TYPE_PALETTE 		3
#define PNG_COLOR_TYPE_GRAY_ALPHA	4
#define PNG_COLOR_TYPE_RGB_ALPHA	6

这里写图片描述

在这里插入图片描述

2019-10-02 23:16:05 wangweijundeqq 阅读数 614
  • 6.小项目.图片解码播放器

    本课程是《朱有鹏老师嵌入式linux核心课程》第6部分,是一个课程后的小项目。用开发板本身自带的硬件完成一个基于linux API开发的图片解码播放器,实现了对BMP、JPG、PNG等格式图片进行解码播放的功能。

    7696 人正在学习 去看看 朱有鹏

有道云笔记详细地址:
文档:图片解码播放器小项目(详解).note
链接:http://note.youdao.com/noteshare?id=9f9a43ac5ec6828cf467940dfa10da51&sub=A8280EB4B5A146C9A1F6612031305071

文章目录

一、开始动手写代码

1、Makefile介绍

(1)这是一个通用的项目管理的Makefile体系,自己写的(有子文件夹组织的)项目可以直接套用这套Makefile体系。

(2)包含三类:顶层Makefile、Makefile.build(完全不需要改动)、子文件夹下面的Makefile。
子文件夹下的Makefile一般是类似下面的内容。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pwsgMELC-1570028293237)(F95308E307894BA1A9ECE5BC07CFA0A1)]

(3)可以参考:http://www.cnblogs.com/lidabo/p/4521123.html。

2、使用SI建立工程

打开SI,在E:\Linux\winshare\x210kernel\tupian_project中新建一个SI_Proj文件夹,用于建立SI工程,然后新建一个输出hello word的main.c文件,并在ubuntu中make整个工程,并make cp到根文件
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-28cgC87l-1570028293240)(D3212F76475F446D8A238276FDAB198D)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3oy9ZgzM-1570028293241)(61AD59BE2EEA47A89184B37C33D2317A)]

启动开发板,进入driver_test目录下,可以查看到我们的工程目录,进入执行生成的imageplayer文件,可以输出hello word,到此新建工程结束。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tuvzJgKR-1570028293244)(321FA7F935AC44FBAEB009AF46656C7A)]

新建一个脚本文件run.sh,用于管理各种可执行文件,比如上面我们生成的./imageplayer文件
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bOKzRF4J-1570028293246)(107A0A80C7444F0EACCFD669B6224802)]

二、framebuffer驱动基本操作代码

E:\Linux\winshare\x210kernel\tupian_project

显示图片,需要framebuffer驱动。

①在SI工程中新建fb.c文件(保存在display文件夹下)

并在display文件夹里新建Makefile并添加内容

obj-y += fb.o(这就是子makefile,作用就是让fb.c能被编译进去)

②这里需要用到framebuffer显示图片

故将我们在驱动学习中framebuffer哪一阶段的应用程序拷贝到fb.c中
(即E:\Linux\4.LinuxDriver\7.frambuffer\7.1.fb1\appfb.c),并修改部分代码

(即将之前的所有宏定义和函数声明放在fb.h中,并在主Makefile中添加命令,使其将子目录能编译进去)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eGAxAg1q-1570028293250)(C0D3963AFF084915B8931301C41F7343)]

man.c如下:

#include <stdio.h>
#include <fb.h>
#include <unistd.h>

int main(void)
{
	int ret=-1;
	
	printf("image player demo........star \n");
	
	//第一步,打开设备
	ret = fb_open();
	if(ret < 0)
	    {
			perror("fb_open error.\n");
			return -1;
		}
	fb_draw_badk(WIDTH, HEIGHT, YELLOW);//调用背景填充函数
	
}

从新make编译,并拷贝到根文件系统中,重启开发板,执行脚本,显示预期一致。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-86ARibZo-1570028293255)(9AEAFC67E9E34E4BB28D68B3BA5FD8A5)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RXbxT1fd-1570028293256)(2108333A2096471A97479662733A7263)]

三、图片显示原理和实践

1、图片显示原理

  • (1)概念1:像素
    我们开发板的屏幕像素为1024*600,填充部分颜色,即是想要实现的图案
  • (2)概念2:点阵(像素点构成的阵列)
  • (3)分辨率
    物理分辨率:物理屏幕像素点的真实个数;
    显示分辨率:可以小于等于物理分辨率;(通过抽样)
  • (4)清晰度(与分辨率和像素间距有关,主观概念)
    像素间距相同时(物理尺寸固定了,则像素间距就固定了),分辨率越大越清晰;分辨率相同时,像素间距越小越清晰。
  • (5)bpp(RGB565、RGB888)
    像素深度,每个像素用多少bit数据表示。
    一般每个像素点使用32bit(4个字节),但一般是24位色(高八位一般没用或者用着其他标识,用00或者ff填充以达到内存对齐);
    RGB888表示红用8位,绿8位,蓝8位。
    (6)颜色序(RGB、BGR)

2、图片点阵数据获取

(1)Image2LCD软件提取

  • 输出数据类型:C语言数组;
  • 一般不要图像头数据,只需要纯数据;
  • 一般水平扫描;
  • 一般选24位真彩色(即RGB888);
  • 选1024*600后,点右边按钮更新;
  • 输出图像调整中,可以调整RGB。
  • 最后点击保存为xxx.h文件(保存在include目录下)
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YGYaR6s8-1570028293257)(4E611CF9449B4344ADB09C2D7307643A)]

在fb.c中添加如下代码,且在主函数中调动该函数,编译程序,拷贝到跟文件系统
//测试显示1024600分辨率的图片
void fb_draw_picture(void)
{
unsigned char
pdata=gImage_1024;//指针指向图像数组
unsigned int i,j,cnt;
unsigned int*p=pfb;//等于显示缓存区的首地址

for(i=0;i<HEIGHT;i++)
{
for(j=0;j<WIDTH;j++)
{
         cnt=i*WIDTH+j;//取出当前像素点编号
         cnt*=3;//一个像素点是3个字节,当前像素点的数据在数组中的下标
         //当前像素点对应的图像数据的RGB应该分别是pData[cnt+0]、pData[cnt+1]、pData[cnt+2]
         //当前像素点的数据
         *p=((pdata[cnt+0]<<0)|(pdata[cnt+1]<<8)|(pdata[cnt+2]<<16));//可以在这里修改,达到正确的显示(当RB相反时)
     p++;
}
}

}

重启开发板,执行.、run.sh脚本,看到屏幕如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NNbLZtZH-1570028293258)(63BFF505A0AF4F519147EEF410B2D335)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-10zlyp5i-1570028293259)(CD381E03B4114362AE2046736739C10D)]

与我们图片周边是粉色颜色不符合,原因肯定是RGB弄反了,这里,我们可以在上面的fb_draw_picture函数中进行修改,将RB调换,从新编译,执行脚本,显示和我们的原图就一样了。
*p=((pdata[cnt+2]<<0)|(pdata[cnt+1]<<8)|(pdata[cnt+0]<<16));//可以在这里修改,达到正确的显示(当RB相反时)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3l0QCy6i-1570028293260)(05F0F24F487648B7BB91E05F2335095B)]

(2)通过图片/视频文件直接代码方式提取

四、图片显示的高级话题

1、RGB顺序调整

(1)RGB顺序有三个地方都有影响

  • 第一个是fb驱动中的排布;
  • 第二个是应用程序中的排布;
  • 第三个是图像数据本身排布(Image2LCD中调整RGB顺序);

(2)如果三个点中RGB顺序是一致的就会显示正常。如果三个设置不一致就可能会导致显示结果中R和B相反了;

(3)实际写程序时,一般不去分析这东西,而是根据实际显示效果去调。如果反了就去调正应用程序中的RGB顺序就行了,这样最简单。

2、显示函数的其他写法

void fb_draw_picture2(void)
{
unsigned char* pdata=gImage_1024;//指针指向图像数组
unsigned int x,y,cnt;

for(y=0;y<HEIGHT;y++)
{
for(x=0;x<WIDTH;x++)
{
    cnt=y*WIDTH+x;//取出当前像素点编号          
    //当前像素点对应的图像数据的RGB应该分别是pData[cnt+0]、pData[cnt+1]、pData[cnt+2]
    //当前像素点的数据
    *(pfb+cnt)=((pdata[cnt*3+2]<<0)|(pdata[cnt*3+1]<<8)|(pdata[cnt*3+0]<<16));////这里的像素矩阵和cnt有线性关系,所以可以这样写
    }
    }

}

五、任意分辨率大小图片显示

(1)图片比屏幕分辨率大

这种情况下多出来的部分肯定是没法显示的。处理方法是直接在显示函数中把多余不能被显示的部分给丢掉。

(2)图片大小比屏幕大小要小

这种情况下图片只是填充屏幕中一部分,剩余部分仍然保持原来的底色。
在获取图片数据时,大小和图片实际大小在这里是一致的。假如不一致呢?

  • ①从新使用Image2LCD软件制作分辨率为320*480的图片
  • ②重新编写测试图片显示函数后,编译拷贝…重启开发板,对比两种写法
//测试显示分辨率比屏幕小的图片
//pdata://指针指向图像数组
void fb_draw_picture3(unsigned char* pdata)
{
    //unsigned char* pdata=gImage_320480;
    unsigned int x,y,cnt;
    unsigned int a=0;

//图片的分辨率是320*480
    for(y=0;y<480;y++)
    {
		for(x=0;x<320;x++)
		{
                  cnt=y*WIDTH+x;/*cnt始终都是framebuff像素点的编号*/
      		     *(pfb+cnt)=((pdata[a+0]<<16)|(pdata[a+1]<<8)|(pdata[a+2]<<0));
		     a+=3;/*由于空缺一部分,像素点编号与像素点矩阵不存在倍数关系了,此时应该三个三个地传送进来*/                       
		}
	}
}
//测试显示分辨率比屏幕小的图片 另一种写法,对比看出
//pdata:指针指向图像数组
void fb_draw_picture4(unsigned char* pdata)
{
   // unsigned char* pdata=gImage_320480;//指针指向图像数组
    unsigned int x,y,cnt1;
    unsigned int cnt2=0;

 //图片的分辨率是320*480
    for(y=0;y<480;y++)
    {
	for(x=0;x<320;x++)
	{
             cnt1=y*WIDTH+x;//取出当前屏幕像素点编号   
             cnt2=y*320+x;	//取出当前图片像素点编号  
             //当前像素点对应的图像数据的RGB应该分别是pData[cnt+0]、pData[cnt+1]、pData[cnt+2]
             //当前像素点的数据
   	 *(pfb+cnt1)=((pdata[cnt2*3+2]<<0)|(pdata[cnt2*3+1]<<8)|(pdata[cnt2*3+0]<<16));////这里的像素矩阵和cnt有线性关系,所以可以这样写

	}
    }
}

另一种写法,对比两种函数的写法,看出区别,烧录效果都一样,这里,不再赘述
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VBD33q4p-1570028293261)(108ACE9C4BBF4C62A50F3FAFA3E634CB)]

六、任意起点位置图片显示

1、小图片任意起点(但整个图片显示没有超出屏幕范围内)

算法1:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s1btYqX1-1570028293263)(B819CC65CF0C41769D83150895A1202B)]

//测试在屏幕指定坐标显示图片的位置 
//x0:图片想要显示的x坐标  y0:图片想要显示的y坐标
void fb_draw_picture5(unsigned int x0,unsigned int y0,unsigned char* pdata)
{
    //unsigned char* pdata=gImage_320480;//指针指向图像数组
    unsigned int x,y,cnt1;
    unsigned int cnt2=0;

 //图片的分辨率是320*480
    for(y=y0;y<480+y0;y++)
    {
	for(x=x0;x<320+x0;x++)
	{
             cnt1=y*WIDTH+x;//取出当前屏幕像素点编号   
             cnt2=(y-y0)*320+(x-x0);	//取出当前图片像素点编号  

   	 *(pfb+cnt1)=((pdata[cnt2*3+2]<<0)|(pdata[cnt2*3+1]<<8)|(pdata[cnt2*3+0]<<16));////这里的像素矩阵和cnt有线性关系,所以可以这样写
	/*左值考虑当前像素点在fb中的偏移量*/
      /*右值考虑当前像素点在图像数据数组的下标(这里是三个为一个元素的数组的下标,因此上面会*3)*/

	}
    }
}

算法2:(因为每循环一次,+3)

//测试在屏幕指定坐标显示图片的位置,和fb_draw_picture5函数一样的功能,只是实现算法不一样,更加简单
//x0:图片想要显示的x坐标  y0:图片想要显示的y坐标
void fb_draw_picture6(unsigned int x0,unsigned int y0,unsigned char* pdata)
{
    //unsigned char* pdata=gImage_320480;
    unsigned int x,y,cnt;
    unsigned int a=0;

//图片的分辨率是320*480
    for(y=y0;y<480+y0;y++)
    {
	for(x=x0;x<320+x0;x++)
		{
                  cnt=y*WIDTH+x;/*cnt始终都是framebuff像素点的编号*/
      		     *(pfb+cnt)=((pdata[a+0]<<16)|(pdata[a+1]<<8)|(pdata[a+2]<<0));
		     a+=3;/*由于空缺一部分,像素点编号与像素点矩阵不存在倍数关系了,此时应该三个三个地传送进来*/
                         
		}
	}
}

在主函数中,调用上面两种不同算法实现的任意起点位置图片显示函数

int main()
{
int ret=-1;
	printf("image player demo........star \n");
	//第一步,打开设备
	ret = fb_open();
	if(ret < 0)
	    {
			perror("fb_open error.\n");
			return -1;
		}
  
fb_draw_badk(WIDTH, HEIGHT, RED);//调用背景填充函数
fb_draw_picture6((1024-320)/2,(600-480)/2,gImage_320480);//居中显示该图片
fb_close();
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jmpwWQkS-1570028293264)(E33B3E0C2EBC41DDAAFAB3FBCB3BF3D2)]

2、起点导致图片超出屏幕外

(1)现象
在主函数中调用任意起点位置图片显示函数:
fb_draw_picture5(900,100);//图片大小超出屏幕范围测试

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xSoDa7N4-1570028293265)(979CE78FD41744F2B7D0CE7C59E6EB29)]

  • 左右超出去,会在相反方向补全:这是内部for循环可能超过1024的界定(但没有超出fb的大小)造成的。
  • 上下超出去,则会消失:因为双缓冲进到了另一帧。如果没有双缓冲,则会内存溢出。
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mX0sz4KF-1570028293265)(042BB86735124F09B09E687683944174)]

(2)修改代码,使得超出部分不再显示。
算法1:

//x和y两个方向超出屏幕外的部分都不显示
void fb_draw_picture7(unsigned int x0,unsigned int y0, unsigned char* pdata)
{
    //unsigned char* pdata=gImage_320480;
    unsigned int x,y,cnt;
    unsigned int a=0;

//图片的分辨率是320*480
    for(y=y0;y<480+y0;y++)
    {
    		if(y>=HEIGHT)//y方向超出
	    {
		 a+=3;
		 break;//最后一行已经显示了,剩下的不用考虑了
	     }

	for(x=x0;x<320+x0;x++)
		{

			if(x>=WIDTH)//x方向超出了
			 {
			      a+=3;
			    continue;//仅结束本次循环
			  }		  
			
                  cnt=y*WIDTH+x;/*cnt始终都是framebuff像素点的编号*/
      		     *(pfb+cnt)=((pdata[a+0]<<16)|(pdata[a+1]<<8)|(pdata[a+2]<<0));
		     a+=3;/*由于空缺一部分,像素点编号与像素点矩阵不存在倍数关系了,此时应该三个三个地传送进来*/
                         
		}
	}
}

算法2:

//x和y两个方向超出屏幕外的部分都不显示 和fb_draw_picture7一样的功能 实现算法不一样
void fb_draw_picture8(unsigned int x0,unsigned int y0, unsigned char* pdata)
{
   // unsigned char* pdata=gImage_320480;//指针指向图像数组
    unsigned int x,y,cnt1;
    unsigned int cnt2=0;

 //图片的分辨率是320*480
    for(y=y0;y<480+y0;y++)
    {
	    		if(y>=HEIGHT)//y方向超出
		    {
			 break;//最后一行已经显示了,剩下的不用考虑了
		     }
	for(x=x0;x<320+x0;x++)
	{
			if(x>=WIDTH)//x方向超出了
			 {
			    continue;//仅结束本次循环
			  }	
             cnt1=y*WIDTH+x;//取出当前屏幕像素点编号   
             cnt2=(y-y0)*320+(x-x0);	//取出当前图片像素点编号  

   	 *(pfb+cnt1)=((pdata[cnt2*3+2]<<0)|(pdata[cnt2*3+1]<<8)|(pdata[cnt2*3+0]<<16));////这里的像素矩阵和cnt有线性关系,所以可以这样写
	/*左值考虑当前像素点在fb中的偏移量*/
      /*右值考虑当前像素点在图像数据数组的下标(这里是三个为一个元素的数组的下标,因此上面会*3)*/

	}
    }
}

在主函数中调用该函数:

//fb_draw_picture7(900,100);
	fb_draw_picture8(900,100,gImage_320480);//图片大小超出屏幕范围测试

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Tn3LV1BO-1570028293266)(682FEBF34B51492B922EC770ED1D7FC1)]

七、BMP图片的显示

1、图片文件的本质

(1)图片文件是二进制文件。
文件分两种,即二进制文件、文本文件;

(2)不同格式的图片文件的差别。
图片被压缩与否的区别。

(3)BMP图片的基本特征
未被压缩的元素位图格式(bit map)。

2、BMP图片详解

(1)如何识别BMP文件?
每种图片格式都有定义好的一种识别方法,BMP图片特征是以0x424D开头;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0cOqRkx3-1570028293267)(0078B254880F41FA8EA756D6AEF94BA2)]

(2)BMP文件组成
头信息+有效信息

3、BMP文件头信息、图片有效数据区

以下对BMP文件的测试都是参考这里:https://blog.csdn.net/oqqHuTu12345678/article/details/78862613

(1)文件大小

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RYjtiQur-1570028293268)(F124EA63B0BC497AB887E31665689473)]

(2)有效数据开始的位置

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kojTUOTh-1570028293292)(4030384155844CF2AB82D92A32C1EC27)]

(3)信息头的大小:40个字节

(4)分辨率

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QIiMZfWn-1570028293293)(88E96E617AF2439C82B9D8C3B68FCE86)]

(5)24位真彩色

4、写代码解析BMP图片

  • 第一步:打开BMP图片(并将BMP文件放入工程中)
  • 第二步:判断图片格式是否真是BMP
  • 第三步:解析头信息,得到该BMP图片的详细信息
  • 第四步:根据第三步得到的信息,去合适位置提取真正的有效图片信息
  • 第五步:将得到的有效数据丢到fb中去显示
  • 这样实际比较繁琐!使用结构体比较好!

①.新建Fb_bmp.c文件,写入程序如下:

//path是bmp图片的pathname
//该函数解析path图片,并将图片数据丢到bmp_buf中
//返回值错误时返回-1,正确返回0
int bmp_analyze(unsigned char *path)
{

	int fd=-1;
	unsigned char buf[54]={0};
	ssize_t ret=-1;

	
	//第一步:打开BMP图片
	fd=open(path,O_RDONLY);
	if(fd<0)
		{
			fprintf(stderr,"open %s error.\n",path);
			return -1;	 	
		}

	// 第二步: 读取文件头信息
	ret=read(fd,buf,54);//这里为什么是54,因为有效数据开始的位置地址为0x36=54
	if(ret!=54)
		{
			fprintf(stderr,"read file header error.\n");
			return -1;	 	
		}
	
	// 解析头
	// 第三步: 判断是不是BMP图片
	if(buf[0]!='B'||buf[1]!='M')//BMP头肯定为BM
		{
			fprintf(stderr,"file %s is not a bmp picture.\n",path);
			return -1;
		}
 
	printf("file %s is a bmp picture....ok\n",path);
    
    // 第四步:
	printf("width is %d\n",*((unsigned int*)(buf+0x12)));//这里为什么是buf+0x12,因为0x12是位图的宽度
	printf("hith   is %d\n",*((unsigned int*)(buf+0x16)));//0x16是位图的高度
	close(fd);
	return 0;
}

②. 函数分析:这里为什么是54???

ret=read(fd,buf,54);//这里为什么是54,因为有效数据开始的位置地址为0x36=54

因为有效数据开始的位置地址为0x36=54
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4gWJOTD0-1570028293294)(41C67C8DFD754CA89F9D1BEC321CF639)]

为什么这里使用了buf+0x12??

printf("width is %d\n",
*((unsigned int*)(buf+0x12)));//这里为什么是buf+0x12,
因为0x12是位图的宽度printf("hith   is %d\n",*((unsigned int*)(buf+0x16)));//0x16是位图的高度

原因如下,这是在上面的博客地址中BMP头信息的地址
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cdIb2y69-1570028293295)(EB2DCC1F79B4439C9E2EDD771061DC7B)]

③.主函数中调用该函数

bmp_analyze("cute_pic.bmp");//必须保证在工程中已经存在该BMP文件

且修改dispaly子文件夹下的Makefile,包含Fb_bmp.c,不然编译肯定报错

obj-y += Fb_bmp.o

④.编译拷贝到跟文件系统,然后运行run.shu脚本,打印BMP信息正确。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pWkAAKht-1570028293296)(BC2C0AC96CA94264A9E629DA27D9D3C0)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SK6ebTeN-1570028293297)(ABC4C30F2F03460BBE73F7C8FC8D346E)]

5、用结构体方式解析BMP信息头

①.在fb_bmp.h中添加一下结构体,此结构体是百度BMP信息头结构体得来

typedef struct
{
   unsigned  char    bfType[2];//文件类?
   unsigned long    bfSize; //位图大小 
   unsigned short   bfReserved1; //位0 
   unsigned short   bfReserved2; //位0 
   unsigned long    bfOffBits;//到数据偏移量
}__attribute__((packed))BitMapFileHeader;//使编译器不优化,其大小为14字节 
 
//信息头结构体
typedef struct 
{ 
    unsigned long biSize;// BitMapFileHeader 字节数
    long biWidth;//位图宽度 
    long biHeight;//位图高度,正位正向,反之为倒图 
    unsigned short biPlanes;//为目标设备说明位面数,其值将总是被设为1
    unsigned short biBitCount;//说明比特数/象素,为1、4、8、16、24、或32。 
    unsigned long biCompression;//图象数据压缩的类型没有压缩的类型:BI_RGB 
    unsigned long biSizeImage;//说明图象的大小,以字节为单位 
    long biXPelsPerMeter;//说明水平分辨率 
    long biYPelsPerMeter;//说明垂直分辨率 
    unsigned long biClrUsed;//说明位图实际使用的彩色表中的颜色索引数
    unsigned long biClrImportant;//对图象显示有重要影响的索引数,0都重要。 
} __attribute__((packed)) BitMapInfoHeader;

②.将判断一个图片文件是不是一个合法的bmp文件从新封装为一个函数,然后在主函数中先调用该函数,判断是否为一个合法的bmp文件

// 判断一个图片文件是不是一个合法的bmp文件
// 返回值: 如果是则返回0,不是则返回-1
int is_bmp(const char *path)
{

int fd=-1;
unsigned char buf[2]={0};
ssize_t ret=-1;

//第一步:打开BMP图片
fd=open(path,O_RDONLY);
if(fd<0)
	{
		fprintf(stderr,"open %s error.\n",path);
		return -1;	 	
	}

// 第二步: 读取文件头信息
ret=read(fd,buf,2);//只读取文件头2个字节
if(ret!=2)
	{
		fprintf(stderr,"read file header error.\n");
		ret = -1;
		goto close;
	}

// 解析头
// 第三步: 判断是不是BMP图片
if(buf[0]!='B'||buf[1]!='M')//BMP头肯定为BM
	{
		fprintf(stderr,"file %s is not a bmp picture.\n",path);
		ret = -1;
		goto close;
	}
else
	{
		ret=0;
	}

printf("file %s is a bmp picture....ok\n",path);

close:
close(fd);
return ret;
}

③.调用BMP头信息结构体,实现解析BMP图片数据,并将数据丢到FB中显示

//path是bmp图片的pathname
//该函数解析path图片,并将图片数据丢到bmp_buf中
//返回值错误时返回-1,正确返回0
int bmp_analyze(unsigned char *path)
{

	int fd=-1;
	BitMapFileHeader fheader;
	BitMapInfoHeader info;
	ssize_t ret=-1;
	int i=0;

	
	//第一步:打开BMP图片
	fd=open(path,O_RDONLY);
	if(fd<0)
		{
			fprintf(stderr,"open %s error.\n",path);
			return -1;	 	
		}

	// 第二步: 读取文件头信息
	ret=read(fd,&fheader,sizeof(fheader));
	printf("bfsize =%d.\n",fheader.bfSize);//位图大小
	printf("boffsize =%d.\n",fheader.bfOffBits);//有效信息偏移量

#if 	0
	for( i=0;i<sizeof(fheader);i++)//测试打印出的字节数据是否一致
	{
         printf("%x ",*((unsigned char *)&fheader+i));
		 
	}
       printf("\n");
#endif
	 ret= read(fd,&info,sizeof(info));/*会沿着继续读下去*/
 	printf("picture resolution : %d * %d.\n",info.biHeight,info.biWidth);//读取位图宽度和高度
	 printf("picture bpp : %d \n",info.biBitCount);//像素比特数

      // 第三步: 读取图片有效信息
	// 先把文件指针移动到有效信息的偏移量处
	//然后读出图片大小=info.biHeight*info.biWidth*info.biBitCount/8个字节,这个公式在驱动frambuff有说明
	lseek(fd,fheader.bfOffBits,SEEK_SET);//从文件开始的位置开始	 
	unsigned long len=info.biHeight*info.biWidth*info.biBitCount /8;  
	printf("len put to buff  :%ld.\n",len);	

	read(fd,BMP_BUF_SIZE,len);	 	 	
	//把内容丢到fb中去显示	      
	fb_draw(BMP_BUF_SIZE);//用于测试使用结构体来解析图片数据时,图片会180°显示后还原显示函数
	/*数据和使用image2lcd获取的不一样        
	*这里会旋转180度颠倒       
	*即第一个像素放到最后一个像素点像素,依次类推了	 */

	close(fd);
	return 0;

}

====> 细节:上面的这段代码用于测试打印出的像素数据是否一致

#if  1
	for( i=0;i<sizeof(fheader);i++)//测试打印出的字节数据是否一致
	{
         printf("%x ",*((unsigned char *)&fheader+i));
		 
	}
       printf("\n");
#endif

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iTFQuaXg-1570028293298)(DF2F4E57A4DF40A593A45C8E4183301E)]

④.重新修改图片显示函数

注意:

  • 使用结构体来解析图片数据时,和使用image2lcd不一样。
  • 这里会旋转180度颠倒,即第一个像素放到最后一个像素点像素,依次类推了。
  • 因此在fb_draw()函数中,需要将最后一个像素点数据放在第一个像素处显示,以此类推。
//用于测试使用结构体来解析图片数据时,图片会180°显示后还原显示函数
void fb_draw(unsigned char* pdata)
{
    //unsigned char* pdata=gImage_320480;
    unsigned int x,y,cnt;
    unsigned int a=0;

/*数据和使用image2lcd获取的不一样 这里会旋转180度颠倒
即第一个像素放到最后一个像素点像素,依次类推了*/
a= HEIGHT*WIDTH*3-3;//让a一开始就指向了最后一个像素的第一个字节 
for(y=0;y<HEIGHT;y++)
    {
for(x=0;x<WIDTH;x++)
{
                  cnt=y*WIDTH+x;/*cnt始终都是framebuff像素点的编号*/
      		     *(pfb+cnt)=((pdata[a+0]<<16)|(pdata[a+1]<<8)|(pdata[a+2]<<0));
		     a-=3;/*由于空缺一部分,像素点编号与像素点矩阵不存在倍数关系了,此时应该三个三个地传送进来*/
                         
}
}
}

主函数调用:

is_bmp("cute_pic.bmp");
	bmp_analyze("cute_pic.bmp");

smen_len = xres_virtual * yres_virtual * bpp / 8

若调用之前没有修改的显示图片函数,则会180°倒置 如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EpPJinvf-1570028293301)(1E94ED09D0EB467E824F9370DDCEE951)]

调用上面修改之后的fb_draw函数,则会正常显示如下:”

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-g5dpCreK-1570028293301)(FCDB3B7AE5354B6CAED5EB7C109086FD)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7RqjGE1N-1570028293302)(D2A7680579BC45439B8F0D4123910267)]

八、及时规整

1、再次强调规范问题

  • (1)函数、变量起名字要合法合理;小写函数;
  • (2)要写注释;第一步,第二步……可以先写注释再写代码;
  • (3)函数长短要合适;
  • (4)多文件组织,每个东西丢到合理的位置

2、为什么要规整项目?

  • (1)完全自由写项目时不可能一步到位,只能先重内容和功能,后补条理和规范;
  • (2)规整的过程也是一个梳理逻辑和分析架构的过程。

3、对本项目进行规整

(1)去掉测试显示时头文件形式提供的图片显示相关的东西

  • 即去除fb_draw_picture_n()测试函数,因为我们的最终目的是解析bmp格式图片(已经实现了,因此要删除之前的测试函数)

4、一些重构代码的技巧

  • (1)用#if 0 #endif来屏幕不需要的代码,不要用/* */;
  • (2)暂时不要的代码先不要删除,而是屏幕掉

5、添加DEBUG宏以控制调试信息输出

debug宏添加好后,要使能输出可以有2种方式:

第一种:在debug宏定义之前定义DEBUG宏。

#define	DEBUG 	//打开调试信息输出的开关
// debug宏的定义
#ifdef DEBUG
#define debug(...)                                                      \
        {                                                               \
            fprintf(stderr, "[debug][%s:%s:%d] ",                     \
                    __FILE__, __FUNCTION__, __LINE__);                  \
            fprintf(stderr, __VA_ARGS__);                               \
        }
#else
#define debug(...)  
#endif

第二种:在编译参数中添加-DDEBUG编译选项。(在makefile中添加)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-L3HQS8vw-1570028293303)(AB4DB63A7A10497DBD3A7ECDD8AB4127)]

6、图片信息用结构体来封装传递

//封装图片各种信息

//封装图片各种信息
typedef struct pic_info
{
    char* pathname;//路径和文件名字
    unsigned int width;//位图宽度
    unsigned int height;//位图高度
    unsigned int bpp;//位图bpp
     char *pData;//指向图片有效数据存储的buff
}pic_info;

从而需要修改相关代码。

①.主要是Fb_bmp.c中的bmp_analyze函数

//path是bmp图片的pathname
//该函数解析path图片,并将图片数据丢到bmp_buf中
//返回值错误时返回-1,正确返回0
int bmp_analyze(struct pic_info *pPic)
{

int fd=-1;
BitMapFileHeader fheader;
BitMapInfoHeader info;
ssize_t ret=-1;
unsigned long len;
//int i=0;


//第一步:打开BMP图片
fd=open(pPic->pathname,O_RDONLY);
if(fd<0)
{
fprintf(stderr,"open %s error.\n",pPic->pathname);
return -1;
}

// 第二步: 读取文件头信息
ret=read(fd,&fheader,sizeof(fheader));
debug("bfsize =%ld.\n",fheader.bfSize);//位图大小
debug("boffsize =%ld.\n",fheader.bfOffBits);//有效信息偏移量

#if  0
for( i=0;i<sizeof(fheader);i++)//测试打印出的字节数据是否一致
{
         printf("%x ",*((unsigned char *)&fheader+i));
		 
}
       printf("\n");
#endif
ret= read(fd,&info,sizeof(info));/*会沿着继续读下去*/
 	 debug("picture resolution : %ld * %ld.\n",info.biHeight,info.biWidth);//读取位图宽度和高度
debug("picture bpp : %hu \n",info.biBitCount);//像素比特数

pPic->width = info.biWidth;//填充图片的各个数据
pPic->height =info.biHeight;
pPic->bpp = info.biBitCount;// 利用输出型参数

debug("image resolution: %d * %d, bpp=%d.\n", pPic->width, pPic->height, pPic->bpp);

      // 第三步: 读取图片有效信息
// 先把文件指针移动到有效信息的偏移量处
//然后读出图片大小=info.biHeight*info.biWidth*info.biBitCount/8个字节,这个公式在驱动frambuff有说明
	lseek(fd,fheader.bfOffBits,SEEK_SET);//从文件开始的位置开始	 
	 len=info.biHeight * info.biWidth * info.biBitCount /8;  
debug("len put to buff  :%ld.\n",len);

	read(fd,bmp_buf,len);	 
pPic->pData = bmp_buf;

	//把内容丢到fb中去显示	      
fb_draw(pPic);//用于测试使用结构体来解析图片数据时,图片会180°显示后还原显示函数


close(fd);
return 0;
}

②.fb.c中的fb_draw函数

//用于测试使用结构体来解析图片数据时,图片会180°显示后还原显示函数
void fb_draw(struct pic_info *pPic)
{
    char* pData=pPic->pData;	//指针指向图像数组
   //const char *pData = (const char *)pPic->pData;	
    unsigned int x,y,cnt;
    unsigned int a=0;

debug("image resolution: %d * %d, bpp=%d.\n", pPic->width, pPic->height, pPic->bpp);

if((pPic->bpp !=24))
{
fprintf(stderr,"bpp %s is not support.\n",pPic->bpp);
return -1;
}

/*数据和使用image2lcd获取的不一样 这里会旋转180度颠倒
即第一个像素放到最后一个像素点像素,依次类推了*/
a = pPic->height * pPic->width * 3 - 3;//让a一开始就指向了最后一个像素的第一个字节 
for (y=0; y<(pPic->height); y++)
{
for (x=0; x<(pPic->width); x++)
		{ 
                  cnt = WIDTH * y + x;/*cnt始终都是framebuff像素点的编号,注意这里的WIDTH是屏幕的高度*/
      		     *(pfb+cnt)=((pData[a+0]<<16)|(pData[a+1]<<8)|(pData[a+2]<<0));
a-=3;/*由于空缺一部分,像素点编号与像素点矩阵不存在倍数关系了,此时应该三个三个地传送进来*/
                         
}
}
}
                  		}	}}

主函数调用:开发板能正常显示图片,且终端打印信息正确。

struct pic_info pictrue;//要显示的图片
	// 测试bmp图片显示,ok	
	pictrue.pathname = "cute_pic.bmp";// 指向要显示的图片
	bmp_analyze(&pictrue);

在这个过程中遇到的问题:一定要多用DEBUG宏以控制调试信息输出,例如我遇到了刚开始段错误以及图片不正常显示等问题,都是跟踪debug宏输出调试信息解决:截图如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Q7suSW5M-1570028295463)(A55015739417449E9DA6413A53AF013D)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z0Y7tgBb-1570028293304)(EB2BDF4E94F447579A7E58BE8AD4BC3E)]

九、jpg图片的显示原理分析

1、认识jpg图片

(1)属于二进制文件。
(2)有其固定的识别特征,后面我们判断是否为jpg图片,需要识别特征http://www.cnblogs.com/Wendy_Yu/archive/2011/12/27/2303118.html

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kWfIx3oM-1570028293305)(028CAD3110E342D495E2A018F13EC352)]

(3)是经过压缩的图片格式。

2、jpg图片如何显示

(1)jpg图片中的二进制数并不对应像素数据。

(2)LCD显示器的接口仍然是framebuffer。

(3)要显示jpg图片必须先解码jpg得到相应的位图数据。

3、如何解码jpg图片

(1)图片编码和解码对应着压缩和解压缩过程

(2)编码和解码其实就是一些数学运算(压缩度、算法复杂度、时间、清晰度)

(3)软件编解码和硬件编解码需要频繁进行编解码的话,一般使用硬件编解码。

(4)不同的图片格式其实就是编解码的算法不同,结果是图片特征不同

(5)编程实战:使用开源编解码库

十、libjpeg介绍及开源库的使用方法

1、libjpeg介绍

(1)基于linux的开源软件;

(2)C语言编写(gcc、使用Makefile管理);

(3)提供JPEG图片的编解码算法实现;

2、libjpeg版本及下载资源

(1)经典版本v6b:https://sourceforge.net/projects/libjpeg/files/libjpeg/6b/

(2)最新版本v9b:http://www.ijg.org/

3、开源库的使用方法

(1)移植(源码下载、解压、配置、修改Makefile、编译或交叉编译)

  • 移植的目的是由源码得到三个东西:动态库.so,静态库.a,头文件.h。
    (2)部署(部署动态库so、部署静态库.a和头文件.h)
  • 动态库是程序在运行时才需要的,编译程序时不需要。
  • 静态库是静态连接时才需要,动态链接时不需要。
  • 头文件.h是在编译程序时使用的,运行时不需要的。

总结:静态库和头文件,是在编译链接过程中需要的,因此要把静态库.a文件和头文件.h文件放到ubuntu的文件系统中。
动态库是在运行时需要的,所以动态库so文件要放到开发板的文件系统中(放的过程就叫部署)。

(3)注意三个编译链接选项:-I -l(小L) -L
举例如-I:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1yTyLgOS-1570028293306)(2B410CAF37F6411894CBC0E744109972)]

  • -I 是编译选项(准确的是说是makefile预处理选项CFLAGS或者CPPFLAGS中指定),用来指定预处理时查找头文件的范围的。
  • -l(小写L)是链接选项(LDFLAGS中指定),用来指定链接额外的库的名字(譬如我们用到了数学函数,就用-lm,链接器就会去链接libm.so;那么我们使用了libjpeg,对应的库名字就叫libjpeg.so,就需要用-ljpeg选项去链接)。
  • -L是链接选项(LDFLAGS中指定),用来指定链接器到哪个路径下面去找动态链接库。

总结:-l(小写L)是告诉链接器要链接的动态库的名字,而-L是告诉链接器要链接的动态库的路径。

十一、libjpeg的移植实战

1、移植

(1)源码下载、解压至/root/decodeporting/目录下。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-42S0TZJA-1570028293307)(B5B1205C32FB487F9F3E038F90100C38)]

(2)编译前的配置(分析configure文件的usage、借鉴前辈的设置)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1Kkgv1sf-1570028293307)(377FB57A528F4FCE91DD7AB6C597EBAF)]

得到应该在/root/decodeporting/jpeg-6b目录下执行下面命令:
./configure --prefix=/opt/libcode --exec-prefix=/opt/libcode --enable-shared --enable-static-build=i386 -host=arm

说明:/opt/libcode:用于存放移植后得到的3个文件,所以我们需要先建立这个目录

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PRPsXThY-1570028293308)(AAB032DBC7124B72B33A4F0BDEC6D82F)]

指定编译结果放置的目录(如果没有则需要先建立相应的目录)
配置完成后生成了makefile文件,通过查看知道需要在/opt/libcode中创建include、bin、lib目录。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ssiiMTdD-1570028293309)(0FA0FF963EA94C1DAF84E2AF8FFB634D)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-00fVttTK-1570028293310)(8E4BBCC6A90B4D50BF14DD9FD7825A5F)]

(3)Makefile检查,主要查看交叉编译设置是否正确

CC=gcc 改为 CC=arm-linux-gcc //编译器,如果不改,只能在ubuntu使用,不能在开发板使用。
AR=ar rc 改为 AR=arm-linux-ar rc
AR2=ranlib 改为 AR2=arm-linux-ranlib

(4)编译:执行make命令。
(5)安装:执行make install-lib;
安装就是将编译生成的库文件、头文件、可执行文件分别装载到–prefix --exec-prefix所指定的那些目录中去。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2SyFEl6H-1570028293311)(4950154F750A4101A3134EF8E7337367)]

2、部署

前面只是完成移植,相关文件都在ubuntu上。我们要把动态链接库(.so文件)放到开发板的根文件系统中(/root/porting_x210/rootfs/rootfs),可以考虑三者之一:

  • 第一个:/root/porting_x210/rootfs/rootfs/lib

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iILIW5AC-1570028293312)(B2A88702CAF84D8FB75C5CA24B63EBE9)]

  • 第二个:/root/porting_x210/rootfs/rootfs/usr/lib
  • 第三个:任意指定目录
    (前两个在程序运行时,可以自动找到。后面任意目录时,需要在程序中指定)
    我们选择第二个,然后将生成的动态.so文件拷贝到/usr/lib目录下

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2n67efTl-1570028293313)(9E9B683BCA8A4CFF99DAC980FE0F1964)]

十二、使用libjpeg解码显示jpg图片

1、如何使用一个新的库

(1)思路一:网络上找别人使用过后写的文档、博客等作为参考。
(2)思路二:看库源码自带的文档(说明文档和示例代码)。我们从思路二出发。

2、libjpeg说明文档和示例代码

(1)说明文档:README和libjpeg.doc
(2)示例代码:example.c

3、结合说明文档来实践

example.c测试程序提供了两个接口:

  • 写jpeg文件
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mR27RL8z-1570028293314)(90878E2F277C49A5835043BC095FF187)]

  • 读jpeg文件
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-weJUH05t-1570028293314)(F546B1326276447E9F88F109D10587FC)]

另外,还定义了几个外部变量和一个外部函数:

extern JSAMPLE * image_buffer; 
extern int image_height; 
extern int image_width;  
void put_scanline_someplace(char *buf, int count);

这个函数就是在读jpeg文件时,扫描一行的数据通过这个函数传出

4、解读example.c和移植

  • 解读example.c
  • 根据example.c的步骤移植添加到我们之前的工程中
    建立fb_jpeg.c文件,并根据example.c读jpeg文件源码开始,移植到我们的.c文件,如下:
/*
 * 本文件用来解码jpg图片,并调用fb.c中的显示接口来显示到lcd上
 */
#include <stdio.h>
 
#include <config.h>			// for debug
#include <fb_jpeg.h>	

#include <jpeglib.h>
#include <jerror.h>
#include <string.h>



struct my_error_mgr 
{  
	struct jpeg_error_mgr pub;	/* "public" fields */  
//	jmp_buf setjmp_buffer;	/* for return to caller */
};
typedef struct my_error_mgr * my_error_ptr;


// 自己定义的错误处理函数
METHODDEF(void)my_error_exit (j_common_ptr cinfo)
{  
	//my_error_ptr myerr = (my_error_ptr) cinfo->err;  
	//(*cinfo->err->output_message) (cinfo);    
	fprintf(stderr, "my_error_exit\n");
	//longjmp(myerr->setjmp_buffer, 1);
}

/*
 * 函数功能: 解码jpg图片,并将解码出来的数据存储
 * 函数参数: pPIC,记录源jpg图片,解码出来的图片宽高、图片数据缓冲区等信息
 * 返回值  : 成功解码则返回0,失败则返回-1
 */
 
 int jpg_analyze(struct pic_info *pPic)
{
	struct jpeg_decompress_struct cinfo;		// cinfo贯穿整个解码过程的信息记录和传递的数据结构
	struct my_error_mgr jerr;					// 错误处理的	
	//JSAMPARRAY buffer = NULL;					// 指向解码行数据的指针
	char * buffer = NULL;
	FILE * infile;								// 指向fopen打开源jpg图片文件的指针
	int row_stride;								// 解码出来的一行图片信息的字节数

	if ((infile = fopen(pPic->pathname, "rb")) == NULL) 
	{    
		fprintf(stderr, "can't open %s\n", pPic->pathname);    
		return -1;  
	}

	// 第1步: 错误处理函数部分的绑定
	cinfo.err = jpeg_std_error(&jerr.pub);	
	jerr.pub.error_exit = my_error_exit;
	// 给解码器做必要的内存分配和数据结构的初始化
	jpeg_create_decompress(&cinfo);

	// 第2步: 将fopen打开的源jpg图片和解码器相关联
	jpeg_stdio_src(&cinfo, infile);

	// 第3步: 读jpg文件头
	jpeg_read_header(&cinfo, TRUE);

 //打印出图片的宽度高度信息等	
 	debug("image resolution: %d * %d, bpp/8=%d.\n", 
		cinfo.output_width, cinfo.output_height, cinfo.output_components);
	

	//  解码完了,做各种清理工作
	jpeg_destroy_decompress(&cinfo);
	fclose(infile);


	return 0;
}

主函数调用该函数:

pictrue.pathname = "cute_pic.jpg";// 指向要显示的图片
	jpg_analyze(&pictrue);

5、代码问题

(1)测试代码,先试图读取jpg图片头信息。
(2)问题排除

  • 编译时问题:主要就是头文件包含,除了在代码中包含头文件外,还要注意指明头文件的路径。
  • 因为尽管包含了头文件,但只会在当前的目录和path指定的路径中寻找。我们应该在总Makefile中指定。

注意-I、-l、-L三个编译链接选项的使用

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sopy99CJ-1570028293315)(DEA568A9B0354A7197E0266DB241057B)]

重新make&make cp,然后启动开发板,运行。run.sh脚本,能正常打印信息,但当我们把lib目录下的动态链接文件删除后,从新运行如下,显示错误。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pd0iigYt-1570028293316)(33FBA27822014DEFB0DA8FF56E14AA5C)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xM7ubpjT-1570028293317)(E61CEBABD49E427C961318ED635FB27A)]

6、部署动态库以使程序运行起来

(1)一般放到开发板根文件系统/lib或者/usr/lib下

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nUfcVOF5-1570028293318)(5E99CC674B9C4D05A5C5172E0E75E3A3)]

  • 这样不需要给系统指定库路径,就能自动找到。
  • 强调一下是开发板根文件系统下的路径,千万不要弄成了ubuntu的根文件系统下的目录。

(2)放到自定义的第三方的目录
(在开发板根文件中新建/opt/mylib/目录,用于存放动态库)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hhtPeh5p-1570028293318)(BD30339BB3674C40A22F9026B2D74B4E)]

将该自定义第三方目录导出到环境变量LD_LIBRARY_PATH下即可。

export LD_LIBRARY_PATH=/opt/mylib:$LD_LIBRARY_PATH

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HDhapWrH-1570028293319)(9352EB30DE4E4E999510DE73E8BE569A)]

  • 可以使用echo $LD_LIBRARY_PATH查看当前的路径环境变量包含哪些路径。
  • 可以把上述操作写进到run.sh脚本文件中,省得每次都要这样操作。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cgfuI58T-1570028293320)(11C16DA371884FD3B36B71D21360CB28)]

7、测试读取头信息

在fb_jpeg.c文件中的jpg_analyze函数中继续添加源码如下(注意,都是根据example.c的步骤添加修改的):

// 第1步: 错误处理函数部分的绑定
	cinfo.err = jpeg_std_error(&jerr.pub);	
	jerr.pub.error_exit = my_error_exit;
	// 给解码器做必要的内存分配和数据结构的初始化
	jpeg_create_decompress(&cinfo);

	// 第2步: 将fopen打开的源jpg图片和解码器相关联
	jpeg_stdio_src(&cinfo, infile);

	// 第3步: 读jpg文件头
	jpeg_read_header(&cinfo, TRUE);

	// 第4步: 启动解码器
	jpeg_start_decompress(&cinfo);
	
 	debug("image resolution: %d * %d, bpp/8=%d.\n", 
		cinfo.output_width, cinfo.output_height, cinfo.output_components);
	
	// 解码出来的数据一行的字节数
	row_stride = cinfo.output_width * cinfo.output_components;
	buffer = (*cinfo.mem->alloc_sarray)((j_common_ptr) &cinfo, JPOOL_IMAGE, row_stride, 1);
	if (NULL == buffer)
	{
		fprintf(stderr, "cinfo.mem->alloc_sarray error.\n");
		return -1;
	}
	// 第5步: 逐行解码,并将解码出的数据丢到事先准备好的缓冲区去
	while (cinfo.output_scanline < cinfo.output_height) 
	{	 
		// 解码一行信息,并且丢到buffer中
		jpeg_read_scanlines(&cinfo, buffer, 1);
		 
		// 将buffer中这一行数据移走到别的地方去暂存或者使用,总之是要腾出buffer空间
		// 来给循环的下一次解码一行来使用
		memcpy(pPic->pData + (cinfo.output_scanline-1) * row_stride, buffer, row_stride);
	}

	// 第6步: 解码完了,做各种清理工作
	jpeg_finish_decompress(&cinfo);
	jpeg_destroy_decompress(&cinfo);
	fclose(infile);


	//显示该图片,先填充pPic的数据
	pPic->width = cinfo.output_width;
	pPic->height = cinfo.output_height;
	pPic->bpp = cinfo.output_components * 8;
	fb_draw(pPic);

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Lxf9N4iO-1570028293321)(4C252DD4039F465C8E472329B321EC79)]

主函数调用如下:

对pictrue.pData 缓存区必须先指定填充大小,

// ②测试jpg图片显示
	pictrue.pathname = "cute_pic.jpg";// 指向要显示的图片
	pictrue.pData = bmp_buf;
	jpg_analyze(&pictrue);

从新make&&make cp.,运行脚本,开发板显示图片有问题。

十三、解决解码显示中的问题

1、问题分析及解决记录

(1)根据LCD错误的显示状态,分析有可能是显示函数fb_draw中的图片宽高数据有误,于是在fb_draw函数中添加debug打印出宽和高来。结果发现是对的。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UpJaS1Tm-1570028293322)(C18EA21AA593465AAA3BBA366367BBE2)]

(2)显示函数中的图片宽高和fb宽高都是对的,结果显示时还是只有一溜(其余位置黑屏),可能的一个原因就是:显示数据本身不对,很多都是0。

  • 如何验证?只要把显示数据打印出来看一看就知道了。
    在fb.c中的fb_draw显示函数中添加如下打印信息:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-auLfphsS-1570028293323)(B6CCE5C0E9F14E0ABB14633E95409E8B)]

  • 结果发现打印出的待显示数据是很多0,说明显示函数的待显示数据就是错的。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kaw9ATL2-1570028293324)(F395199C15B6419EBDFBF35F4AA0016B)]

(3)这些待显示数据为什么会错?

  • 第一种可能性就是libjpeg解码出来的数据就是错的;
  • 第二种可能性是解码一行出来暂存到buffer的时候,或者memcpy从暂存的buffer拿出来给pData指向的空间的时候给搞错了。
  • 相对来说第二种很好验证而第一种不好验证。只需要在jpeg_read_scanlines函数后面直接打印显示解码出来的一行数据,就可以知道是不是第二种情况。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Vkp7C8L4-1570028293326)(DAFADBC0E41F4719A566AEC825768CF9)]

  • 结果打印出来好多0,说明是第一种情况。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9XM7DlKT-1570028293326)(29E9D6CAA3DC45389EDCCE3E8E87A5D7)]

(4)截至目前已经锁定问题,就是jpeg_read_scanlines解码出来的数据本身就不对。
(5)可能的问题

  • 有可能是libjpeg本身就有问题;
  • 有可能我们对libjpeg的部署不对导致他工作不对;
  • 有可能我们写的代码不对,也就是说我们没用正确的方法来使用libjpeg。

(6)没有思路怎么办

  • 方法一:去网上找一些别人写的libjpeg解码显示图片的示例代码,多看几个,对着和我们的关键部位对比,寻找思路。
  • 方法二:如果在网上找不到相关资料,这时候就只有硬着头皮去看源码了。譬如去libjpeg的源码中查看:jpeg_read_scanlines、cinfo.mem->alloc_sarray等。
  • 怎么解决的????
    在jpg_analyze函数中修改如下源码,即可解决(即下面标红的,就是修改后的)
int jpg_analyze(struct pic_info *pPic)
{
    .......
//JSAMPARRAY buffer = NULL; // 指向解码行数据的指针 这是原生的
char * buffer = NULL;
............................
    
// 第4步: 启动解码器
jpeg_start_decompress(&cinfo);

 	debug("image resolution: %d * %d, bpp/8=%d.\n", 
cinfo.output_width, cinfo.output_height, cinfo.output_components);

// 解码出来的数据一行的字节数
row_stride = cinfo.output_width * cinfo.output_components;

/*按照提供的example.c,这里这样使用就是有问题,所以解码出来的数据全是0,故我们选择下面另一种方法,自己申请空间*/
//buffer = (*cinfo.mem->alloc_sarray)((j_common_ptr) &cinfo, JPOOL_IMAGE, row_stride, 1);
buffer = (char *)malloc(row_stride);
if (NULL == buffer)
{
fprintf(stderr, "cinfo.mem->alloc_sarray error.\n");
return -1;
}
// 第5步: 逐行解码,并将解码出的数据丢到事先准备好的缓冲区去
	while (cinfo.output_scanline < cinfo.output_height) 
	{	 
// 解码一行信息,并且丢到buffer中
//jpeg_read_scanlines(&cinfo, buffer, 1);
jpeg_read_scanlines(&cinfo, &buffer, 1);

.............
}

编译拷贝,重启开发板运行,就能显示图片了(但图片是翻过来,还有RGB不对) 。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gP5rv6a6-1570028293327)(8FD52B9118C849FEB98E343DBCCE9827)]

(7)解决了buffer申请导致的问题之后,我们再来解决2个遗留的问题

  • 一个就是RGB顺序问题,另一个是图像转了180度的问题。

void fb_draw2(const struct pic_info *pPic)
{
const char *pData = (const char *)pPic->pData;  // 指针指向图像数组
unsigned int cnt = 0, a = 0;
unsigned int x, y;

if ((pPic->bpp != 32) && (pPic->bpp != 24))
{
fprintf(stderr, "BPP %d is not support.\n", pPic->bpp);
return;
}

a = 0;
for (y=0; y<pPic->height; y++)
{
for (x=0; x<pPic->width; x++)
		{ 
//cnt表示当前像素点的编号
cnt = WIDTH * y + x;
// 当前像素点对应的图像数据的RGB就应该分别是:
			// pData[cnt+0]  pData[cnt+1]  pData[cnt+2]  
// 当前像素点的数据
*(pfb + cnt) = ((pData[a+2]<<0) | (pData[a+1]<<8)| (pData[a+0]<<16)); 
			//*p = ((pData[cnt+0]<<16) | (pData[cnt+1]<<8)| (pData[cnt+2]<<0)); 
a += 3;
}
}
}

(8)添加了fb_draw2函数并且调用后,2个遗留问题彻底解决。至此,jpg图片显示完美实现。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KdXBagMd-1570028293336)(677469A2D0244133A3AAC8DDF98E846E)]

2、结束jpg图片部分

(1)加上jpg图片格式识别:判断开头和结尾的特征字节。

  • 在fb.jipeg.c中添加is_jpg函数,用于判断一个图片文件是不是jpg图片

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oiTagJUQ-1570028293338)(1897C78CE11B49588308F94D0CBED565)]

// 函数功能: 判断一个图片文件是不是jpg图片
// 函数参数: path是图片文件的pathname
// 返回值:   如果是jpg则返回0,不是则返回1,错误返回-1
int is_jpg(const char *path)
{
	FILE *file = NULL;
	char buf[2] = {0};
	// 打开文件
	file = fopen(path, "rb");
	if (NULL == file)
	{
		fprintf(stderr, "fopen %s error.\n", path);
		fclose(file);
		return -1;
	}
	// 读出前2个字节
	fread(buf, 1, 2, file);
	debug("read: 0x%x%x\n", buf[0], buf[1]);
	// 判断是不是0xffd8
	if (!((buf[0] == 0xff) && (buf[1] == 0xd8)))
	{
		fclose(file);
		return 1;		// 不是jpg图片
	}
	// 是0xffd8开头,就继续
	// 文件指针移动到倒数2个字符的位置
	fseek(file, -2, SEEK_END);
	// 读出2个字节
	fread(buf, 1, 2, file);
	debug("read: 0x%x%x\n", buf[0], buf[1]);
	// 判断是不是0x
	if (!((buf[0] == 0xd9) && (buf[1] == 0xa)))
	{
		fclose(file);
		return 1;		// 不是jpg图片
	}

	fclose(file);	
	return 0;
}
  • 测试该函数是否正确。

主函数调用:

// ②测试jpg图片显示
	ret=is_jpg("cute_pic.jpg");
	printf("ret = %d.\n",ret);	
	pictrue.pathname = "cute_pic.jpg";// 指向要显示的图片
	pictrue.pData = bmp_buf;	
	jpg_analyze(&pictrue);

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hT8VQJZe-1570028293339)(4A9DEE52195D4D79B3D9CF90B9011F36)]

(2)对外封装好用的jpg图片显示函数

// 封装的一个对外使用的jpg显示函数
// 本函数对外只需要一个jpg图片的pathname即可,那些复杂的数据结构都是jpg显示模块内部处理的
// 正确显示图片返回0,显示过程中出错则返回-1
int display_jpg(const char *pathname)
{
	int ret = -1;
	struct pic_info picture;
	
	// 第一步: 检测给的图片是不是jpg图片
	ret = is_jpg(pathname);
	if (ret != 0)
	{
		return -1;
		printf("ret = %d.\n",ret);
	}
	printf("ret = %d.\n",ret);	
	// 第二步: 解析该jpg图片
	picture.pathname = pathname;
	picture.pData = rgb_buf;
	jpg_analyze(&picture);

	// 第三步: 显示该jpg图片
	fb_draw2(&picture);
}

(3)对外封装好用的bmp图片显示函数

// 封装的一个对外使用的bmp显示函数
// 本函数对外只需要一个bmp图片的pathname即可,那些复杂的数据结构都是bmp显示模块内部处理的
// 正确显示图片返回0,显示过程中出错则返回-1
int display_bmp(const char *pathname)
{
	int ret = -1;
	struct pic_info picture;
	
	// 第一步: 检测给的图片是不是jpg图片
	ret = is_bmp(pathname);
	if (ret != 0)
	{
		return -1;
		printf("ret = %d.\n",ret);
	}
	printf("ret = %d.\n",ret);	
	// 第二步: 显示该jpg图片
	picture.pathname = pathname;
	picture.pData = rgb_buf;
	bmp_analyze(&picture);

}

测试:

主函数调用:编译拷贝,重启运行开发板,开发板图片正常显示,先显示jpg图片,3s后显示bmp图片,完美解决所以小问题。

// ③测试新的显示接口函数jpg图片显示和bmp图片显示
 display_jpg("cute_pic2.jpg");
	sleep(3);
	display_bmp("cute_pic.bmp");

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-r9PaEWNd-1570028293339)(6A684775A885476FBB5016BC74A25F34)]

十四、解码显示png图片

1、思路分析

(1)png更像是jpg而不像是bmp;
(2)png和jpg都是压缩格式的图片,都是二进制文件,不同之处是压缩和解压缩的算法不同。
(3)通过libjpeg来编解码jpg图片,那么同样有一个libpng用来编解码png图片。
(4)工作思路和顺序
找到并移植并部署libpng,然后查readme和其他文档示例代码等来使用libpng提供的API来对png图片进行解码,并将解码出来的数据丢到framebuffer中去显示。

2、libpng移植

(1)下载源码包:

(2)解压、配置、修改Makefile、编译、部署。注意实际路径。

①解压:

root@wwj:~/decodeporting# cp /mnt/hgfs/Linux/winshare/x210kernel/tupian_project/libpng-1.6.6.tar.gz  ./

root@wwj:~/decodeporting# tar -zxvf libpng-1.6.6.tar.gz 

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kMNv4Ks3-1570028293340)(DE86545FFEF74281A0AF742027A6B5E8)]

②配置:进入libpng-1.6.6目录下,运行一下命令

./configure --host=arm-linux --enable-shared --enable-static --prefix=/opt/libcode

(3)配置出错,报错信息:configure: error: zlib not installed

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ieZ15hGA-1570028293341)(F555948F39AE4DE3BEB966D7A8B43460)]

  • 分析问题是因为libpng依赖于zlib库,所以要先移植zlib库才可以。

3、zlib移植

①下载zlib库,并拷贝到上面的/root/decodeporting/目录下,解压…这里不再赘述

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tHI4Y2ZU-1570028293342)(5F69EC1AE95E418CBB7A18A5F23DF9E1)]

②导出CC以确定配置时为arm-linux-:

root@wwj:~/decodeporting/zlib-1.2.8# export CC=arm-linux-gcc

③配置zlib库,得到makefile:

root@wwj:~/decodeporting/zlib-1.2.8# ./configure  -shared --prefix=/opt/libcode

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ym3IoAFZ-1570028293343)(D762437977594388BE1A40866B580906)]

④make && make install:执行后/out/libcode目录下的lib和include目录下就有zlib的静态库动态库和头文件了,然后回到上面的libpng的配置步骤

4、再次回到==》(2、libpng移植)配置环节

①配置:进入libpng-1.6.6目录下,配置libpng,还是报错,

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9NCHbQz5-1570028293349)(A5F829A8813A40E58DABE79307F097CB)]

原因是因为没有导出相关环境变量,所以libpng在配置的时候找不到刚才移植的zlib库的库文件和头文件。

②解决方案就是使用epport临时性的导出,在终端中依次输入以下命令:

export LDFLAGS="-L/opt/libcode/lib"
export CFLAGS="-I/opt/libcode/include"
export CPPFLAGS="-I/opt/libcode/include"

③导出后再次配置就过了,然后编译和安装

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vEjg5ap0-1570028293350)(175E21CB39F9461188F34D576F6FB4A0)]

④ make && make install

然后在/opt/libcode/lib目录下,可以看到动态库文件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ffGuKGWX-1570028293350)(99155F8BD8AE458DAFD98A667B54E254)]

5、参考源码包自带的资料开始编程

(1)readme

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-29r8cYhs-1570028293351)(C1819047A3694F849462D862B5537D6F)]

(2)libpng-manual.txt

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IX8ZQYtd-1570028293352)(150DFB1780DD47D8BAFB9E2D9B7B6878)]

(3)example.c 和 pngtest.c

  • 仔细阅读example.c源码,推敲各个函数功能,然后着手编写代码

6、开始编程

①先is_png函数使用判断一个图片文件是不是png图片

新建fb_png.c,并将之前的fb_jpeg.c中的函数原型cp过来修改,并在子文件夹下makefile包含头文件等,这里不再赘述

  • ①测试我们写的int is_png函数(函数内容是直接将example.c 中的check_if_png函数cp过来的)
int is_png(const char *path)
{
FILE *fp = NULL;
	char buf[PNG_BYTES_TO_CHECK];  

/* Open the prospective PNG file. */
	if ((*fp = fopen(file_name, "rb")) == NULL)     
		return -1;  
     /* Read in some of the signature bytes */   
	if (fread(buf, 1, PNG_BYTES_TO_CHECK, fp) != PNG_BYTES_TO_CHECK)    
		return -1;  
	/* Compare the first PNG_BYTES_TO_CHECK bytes of the signature.      Return nonzero (true) if they match */  
return(!png_sig_cmp(buf, (png_size_t)0, PNG_BYTES_TO_CHECK));
}

然后同样步骤将之前jpeg显示函数cp过来修改,我们这里只是先测试is_png函数能否工作。

int display_png(const char *pathname)
{
int ret = -1;
struct pic_info picture;

// 第一步: 检测给的图片是不是jpg图片
ret = is_png(pathname);
if (ret != 0)
{
return -1;
printf("ret = %d.\n",ret);
}
printf("ret = %d.\n",ret);

/*
// 第二步: 解析该jpg图片
picture.pathname = pathname;
picture.pData = rgb_buf;
jpg_analyze(&picture);

// 第三步: 显示该jpg图片
fb_draw2(&picture);
*/
}

主函数调用:

//④测试png图片显示
	display_png("cute_pic4.png");//测试是否为png文件

make编译报错如下:没有找到这个,原因是没有包含相关头文件 怎么解决?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-x9k8W7fl-1570028293353)(367344F78ED748C08E3B9F067829EB30)]

我们可以在ubuntu中使用grep命令查找这个变量头文件在那?

grep "PNG_BYTES_TO_CHECK" * -nR

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SCUgs6wV-1570028293354)(4333D403D98D48F39ACDC12A03395C2B)]

我们也在开始处直接宏定义即可

继续make,报错如下,包含了头文件的,为什么还是找不到这个函数,原因是主Makefile 没有链接进来

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-soHKLIVm-1570028293354)(2F887788925D4B0CB4F302C28DF961F7)]

从新make&&make cp…重启开发板,运行脚本,显示找不到lib相关动态库

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BOtjiVOT-1570028293355)(79774617725B4D5AA2CFB9897C76AF0C)]

解决方法:我们将之前生成的动态库拷贝到我们的跟文件系统的目录下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Br8MkVY1-1570028293356)(F010731D32354AEF8947303C8BC946C6)]

然后从新make &&make cp ,运行后并没有打印 is_png函数相关信息,经过debug排查问题,发现错误(方法就是使用debug定位错误在最后的png_sig_cmp函数这,然后使用SI建立libpng-1.6.6的工程,找到这个函数的原型,分析它的功能,发现正常匹配的话,返回值应该是0,但是我们这里使用!取反了,故判断不是png),修改,从新执行

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-C9ZWQPHz-1570028293357)(FD1C79F424904EABA29A61157858E5E2)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zFW82L9K-1570028293358)(16BB1996F2454DFFA52FB94F596DE013)]

② int png_analyze解码图片函数的编写(上)

这个函数参考example.c中的解码函数read_png函数了(因为过于复杂),我们选择直接在网上参考别人的。

int png_analyze(struct pic_info *pPic)
{
FILE *fp = NULL;
	png_structp png_ptr;   
png_infop info_ptr;
int color_type;
png_bytep* row_pointers;
unsigned long len = 0;
int pos = 0;
int i = 0, j = 0;

	if ((fp = fopen(pPic->pathname, "rb")) == NULL) 
	{	 
		fprintf(stderr, "can't open %s\n", pPic->pathname);    
return -1;
}

// 第1步: 相关数据结构实例化
png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);
if (png_ptr == 0)
{
fclose(fp);
return -1;
}

info_ptr = png_create_info_struct(png_ptr);
  	if (info_ptr == 0)
  	{
   		png_destroy_read_struct(&png_ptr, 0, 0);
   		fclose(fp);
   		return -1;
  	}

// 第2步: 设置错误处理函数
if (setjmp(png_jmpbuf(png_ptr)))
{
png_destroy_read_struct(&png_ptr, &info_ptr, 0);
fclose(fp);
return -1;
}

// 第3步: 将要解码的png图片的文件指针和png解码器绑定起来
png_init_io(png_ptr, fp);

// 第4步: 读取png图片信息
png_read_png(png_ptr, info_ptr, PNG_TRANSFORM_EXPAND | PNG_TRANSFORM_STRIP_ALPHA, 0);

// 第5步: 相关图片信息打印出来看一看
color_type = info_ptr->color_type;
debug("color_type = %d\n", color_type);

pPic->width = info_ptr->width;
pPic->height = info_ptr->height;
pPic->bpp = info_ptr->pixel_depth;
len = info_ptr->width * info_ptr->height * info_ptr->pixel_depth / 8;
debug("width = %u, height = %u, bpp = %u\n", pPic->width, pPic->height, pPic->bpp);
//打印png图片相关信息


return 0;

}

make 报错如下:没找到这个文件,说明我们配置libpng的时候这几个文件没有生成,需要我们手动添加到动态库。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rYJYcEAu-1570028293359)(D5CB69D31E744F34BEE454DD05A4944D)]

依次执行以下命令即可:

cp pngstruct.h  /opt/libcode/include/
cp pnginfo.h  /opt/libcode/include/

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Aw5FpM0u-1570028293359)(900D1EEE7A4B4A8694CE7BA93D12AE48)]

然后make && make cp,…从新启动开发板,运行脚本,显示如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nqXVTaoZ-1570028293360)(D2FF42F0690D49EB892F76A3EAA5F801)]

③ int png_analyze解码图片函数的编写(下)

在上一步中,已经实现将相关图片信息打印出来了,接下来就是图片的显示相关了。
继续对函数进行添加:

int png_analyze(struct pic_info *pPic)
{
// 第5步: 相关图片信息打印出来看一看
.......
// 第6步: 读取真正的图像信息
row_pointers = png_get_rows(png_ptr,info_ptr);

// 只处理RGB24位真彩色图片,其他格式的图片不管
// 第7步: 图像数据移动到我们自己的buf中
if(color_type == PNG_COLOR_TYPE_RGB)//PNG_COLOR_TYPE_RGB=2,和我们之前打印的信息要吻合
  	{
   		//memcpy(pPic->pData, row_pointers, len);/*直接这样不行,因为数据是乱的*/
for(i=0; i<pPic->height; i++)
{
for(j=0; j<3*pPic->width; j+=3)
{
pPic->pData[pos++] = row_pointers[i][j+0]; //red
pPic->pData[pos++] = row_pointers[i][j+1]; //green
pPic->pData[pos++] = row_pointers[i][j+2]; //blue
}
}
  	}
// 第8步: 收尾处理
png_destroy_read_struct(&png_ptr, &info_ptr, 0);
// close file
fclose(fp);

return 0;
}

主函数调用

//④测试png图片显示
	display_png("cute_pic4.png");

然后make && make cp,…从新启动开发板,运行脚本,显示如下,且开发板正常显示PNG图片

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RAGV2YKS-1570028293361)(DCB95ACEBFFC47B58399AF514EC13A85)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nKvrT7Dq-1570028293362)(C6561FDA37464B3DBDACCA7827858480)]

到这里,对png图片的解码显示就搞定了。

十五、目录扫描,图片文件的管理模块

1、图片文件的管理

(1)在物理磁盘存储层次上,用一个文件夹来管理;

(2)在程序中,用数据结构来管理。

  • 用数组管理
  • 用链表管理

(4)编程实战(细节见代码,下面是关键点)

  • a、新建一个文件夹image_manage,记得要在makefile中添加新建的文件夹路径,以及在新建文件夹中新建makefile来管理文件。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YEwdwJbR-1570028293363)(87268EC3BE6C44B9B855B6A0B5B53FE5)]

  • b、文件夹的打开操作、读取操作
    opendir的使用====打开文件夹

readdir的使用====读取文件夹的文件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AY0KryjT-1570028293364)(98CEAA27434D458686A02A24C4F5487E)]

2、图片信息的自动检索

linux读取文件夹内容,我们直接百度,选择这篇文章参考,修改为自己的
https://blog.csdn.net/weixin_40535588/article/details/89668934

①使用readdir函数来读取文件夹下内容

新建image_manage.h

typedef enum image_type
{
	IMAGE_TYPE_BMP,
	IMAGE_TYPE_JPG,
	IMAGE_TYPE_PNG,
	IMAGE_TPPE_UNKNOWN,
}image_type_e;

// 结构体用来封装一个图片的信息
typedef struct image_info
{
	char pathname[PATHNAME_LEN];	// 图片文件的pathname
	image_type_e type;				// 图片文件的格式
}image_info_t;

新建image_manage.c, 使用readdir函数来读取文件夹内容
(readdir函数有问题,有时可以,有时不行,仔细阅读man手册,发现readdir函数中真正起作用的是lstat函数)

// images数组本来是空的,然后程序初始化时会去一个事先约定好的目录(image目录)下去
// 递归检索所有的文件及子文件夹,并且将所有的图片格式收集并且填充记录到images数组中
// 经过检索后,image数组中就记录了所有的图片,然后显示图片逻辑部分再去这个图片库中
// 拿出相应的图片来显示即可
// path是要去检索的文件夹的目录的路径
int scan_image(const char *path)
{
	// 在本函数中递归检索path文件夹,将其中所有图片填充到iamges数组中去
	DIR *dir;
	struct dirent *ptr;
	char base[1000];

	if ((dir = opendir(path)) == NULL)
	{
		perror("Open dir error...");
		exit(1);
	}

	// readdir函数每调用一次就会返回opendir打开的basepath目录下的一个文件,直到
	// basepath目录下所有文件都被读完之后,就会返回NULL
	while ((ptr = readdir(dir)) != NULL)
	{
		if(strcmp(ptr->d_name, ".")==0 || strcmp(ptr->d_name, "..")==0)    ///current dir OR parrent dir
			continue;

		debug("d_name = %s.\n", ptr->d_name);
		debug("d_type = %d, DT_REG = %d, DT_DIR = %d, DT_UNKNOWN = %d.\n", 
			ptr->d_type, DT_REG, DT_DIR, DT_UNKNOWN);
		switch (ptr->d_type)
		{
			case DT_REG:			// 普通文件
				printf("d_name:%s/%s\n", path, ptr->d_name);
				break;
			case DT_DIR:			// 文件夹
				memset(base,'\0',sizeof(base));
				strcpy(base,path);
				strcat(base,"/");
				strcat(base,ptr->d_name);
				scan_image(base);
				break;
			case DT_UNKNOWN:		// 不识别的文件格式
				printf("unknown file type.\n");
				break;
			default:
				break;
		}
	}
}

主函数调用该函数:

scan_image("./image");//readdir读取文件夹下相关内容

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mLcsAZVL-1570028293364)(E53B4EA5A57245498B1D152C0033B06C)]

②使用lstat函数来实现读取文件夹下内容

/*
使用lstat函数来实现读取文件夹下的内容,因为使用readdir函数有问题
扫描目录,索引图片,并完成图片数据的初始化
参数:目录的路径
*/
int scan_image2(const char *path)
{
	// 在本函数中递归检索path文件夹,将其中所有图片填充到iamges数组中去
	DIR *dir;
	struct dirent *ptr;
	char base[1000];
	struct stat sta;

	if ((dir = opendir(path)) == NULL)
	{
		perror("Open dir error...");
		exit(1);
	}

	// readdir函数每调用一次就会返回opendir打开的basepath目录下的一个文件,直到
	// basepath目录下所有文件都被读完之后,就会返回NULL
	while ((ptr = readdir(dir)) != NULL)
	{
		if(strcmp(ptr->d_name, ".")==0 || strcmp(ptr->d_name, "..")==0)    ///current dir OR parrent dir
			continue;

		// 用lstat来读取文件属性并判断文件类型
		memset(base,'\0',sizeof(base));
		strcpy(base,path);
		strcat(base,"/");
		strcat(base,ptr->d_name);
		lstat(base, &sta);

		if (S_ISREG(sta.st_mode))
		{
			//printf("regular file.\n");
			//printf("d_name:%s/%s\n", path, ptr->d_name);
			// 如果是普通文件,就要在这里进行处理:
			// 处理思路就是 先判定是否属于已知的某种图片格式,如果是则放到images数组中
			// 如果都不属于则不理他
			if (!is_bmp(base))
			{
				strcpy(images[image_index].pathname, base);
				images[image_index].type = IMAGE_TYPE_BMP;
			}
			if (!is_jpg(base))
			{
				strcpy(images[image_index].pathname, base);
				images[image_index].type = IMAGE_TYPE_JPG;
			}
			if (!is_png(base))
			{
				strcpy(images[image_index].pathname, base);
				images[image_index].type = IMAGE_TYPE_PNG;
			}		
			image_index++;
			
		}
		if (S_ISDIR(sta.st_mode))
		{
			//printf("directory.\n");
			//printf("d_name:%s/%s\n", path, ptr->d_name);
			scan_image2(base);
		}
	}
}
//打印出文件夹下图片的路径和文件名
void print_images(void)
{
	int i;

	printf("iamge_index = %d.\n", image_index);
	for (i=0; i<image_index; i++)
	{
		printf("images[i].pathname = %s,		type = %d.\n", images[i].pathname, images[i].type);
	}
}

测试print_images函数,主函数调用:

scan_image2("./image");
	print_images();

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mTbZ7a2n-1570028293366)(4A769E0560F54CCD83D97AD7CED27F5E)]

//循环显示各个图片函数,并打印出文件路径和文件名

void show_images(void)
{
	int i;

	for (i=0; i<image_index; i++)
	{
		switch (images[i].type)
		{
			case IMAGE_TYPE_BMP:
				display_bmp(images[i].pathname);		break;
			case IMAGE_TYPE_JPG:
				display_jpg(images[i].pathname);		break;
			case IMAGE_TYPE_PNG:
				display_png(images[i].pathname);		break;
			default:
				break;
		}
		sleep(2);
	}
}

测试show_images函数,主函数调用://循环显示各个图片函数,并打印出文件路径和文件名

scan_image2("./image");
	//print_images();
	while(1)
		{
			show_images();
	}

编译拷贝,运行开发板,能看到循环显示我们imag目录下的图片,且终端移植打印出各个图片的类型及其路径等相关信息。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V9CU2FbQ-1570028293366)(7094E6834F6641808B5A5759E6CB6017)]

十六、添加触摸翻页功能(前提是已经学习过触摸屏驱动相关)

完成触摸屏驱动后,点击左右屏幕实现切换图片。

1、读取触摸坐标数据

参考如下触摸屏驱动移植实战中的app_input目录下的程序,并修改为我们的

在image_manage.c中新建一下函数,
开发板上的触摸屏设备文件是/dev/input/event2(我这里是event2)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rFj7AdC6-1570028293367)(06488D27A01C4AC0838088DEFECA8619)]

//定义系统中的触摸屏设备的设备名
#define DEVICE_TOUCHSCREEN			"/dev/input/event2"
/*
*检测触摸屏,根据结果实现图片上下切换
*参数:无传参
*/
int ts_updown(void)
{	
	// 第一步: 触摸屏的触摸操作检测
	int fd = -1, ret = -1;
	struct input_event ev;
	
	fd = open(DEVICE_TOUCHSCREEN, O_RDONLY);// 打开设备文件
	if (fd < 0)
	{
		perror("open");
		return -1;
	}	
	while (1)
	{
		// 读取一个event事件包
		memset(&ev, 0, sizeof(struct input_event));
		ret = read(fd, &ev, sizeof(struct input_event));
		if (ret != sizeof(struct input_event))
		{
			perror("read");
			close(fd);
			return -1;
		}		
		// 解析event包,才知道发生了什么样的输入事件
		printf("-------------------------\n");
		printf("type: %hd\n", ev.type);
		printf("code: %hd\n", ev.code);
		printf("value: %d\n", ev.value);
		printf("\n");
	}	
	// 关闭设备
	close(fd);

	// 第二步: 根据触摸坐标来翻页
 
return 0;
}

主函数调用:

//⑥测试触摸翻页显示图片功能
	scan_image2("./image");
	 ts_updown();

make &&make cp, 开发板运行该程序,触摸屏幕,终端显示坐标信息如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qj8K5A6s-1570028293368)(89E56667FE684193A3DD7DFE324C32F0)]

code 0表示x坐标,value为x的值;code 1表示y坐标,value为y的值。

2、使用触摸坐标判断并执行翻页操作

修改ts_updown函数,使其能实现触摸翻页,红色部分为修改的源码

//定义触摸翻页区域的宽度
#define   TOUCH_WIDTH			200
/*
*检测触摸屏,根据结果实现图片上下切换
*参数:无传参
*/
int ts_updown(void)
{
// 第一步: 触摸屏的触摸操作检测
int fd = -1, ret = -1;
struct input_event ev;
int i = 0;     // 用来记录当前显示的是第几个图片

fd = open(DEVICE_TOUCHSCREEN, O_RDONLY);// 打开设备文件
if (fd < 0)
{
perror("open");
return -1;
}
while (1)
{
// 读取一个event事件包
memset(&ev, 0, sizeof(struct input_event));
ret = read(fd, &ev, sizeof(struct input_event));
if (ret != sizeof(struct input_event))
{
perror("read");
close(fd);
return -1;
}

// 第二步: 根据触摸坐标来翻页
if ((ev.type == EV_ABS) && (ev.code == ABS_X))
{
// 确定这个是x坐标 如果在这个0~200的宽度范围
if ((ev.value >= 0) && (ev.value < TOUCH_WIDTH))
{
// 上翻页
if (i-- <= 1)
{
i = image_index;
debug("i=%d.\n", i);
}

} //如果在这个1024-200 ~1024的宽度范围
else if ((ev.value > (WIDTH - TOUCH_WIDTH)) && (ev.value <= WIDTH))
{
// 下翻页
if (i++ >= image_index)
{
i = 1;
debug("i=%d.\n", i);
}
}
else
{
// 不翻页
}
show_image(i - 1);//将i用来记录当前显示的是第几个图片,然后传给这个函数,用于显示图片

}

/*
// 解析event包,才知道发生了什么样的输入事件
printf("-------------------------\n");
printf("type: %hd\n", ev.type);
printf("code: %hd\n", ev.code);
printf("value: %d\n", ev.value);
printf("\n");
*/
}
// 关闭设备
close(fd);


return 0;

}

主函数调用该函数

//⑥测试触摸翻页显示图片功能
scan_image2("./image");
 ts_updown();

make &&make cp, 开发板运行该程序,触摸屏幕,终端显示坐标信息如下:

(1)执行./run.sh后会阻塞,如果点击触摸屏,会在终端中显示测试的内容。
(2)在不同区域点一下,有不同的效果。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hGATF2JG-1570028293369)(C0C0B0C9E68547D79AE8D4A06B25A431)]

十七、总结与回顾

1、项目总结

(1)项目描述:软硬件平台等

硬件平台使用使用s5pv210开发板,软件以linux平台开发。
支持解码显示bmp,png,JPEG图片,并通过点击触摸屏左右两端实现上下切换图片的效果

(2)重点和难点

第三方库libpng、 libgpeg库的移植
图片文件的管理和检索,刚开始使用read函数,出现问题,后来使用lstat函数来实现读取文件夹下内容,实现目录扫描

(3)主要技术:

  • linux Framebuffer驱动移植
  • linux input输入子系统移植
  • linux i2c子系统
  • 触摸屏驱动移植
  • libjpeg库移植
  • libpng库移植

1.1.驱动模块(驱动模块主要的任务就是进行Framebuffer地址映射)

  • linux Framebuffer驱动
    参考前面我们学习的framebufer驱动,移植framebufer驱动。
  • 触摸屏(gslX680)驱动
    参考驱动篇触摸屏驱动,移植gslX680驱动。

1.2.目录扫描,图片管理模块

  • 目录扫描功能采用递归方法,扫描目录的所有文件,经过排除,最后只对普通文件做进一步处理。图片管理采用数组来组织。

1.3.图片切换模块

  • 完成触摸屏驱动后,点击左右屏幕实现切换图片。

1.4.图片显示模块

  • BMP显示

通过判断头两个字符是否为”BM”,判断是否为BMP图片,BMP本身没有对数据进行压缩,所以可直接读取数据进行操作

  • JPEG显示

判断是否为JPEG图片, 通过判断特点位是否为JPEG专用字符,进而确认为JPEG图片后,解码jpg图片(使用libjpeg库提供JPEG图片的编解码算法实现),并将解码出来相应的位图数据进行存储。显示该图片

  • 显示PNG图片

通过调用libpng库函数png_sig_cmp,判断是否为PNG图片。显示PNG图片,运用libpng库,完成PNG图片的显示

TOUCH_WIDTH 200



/*
*检测触摸屏,根据结果实现图片上下切换
*参数:无传参
*/
int ts_updown(void)
{
// 第一步: 触摸屏的触摸操作检测
int fd = -1, ret = -1;
struct input_event ev;
int i = 0; // 用来记录当前显示的是第几个图片

fd = open(DEVICE_TOUCHSCREEN, O_RDONLY);// 打开设备文件
if (fd < 0)
{
perror(“open”);
return -1;
}
while (1)
{
// 读取一个event事件包
memset(&ev, 0, sizeof(struct input_event));
ret = read(fd, &ev, sizeof(struct input_event));
if (ret != sizeof(struct input_event))
{
perror(“read”);
close(fd);
return -1;
}

// 第二步: 根据触摸坐标来翻页
if ((ev.type == EV_ABS) && (ev.code == ABS_X))
{
// 确定这个是x坐标 如果在这个0~200的宽度范围
if ((ev.value >= 0) && (ev.value < TOUCH_WIDTH))
{
// 上翻页
if (i-- <= 1)
{
i = image_index;
debug(“i=%d.\n”, i);
}

} //如果在这个1024-200 ~1024的宽度范围
else if ((ev.value > (WIDTH - TOUCH_WIDTH)) && (ev.value <= WIDTH))
{
// 下翻页
if (i++ >= image_index)
{
i = 1;
debug(“i=%d.\n”, i);
}
}
else
{
// 不翻页
}
show_image(i - 1);//将i用来记录当前显示的是第几个图片,然后传给这个函数,用于显示图片

}

/*
// 解析event包,才知道发生了什么样的输入事件
printf("-------------------------\n");
printf(“type: %hd\n”, ev.type);
printf(“code: %hd\n”, ev.code);
printf(“value: %d\n”, ev.value);
printf("\n");
*/
}
// 关闭设备
close(fd);

return 0;

}



## 主函数调用该函数
	//⑥测试触摸翻页显示图片功能
	scan_image2("./image");
	 ts_updown();

make &&make cp, 开发板运行该程序,触摸屏幕,终端显示坐标信息如下:

(1)执行./run.sh后会阻塞,如果点击触摸屏,会在终端中显示测试的内容。
(2)在不同区域点一下,有不同的效果。

[外链图片转存中...(img-hGATF2JG-1570028293369)]

 
# 十七、总结与回顾
## 1、项目总结
### (1)项目描述:软硬件平台等
硬件平台使用使用s5pv210开发板,软件以linux平台开发。
支持解码显示bmp,png,JPEG图片,并通过点击触摸屏左右两端实现上下切换图片的效果

### (2)重点和难点
第三方库libpng、 libgpeg库的移植
图片文件的管理和检索,刚开始使用read函数,出现问题,后来使用lstat函数来实现读取文件夹下内容,实现目录扫描

### (3)主要技术:
- linux Framebuffer驱动移植
- linux input输入子系统移植
- linux i2c子系统
- 触摸屏驱动移植
- libjpeg库移植
- libpng库移植

#### 1.1.驱动模块(驱动模块主要的任务就是进行Framebuffer地址映射)
- linux Framebuffer驱动
参考前面我们学习的framebufer驱动,移植framebufer驱动。
- 触摸屏(gslX680)驱动
参考驱动篇触摸屏驱动,移植gslX680驱动。

#### 1.2.目录扫描,图片管理模块
- 目录扫描功能采用递归方法,扫描目录的所有文件,经过排除,最后只对普通文件做进一步处理。图片管理采用数组来组织。

#### 1.3.图片切换模块
- 完成触摸屏驱动后,点击左右屏幕实现切换图片。

#### 1.4.图片显示模块
- BMP显示

通过判断头两个字符是否为”BM”,判断是否为BMP图片,BMP本身没有对数据进行压缩,所以可直接读取数据进行操作
- JPEG显示

判断是否为JPEG图片, 通过判断特点位是否为JPEG专用字符,进而确认为JPEG图片后,解码jpg图片(使用libjpeg库提供JPEG图片的编解码算法实现),并将解码出来相应的位图数据进行存储。显示该图片
- 显示PNG图片

通过调用libpng库函数png_sig_cmp,判断是否为PNG图片。显示PNG图片,运用libpng库,完成PNG图片的显示

















没有更多推荐了,返回首页