Nano-X的详细介绍

时间:2022-04-25 04:54:06
 
 
  
  前       言

     nano- X是一个著名的开放式源码嵌入式GUI 软件,目的是把图形视窗环境引入到运行Linux 的小型设备和平台上。nano-X使用了分层设计的思想,可移植性非常好,nano-X 的图形引擎能够运行在任何支持readpixel,writepixel,drawhorzline, drawvertline 和setpalette 的系统之上,在底层函数的支持之下,nano-X 支持新的Linux内核帧缓存结构,并基于framebuffer来实现图像的绘制。nanox应用程序运行时占用的资源较少,server只有100多K,精巧的设计并不代表功能的简陋,目前提供每像素1、2、4、8、16、24和32位的支持,另外还支持彩色显示和灰度显示. 目前支持的图形文件包括WINDOWS的GIF,JPEG,BMP、PNG、XPM和PBM、PGM、XPM格式。本文档主要介绍了nano-X的体系结构,分析了nano-X 的图形引擎,然后揭示nano-X 图形解析和显示的过程和原理。

             第1章  nano-X图形显示框架
1.1  nano-X 体系结构
     
     nano-X 采用了分层结构设计方法,其层次结构如下图所示。同时, 这里也列出 nano-X  源代码目录树下的主要目录结构,以便于对照参考。
                                             
nano-X
             |--bin                                
             |--Configs                    
             |--demos
                    |--drivers
             |--engine
   |--fonts
             |--include
   |--lib
   |--mwin
             |--nanox

图1   nano-X 体系结构
Nano-X 提 供 两 种 类 型 的 API , 一 种是与win32/WinCE GDI 相兼容的 API 接口,另一种是与 Linux 上著名的 X Window 相兼容的 API。实现API 的程序放置在 mwin 目录下,实现 Nano-X API 的程序放置在 nanox 目录下。
    API层之下是图形引擎层,该层主要实现各种图形绘制, 各种图像格式处理,字体处理等功能。该层的程序主要放置在 engine 和 fonts 目录下。
    在最下层是设备驱动层,该层为各类设备提供驱动程序,目前包括显示/触摸屏,鼠标,键盘等设备的驱动程序该层的程序都放置在 drivers 目录下。
1.2  引擎层
1.2.1  引擎层剖析   
    nano-X 的中间层是图形引擎层(也称为设备与平台无关层),因为所有的绘图函数都通过调用屏幕驱动来实现,所以该层与硬件无关。
    图形引擎层在nano-X中起着承上启下的作用,既对下层设备驱动的功能实行封装,又为上层应用编程接口 API 提供服务,。系统核心图形绘制功能都是在该层实现的 由于我们这次我们主要讲解的是关于图像处理方面的知识,所以其他不相关的我们暂且不再涉及。以下我们从,图形上下文,绘图区域,裁剪四个方面介绍图形引擎层关于图像绘制的部分功能与实现,并先列举 engine 目录下的关于图像处理主要文件及其完成的功能以供参考:
Devdraw.c:主要的图形绘制函数,包括调色板操作,背景绘制,画线,画圆,多边型与填充,位操作及图形的放大和缩小等。
Devclip.c : 剪裁操作。
Devrgn.c :实现图形的布尔操作。布尔操作就是通过对两个以上的物体进行并集、差集、        
          交集的运算,从而得到新的物体形态。
Devpalx.c :处理 1,2,4,8 位调色板的映射。
Devpalgray4.c: 处理 4 级灰度映射。
Devimage.c :处理对 GIP,BMP,JPEG,PPM,PNG,TIFF 等图形格式的显示操作。
   
   图形引擎层中的函数在逻辑屏幕上进行绘制工作,这些绘制有可能是在物理的屏幕内,也有可能是在物理的屏幕外。这些绘制函数的第一个参数是 SCREENDEVICE 结构类型的指针psd,他在 device.h 中定义,描述了显示设备的底层特性,是显示设备驱动程序的接口,。SCREENDEVICE 结构中包含了显示设备的物理特性,比如横向和纵向的大小,显示模式,色彩,显示内存的大小等属性,此外该结构中也包含了各种和屏幕设备相关的操作的函数指针。屏幕的坐标类型在 COORD 结构体中定义,在系统中使用左上角作为系统的坐标原点。

1.2.1.1 图形上下文
    我理解的图形上下文:在日常生活中我们来作画,如果我们刚画了素描,接下来还要画素描我们只需重新拿张宣纸就行,不用换笔和调色板和作画的方式,如果我们刚才画素描,现在要画彩色画,我们就要不仅是重新拿张纸了,我们的笔,我们的调色板,我们作画的方式,甚至我们的宣纸的大小也要改变,说白了,图形上下文,就是我们作画要用的工具和作画所需要的场景,nano-X 作画也是这个道理。
    Nano-X 中使用图形上下文描述绘图属性,下面我们列车他的数据结构,以帮助我们直观的走进和认识图形上下文。
typedef struct {
        GR_GC_ID gcid;         /*图形上下文ID */
        int mode;         /*绘画模式*/
        GR_REGION_ID region;       /*绘画区域*/
           int xoff;         /*x偏移量(以绘画区域的左上角为参照)*/
        int yoff;         /*y偏移量*/
        GR_FONT_ID font;       /*字体大小设置*/
        GR_COLOR foreground;       /*前景色RGB值或像素值*/
        GR_COLOR background;       /* 背景色RGB值或像素值 */
        GR_BOOL fgispixelval;      /*如果前景色为像素值则定义为TRUE */
        GR_BOOL bgispixelval;  /* 如果背景色为像素值则定义为TRUE */
        GR_BOOL usebackground;     /*位图作为背景 */
        GR_BOOL exposure;      /* 向 GrCopyArea 发出显示事件*/
  } GR_GC_INFO;
   要在屏幕上绘图,需要先调用GrNewGC()函数分配一个图形上下文数据结构,然后返回数据结构的 ID 值。图形上下文实际上标识了一个 GR_GC_INFO 结构体,如下所示,这个结构体包含了前景,背景,字体,绘图模式和绘图区域等信息,设置图形上下文时使用函数进行操作,而不是对结构体直接操作。  
1.2.1.2 绘图区域
    矩形区域被用来描述屏幕上点的集合。简单的情况下,矩形区域可以使用一个长方形来描述,但是在复杂情况下可能要使用复杂的数据结构描述。nano-X 中绘图区域使用互不重叠的矩形区域表示,因此对于一个不规则的凸边形要使用多个不重叠的矩形拼和。
   nano-X 通过使用DYNAMICREGIONS 宏定义控制动态分配还是静态分配。通过 GdAllocRegion 函数对事先赋值为空的(MWCLIPREGION *)clipregion 分配内存空间。为了保证切割的唯一性,每个矩形都在 y 方向上尽量延伸。这种动态分配的方案中,各个矩形的排序按照先纵向后横向的方式排列,也就是区域里面的矩形先按照它们的纵坐标 y 的大小排列,对于相同纵坐标的矩形再按照横坐标的大小排列。为了方便对区域进行操作,排列的时候,区域中所有左上角 y 坐标和右下角 y 坐标相同的矩形组成一组,也就是说同在一组的矩形宽度不相同,高度是一样的,并且上沿是水平的。这样做的好处是在以后对区域操作时(比如矩形区域合并),只需要对不同矩形的宽度进行对比即可。

1.2.1.3裁剪
  
   在nano-X 中图形引擎都有一个绘图区域,这个区域是由一系列的矩形组成的,所有对图形的操作都是在这个区域进行。系统中实现裁剪有两种方法,都使用了GdSetClipRects()函数将裁剪区域传入图形引擎,  此后所有的绘图函数都要先判断绘图区域是否能被绘制,然后才进行绘制。
    比如在图像显示和解码以前我们都会看到这样一段程序,这段代码有什么作用呢?
     GdClipArea(psd, x, y, x + width - 1, y + height - 1);

     if(clip == CLIP_INVISIBLE)

                return;
   系统中实现裁剪有两种方法,都使用了GdSetClipRects()函数将裁剪区域传入图形引擎,   此后所有的绘图函数都要先判断绘图区域是否能被绘制,然后才进行绘制。进行判断的两个函数是GdClipPoint()和 GdClipArea()。GdClipPoint()函数判断点是否在一个裁剪区域里面,   它的输入是点的屏幕坐标, 返回值如果是真, 那么点就在绘制区域里面,这个函数是其他判定的基础,比如在绘制直线的时候,先使用这个函数判断直线哪部分在绘制区域里面,找到直线在绘制区域里面的起点和终点后才开始绘制;GdClipArea() 函 数 的 输 入 是 一 个 区 域 的 左 上 角 和 右 下 角 , 如 果 输 出 是CLIP_VISIBLE,表明输入区域全部在绘图区域内,如果输出是 CLIP_PARTIAL,表明输入区域有一部分在绘图区域内。

1.3底层驱动
    人机交互系统必然都包含输入/输出设备,nano-X 中控制的设备包括显示器/屏输出设备和鼠标/键盘/触摸屏等输入设备。设备驱动程序的接口函数在 device.h 中定义,至少包含一个屏幕驱动程序,一个鼠标驱动程序和一个键盘驱动程序。图形引擎层能够直接调用这些驱动程序进行相应的硬件设备操作, 从而实现设备无关性,  使用这样的层次结构能保证添加其他的硬件设备时不影响整个系统的正常工作。

1.3.1屏幕驱动
      nano-X 屏幕驱动是基于Linux内核中framebuffer,这要求在编译内核的时候选择支持framebuffer编译参数选项。它是通过fd=open(env="/dev/fb0")打开,用SCREENDEVICE的指针PSD指向这片显存,然后对这片显存根据屏幕的不同位色设置情况为中间引擎层提供相应的图形操作支持,包括画点线、图片显示、屏幕拷贝以及中西文字的显示等等。由于nano-X 自身的特性他被被设计成能够极其容易的向nano-X 中移植新硬件,因此必须提供一些访问硬件的入口点,同时提供其他的函数保证只使用小部分的核心函数集就可以完成此任务。例如:屏幕驱动程序必须实现ReadPixel,DrawPixel,DrawHorzline和DrawVertLine。这些函数在显存中读取或写入一个像素,或画一条水平或垂直的线。位图传送允许nano-X执行屏幕外的作图。nano-X允许任何能在屏幕上进行的图形操作在屏幕外进行,然后再将图形传送到物理显存。下面我们就介绍显示设备驱动使用的典型数据结构,参考该结构可以帮助了解显示设备的基本功能。

显示驱动接口数据结构:
typedef struct _mwscreendevice {
        MWCOORD xres;       /* X screen res (real) */
        MWCOORD yres;       /* Y screen res (real) */
        MWCOORD xvirtres; /* X drawing res (will be flipped in portrait mode) */
        MWCOORD yvirtres; /* Y drawing res (will be flipped in portrait mode) */
        int planes;     /* # planes*/
        int bpp;        /* # bpp*/
         int linelen;    /* line length in bytes for bpp 1,2,4,8*/
                           /* line length in pixels for bpp 16, 24, 32*/
        int size;       /* size of memory allocated*/
        long    ncolors;    /* # screen colors*/
        int pixtype;    /* format of pixel value*/
        int flags;      /* device flags*/
        void * addr;        /* address of memory allocated (memdc or fb)*/
        PSD (*Open)(PSD psd);
        void    (*Close)(PSD psd);
        void    (*GetScreenInfo)(PSD psd,PMWSCREENINFO psi);
        void    (*SetPalette)(PSD psd,int first,int count,MWPALENTRY *pal);       
        void    (*DrawPixel)(PSD psd,MWCOORD x,MWCOORD y,MWPIXELVAL c);
        MWPIXELVAL (*ReadPixel)(PSD psd,MWCOORD x,MWCOORD y);
        void    (*DrawHorzLine)(PSD psd,MWCOORD x1,MWCOORD x2,MWCOORD                         y,MWPIXELVAL c);
        void    (*DrawVertLine)(PSD psd,MWCOORD x,MWCOORD y1,MWCOORD y2,
                MWPIXELVAL c);
void    (*FillRect)(PSD psd,MWCOORD x1,MWCOORD y1,MWCOORD x2,MWCOORD ,y2,MWPIXELVAL c);
        PMWCOREFONT builtin_fonts;
        /* *void (*DrawText)(PSD psd,MWCOORD x,MWCOORD y,const MWUCHAR *str,
                 int count, MWPIXELVAL fg, PMWFONT pfont);***/
        void    (*Blit)(PSD destpsd,MWCOORD destx,MWCOORD desty,MWCOORD w,
                MWCOORD h,PSD srcpsd,MWCOORD srcx,MWCOORD srcy,long op);
        void    (*PreSelect)(PSD psd);
        void    (*DrawArea)(PSD psd, driver_gc_t *gc, int op);
         int (*SetIOPermissions)(PSD psd);
        PSD (*AllocateMemGC)(PSD psd);
        MWBOOL (*MapMemGC)(PSD mempsd,MWCOORD w,MWCOORD h,int planes,int                 bpp,int linelen,int size,void *addr);
        void    (*FreeMemGC)(PSD mempsd);
        void    (*StretchBlit)(PSD destpsd,MWCOORD destx,MWCOORD desty,
                     MWCOORD destw,MWCOORD desth,PSD srcpsd,MWCOORD srcx,
                     MWCOORD srcy,MWCOORD srcw,MWCOORD srch,long op);
        void    (*SetPortrait)(PSD psd,int portraitmode);
        int portrait;    /* screen portrait mode*/
        PSUBDRIVER orgsubdriver; /* original subdriver for portrait modes*/
        void    (*StretchBlitEx) (PSD dstpsd, PSD srcpsd,
                MWCOORD dest_x_start, MWCOORD dest_y_start,
                    MWCOORD width, MWCOORD height,
                    int x_denominator, int y_denominator,
          int src_x_fraction, int src_y_fraction,
          int x_step_fraction, int y_step_fraction,
          long op);
} SCREENDEVICE;
xres :表示屏幕的宽 (以像素为单位);
yres :表示屏幕的高 (以像素为单位);
planes :当处于平面显示模式时,planes 用于记录所使用的平面数,如平面模式相对的时  
    线性模式,此时该变量没有意义。通常将其置为0。
bpp :表示每个像素所使用的比特数,可以为1,2,4,8,15,16,24,32。
linelen :对与1,2,4,8比特每像素模式,它表示一行像素使用的字节数,对于大于8比
    特每像素模式,它表示一行的像素总数。
size :表示该显示模式下该设备使用的内存数。linelen 和 size 的存在主要是为了方便为内
    存屏幕设备分配内存。
gr_foreground 和 gr_background :表示该内存屏幕的前景颜色和背景颜色,主要被一些  
    GDI 函数使用。
gr_mode :说明如何将像素画到屏幕上,可选值为:MODE_SET MODE_XOR MODE_OR   
    MODE_AND MODE_MAX,比较常用的是MODE_SET和MODE_XOR
flags :该屏幕设备的一些选项,比较重要的是 PSF_MEMORY 标志,表示该屏幕设备代
    表物理屏幕设备还是一个内存屏幕设备。
addr :每个屏幕设备都有一块内存空间用来作为存储像素。addr 变量记录了这个空间的起  
    始地址。
下面的函数是屏幕驱动的接口函数,我将会在下面驱动接口中解析他们,在这里就不再介绍。

1.3.2驱动接口
    在上面的文章中我们看到这样一句话:显示驱动是整个系统中最为复杂的部分,但是驱动程序的复杂性却换来了移植的方便性,nano-X 是怎么让复杂的显示驱动来方便移动呢?就是因为系统的显示驱动程序里只有几个接口和硬件设备直接交互,其他程序提供核心的绘图操作比如读取像素,绘制像素,绘制更复杂的图形等,这些绘图操作对系统映射的显存进行操作,读写像素点的信息。举个通俗的例子,现在的轿车和以前的轿车在和刚开始发明的时候的驾驶方式几乎没有什么改变,都是离合,油门,方向盘,这个固定模式就好比我们的接口,他就是这样,不管你的车性能多好,多高级,都是内部的事,驾驶方式都是一样的。来让我们来看看nano-X的离合,油门,方向盘。在nano-X 里们不论是画多少位图像的驱动我们都有统一的接口,都有统一的功能函数。下们就是nano-X驱动的接口,如果有一天我们中的那位发明一个nano-X没有的图像格式,如果还要用nano-X的话,那么我们就要自己来写驱动了,不过不要头大,我们的驱动中只要包括下面接口中的函数就可以了,这就是nano-X的马克思理论性,永远没有尽头,永远可以延伸和扩展。
typedef struct {
    int  (*Init)(PSD psd);
    void     (*DrawPixel)(PSD psd, int x, int y, gfx_pixel c);
    gfx_pixel (*ReadPixel)(PSD psd, int x, int y);
    void    (*DrawHLine)(PSD psd, int x, int y, int w, gfx_pixel c);
    void    (*PutHLine) (GAL gal, int x, int y, int w, void* buf);
    void    (*GetHLine) (GAL gal, int x, int y, int w, void* buf);
    void    (*DrawVLine)(PSD psd, int x, int y, int w, gfx_pixel c);
    void    (*PutVLine) (GAL gal, int x, int y, int w, void* buf);
    void    (*GetVLine) (GAL gal, int x, int y, int w, void* buf);
    void     (*Blit)(PSD dstpsd, int dstx, int dsty, int w, int h, PSD srcpsd, int srcx, int srcy);
    void    (*PutBox)( GAL gal, int x, int y, int w, int h, void* buf );
    void    (*GetBox)( GAL gal, int x, int y, int w, int h, void* buf );
    void    (*PutBoxMask)( GAL gal, int x, int y, int w, int h, void *buf);
    void    (*CopyBox)(PSD psd,int x1, int y1, int w, int h, int x2, int y2);
} SUBDRIVER, *PSUBDRIVER;

下面介绍各接口函数:
DrawPixel(PSD psd, int x, int y, gfx_pixel c):
功能:在给定的地址上画一个像素点。
参数介绍:psd在这里我们对psd的应用就是psd的基地址,x,y就是该点相对于基址的偏移量他的算法是这样的。addr = x * pitch + y ,addr就是该点的所要画的位置。gfx_pixelc就是传递过来的像素值,该函数解析后画到addr这个位置。绚烂多彩的图像就是靠勤劳的microwindows这样一点一点画出来了。
ReadPixel(PSD psd, int x, int y):
功能:在给定的地址上读出一个像素值。
参数介绍:psd在该函数中的用处还是他所指向的显存的基地址,x,y就是该点相对于基址的偏移量,他的算法和上面的方式是相同的,然后就是该函数把该点像素解析后返回该像素的RGB值。
DrawHorzLine(PSD psd,MWCOORD x1,MWCOORD x2,MWCOORD y,
MWPIXELVAL c):
功能:在两点之间画横线。
参数介绍:psd的作用还是向程序提供基地址,虽然该函数有三个坐标数据,其实他代表的是两个坐标点,(x1,y),(x2,y).然后在y的位置从x1向x2从左到右画线。c就是画线时的填充像素了。

DrawVertLine(PSD psd,MWCOORD x,MWCOORD y1,MWCOORD y2,
MWPIXELVAL c):
功能:在两点之间画竖线。
参数介绍:psd作用还是提供绘图的基地址。x,y1,y2的意思是在(x,y1)(x,y2)两点见画竖线。
c的作用还是画线时的像素填充值。提醒:不论是画竖线还是横线,我们的microwindows都没有偷懒,把最后一点也画了,也就是我们的线是有头有尾是完整的。
FillRect(PSD psd,MWCOORD x1,MWCOORD y1,MWCOORD x2,MWCOORD y2,
MWPIXELVAL c):
功能:填充一个矩形,也就是画一个实心的矩形。
参数介绍:聪明的我们也许看了上两个这个就不用解释了把,是的和你认为的一样,就是在x1,y1,x2,y2的坐标范围内用c的像素值来填充。
PutHLine,GetHLine,PutVLine,GetVLine,PutBox,GetBox,PutBoxMask
Get* 函数用于从屏幕拷贝像素到一块内存区,而Put*函数用于将存放于内存区的像素画到屏幕上。PutBoxMask 与PutBox的唯一区别是要画的像素如果是白色,就不会被画到屏幕上,从而达到一种透明的效果。
Blit,CopyBox
Blit 用于在不同的屏幕设备(物理的或者内存的)之间拷贝一块像素点,CopyBox则用于在同一屏幕上实现区域像素的拷贝。如果使用的是线性模式,Blit的实现非常简单,直接memcpy 就可以了,而CopyBox 为了防止覆盖问题,必须根据不同的情况,采用不同的拷贝方式,比如从底到顶底拷贝,当新老位置在同一水平位置并且重复时,则需要利用缓冲间接拷贝。如果使用平面显示模式,这里就比较复杂了。因为内存设备总是采用线性模式的,所以就要判断是物理设备还是内存设备,再分别处理。这也大大地增加了fbvga16 实现的代码。
1.3.3屏幕驱动和驱动接口的关系
    由上面的介绍我们可以看到图形引擎中的图形驱动已经提供了基于framebuffer之上的图形驱动,这些驱动可以完成目前包括对每像素1、2、4、8、16、24和32位的支持,我们可以看到图形引擎提供的接口函数大多数和图形有关,他们主要就是通过调用图形驱动来完成任务。图形驱动程序屏蔽了底层驱动的细节,完成底层驱动相关的功能。
    我们知道图形显示有个显示模式的概念,一个像素可以用一比特表示,也可以用2.4.6.8.15.16.24.32比特表示,另外VGA16标准模式 使用平面图形模式,而VESA2.0使用的是线性图形模式。所以即使是同样基于Framebuffer 的驱动,不同的模式也要使用不同的驱动函数:画一个1比特的单色点和画一个24位的真彩点显然是不一样的。所以图形驱动程序使用了子驱动程序的概念来支持各种不同的显示模式,事实上,它们才是最终的功能函数。为了保持数据结构在层次上不至于很复杂,我们通过图形驱动程序的初始函数Open直接将子驱动程序的各功能函数赋到图形驱动程序的接口函数指针。最后我再举例说明一下,比如我们的屏幕显存驱动支持24位的图像显示模式,所以只要你的图像显示模式不是24你都要转换成24比特,因为图像的显示模式有很多种,为了便于管理和对接,我们就定义了转换驱动必须有特定数目和函数名的驱动函数,在初始化后,屏幕驱动就可以调用相应的函数来画图了,事实上他是感觉不到这些变化的,这些驱动选择的工作在server启动的初始化中就已经决定了。

1.3.3驱动选择
我说的这个内容是在microwindows/src/nanox/srvmain.c中,当我们启动服务端后microwindows的初始化也就随之开始了,通过GrOpenScreen我们来到了/microwindows/src/drivers/scr_fb.c中的fb_open内部,我们就是通过select_fb_subdriver来进行选择,刚才在fb_open屏幕驱动已经对psd进行了初始化,他的过程是这样的首先我们把framebuffer映射到我们申请的地址,然后通过FrameBuffer 中的ioctl函数(下面有详细解释),framebuffer设备提供了若干 ioctl 命令,通过这些命令,可以获得显示设备的一些固定信息(比如显示内存大小)、与显示模式相关的可变信息(比如分辨率、象素结构、每扫描线的字节宽度),以 及伪彩色模式下的调色板信息等等。然后microwindox根据ioctl函数返回的屏幕对psd进行初始化,然后根据psd->bpp的不同让driver指针指向不同的驱动地址,来选择对应的驱动,也就说驱动的选择是microwindows根据framebuffer的返回值来选择对应的驱动。就这样microwindows草船借了箭.

第2章 microwindows基于framebuffer显像原理

2.1  framebuffer简介
   Framebuffer机制模仿显卡的功能,将显卡硬件结构抽象掉,可以通过 Framebuffer的读写直接对显存进行操作。用户可以将Framebuffer看成是显示内存的一个映像,将其映射到进程地址空间之后,就可以直接进行读写操作,而写操作可以立即反应在屏幕上。FrameBuffer 设备还提供了若干 ioctl 命令,通过这些命令(上面的驱动选择中用到)可以获得显示设备的一些固定信息(比如显示内存大小)、与显示模式相关的可变信息(比如分辨率、象素结构、每扫描线的字节宽度),以及伪彩色模式下的调色板信息。

2.2  framebuffer在microwindows图形解析中的应用
    如果在nano-X使用framebuffer我们首先会在microwidnows的预编译选项中,选中framebuffer,然后运行microwidnow服务端在初始化过程中打开帧缓冲设备,过程如下:(microwindows-0.90/src/nanox/srvmain.c中的GsInitialize/GdOpenScreen/scrdev/fbopen()函数),
if(!(env = getenv("FRAMEBUFFER")))
{
        env = "/dev/fb0";
}
fb = open(env, O_RDWR);
从上面代码我们可以看到,我们首先得到framebuffer环境变量,然后我们打开了他,并返回了她的文件描述符。然后我们利用 mmap 功能将帧缓冲映射到用户进程空间形成用户能操作的显存对显存进行读写操作 。通过程序我们看到mmap把framebufer隐射到psd的地址空间,然后我们可以看到在microwindows所有的有关图像处理的函数都是在psd申请的内存内进行,,就这样microwindows通过写这片内存来实现对framebuffer的使用,而对framebuffer的任何操作马上就反映屏幕上,就这样microwindows用framebuffer来实现了所有的图像显示,就这样microwindows让我们看到了艳丽多彩的图形界面。

                  第3章 microwindows两种绘图区
4.1创建实窗口
   创建实窗口使用GrNewWindow,当然我们也可以调用GrNewInputWindow 函数
创间只输入(input-only)的窗口,这些函数可以指定窗口的边界和颜色,函数的返回值是窗口的ID,此ID可以被后来的图形上下文和窗口操作函数使用,然后调用GrMapWindow 函数显示窗口,我们也可以调用GrUnmapWindow 函数隐藏窗口。窗口的显
示的前提示需要它的所有祖先窗口可视。再后来就是绘制窗口了,绘制窗口需要窗口号和图形上下文作参数,我们已经在前面生成。再后来就是我们可以看到绚丽的图像窗口了。
4.2 虚窗口(nano-X中的offscreen)
    nano-X也支持一种从来不在屏幕上显示的窗口,即像素映射(pixmap)。
    像素映射窗口有时也称作虚窗口(offscreen),虚窗口不能在屏幕上显示出来,但是可以使用GrCopyArea函数复制到别的窗口。有时在expose events期间,CPU太忙而无法保存显示窗口的内容,普通窗口在被遮掩时又从不保存它们的内容,这时就可以使用像素映射。用GrNewPixmap函数可以生成一个像素映射。例如(/microewindows/src/nanox/srvfunc.c中的movewindow函数)我剪切了他的部分函数来进行说明。
   GrMoveWindow(GR_WINDOW_ID wid, GR_COORD x, GR_COORD y)
   {
            pixid = GrNewPixmap(wp->width, wp->height,NULL);
      //首先创建一个虚窗口
      GrCopyArea(pixid, gc, 0, 0, wp->width, wp->height,

                        GR_ROOT_WINDOW_ID, oldx, oldy, MWROP_COPY);
      //把要移动的窗口copy到许窗口上
      OffsetWindow(wp, offx, offy);

      //计算偏移量
      GrCopyArea(GR_ROOT_WINDOW_ID, gc, wp->x, wp->y, wp->width,

                        wp->height, pixid, 0, 0, MWROP_COPY);
      //把虚窗口上的图像copy到偏移后的窗口上
      DeliverUpdateMoveEventAndChildren(wp);
      //产生窗口显示消息,显示移动后的窗口和该窗口的子窗口
    }
   系统使用malloc分配的内存地址代替用mmap分配的物理framebuffer地址。在系统不使用实际物理内存地址的情况下(X或MS窗口),必须写两套函数,一套用于图形系统硬件,一套用于内存地址。另外,需要知道在两种格式之间的复制方法。位的blitting函数必须要快。查看fblin8.c和mempl4.c文件可以找到支持两种显示硬件类型的例子。我做过一个对于nano-X图形引擎的修改,就是自己在devimage.c自己定义了一套绘图函数和驱动,然后把图像画在自己申请的内存中,然后把画好后的图像用GrArea拷贝到一个正在显示的图像界面上,结果获得了成功,我想这是我对在屏幕外作图最深刻的体会把。

  第4章nano-X对图片的解码和显示

   nano-X支持多种图片格式,包括GIF,JPEG,BMP,PNG,XPM,PBM,PPM等等,那么nano-X是怎么对这些图像进行选择和解码的呢?位图是现在比较常见的图像格式,那么我们就主要说一下nano-X对位图的解码过程。
4.1位图的格式
bmp文件大体上分成四个部分:
第一部分:为位图文件头BITMAPFILEHEADER,是一个结构,其定义如下:
typedefstructtagBITMAPFILEHEADER{
WORD bfType;
DWORD bfSize;
WORD bfReserved1;
WORD bfReserved2;
DWORD bfOffBits;
} BITMAPFILEHEADER;
这个结构的长度是固定的,为14个字节(WORD为无符号16位整数,DWORD为无符号32位整数),各个域的说明如下:
bfType
指定文件类型,必须是0x424D,即字符串"BM",也就是说所有.bmp文件的头两个字节都是"BM"
bfSize
指定文件大小,包括这14个字节
bfReserved1,bfReserved2
为保留字,不用考虑
bfOffBits
为从文件头到实际的位图数据的偏移字节数,即图3中前三个部分的长度之和。
第二部分:为位图信息头BITMAPINFOHEADER,也是一个结构,其定义如下:
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; 这个结构的长度是固定的,为40个字节(WORD为无符号16位整数,DWORD无符号32位整数,LONG为32位整数),各个域的说明如下:
biSize
指定这个结构的长度,为40
biWidth
指定图象的宽度,单位是象素
biHeight
指定图象的高度,单位是象素
biPlanes
必须是1,不用考虑
biBitCount
指定表示颜色时要用到的位数,常用的值为1(黑白二色图),4(16色图),8(256色),24(真彩色图)(新的.bmp格式支持32位色,这里就不做讨论了)。
biCompression
指定位图是否压缩,有效的值为BI_RGB,BI_RLE8,BI_RLE4,BI_BITFIELDS(都是一些Windows定义好的常量)。要说明的是,Windows位图可以采用RLE4,和RLE8的压缩格式,但用的不多。我们今后所讨论的只有第一种不压缩的情况,即biCompression为 BI_RGB的情况。
biSizeImage
指定实际的位图数据占用的字节数,其实也可以从以下的公式中计算出来:
biSizeImage=biWidth'*biHeight
要注意的是:上述公式中的biWidth'必须是4的整倍数(所以不是biWidth,而是biWidth',表示大于或等于biWidth的,离4最近的整倍数。举个例子,如果biWidth=240,则biWidth'=240;如果biWidth=241,biWidth'=244)如果 biCompression为BI_RGB,则该项可能为零
biXPelsPerMeter
指定目标设备的水平分辨率,单位是每米的象素个数。
biYPelsPerMeter
指定目标设备的垂直分辨率,单位同上。
biClrUsed
指定本图象实际用到的颜色数,如果该值为零,则用到的颜色数为2的biBitCount次方。
biClrImportant
指定本图象中重要的颜色数,如果该值为零,则认为所有的颜色都是重要的。
第三部分:为调色板(Palette),当然,这里是对那些需要调色板的位图文件而言的。有些位图,如真彩色图,前面已经讲过,是不需要调色板的,BITMAPINFOHEADER后直接是位图数据。
调色板实际上是一个数组,共有biClrUsed个元素(如果该值为零,则有2的biBitCount次方个元素)。数组中每个元素的类型是一个RGBQUAD结构,占4个字节,其定义如下:
typedef struct tagRGBQUAD{
BYTE rgbBlue; //该颜色的蓝色分量
BYTE rgbGreen; //该颜色的绿色分量
BYTE rgbRed; //该颜色的红色分量
BYTE rgbReserved; //保留值
} RGBQUAD;
第四部分:就是实际的图象数据了。对于用到调色板的位图,图象数据就是该像素颜在调色板中的索引值,对于真彩色图,图象数据就是实际的R,G,B值。下面就2色,16色,256色位图和真彩色位图分别介绍。对于2色位图,用1位就可以表示该像素的颜色(一般0表示黑,1表示白),所以一个字节可以表示8个像素对于16色位图,用4位可以表示一个像素的颜色,所以一个字节可以表示2个像素。对于256色位图,一个字节刚好可以表示1个像素。对于真彩色图,三个字节才能表示1个像素。
4.2 nano-X对位图的解码
  microwidnows对位图的解码是在/microwindows-0.90/src/engine/devimage.c文件中的loadBmp函数中进行的,下面我们就详细分析这个函数,激动人心的时候终于来了。
下面这个函数就是bmp的解码函数:
1.static int
2.LoadBMP(buffer_t *src, PMWIMAGEHDR pimage)
3.{
4.        int                h, i, compression;
5.        int                headsize;
6.        MWUCHAR                *imagebits;
7.        BMPFILEHEAD        bmpf;
8.        BMPINFOHEAD        bmpi;
9.        BMPCOREHEAD        bmpc;
10.        MWUCHAR         headbuffer[INFOHEADSIZE];
11.
12.        bseek(src, 0, SEEK_SET);
13.
14.        pimage->imagebits = NULL;
15.        pimage->palette = NULL;
16.
17.        /* read BMP file header*/
18.        if (bread(src, &headbuffer, FILEHEADSIZE) != FILEHEADSIZE)
19.          return(0);
20.
21.        bmpf.bfType[0] = headbuffer[0];
22.        bmpf.bfType[1] = headbuffer[1];
23.
24.        /* Is it really a bmp file ? */

25.        if (*(WORD*)&bmpf.bfType[0] != wswap(0x4D42)) /* 'BM' */
26.                return 0;        /* not bmp image*/
#if 0
      通过18行我们知道FILEHEADSIZE=14,25行读出数组的前两个字节,验证是否是0x424D,如果是就是位图,若不是位图,退出。
#endif
27.
28.        /*bmpf.bfSize = dwswap(dwread(&headbuffer[2]));*/
29.        bmpf.bfOffBits = dwswap(dwread(&headbuffer[10]));
30.        /* Read remaining header size */
31.        if (bread(src,&headsize,sizeof(DWORD)) != sizeof(DWORD))
32.                return 0;        /* not bmp image*/
33.        headsize = dwswap(headsize);
34.
35.        /* might be windows or os/2 header */
36.
37.        if(headsize == COREHEADSIZE) {
#if 0
      如果headsize=12证明这张位图是有os/2系统产生的,OS/2是Operating System 2的缩写,意思为第二代的操作系统。在DOS于PC上的巨大成功后,以及GUI图形化界面的潮流影响下,IBM和Microsoft共同研制和推出了OS/2这一当时先进的个人电脑上的新一代操作系统由于现在已不常见,我就不在叙述。
#endif
38.
39.                /* read os/2 header */
40.                if(bread(src, &headbuffer, COREHEADSIZE-sizeof(DWORD)) !=
41.                        COREHEADSIZE-sizeof(DWORD))
42.                                return 0;        /* not bmp image*/
43.
44.                /* Get data */
45.                bmpc.bcWidth = wswap(*(WORD*)&headbuffer[0]);
46.                bmpc.bcHeight = wswap(*(WORD*)&headbuffer[2]);
47.                bmpc.bcPlanes = wswap(*(WORD*)&headbuffer[4]);
48.                bmpc.bcBitCount = wswap(*(WORD*)&headbuffer[6]);
49.                 
50.                pimage->width = (int)bmpc.bcWidth;
51.                pimage->height = (int)bmpc.bcHeight;
52.                pimage->bpp = bmpc.bcBitCount;
53.
54.                if (pimage->bpp <= 8)
55.                        pimage->palsize = 1 << pimage->bpp;
56.                else pimage->palsize = 0;
57.                compression = BI_RGB;
58.        } else {
59.                /* read windows header */
60.                if(bread(src, &headbuffer, INFOHEADSIZE-sizeof(DWORD)) !=
61.                        INFOHEADSIZE-sizeof(DWORD))
62.                                return 0;        /* not bmp image*/
63.
64.                /* Get data */
#if 0  
     把位图信息头数据读取到bmpi中。
    dswap的作用就是把little-endian格式转换成主cpu的数据存放形式,little-endian是表示计算机字节顺序的格式,所谓的字节顺序指的是长度跨越多个字节的数据的存放形式. 比如我们读书的习惯是从左到右的方式而cpu读数的方式是从右到左的方式,所以我们在用计算机处理数据时,就必须发他转换成正确的cpu读数方式。
#endif
65.                bmpi.BiWidth = dwswap(*(DWORD*)&headbuffer[0]);
66.                bmpi.BiHeight = dwswap(*(DWORD*)&headbuffer[4]);
67.                bmpi.BiPlanes = wswap(*(WORD*)&headbuffer[8]);
68.                bmpi.BiBitCount = wswap(*(WORD*)&headbuffer[10]);
69.                bmpi.BiCompression = dwswap(*(DWORD*)&headbuffer[12]);
70.                bmpi.BiSizeImage = dwswap(*(DWORD*)&headbuffer[16]);
71.                bmpi.BiXpelsPerMeter = dwswap(*(DWORD*)&headbuffer[20]);
72.                bmpi.BiYpelsPerMeter = dwswap(*(DWORD*)&headbuffer[24]);
73.                bmpi.BiClrUsed = dwswap(*(DWORD*)&headbuffer[28]);
74.                bmpi.BiClrImportant = dwswap(*(DWORD*)&headbuffer[32]);
75.
76.                pimage->width = (int)bmpi.BiWidth;
77.                pimage->height = (int)bmpi.BiHeight;
78.                pimage->bpp = bmpi.BiBitCount;
79.                pimage->palsize = (int)bmpi.BiClrUsed;
80.                if (pimage->palsize > 256)
81.                        pimage->palsize = 0;
82.                else if(pimage->palsize == 0 && pimage->bpp <= 8)
83.                        pimage->palsize = 1 << pimage->bpp;
84.                compression = bmpi.BiCompression;
85.        }
86.        pimage->compression = MWIMAGE_BGR;        /* right side up, BGR order*/
87.        pimage->planes = 1;
88.
89.        /* currently only 1, 4, 8 and 24 bpp bitmaps*/
90.        if(pimage->bpp > 8 && pimage->bpp != 24) {
91.                EPRINTF("LoadBMP: image bpp not 1, 4, 8 or 24/n");
92.                return 2;        /* image loading error*/
93.        }
94.
95.        /* compute byte line size and bytes per pixel*/
96.        ComputePitch(pimage->bpp, pimage->width, &pimage->pitch,
97.                &pimage->bytesperpixel);
#if 0
     计算行距pitch:如果你的应用程序要写视频RAM,内存中的位图并不需要占据连续的内存块。在这种情况下,一条线的width和pitch含义是不同的。width是指内 存中位图的一条线的开始和结束位置的内存地址之差。这个距离只代表了内存中位图的宽度,它不包括位图中到达下一条线开始位置所需要的任何额外的内存。 pitch是指内存中位图的一条线到下一条线开始位置的内存地址之差。
#endif
98.
99.        /* Allocate image */
100.        if( (pimage->imagebits = malloc(pimage->pitch*pimage->height)) == NULL)
101.                goto err;
      //为位图分配内存,注意:不是pimage->height *pimage ->height;
102.        if( (pimage->palette = malloc(256*sizeof(MWPALENTRY))) == NULL)
103.                goto err;
104.      
105.     /* get colormap*/
      //生成颜色查找表
106.        if(pimage->bpp <= 8) {
107.                for(i=0; i<pimage->palsize; i++) {
108.                        pimage->palette[i].b = bgetc(src);
109.                        pimage->palette[i].g = bgetc(src);
110.                        pimage->palette[i].r = bgetc(src);
111.                        if(headsize != COREHEADSIZE)
112.                                bgetc(src);
113.                }
114.        }
115.
116.        /* decode image data*/
117.        bseek(src, bmpf.bfOffBits, SEEK_SET);
118.
119.        h = pimage->height;
120.        /* For every row ... */
     //从右下角开始逐行向上读取数据
121.        while (--h >= 0) {
122.                /* turn image rightside up*/
            // 把imagebits指针移动到内存的右下角
123.                imagebits = pimage->imagebits + h*pimage->pitch;
            
124.
125.                /* Get row data from file */
           //解压缩数据,BI_RLE8,BI_RLE4均为数据压缩格式
126.                if(compression == BI_RLE8) {
127.                        if(!DecodeRLE8(imagebits, src))
128.                                break;
129.                } else if(compression == BI_RLE4) {
130.                        if(!DecodeRLE4(imagebits, src))
131.                                break;
132.                } else {
133.                        if(bread(src, imagebits, pimage->pitch) !=
134.                                pimage->pitch)
135.                                        goto err;
136.                }
137.        }
138.        return 1;                /* bmp image ok*/
139.         
140.err:
141.        EPRINTF("LoadBMP: image loading error/n");
142.        if(pimage->imagebits)
143.                free(pimage->imagebits);
144.        if(pimage->palette)
145.                free(pimage->palette);
146.        return 2;                /* bmp image error*/
147.}

4.3位图的显示
   在nano-X 中 ,位图的显示基本上都是以窗口的形式出现的,他的显示过程是这样的,首先调用窗口创建函数,然后通过GrLoadImageFromFile API函数连接服务器把位图解码,返回图像ID并把图像数据注册到窗口连表中,然后调用GrMapWindow函数来显示窗口,他的实质是然后填充窗口updata域,然后激发服务端调用相应的函数汉实现图像绘制,图像绘制的过程是这样的首先我们通过findimage()函数在图像窗口链表中查找是否有该窗口(其实也就是看该窗口解码没有),如果找到就返回有关该窗口注册的数据节点,然后把图像的像素宽度和显示屏的像素宽度和高度进行对比,如果像素宽度一样直接进行像素值解码,否则还要调用GdStretchImage()函数来对图像进行延伸,然后进行像素值解码,具体解码是怎样计算的我也不清楚这点,然后每解码一个点,就在framebuffer中画出一个点,nano-X就这样不厌其烦的一遍遍的重复这个动作来完成枯燥的图像绘制。