一、BMP图像介绍与显示

我们常用的图片格式有很多,一般最常用的有三种:JPEG(或 JPG)、PNG、BMP和GIF。其中 JPEG(或JPG)、PNG以及 BMP 都是静态图片,而 GIF 则可以实现动态图片。

BMP(全称 Bitmap)是Window操作系统中的标准图像文件格式,文件后缀名为.bmp,使用非常
广。它采用位映射存储格式,除了图像深度可选以外,图像数据没有进行任何压缩,因此,BMP 图像文件所占用的空间很大,但是没有失真、并且解析BMP图像简单

BMP文件的图像深度可选1bit、4bit、8bit、16bit、24bit 以及32bit,典型的BMP图像文件由四部分组成:

  • ①、BMP文件头(BMP file header),它包含BMP文件的格式、大小、位图数据的偏移量等信息;
  • ②、位图信息头(bitmap information),它包含位图信息头大小、图像的尺寸、图像大小、位平面数、压缩方式以及颜色索引等信息;
  • ③、调色板(color palette),这部分是可选的,如果使用索引来表示图像,调色板就是索引与其对应颜色的映射表;
  • ④、位图数据(bitmap data),也就是图像数据。

一般常见的图像都是以 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文件打开,文件头部分的内容如下所示:

1.1 bmp文件头

Windows下为bmp文件头定义了如下结构体:

typedef struct tagBITMAPFILEHEADER
{UINT16 bfType;DWORD bfSize;UINT16 bfReserved1;UINT16 bfReserved2;DWORD bfOffBits;
} BITMAPFILEHEADER;

结构体中每一个成员说明如下:


从上面的描述信息,再来对照文件数据:

  • 00~01H :0x42、0x4D 对应的 ASCII 字符分别为为B、M,表示这是 Windows 所支持的位图格式,该字段必须是BM才是Windows位图文件。
  • 02~05H :对应于文件大小,0x000BB848=768072字节=750KB,与image.bmp文件大小是相符的。
  • 06~09H :保留字段。
  • 0A~0D :0x00000046=70,即从文件头部开始到位图数据需要偏移70个字节。

bmp文件头的大小固定为 14 个字节。

1.2 位图信息头

同样,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;

结构体中每一个成员说明如下:

从上面的描述信息,再来对照文件数据:

  • 0E~11H :0x00000038=56,这说明这个位图信息头的大小为56个字节。
  • 12~15H :0x00000320=800,图像宽度为 800 个像素,与文件属性一致。
  • 16~19H :0x000001E0=480,图像高度为 480 个像素,与文件属性一致;
  • 1A~1BH :0x0001=1,这个值总为 1。
  • 1C~1DH :0x0010=16,表示每个像素占16个bit。
  • 1E~21H :0x00000003=0,bit-fileds 方式。
  • 22~25H :0x000BB802=768002,图像的大小,注意图像的大小并不是BMP文件的大小,而是图像数据的大小。
  • 26~29H :0x00001274=4724,水平分辨率为4724像素/米。
  • 2A~2DH :0x00001274=4724,垂直分辨率为4724像素/米。
  • 2E~31H :0x00000000=0,本位图未使用调色板。
  • 32~35H :0x00000000=0。

只有压缩方式选项被设置为bit-fileds(0x3)时,位图信息头的大小才会等于56字节,否则,为40字节。

1.3 调色板

调色板是单色、16 色、256 色位图图像文件所持有的,如果是 16 位、24 位以及 32 位位图文件,则 BMP文件组成部分中不包含调色板,关于调色板这里不过多介绍,有兴趣可以自己去了解。

1.4 位图数据

位图数据其实就是图像的数据,对于24位位图,使用3个字节数据来表示一个像素点的颜色,对于16位位图,使用2个字节数据来表示一个像素点的颜色,同理,32 位位图则使用4个字节来描述。
BMP位图分为正向的位图和倒向的位图,主要区别在于图像数据存储的排列方式,前面已经给大家解释的比较清楚了,如下如所示(左边对应的是正向位图,右边对应的则是倒向位图):

所以正向位图先存储图像的第一行数据,从左到右依次存放,接着存放第二行,依次这样;而倒向位图,则先存储图像的最后一行(倒数第一行)数据,也是从左到右依次存放,接着倒数二行,依次这样。

1.5 源码

#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>/**** BMP文件头数据结构 ****/
typedef struct {unsigned char type[2];      //文件类型unsigned int size;          //文件大小unsigned short reserved1;   //保留字段1unsigned short reserved2;   //保留字段2unsigned 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;elsemin_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;elsemin_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 <bmp_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);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);    //退出进程
}

1.6 编译程序

我们要想给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
  • 1、arm表示这是编译arm架构代码的编译器。
  • 2、linux表示运行在linux环境下。
  • 3、gnueabihf表示嵌入式二进制接口。
  • 4、gcc表示是gcc工具。

这样编译出来的 led程序才可以在ARM板子上运行。执行file bmp_show命令就可以看出hello是32位LSB的ELF格式文件,目标机架构为ARM,说明这个交叉编译正常,可执行文件可以在ARM板上执行。

1.7 上传程序到开发板执行

开发板启动后通过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

1.8 如何得到16位色RGB565格式 BMP图像?

在Windows下我们转换得到的BMP位图通常是24位色的RGB888格式图像,那如何得到RGB565格式 BMP位图呢?当然这个方法很多,这里笔者向大家介绍一种方法就是通过 Photoshop软件来得到 RGB565格式的 BMP 位图。

首先,找一张图片,图片格式无所谓,只要Photoshop软件能打开即可;确定图片之后,我们启动Photoshop软件,并且使用Photoshop软件打开这张图片,打开之后点击菜单栏中的文件—>存储为,接着出现如下界面:


二、jpeg图像介绍与显示

我们常用的图片格式有很多,一般最常用的有三种:JPEG(或 JPG)、PNG、BMP。BMP 图像虽然没有失真、并且解析简单,但是由于图像数据没有进行任何压缩,因此,BMP 图像文件所占用的存储空间很大,不适合存储在磁盘设备中。

而 JPEG(或 JPG)、PNG 则是经过压缩处理的图像格式,将图像数据进行压缩编码,大大降低了图像文件的大小,适合存储在磁盘设备中,所以很常用。

2.1 JPEG 简介

JPEG(Joint Photographic Experts Group)是由国际标准组织为静态图像所建立的第一个国际数字图像压缩标准,也是至今一直在使用的、应用最广的图像压缩标准

JPEG由于可以提供有损压缩,因此压缩比可以达到其他传统压缩算法无法比拟的程度;JPEG虽然是有损压缩,但这个损失的部分是人的视觉不容易察觉到的部分,它充分利用了人眼对计算机色彩中的高频信息部分不敏感的特点,来大大节省了需要处理的数据信息。JPEG 压缩文件通常以.jpg 或.jpeg 作为文件后缀名。

2.2 libjpeg简介

JPEG 压缩标准使用了一套压缩算法对原始图像数据进行了压缩得到.jpg 或.jpeg 图像文件,如果想要在LCD 上显示.jpg 或.jpeg 图像文件,则需要对其进行解压缩、以得到图像的原始数据,譬如RGB 数据。

既然压缩过程使用了算法,那对.jpg 或.jpeg 图像文件进行解压同样也需要算法来处理,这里并不会教大家如何编写解压算法,这些算法的实现也是很复杂的,我不会,自然教不了大家!但是,我们可以使用别人写好的库、调用别人写好的库函数来解压.jpg 或.jpeg 图像文件—libjpeg 库。

libjpeg是一个完全用C语言编写的函数库,包含了JPEG解码(解压缩)、JPEG 编码(创建压缩)和
其他的 JPEG 功能的实现。可以使用 libjpeg 库对.jpg 或.jpeg 压缩文件进行解压或者生成.jpg 或.jpeg 压缩文件。

2.3 libjpeg移植

libjpeg是一个开源 C 语言库,我们获取到它的源代码。

2.3.1 下载源码包

首先,打开http://www.ijg.org/files/链接地址,如下所示


目前最新的一个版本是v9e,对应的年份为2022年,这里我们选择一个适中的版本,笔者以v9b 为例,对应的文件名为jpegsrc.v9b.tar.gz,点击该文件即可下载。我把源码下在完成后放在共享文件夹下:

其实开发板出厂系统中已经移植了libjpeg库,但是版本太旧了!所以这里我们选择重新移植

2.3.2 编译源码

将 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 也就安装成功了!

2.3.3 安装目录下的文件夹介绍

进入到 libjpeg 安装目录:

2.3.4 移植到开发板

开发板出厂系统已经移植了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

2.4 libjpeg使用说明

libjpeg 提供的库函数对.jpg/.jpeg 进行解码(解压),得到 RGB 数据。首先,使用 libjpeg 库需要在我们的应用程序中包含它的头文件 jpeglib.h,该头文件包含了一些结构体数据结构以及 API 接口的申明。先来看看解码操作的过程:

⑴、创建 jpeg 解码对象;
⑵、指定解码数据源;
⑶、读取图像信息;
⑷、设置解码参数;
⑸、开始解码;
⑹、读取解码后的数据;
⑺、解码完毕;
⑻、释放/销毁解码对象。

2.5 libjpeg应用编程

对一个指定的 jpeg 图像进行解码,显示在 LCD 屏上,示例代码如下所示:

#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>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;    //像素深度bppstatic 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;elsemin_w = cinfo.output_width;if (cinfo.output_height > height)min_h = height;elsemin_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数据转为RGB565for (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 <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);}/* 显示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图像介绍与显示

3.1 PNG 简介

PNG(便携式网络图形格式 PortableNetwork Graphic Format,简称 PNG)是一种采用无损压缩算法的位图格式,其设计目的是试图替代GIF和TIFF 文件,同时增加一些GIF文件所不具备的特性。PNG 使用从LZ77 派生的无损数据压缩算法,它压缩比高,生成文件体积小,并且支持透明效果,所以被广泛使用。

特点

  • 无损压缩:PNG文件采用LZ77算法的派生算法进行压缩,其结果是获得高的压缩比,不损失数据。它利用特殊的编码方法标记重复出现的数据,因而对图像的颜色没有影响,也不可能产生颜色的损失,这样就可以重复保存而不降低图像质量。
  • 体积小:在保证图片清晰、逼真、不失真的前提下,PNG使用从LZ77派生的无损数据压缩算法,它压缩比高,生成文件体积小;
  • 索引彩色模式:PNG-8 格式与 GIF 图像类似,同样采用8位调色板将RGB彩色图像转换为索引彩色图像。图像中保存的不再是各个像素的彩色信息,而是从图像中挑选出来的具有代表性的颜色编号,每一编号对应一种颜色,图像的数据量也因此减少,这对彩色图像的传播非常有利。
  • 更优化的网络传输显示:PNG图像在浏览器上采用流式浏览,即使经过交错处理的图像会在完全下载之前提供浏览者一个基本的图像内容,然后再逐渐清晰起来。它允许连续读出和写入图像数据,这个特性很适合于在通信过程中显示和生成图像。
  • 支持透明效果:PNG可以为原图像定义256个透明层次,使得彩色图像的边缘能与任何背景平滑地融合,从而彻底地消除锯齿边缘。这种功能是GIF和JPEG没有的。

3.2 libpng简介与zlib移植

对于 png 图像,我们可以使用 libpng 库对其进行解码,跟libjpeg 一样,它也是一套免费、开源的 C 语言函数库,支持对png图像文件解码、编码等功能。

zlib其实是一套包含了数据压缩算法的函式库,此函数库为自由软件,是一套免费、开源的C语言函数库,所以我们可以获取到它源代码。

libpng 依赖于zlib库,所以要想移植libpng先得移植zlib库才可以,zlib也好、libpng也好,其实移植过程非常简单,无非就是下载源码、编译源码这样的一些工作。

在移植之前,开发板出厂系统都是已经移植好了这些库,其实是可以直接使用的,但是作为学习,必须要自己亲自把这些库给移植到开发板,这是非常重要的

3.2.1 下载源码包

我们可以进入到https://www.zlib.net/fossils/这个链接地址下载 zlib 源码包:


往下翻,找到一个合适的版本,这里我们就选择1.2.10版本的 zlib,点击文件名就可以下载了,下载成功之后就会得到.tar.gz 格式的压缩文件:

3.2.2 编译源码

我这里还是将将下载的 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/下面了。

3.2.3 安装目录下的文件夹介绍

进入到zlib库的安装目录:

头文件目录 include以及库文件目录 lib。至此,zlib库就已经编译好了,接下来我们需要把编译得到的库文件拷贝到开发板。

3.2.4 移植到开发板

进入到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.*

3.3 libpng移植

移植好zlib库之后,接着我们开始移植libpng。

3.3.1 下载源码包

首先下载libpng源码包,进入https://github.com/glennrp/libpng/releases链接地址,如下:

3.3.2 编译源码

我这里还是将将下载的 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.*

3.4 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

3.5 libpng应用编程

#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 <png.h>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;    //像素深度bppstatic 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;elsemin_w = image_w;if (image_h > height)min_h = height;elsemin_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转为RGB565for(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 <png_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);}/* 显示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

【正点原子I.MX6U-MINI应用篇】5、嵌入式Linux在LCD上显示BMP、JPG、PNG图片相关推荐

  1. 【正点原子MP157连载】第二十一章 嵌入式Linux LED驱动开发实验-摘自【正点原子】STM32MP1嵌入式Linux驱动开发指南V1.7

    1)实验平台:正点原子STM32MP157开发板 2)购买链接:https://item.taobao.com/item.htm?&id=629270721801 3)全套实验源码+手册+视频 ...

  2. 【正点原子I.MX6U-MINI应用篇】6、嵌入式Linux在LCD屏幕上显示字符

    一.原始方式: 取模显示字符 LCD显示屏是由width * height个像素点构成的,显示字符,一个非常容易想到的方法便是对字符取模,然后在LCD屏上打点显示字符:如果大家以前学习过单片机,想必接 ...

  3. 正点原子Linux移植Qt,正点原子I.MX6U Qt综合例程源码

    I.MX6U Qt综合例程源码 介绍 正点原子I.MX6U Qt综合例程源码 使用说明 1.Windows下编译,安装Qt 5.12.9,可以在Windows编译,打开工程,编译完成后把src文件夹放 ...

  4. 【正点原子FPGA连载】第三十九章OV7725摄像头RGB-LCD显示实验 -摘自【正点原子】新起点之FPGA开发指南_V2.1

    1)实验平台:正点原子新起点V2开发板 2)平台购买地址:https://detail.tmall.com/item.htm?id=609758951113 2)全套实验源码+手册+视频下载地址:ht ...

  5. Windows 7使用宝典安装技巧篇之——如何在Win7桌面上显示“我的电脑”

    Windows 7使用宝典安装技巧篇之 --如何在Win7桌面上显示"我的电脑" 现在有很多朋友都开始使用Windows 7了,其中很大一部分人都是从Windows XP直接跨越到 ...

  6. stm32 读取sd卡图片显示_「正点原子STM32Mini板资料连载」第三十五章 汉字显示实验...

    1)实验平台:正点原子STM32mini开发板 2)摘自<正点原子STM32 不完全手册(HAL 库版)>关注官方微信号公众号,获取更多资料:正点原子 第三十五章 汉字显示实验 汉字显示在 ...

  7. 【正点原子FPGA连载】第四十四章MT9V034摄像头HDMI显示实验 -摘自【正点原子】新起点之FPGA开发指南_V2.1

    1)实验平台:正点原子新起点V2开发板 2)平台购买地址:https://detail.tmall.com/item.htm?id=609758951113 2)全套实验源码+手册+视频下载地址:ht ...

  8. 【正点原子FPGA连载】 第三十章双目OV5640摄像头LCD显示实验 摘自【正点原子】DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南

    1)实验平台:正点原子MPSoC开发板 2)平台购买地址:https://detail.tmall.com/item.htm?id=692450874670 3)全套实验源码+手册+视频下载地址: h ...

  9. 基于正点原子stm32的mini板的TFTLCD显示实验

    本章将讲述TFTLCD显示实验,小白总结,如有错误,请大神指教. 一.TFTLCD 简介 1.TFT-LCD 即薄膜晶体管液晶显示器.TFT-LCD具有:亮度好.对比度高.层次感强.颜色鲜艳等特点.是 ...

最新文章

  1. python函数应用_Python 函数及其应用
  2. FastReport4.6程序员手册_翻译 转
  3. XML4跨浏览器兼容
  4. 可以发外链的网站_SEO分享:网站推广的四大推广方法
  5. html状态查询爱站,批量获取爱站数据
  6. 【数论】[CF258C]Little elephant and LCM
  7. 【AD】如何删除AD20右下角Title
  8. ZH奶酪:【阅读笔记】Deep Learning, NLP, and Representations
  9. 你真的了解JavaScript的Promise吗?
  10. Compiling XORP v1.2 in Debian 3.1
  11. 总结的Server.Mappath的用法
  12. Java面试 Java简历 Java简历模板
  13. 毕业设计之 - 题目:基于LSTM的预测算法 - 股票预测 天气预测 房价预测
  14. C#学习笔记:矩形判断
  15. SAP培训行业权威评测---51sap培训评测网(www.51sap.net)
  16. Crucible 安装日志
  17. 移动通信核心网技术总结(一)语音与上网业务的实现
  18. SSM SpringBoot vue办公自动化计划管理系统
  19. MinGW介绍与使用
  20. linux peek,Peek - Gif 录制软件

热门文章

  1. 那些年,我们关注过的放大电路设计要点
  2. 三生三世.枕上书 麦田里的守望者坎坷飞的
  3. 学计算机编程难吗,电脑编程难学吗 如何才能学好电脑编程
  4. 学习编程是否真的有用?
  5. 【读书】少有人走的路---自律(斯科特 派克)
  6. 个人看过的动漫、动画电影推荐
  7. vmbox主机和虚拟机无法共通网络服务 主机无法使用虚拟机的网络服务 虚拟机无法使用主机的网络服务
  8. 九麟SDK 接入文档
  9. Mac终端命令和连接服务器
  10. 【广告计算】互联网控制舆论的三个方法