图片播放器的实现1——利用Image2LCD提取图片数据并显示

news2024/11/20 19:33:14

以下内容源于朱有鹏嵌入式课程的学习与整理,如有侵权请告知删除。

参考内容

(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)为起点,此时实际显示效果如下。可知使用该函数后,右边超出去的部分,不会在左边补全。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/159242.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2022. 12 青少年软件编程(图形化) 等级考试试卷(四级)

2022年12月 青少年软件编程&#xff08;图形化&#xff09; scratch等级考试试卷&#xff08;四级&#xff09; 分数&#xff1a; 100 题数&#xff1a; 28 一、单选题(共 15题&#xff0c;共 30分) 1.运行下列程序&#xff0c; 变量“结果”的值为&#xff1f; &#xff08; &…

WXSS 如何进行编译?

过往中小企业或技术团队开发一个 App 的时间成本和人力成本居高难下&#xff0c;但是随着微信上线小程序&#xff0c;更像是为这部分群体打开了一扇天窗&#xff0c;此后小程序呈现出井喷式发展的状态&#xff0c;不仅微信&#xff0c;支付宝、百度、抖音等超级 App 都跟上步伐…

【C++核心编程】C++全栈体系(十)

C核心编程 第四章 类和对象 六、继承 继承是面向对象三大特性之一 有些类与类之间存在特殊的关系&#xff0c;例如下图中&#xff1a; 我们发现&#xff0c;定义这些类时&#xff0c;下级别的成员除了拥有上一级的共性&#xff0c;还有自己的特性。 这个时候我们就可以考…

华为DHCPv6实验配置

目录 配置AR1作为DHCPv6服务器为PC1分配IPv6地址 配置AR2作为DHCPv6服务器&#xff0c;AR1作为DHCPv6中继器为PC2分配IPv6地址 配置AR3作为DHCPv6 PD服务器为AR1分配地址前缀 什么是DHCP PD 配置AR1作为DHCPv6服务器为PC1分配IPv6地址 AR1 DHCPv6服务器端配置 ipv6 …

解决N+1问题的另一种方法 - 关联的多结果集ResultSet

如果我的博客对你有帮助&#xff0c;欢迎进行评论✏️✏️、点赞&#x1f44d;&#x1f44d;、收藏⭐️⭐️&#xff0c;满足一下我的虚荣心&#x1f496;&#x1f64f;&#x1f64f;&#x1f64f; 。 从版本 3.2.3 开始&#xff0c;MyBatis 提供了另一种解决 N1 查询问题的方…

C语言——位段

文章目录思维导图&#xff1a;一. 什么是位段二.位段的内存分配三.位段的跨平台问题四.位段的应用结语:思维导图&#xff1a; 一. 什么是位段 位段的声明和结构体类似&#xff0c;但是有2个不同&#xff1a; 位段的成员必须是int、unsigned int 或 signed int(在很多平台上cha…

python自学之《21天学通Python》(9)——基于tkinter的GUI编程

第12章 基于tkinter的GUI编程 Windows的图形用户界面非常方便用户操作&#xff0c;因此&#xff0c;Windows操作系统得到了广大个人计算机用户的欢迎。在Python中&#xff0c;也可以编写美观的GUI界面应用程序与项目。tkinter是Python自带的用于GUI编程的模块&#xff0c;tkin…

【论文速递】CVPR2022 - 学习 什么不能分割:小样本分割的新视角

【论文速递】CVPR2022 - 学习 什么不能分割:小样本分割的新视角 【论文原文】&#xff1a;Learning What Not to Segment: A New Perspective on Few-Shot Segmentation 获取地址&#xff1a;https://openaccess.thecvf.com/content/CVPR2022/papers/Lang_Learning_What_Not_…

Linux--线程控制--线程相关函数--tid--0109 10

1.如何理解线程 定义&#xff1a;在一个程序里的一个执行路线就叫做线程&#xff08;thread&#xff09;。 更准确的定义是&#xff1a;线程是“一个进程内部的控制序列”。 每个进程都有自己的进程地址空间和task_struct结构体&#xff0c;如果我们通过一定的方式在创建进程…

【记录】ChatGPT|近期两次更新一览(更新至2023年1月12日)

如果你还没有使用过ChatGPT&#xff0c;可以先看看我的上一篇文章&#xff1a;【记录】ChatGPT&#xff5c;注册流程、使用技巧与应用推荐&#xff08;更新至2022年12月14日&#xff09;。   昨天晚上&#xff0c;ChatGPT突然很多人都无法登录&#xff0c;包括我。我当时以为…

SpringBoot+Redis+@Cacheable实现缓存功能

SpringBootRedisCacheable实现缓存功能一、pom文件加入Redis与cache的依赖和yml配置二、EnableCaching允许使用注解进行缓存三、Redis配置四、业务逻辑1.UserController2.UserService3.UserServiceImpl4.AdminServiceImpl5.Cacheable和CachePut区别五、测试1.执行saveUser方法2…

剑指offer----C语言版----第十七天----面试题23:链表中环的入口节点

目录 1. 链表中环的入口节点 1.1 环形链表Ⅰ 1.1.1 题目描述 1.1.2解题思路 1.1.3 扩展问题 1.2 环形链表Ⅱ 1.2.1 题目描述 1.2.2 思路分析 1. 链表中环的入口节点 在leetcode上的剑指offer专栏没有收录这道题目&#xff0c;但Leetcode上是有这道题目的&#xff0c;环…

U3D客户端框架之 音效管理器 与 Fmod介绍安装导入Unity

一、Fmod介绍与安装导入Unity 1.Fmod与Unity内置Audio播放器对比 Unity内置的Audio底层使用的是FMOD&#xff0c;但是功能不够齐全&#xff0c;高级一点的功能如混合(Mix)等无法使用&#xff1b; 音效管理应该和Unity工程解耦合&#xff0c;这样子可以减轻音效设计师的负担&a…

ArcGIS基础实验操作100例--实验86矢量面重叠分析

本实验专栏参考自汤国安教授《地理信息系统基础实验操作100例》一书 实验平台&#xff1a;ArcGIS 10.6 实验数据&#xff1a;请访问实验1&#xff08;传送门&#xff09; 空间分析篇--实验86 矢量面重叠分析 目录 一、实验背景 二、实验数据 三、实验步骤 &#xff08;1&am…

初阶指针详解✍

目录1.内存和地址2.指针变量的大小3.指针类型的意义意义1&#xff1a;指针访问权限的大小意义2&#xff1a;指针类型决定指针的步长4.野指针野指针成因如何规避野指针5.指针的运算指针加减整数指针减指针指针的比较运算6.指针与数组的关系7.二级指针1.内存和地址 内存是电脑上特…

2、C语言程序规范

目录 1. 代码缩进 2. 变量、常量命名规范 3. 函数的命名规范 4. #include指令 5. 注释 6. main函数 7.函数返回值 8. 变量赋初值 俗话说&#xff0c;“没有规矩&#xff0c;不成方圆。” 如&#xff1a;第一个程序 #include <stdio.h>void main(){printf("…

基于java Springmvc+mybatis 电影院售票管理系统设计和实现以及文档

基于java Springmvcmybatis 电影院售票管理系统设计和实现以及文档 博主介绍&#xff1a;5年java开发经验&#xff0c;专注Java开发、定制、远程、文档编写指导等,csdn特邀作者、专注于Java技术领域 作者主页 超级帅帅吴 Java毕设项目精品实战案例《500套》 欢迎点赞 收藏 ⭐留…

vue报错汇总

项目场景&#xff1a; 使用vue报错汇总。 1、项目启动不报错也不成功 提示&#xff1a;这里描述项目中遇到的问题&#xff1a; 项目启动时&#xff0c;一直启动不成功&#xff0c;末句提示 98% emitting Copyplugin… 原因分析&#xff1a; 最有可能是因为require或者import了…

系统设计技巧:使用Postgres作为发布/订阅和作业服务器

如果在项目中需要发布/订阅和作业服务器&#xff0c;可以尝试使用 Postgres。它将为您提供大量数据完整性和性能保证&#xff0c;并且不需要您或您的团队学习任何新技术。如果你正在做任何足够复杂的项目&#xff0c;你将需要一个 发布/订阅[1] 服务器来处理事件。本文将向你介…

黑马“兔年限定”春节礼盒准时送达,快来领!

哈咯艾瑞巴蒂&#xff0c;我是播妞前几天一个热搜引起了我的注意# 原来兔年要打384天的工 #看到这标题播妞突然头皮发紧我搜索了一下&#xff0c;原来是......农历癸卯兔年全年共有384天今年的春节是2023年1月22号2024年的春节是2月10号从今年春节到明年的春节算一年由于“闰二…