[2014年写一个UI库时写的几个文章,公布出来]
几年前的一个嵌入式的UI开发,使自己有机会接触到了UI的一些底层知识,尽管之前也开发过非常多Windows下的信息应用系统,也做非常多的界面开发,但一直却对UI的一些运作却不了解。
BOSS决定使用UCGUI做为UI的基本库来开发UI界面的一些应用。用UCGUI的库来做开发。它已经有非常完好的基本构件,像窗口的管理,主要的控件,图片,文字处理等。只是听说UCGUI的授权费用也是不菲的。但我觉得它确实是一个物有所值的东西。
在使用中也还是遇到过一些问题,但这些基本都不会有什么大的影响,基本的是有源码,有一些小的BUG。也能够自己处理。满足不了的控件能够自己开发,也能够非常方便的在他的控件基础上做一些扩展。
使用它避免不了要去读了解它的一些底层代码,这样自己有机会对UI的动作有了一些主要的了解。近期也做些UI的事。想起UCGUI的代码架构还是值得学习的,所以自己从UI的基本原理上再次学习了一次,自己也花时间动手做了一些主要的代码实现。非常多基本原理都是从UCGUI上学习来的。再把这些主要的东西记下来,方便后来的学习者。
怎样自己动手写一个UI?在我自己没接触底层UI前,一直没想过,也认为它是一件比較复杂和难的事情。
它有多难?如今我认为假设会一门编程语言就能够写一个UI出来。这种基础就够了,我想能够试一试。
从画一个点開始
“像素”这个词我们一定已经非常熟悉了。如今买手机时大家非常观注的一个參数就是摄像头的成像像素是多少,由于图像的质量非常大成度就是它决定的。一个像素代表一个点。相同在显示器上也是一样,我们说的分辨率就是长宽多少个像素,在一个屏幕上显示有文字,有窗口。有图片及各种形状等等!看起来这是一个挺复杂的东西,但它却是由一个一个的点构成的。一个点代表一个像素,而这个点不同的像素值就代表不同的颜色。一个像素能够用8位,16位,32位来表示,位数越多。表示它能表示的颜色就越多显示的色彩也就越丰富了。
一个屏幕像是这种:
我们以左上角为原点,做一个坐标系。分别有X,Y方向。
像我如今的电脑的分辨率是1440x900,这样 X 最大取值是1440,Y最大取值是900。
假设我们要在一个屏幕上画一个点,给定一个点如:(500,500),仅仅要在坐标系里找到对应的位置就能够写一个像素值进去。屏幕就会显示出一个颜色点。
显示器有一个显存。要在屏幕上画出点,也就须要在对应的显存位置写入颜色值。显存又会映射到内存的一块连续的区域。这样我们仅仅要把值写入内存的区域。系统又会作I/O读写把更新显存的值。所心我们仅仅要关心写的内存的区域就好了。
操作显存
在Linux里有一个叫framebuffer的概念。叫“帧缓存”,事实上就是对显存当前值的缓存,Linux系统的显卡驱动都有实现,我们对framebuffer的读写就是对显存的读写。
在Linux里/dev文件夹里,应都有一个类似fbx(x表示一个数字)设备文件。打开它,再用mmap函数把framebuffer内存映射进我们的进程里就可认方便的对显示器操作了,这样我们能够画点,画线显示在显示器上面。
如以下的代码操作,打开fb0,并读取设置对应的參数:
static struct fb_var_screeninfo stVarInfo;
static struct fb_fix_screeninfo stfixInfo;
static unsigned char *pFrameBuffer = NULL;
char *file_name = "/dev/fb0";
int fbDev, s32Ret;
fbDev = open(file_name,O_RDWR,0);
if(fbDev < 0)
{
printf("open framebuff failed! ");
return;
}
stVarInfo.bits_per_pixel = 16;
stVarInfo.activate = FB_ACTIVATE_NOW;
stVarInfo.xres = stVarInfo.xres_virtual = 1440;
stVarInfo.yres = stVarInfo.yres_virtual = 900;
s32Ret = ioctl(fbDev, FBIOPUT_VSCREENINFO, &stVarInfo);
if(s32Ret < 0)
{
printf("PUT_VSCREENINFO failed!%x ",s32Ret);
return ;
}
if (ioctl(fbDev, FBIOGET_FSCREENINFO, &stfixInfo) < 0)
{
printf("Get fix screen info failed! ");
return ;
}
pFrameBuffer = mmap(HI_NULL, stfixInfo.smem_len, PROT_READ|PROT_WRITE, MAP_SHARED, fbDev, 0);
memset(pFrameBuffer,0x00,stfixInfo.smem_len);
在上面的代码中打开一个framebuffer,设置读取显示器的信息当中两个比較重要的结构:fb_var_screeninfo和fb_fix_screeninfo 。详细的能够搜索了解一下。
在mmap时,stfixInfo.smem_len就是显存在内存区域的大小。 memset的操作效果就是我们把显示器设成全黑色了,由于我们写入的每个点的像素值都是0x0。
在一个指定的点画点
显存的地址空间是一个线性的一维地址空间,我们的屏幕像上面的坐标系,是一个二维的了。给定一个点(x,y),这样我们就须要把它转换成对应的内存所在的地址。当知道一个屏幕的分辨率了,如1440 * 900,如今通过FBIOGET_FSCREENINFO知道了一些主要的信息,通过mmap知道了首地址。一个像素能够用8位。或16位,或32位,或很多其它的位表示。如我的板上的系统是16位,我用的LINUX系统是用32位的。以我板上的为例,用16位来表示一个像素点。也就是2个字节表示一个像素点。
那么屏幕上的点是这样确定的,想像你拿支笔从第一行画点,但把第一行画满后。再从第二行開始,持续的把整个屏画满,在屏上画点也是这样,假如是16位2个字节表示一个像素,分辨率是1400*900。所以给定的点(x,y)的地址为:
x * 2 + y * (1440 * 2),
假设用上一步代码里得到的信息来表示就是:
(x + stVarInfo.xoffset) * (stVarInfo.bits_per_pixel >> 3) + (y + stVarInfo.yoffset) * stfixInfo.line_length;
Xoffset,yoffset表示是否相对原点的偏移,bit_per_pixel表示一个像素用多少位表示。line_length表示一行占用多少字节。
画一个点(x,y)的地址确定了,这样写入一个值,对应的位置显示对应的颜色。
那么对应的画点函数例如以下:
void UI_SetPointPixel(int x, int y, int pixelValue)
{
int location = 0;
location = (x + stVarInfo.xoffset) * (stVarInfo.bits_per_pixel >> 3) + (y + stVarInfo.yoffset) * stfixInfo.line_length;
*(short *)(pFrameBuffer + location) = (short)pixelValue;
}
应为是16位的。所以注意上面的转换 (short *).
获取一个点的颜色
相同有时操作须要得到一个点的颜色,依据上面画点的函数。能够例如以下写出获取点的函数:
unsigned int fb_GetPointPixel(int x, int y)
{
int location = 0;
location = (x + stVarInfo.xoffset) * (stVarInfo.bits_per_pixel >> 3) + (y + stVarInfo.yoffset) * stfixInfo.line_length;
int PixelIndex = *(short*)(pShowScreen + location);
return PixelIndex;
}
主要的UI底层操作
我们工作用的操作UI上,有窗口。图片,视频等等,非常多东西,看起来非常复杂。但这种复杂北后,却是主要的像素点组成的,但再复杂的实现。都是在上面的画点函数。为基础的。为了方便我们开法。于是扩展了上面的画点函数,有画线段。矩形,圆等等。为了方便后面画更复杂的。我们实现画线和填充矩形函数:
void fb_DrawHLine(int x0, int y0, int x1)
{
U16 PixelColor = UI_GetDrawPixeColor();
for(; x0 <= x1; x0++)
{
UI_SetPointPixel(x0, y0, PixelColor);
}
}
void fb_DrawVLine(int x0, int y0, int y1)
{
U16 PixelColor = UI_GetDrawPixeColor();
for(; y0 <= y1; y0++)
{
UI_SetPointPixel(x0, y0, PixelColor);
}
}
void fb_DrawFillRect(int x0, int y0, int x1, int y1)
{
for(; y0<=y1; y0++)
{
UI_DrawHLine(x0, y0, x1);
}
}
分别实现画水平,垂直线。画填充的矩形。上面的 UI_GetDrawPixeColor 函数得到当前画点线的填充颜色值。
这样我们就有了主要的操作UI的工具了。
我们如今用上面的函数就能够画点,线。矩形,还是非常easy的。
2014-11-15