原文:http://blog.csdn.net/doon/article/details/8964750
虽然MiniGUI的应用范围越来越窄,很多功能和思想都落后于时代的发展,但是,作为一款开源的优秀的GUI库,具有很好的学习和参考价值。基于这个考虑,在我忘掉MiniGUI之前,把我所知道的写下来。
前言
熟悉windows的朋友都了解,GDI对外提供一个HDC的DC句柄。DC为(Device Context)的缩写,表示一个抽象的设备当前绘图情况。
一般情况下,屏幕、打印设备、内存像素都可以抽象出一个HDC句柄。 一个设备可以抽象多个HDC句柄,每个HDC句柄,都有自己独立的上下文信息。
在MiniGUI中,从窗口创建的DC都称为硬件DC,而从位图或者其他DC衍生的DC,称为MemDC。
注:MiniGUI源码目录中有gdi和newgdi两个目录。其中gdi已经被废弃,newgdi是目前使用的。但gdi和newgdi很多文件和函数名都是一样的,容易发生混淆,阅读源码时,要注意区分。
通常,在DC中保存的上下文信息有
当前点,当调用LineTo一类函数时,会绘制一条从当前点到LineTo指定参数的线。可以通过MoveTo来改变;
当前画笔的属性,如:画笔宽度(SetPenWidth), 颜色(SetPenColor),线段连接类型(SetPenJoinStyle), 线段端点的类型(SetPenCapStyle)
当前画刷的属性,如:画刷类型(SetBrushType),颜色(SetBrushColor),等
当前文字的信息:如,选择当前字体(SelectFont),文字颜色(SetTextColor)等
当前剪切域信息:如,SelectClipRect, SelectClipRgn等
逻辑坐标的信息:如,SetWindowOrg,SetWindowExt等。
几乎所有绘图函数,都是通过HDC来操作的。
HDC及tagDC
HDC只是一个简单的句柄,被定义为
-
typedef unsigned long HDC;
typedef unsigned long HDC;
而它真正对应的结构体就是tagDC。
HDC就是将tagDC的指针转为unsigned long后的值。
tagDC的定义如下
/* Device Context */
struct tagDC
{
short DataType; /* the data type, always be TYPE_HDC */
short DCType; /* the dc type */
BOOL inuse; //对于硬件DC,它从一个固定长度的数组缓存中获取一个DC对象。inuse TRUE表示这个DC已经被分配出去了,FALSE表示空闲。这是为了避免频繁调用malloc和free函数。
HWND hwnd; //和硬件DC关联的 (DCType == TYPE_GENDC)
/* surface of this DC */
GAL_Surface* surface; //DC所依赖的surface,最重要的数据结构
/* background color */
gal_pixel bkcolor;
/* pen color */
gal_pixel pencolor;
/* solid brush color */
gal_pixel brushcolor;
/* text color */
gal_pixel textcolor;
int bkmode;
int tabstop;
int cExtra; /* Character extra */
int alExtra; /* Above line extra */
int blExtra; /* Bellow line extra */
int mapmode; /* mappping mode */
int ta_flags; /* Text alignment flags */
#ifdef _MGHAVE_ADV_2DAPI
/* pen attributes */
int pen_type;
int pen_cap_style;
int pen_join_style;
unsigned int pen_width;
/* brush attributes */
int brush_type;
POINT brush_orig;
const BITMAP* brush_tile;
const STIPPLE* brush_stipple;
/* custom dash info */
int dash_offset;
const unsigned char* dash_list;
size_t dash_list_len;
#endif
PLOGFONT pLogFont;
POINT CurPenPos;
POINT CurTextPos;
POINT ViewOrig;
POINT ViewExtent;
POINT WindowOrig;
POINT WindowExtent;
/* raster operation */
int rop;
/* used by the text rendering for anti-aliasing fonts. */
gal_pixel gray_pixels [17];
/* used by the text rendering for low-pass filtering. */
gal_pixel filter_pixels [17];
GAL_PixelFormat* alpha_pixel_format;
/* pixel and line operation */
CB_COMP_SETPIXEL draw_pixel;
CB_COMP_SETHLINE draw_pixel_span;
CB_COMP_PUTHLINE draw_src_span;
DC_MOVE_TO move_to;
DC_STEP_X step_x;
/* === context information. ============================================= */
/* DK[01/22/10]:This segment is binary compatible with _COMP_CTXT struct */
int step;
gal_uint8* cur_dst;
gal_pixel skip_pixel;
gal_pixel cur_pixel;
void * user_comp_ctxt;
/* ====================================================================== */
CLIPRECT* cur_ban;
RECT rc_output;
/* local clip region information */
CLIPRGN lcrgn;
/* effective clip region information */
CLIPRGN ecrgn;
/* device rect */
BOOL bIsClient;
RECT DevRC;
PGCRINFO pGCRInfo;
unsigned int oldage;
CB_BITMAP_SCALER_FUNC bitmap_scaler; //图片绘制所使用的数据结构
};
和窗口相关的DC
和窗口相关的DC,有几个重要函数:
BeginPaint/EndPaint, GetDC, GetClientDC/ReleaseDC。还有一种PrivateDC,是一个历史遗留的DC,现在几乎不使用,不必考虑它。
BeginPaint/EndPaint是必须用在窗口回调的MSG_PAINT消息中的。之所以这样做,是因为BeginPaint会清除窗口的无效区域(详细了解 MiniGUI源码分析--Helloworld(3):消息概览)。
GetDC针对整个窗口,而GetClientDC则针对客户区。 这两个函数在MSG_CREATE消息中和所有的key, mouse消息等都可以使用。但是对于 MSG_NCCREATE, MSG_SIZECHANGING的极个别消息不能使用。
这些DC创建方法大同小异,我以GetClientDC为例来说明
-
HDC GUIAPI GetClientDC(HWND hWnd)
-
{
-
int i;
-
-
#ifndef _LITE_VERSION
-
pthread_mutex_lock (&dcslot);
-
#endif
-
for (i = 0; i < DCSLOTNUMBER; i++) {
-
if (!DCSlot[i].inuse) {
-
DCSlot[i].inuse = TRUE;
-
DCSlot[i].DataType = TYPE_HDC;
-
DCSlot[i].DCType = TYPE_GENDC;
-
break;
-
}
-
}
-
#ifndef _LITE_VERSION
-
pthread_mutex_unlock (&dcslot);
-
#endif
-
-
if (i >= DCSLOTNUMBER)
-
return HDC_SCREEN;
-
-
dc_InitDC (DCSlot + i, hWnd, TRUE);
-
return (HDC) (DCSlot + i);
-
}
HDC GUIAPI GetClientDC(HWND hWnd)
{
int i;
#ifndef _LITE_VERSION
pthread_mutex_lock (&dcslot);
#endif
for (i = 0; i < DCSLOTNUMBER; i++) { //从固定缓冲区分配
if (!DCSlot[i].inuse) {
DCSlot[i].inuse = TRUE;
DCSlot[i].DataType = TYPE_HDC;
DCSlot[i].DCType = TYPE_GENDC;
break;
}
}
#ifndef _LITE_VERSION
pthread_mutex_unlock (&dcslot);
#endif
if (i >= DCSLOTNUMBER)
return HDC_SCREEN;
dc_InitDC (DCSlot + i, hWnd, TRUE); //这是关键函数
return (HDC) (DCSlot + i);
}
GetClientDC和GetDC都是通过dc_InitDC来初始化的,这是DC创建的关键点 (请仔细看注释)
-
-
static void dc_InitDC (PDC pdc, HWND hWnd, BOOL bIsClient)
-
{
-
PCONTROL pCtrl;
-
CLIPRGN ergn;
-
-
pdc->hwnd = hWnd;
-
-
-
memset (pdc->gray_pixels, 0, sizeof (pdc->gray_pixels));
-
memset (pdc->filter_pixels, 0, sizeof (pdc->filter_pixels));
-
-
pdc->bkcolor = GAL_MapRGB (pdc->surface->format, 0xFF, 0xFF, 0xFF);
-
pdc->bkmode = 0;
-
-
pdc->pencolor = GAL_MapRGB (pdc->surface->format, 0x00, 0x00, 0x00);
-
pdc->brushcolor = GAL_MapRGB (pdc->surface->format, 0xFF, 0xFF, 0xFF);
-
-
pdc->textcolor = GAL_MapRGB (pdc->surface->format, 0x00, 0x00, 0x00);
-
if (!(pdc->pLogFont = GetWindowFont (hWnd)))
-
pdc->pLogFont = GetSystemFont (SYSLOGFONT_WCHAR_DEF);
-
pdc->tabstop = 8;
-
pdc->CurTextPos.x = pdc->CurTextPos.y = 0;
-
pdc->cExtra = pdc->alExtra = pdc->blExtra = 0;
-
-
pdc->mapmode = MM_TEXT;
-
pdc->ta_flags = TA_LEFT | TA_TOP | TA_NOUPDATECP;
-
-
pdc->ViewOrig.x = pdc->ViewOrig.y = 0;
-
pdc->ViewExtent.x = pdc->ViewExtent.y = 1;
-
pdc->WindowOrig.x = pdc->WindowOrig.y = 0;
-
pdc->WindowExtent.x = pdc->WindowExtent.y = 1;
-
-
#ifdef _MGHAVE_ADV_2DAPI
-
pdc->pen_type = PT_SOLID;
-
pdc->pen_cap_style = PT_CAP_BUTT;
-
pdc->pen_join_style = PT_JOIN_MITER;
-
pdc->pen_width = 0;
-
pdc->dash_offset = 0;
-
pdc->dash_list = NULL;
-
pdc->dash_list_len = 0;
-
-
pdc->brush_type = BT_SOLID;
-
pdc->brush_orig.x = pdc->brush_orig.y = 0;
-
pdc->brush_tile = NULL;
-
pdc->brush_stipple = NULL;
-
#endif
-
-
-
-
if (dc_IsGeneralDC (pdc)) {
-
RECT minimal;
-
-
pdc->pGCRInfo = kernel_GetGCRgnInfo (hWnd);
-
LOCK_GCRINFO (pdc);
-
-
pdc->oldage = pdc->pGCRInfo->age;
-
ClipRgnCopy (&pdc->ecrgn, &pdc->pGCRInfo->crgn);
-
-
pdc->bIsClient = bIsClient;
-
if (bIsClient)
-
WndClientRect (pdc->hwnd, &pdc->DevRC);
-
else
-
WndRect (pdc->hwnd, &pdc->DevRC);
-
-
minimal = pdc->DevRC;
-
-
pCtrl = gui_Control (pdc->hwnd);
-
-
if (pCtrl && !(pCtrl->dwExStyle & WS_EX_CTRLASMAINWIN))
-
{
-
InitClipRgn (&ergn, &__mg_FreeClipRectList);
-
-
if (RestrictControlECRGNEx (&minimal, pCtrl, &ergn)) {
-
ClipRgnIntersect (&pdc->ecrgn, &pdc->ecrgn, &ergn);
-
EmptyClipRgn (&ergn);
-
}
-
else {
-
EmptyClipRgn (&pdc->ecrgn);
-
}
-
}
-
else {
-
IntersectClipRect (&pdc->ecrgn, &minimal);
-
}
-
-
UNLOCK_GCRINFO (pdc);
-
}
-
-
-
pdc->CurPenPos.x = pdc->CurTextPos.x = 0;
-
pdc->CurPenPos.y = pdc->CurTextPos.y = 0;
-
-
-
pdc->rop = ROP_SET;
-
pdc->step = 1;
-
-
pdc->draw_pixel = draw_pixel_ops [pdc->rop]
-
[pdc->surface->format->BytesPerPixel - 1];
-
pdc->draw_pixel_span = draw_pixel_span_ops [pdc->rop]
-
[pdc->surface->format->BytesPerPixel - 1];
-
pdc->draw_src_span = draw_src_span_ops [pdc->rop]
-
[pdc->surface->format->BytesPerPixel - 1];
-
-
pdc->cur_dst = (BYTE*)pdc->surface->pixels
-
+ pdc->surface->pitch * pdc->DevRC.top
-
+ pdc->surface->format->BytesPerPixel * pdc->DevRC.left;
-
-
pdc->move_to = move_to_ops [pdc->surface->format->BytesPerPixel - 1];
-
pdc->step_x = step_x_ops [pdc->surface->format->BytesPerPixel - 1];
-
-
pdc->alpha_pixel_format = NULL;
-
SetBitmapScalerType((HDC)pdc, BITMAP_SCALER_DDA);
-
}
/* This function initializes a DC: set the default parameters. */
static void dc_InitDC (PDC pdc, HWND hWnd, BOOL bIsClient)
{
PCONTROL pCtrl;
CLIPRGN ergn;
pdc->hwnd = hWnd; //关联窗口
//初始化上下文代码的内容,基本可以跳过不看
memset (pdc->gray_pixels, 0, sizeof (pdc->gray_pixels));
memset (pdc->filter_pixels, 0, sizeof (pdc->filter_pixels));
pdc->bkcolor = GAL_MapRGB (pdc->surface->format, 0xFF, 0xFF, 0xFF);
pdc->bkmode = 0;
pdc->pencolor = GAL_MapRGB (pdc->surface->format, 0x00, 0x00, 0x00);
pdc->brushcolor = GAL_MapRGB (pdc->surface->format, 0xFF, 0xFF, 0xFF);
pdc->textcolor = GAL_MapRGB (pdc->surface->format, 0x00, 0x00, 0x00);
if (!(pdc->pLogFont = GetWindowFont (hWnd)))
pdc->pLogFont = GetSystemFont (SYSLOGFONT_WCHAR_DEF);
pdc->tabstop = 8;
pdc->CurTextPos.x = pdc->CurTextPos.y = 0;
pdc->cExtra = pdc->alExtra = pdc->blExtra = 0;
pdc->mapmode = MM_TEXT;
pdc->ta_flags = TA_LEFT | TA_TOP | TA_NOUPDATECP;
pdc->ViewOrig.x = pdc->ViewOrig.y = 0;
pdc->ViewExtent.x = pdc->ViewExtent.y = 1;
pdc->WindowOrig.x = pdc->WindowOrig.y = 0;
pdc->WindowExtent.x = pdc->WindowExtent.y = 1;
#ifdef _MGHAVE_ADV_2DAPI
pdc->pen_type = PT_SOLID;
pdc->pen_cap_style = PT_CAP_BUTT;
pdc->pen_join_style = PT_JOIN_MITER;
pdc->pen_width = 0;
pdc->dash_offset = 0;
pdc->dash_list = NULL;
pdc->dash_list_len = 0;
pdc->brush_type = BT_SOLID;
pdc->brush_orig.x = pdc->brush_orig.y = 0;
pdc->brush_tile = NULL;
pdc->brush_stipple = NULL;
#endif
/* Assume that the local clip region is empty. */
/* Get global clip region info and generate effective clip region. */
if (dc_IsGeneralDC (pdc)) {
RECT minimal;
pdc->pGCRInfo = kernel_GetGCRgnInfo (hWnd);
LOCK_GCRINFO (pdc);
pdc->oldage = pdc->pGCRInfo->age;
ClipRgnCopy (&pdc->ecrgn, &pdc->pGCRInfo->crgn);
pdc->bIsClient = bIsClient;
if (bIsClient) // 限制DC的剪切域为客户区/整个窗口
WndClientRect (pdc->hwnd, &pdc->DevRC);
else
WndRect (pdc->hwnd, &pdc->DevRC);
minimal = pdc->DevRC;
pCtrl = gui_Control (pdc->hwnd);
if (pCtrl && !(pCtrl->dwExStyle & WS_EX_CTRLASMAINWIN))
{
InitClipRgn (&ergn, &__mg_FreeClipRectList);
if (RestrictControlECRGNEx (&minimal, pCtrl, &ergn)) {
ClipRgnIntersect (&pdc->ecrgn, &pdc->ecrgn, &ergn);
EmptyClipRgn (&ergn);
}
else {
EmptyClipRgn (&pdc->ecrgn);
}
}
else {
IntersectClipRect (&pdc->ecrgn, &minimal);
}
UNLOCK_GCRINFO (pdc);
}
/* context info and raster operations. */
pdc->CurPenPos.x = pdc->CurTextPos.x = 0;
pdc->CurPenPos.y = pdc->CurTextPos.y = 0;
//设置图像和背景的混合方法,同windows中的 NOT画笔、异或笔等类型的功能
pdc->rop = ROP_SET;
pdc->step = 1;
//每种混合方式都有对应的函数,这是为了提高绘制速度
pdc->draw_pixel = draw_pixel_ops [pdc->rop]
[pdc->surface->format->BytesPerPixel - 1];
pdc->draw_pixel_span = draw_pixel_span_ops [pdc->rop]
[pdc->surface->format->BytesPerPixel - 1];
pdc->draw_src_span = draw_src_span_ops [pdc->rop]
[pdc->surface->format->BytesPerPixel - 1];
//设置dc的像素起点值
pdc->cur_dst = (BYTE*)pdc->surface->pixels
+ pdc->surface->pitch * pdc->DevRC.top
+ pdc->surface->format->BytesPerPixel * pdc->DevRC.left;
pdc->move_to = move_to_ops [pdc->surface->format->BytesPerPixel - 1];
pdc->step_x = step_x_ops [pdc->surface->format->BytesPerPixel - 1];
pdc->alpha_pixel_format = NULL;
SetBitmapScalerType((HDC)pdc, BITMAP_SCALER_DDA); //设置默认的位图缩放算法:DDA 这是一种速度快但是效果差的缩放算法
}
这里,值得一提的是关于HDC如何确定像素的内存地址的。这需要一个重要的概念:pitch。pitch表示一行像素的字节宽度(单位为字节)。
像素在显存(内存)中,是一段连续的内存。如 800x600-16bpp的一个屏幕,它的像素占用的内存是 800*2*600 = 960000 byte。 每一行占用的字节数为800x2 = 1600。那么,它的pitch=1600。
pitch和3个因素相关:像素矩阵的宽度、单个像素占用的字节数和对齐。在32位设备上,每一行像素必须是4的倍数。宽度为799的像素矩阵,其pitch = 1600,因为最后两个字节因为对其的原因,必须是补充进来。
上图中,外层为屏幕的surface的像素矩阵;内层位DC的大小和位置。DecRC表示的DC在surface中的大小和位置。
cur_dst的像素指针为
-
pdc->cur_dst = (BYTE*)pdc->surface->pixels
-
+ pdc->surface->pitch * pdc->DevRC.top
-
+ pdc->surface->format->BytesPerPixel * pdc->DevRC.left;
pdc->cur_dst = (BYTE*)pdc->surface->pixels
+ pdc->surface->pitch * pdc->DevRC.top
+ pdc->surface->format->BytesPerPixel * pdc->DevRC.left;
如果要计算DC中某个点(a,b)的像素位置,只要进行如下运算:
-
pixel = cur_dst + a + b * pitch;
pixel = cur_dst + a + b * pitch;
兼容的MemDC
兼容的概念指,创建的内存DC的像素格式和屏幕的像素格式一样。
创建兼容DC,使用CreateCompatibleDCEx。这个函数,接受一个HDC参数(可以是任意一个有效的HDC)。
它们的实际实现是,根据传递的DC类型,创建一个和DC的像素格式一样的内存DC。
但是CreateComnpatibleDCEx将直接分配内存来供新的DC来使用。
自定义的MemDC
另外,我们还可以创建的DC包括CreateMemDCEx和CreateMemDCFromBitmap。CreateMemDC(CreateMemDCEx)这个函数会为dc生成专门的surface,而CreateMemDCFromBitmap则利用位图的像素矩阵作为surface。 这样做就可以在一个位图上绘制信息了。
Sub DC
SubDC的含义为子DC。即在原DC的基础上,创建一个区域小于或者等于原DC的新DC。这个DC将继承原DC的所有属性值。SubDC和原DC都基于同一个surface,这样,SubDC和原DC都可以在同一位置绘制了。
SubDC的概念来自于java的AWT。主要用于窗口的绘制(从父窗口向子窗口绘制时,创建一个SubDC,可以避免子窗口的绘制上下文污染父窗口)
涉及的函数包括 GetSubDC(针对BeginPaint, GetDC和GetClientDC得到的dc)和CreateSubMemDC。
Secondary DC
SecondaryDC是在MiniGUI 3.0引入的。它是和窗口关联的一个后备缓冲区。起到的作用是防止闪烁(实际有限)和在窗口不可见的情况下获取窗口的内容(目的是为了实现窗口特效)。
SecondaryDC只有主窗口才有,子窗口是没有SecondaryDC的。通过给窗口的扩展风格,增加WS_EX_AUTOSECONDARYDC(必须在窗口创建时),可以自动创建一个SecondaryDC。 通过GetSecondaryDC能够获取到该DC。
个人认为SecondaryDC的设置不是很成功,读者不需要给予过多关注。