图形用户接口

图形用户接口

一、实验目的:

1、了解嵌入式系统图形界面的基本编程方法;

2、学习图形库的制作。

二 、 实验原理:

1Frame Buffer

      显示屏的整个显示区域,在系统内会有一段存储空间与之对应。通过改变存储空间的内容达到改变现实信息的目的。改存储空间被称为Frame Buffer,或显存。显示屏上的每一个点都与Frame Buffer里的某一个位置对应。     Frame Buffer的读写直接对显存进行操作。用户可以将Frame Buffer看成是显示内存的一个映像,将其映射到进程地址空间之后,就可以直接进行读写操作,而写操作可以立即反应在屏幕上。这种操作是抽象的,统一的。用户不必关心物理显存的位置、换页机制等等具体细节,这些都是由Frame Buffer设备驱动来完成的。但FrameBuffer本身不具备任何运算数据的能力,就只好比是一个暂时存放水的水池。CPU将运算后的结果放到这个水池,水池再将结果流到显示器.中间不会对数据做处理。应用程序也可以直接读写这个水池的内容。

      Frame Buffer通常就是从内存空间分配所得,并且它是由连续的字节空间组成。由于屏幕的现实操作通常是从左到右逐点像素扫描,从上到下逐行扫描,直到扫描到右下角,然后再折返到左上角,而Frame Buffer里的数据则是按地址递增的顺序被提取,当Frame Buffer里的最后一个字节被提取后,在返回到Frame Buffer的首地址,所以屏幕同一行上相邻的两像素被映射到Frame Buffer里是连续的,某一行的最末像素与它下一行的首像素反映在Frame Buffer里也是连续的,并且屏幕最左上角的像素对应的Frame Buffer的第一单元空间,最右下角的像素对应Frame Buffer的最后一个单元空间。

2、Frame Buffer与色彩

     计算机反映自然界的颜色是通过RGB(Red-Green-Blue)值来表示的。如果在屏幕某一点显示某种颜色,则必须给出相应的RGB值。Frame Buffer为屏幕提供的内容就必须能够从Frame Buffer里得到每一个像素的RGB值。像素的RGB值可以从Frame Buffer里得到,或是从调色板间接得到。

   RGB的格式:

    RGB32 ----使用32位来表示一个像素,RGB分量各用去8位,剩下的8位用作Alpha通道或者不用;

    RGB24 ----使用24位来表示一个像素,RGB分量都用8位表示,取值范围为0~255;

    RGB555----是16位的格式,分量都用5位表示,剩下的1位不用:

              X R R R R R G G G G G B B B BB 

    RGB565----是16位的另一个格式,5位用于R,6位用于G,5位用于B:

              R R R R R G G G G G G B B B B B

本实验系统使用的是16位TFTLCD,像素分辨率为640×480,选用的是RGB565格式。

3、LCD控制器

     在Frame Buffer与显示屏之间还需要一个中间件,该中间件负责从Frame Buffer里提取数据进行处理,并传输到显示屏上。

     PXA270处理器内部集成LCD控制器,提供了一个从PXA270出来器到Passive或Active显示屏的接口。LCDC的作用是将Frame Buffer里的数据传输到LCDC内部,然后经过处理,输出数据到LCD的输入引脚上。

三、 实验内容:

1、图形库的制作:编写点、线、圆、星、矩形、菱形、正三角形等函数,编译生成xx.o文件,形成静态链接库libxc.a,(同时尝试形成动态链接库libxc.so)供应用程序的调用;

2、编写一个应用程序(界面游戏),调用图形库,调试运行;

3、理解Frame Buffer的字符显示原理,将字符显示功能加到应用程序中;

4、理解Frame Buffer显示bmp格式图像的原理,编写程序,使Frame Buffer能够显示图像。

四、   实验步骤:

(一)  图形库的制作:

  对于本实验中,仅编写点、线、圆、星、矩形、菱形、正三角形等程序,制作的图形库最重要的便是函数的调用和画图的算法。画点是其他图形的基础,其他的图形都调用到画点的程序;对于算法,一个好的算法,能够提高代码的执行效率,更重要的是能够使画出的图形更美观。

1.    画点draw_point:

void draw_point(int x,int y,int color)
{
          long offset = 0;
        offset=(y*vinfo.xres+x)*vinfo.bits_per_pixel/8;
         *(unsignedchar *)(fbp + offset + 0) = color & 0xff;       
         *(unsignedchar *)(fbp + offset + 1) = (color>>8)&0xff;
}               

其中的3个参数分别为点的x、y坐标值和点的颜色。

1.    画线 draw_line:(程序见附录)

给出任意两点绘制出的直线,采用的是直线斜率表达式的方式,在逐个取点,调用draw_point进行绘制。此方法需注意的问题:

a.对于斜率不存在的竖直线的绘制;

b.逐个取点的方式有两种,将x的值逐个增加,通过表达式计算出其y的值,表达式是算出的y是取整后的值,或者是将y的值逐个增加,计算出取整后x的值。开始仅采用第一种方式,所带来的问题是,当所要绘制的直线斜率增大时,可以明显地看出直线只是断断续续的,效果不理想。因此将上述两种方法同时使用,即对直线进行两次描点,但两种方式得到的点并不完全重复,使得直线连续;

3. 画圆 draw_circle:

  画圆采用的是极坐标的方式,调用数学库中的sin、cos函数,将坐标进行转化。

j=0.5*pi/r;
for(i=0;i<=2*r;i++)
  {
    x=r*cos(j*i)+x0;
    y=r*sin(j*i);
    draw_point(x,y+y0,color);
    draw_point(x,abs(y-y0),color);
  }

同样采用的是逐点扫描的方法,在平行与x轴的直径上,需扫描2*r个点,而半圆的角度变化为pi,因此定义j=0.5*pi/r,即每次增加的角度。调用画点的函数,确定一个x值后,可以画出上下两个点。

       这中算法的问题在于,当圆的半径很小时,圆上所能够取到的整数点的个数也将变少,导致画出的圆也是断断续续的。

 4. 画星 draw_star:

     画星形的程序是建立在画圆的基础上的,在一个圆上取出5个点,每相邻的两个点所对应的弧度为0.4*pi,再调用画线的函数进行连接。

5. 画正三角形 draw_triangle:

     类似与画星形的画法,在圆上取3个点,每相邻的两个点所对应的弧度为2.0*pi/3,调用画线的函数将相邻的两个点连接,形成正三角形。

 6. 画矩形 draw_rectangle:

     通过给出的矩形的左上顶点的坐标值及其长和宽,确定出其他3个顶点的位置,调用画线函数连接。

 7. 画菱形 draw_rhombus:

     通过给出的菱形的两对角线的交点,以及交点距上顶点和左顶点的距离,确定出菱形4个顶点的坐标值,调用画线函数连接。

 

以上即7个图形的函数,最主要的是画点画线画圆的函数,其他的图形函数建立在点线圆的函数上,同时也可以拓展出其他的图形,如平行四边形、梯形、椭圆等。

将写好的画图程序分别编译:

$ arm-linux-gcc -c draw_point.c

$ arm-linux-gcc -c draw_circle.c

$ arm-linux-gcc -c draw_line.c

$ arm-linux-gcc -c draw_rhombus.c

$ arm-linux-gcc -c draw_triangle.c

$ arm-linux-gcc -c draw_rectangle.c

$ arm-linux-gcc -c draw_star.c

通过上述编译将生成:draw_point.o 、draw_line.o 、draw_circle.o、draw_star.o、 draw_rectangle.o 、draw_rhombus.o 、draw_triangle.o

共7个xx.o文件。

(1) 生成静态库libxc.a:

 $ ar cq libxc.adraw_point.o draw_line.o draw_circle.o draw_star.o draw_rectangle.o  draw_rhombus.o  draw_triangle.o

(2) 生成动态库libxc.so(此时不需要先生成xx.o的文件):

$ arm-linux-gcc -o libxc.so-shared draw_circle.c draw_point.c draw_triangle.c draw_line.c  draw_rectangle.c  draw_rhombus.c  draw_star.c

 

思考:静态库和动态库的区别:

静态库的名字一般是libxxx.a; 利用静态库编译成的文件比较大,因为整个函数库的所有数据都会被整合进目标代码中,编译后的执行程序不需要外部的函数库支持。当然这也是静态库的缺点,因为如果静态库改变了,那么应用程序必须重新编译。

动态库的名字一般是libxxx.so; 相对于静态函数库,动态函数库在编译的时候并没有被编译进目标代码中,应用程序执行到相关函数时才调用该函数库里的相应函数,因此动态函数库所产生的可执行文件比较小。由于函数库没有被整合进应用程序,而是程序运行时动态的申请并调用,所以程序的运行环境中必须提供相应的库。动态函数库的改变并不影响应用程序,所以动态函数库的升级比较方便。 

由于本实验形成的图形库的很小,里面的供调用的函数也有限,更多的是采用简单易行的静态库连接方式。

(一)   主程序的开发

总体介绍:该程序是一个互动的小游戏,界面的设计是调用生成的图形库。游戏界面的底部分别为星形、圆、三角、菱形、矩形,并分别对应键盘上的1~5数字;游戏开始时,首先选择的是游戏的等级(1~4个等级),等级越高,其图形下落的速度越快;游戏一共5个回合,每个回合对应产生一个图形的掉落,在图形掉到底部前,正确输入其对应列的数字即进入下一个回合;当图形掉落到底部时,游戏结束,调用字符显示“Game Over!”,并在下一行显示总共赢得回合数,当5个回合全部结束时,游戏结束,并调用字符显示“Excellent!”。

游戏结果如下图所示:



程序(game.c)的设计及问题:

(1) 在应用程序中,操作/dev/fb0的一般步骤如下:

a.打开/dev/fb0设备文件。

b.用 ioctl 操作取得当前显示屏幕的参数,如屏幕分辨率,每个像素点的比特数。根据屏幕参数可计算屏幕缓冲区的大小。

c.将屏幕缓冲区映射到用户空间(mmap)。

d.映射后就可以直接读写屏幕缓冲区,进行绘图和字符的显示了。

主要的程序片段:

long screensize=0;
fp = open("/dev/fb0", O_RDWR);
ioctl(fp, FBIOGET_VSCREENINFO, &vinfo);
screensize = vinfo.xres * vinfo.yres * vinfo.bits_per_pixel / 8;
fbp=(char*)mmap(0,screensize,PROT_READ|PROT_WRITE,MAP_SHARED, fp, 0);

实验中关于fb0设备的探究:fb0设备只是Frame Buffer设备中的一个,如果系统有多个显示卡,可支持多个Frame Buffer设备,最多可达 32个,分别为/dev/fb0 到/dev/fb31,而/dev/fb 则为当前缺省的Frame Buffe设备,通常指向/dev/fb0。本实验系统中,在文件系统的构建过程时,创建的/dev目录下:

$ mknod fb0 c 29 0 ,仅只创建了fb0这一个设备文件。

 

(2) 线程的使用:

   实验需实现图形在下落的过程中同时能进行键盘的输入(主要用到是“scanf”),由于scanf是阻塞型,当使用scanf一直读取键盘输入时,将阻塞图      形的移动。解决此问题的途径是合理地使用线程。具体使用方法:

调用函数pthread_create来创建一个线程,它的原型为:

externint pthread_create __P ((pthread_t *__thread, __const pthread_attr_t *__attr, void*(*__start_routine) (void *), void *__arg));

 其中第一个参数为指向线程标识符的指针,第二个参数用来设置线程属性,第三个参数是线程运行函数的起始地址,最后一个参数是运行函数的参数。创建线程成功后,新创建的线程则运行参数三和参数四确定的函数,原来的线程则继续运行下一行代码。

主要程序片段:

void *thread_func() {
   …..
scanf("%d",&m);
}
int main()
{
  int rc=0;
  ……
  rc= pthread_create( &mythread, NULL, thread_func, NULL);
  ……
}

实验中将“scanf”放入到一个子线程中,在主程序(即主线程)中,成功创建线程后继续执行以下的图形下落的程序,而子线程中一直在扫描键盘,当符合条件时,则退出并关闭该线程,在主程序中相应地作出判断。线程的使用有效地解决了实验中两个程序同时执行的问题。

 

(3) 简单动画效果的实现:

  Frame帧:你所看到的屏幕的图像,或者在一个窗口中的图像,叫做一帧。实验中实现图形的下落的动画,如果采用整个屏幕一帧一帧地,连续图像的刷新,将会消耗很多的时间,并且此时主程序的代码也会相应地增多,执行的效率不高。考虑到在制作图形库里的各个函数时,每一个画图函数里均有一个“color”参数,实现一个区域内的图形动画就比较简单。

do{
   draw_circle(x0,y0+25*j,25,color);
   usleep(100*1000);
   draw_circle(x0,y0+25*j,25,0);  
   j=j+1; }  while(m!=k &&j!=15);

首先设定需要显示图形的颜色,如圆的颜色是蓝色,显示100ms后,在同样的位置,将该圆的颜色设置为0,即黑色,与背景的颜色相同,就可以看到刚才显示的圆消失;保持x值不变,增大y值,在其下方即可显示下一个圆。利用循环和颜色的变化实现了图形下落的动画效果,这种方式不需要重新绘制外框,使得主程序的代码简单,并且数据的计算量很小,执行的时间也较短。

 

(4) Frame Buffer字符显示:

  在LCD绘制字符,可以将每一个字符看作是一幅图像,字符的大小对应于图像的大小,字符的笔画对应于图像的内容。实现字符转化成图像简单方法是“字模提取”,把任意的字符转换为一个字节型的数组,数组元素中的每一位代表LCD上的一个像素点,当为1时,调用画点函数进行画点填充,为0时,不进行调用。实验中的字符数在8×16的面积上显示,即该字符的宽和高各为8、16个像素。动手制作字符显示时,首先下载了一个定义好的字符头文件(font_8*16.h),在该头文件中,定义了一个fontdata_8x16[4096]的数组,其中字符的排列是按照ASCII表的顺序。如字母“A”的字模是数组中的fontdata_8x16[65*16]~ fontdata_8x16[66*16-1]。
如下左图所示:


主程序中字符显示的子程序如上右图所示,其中子函数的前两个参数是显示字符左上顶点的坐标,第三个参数是要显示的字符。ASCII表中的字符的字模的起始点在数组中对应的下标为该字符的ASCII的值乘以16;然后,从上到下,从左到右每个像素进行扫描,当list&1<<(7-j)=1时,调用画点函数画出,否则,不进行调用描点。当要显示一个词或一个句子的时,首先将该词或句子定义成字符串数组,通过变换字符左上角的坐标改变整个字符显示的位置,在用循环语句重复调用字符显示的子程序即可。如显示“Game Over”:

int main{
  …..
char str1[]="Game Over!";
 …..
for(i=0;i<strlen(str1);i++)
  { show_char(480+10*i,450,str1[i],color); }  
}

(5) 程序的编译调试:

  编译生成show_char.o文件,并重新创建静态库libxc.a.

$  arm-linux-gcc –o game  game.c  -lxc  -lm  -lpthread  -L ./

说明:-lxc  –lm  -lpthread 分别是连接自己制作的静态图形库、数学库和线程库;图形库libxc.a存放在当前目录下。

 将生成的game可执行文件拷贝到/exp目录下,在目标机上执行;

 $ ./game

 提示错误:error while loading sharedlibraries: libpthread.so.0: cannot open shary

 分析:回顾文件系统的创建,在/lib目录下仅有ld-2.3.2.so、libc-2.3.2.so、libm-2.3.2.so共3个动态链接库。当执行game时,需要动态链接库libpthread.so.0的支持,但目标机上的链接库中没有,导致了上述的错 误。

解决方法: 将交叉编译器链接库中的libpthread-0.10.so拷贝到/lib下,并作软链接ln -s libpthread-0.10.so libpthread.so.0。重新执行game时,无错误发生,正常执行。在以后的开发中,当所写的程序中使用到的链接库需要提前放入到/lib下,这样才能确保应用程序在目标机上能够执行。但基于目标机的存储空间有限,不采取将所有的链接库放入/lib下。

(三)  Frame Buffer 显示bmp图像

1. bmp图像文件格式简介:

bmp是一种与硬件设备无关的图像文件格式,它采用位映射存储格式,除了图像深度可选以外,不采用其他任何压缩,因此,bmp文件所占用的空间很大。bmp文件的图像深度可选1bit、4bit、8bit及24bit。bmp文件存储数据时,图像的扫描方式是按从左到右、从下到上的顺序。典型的bmp图像文件至少有3个部分组成:

(1)位图文件数据结构,包含bmp图像文件的类型、显示内容等信息,其结构体定义如下:
typedef struct
{ char cfType[2];         /* 文件类型*/
char cfSize[4];         /* 文件的大小(字节) */
char cfReserved[4];     /* 保留, 必须为 0 */
char cfoffBits[4];      /* 位图阵列相对于文件头的偏移量(字节) */
} BITMAPFILEHEADER;       /* 文件头结构 */

 (2)  位图信息数据结构,包含有bmp图像的宽、高、压缩方法,以及定义颜色等信息,结构体如下:

typedef struct
{ char ciSize[4];         /* 说明BITMAPINFOHEADER 结构所需的字节数*/
char ciWidth[4];        /* 位图宽度(像素) */
char ciHeight[4];       /* 位图高度(像素) */
char ciPlanes[2];       /* 目标设备的位平面数, 必须置为1 */
char ciBitCount[2];     /* 每个像素的位数*/
char ciCompress[4];     /* 位图阵列的压缩方法*/
char ciSizeImage[4];    /* 图像大小(字节) */
char ciXPelsPerMeter[4];/* 目标设备水平每米像素个数 */
char ciYPelsPerMeter[4];/* 目标设备垂直每米像素个数 */
char ciClrUsed[4];      /* 位图实际使用的颜色表的颜色数 */
char ciClrImportant[4];  /* 重要颜色索引的个数 */
} BITMAPINFOHEADER;       /* 位图信息头结构 */

(3)  调色板,这个部分是可选的,有些位图需要调色板,有些位图,比如真彩色图(24位的bmp)就不需要调色板;

(4)  位图数据。这部分的内容根据bmp位图使用的位数不同而不同,在24位图中直接使用RGB,而其他的小于24位的使用调色板中颜色索引值,结构体如下:

typedef struct
{ char rgbBlue;
char rgbGreen;
char rgbRed;
char rgbReserved;
} RGBQUAD;        /* 颜色模式 */

2.  bmp图片数据的获取

使用fopen()函数打开位图文件,使用fread()读取位图头文件,并判断位图

的类型,接着读取位图信息头。利用memcpy()函数将InfoHead.ciWidth、

InfoHead.ciHeight、FileHead.cfoffBits转成整形。获取每一个像素点的颜色

后调用画点函数draw_point()进行图像每一个点的显示。

图片数据的获取:

memcpy( &ciWidth, InfoHead.ciWidth, 4 );
memcpy( &ciHeight, InfoHead.ciHeight, 4 );
memcpy( &cfoffBits, FileHead.cfoffBits, 4 );
fseek(fp, cfoffBits, SEEK_SET);         /*从文件开始偏移bfoffbits个字节的位置*/
int i,j;
for(j=y0+ciHeight;j>y0;j--)
 {
  for(i=x0;i<x0+ciWidth;i++)
   {
   fread( (char *)&rgbquad, 1, sizeof(RGBQUAD), fp );
color=((rgbquad.rgbRed>>3)<<11)|(((rgbquad.rgbGreen>>2)<<5)&0x07e0)| ((rgbquad.rgbBlue>>3)&0x1f);      
   draw_point(i,j,color);
    }
  }

问题:

(1)       实验中采用的是24位bmp格式的图片,因此需要将RGB24位压缩成

16位后显示。若采用书中的做法:

color=(rgbquad.rgbRed<<11)|((rgbquad.rgbGreen<<5)&0x07e0)|(rgbquad.rgbBlue&0x1f);

显示图片的颜色有很严重的失真。压缩时,应先将Red向右移动3位,舍弃低位的3位,同理,将Green、Blue分别舍弃低位的2位和3位,从而实现数据的压缩。编译执行后,再观察显示图片,与原图颜色符合。

(2)       对于第一个循环,刚开始时是:  for(j=y0;j<y0+ciHeight;j++)

但发现此时显示的图片是倒置的,改用for(j=y0+ciHeight;j>y0;j--) 后图片显示正常。因为在每次循环时,首先获取的图片最后一行的数据,这是由于bmp文件存储数据时,图像的扫描方式是按从左到右、从下到上的顺序,这一点需要特别关注。

1.      程序的编译调试:

 编译生成show_bmp.o文件,并重新生成静态链接库:

$ ar cq libxc.adraw_point.o draw_line.o draw_circle.o draw_star.o draw_rectangle.o  draw_rhombus.o  draw_triangle.o  show_char.o show_bmp.o

$  arm-linux-gcc –o game  game.c  -lxc  -lm  -lpthread  -L ./

执行后显示效果:

游戏结束后,当显示字符“Excellent”时,调用图片显示程序在其上方显示一张笑脸图;当显示字符“Game Over”时,在其上方显示一张哭脸图。

五、实验总结:

    本次实验花费的时间较长,主要内容有3个方面:图形库制作、字符显示、图片显示。其中在图形库的制作上花的精力较多,掌握并理解静态库的生成方式,并能够对静态库的调用。Frame Buffer总体上是对数据的操作,如何获取简单图形、字符及图片的数据显得很重要,再者便是程序中算法的运用上,好的算法不仅能简单地得到数据,还能显示出更清晰的图形。

     实验的过程虽然很曲折,由于自己的计算机是32位,打开Frame Buffer设备后,显示的图片的颜色与实验室目标机上的不一样,程序在目标机上运行时需要重新调整,也花费了一些时间;对于实验中遇到一些新的概念或者是自己并未接触过的知识时,通过书籍的查阅或者是网上的搜索还是能够及时了解并掌握;C语言的使用上,可能是长时间的没有使用,很多函数都已经忘记,也犯了很多低级的错误,在以后的编程开发中,相信多练习C语言还是能够熟练掌握使用的。

                                                                          链接:源程序

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值