分类: LINUX
2009-08-06 16:30:46
第1章 microwinodows图形显示框架
1.1 microwindows体系结构
Microwindows 采用了分层结构设计方法,其层次结构如下图所示。同时, 这里也列出 Microwindows 源代码目录树下的主要目录结构,以便于对照参考。
microwindows --bin
--Configs
nano-X /win32 API --demos
--drivers
engine --engine
色彩控制|blitting|区域剪裁|clipping|图形绘制 microwindows-0.90 --fonts
--includes --lib
显示驱动 | 键盘驱动 | 鼠标/触摸屏驱动 --mwin
--nanox
图1 microwindows体系结构
Microwindows 提 供 两 种 类 型 的 API , 一 种 叫 Microwindows API,是与win32/WinCE GDI 相兼容的 API 接口,另一种叫 Nano-X API,是与 Linux 上著名的 X Window 相兼容的 API。实现 Microwindows API 的程序放置在 mwin 目录下,实现 Nano-X API 的程序放置在 nanox 目录下。
API层之下是设备无关的图形引擎层,该层负责底层绘图原语,实现各种图形绘制, 各种图像格式处理,字体处理等功能。该层的程序主要放置在 engine 和 fonts 目录下。
在最下层是设备驱动层,该层为各类设备提供驱动程序,目前包括显示/触摸屏,鼠标,键盘等设备的驱动程序,驱动层负责与物理设备进行交互通信,该层的程序都放置在 drivers 目录下。
1.2 引擎层
1.2.1 引擎层剖析
图形引擎层在 Microwindows 中起着承上启下的作用,既对下层设备驱动的功能实行封装,又为上层应用编程接口 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 等图形格式的显示操作。
Microwindows的中间层是图形引擎层(也称为设备与平台无关层),图形引擎层为应用层提供了一系列的绘图函数,因为Microwindows系统中最核心的图形函数是图形引擎层通过调用下层的硬件设备驱动程序实现的,所以该层与硬件无关。在microwindows的绘图过程中用户应用程序通常不直接调用引擎层的绘图函数,而是通过调用最上层所提供的编程接口API,来实现图像的绘制。这些绘制函数的命名遵循 GdXXX()的模式,第一个参数是 SCREENDEVICE 结构的指针,在 device.h 中定义,描述了显示器的底层特性,是显示设备驱动程序的接口。SCREENDEVICE 结构中包含了显示设备的物理特性,比如横向和纵向的大小,显示模式,色彩,显示内存的大小等属性,此外该结构中也包含了各种和屏幕设备相关的操作的函数指针,所有的屏幕坐标都是COORD类型的,以设备坐标的方式指明,即对屏幕左上角的位移。颜色总是以RGB COLORVAL值的方式传送给图形引擎。他们可能在转换为调色板的索引值后,作为PIXELVAL值被传送给显示硬件。在下面的底层驱动的章节中我会详细介绍他们,下面我们对图形引擎中的用到的概念进行解释。
1.2.1.1 图形上下文
Nano-X 中使用图形上下文描述绘图属性。要在屏幕上绘图,需要先调用GrNewGC()函数分配一个图形上下文数据结构,然后返回数据结构的 ID 值。图形上下文实际上标识了一个 GR_GC_INFO 结构体,如下所示,这个结构体包含了前景,背景,字体,绘图模式和绘图区域等信息,设置图形上下文时使用函数进行操作,而不是对结构体直接操作。
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;
1.2.1.2 绘图区域
矩形区域被用来描述屏幕上点的集合。简单的情况下,矩形区域可以使用一个长方形来描述,但是在复杂情况下可能要使用复杂的数据结构描述。Microwindows 中绘图区域使用互不重叠的矩形区域表示,因此对于一个不规则的凸边形要使用多个不重叠的矩形拼和。
1.2.1.3裁剪
Microwindows 中裁剪和以上提及的绘图区域有着密切的关系。任何时候, 图形引擎都有一个绘图区域,这个区域是由一系列的矩形组成的,所有对图形的操作都是在这个区域进行。系统中实现裁剪有两种方法,都使用了GdSetClipRects()函数将裁剪区域传入图形引擎, 此后所有的绘图函数都要先判断绘图区域是否能被绘制,然后才进行绘制。
1.3底层驱动
人机交互系统必然都包含输入/输出设备,Microwindows 中控制的设备包括显示器/屏输出设备和鼠标/键盘/触摸屏等输入设备。设备驱动程序的接口函数在 device.h 中定义,至少包含一个屏幕驱动程序,一个鼠标驱动程序和一个键盘驱动程序。图形引擎层能够直接调用这些驱动程序进行相应的硬件设备操作, 从而实现设备无关性, 使用这样的层次结构能保证添加其他的硬件设备时不影响整个系统的正常工作。
1.3.1屏幕驱动
microwindows屏幕驱动是基于Linux内核中framebuffer,这要求在编译内核的时候选择支持framebuffer编译参数选项。它是通过fd=open(env="/dev/fb0")打开,用SCREENDEVICE的指针PSD指向这片显存,然后对这片显存根据屏幕的不同位色设置情况为中间引擎层提供相应的图形操作支持,包括画点线、图片显示、屏幕拷贝以及中西文字的显示等等。由于microwindows自身的特性他被被设计成能够极其容易的向Microwindows中移植新硬件,因此必须提供一些访问硬件的入口点,同时提供其他的函数保证只使用小部分的核心函数集就可以完成此任务。例如:屏幕驱动程序必须实现ReadPixel,DrawPixel,DrawHorzline和DrawVertLine。这些函数在显存中读取或写入一个像素,或画一条水平或垂直的线。位图传送允许Microwindows执行屏幕外的作图。Microwindows允许任何能在屏幕上进行的图形操作在屏幕外进行,然后再将图形传送到物理显存。下面我们就介绍显示设备驱动使用的典型数据结构,参考该结构可以帮助了解显示设备的基本功能。
显示驱动接口数据结构:
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驱动接口
在上面的文章中我们看到这样一句话:显示驱动是整个系统中最为复杂的部分,但是驱动程序的复杂性却换来了移植的方便性,microwindows是怎么让复杂的显示驱动来方便移动呢?就是因为系统的显示驱动程序里只有几个接口和硬件设备直接交互,其他程序提供核心的绘图操作比如读取像素,绘制像素,绘制更复杂的图形等,这些绘图操作对系统映射的显存进行操作,读写像素点的信息。举个通俗的例子,现在的轿车和以前的轿车在和刚开始发明的时候的驾驶方式几乎没有什么改变,都是离合,油门,方向盘,这个固定模式就好比我们的接口,他就是这样,不管你的车性能多好,多高级,都是内部的事,驾驶方式都是一样的。来让我们来看看microwindows的离合,油门,方向盘。在microwindows里们不论是画多少位图像的驱动我们都有统一的接口,都有统一的功能函数。下们就是microwindows驱动的接口,如果有一天我们中的那位发明一个microwidnows没有的图像格式,如果还要用microwindows的话,那么我们就要自己来写驱动了,不过不要头大,我们的驱动中只要包括下面接口中的函数就可以了,这就是microwindows的马克思理论性,永远没有尽头,永远可以延伸和扩展。
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驱动选择
我们知道,图形显示有个显示模式的概念,一个像素可以用一位比特表示,也可以用2,4,8,15,16,24,32个比特表示,另外,VGA16标准模式使用平面图形模式,而VESA2.0使用的是线性图形模式。所以即使是同样基于Framebuffer 的驱动,不同的模式也要使用不同的驱动函数:那么microwidnows是怎样选择驱动的呢。写到这里我不禁要感慨一下,今天看到这篇文章的人是多么的幸福,有人已经为你总结好了,可我可是一天几行,请教无数善良的人们才知道的。
我说的这个内容是在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图形解析中的应用
如果在microwidnows使用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 虚窗口(microwindows中的offscreen)
Microwindows也支持一种从来不在屏幕上显示的窗口,即像素映射(pixmap)。像素映射窗口有时也称作虚窗口(offscreen),虚窗口不能在屏幕上显示出来,但是可以使用GrCopyArea函数复制到别的窗口。有时在expose events期间,CPU太忙而无法保存显示窗口的内容,普通窗口在被遮掩时又从不保存它们的内容,这时就可以使用像素映射。用GrNewPixmap函数可以生成一个像素映射。系统使用malloc分配的内存地址代替用mmap分配的物理framebuffer地址。在系统不使用实际物理内存地址的情况下(X或MS窗口),必须写两套函数,一套用于图形系统硬件,一套用于内存地址。另外,需要知道在两种格式之间的复制方法。位的blitting函数必须要快。查看fblin8.c和mempl4.c文件可以找到支持两种显示硬件类型的例子。我做过一个对于microwindows图形引擎的修改,就是自己在devimage.c自己定义了一套绘图函数和驱动,然后把图像画在自己申请的内存中,然后把画好后的图像用GrArea拷贝到一个正在显示的图像界面上,结果获得了成功,我想这是我对在屏幕外作图最深刻的体会把。
第4章microwidows对图片的解析
microwindows支持多种图片格式,包括GIF,JPEG,BMP,PNG,XPM,PBM,PPM等等,那么microwindows是怎么对这些图像进行选择和解码的呢?位图是现在比较常见的图像格式,那么我们就主要说一下microwindows对位图的解码过程。
3.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个像素。
3.2 microwindows对位图的解码
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
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.}