我们常用的图片格式有很多,一般最常用的有三种:JPEG(或 JPG)、PNG、BMP和GIF。其中 JPEG(或JPG)、PNG以及 BMP 都是静态图片,而 GIF 则可以实现动态图片。
BMP(全称 Bitmap)是Window操作系统中的标准图像文件格式,文件后缀名为.bmp
,使用非常
广。它采用位映射存储格式,除了图像深度可选以外,图像数据没有进行任何压缩,因此,BMP 图像文件所占用的空间很大,但是没有失真、并且解析BMP图像简单。
BMP文件的图像深度可选1bit、4bit、8bit、16bit、24bit 以及32bit,典型的BMP图像文件由四部分组成:
一般常见的图像都是以 16 位(R、G、B 三种颜色分别使用 5bit、6bit、5bit 来表示)、24 位(R、G、B 三种颜色都使用 8bit 来表示)色图像为主,我们称这样的图像为真彩色图像,真彩色图像是不需要调色板的,即位图信息头后面紧跟的就是位图数据了。
对某些BMP位图文件说并非如此,譬如16色位图、256色位图,它们需要使用到调色板,具体调色板如何使用,我们不关心,本节我们将会以16 位色(RGB565)BMP 图像为例。
可以看到该图片的分辨率为800*480,位深度为16bit,每个像素点使用16位表示,也就是RGB565。为了向大家介绍BMP文件结构,接下来使用十六进制查看工具将image.bmp文件打开,文件头部分的内容如下所示:
Windows下为bmp文件头定义了如下结构体:
typedef struct tagBITMAPFILEHEADER
{
UINT16 bfType;
DWORD bfSize;
UINT16 bfReserved1;
UINT16 bfReserved2;
DWORD bfOffBits;
} BITMAPFILEHEADER;
结构体中每一个成员说明如下:
从上面的描述信息,再来对照文件数据:
bmp文件头的大小固定为 14 个字节。
同样,Windows 下为位图信息头定义了如下结构体:
typedef struct tagBITMAPINFOHEADER
{
DWORD biSize;
LONG biWidth;
LONG biHeight;
WORD biPlanes;
WORD biBitCount;
DWORD biCompression;
DWORD biSizeImage;
LONG biXPelsPerMeter;
LONG biYPelsPerMeter;
DWORD biClrUsed;
DWORD biClrImportant;
} BITMAPINFOHEADER;
结构体中每一个成员说明如下:
从上面的描述信息,再来对照文件数据:
只有压缩方式选项被设置为bit-fileds(0x3)
时,位图信息头的大小才会等于56字节,否则,为40字节。
调色板是单色、16 色、256 色位图图像文件所持有的,如果是 16 位、24 位以及 32 位位图文件,则 BMP文件组成部分中不包含调色板,关于调色板这里不过多介绍,有兴趣可以自己去了解。
位图数据其实就是图像的数据,对于24位位图,使用3个字节数据来表示一个像素点的颜色,对于16位位图,使用2个字节数据来表示一个像素点的颜色,同理,32 位位图则使用4个字节来描述。
BMP位图分为正向的位图和倒向的位图,主要区别在于图像数据存储的排列方式,前面已经给大家解释的比较清楚了,如下如所示(左边对应的是正向位图,右边对应的则是倒向位图):
所以正向位图先存储图像的第一行数据,从左到右依次存放,接着存放第二行,依次这样;而倒向位图,则先存储图像的最后一行(倒数第一行)数据,也是从左到右依次存放,接着倒数二行,依次这样。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
/**** BMP文件头数据结构 ****/
typedef struct {
unsigned char type[2]; //文件类型
unsigned int size; //文件大小
unsigned short reserved1; //保留字段1
unsigned short reserved2; //保留字段2
unsigned int offset; //到位图数据的偏移量
} __attribute__ ((packed)) bmp_file_header;
/**** 位图信息头数据结构 ****/
typedef struct {
unsigned int size; //位图信息头大小
int width; //图像宽度
int height; //图像高度
unsigned short planes; //位面数
unsigned short bpp; //像素深度
unsigned int compression; //压缩方式
unsigned int image_size; //图像大小
int x_pels_per_meter; //像素/米
int y_pels_per_meter; //像素/米
unsigned int clr_used;
unsigned int clr_omportant;
} __attribute__ ((packed)) bmp_info_header;
/**** 静态全局变量 ****/
static int width; //LCD X分辨率
static int height; //LCD Y分辨率
static unsigned short *screen_base = NULL; //映射后的显存基地址
static unsigned long line_length; //LCD一行的长度(字节为单位)
/********************************************************************
* 函数名称: show_bmp_image
* 功能描述: 在LCD上显示指定的BMP图片
* 输入参数: 文件路径
* 返 回 值: 成功返回0, 失败返回-1
********************************************************************/
static int show_bmp_image(const char *path)
{
bmp_file_header file_h;
bmp_info_header info_h;
unsigned short *line_buf = NULL; //行缓冲区
unsigned long line_bytes; //BMP图像一行的字节的大小
unsigned int min_h, min_bytes;
int fd = -1;
int j;
/* 打开文件 */
if (0 > (fd = open(path, O_RDONLY))) {
perror("open error");
return -1;
}
/* 读取BMP文件头 */
if (sizeof(bmp_file_header) !=
read(fd, &file_h, sizeof(bmp_file_header))) {
perror("read error");
close(fd);
return -1;
}
if (0 != memcmp(file_h.type, "BM", 2)) {
fprintf(stderr, "it's not a BMP file\n");
close(fd);
return -1;
}
/* 读取位图信息头 */
if (sizeof(bmp_info_header) !=
read(fd, &info_h, sizeof(bmp_info_header))) {
perror("read error");
close(fd);
return -1;
}
/* 打印信息 */
printf("文件大小: %d\n"
"位图数据的偏移量: %d\n"
"位图信息头大小: %d\n"
"图像分辨率: %d*%d\n"
"像素深度: %d\n", file_h.size, file_h.offset,
info_h.size, info_h.width, info_h.height,
info_h.bpp);
/* 将文件读写位置移动到图像数据开始处 */
if (-1 == lseek(fd, file_h.offset, SEEK_SET)) {
perror("lseek error");
close(fd);
return -1;
}
/* 申请一个buf、暂存bmp图像的一行数据 */
line_bytes = info_h.width * info_h.bpp / 8;
line_buf = malloc(line_bytes);
if (NULL == line_buf) {
fprintf(stderr, "malloc error\n");
close(fd);
return -1;
}
if (line_length > line_bytes)
min_bytes = line_bytes;
else
min_bytes = line_length;
/**** 读取图像数据显示到LCD ****/
/*******************************************
* 为了软件处理上方便,这个示例代码便不去做兼容性设计了
* 如果你想做兼容, 可能需要判断传入的BMP图像是565还是888
* 如何判断呢?文档里边说的很清楚了
* 我们默认传入的bmp图像是RGB565格式
*******************************************/
if (0 < info_h.height) {//倒向位图
if (info_h.height > height) {
min_h = height;
lseek(fd, (info_h.height - height) * line_bytes, SEEK_CUR);
screen_base += width * (height - 1); //定位到屏幕左下角位置
}
else {
min_h = info_h.height;
screen_base += width * (info_h.height - 1); //定位到....不知怎么描述 懂的人自然懂!
}
for (j = min_h; j > 0; screen_base -= width, j--) {
read(fd, line_buf, line_bytes); //读取出图像数据
memcpy(screen_base, line_buf, min_bytes);//刷入LCD显存
}
}
else { //正向位图
int temp = 0 - info_h.height; //负数转成正数
if (temp > height)
min_h = height;
else
min_h = temp;
for (j = 0; j < min_h; j++, screen_base += width) {
read(fd, line_buf, line_bytes);
memcpy(screen_base, line_buf, min_bytes);
}
}
/* 关闭文件、函数返回 */
close(fd);
free(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 \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);
screen_size = fb_fix.line_length * fb_var.yres;
line_length = fb_fix.line_length;
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);
}
/* 显示BMP图片 */
memset(screen_base, 0xFF, screen_size);
show_bmp_image(argv[1]);
/* 退出 */
munmap(screen_base, screen_size); //取消映射
close(fd); //关闭文件
exit(EXIT_SUCCESS); //退出进程
}
我们要想给ARM板编译出程序,需要使用交叉编译工具链,交叉编译的工具链我们已经安装过了,详细请看【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.x.pdf 的第4.3小节
。我是用的是arm-linux-gnueabihf交叉编译工具链。使用arm-linux-gnueabihf-gcc -v
可以查看交叉编译工具链的版本号。
然后就可以使用下面命令编译出可以在ARM板子上运行的可执行文件了。
arm-linux-gnueabihf-gcc -o bmp_show bmp_show.c
这样编译出来的 led程序才可以在ARM板子上运行。执行file bmp_show
命令就可以看出hello是32位LSB的ELF格式文件,目标机架构为ARM,说明这个交叉编译正常,可执行文件可以在ARM板上执行。
开发板启动后通过nfs挂载Ubuntu目录的方式,将相应的文件拷贝到开发板上。简单来说,就是通过NFS在开发板上通过网络直接访问ubuntu虚拟机上的文件,并且就相当于自己本地的文件一样。
开发板想访问/home/zhiguoxin/myproject/alientek_app_development_source
这个目录中的文件,就要把/home/zhiguoxin/myproject/alientek_app_development_source
挂载到开发板的mnt
目录,这样就可以通过nfs来访问/home/zhiguoxin/myproject/alientek_app_development_source
了。
因为我的代码都放在/home/zhiguoxin/myproject/alientek_app_development_source
这个目录下,所以我们将这个目录作为NFS共享文件夹。设置方法参考移植SQLite3、OpenCV到RV1126开发板上开发人脸识别项目第一章。
Ubuntu IP为192.168.10.100,然后一般都是挂载在开发板的mnt目录下,这个目录是专门用来给我们作为临时挂载的目录。
然后使用MobaXterm软件通过SSH访问开发板。
ubuntu ip:192.168.10.100
windows ip:192.168.10.200
开发板ip:192.168.10.50
在开发板上执行以下命令:
mount -t nfs -o nolock,vers=3 192.168.10.100:/home/zhiguoxin/myproject/alientek_app_development_source /mnt
就将开饭的mnt
目录挂载在ubuntu的/home/zhiguoxin/myproject/alientek_app_development_source
目录下了。这样我们就可以在Ubuntu下修改文件,然后可以直接在开发板上执行可执行文件了。当然我这里的/home/zhiguoxin/myproject/
和windows
之间是一个共享目录,我也可以直接在windows
上面修改文件,然后ubuntu和开发板直接进行文件同步了。
执行应用程序
./bmp_show xiaoya.bmp
在Windows下我们转换得到的BMP位图通常是24位色的RGB888格式图像,那如何得到RGB565格式 BMP位图呢?当然这个方法很多,这里笔者向大家介绍一种方法就是通过 Photoshop软件来得到 RGB565格式的 BMP 位图。
首先,找一张图片,图片格式无所谓,只要Photoshop软件能打开即可;确定图片之后,我们启动Photoshop软件,并且使用Photoshop软件打开这张图片,打开之后点击菜单栏中的文件—>存储为,接着出现如下界面:
我们常用的图片格式有很多,一般最常用的有三种:JPEG(或 JPG)、PNG、BMP。BMP 图像虽然没有失真、并且解析简单,但是由于图像数据没有进行任何压缩,因此,BMP 图像文件所占用的存储空间很大,不适合存储在磁盘设备中。
而 JPEG(或 JPG)、PNG 则是经过压缩处理的图像格式,将图像数据进行压缩编码,大大降低了图像文件的大小,适合存储在磁盘设备中,所以很常用。
JPEG(Joint Photographic Experts Group)是由国际标准组织为静态图像所建立的第一个国际数字图像压缩标准,也是至今一直在使用的、应用最广的图像压缩标准。
JPEG由于可以提供有损压缩,因此压缩比可以达到其他传统压缩算法无法比拟的程度;JPEG虽然是有损压缩,但这个损失的部分是人的视觉不容易察觉到的部分,它充分利用了人眼对计算机色彩中的高频信息部分不敏感的特点,来大大节省了需要处理的数据信息。JPEG 压缩文件通常以.jpg 或.jpeg 作为文件后缀名。
JPEG 压缩标准使用了一套压缩算法对原始图像数据进行了压缩得到.jpg 或.jpeg 图像文件,如果想要在LCD 上显示.jpg 或.jpeg 图像文件,则需要对其进行解压缩、以得到图像的原始数据,譬如RGB 数据。
既然压缩过程使用了算法,那对.jpg 或.jpeg 图像文件进行解压同样也需要算法来处理,这里并不会教大家如何编写解压算法,这些算法的实现也是很复杂的,我不会,自然教不了大家!但是,我们可以使用别人写好的库、调用别人写好的库函数来解压.jpg 或.jpeg 图像文件—libjpeg 库。
libjpeg是一个完全用C语言编写的函数库,包含了JPEG解码(解压缩)、JPEG 编码(创建压缩)和
其他的 JPEG 功能的实现。可以使用 libjpeg 库对.jpg 或.jpeg 压缩文件进行解压或者生成.jpg 或.jpeg 压缩文件。
libjpeg是一个开源 C 语言库,我们获取到它的源代码。
首先,打开http://www.ijg.org/files/链接地址,如下所示
目前最新的一个版本是v9e,对应的年份为2022年,这里我们选择一个适中的版本,笔者以v9b 为例,对应的文件名为jpegsrc.v9b.tar.gz
,点击该文件即可下载。我把源码下在完成后放在共享文件夹下:
其实开发板出厂系统中已经移植了libjpeg库,但是版本太旧了!所以这里我们选择重新移植。
将 jpegsrc.v9b.tar.gz 压缩包文件放到共享文件夹下,如下所示:
执行命令解压
tar -vxzf jpegsrc.v9b.tar.gz
解压成功之后会生成 jpeg-9b 文件夹,也就是 libjpeg 源码文件夹。
编译之前,在家目录下的tools
文件夹中创建一个名为jpeg
的文件夹,该目录作为libjpeg
库的安装目录:
mkdir jpeg
回到共享文件夹目录下,进入到libjpeg
源码目录jpeg-9b
中,该目录下包含的内容如下所示:
接下来对 libjpeg 源码进行交叉编译,跟编译 tslib 时步骤一样,包含三个步骤:
一套流程下来非常地快!没有任何难点。在此之前,先对交叉编译工具的环境进行初始化,使用 source执行交叉编译工具安装目录下的environment-setup-cortexa7hf-neon-poky-linux-gnueabi
脚本文件(如果已经
初始化过了,那就不用再进行初始化了):
source /opt/fsl-imx-x11/4.1.15-2.1.0/environment-setup-cortexa7hf-neon-poky-linux-gnueabi
执行下面这条命令对 libjpeg 工程进行配置:
./configure --host=arm-poky-linux-gnueabi --prefix=/home/zhiguoxin/linux/tool/jpeg
大家可以执行./configure --help
查看它的配置选项以及含义,--host
选项用于指定交叉编译得到的库文件是运行在哪个平台,通常将--host
设置为交叉编译器名称的前缀,譬如arm-poky-linux-gnueabi-gcc
前缀就是arm-poky-linux-gnueabi
;--prefix
选项则用于指定库文件的安装路径,将/home/zhiguoxin/linux/tool
目录作为libjpeg
的安装目录。
接着执行 make 命令编译工程:
make
编译完成之后,执行命令安装 libjpeg:
make install
命令执行完成之后,我们的 libjpeg 也就安装成功了!
进入到 libjpeg 安装目录:
开发板出厂系统已经移植了libjpeg
库,前面给大家提到过,只是移植的版本太低了,所以这里不打算使用出厂系统移植的libjpeg
库,而使用交叉编译好的libjpeg
库。
进入到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 图片解码)时,它的底层使用到了 ibjpeg 库,而现在我们将出厂系统原有的 libjpeg 给删除了,自然就会导致Qt GUI应用程序中图片显示不出来(无法对 jpeg 图片进行解码)!
接着我们将lib.tar.gz
压缩文件解压到开发板Linux系统/usr/lib
目录下。我们可以使用scp命令将/home/zhiguoxin/linux/jpeg/lib目录下的lib.tar.gz发送到发开板中。
scp lib.tar.gz root@192.168.10.50:/home/root
注意:有的时候发下面这种错误,拷贝失败,可以关闭命令窗口,在重新打开一次就好了。
然后lib.tar.gz就拷贝到开发板了,然后使用下面命令将其解压至/usr/lib 目录下
tar -xzf lib.tar.gz -C /usr/lib
tar -xmzf lib.tar.gz -C /usr/lib
解压成功之后,接着执行libjpeg
提供的测试工具,看看我们移植成功没:
djpeg --help
libjpeg 提供的库函数对.jpg/.jpeg 进行解码(解压),得到 RGB 数据。首先,使用 libjpeg 库需要在我们的应用程序中包含它的头文件 jpeglib.h,该头文件包含了一些结构体数据结构以及 API 接口的申明。先来看看解码操作的过程:
⑴、创建 jpeg 解码对象;
⑵、指定解码数据源;
⑶、读取图像信息;
⑷、设置解码参数;
⑸、开始解码;
⑹、读取解码后的数据;
⑺、解码完毕;
⑻、释放/销毁解码对象。
对一个指定的 jpeg 图像进行解码,显示在 LCD 屏上,示例代码如下所示:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
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
static int show_jpeg_image(const char *path)
{
struct jpeg_decompress_struct cinfo;
struct jpeg_error_mgr jerr;
FILE *jpeg_file = NULL;
bgr888_t *jpeg_line_buf = NULL; //行缓冲区:用于存储从jpeg文件中解压出来的一行图像数据
unsigned short *fb_line_buf = NULL; //行缓冲区:用于存储写入到LCD显存的一行数据
unsigned int min_h, min_w;
unsigned int valid_bytes;
int i;
//绑定默认错误处理函数
cinfo.err = jpeg_std_error(&jerr);
//打开.jpeg/.jpg图像文件
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
//cinfo.scale_num = 1;
//cinfo.scale_denom = 2;
//开始解码图像
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;//+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 \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);
}
/* 显示BMP图片 */
memset(screen_base, 0xFF, screen_size);
show_jpeg_image(argv[1]);
/* 退出 */
munmap(screen_base, screen_size); //取消映射
close(fd); //关闭文件
exit(EXIT_SUCCESS); //退出进程
}
代码就不再讲解了,前面的内容看懂了,代码自然就能看懂!
在 while 循环中,通过 jpeg_read_scanlines()每次读取一行数据,注意,jpeg_read_scanlines()函数的第二个参数是一个 unsigned char **类型指针。读取到数据之后,需要将其转为 RGB565 格式,因为我们这个开发板出厂系统,LCD 是 RGB565 格式的显示设备,这个转化非常简单,没什么可说的,懂的人自然懂!
编译上述代码:
${CC} -o show_jpeg_image show_jpeg_image.c -I /home/zhiguoxin/linux/tool/jpeg/include -L /home/zhiguoxin/linux/tool/jpeg/lib -ljpeg
编译的时候需要指定头文件的路径、库文件的路径以及需要链接的库文件。将编译得到的可执行文件和一个.jpg/.jpeg
图像文件挂载到开发板目录下目录下,执行:
./show_jpeg_image image.jpg
此时LCD屏上便会显示这张图片,如下所示:
PNG(便携式网络图形格式 PortableNetwork Graphic Format,简称 PNG)是一种采用无损压缩算法的位图格式,其设计目的是试图替代GIF和TIFF 文件,同时增加一些GIF文件所不具备的特性。PNG 使用从LZ77 派生的无损数据压缩算法,它压缩比高,生成文件体积小,并且支持透明效果,所以被广泛使用。
特点
对于 png 图像,我们可以使用 libpng 库对其进行解码,跟libjpeg 一样,它也是一套免费、开源的 C 语言函数库,支持对png图像文件解码、编码等功能。
zlib其实是一套包含了数据压缩算法的函式库,此函数库为自由软件,是一套免费、开源的C语言函数库,所以我们可以获取到它源代码。
libpng 依赖于zlib库,所以要想移植libpng先得移植zlib库才可以,zlib也好、libpng也好,其实移植过程非常简单,无非就是下载源码、编译源码这样的一些工作。
在移植之前,开发板出厂系统都是已经移植好了这些库,其实是可以直接使用的,但是作为学习,必须要自己亲自把这些库给移植到开发板,这是非常重要的!
我们可以进入到https://www.zlib.net/fossils/这个链接地址下载 zlib 源码包:
往下翻,找到一个合适的版本,这里我们就选择1.2.10版本的 zlib,点击文件名就可以下载了,下载成功之后就会得到.tar.gz 格式的压缩文件:
我这里还是将将下载的 zlib-1.2.10.tar.gz 压缩文件拷贝到 Ubuntu 的共享文件夹下,然后将其解压开:
tar -xzf zlib-1.2.10.tar.gz
解压之后就会得到zlib-1.2.10文件夹,这就是zlib的源代码目录。
在编译zlib之前,我们先在 tool目录下创建一个名为zlib的文件夹,作为zlib库的安装目录:
接着我们进入到 zlib 的源码目录zlib-1.2.10,如下所示:
同样也是执行三部曲:配置、编译、安装,一套流程下来就 OK 了!
在此之前,先对交叉编译工具的环境进行初始化,使用 source 执行交叉编译工具安装目录下的
environment-setup-cortexa7hf-neon-poky-linux-gnueabi 脚本文件(如果已经初始化过了,那就不用再进行初始
化了):
source /opt/fsl-imx-x11/4.1.15-2.1.0/environment-setup-cortexa7hf-neon-poky-linux-gnueabi
执行下面这条命令对 zlib 工程进行配置:
./configure --prefix=/home/zhiguoxin/linux/tool/zlib/
–prefix 选项指定 zlib 库的安装目录,将家目录下的 tools/zlib 作为 zlib 库的安装目录。
配置完成之后,直接 make 编译:
make
编译完成之后,接着执行 make install 安装即可!
make install
这样就解开那个zlib安装到/home/zhiguoxin/linux/tool/zlib/
下面了。
进入到zlib库的安装目录:
头文件目录 include以及库文件目录 lib。至此,zlib库就已经编译好了,接下来我们需要把编译得到的库文件拷贝到开发板。
进入到zlib安装目录下,将lib目录下的所有动态链接库文件拷贝到开发板 Linux系统/usr/lib 目录;注意在拷贝之前,需要先将出厂系统中原有的zlib 库文件删除,在开发板 Linux 系统下执行命令:
rm -rf /usr/lib/libz.* /lib/libz.*
删除之后,再将我们编译得到的zlib库文件通过scp命令拷贝到开发板/usr/lib目录,拷贝lib目录下的库文件时,需要注意符号链接的问题,不能破坏原有的符号链接;可以将lib目录下的所有文件打包成压缩包的形式,譬如进入到lib目录,执行命令:
tar -czf libz.tar.gz ./libz.*
拷贝过去之后,开发板/usr/lib
目录下就应该存在这些库文件,如下所示:
scp libz.tar.gz root@192.168.10.50:/home/root/
这里如果使用scp命令不成功,可以用其他的方法拷贝。
然后lib.tar.gz就拷贝到开发板了,然后使用下面命令将其解压至/usr/lib 目录下
tar -xmzf libz.tar.gz -C /usr/lib
然后使用下面命令查看是是否成功拷贝至/usr/lib目录。
ls -l /usr/lib/libz.*
移植好zlib库之后,接着我们开始移植libpng。
首先下载libpng源码包,进入https://github.com/glennrp/libpng/releases链接地址,如下:
我这里还是将将下载的 zlib-1.2.10.tar.gz 压缩文件拷贝到 Ubuntu 的共享文件夹下,然后将其解压开:
tar -zvxf libpng-1.6.35.tar.gz
解压之后得到 libpng-1.6.35文件夹,这便是 libpng 的源码目录。在编译 libpng 之前,先在tool目录下创建一个名为png的文件夹,作为libpng库的安装目录:
mkdir png
接着我们进入到 libpng 源码目录下,同样也是执行三部曲:配置、编译、安装,一套流程下来就 OK 了!在此之前,先对交叉编译工具的环境进行初始化,使用 source 执行交叉编译工具安装目录下的
environment-setup-cortexa7hf-neon-poky-linux-gnueabi 脚本文件(如果已经初始化过了,那就不用再进行初始化了):
source /opt/fsl-imx-x11/4.1.15-2.1.0/environment-setup-cortexa7hf-neon-poky-linux-gnueabi
libpng依赖于zlib库,前面我们已经将zlib库编译成功了,但是我们得告知编译器zlib库的安装目录,这样编译器才能找到zlib的库文件以及头文件,编译 libpng 的时才不会报错。
执行下面这三条命令,将zlib库安装目录下的include和lib路径导出到环境变量:
export LDFLAGS="${LDFLAGS} -L/home/zhiguoxin/linux/tool/zlib/lib"
export CFLAGS="${CFLAGS} -I/home/zhiguoxin/linux/tool/zlib/include"
export CPPFLAGS="${CPPFLAGS} -I/home/zhiguoxin/linux/tool/zlib/include"
接着执行下面这条命令对libpng源码工程进行配置:
./configure --prefix=/home/zhiguxoin/linux/tool/png --host=arm-poky-linux-gnueabi
–prefix选项指定libpng的安装目录,目录/home/zhiguxoin/linux/tool/png作为libpng的安装目录。
接着执行make进行编译:
make
最后执行make install安装即可!
make install
这时候我发现了一个问题,安装没有错误,但是我的安装目录/home/zhiguoxin/linux/tool/png
下就是没有任何东西,正常情况下安装目录下应该有bin include lib share
这四个文件夹,但是我的没有,我找了好半天问题也值不值为啥没有生成这四个文件夹,于是我就在我就在源码目录下新建了一个文件件,将安装路径换了一下,重新配置、编译、安装发现可以生成那四个文件夹。不知道这是啥原因,所以如果大家也遇到这个问题,可以在源码目录下新建一个文件夹,用来存放安装文件。下面是我的操作命令。
cd /home/zhiguoxin/shared/
tar -zvxf libpng-1.6.35.tar.gz
cd libpng-1.6.35
mkdir arm_png_install
source /opt/fsl-imx-x11/4.1.15-2.1.0/environment-setup-cortexa7hf-neon-poky-linux-gnueabi
export LDFLAGS="${LDFLAGS} -L/home/zhiguoxin/linux/tool/zlib/lib"
export CFLAGS="${CFLAGS} -I/home/zhiguoxin/linux/tool/zlib/include"
export CPPFLAGS="${CPPFLAGS} -I/home/zhiguoxin/linux/tool/zlib/include"
./configure --prefix=/home/zhiguoxin/shared/libpng-1.6.35/arm_png_install --host=arm-poky-linux-gnueabi
make
make install
之后我再将这几个文件夹拷贝到/home/zhiguoxin/linux/tool/png
目录下。
进入到 libpng 安装目录:
同样包含了 bin、include、lib 这些目录。
进入到 libpng 安装目录,将 bin 目录下的所有测试工具拷贝到开发板 Linux 系统/usr/bin 目录;将 lib 目录下的所有库文件拷贝到 Linux 系统/usr/lib 目录,注意在拷贝之前,先将开发板出厂系统中已经移植好的libpng 库文件删除,执行下面这条命令:
rm -rf /lib/libpng* /usr/lib/libpng*
删除之后,再将编译得到的 libpng 库文件拷贝到开发板/usr/lib 目录,拷贝lib目录下的库文件时,需要注意符号链接的问题,不能破坏原有的符号链接;可以将lib目录下的所有文件打包成压缩包的形式,譬如进入到lib目录,执行命令:
tar -czf lib.tar.gz ./libpng*
拷贝过去之后,开发板/usr/lib
目录下就应该存在这些库文件,如下所示:
scp lib.tar.gz root@192.168.10.50:/home/root/
这里如果使用scp命令不成功,可以用其他的方法拷贝。
然后lib.tar.gz就拷贝到开发板了,然后使用下面命令将其解压至/usr/lib 目录下
tar -xmzf lib.tar.gz -C /usr/lib
然后使用下面命令查看是是否成功拷贝至/usr/lib目录。
ls -l /usr/lib/libpng.*
libpng 除了解码功能之外,还包含编码功能,也就是创建 png 压缩文件,当然,这个笔者就不再介绍了。libpng 官方提供一份非常详细地使用文档,
笔者也是参考了这份文档给大家进行介绍的,这份文档的链接地址如下:
http://www.libpng.org/pub/png/libpng-1.4.0-manual.pdf
http://www.libpng.org/pub/png/libpng-manual.txt
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
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
static int show_png_image(const char *path)
{
png_structp png_ptr = NULL;
png_infop info_ptr = NULL;
FILE *png_file = NULL;
unsigned short *fb_line_buf = NULL; //行缓冲区:用于存储写入到LCD显存的一行数据
unsigned int min_h, min_w;
unsigned int valid_bytes;
unsigned int image_h, image_w;
png_bytepp row_pointers = NULL;
int i, j, k;
/* 打开png文件 */
png_file = fopen(path, "r"); //以只读方式打开
if (NULL == png_file) {
perror("fopen error");
return -1;
}
/* 分配和初始化png_ptr、info_ptr */
png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
if (!png_ptr) {
fclose(png_file);
return -1;
}
info_ptr = png_create_info_struct(png_ptr);
if (!info_ptr) {
png_destroy_read_struct(&png_ptr, NULL, NULL);
fclose(png_file);
return -1;
}
/* 设置错误返回点 */
if (setjmp(png_jmpbuf(png_ptr))) {
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
fclose(png_file);
return -1;
}
/* 指定数据源 */
png_init_io(png_ptr, png_file);
/* 读取png文件 */
png_read_png(png_ptr, info_ptr, PNG_TRANSFORM_STRIP_ALPHA, NULL);
image_h = png_get_image_height(png_ptr, info_ptr);
image_w = png_get_image_width(png_ptr, info_ptr);
printf("分辨率: %d*%d\n", image_w, image_h);
/* 判断是不是RGB888 */
if ((8 != png_get_bit_depth(png_ptr, info_ptr)) &&
(PNG_COLOR_TYPE_RGB != png_get_color_type(png_ptr, info_ptr))) {
printf("Error: Not 8bit depth or not RGB color");
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
fclose(png_file);
return -1;
}
/* 判断图像和LCD屏那个的分辨率更低 */
if (image_w > width)
min_w = width;
else
min_w = image_w;
if (image_h > height)
min_h = height;
else
min_h = image_h;
valid_bytes = min_w * bpp / 8;
/* 读取解码后的数据 */
fb_line_buf = malloc(valid_bytes);
row_pointers = png_get_rows(png_ptr, info_ptr);//获取数据
unsigned int temp = min_w * 3; //RGB888 一个像素3个bit位
for(i = 0; i < min_h; i++) {
// RGB888转为RGB565
for(j = k = 0; j < temp; j += 3, k++)
fb_line_buf[k] = ((row_pointers[i][j] & 0xF8) << 8) |
((row_pointers[i][j+1] & 0xFC) << 3) |
((row_pointers[i][j+2] & 0xF8) >> 3);
memcpy(screen_base, fb_line_buf, valid_bytes);//将一行数据刷入显存
screen_base += width; //定位到显存下一行
}
/* 结束、销毁/释放内存 */
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
free(fb_line_buf);
fclose(png_file);
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 \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);
}
/* 显示BMP图片 */
memset(screen_base, 0xFF, screen_size);//屏幕刷白
show_png_image(argv[1]);
/* 退出 */
munmap(screen_base, screen_size); //取消映射
close(fd); //关闭文件
exit(EXIT_SUCCESS); //退出进程
}
代码不再进行讲解,上述示例代码使用的是high-level接口处理方式,直接调用了png_read_png,一次性把整个png文件的数据解码出来,由于得到的数据是RGB888 格式,所以我们需要将其转为RGB565,转换完成之后将其刷入到显存中。
接下来我们编译示例代码,这里要注意下,使用交叉编译器编译代码时,需要指定libpng 库和zlib 库,如下所示:
arm-linux-gnueabihf-gcc -o show_png_image show_png_image.c -I /home/zhiguoxin/linux/tool/png/include -L /home/zhiguoxin/linux/tool/png/lib -L /home/zhiguoxin/linux/tool/zlib/lib -lpng -lz
使用-I选项指定libpng的头文件(也就是安装目录下的 include 目录),不需要指定zlib的头文件;使用了两次-L选项,分别指定了libpng和zlib的库目录(也就是安装目录下的 lib 目录);再使用-l 选项指定需要链接的库(z 表示 libz.so、png 表示 libpng.so)。
将编译得到的可执行文件拷贝到开发板Linux 系统的用户家目录下,并准备一个png文件,接着执行测试程序:
./show_png_image image.png