一. jpeg介绍
jpeg (jpg)是一种国际图片压缩标准格式
手机照片
网页图片用jpeg算法压缩的格式。 "jpeg算法"
jpeg的所有压缩和解压缩的代码都是一个开源的第三方库(libjpeg)提供的
只需要使用一下libjpeg中的函数解压缩jpg图片即可获取图片中的像素数据libjpeg是什么呢?
是一个开源的第三方库,与jpeg图片的压缩和解压缩有关系,压缩和解压缩都需要使用libjpeg库
libjpeg的开源的库:
compress:压缩 RGB数据 -> jpeg图片
decompress:解压缩 jpeg图片 -> RGB数据
目的:学会第三方库的使用和移植(不仅仅是jpeg)
一般在网络上面下载的仅仅是源代码,需要把源代码编译成动态库或者静态库的形式才可以使用使用自己的程序去调用libjpeg库中的功能
libjpeg库,放到x86上面使用: gcc
libjpeg库,放到开发板上面使用: arm-linux-gcc
移植/适配:把库放到开发板或者其他的宿主机器中使用
二. libjpeg库移植及使用
移植步骤:
1. 下载并且解压源码(jpegsrc.v8a.tar.gz)
(不要解压在共享目录里面,共享目录是windows下面的文件系统,它不支持链接文件)
mkdir ~/libjpeg
cp /mnt/hgfs/CS2406F/2024-1-22文件IO/2024-1-24项目知识点/03jpeg库的移植和jpeg图片的显示/jpegsrc.v8a.tar.gz /home/china/libjpeg/
cd ~/libjpeg
tar -zxvf jpegsrc.v8a.tar.gz
2. 编译源代码生成库文件
cd jpeg-8a
// 创建一个文件夹,用来保存编译之后生成的库文件等信息
mkdir /home/china/libjpeg/arm_libs
// 配置,根据指定信息配置编译选项
(直接配置./configure,会检测系统信息,认为你需要安装到当前的系统)
// 我们的库是需要放到开发板上面去运行的,所以在配置的时候需要指定主机架构和库文件的生成路径
./configure --host=arm-linux --target=arm-linux--prefix=/home/china/libjpeg/arm_libs CC=arm-linux-gcc
自动生成
=> Makefile
3.
根据Makefile 编译源代码
make
4. 因为你在配置的时候,指定了安装路径 /home/china/libjpeg/arm_libs
// 安装
make install
5. 所有的库都安装在指定的目录中
/home/china/libjpeg/arm_libs
因为是动态库,在编译和链接的时候都需要使用动态库
libjpeg.so
libjpeg.so.8
libjpeg.so.8.0.1
把需要的库,下载到开发板的/lib目录中
如果没有放到/lib中,在使用的时候需要指定库的加载路径
到此为止,jpeg库的编译移植就完成了,就可以在开发板上面使用jpeg库了
利用libjpeg库解压jpeg文件的步骤:
1. 分配并初始化一个jpeg解压对象
struct jpeg_decompress_struct 这个结构体在libjpeg这个库中,
是用来保存解压一个jpeg文件所需要的信息的struct jpeg_error_mgr 这个结构体在libjpeg这个库中,
是用来保存解压或压缩过程的一些出错信息的典型代码:
struct jpeg_decompress_struct dinfo; // 声明一个解压的对象
struct jpeg_error_mgr jerr; // 声明一个出错信息的对象
dinfo.err = jpeg_std_error(&jerr); // 初始化这个出错对象
jpeg_create_decompress(&dinfo); // 初始化dinfo这个解压对象2. 指定要解压缩的图像文件
图像文件(jpeg文件)来源有两个:
1) 一个在文件系统中(路径名)
2) 在内存中(内存地址,长度)
打开文件,把所有的内容读取(read)到指定的内存地址
1) 用标准IO去打开这个文件
FILE *infile;
infile = fopen("xxx.jpg", "r");
if (infile == NULL)
{}
jpeg_stdio_src(&dinfo, infile); // 指定要解压的图像文件
2)
jpeg_mem_src(&dinfo, pbuf, len);
// 指定要解压的图像在pbuf指向的内存中,并且长度为len
3. 调用jpeg_read_header()获取图像信息
jpeg_read_header(&dinfo, TRUE);
4. 用于设置jpeg解压对象dinfo的一些参数。
可采用默认参数5. 调用jpeg_start_decompress()启动解压过程
jpeg_start_decompress(&dinfo);
调用jpeg_start_decompress函数之后,JPEG解压对象dinfo中
下面这几个字段(成员变量)将会比较有用:
dinfo.output_width: 图像输出宽度,一行占多少个像素点
dinfo.output_height: 图像输出高度,占多少行
dinfo.output_components: 每个像素点的分量数,每个像素点占多少个字节
3: R G B
4:A R G B
width * height * components 图像大小在调用jpeg_start_decompress之后,往往需要为解压后的扫描线上的所有像素点分配存储空间:
可以每一次读取一行解压后的像素点信息(RGB)
开辟合适的空间,存储那一行的所有像素点的信息。
存一行: output_width * output_components6. 读取一行或者多行扫描线上数据并处理,通常的代码是这样子的:
unsigned char *buffer = (unsigned char*)malloc(dinfo.output_width * dinfo.output_components);
// dinfo.output_scanline 表示的意思是,已经扫描了多少行
while (dinfo.output_scanline < dinfo.output_height) {
jpeg_read_scanlines(&dinfo, // 解压对象
&buffer, // 保存解压后数据的二级指针,
1 // 读取多少行数据来解压
);
// dinfo.output_scanline + 1
}
对扫描线的读取是按照从上到下的顺序进行的,也就是说图像最上方的扫描线最先被jpeg_read_scanlines()读入到存储空间中,紧接着是第二行扫描线,最后是图像底边的扫描线被读入到存储空间中去
一行一行的把jpeg图像的信息(RGB)拿出来7. 调用jpeg_finish_decompress()完成解压过程
jpeg_finish_decompress(&dinfo);
8. 调用jpeg_destroy_decompress释放jpeg解压对象dinfojpeg_destroy_decompress(&dinfo);
// 关闭前面打开的文件
fclose(infile);
代码实现:
// 1.分配并初始化一个jpeg解压对象 struct jpeg_decompress_struct dinfo; // 声明一个解压的对象 struct jpeg_error_mgr jerr; // 声明一个出错信息的对象 dinfo.err = jpeg_std_error(&jerr); // 初始化这个出错对象 jpeg_create_decompress(&dinfo); // 初始化dinfo这个解压对象 // 2.指定要解压缩的图像文件 FILE *infile = fopen("1.jpg", "r"); // 用标准IO去打开这个文件 if (infile == NULL) { perror("in jpeg decompress fopen failed"); return -1; } jpeg_stdio_src(&dinfo, infile); // 指定要解压的图像文件 // 3.调用jpeg_read_header()获取图像信息 jpeg_read_header(&dinfo, TRUE); // 4.用于设置jpeg解压对象dinfo的一些参数。可采用默认参数 // 5.调用jpeg_start_decompress()启动解压过程 jpeg_start_decompress(&dinfo); // 调用jpeg_start_decompress函数之后,JPEG解压对象dinfo中 // 下面这几个字段(成员变量)将会比较有用: // dinfo.output_width: 图像输出宽度,一行占多少个像素点 // dinfo.output_height: 图像输出高度,占多少行 // dinfo.output_components: 每个像素点的分量数,每个像素点占多少个字节 // 在调用jpeg_start_decompress之后,往往需要为解压后的扫描线上的所有像素点分配存储空间:可以每一次读取一行解压后的像素点信息(RGB) printf("dinfo.output_width = %d\n", dinfo.output_width); printf("dinfo.output_height = %d\n", dinfo.output_height); printf("dinfo.output_components = %d\n", dinfo.output_components); // 6.读取一行或者多行扫描线上数据并处理,通常的代码是这样子的: unsigned char *buffer = (unsigned char*)malloc(dinfo.output_width * dinfo.output_components); // dinfo.output_scanline 表示的意思是,已经扫描了多少行 int i, j = 0; unsigned char a = 0, r, g, b; // 一行一行的把jpg图像信息(RGB)拿出来,并且显示 while (dinfo.output_scanline < dinfo.output_height) { jpeg_read_scanlines(&dinfo, // 解压对象 &buffer, // 保存解压后数据的二级指针, 1 // 读取多少行数据来解压 ); // dinfo.output_scanline + 1 // 解析当前行的像素信息(保存到了buffer中) char *p = buffer; for (i = 0; i < dinfo.output_width; i++) { // 一行有dinfo.output_width个点 if (dinfo.output_components == 4) { a = *p++; } r = *p++; g = *p++; b = *p++; int color = a << 24 | r << 16 | g << 8 | b; *(plcd + j * 800 + i) = color; } j++; } free(buffer); // 7.调用jpeg_finish_decompress()完成解压过程 jpeg_finish_decompress(&dinfo); // 8.调用jpeg_destroy_decompress释放jpeg解压对象dinfo jpeg_destroy_decompress(&dinfo); // 关闭前面打开的文件 fclose(infile);
pic代码实现
pic.h
#ifndef __PIC_H__ #define __PIC_H__ #include "lcd.h" // #include "jpeglib.h" /* lcd_draw_bmp:把bmpname表示的图片显示到lp表示的屏幕的(x0,y0)位置 */ void lcd_draw_bmp(lcd *lp, char *bmpname, int x0, int y0); /* lcd_draw_jpg:把指定的jpg图片显示到开发板的指定的位置 lp:你要操作屏幕结构体的地址 jpgname:图片的路径名 x0,y0:图片的显示位置坐标(显示到开发板的哪一个位置) 返回值:无 */ // void lcd_draw_jpg(lcd *lp, char *jpgname, int x0, int y0); #endif
pic.c
#include "pic.h" /* lcd_draw_bmp:把bmpname表示的图片显示到lp表示的屏幕的(x0,y0)位置 */ void lcd_draw_bmp(lcd *lp, char *bmpname, int x0, int y0) { // 打开BMP图片 int fd = open(bmpname, O_RDWR); if (fd == -1) { perror("open bmp failed"); return ; } // 读取BMP图片的像素信息到内存 char buf[lp->xres * lp->yres * lp->bits_per_pixel + 54]; memset(buf, 0, sizeof(buf)); ssize_t r_bmp = read(fd, buf, lp->xres * lp->yres * lp->bits_per_pixel + 54); if (-1 == r_bmp) { perror("read bmp failed"); close(fd); return ; } // 关闭BMP图片 close(fd); int size = 0, w = 0, h = 0; size = buf[5] << 24 | buf[4] << 16 | buf[3] << 8 | buf[2]; w = buf[21] << 24 | buf[20] << 16 | buf[19] << 8 | buf[18]; h = buf[25] << 24 | buf[24] << 16 | buf[23] << 8 | buf[22]; int colour_dep = buf[29] << 8 | buf[28]; // 判断是否超出屏幕范围 if (x0 + w > lp->xres || y0 + h > lp->yres) { printf("brother,Out of range\n"); return ; } // 把读取到的像素点信息解析出来,显示到屏幕对应的位置 int i, j, k = 54; unsigned char a, r, g, b; int colour; for (j = h - 1; j >= 0; j--) // 图片有h行 { // 显示当前行的每一个点 for (i = 0; i < w; i++) // 每一行w个点 { // 解析每一个像素点 b = buf[k++]; g = buf[k++]; r = buf[k++]; a = (colour_dep == 32) ? buf[k++] : 0; colour = (a << 24) | (r << 16) | (g << 8) | b; lcd_draw_point(lp, i + x0, j + y0, colour); } if ((w * (colour_dep / 8)) % 4 != 0) { k = k + 4 - (w * (colour_dep / 8)) % 4; } } } /* lcd_draw_jpg:把指定的jpg图片显示到开发板的指定的位置 lp:你要操作屏幕结构体的地址 jpgname:图片的路径名 x0,y0:图片的显示位置坐标(显示到开发板的哪一个位置) 返回值:无 */ // void lcd_draw_jpg(lcd *lp, char *jpgname, int x0, int y0) // { // // 1.分配并初始化一个jpeg解压对象 // struct jpeg_decompress_struct dinfo; // 声明一个解压的对象 // struct jpeg_error_mgr jerr; // 声明一个出错信息的对象 // dinfo.err = jpeg_std_error(&jerr); // 初始化这个出错对象 // jpeg_create_decompress(&dinfo); // 初始化dinfo这个解压对象 // // 2.指定要解压缩的图像文件 // FILE *infile = fopen(jpgname, "r"); // 用标准IO去打开这个文件 // if (infile == NULL) // { // perror("in jpeg decompress fopen failed"); // return ; // } // jpeg_stdio_src(&dinfo, infile); // 指定要解压的图像文件 // // 3.调用jpeg_read_header()获取图像信息 // jpeg_read_header(&dinfo, TRUE); // // 4.用于设置jpeg解压对象dinfo的一些参数。可采用默认参数 // // 5.调用jpeg_start_decompress()启动解压过程 // jpeg_start_decompress(&dinfo); // // 调用jpeg_start_decompress函数之后,JPEG解压对象dinfo中 // // 下面这几个字段(成员变量)将会比较有用: // // dinfo.output_width: 图像输出宽度,一行占多少个像素点 // // dinfo.output_height: 图像输出高度,占多少行 // // dinfo.output_components: 每个像素点的分量数,每个像素点占多少个字节 // // 在调用jpeg_start_decompress之后,往往需要为解压后的扫描线上的所有像素点分配存储空间:可以每一次读取一行解压后的像素点信息(RGB) // // printf("dinfo.output_width = %d\n", dinfo.output_width); // // printf("dinfo.output_height = %d\n", dinfo.output_height); // // printf("dinfo.output_components = %d\n", dinfo.output_components); // // 6.读取一行或者多行扫描线上数据并处理,通常的代码是这样子的: // unsigned char *buffer = (unsigned char*)malloc(dinfo.output_width * // dinfo.output_components); // // dinfo.output_scanline 表示的意思是,已经扫描了多少行 // int i, j = 0; // unsigned char a = 0, r, g, b; // // 一行一行的把jpg图像信息(RGB)拿出来,并且显示 // while (dinfo.output_scanline < dinfo.output_height) // { // jpeg_read_scanlines(&dinfo, // 解压对象 // &buffer, // 保存解压后数据的二级指针, // 1 // 读取多少行数据来解压 // ); // dinfo.output_scanline + 1 // // 解析当前行的像素信息(保存到了buffer中) // char *p = buffer; // for (i = 0; i < dinfo.output_width; i++) // 一行有dinfo.output_width个点 // { // if (dinfo.output_components == 4) // { // a = *p++; // } // r = *p++; // g = *p++; // b = *p++; // int color = a << 24 | r << 16 | g << 8 | b; // lcd_draw_point(lp, i + x0, j + y0, color); // } // j++; // } // free(buffer); // // 7.调用jpeg_finish_decompress()完成解压过程 // jpeg_finish_decompress(&dinfo); // // 8.调用jpeg_destroy_decompress释放jpeg解压对象dinfo // jpeg_destroy_decompress(&dinfo); // // 关闭前面打开的文件 // fclose(infile); // }
练习
完成一个简单的程序,实现轮播指定目录下面所有的bmp图片文件和jpg图片文件