以下内容源于朱有鹏嵌入式课程的学习与整理,如有侵权请告知删除。
参考内容
(1)https://xiefor100.blog.csdn.net/article/details/71941527
(2)https://xiefor100.blog.csdn.net/article/details/78529519
内容总结
1、本文主要介绍如何设计一个具有以下功能的图片播放器。
(1)“解码显示图片”功能,包括显示bmp图片、jpg图片、png图片。
(2)“图片文件的检索与管理”功能。
(3)“触摸翻页”功能。
2、在介绍如何设计的过程中,补充说明了以下内容。
(1)介绍了一些与 framebuff 有关的基本操作,比如打开、ioctl、map、填充背景等。
(2)介绍了像素、点阵、分辨率等与显示相关的概念。
(3)介绍了Image2LCD软件的设置与使用。
(4)介绍了像素数据RGB和BGR两种顺序引起的编程调整。
(5)介绍了如何任意起点位置来显示图片。
(6)介绍了bmp图片的本质与解析方法。
(7)介绍了jpg图片的本质与解析方法、libjpeg库的移植与使用。
(8)介绍了png图片的本质与解析方法、libpng库的移植与使用。
一、项目规划与展示
1、期待的效果
(1)图片播放器具有可移植性,即通过简单的配置,可以把它移植到各种开发板上(指不同大小尺寸的LCD,不同类型的触摸屏)。
(2)图片播放器支持以下图片格式:
bmp格式,则直接读取图片文件显示,如果图片大于LCD尺寸则使用插值缩放。
jpg格式,则libjpeg解码jpg->rgb888->rgb565。
png格式,则libpng解码。
gif格式,则libgif解码(该项目没有实现对gif格式图片的支持)。
(3)图片播放器支持以下操作效果:
设定时间自动播放(通过串口操作,实现顺序播放、随机播放等)。
点击屏幕两侧实现播放换页(上翻下翻)(目前只实现了该功能)。
划屏实现图片翻页(上翻下翻)。
实现简单屏幕按钮控件,点击“上一页”、“下一页”等button进行换页显示。
(4)还可以拓展以下功能:
在多点电容触摸屏上实现两指触摸放大缩小图片。
图片播放同时增加背景音乐播放。
2、项目的规划
项目采用逐步推进的方式执行,分几个版本来逐步实现所有功能。
(1)版本1
实现bmp、jpg、png、gif等图片的显示,移植各种解码库。
实现播放列表组织,自动识别并显示各种格式的混杂图片库文件。
实现自动定时切换顺序播放。
(2)版本2
添加触摸屏支持,实现侧边点击上下翻。
3、项目的展示
(1)整个项目的文件夹见链接,包含的文件如下所示。
root@ubuntu:/home/xjh/iot/embedded_basic/pictureShow/testproject# ls
display image image_manage include main.c Makefile Makefile.build README run.sh SI_Proj
root@ubuntu:/home/xjh/iot/embedded_basic/pictureShow/testproject# cd image
root@ubuntu:/home/xjh/iot/embedded_basic/pictureShow/testproject/image# ls *
bmp:
fengjing2.bmp qiche.bmp
jpg:
gougou.jpg meinv.jpg
png:
AdvancedC.png fengjing1.png
root@ubuntu:/home/xjh/iot/embedded_basic/pictureShow/testproject/image# cd ../display/
root@ubuntu:/home/xjh/iot/embedded_basic/pictureShow/testproject/display# ls
1024600.h bmp.h fb_bmp.c fb.c fb_jpeg.c fb_png.c Makefile picture.h
root@ubuntu:/home/xjh/iot/embedded_basic/pictureShow/testproject/display# cd ../image_manage/
root@ubuntu:/home/xjh/iot/embedded_basic/pictureShow/testproject/image_manage# ls
image_manager.c image_manager.h Makefile
root@ubuntu:/home/xjh/iot/embedded_basic/pictureShow/testproject/image_manage# cd ../SI_Proj/
root@ubuntu:/home/xjh/iot/embedded_basic/pictureShow/testproject/SI_Proj# ls
imageplayer.IAB imageplayer.IMB imageplayer.PFI imageplayer.PR imageplayer.PS
imageplayer.IAD imageplayer.IMD imageplayer.PO imageplayer.PRI imageplayer.WK3
root@ubuntu:/home/xjh/iot/embedded_basic/pictureShow/testproject/SI_Proj#
(2)如何运行这个播放器呢?
二、确认开发环境
1、开发环境
(1)硬件:PC(主机Win7 X64、虚拟机ubuntu14.04)、 X210开发板。
(2)软件:开发板运行着linux系统,而且程序只使用 linux API和C库,没有使用QT相关的库。
2、基础环境
(1)在开发板的iNand中或者外部SD卡中,部署好uboot。
(2)使用九鼎提供的移植好的源码来编译生成zImage,并放在tftp服务器中。
(3)自己制作完成的rootfs,即根据从零开始构建根文件系统一文得到的文件夹形式根文件系统。
(4)主机ubuntu中部署好tftp服务器、nfs服务器。
3、其他细节
(1)代码编辑器:SourceInsight。
(2)代码管理工具:通用的Makefile体系或者说模板。
(3)调试流程
- 首先,在PC主机的共享文件夹中,利用SI工软件编辑好代码(即完成testproject文件夹里面内容的编写);
- 然后,在虚拟机中编译(即在/mnt/hgfs/vmshare/testproject目录下执行make指令);
- 接着,将编译后的项目文件夹复制到文件夹形式的根文件系统中(即/home/xjh/iot/embedded_basic/rootfs/rootfs_xjh目录)。
(4)开发板标准:代码以X210V3S(屏幕分辨率为1024*600)开发板为准,而V3是800*480的。
三、开始编写代码
1、通用的Makefile模板
(1)本项目使用一个通用的Makefile体系来管理项目,包括顶层Makefile、Makefile.build、各个子文件夹中的Makefile,如下所示。
root@ubuntu:/home/xjh/iot/embedded_basic/pictureShow/testproject# ls
display image image_manage include main.c Makefile Makefile.build README run.sh SI_Proj
root@ubuntu:/home/xjh/iot/embedded_basic/pictureShow/testproject# cd display/
root@ubuntu:/home/xjh/iot/embedded_basic/pictureShow/testproject/display# ls
1024600.h bmp.h fb_bmp.c fb.c fb_jpeg.c fb_png.c Makefile picture.h
(2)各个子文件夹中的Makefile文件,其内容与下面的内容类似。
obj-y += fb.o
obj-y += fb_bmp.o
obj-y += fb_jpeg.o
obj-y += fb_png.o
(3)顶层Makefile文件的内容如下。
#CROSS_COMPILE = arm-none-linux-gnueabi- #交叉编译工具链的路径导出到PATH后可以这样写
CROSS_COMPILE = /usr/local/arm/arm-2009q3/bin/arm-none-linux-gnueabi-
AS = $(CROSS_COMPILE)as
LD = $(CROSS_COMPILE)ld
CC = $(CROSS_COMPILE)gcc
CPP = $(CC) -E
AR = $(CROSS_COMPILE)ar
NM = $(CROSS_COMPILE)nm
STRIP = $(CROSS_COMPILE)strip
OBJCOPY = $(CROSS_COMPILE)objcopy
OBJDUMP = $(CROSS_COMPILE)objdump
# export导出的变量是给子目录下的Makefile使用的
export AS LD CC CPP AR NM STRIP OBJCOPY OBJDUMP
# 编译器在编译时的参数设置
# -Wall表示所有的警告都显示出来 -O2表示优化等级 -g表示添加调试信息
CFLAGS := -Wall -O2 -g -DDEBUG
# 添加头文件路径,不添加的话,include目录下的头文件编译时找不到
# -I表示指定头文件路径,$(shell pwd)表示当前目录
#CFLAGS += -I $(shell pwd)/include -I/opt/libdecode/include
CFLAGS += -I $(shell pwd)/include
# 链接器的链接参数设置
# -l是在链接时找到后面的库,-L是为了能解析那些动态链接库
#LDFLAGS := -ljpeg -lz -lpng -L/opt/libdecode/lib
LDFLAGS :=
export CFLAGS LDFLAGS
TOPDIR := $(shell pwd)
export TOPDIR
# 定义将来编译生成的可执行程序的名字
TARGET := imageplayer
# 添加项目中所有使用到的源文件,包括顶层目录下的.c文件、子文件夹
# (1)添加顶层目录下的.c文件
obj-y += main.o
# (2)添加顶层目录下的子文件夹(注意目录名后面需要加一个/)
# 注意,如果子文件夹里还有文件夹,不需要在这里添加,
# 而是在子文件里的Makefile文件中添加。即各自管理下级。
# 我们暂时屏蔽这两个文件夹,因为还没用到
#obj-y += display/
#obj-y += image_manage/
# -C 后面加路径,表示进入到该路径,然后再执行make
# 第一条命令编译得到 built-in.o文件
# 第二条命令链接得到 imageplayer文件
all:
make -C ./ -f $(TOPDIR)/Makefile.build
$(CC) $(LDFLAGS) -o $(TARGET) built-in.o
# 这里是为了将整个文件夹拷贝至根文件系统中,注意实际路径的修改
cp:
#cp ../testproject/ /root/rootfs/ -rf
cp ../testproject/ /home/xjh/iot/embedded_basic/rootfs/rootfs_xjh -rf
clean:
rm -f $(shell find -name "*.o")
rm -f $(TARGET)
#.d文件是Makefile工作时生成的中间文件
distclean:
rm -f $(shell find -name "*.o")
rm -f $(shell find -name "*.d")
rm -f $(TARGET)
(4)Makefile.build文件内容如下。该文件是纯规则文件,不需要做任何改动。
PHONY := __build
__build:
obj-y :=
subdir-y :=
include Makefile
__subdir-y := $(patsubst %/,%,$(filter %/, $(obj-y)))
subdir-y += $(__subdir-y)
subdir_objs := $(foreach f,$(subdir-y),$(f)/built-in.o)
cur_objs := $(filter-out %/, $(obj-y))
dep_files := $(foreach f,$(cur_objs),.$(f).d)
dep_files := $(wildcard $(dep_files))
ifneq ($(dep_files),)
include $(dep_files)
endif
PHONY += $(subdir-y)
__build : $(subdir-y) built-in.o
$(subdir-y):
make -C $@ -f $(TOPDIR)/Makefile.build
built-in.o : $(cur_objs) $(subdir_objs)
$(LD) -r -o $@ $^
dep_file = .$@.d
%.o : %.c
$(CC) $(CFLAGS) -Wp,-MD,$(dep_file) -c -o $@ $<
.PHONY : $(PHONY)
(5)关于这套体系的更多介绍,见博客通用的Makefile。我们平常自己写的、包含子文件夹的项目可以直接套用这套Makefile体系。
2、利用SI软件建立工程
(1)创建一个名为testproject的文件夹,在该文件夹下操作:创建名为 include 的文件夹(内容暂时为空);创建上面的顶层Makefile文件、Makefile.build文件(新建文本文档,在重命名的时候去掉后缀“.txt”,然后使用notepad修改内容);创建名为SI_Proj的文件夹(用来存放SI工程相关的文件);创建名为main.c的文件(内容暂时为空)。
(2)在SI中新建名为imageplayer的项目,项目文件存储路径为SI_Proj文件夹的路径。
然后在弹出的“添加删除项目文件”界面,选择添加上面创建的main.c文件。
(3)在SI中编辑main.c文件如下(用于测试),然后在虚拟机共享目录下执行make(需要根据实际情况修改顶层Makefile文件的内容。另外在虚拟机共享目录下修改文件时,会提示文件是只读属性,因此在保持退出时要输入“wq!”),如果编译成功,则会生成可执行文件imageplayer。
#include<stdio.h>
int main()
{
printf("hello world!\n");
return 0;
}
(4)执行“make cp”将编译后的整个testproject文件夹拷贝到根文件系统中,将来开发板启动并加载根文件系统后,可以进入开发板的testproject文件夹,通过执行命令“./imageplayer”来运行播放器。但是我们一般不直接运行可执行文件,而是另外编写一个名为run.sh的脚本,该脚本的内容一般是“./imageplayer”,通过运行该脚本来运行可执行文件。这样操作的原因,是执行“./imageplayer”之前可能需要做一些前置工作,这时就可以在run.sh脚本的“./imageplayer”语句之前添加代码。
[root@xjh /]# ls
bin home mnt sbin tmp
dev lib proc sys usr
etc linuxrc root testproject var
[root@xjh /]# cd testproject/
[root@xjh /testproject]# ls
Makefile SI_Proj imageplayer main.c run.sh
Makefile.build built-in.o include main.o
[root@xjh /testproject]# ./imageplayer
hello world!
[root@xjh /testproject]# ./run.sh
hello world!
[root@xjh /testproject]#
四、添加framebuff的基本操作
这里说的framebuff的基本操作,是指打开显示屏设备,通过ioctl来探知显示屏设备的信息,进行内存映射,进行一些显示测试等。
首先,在PC主机共享文件夹里面的testproject目录中,创建名为display的文件夹。然后,在SI中通过“文件”菜单创建名为fb.c的文件(内容暂时为空),保存到display文件夹,并将该文件添加到当前项目。接着,在display文件夹里创建Makefile文件(新建文本文档,重命名时去掉后缀“.txt”,然后通过sublime text或notepad编辑):
obj-y += fb.o
最后,我们参考网络资源或者framebuffer驱动详解1——应用层编程实践来编写相关的代码。
(1)编辑fb.h文件
我们在SI中创建一个名为fb.h的文件,编辑器内容如下,然后把它保存到testproject/include目录,并添加到SI工程中。
// 宏定义
#define FBDEVICE "/dev/fb0"
// 旧开发板
//#define WIDTH 800
//#define HEIGHT 480
// 新开发板
#define WIDTH 1024
#define HEIGHT 600
#define WHITE 0xffffffff // test ok
#define BLACK 0x00000000
#define RED 0xffff0000
#define BLUE 0xff0000ff
#define GREEN 0xff00ff00 // test ok
#define GREENP 0x0000ff00 // 和GREEN一样,说明前2个ff透明位不起作用
// 函数声明
int fb_open(void);
void fb_close(void);
void fb_draw_back(unsigned int width, unsigned int height, unsigned int color);
void fb_draw_line(unsigned int color);
(2)编辑fb.c文件
编辑testproject/display/fb.c文件,内容如下:
/*****************************************************
fb.c文件是操作framebuffer的基础代码,
包括fb的打开、ioctl获取信息、基本的测试fb显示代码
*****************************************************/
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <linux/fb.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <unistd.h>//close函数
#include <fb.h>
// 全局变量
unsigned int *pfb = NULL;
int fbfd=-1;
int fb_open(void)
{
int ret = -1;
struct fb_fix_screeninfo finfo = {0};
struct fb_var_screeninfo vinfo = {0};
// 第1步:打开设备
fbfd = open(FBDEVICE, O_RDWR);
if (fbfd < 0)
{
perror("open error");
return -1;
}
printf("open %s success.\n", FBDEVICE);
// 第2步:获取设备的硬件信息
ret = ioctl(fbfd, FBIOGET_FSCREENINFO, &finfo);
if (ret < 0)
{
perror("ioctl error");
return -1;
}
printf("smem_start = 0x%lx, smem_len = %u.\n", \
finfo.smem_start, finfo.smem_len);
ret = ioctl(fbfd, FBIOGET_VSCREENINFO, &vinfo);
if (ret < 0)
{
perror("ioctl error");
return -1;
}
printf("xres = %u, yres = %u.\n", vinfo.xres, vinfo.yres);
printf("xres_virtual = %u, yres_virtual = %u.\n",vinfo.xres_virtual, vinfo.yres_virtual);
printf("bpp = %u.\n", vinfo.bits_per_pixel);
// 第3步:进行mmap
unsigned long len = vinfo.xres_virtual * vinfo.yres_virtual * vinfo.bits_per_pixel / 8;
printf("len = %ld\n", len);
pfb = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fbfd, 0);
if (NULL == pfb)
{
perror("mmap error");
return -1;
}
printf("pfb = %p.\n", pfb);
return 0;
}
void fb_close(void)
{
close(fbfd);
}
void fb_draw_back(unsigned int width, unsigned int height, unsigned int color)
{
unsigned int x, y;
for (y=0; y<height; y++)
{
for (x=0; x<width; x++)
{
*(pfb + y * WIDTH + x) = color;
}
}
}
void fb_draw_line(unsigned int color)
{
unsigned int x;
for (x=50; x<600; x++)
{
*(pfb + 200 * WIDTH + x) = color;
}
}
(3)编辑main.c文件
编辑testproject/main.c文件,内容如下:
#include<stdio.h>
#include<fb.h>
int main()
{
int ret=-1;
printf("image decode player.....\n");
ret = fb_open();
if (ret < 0)
{
printf("fb_open error.\n");
return -1;
}
fb_draw_back(1024,600,0xffff0000);
fb_draw_line(0xff00ff00);
fb_close();
return 0;
}
(4)测试运行
首先删除顶层Makefiel文件中“#obj-y += display/”语句前的“#”,然后在虚拟机/mnt/hgfs/vmshare/testproject目录下执行make,编译生成imageplayer这个可执行程序。
然后执行“make cp”将编译后的整个testproject文件夹拷贝到根文件系统中。
启动开发板后,在板载系统的根目录下存在testproject目录,我们进入该目录中执行程序。
[root@xjh ]# ls
bin home mnt sbin tmp
dev lib proc sys usr
etc linuxrc root testproject var
[root@xjh ]# cd testproject/
[root@xjh testproject]# ls
Makefile SI_Proj display include main.o
Makefile.build built-in.o imageplayer main.c run.sh
[root@xjh testproject]# ./run.sh
image decode player.....
open /dev/fb0 success.
smem_start = 0x475ab000, smem_len = 3072000.
xres = 800, yres = 480.
xres_virtual = 800, yres_virtual = 960.
bpp = 32.
len = 3072000
pfb = 0x40183000.
[root@xjh testproject]# testproject
但显示屏的显示为何如此怪异?是因为我哪里设置不对吗?或者显示屏本身损坏了?
以前做驱动开发时,开发板启动后显示屏显示的内容如下,也是一半显示一半怪异的。
注意到上面打印的分辨率信息居然是800*480的,但我在fb.h中明明设置为1024*600,哪个环节出现问题呢?根据应用层为何不能设置分辨率,应该是内核源码中LCD驱动部分设置得不对。
第二节“确认开发环境”的“基础环境”提到,使用九鼎提供的移植好的内核源码来编译生成zImage,当时完成这个步骤时,我并没有注意查看它所设置的LCD分辨率是多少。
我们在内核源码x210_kernel/arch/arm/mach-s5pv210/mach-x210.c文件中,修改如下。
//该文件开头部分的代码
#define AT070TN92 1
#define VGA_800X600 2
#define VGA_1024X768 3
#define DISP_MODE AT070TN92
//忽略部分代码
//由开头部分的代码可知,这里的DISP_MODE=AT070TN92,因此选择#else的分支
#if(DISP_MODE==VGA_800X600)
#define S5PV210_LCD_WIDTH 800
#define S5PV210_LCD_HEIGHT 600
#elif(DISP_MODE==VGA_1024X768)
#define S5PV210_LCD_WIDTH 1024
#define S5PV210_LCD_HEIGHT 768
#else
//#define S5PV210_LCD_WIDTH 800
//#define S5PV210_LCD_HEIGHT 480
//根据实际情况修改如下
#define S5PV210_LCD_WIDTH 1024
#define S5PV210_LCD_HEIGHT 600
#endif
然后参考内核配置与编译——编译初体验、以NFS方式挂载rootfs的设置方法,重新配置与编译,在内核源码的arch/arm/boot目录下生成zImage,然后拷贝到/tftpboot目录。
接着重新执行上面的测试操作,发现显示屏正常显示。
五、与显示有关的概念
这部分的内容参考博客LCD——S5PV210的LCD的理论与操作的第一节的第5小点。
六、利用Image2LCD提取图片数据
如何显示一幅图片呢?我们需要先获取这幅图片数据,然后丢到显存即可。
一幅图片的数据,可以用一个char类型的数组来表示。比如一张1024*600分辨率的图片,一共有1024*600个像素点,每个像素点的数据用3个字节来表示(RGB888),那么这个数组的长度就是1024*600*3,数组中每3个成员为一组,表示一个像素点。
如何获取一幅图片的数组表示呢?这里有两种方式:
(1)利用软件Image2LCD来提取图片数据。
(2)自己编写代码来提取图片数据(即自己实现Image2LCD的核心功能)。
这里首先介绍如何利用软件Image2LCD来提取图片数据。后续在显示bmp、png、peg图片是再自己编写代码来提取图片数据。
1、下载Image2LCD软件
软件Image2LCD的下载地址见链接。
2、使用软件提取图片数据
比如待提取数据的图片,是bmp格式的1024*600分辨率的图片,如下所示:
(1)在软件界面顶部的菜单栏里,打开待提取数据的图片文件(因为显示屏是1024*600的,所以图片文件也必须是1024*600的)。
(2)在软件界面的左侧部分,进行以下操作:
根据实际需要,输出数据类型可以选择不同类型。这里我们选择“C语言数组”。
一般情况下,扫描模式选择“水平扫描”。
一般情况下,输出灰度选择“24位真彩色”(即RGB888)。也可以选择32位真彩色,但此时高八位的数据没有意义。
一般情况下,不勾选“包含图像头数据”,即我们只需要纯数据。
根据图片大小来设置最大宽度和高度,然后点击右边按钮更新。因为图片是1024*600,所以这里我们设置为1024*600。
(3)在软件界面的底部,选中24位彩色,可以通过拖动色块来调整RGB顺序。
(4)在软件界面的顶部,点击保存,然后输入文件名xxx.h,则会生成xxx.h文件,该文件定义了一个字符数组gImage_xxx。
比如1024600.h文件中定义了一个字符数组gImage_1024600。
const unsigned char gImage_1024600[1843200] = //1024*600*3=1843200
{
/* 0X00,0X18,0X00,0X04,0X58,0X02,0X00,0X1B, */ //被注释的是文件头信息
0X28,0X17,0X0F,0X2B,0X1A,0X12,0X2B,0X1A,0X12,0X28,0X17,0X0F,0X27,0X16,0X0E,0X2A,
0X19,0X11,0X2E,0X1D,0X15,0X30,0X1F,0X17,0X2C,0X1B,0X13,0X2E,0X1B,0X14,0X2F,0X1C,
0X15,0X30,0X1C,0X15,0X32,0X1E,0X17,0X35,0X22,0X1B,0X37,0X24,0X1D,0X36,0X25,0X1D,
0X39,0X27,0X1D,0X39,0X27,0X1D,0X39,0X28,0X1E,0X3B,0X2A,0X20,0X3A,0X29,0X1F,0X34,
0X26,0X1B,0X33,0X25,0X1A,0X35,0X27,0X1C,0X2F,0X26,0X17,0X36,0X2A,0X1C,0X36,0X2A,
//这里省略很多数值
}
3、显示图片内容
(1)将上面的1024600.h文件保存到testproject/display目录下,并添加到SI工程中。
(2)在testproject/display/fb.c文件中追加以下两个函数的实体,以及包含1024600.h这个头文件。
//在fb.c文件开头添加
#include "1024600.h"//因为1024600.h与fb.c处于同一目录下,所以这样写
//如果将1024600.h放在testproject/include目录下,则这样写“ #include <1024600.h> ”
//省略旧fb.c文件内容
/******显示1024*600分辨率的图片*********/
// 写法一
void fb_draw_picture1(void)
{
const unsigned char *pData = gImage_1024600; //指向图像对应的数组
unsigned int *p = pfb;
unsigned int i,j,cnt;
for (i=0; i<HEIGHT; i++)
{
for (j=0; j<WIDTH; j++)
{
//cnt = WIDTH * i + j; // 当前像素点的编号
//cnt *= 3; // 当前像素点的数据在数组中的下标
cnt = 3 * (WIDTH * i + j);
// 当前像素点对应的RGB分别是pData[cnt+0]、pData[cnt+1]、pData[cnt+2]
// 当前像素点的数据
//*p = ((pData[cnt+2]<<0) | (pData[cnt+1]<<8)| (pData[cnt+0]<<16));
// 当R和B的顺序相反时
*p = ((pData[cnt+2]<<16) | (pData[cnt+1]<<8)| (pData[cnt+0]<<0));
p++;
}
}
}
// 写法二
void fb_draw_picture2(void)
{
const unsigned char* pdata = gImage_1024600;
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有线性关系,所以可以像下面这样写
*(p+cnt)=((pdata[cnt*3+0]<<16)|(pdata[cnt*3+1]<<8)|\
(pdata[cnt*3+2]<<0));
}
}
}
(3)在testproject/display/fb.h文件中追加这两个函数的声明。
//旧fb.h之前的代码
//显示函数写法一
void fb_draw_picture1(void);
//显示函数写法二
void fb_draw_picture2(void);
(4)将testproject/main.c文件修改如下。
#include<stdio.h>
#include<fb.h>
int main()
{
int ret=-1;
printf("image decode player.....\n");
ret = fb_open();
if (ret < 0)
{
printf("fb_open error.\n");
return -1;
}
//fb_draw_back(1024,600,0xffff0000);
//fb_draw_line(0xff00ff00);
fb_draw_picture1();//或者fb_draw_picture2(),二选一
fb_close();
return 0;
}
(5)测试运行
在虚拟机共享目录下完成编译,然后执行“make cp”,启动开发板后进入testproject目录,执行“./run.sh”,此时显示的内容如下:
[root@xjh ]# ls
bin home mnt sbin tmp
dev lib proc sys usr
etc linuxrc root testproject var
[root@xjh ]# cd testproject/
[root@xjh testproject]# ./run.sh
image decode player.....
open /dev/fb0 success.
smem_start = 0x475ab000, smem_len = 4915200.
xres = 1024, yres = 600.
xres_virtual = 1024, yres_virtual = 1200.
bpp = 32.
len = 4915200
pfb = 0x40183000.
[root@xjh testproject]#
七、解决其他显示问题
1、调整RGB顺序
(1)上面的图片没有明显的红蓝区域,难以体现RGB顺序所导致的问题,因此更换另外一张分辨率为1024*600的jpg格式的图片:
情形1:不修改第六节的代码,在利用Image2LCD提取图片数据时,在“输出图像调整-24位真彩色”中是RGB,则显示屏内容如下:
情形2:不修改第六节的代码,在利用Image2LCD提取图片数据时,在“输出图像调整-24位真彩色”中是BGR,则显示屏内容如下:
情形3:修改第六节中的fb_draw_picutre1函数(修改细节见该函数内部),在利用Image2LCD提取图片数据时,在“输出图像调整-24位真彩色”中是RGB,此时显示屏显示与情形2一致。
情形4:修改第六节中的fb_draw_picutre1函数(修改细节见该函数内部),在利用Image2LCD提取图片数据时,在“输出图像调整-24位真彩色”中是BGR,此时显示屏显示与情形1一致。
(2)由上面可知,图像数据中的排布(可在Image2LCD中调整RGB顺序来实现不同排布)、应用程序中的排布(即在fb_draw_picutre1函数中是如何合成一个像素点数据的),另外framebuff驱动中的排布(驱动中是如何规定RGB顺序的),都会影响RGB的顺序。
(3)上面三个因素的RGB顺序一致时则显示正常,不一致时则可能导致显示结果中R和B相反(假设这里的G始终位于中间)。不过我们在写程序时一般不分析这东西,而是根据实际显示效果来调整,即如果 R和 B相反,我们调整应用程序中的 RGB 的顺序即可(这样只需要修改一行代码)。如果选择调整驱动,需要重新编译内核;如果选择调整图像数据,图片数量太多时操作会很麻烦。
2、显示任意分辨率的图片
(1)图片比屏幕分辨率大。此时多出来的部分肯定无法显示。处理方法是在显示函数中丢弃不能被显示的部分的数据。
这种情况下,我们采用Image2LCD提取图片数据时,在“最大宽度和高度”里面设置为显示屏分辨率,即设置为1024*600,然后点击右边的按钮来更新,最终生成的图片数据不包含无法显示部分的数据(我原以为是截取,但实际是抽样)。
比如,我们要在屏幕中显示下面这张分辨率为1920*1080 的jpg格式的图片,由于屏幕分辨率只是1024*600,因此从左上角算起,水平方向1025~1920,垂直方向601~1080的内容将无法显示。
忽略操作过程(和第六节一样),实际显示屏的显示如下,可见并非跟我预期的那样,即从上面图片左上角开始截取 1024*600 来显示,而是通过对上面图片进行抽样显示。而且屏幕的底部也没有填满,这是为何呢?该软件的帮助手册有这样的说明:输出图像是由输入图像按照当前设置的数据格式转变得到显示数据并显示。如果输入图像超过用户设置的最大宽度或最大高度,输入图像将被比例缩小到最大宽度和最大高度之内。这只解释了为何是抽样显示,那屏幕底部的内容是什么?
这里暂时无法解决,等价解决见下面的“任意起点显示图片”。
(2)图片比屏幕分辨率小。此时图片只是填充屏幕中一部分,屏幕的剩余部分保持原来的底色。
比如,我们要在屏幕中显示下面这张分辨率为901*460的jpg格式的图片:
首先,我们在testproject/display/fb.c文件中追加下面的fb_draw_picture3函数。
//省略旧fb.c文件代码
/******显示901*460分辨率的图片*********/
void fb_draw_picture3(void)
{
const unsigned char* pdata = gImage_901460;
unsigned int x,y,cnt;
unsigned int a=0;
unsigned int *p = pfb;
for(y=0;y<460;y++)
{
for(x=0;x<901;x++)
{
cnt=y*WIDTH+x;/*cnt始终都是framebuff像素点的编号*/
*(p+cnt)=((pdata[a+0]<<16)|(pdata[a+1]<<8)|(pdata[a+2]<<0));
a+=3;/*由于空缺一部分,像素点编号与像素点矩阵不存在倍数关系了,此时应该三个三个地传送进来*/
}
}
}
然后,在testproject/display/fb.h文件中追加fb_draw_picture3函数的声明。
接着,将第六节的testproject/main.c文件中的main函数里面的fb_draw_picture1函数,修改为这里的函数fb_draw_picture3。
然后,利用Image2LCD提取上面的图片数据,命名为901460.h文件,并保存在display文件下。
接着,将testproject/display/fb.c文件开始位置添加 #include "901460.h" 。
最后,在虚拟机共享目录下完成编译,执行make cp,在开发板的/testproject目录下执行“./run.sh”。
[root@xjh testproject]# ls
Makefile SI_Proj display include main.o
Makefile.build built-in.o imageplayer main.c run.sh
[root@xjh testproject]# ./run.sh
image decode player.....
open /dev/fb0 success.
smem_start = 0x475ab000, smem_len = 4915200.
xres = 1024, yres = 600.
xres_virtual = 1024, yres_virtual = 1200.
bpp = 32.
len = 4915200
pfb = 0x40183000.
[root@xjh testproject]
3、在任意起点位置显示图片
(1)情形1:小图片任意起点,但整个图片显示没有超出屏幕范围内。
比如我们要在屏幕上任意起点位置,显示一张分辨率为500*300的jpg格式图片,如下所示:
首先我们知道,屏幕上坐标(x,y)这个像素点的编号 =(y-y0)*图宽度 + (x-x0),如下所示。
因此我们在testproject/display/fb.c文件中,追加下面的fb_draw_picture4函数与头文件包含,并在文件fb.h中追加这个函数的声明。
//fb.c文件开头处添加
#include "500300.h"
/******任意起点显示小图片**********/
//写法一
//假设要显示的图片分辨率为500*300,从(x0,y0)开始显示
void fb_draw_picture4(unsigned int x0,unsigned int y0)
{
unsigned char* pdata=gImage_500300;
unsigned int x,y,cnt;
unsigned int *p=pfb;
unsigned int a=0;
for(y=y0;y<y0+300;y++)
{
for(x=x0;x<x0+500;x++)
{
cnt=y*WIDTH+x;/*cnt始终都是framebuff像素点的编号*/
*(p+cnt)=((pdata[a+0]<<16)|(pdata[a+1]<<8)|(pdata[a+2]<<0));
a+=3;/*由于空缺一部分,像素点编号与像素点矩阵不存在倍数关系了,此时应该三个三个地传送进来*/
}
}
}
#if 0 //写法二
void fb_draw_picture4(unsigned int x0,unsigned int y0)
{
const unsigned char *pData = gImage_500300;
unsigned int cnt1, cnt2;
unsigned int x, y;
for(y=y0;y<y0+300;y++)
{
for(x=x0;x<x0+500;x++)
{
cnt1 = WIDTH * y + x;//当前像素点的编号
cnt2 = 500 * y + x;//cnt2乘以3之后,表示当前像素点的数据在数组中的下标
*(pfb + cnt1) = ((pData[3*cnt2+2]<<0) | (pData[3*cnt2+1]<<8) | pData[3*cnt2+0]<<16));
}
}
}
#endif
接着,利用Image2LCD提取上面图片的数据,保存名字为500300.h,并保存到display目录中。
然后,修改main.c文件中的main函数,测试fb_draw_picture4是否起效。
#include<stdio.h>
#include<fb.h>
int main()
{
int ret=-1;
printf("image decode player.....\n");
ret = fb_open();
if (ret < 0)
{
printf("fb_open error.\n");
return -1;
}
//fb_draw_back(1024,600,0xffff0000);
//fb_draw_line(0xff00ff00);
//fb_draw_picture1();//fb_draw_picture1与fb_draw_picture2是同样效果
//fb_draw_picture3();//以(0,0)为起点,显示901*460图片
fb_draw_picture4(300,200);//以(300,200)为起点,显示500*300的图片
fb_close();
return 0;
}
实际显示效果如下(这里的QT是启动后的屏幕显示,因为在main.c中没有刷背景,所以这里保持原来的背景图):
(2)情形2:起点位置导致图片超出屏幕外。
情形1中main函数里规定的起点是(300,200),加上图片的大小,依然没有超出屏幕的范围。
测试1:左右超出)为了演示起点位置导致图片超出屏幕外,我们在main函数里规定起点为(900,200),此时实际显示效果如下。从中可知,右边超出去的部分,会在左边补全。这是内部for循环可能超过1024的界定(但没有超出fb的大小)造成的。
测试2:上下超出)或者我们在main函数里规定起点为(300,400),此时实际显示效果如下。从中可知,下边的超出去的部分,不会在上边显示,而是会消失。这是因为双缓冲进到了另一帧,如果没有双缓冲则会内存溢出(这里说的双缓冲,是指 framebuff 的大小是显示屏分辨率的两倍)。
修正显示)为了让测试1中右边超出去的部分不再显示,让测试2中双缓冲里那些没有被显示的数据丢失,我们在fb.c文件中追加fb_draw_picture5函数,在fb.h文件追加这个函数的声明。这个函数的内容如下:
/**********让右边超出去的部分不再显示************************/
//写法1
void fb_draw_picture5(unsigned int x0,unsigned int y0)
{
unsigned char* pdata=gImage_500300;
unsigned int x,y,cnt;
unsigned int a=0;
for(y=y0;y<y0+300;y++)
{
if(y>=HEIGHT)//y方向超出
{
a+=3;
break;//最后一行已经显示了,剩下的不用考虑了
}
for(x=x0;x<x0+500;x++)
{
if(x>=WIDTH)//x方向超出了
{
a+=3;
continue;
}
cnt=y*WIDTH+x;/*cnt始终都是像素点的编号*/
*(pfb+cnt)=((pdata[a+0]<<16)|(pdata[a+1]<<8)|(pdata[a+2]<<0));
a+=3;/*因此,像素点矩阵也应该三个三个地传送进来*/
}
}
}
#if 0 //写法二
void fb_draw_picture5(unsigned int x0,unsigned int y0)
{
unsigned char* pdata=gImage_500300;
unsigned int x,y,cnt;
unsigned int cnt1,cnt2;
for(y=y0;y<300+y0;y++)
{
for(x=x0;x<500+x0;x++)
{
if(x>=WIDTH)//x方向超出了
{
continue;
}
cnt1=y*WIDTH+x;//cnt1是当前像素点的编号
cnt2=500*(y-y0)+(x-x0);
//左值考虑当前像素点在fb中的偏移量,右值考虑当前像素点在图像数据数组的下标
*(pfb+cnt1)=((pdata[cnt2*3+0]<<16)|(pdata[cnt2*3+1]<<8)|(pdata[cnt2*3+2]<<0));
}
}
}
#endif
在main函数中调用fb_draw_picture5函数,并以(900,200)为起点,此时实际显示效果如下。可知使用该函数后,右边超出去的部分,不会在左边补全。