1.JPEG图片显示
JPEG(Joint Photographic Experts Group)是由国际标准组织为静态图像所建立的第一个国际数字图像压缩标准,也是至今一直在使用的、应用最广的图像压缩标准。JPEG 由于可以提供有损压缩,因此压缩比可以达到其他传统压缩算法无法比拟的程度; JPEG 虽然是有损压缩,但这个损失的部分是人的视觉不容易察觉到的部分,它充分利用了人眼对计算机色彩中的高频信息部分不敏感的特点,来大大节省了需要处理的数据信息。
libjpeg 是一个完全用 C 语言编写的函数库,包含了 JPEG 解码(解压缩) 、 JPEG 编码(创建压缩) 和其他的 JPEG 功能的实现。 可以使用 libjpeg 库对.jpg 或.jpeg 压缩文件进行解压或者生成.jpg 或.jpeg 压缩文件
1.1libjpeg 移植
下载源码包;
打开 http://www.ijg.org/files/链接地址,
以 v9b 为例,对应的文件名为 jpegsrc.v9b.tar.gz,点击该文件即可下载。
将下载的文件传至ubuntu,
解压:
tar -xzf jpegsrc.v9b.tar.gz
压成功之后会生成 jpeg-9b 文件夹,也就是 libjpeg 源码文件夹
编译之前,在 tools 文件夹中(可以自己创建路径)创建一个名为 jpeg 的文件夹,该目录作为 libjpeg 库的安装目录。
进入到 libjpeg 源码目录 jpeg-9b 中
接下来对 libjpeg 源码进行交叉编译,跟编译 tslib 时步骤一样,包含三个步骤:
1. 配置工程;
2.编译工程;
3.安装;
执行下面这条命令对 libjpeg 工程进行配置:
./configure --host=arm-poky-linux-gnueabi --prefix=/home/book/linux/tool/jpeg
最后面是我们之前设的安装目录,jpeg的路径
./configure --help 查看它的配置选项以及含义, --host 选项用于指定交叉编译得到的库文件是运行在哪个平台,通常将--host 设置为交叉编译器名称的前缀,譬如 arm-poky-linux-gnueabi-gcc 前缀就是 arm-poky-linux-gnueabi; --prefix 选项则用于指定库文件的安装路径, 将家目录下的 tools/jpeg 目录作为libjpeg 的安装目录。
接着执行 make 命令编译工程:
make
编译完成之后,执行命令安装 libjpeg:
make install
我们进入安装目录,可见有以下文件夹:
与 tslib 库安装目录下的包含的文件夹基本相同(除了没有 etc 目录)
1.2移植到开发板
进入到 libjpeg 安装目录下,将 bin 目录下的所有测试工具拷贝到开发板 Linux 系统/usr/bin 目录;将 lib目录下的所有库文件拷贝到开发板 Linux 系统/usr/lib 目录。
拷贝 lib 目录下的库文件时,需要注意符号链接的问题, 不能破坏原有的符号链接; 可以将 lib 目录下的所有文件打包成压缩包的形式,譬如进入到 lib 目录,执行命令:
tar -czf lib.tar.gz ./*
再将 lib.tar.gz 压缩文件拷贝到开发板 Linux 的用户家目录下,在解压之前,将开发板出厂系统中已经移植的 libjpeg 库删除,执行命令
rm -rf /usr/lib/libjpeg.*
Tips:注意!当出厂系统原有的 libjpeg 库被删除后,将会导致开发板下次启动后, 出厂系统的 Qt GUI应用程序会出现一些问题,原本显示图片的位置变成了空白,显示不出来了!原因在于 Qt 程序处理图片(对jpeg 图片解码)时,它的底层使用到了 libjpeg 库,而现在我们将出厂系统原有的 libjpeg 库给删除了,自然就会导致 Qt GUI 应用程序中图片显示不出来(无法对 jpeg 图片进行解码) !
进入到 libjpeg 安装目录下,利用scp语句,将压缩包传至开发板
scp -r book@192.168.5.12:/home/book/linux/tool/jpeg/lib/lib.tar.gz /home/root
压缩:
(如果压缩包解压发现有时间戳报错问题,也可以直接把lib文件传过去)
解码操作的过程:
⑴、创建 jpeg 解码对象; ⑵、指定解码数据源; ⑶、 读取图像信息; ⑷、设置解码参数; ⑸、开始解码; ⑹、读取解码后的数据; ⑺、解码完毕;(8)、释放/销毁解码对象
libjpeg 库解码 jpeg 数据的时候,最重要的一个数据结构为 struct jpeg_decompress_struct 结构体,除此之外, 还需要定义一个用于处理错误的对象, 错误处理对象是一个 struct jpeg_error_mgr 结构体变量
struct jpeg_decompress_struct cinfo;
struct jpeg_error_mgr jerr;
错误处理:
使用 libjpeg 库函数的时候难免会产生错误,所以我们在使用 libjpeg 解码之前,首先要做好错误处理。在 libjpeg 库中,实现了默认错误处理函数,当错误发生时, 譬如如果内存不足、文件格式不对等, 则会 libjpeg实现的默认错误处理函数, 默认错误处理函数将会调用 exit()结束束整个进程;当然,我们可以修改错误处理的方式, libjpeg 提供了接口让用户可以注册一个自定义错误处理函数。
错误处理对象使用 struct jpeg_error_mgr 结构体描述,该结构体内容如下所示
/* Error handler object */
struct jpeg_error_mgr {
/* Error exit handler: does not return to caller */
JMETHOD(noreturn_t, error_exit, (j_common_ptr cinfo));
/* Conditionally emit a trace or warning message */
JMETHOD(void, emit_message, (j_common_ptr cinfo, int msg_level));
/* Routine that actually outputs a trace or error message */
JMETHOD(void, output_message, (j_common_ptr cinfo));
/* Format a message string for the most recent JPEG error or message */
JMETHOD(void, format_message, (j_common_ptr cinfo, char * buffer));
#define JMSG_LENGTH_MAX 200 /* recommended size of format_message buffer */
/* Reset error state variables at start of a new image */
JMETHOD(void, reset_error_mgr, (j_common_ptr cinfo));
/* The message ID code and any parameters are saved here.
* A message can have one string parameter or up to 8 int parameters.
*/
int msg_code;
#define JMSG_STR_PARM_MAX 80
union {
int i[8];
char s[JMSG_STR_PARM_MAX];
} msg_parm;
/* Standard state variables for error facility */
int trace_level; /* max msg_level that will be displayed *//* For recoverable corrupt-data errors, we emit a warning message,
* but keep going unless emit_message chooses to abort. emit_message
* should count warnings in num_warnings. The surrounding application
* can check for bad data by seeing if num_warnings is nonzero at the
* end of processing.
*/
long num_warnings; /* number of corrupt-data warnings */
/* These fields point to the table(s) of error message strings.
* An application can change the table pointer to switch to a different
* message list (typically, to change the language in which errors are
* reported). Some applications may wish to add additional error codes
* that will be handled by the JPEG library error mechanism; the second
* table pointer is used for this purpose.
*
* First table includes all errors generated by JPEG library itself.
* Error code 0 is reserved for a "no such error string" message.
*/
const char * const * jpeg_message_table; /* Library errors */
int last_jpeg_message; /* Table contains strings 0..last_jpeg_message */
/* Second table can be added by application (see cjpeg/djpeg for example).
* It contains strings numbered first_addon_message..last_addon_message.
*/
const char * const * addon_message_table; /* Non-library errors */
int first_addon_message; /* code for first string in addon table */
int last_addon_message; /* code for last string in addon table */
};
error_exit 函数指针便指向了错误处理函数。使用 libjpeg 库函数 jpeg_std_error()会将 libjpeg 错误处理设置为默认处理方式。如下所示
//初始化错误处理对象、并将其与解压对象绑定
cinfo.err = jpeg_std_error(&jerr);
如果我们要修改默认的错误处理函数,可这样操作:
void my_error_exit(struct jpeg_decompress_struct *cinfo)
{
/* ... */
}
cinfo.err.error_exit = my_error_exit;
创建解码对象:
要使用 libjpeg 解码 jpeg 数据,这步是必须要做的
jpeg_create_decompress(&cinfo);
在创建解码对象之后,如果解码结束或者解码出错时,需要调用 jpeg_destroy_decompress 销毁/释放解码对象,否则将会内存泄漏。
设置数据源:
读取 jpeg 文件的头信息:
设置解码处理参数:
开始解码:
读取数据:
结束解码:
释放/销毁解码对象:
以上具体可看手册
实验测试::
加注释源码
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <string.h>
#include <linux/fb.h>
#include <sys/mman.h>
#include <jpeglib.h>
// 定义BGR888颜色结构
typedef struct bgr888_color {
unsigned char red;
unsigned char green;
unsigned char blue;
} __attribute__ ((packed)) bgr888_t;
static int width; // LCD X分辨率
static int height; // LCD Y分辨率
static unsigned short *screen_base = NULL; // 映射后的显存基地址
static unsigned long line_length; // LCD一行的长度(字节为单位)
static unsigned int bpp; // 像素深度bpp
// 显示JPEG图像的函数
static int show_jpeg_image(const char *path)
{
struct jpeg_decompress_struct cinfo; // JPEG解压缩结构体
struct jpeg_error_mgr jerr; // 错误管理器
FILE *jpeg_file = NULL; // JPEG文件指针
bgr888_t *jpeg_line_buf = NULL; // 行缓冲区:用于存储从JPEG文件中解压出来的一行图像数据
unsigned short *fb_line_buf = NULL; // 行缓冲区:用于存储写入到LCD显存的一行数据
unsigned int min_h, min_w; // 图像和LCD的最小高度和宽度
unsigned int valid_bytes; // 有效字节数
int i;
// 绑定默认错误处理函数
cinfo.err = jpeg_std_error(&jerr);
// 打开JPEG图像文件
jpeg_file = fopen(path, "r");
if (NULL == jpeg_file) {
perror("fopen error");
return -1;
}
// 创建JPEG解码对象
jpeg_create_decompress(&cinfo);
// 指定图像文件
jpeg_stdio_src(&cinfo, jpeg_file);
// 读取图像信息
jpeg_read_header(&cinfo, TRUE);
printf("jpeg图像大小: %d*%d\n", cinfo.image_width, cinfo.image_height);
// 设置解码参数
cinfo.out_color_space = JCS_RGB; // 默认就是JCS_RGB
// 开始解码图像
jpeg_start_decompress(&cinfo);
// 为缓冲区分配内存空间
jpeg_line_buf = malloc(cinfo.output_components * cinfo.output_width);
fb_line_buf = malloc(line_length);
// 判断图像和LCD屏哪个的分辨率更低
if (cinfo.output_width > width)
min_w = width;
else
min_w = cinfo.output_width;
if (cinfo.output_height > height)
min_h = height;
else
min_h = cinfo.output_height;
// 读取数据
valid_bytes = min_w * bpp / 8; // 一行的有效字节数,表示真正写入到LCD显存的一行数据的大小
while (cinfo.output_scanline < min_h) {
// 每次读取一行数据
jpeg_read_scanlines(&cinfo, (unsigned char **)&jpeg_line_buf, 1);
// 将读取到的BGR888数据转为RGB565
for (i = 0; i < min_w; i++)
fb_line_buf[i] = ((jpeg_line_buf[i].red & 0xF8) << 8) |
((jpeg_line_buf[i].green & 0xFC) << 3) |
((jpeg_line_buf[i].blue & 0xF8) >> 3);
// 将转换后的数据复制到显存中
memcpy(screen_base, fb_line_buf, valid_bytes);
screen_base += width; // 定位到LCD下一行显存地址的起点
}
// 解码完成
jpeg_finish_decompress(&cinfo); // 完成解码
jpeg_destroy_decompress(&cinfo); // 销毁JPEG解码对象、释放资源
// 关闭文件、释放内存
fclose(jpeg_file);
free(fb_line_buf);
free(jpeg_line_buf);
return 0;
}
int main(int argc, char *argv[])
{
struct fb_fix_screeninfo fb_fix; // 固定屏幕信息
struct fb_var_screeninfo fb_var; // 可变屏幕信息
unsigned int screen_size; // 屏幕大小
int fd;
/* 传参校验 */
if (2 != argc) {
fprintf(stderr, "usage: %s <jpeg_file>\n", argv[0]);
exit(-1);
}
/* 打开framebuffer设备 */
if (0 > (fd = open("/dev/fb0", O_RDWR))) {
perror("open error");
exit(EXIT_FAILURE);
}
/* 获取参数信息 */
ioctl(fd, FBIOGET_VSCREENINFO, &fb_var);
ioctl(fd, FBIOGET_FSCREENINFO, &fb_fix);
line_length = fb_fix.line_length; // 行长度
bpp = fb_var.bits_per_pixel; // 像素深度
screen_size = line_length * fb_var.yres; // 屏幕大小
width = fb_var.xres; // 屏幕宽度
height = fb_var.yres; // 屏幕高度
/* 将显示缓冲区映射到进程地址空间 */
screen_base = mmap(NULL, screen_size, PROT_WRITE, MAP_SHARED, fd, 0);
if (MAP_FAILED == (void *)screen_base) {
perror("mmap error");
close(fd);
exit(EXIT_FAILURE);
}
/* 显示JPEG图片 */
memset(screen_base, 0xFF, screen_size); // 清屏
show_jpeg_image(argv[1]); // 显示JPEG图片
/* 退出 */
munmap(screen_base, screen_size); // 取消映射
close(fd); // 关闭文件
exit(EXIT_SUCCESS); // 退出进程
}
解释:
1.
cinfo.err = jpeg_std_error(&jerr);
这行代码用于设置JPEG库的错误管理器。jpeg_std_error
是 libjpeg 库提供的一个函数,它会初始化一个 jpeg_error_mgr
结构,并返回该结构的指针。该结构包含了一组默认的错误处理例程,可以用来处理解码或编码过程中可能出现的错误
2.
// 创建JPEG解码对象
jpeg_create_decompress(&cinfo);
// 指定图像文件
jpeg_stdio_src(&cinfo, jpeg_file);
// 读取图像信息
jpeg_read_header(&cinfo, TRUE);
printf("jpeg图像大小: %d*%d\n", cinfo.image_width, cinfo.image_height);
// 设置解码参数
cinfo.out_color_space = JCS_RGB; // 默认就是JCS_RGB
// 开始解码图像
jpeg_start_decompress(&cinfo);
// 为缓冲区分配内存空间
jpeg_line_buf = malloc(cinfo.output_components * cinfo.output_width);
fb_line_buf = malloc(line_length);
-
jpeg_create_decompress(&cinfo);
:- 初始化
jpeg_decompress_struct
结构体cinfo
并分配必要的资源,为后续的解码操作做准备。
- 初始化
-
jpeg_stdio_src(&cinfo, jpeg_file);
:- 将打开的 JPEG 文件
jpeg_file
设置为解码源,这样解码器可以从文件中读取数据。
- 将打开的 JPEG 文件
-
jpeg_read_header(&cinfo, TRUE);
:- 读取 JPEG 文件的头信息,获取图像的基本信息,如宽度、高度和颜色空间等。
- 读取后,可以通过
cinfo.image_width
和cinfo.image_height
访问图像的宽度和高度。
-
cinfo.out_color_space = JCS_RGB;
:- 设置解码输出的颜色空间为 RGB,这样解码后的图像数据将是 RGB 格式。
- 默认情况下,JPEG 解码器输出的颜色空间就是 RGB,所以这一行代码实际上是明确指定颜色空间。
-
jpeg_start_decompress(&cinfo);
:- 启动解码过程,准备解码扫描线。调用后,
cinfo
中的output_width
、output_height
和output_components
将被设置为解码后的图像参数。
- 启动解码过程,准备解码扫描线。调用后,
-
内存分配:
jpeg_line_buf = malloc(cinfo.output_components * cinfo.output_width);
:- 分配一行图像数据的缓冲区。
cinfo.output_components
表示每个像素的字节数(对于 RGB 是 3),cinfo.output_width
表示图像的宽度(像素数)。
- 分配一行图像数据的缓冲区。
fb_line_buf = malloc(line_length);
:- 分配一个行缓冲区,用于存储写入到 LCD 显存的一行数据。
line_length
表示一行显存的字节数。
- 分配一个行缓冲区,用于存储写入到 LCD 显存的一行数据。
3.
memset(screen_base, 0xFF, screen_size); // 清屏
memset
是一个标准库函数,用于将指定的值填充到一块内存区域中
munmap(screen_base, screen_size); // 取消映射
munmap
是一个系统调用,用于解除之前通过 mmap
创建的内存映射
编译:
${CC} -o show_jpeg_image show_jpeg_image.c -I /home/book/linux/tool/jpeg/include -L /home/book/linux/tool/jpeg/lib -ljpeg
目录为jpeg安装目录
利用scp传至开发板
scp -r book@192.168.5.12:/home/book/project/APP/app/20_libjpeg/show_jpeg_image /home/root/app
找一张jpg图片也传至开发板,编译:
./show_jpeg_image R-C.jpg
2.png图片显示
步骤和1类似,
PNG(便携式网络图形格式 PortableNetwork Graphic Format, 简称 PNG) 是一种采用无损压缩算法的位图格式,其设计目的是试图替代 GIF 和 TIFF 文件,同时增加一些 GIF 文件所不具备的特性。 PNG 使用从LZ77 派生的无损数据压缩算法,它压缩比高,生成文件体积小,并且支持透明效果,所以被广泛使用。
特点
无损压缩: PNG 文件采用 LZ77 算法的派生算法进行压缩,其结果是获得高的压缩比,不损失数据。它利用特殊的编码方法标记重复出现的数据,因而对图像的颜色没有影响,也不可能产生颜色的损失,这样就可以重复保存而不降低图像质量。
体积小: 在保证图片清晰、逼真、不失真的前提下, PNG 使用从 LZ77 派生的无损数据压缩算法,它压缩比高,生成文件体积小;
索引彩色模式: PNG-8 格式与 GIF 图像类似,同样采用 8 位调色板将 RGB 彩色图像转换为索引彩色图像。图像中保存的不再是各个像素的彩色信息,而是从图像中挑选出来的具有代表性的颜色编号,每一编号对应一种颜色, 图像的数据量也因此减少,这对彩色图像的传播非常有利。
更优化的网络传输显示: PNG 图像在浏览器上采用流式浏览,即使经过交错处理的图像会在完全下载之前提供浏览者一个基本的图像内容,然后再逐渐清晰起来。它允许连续读出和写入图像数据,这个特性很适合于在通信过程中显示和生成图像。
支持透明效果: PNG 可以为原图像定义 256 个透明层次,使得彩色图像的边缘能与任何背景平滑地融合,从而彻底地消除锯齿边缘。这种功能是 GIF 和 JPEG 没有的。
对于 png 图像,我们可以使用 libpng 库对其进行解码,跟 libjpeg 一样,它也是一套免费、开源的 C 语言函数库,支持对 png 图像文件解码、编码等功能。
zlib 移植
zlib 其实是一套包含了数据压缩算法的函式库,此函数库为自由软件, 是一套免费、开源的 C 语言函数库,所以我们可以获取到它源代码。
libpng 依赖于 zlib 库, 所以要想移植 libpng 先得移植 zlib 库才可以, zlib 也好、 libpng 也好,其实移植过程非常简单,无非就是下载源码、编译源码这样的一些工作,那本小节就向大家介绍如何移植 zlib。
进入到 https://www.zlib.net/fossils/这个链接地址下载 zlib 源码包:
找到一个合适的版本, 这里我们就选择 1.2.10 版本的 zlib
解压:
tar -xzf zlib-1.2.10.tar.gz
和1的步骤一样,在tool中新建zilb,进入zlib-1.2.10执行
./configure --prefix=/home/book/linux/tool/zlib/
编译
make
make install
移植到开发板:
进入到 zlib 安装目录下,将 lib 目录下的所有动态链接库文件拷贝到开发板 Linux 系统/usr/lib 目录;注意在拷贝之前,需要先将出厂系统中原有的 zlib 库文件删除, 在开发板 Linux 系统下执行命令:
rm -rf /usr/lib/libz.* /lib/libz.*
删除之后,再将我们编译得到的 zlib 库文件拷贝到开发板/usr/lib 目录, 拷贝库文件时,需要注意符号链接的问题,不能破坏原有的符号链接(后面换成1.3版本了,1.210版本一样的)
注意:因为删掉了,所以这时候scp命令就用不了了,所以我们之后得用nfs去实现传文件。
nfs指令:
mount -t nfs -o nolock,vers=3 192.168.5.12:/home/book/linux/nfs /mnt
意思是,开发板的mnt文件目录,挂载了ubuntu 的 /home/book/linux/nfs文件夹。
在Ubuntu上,将前面编译好的zlib,复制到nfs文件夹
cp -r zlib /home/book/linux/nfs/
在开发板打开mnt,cd /mnt
便看到了zlib文件
之后利用cp命令,将zlib/lib里的链接文件复制到/usr/lib
首先下载 libpng 源码包,进入 https://github.com/glennrp/libpng/releases 链接地址,如下
下载后传至ubuntu
方法一样,原来地方创个文件夹 libpng
因为libpng依赖zlib,所以执行以下命令
export LDFLAGS="${LDFLAGS} -L /home/book/linux/tool/zlib/lib"
export CFLAGS="${CFLAGS} -I /home/book/linux/tool/zlib/include"
export CPPFLAGS="${CPPFLAGS} -I /home/book/linux/tool/zlib/include"
然后配置
./configure --prefix=/home/book/linux/tool/libpng --host=arm-poky-linux-gnueabi
编译:
make
make install
....后面步骤则和第一步差不多啦