位图的缺点:第一个问题是容易受设备依赖性的影响,第二个缺点是需要很大的储存空间。
4 颜色和位图
除空间尺寸以外,位图还有颜色尺寸。这里指的是每个图素所需要的位数,有时也称为位图的 颜色深度(color depth)、位数(bit-count)或 位/图素(bpp:bits per pixel)数。位图中的每个图素都有相同数量的颜色位。
每图素1位的位图称为二阶(bilevel)、 二色(bicolor)或者单色(monochrome)位图。每图素可以是0或1,0表示黑色,1可以表示白色,但并不总是这样。对于其它颜色,一个图素就需要有多个位。可能的颜色值等于2位数值。用2位可以得到4种颜色,用4位可以得16种颜色,8位可得到256种颜色,16位可得到65,536种颜色,而24位可得到16,777,216种颜色。
如何将颜色位的组合与人们所熟悉的颜色相对应是目前处理位图时经常碰到(而且常常是灾难)的问题。
调色盘管理器允许应用程序在256色显示器上显示实际位图。Windows所储存的20种颜色如表所示。
IRGB | RGB颜色 | 颜色名称 |
00000000 | 00-00-00 | 黑 |
00000001 | 80-00-00 | 暗红 |
00000010 | 00-80-00 | 暗绿 |
00000011 | 80-80-00 | 暗黄 |
00000100 | 00-00-80 | 暗蓝 |
00000101 | 80-00-80 | 暗洋红 |
00000110 | 00-80-80 | 暗青 |
00000111 | C0-C0-C0 | 亮灰 |
00001000 | C0-DC-C0 | 美元绿 |
00001001 | A6-CA-F0 | 天蓝 |
11110110 | FF-FB-F0 | 乳白 |
11110111 | A0-A0-A4 | 中性灰 |
11111000 | 80-80-80 | 暗灰 |
11111001 | FF-00-00 | 红 |
11111010 | 00-FF-00 | 绿 |
11111011 | FF-FF-00 | 黄 |
11111100 | 00-00-FF | 蓝 |
11111101 | FF-00-FF | 洋红 |
11111110 | 00-FF-FF | 青 |
11111111 | FF-FF-FF | 白 |
在调用GetDeviceCaps时,您能利用BITSPIXEL和PLANES常数来获得显示卡的颜色单位,这些值显示如表14-3所示
表14-3 |
BITSPIXEL | PLANES | 颜色数 |
1 | 1 | 2 |
1 | 4 | 16 |
8 | 1 | 256 |
15或16 | 1 | 32,768或65 536 |
24或32 | 1 | 16 777 216 |
6 块传输
我前面提到过,您可以把整个视讯显示器看作是一幅大位图。您在屏幕上见到的图素由储存在视讯显示卡上内存中的位来描述。任何视讯显示的矩形区域也都是一个位图,其大小是它所包含的行列数。
让我们从将图像从视讯显示的一个区域复制到另一个区域,开始我们在位图世界的旅行吧!这个是强大的BitBlt函数的工作。
int nXDest,
int nYDest,
int nWidth,
int nHeight,
HDC hdcSrc,
int nXSrc,
int nYSrc,
DWORD dwRop);
图案(P):1 1 1 1 0 0 0 0 来源(s):1 1 0 0 1 1 0 0 目的(D):1 0 1 0 1 0 1 0 | 布尔操作 | ROP代码 | 名称 | |
结果: | 0 0 0 0 0 0 0 0 | 0 | 0x000042 | BLACKNESS |
0 0 0 1 0 0 0 1 | ~(S|D) | 0x1100A6 | NOTSRCERASE | |
0 0 1 1 0 0 1 1 | ~S | 0x330008 | NOTSRCCOPY | |
0 1 0 0 0 1 0 0 | S & ~D | 0x440328 | SRCERASE | |
0 1 0 1 0 1 0 1 | ~D | 0x550009 | DSTINVERT | |
0 1 0 1 1 0 1 0 | P ^ D | 0x5A0049 | PATINVERT | |
0 1 1 0 0 1 1 0 | S ^ D | 0x660046 | SRCINVERT | |
1 0 0 0 1 0 0 0 | S & D | 0x8800C6 | SRCAND | |
1 0 1 1 1 0 1 1 | ~S| D | 0xBB0226 | MERGEPAINT | |
1 1 0 0 0 0 0 0 | P & S | 0xC000CA | MERGECOPY | |
1 1 0 0 1 1 0 0 | S | 0xCC0020 | SRCCOPY | |
1 1 1 0 1 1 1 0 | S| D | 0xEE0086 | SRCPAINT | |
1 1 1 1 0 0 0 0 | P | 0xF00021 | PATCOPY | |
1 1 1 1 1 0 1 1 | P| ~S| D | 0xFB0A09 | PATPAINT | |
1 1 1 1 1 1 1 1 | 1 | 0xFF0062 | WHITENESS |
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static int cxClient, cyClient, cxSource, cySource ;
HDC hdcClient, hdcWindow ;
int x, y ;
PAINTSTRUCT ps ;
switch (message)
{
case WM_CREATE:
cxSource = GetSystemMetrics (SM_CXSIZEFRAME) +
GetSystemMetrics (SM_CXSMICON) ;
cySource = GetSystemMetrics (SM_CYSIZEFRAME) +
GetSystemMetrics (SM_CYCAPTION) ;
return 0 ;
case WM_SIZE:
cxClient = LOWORD (lParam) ;
cyClient = HIWORD (lParam) ;
return 0 ;
case WM_PAINT:
hdcClient = BeginPaint (hwnd, &ps) ;
hdcWindow = GetWindowDC (hwnd) ;
for (y = 0 ; y < cyClient ; y += cySource)
for (x = 0 ; x < cxClient ; x += cxSource)
{
BitBlt (hdcClient, x, y, cxSource, cySource,
hdcWindow, 0, 0, SRCCOPY) ;
}
ReleaseDC (hwnd, hdcWindow) ;
EndPaint (hwnd, &ps) ;
return 0 ;
case WM_DESTROY:
PostQuitMessage (0) ;
return 0 ;
}
return DefWindowProc (hwnd, message, wParam, lParam) ;
}
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static int cxClient, cyClient, cxSource, cySource ;
HDC hdcClient, hdcWindow ;
PAINTSTRUCT ps ;
static RECT rect,rect1;
switch (message)
{
case WM_CREATE:
GetClientRect(GetDesktopWindow(),&rect);
GetWindowRect(GetDesktopWindow(), &rect1);
return 0 ;
case WM_SIZE:
cxClient = LOWORD (lParam) ;
cyClient = HIWORD (lParam) ;
return 0 ;
case WM_PAINT:
hdcClient = BeginPaint (hwnd, &ps) ;
hdcWindow = GetDC(NULL);
SetStretchBltMode(hdcClient,COLORONCOLOR);
StretchBlt (hdcClient, 0, 0, cxClient, cyClient,
hdcWindow, 0, 0, rect.right, rect.bottom, MERGECOPY) ;
ReleaseDC (hwnd, hdcWindow) ;
EndPaint (hwnd, &ps) ;
return 0 ;
case WM_DESTROY:
PostQuitMessage (0) ;
return 0 ;
}
return DefWindowProc (hwnd, message, wParam, lParam) ;
}
BitBlt和StretchBlt函数中所有的坐标与大小都是依据逻辑单位的。但是当您在BitBlt函数中定义了两个不同的设备内容,而这两个设备内容虽然参考同一个实际设备,却各自有着不同的映像模式,这时将发生什么结果呢?如果出现这种情况,呼叫BitBlt产生的结果就显得不明确了:cx和cy参数都是逻辑单位,而它们同样应用于来源设备内容和目的设备内容中的矩形区。所有的坐标和尺寸必须在实际的位传输之前转换为设备坐标。因为cx和cy值同时用于来源和目的设备内容,所以此值必须转换为设备内容自己的单位。
当来源和目的设备内容相同,或者两个设备内容都使用MM_TEXT图像模式时,设备单位下的矩形尺寸在两个设备内容中会是相同的,然后才由Windows进行图素对图素的转换。不过,如果设备单位下的矩形尺寸在两个设备内容中不同时,则Windows就把此工作转交给更通用的StretchBlt函数。
StretchBlt也允许水平或垂直翻转图像。如果cxSrc和cxDst标记(转换成设备单位以后)不同,那么StretchBlt就建立一个镜像:左右翻转。在STRETCH程序中,通过将xDst参数改为cxClient并将cxDst参数改成-cxClient,您就可以做到这一点。如果cySrc和cyDst不同,则StretchBlt会上下翻转图像。要在STRETCH程序中测试这一点,可将yDst参数改为cyClient并将cyDst参数改成-cyClient。
StretchBlt模式
使用StretchBlt会碰到一些与位图大小缩放相关的一些根本问题。在扩展一个位图时,StretchBlt必须复制图素行或列。如果放大倍数不是原图的整数倍,那么此操作会造成产生的图像有些失真。
如果目的矩形比来源矩形小,那么StretchBlt在缩小图像时就必须把两行(或列)或者多行(或列)的图素合并到一行(或列)。完成此操作有四种方法,它根据设备内容伸展模式属性来选择其中一种方法。您可使用SetStretchBltMode函数来修改这个属性。
iMode可取下列值:
- BLACKONWHITE或者STRETCH_ANDSCANS(内定)如果两个或多个图素得合并成一个图素,那么StretchBlt会对图素执行一个逻辑AND运算。这样的结果是只有全部的原始图素是白色时该图素才为白色,其实际意义是黑色图素控制了白色图素。这适用于白背景中主要是黑色的单色位图。
- WHITEONBLACK或STRETCH_ORSCANS 如果两个或多个图素得合并成一个图素,那么StretchBlt执行逻辑OR运算。这样的结果是只有全部的原始图素都是黑色时才是黑色,也就是说由白色图素决定颜色。这适用于黑色背景中主要是白色的单色位图。
- COLORONCOLOR或STRETCH_DELETESCANS StretchBlt简单地消除图素行或列,而没有任何逻辑组合。这是通常是处理彩色位图的最佳方法。
- HALFTONE或STRETCH_HALFTONE Windows根据组合起来的来源颜色来计算目的的平均颜色。这将与半调调色盘联合使用, 第十六章将展示这一程序。
BITBLT和STRETCH程序简单地将来源位图复制给了目的位图,在过程中也可能进行了缩放。这是把SRCCOPY作为BitBlt和StretchBlt函数最后一个参数的结果。SRCCOPY只是您能在这些函数中使用的256个位映像操作中的一个。让我们先在STRETCH程序中做一个别的实验,然后再系统地研究位映像操作。
尽量用NOTSRCCOPY来代替SRCCOPY。与它们名称一样,位映像操作在复制位图时转换其颜色。在显示区域窗口,所有的颜色转换:黑色变成白色、白色变成黑色,蓝色变成黄色。现在试一下SRCINVERT,您将得到同样效果。如果试一下BLACKNESS,正如其名称一样,整个显示区域都将变成黑色,而WHITENESS则使其变成白色。
现在试一试用下列代码代替StretchBlt函数:
SelectObject (hdcClient, CreateHatchBrush (HS_DIAGCROSS, RGB (0, 0, 0)));
SetStretchBltMode(hdcClient,COLORONCOLOR);
StretchBlt (hdcClient, 0, 0, cxClient, cyClient,hdcWindow, 0, 0, cxSource, cySource, MERGECOPY) ;
DeleteObject (hdcClient, GetStockObject (WHITE_BRUSH)) ;
这次,您将在图像上看到一个菱形的画刷,这是什么?
我在前面说过,BitBlt和StretchBlt函数不是简单的位块传输。此函数实际在下面三种图像间执行位操作。
- Source 来源位图,拉伸或压缩(如果有必要)到目的矩形的尺寸。
- Destination 在BitBlt或StretchBlt呼叫之前的目的矩形。
- Pattern 在目的设备内容中选择的目前画刷,水平或垂直地复制到目的矩形范围内。
结果是复制到了目的矩形中。
PatBlt
除了BitBlt和StretchBlt以外,Windows还包括一个称为PatBlt (「pattern block transfer:图案块传输」)的函数。这是三个「blt」函数中最简单的。与BitBlt和StretchBlt不同,它只使用一个目的设备内容。PatBlt语法是:
PatBlt (hdc, x, y, cx, cy, dwROP) ;
x、y、cx和cy参数字于逻辑单位。逻辑点(x,y)指定了矩形的左上角。矩形宽为cx单位,高为cy单位。这是PatBlt修改的矩形区域。PatBlt在画刷与目的设备内容上执行的逻辑操作由dwROP参数决定,此参数是ROP代码的子集-也就是说,您可以只使用那些不包括来源目的设备内容的ROP代码。下表列出了PatBlt支持的16个位映像操作:
图案(P):1 1 0 0 目的(D):1 0 1 0 | 布尔操作 | ROP代码 | 名称 | |
结果: | 0 0 0 0 | 0 | 0x000042 | BLACKNESS |
0 0 0 1 | ~(P | D) | 0x0500A9 | ||
0 0 1 0 | ~P & D | 0x0A0329 | ||
0 0 1 1 | ~P | 0x0F0001 | ||
0 1 0 0 | P & ~D | 0x500325 | ||
0 1 0 1 | ~D | 0x550009 | DSTINVERT | |
0 1 1 0 | P ^ D | 0x5A0049 | PATINVERT | |
0 1 1 1 | ~(P & D) | 0x5F00E9 | ||
1 0 0 0 | P & D | 0xA000C9 | ||
1 0 0 1 | ~(P ^ D) | 0xA50065 | ||
1 0 1 0 | D | 0xAA0029 | ||
1 0 1 1 | ~P | D | 0xAF0229 | ||
1 1 0 0 | P | 0xF00021 | PATCOPY | |
1 1 0 1 | P | ~D | 0xF50225 | ||
1 1 1 0 | P | D | 0xFA0089 | ||
1 1 1 1 | 1 | 0xFF0062 | WHITENESS |
下面列出了PatBlt一些更常见用途。如果想画一个黑色矩形,您可呼叫
PatBlt (hdc, x, y, cx, cy, BLACKNESS) ;
要画一个白色矩形,请用
PatBlt (hdc, x, y, cx, cy, WHITENESS) ;
实际上,此程序代码是Windows用于执行FillRect函数的动作。如果您呼叫
InvertRect (hdc, &rect) ;
Windows将其转换成函数:
PatBlt (hdc, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, DSTINVERT) ;
CreateCompatibleBitmap 创建一个兼容的位图 不在需要时,使用DeleteObject 删除它
CreateCompatibleDC 创建一个兼容的DC不在需要时 使用 DeleteDC 删除它
CreateBitmapIndirect 创建一个单色位图,不能创建一个彩色位图。
因此,基本的规则是这样的:不要用CreateBitmap、CreateBitmapIndirect或SetBitmapBits来设定彩色DDB的位,您只能安全地使用这些函数来设定单色DDB的位LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static HBITMAP hBitmap ;
static int cxClient, cyClient, cxSource, cySource ;
BITMAP bitmap ;
HDC hdc, hdcMem ;
HINSTANCE hInstance ;
int x, y ;
PAINTSTRUCT ps ;
switch (message)
{
case WM_CREATE:
hInstance = ((LPCREATESTRUCT) lParam)->hInstance ;
hBitmap = LoadBitmap (hInstance, TEXT ("Bricks")) ;
GetObject (hBitmap, sizeof (BITMAP), &bitmap) ;
cxSource = bitmap.bmWidth ;
cySource = bitmap.bmHeight ;
return 0 ;
case WM_SIZE:
cxClient = LOWORD (lParam) ;
cyClient = HIWORD (lParam) ;
return 0 ;
case WM_PAINT:
hdc = BeginPaint (hwnd, &ps) ;
hdcMem = CreateCompatibleDC (hdc) ;
SelectObject (hdcMem, hBitmap) ;
for (y = 0 ; y < cyClient ; y += cySource)
for (x = 0 ; x < cxClient ; x += cxSource)
{
BitBlt (hdc, x, y, cxSource, cySource, hdcMem, 0, 0, SRCCOPY) ;
}
DeleteDC (hdcMem) ;
EndPaint (hwnd, &ps) ;
return 0 ;
case WM_DESTROY:
DeleteObject (hBitmap) ;
PostQuitMessage (0) ;
return 0 ;
}
return DefWindowProc (hwnd, message, wParam, lParam) ;
}
请记住,画刷和位图都是GDI对象,而且您应该在程序终止前删除您在程序中建立画刷和位图。如果您依据位图建立画刷,那么在用画刷画图时,Windows将复制位图位到画刷所绘制的区域内。呼叫CreatePatternBrush(或者CreateBrushIndirect)之后,您可以立即删除位图而不会影响到画笔。类似地,您也可以删除画刷而不会影响到您选进的原始位图。注意,BRICKS3在建立画刷后删除了位图,并在程序终止前删除了画刷。
case WM_CREATE:
hdc = GetDC (hwnd) ;
hdcMem = CreateCompatibleDC (hdc) ;
GetTextExtentPoint32 (hdc, szText, lstrlen (szText), &size) ;
cxBitmap = size.cx ;
cyBitmap = size.cy ;
hBitmap = CreateCompatibleBitmap (hdc, cxBitmap, cyBitmap) ;
ReleaseDC (hwnd, hdc) ;
SelectObject (hdcMem, hBitmap) ;
TextOut (hdcMem, 0, 0, szText, lstrlen (szText)) ;
return 0 ;