缩放到合适尺寸
SetDIBitsToDevice完成了将DIB的图素对点送入输出设备的显示程序。这对于打印DIB用处不大。打印机的分辨率越高,得到的图像就越小,您最终会得到如邮票大小的图像。
要通过缩小或放大DIB,在输出设备上以特定的大小显示它,可以使用StretchDIBits:
iLines = StretchDIBits (
hdc, // device context handle
xDst, // x destination coordinate
yDst, // y destination coordinate
cxDst, // destination rectangle width
cyDst, // destination rectangle height
xSrc, // x source coordinate
ySrc, // y source coordinate
cxSrc, // source rectangle width
cySrc, // source rectangle height
pBits, // pointer to DIB pixel bits
pInfo, // pointer to DIB information
fClrUse, // color use flag
dwRop) ; // raster operation
函数参数除了下列三个方面,均与SetDIBitsToDevice相同。
- 目的坐标包括逻辑宽度(cxDst)和高度(cyDst),以及开始点。
- 不能通过持续显示DIB来减少内存需求。
- 最后一个参数是位映像操作方式,它指出了DIB图素与输出设备图素结合的方式,在最后一章将学到这些内容。现在我们为此参数设定为SRCCOPY。
还有另一个更细微的差别。如果查看SetDIBitsToDevice的声明,您会发现cxSrc和cySrc是DWORD,这是32位无正负号长整数型态。在StretchDIBits中,cxSrc和cySrc(以及cxDst和cyDst)定义为带正负号的整数型态,这意味着它们可以为负数,实际上等一下就会看到,它们确实能为负数。如果您已经开始检查是否别的参数也可以为负数,就让我声明一下:在两个函数中,xSrc和ySrc均定义为int,但这是错的,这些值始终是非负数。
DIB内的来源矩形被映射到目的矩形的坐标显示如表15-4所示。
来源矩形 | 目的矩形 |
(xSrc, ySrc) | (xDst, yDst + cyDst - 1) |
(xSrc + cxSrc - 1, ySrc) | (xDst + cxDst - 1, yDst + cyDst - 1) |
(xSrc, ySrc + cySrc - 1) | (xDst, yDst) |
(xSrc + cxSrc - 1, ySrc + cySrc - 1) | (xDst + cxDst - 1, yDst) |
右列中的-1项是不精确的,因为放大的程度(以及映像方式和其它变换)能产生略微不同的结果。
例如,考虑一个2×2的DIB,这里StretchDIBits的xSrc和ySrc参数均为0,cxSrc和cySrc均为2。假定我们显示到的设备内容具有MM_TEXT映像方式并且不进行变换。如果xDst和yDst均为0,cxDst和cyDst均为4,那么我们将以倍数2放大DIB。每个来源图素(x,y)将映射到下面所示的四个目的图素上:
(0,0) --> (0,2) and (1,2) and (0,3) and (1,3)
(1,0) --> (2,2) and (3,2) and (2,3) and (3,3)
(0,1) --> (0,0) and (1,0) and (0,1) and (1,1)
(1,1) --> (2,0) and (3,0) and (2,1) and (3,1)
上表正确地指出了目的的角,(0,3)、(3,3)、(0,0)和(3,0)。在其它情况下,坐标可能是个大概值。
目的设备内容的映像方式对SetDIBitsToDevice的影响仅是由于xDst和yDst是逻辑坐标。StretchDIBits完全受映像方式的影响。例如,如果您设定了y值向上递增的一种度量映像方式,DIB就会颠倒显示。
您可以通过把cyDst设定为负数来弥补这种情况。实际上,您可以将任何参数的宽度和高度变为负值来水平或垂直翻转DIB。在MM_TEXT映像方式下,如果cySrc和cyDst符号相反,DIB会沿着水平轴翻转并颠倒显示。如果cxSrc和cxDst符号相反,DIB会沿着垂直轴翻转并显示它的镜面图像。
下面是总结这些内容的表达式,xMM和yMM指出映像方式的方向,如果x值向右增长,则xMM值为1;如果x值向左增长,则值为-1。同样,如果y值向下增长,则yMM值为1;如果y值向上增长,则值为-1。Sign函数对于正值传回TURE,对于负值传回FALSE。
if (!Sign (xMM × cxSrc × cxDst))
DIB is flipped on its vertical axis (mirror image)
if (!Sign (yMM × cySrc × cyDst))
DIB is flipped on its horizontal axis (upside down)
若有疑问,请查阅表15-4。
程序15-5 SHOWDIB以实际尺寸显示DIB、放大至显示区域窗口的大小、打印DIB以及把DIB传输到剪贴簿。
SHOWDIB2.C
/*--------------------------------------------------------------------------
SHOWDIB2.C -- Shows a DIB in the client area
(c) Charles Petzold, 1998
---------------------------------------------------------------------------*/
#include <windows.h>
#include "dibfile.h"
#include "resource.h"
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
TCHAR szAppName[] = TEXT ("ShowDib2") ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
HACCEL hAccel ;
HWND hwnd ;
MSG msg ;
WNDCLASS wndclass ;
wndclass.style = CS_HREDRAW | CS_VREDRAW ;
wndclass.lpfnWndProc = WndProc ;
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
wndclass.hInstance = hInstance ;
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
wndclass.lpszMenuName = szAppName ;
wndclass.lpszClassName = szAppName ;
if (!RegisterClass (&wndclass))
{
MessageBox (NULL, TEXT ("This program requires Windows NT!"),
szAppName, MB_ICONERROR) ;
return 0 ;
}
hwnd = CreateWindow (szAppName, TEXT ("Show DIB #2"),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL) ;
ShowWindow (hwnd, iCmdShow) ;
UpdateWindow (hwnd) ;
hAccel = LoadAccelerators (hInstance, szAppName) ;
while (GetMessage (&msg, NULL, 0, 0))
{
if (!TranslateAccelerator (hwnd, hAccel, &msg))
{
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
}
return msg.wParam ;
}
int ShowDib (HDC hdc, BITMAPINFO * pbmi, BYTE * pBits, int cxDib, int cyDib,
int cxClient, int cyClient, WORD wShow)
{
switch (wShow)
{
case IDM_SHOW_NORMAL:
return SetDIBitsToDevice (hdc, 0, 0, cxDib, cyDib, 0, 0,
0, cyDib, pBits, pbmi, DIB_RGB_COLORS) ;
case IDM_SHOW_CENTER:
return SetDIBitsToDevice (hdc, (cxClient - cxDib) / 2,
(cyClient - cyDib) / 2,
cxDib, cyDib, 0, 0, 0, cyDib, pBits, pbmi, DIB_RGB_COLORS) ;
case IDM_SHOW_STRETCH:
SetStretchBltMode (hdc, COLORONCOLOR) ;
return StretchDIBits(hdc,0, 0, cxClient, cyClient, 0, 0, cxDib, cyDib,
pBits, pbmi, DIB_RGB_COLORS, SRCCOPY) ;
case IDM_SHOW_ISOSTRETCH:
SetStretchBltMode (hdc, COLORONCOLOR) ;
SetMapMode (hdc, MM_ISOTROPIC) ;
SetWindowExtEx (hdc, cxDib, cyDib, NULL) ;
SetViewportExtEx (hdc, cxClient, cyClient, NULL) ;
SetWindowOrgEx (hdc, cxDib / 2, cyDib / 2, NULL) ;
SetViewportOrgEx (hdc, cxClient / 2, cyClient / 2, NULL) ;
return StretchDIBits(hdc,0, 0, cxDib, cyDib, 0, 0, cxDib, cyDib,
pBits, pbmi, DIB_RGB_COLORS, SRCCOPY) ;
}
}
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam)
{
static BITMAPFILEHEADER * pbmfh ;
static BITMAPINFO * pbmi ;
static BYTE * pBits ;
static DOCINFO di = {sizeof (DOCINFO),
TEXT ("ShowDib2: Printing") } ;
static int cxClient, cyClient, cxDib, cyDib ;
static PRINTDLG printdlg = { sizeof (PRINTDLG) } ;
static TCHAR szFileName [MAX_PATH], szTitleName [MAX_PATH] ;
static WORD wShow = IDM_SHOW_NORMAL ;
BOOL bSuccess ;
HDC hdc, hdcPrn ;
HGLOBAL hGlobal ;
HMENU hMenu ;
int cxPage, cyPage, iEnable ;
PAINTSTRUCT ps ;
BYTE * pGlobal ;
switch (message)
{
case WM_CREATE:
DibFileInitialize (hwnd) ;
return 0 ;
case WM_SIZE:
cxClient = LOWORD (lParam) ;
cyClient = HIWORD (lParam) ;
return 0 ;
case WM_INITMENUPOPUP:
hMenu = GetMenu (hwnd) ;
if (pbmfh)
iEnable = MF_ENABLED ;
else
iEnable = MF_GRAYED ;
EnableMenuItem (hMenu, IDM_FILE_SAVE, iEnable) ;
EnableMenuItem (hMenu, IDM_FILE_PRINT, iEnable) ;
EnableMenuItem (hMenu, IDM_EDIT_CUT, iEnable) ;
EnableMenuItem (hMenu, IDM_EDIT_COPY, iEnable) ;
EnableMenuItem (hMenu, IDM_EDIT_DELETE, iEnable) ;
return 0 ;
case WM_COMMAND:
hMenu = GetMenu (hwnd) ;
switch (LOWORD (wParam))
{
case IDM_FILE_OPEN:
// Show the File Open dialog box
if (!DibFileOpenDlg (hwnd, szFileName, szTitleName))
return 0 ;
// If there's an existing DIB, free the memory
if (pbmfh)
{
free (pbmfh) ;
pbmfh = NULL ;
}
// Load the entire DIB into memory
SetCursor (LoadCursor (NULL, IDC_WAIT)) ;
ShowCursor (TRUE) ;
pbmfh = DibLoadImage (szFileName) ;
ShowCursor (FALSE) ;
SetCursor (LoadCursor (NULL, IDC_ARROW)) ;
// Invalidate the client area for later update
InvalidateRect (hwnd, NULL, TRUE) ;
if (pbmfh == NULL)
{
MessageBox ( hwnd, TEXT ("Cannot load DIB file"),
szAppName, MB_ICONEXCLAMATION | MB_OK) ;
return 0 ;
}
// Get pointers to the info structure & the bits
pbmi = (BITMAPINFO *) (pbmfh + 1) ;
pBits = (BYTE *) pbmfh + pbmfh->bfOffBits ;
// Get the DIB width and height
if (pbmi->bmiHeader.biSize == sizeof (BITMAPCOREHEADER))
{
cxDib = ((BITMAPCOREHEADER *) pbmi)->bcWidth ;
cyDib = ((BITMAPCOREHEADER *) pbmi)->bcHeight ;
}
else
{
cxDib = pbmi->bmiHeader.biWidth ;
cyDib = abs (pbmi->bmiHeader.biHeight) ;
}
return 0 ;
case IDM_FILE_SAVE:
// Show the File Save dialog box
if (!DibFileSaveDlg (hwnd, szFileName, szTitleName))
return 0 ;
// Save the DIB to a disk file
SetCursor (LoadCursor (NULL, IDC_WAIT)) ;
ShowCursor (TRUE) ;
bSuccess = DibSaveImage (szFileName, pbmfh) ;
ShowCursor (FALSE) ;
SetCursor (LoadCursor (NULL, IDC_ARROW)) ;
if (!bSuccess)
MessageBox ( hwnd, TEXT ("Cannot save DIB file"),
szAppName, MB_ICONEXCLAMATION | MB_OK) ;
return 0 ;
case IDM_FILE_PRINT:
if (!pbmfh)
return 0 ;
// Get printer DC
printdlg.Flags = PD_RETURNDC | PD_NOPAGENUMS | PD_NOSELECTION ;
if (!PrintDlg (&printdlg))
return 0 ;
if (NULL == (hdcPrn = printdlg.hDC))
{
MessageBox (hwnd, TEXT ("Cannot obtain Printer DC"),
szAppName, MB_ICONEXCLAMATION | MB_OK) ;
return 0 ;
}
// Check whether the printer can print bitmaps
if (!(RC_BITBLT & GetDeviceCaps (hdcPrn, RASTERCAPS)))
{
DeleteDC (hdcPrn) ;
MessageBox ( hwnd, TEXT ("Printer cannot print bitmaps"),
szAppName, MB_ICONEXCLAMATION | MB_OK) ;
return 0 ;
}
// Get size of printable area of page
cxPage = GetDeviceCaps (hdcPrn, HORZRES) ;
cyPage = GetDeviceCaps (hdcPrn, VERTRES) ;
bSuccess = FALSE ;
// Send the DIB to the printer
SetCursor (LoadCursor (NULL, IDC_WAIT)) ;
ShowCursor (TRUE) ;
if ((StartDoc (hdcPrn, &di) > 0) && (StartPage (hdcPrn) > 0))
{
ShowDib ( hdcPrn, pbmi, pBits, cxDib, cyDib,
cxPage, cyPage, wShow) ;
if (EndPage (hdcPrn) > 0)
{
bSuccess = TRUE ;
EndDoc (hdcPrn) ;
}
}
ShowCursor (FALSE) ;
SetCursor (LoadCursor (NULL, IDC_ARROW)) ;
DeleteDC (hdcPrn) ;
if (!bSuccess)
MessageBox (hwnd, TEXT ("Could not print bitmap"),
szAppName, MB_ICONEXCLAMATION | MB_OK) ;
return 0 ;
case IDM_EDIT_COPY:
case IDM_EDIT_CUT:
if (!pbmfh)
return 0 ;
// Make a copy of the packed DIB
hGlobal = GlobalAlloc (GHND | GMEM_SHARE, pbmfh->bfSize -
sizeof (BITMAPFILEHEADER)) ;
pGlobal = GlobalLock (hGlobal) ;
CopyMemory ( pGlobal, (BYTE *) pbmfh + sizeof (BITMAPFILEHEADER),
pbmfh->bfSize - sizeof (BITMAPFILEHEADER)) ;
GlobalUnlock (hGlobal) ;
// Transfer it to the clipboard
OpenClipboard (hwnd) ;
EmptyClipboard () ;
SetClipboardData (CF_DIB, hGlobal) ;
CloseClipboard () ;
if (LOWORD (wParam) == IDM_EDIT_COPY)
return 0 ;
// fall through if IDM_EDIT_CUT
case IDM_EDIT_DELETE:
if (pbmfh)
{
free (pbmfh) ;
pbmfh = NULL ;
InvalidateRect (hwnd, NULL, TRUE) ;
}
return 0 ;
case IDM_SHOW_NORMAL:
case IDM_SHOW_CENTER:
case IDM_SHOW_STRETCH:
case IDM_SHOW_ISOSTRETCH:
CheckMenuItem (hMenu, wShow, MF_UNCHECKED) ;
wShow = LOWORD (wParam) ;
CheckMenuItem (hMenu, wShow, MF_CHECKED) ;
InvalidateRect (hwnd, NULL, TRUE) ;
return 0 ;
}
break ;
case WM_PAINT:
hdc = BeginPaint (hwnd, &ps) ;
if (pbmfh)
ShowDib ( hdc, pbmi, pBits, cxDib, cyDib,
cxClient, cyClient, wShow) ;
EndPaint (hwnd, &ps) ;
return 0 ;
case WM_DESTROY:
if (pbmfh)
free (pbmfh) ;
PostQuitMessage (0) ;
return 0 ;
}
return DefWindowProc (hwnd, message, wParam, lParam) ;
}
SHOWDIB2.RC (摘录)
//Microsoft Developer Studio generated resource script.
#include "resource.h"
#include "afxres.h"
/
// Menu
SHOWDIB2 MENU DISCARDABLE
BEGIN
POPUP "&File"
BEGIN
MENUITEM "&Open.../tCtrl+O",IDM_FILE_OPEN
MENUITEM "&Save.../tCtrl+S", IDM_FILE_SAVE
MENUITEM SEPARATOR
MENUITEM "&Print/tCtrl+P", IDM_FILE_PRINT
END
POPUP "&Edit"
BEGIN
MENUITEM "Cu&t/tCtrl+X", IDM_EDIT_CUT
MENUITEM "&Copy/tCtrl+C", IDM_EDIT_COPY
MENUITEM "&Delete/tDelete", IDM_EDIT_DELETE
END
POPUP "&Show"
BEGIN
MENUITEM "&Actual Size", IDM_SHOW_NORMAL, CHECKED
MENUITEM "&Center", IDM_SHOW_CENTER
MENUITEM "&Stretch to Window", IDM_SHOW_STRETCH
MENUITEM "Stretch &Isotropically", IDM_SHOW_ISOSTRETCH
END
END
/
// Accelerator
SHOWDIB2 ACCELERATORS DISCARDABLE
BEGIN
"C", IDM_EDIT_COPY, VIRTKEY, CONTROL, NOINVERT
"O", IDM_FILE_OPEN, VIRTKEY, CONTROL, NOINVERT
"P", IDM_FILE_PRINT, VIRTKEY, CONTROL, NOINVERT
"S", IDM_FILE_SAVE, VIRTKEY, CONTROL, NOINVERT
VK_DELETE, IDM_EDIT_DELETE, VIRTKEY, NOINVERT
"X", IDM_EDIT_CUT, VIRTKEY, CONTROL, NOINVERT
END
RESOURCE.H (摘录)
// Microsoft Developer Studio generated include file.
// Used by ShowDib2.rc
#define IDM_FILE_OPEN 40001
#define IDM_SHOW_NORMAL 40002
#define IDM_SHOW_CENTER 40003
#define IDM_SHOW_STRETCH 40004
#define IDM_SHOW_ISOSTRETCH 40005
#define IDM_FILE_PRINT 40006
#define IDM_EDIT_COPY 40007
#define IDM_EDIT_CUT 40008
#define IDM_EDIT_DELETE 40009
#define IDM_FILE_SAVE 40010
有意思的是ShowDib函数,它依赖于菜单选择以四种不同的方式之一在程序的显示区域显示DIB。可以使用SetDIBitsToDevice从显示区域的左上角或在显示区域的中心显示DIB。程序也有两个使用StretchDIBits的选项,DIB能放大填充整个显示区域。在此情况下它可能会变形,或它能等比例显示,也就是说不会变形。
把DIB复制到剪贴簿包括:在整体共享内存中制作packed DIB内存块的副本。剪贴簿数据型态为CF_DIB。程序没有列出从剪贴簿复制DIB的方法,因为在仅有指向packed DIB的指标的情况下这样做需要更多步骤来确定图素位的偏移量。我将在下一章的末尾示范如何做到这点的方法。
您可能注意到了SHOWDIB2中的一些不足之处。如果您以256色显示模式执行Windows,就会看到显示除了单色或4位DIB以外的其它图形出现的问题,您看不到真正的颜色。存取那些颜色需要使用调色盘,在下一章会做这些工作。您也可能注意到速度问题,尤其在Windows NT下执行SHOWDIB2时。在下一章packed DIB和位图时,我会展示处理的方法。我也给DIB显示添加滚动条,这样也能以实际尺寸查看大于屏幕的DIB。
色彩转换、调色盘和显示效能
记得在虎豹小霸王编剧William Goldman的另一出电影剧本《All the President's Men》中,Deep Throat告诉Bob Woodward揭开水门秘密的关键是「跟着钱走」。那么在位图显示中获得高级性能的关键就是「跟着图素位走」以及理解色彩转换发生的时机。DIB是设备无关的格式,视讯显示器内存几乎总是与图素格式不同。在SetDIBitsToDevice或StretchDIBits函数呼叫期间,每个图素(可能有几百万个)必须从设备无关的格式转换成设备相关格式。
在许多情况下,这种转换是很繁琐的。例如,在24位视讯显示器上显示24位DIB,显示驱动程序最多是切换红、绿、蓝的字节顺序而已。在24位设备上显示16位DIB就需要位的搬移和修剪了。在24位设备上显示4位或8位DIB要求在DIB色彩对照表内查找DIB图素位,然后对字节重新排列。
但是要在4位或8位视讯显示器上显示16位、24位或32位DIB时,会发生什么事情呢?一种完全不一样的颜色转换发生了。对于DIB内的每个图素,设备驱动程序必须在图素和显示器上可用的颜色之间「找寻最接近的色彩」,这包括循环和计算。(GDI函数GetNearestColor进行「最接近色彩搜寻」。)
整个RGB色彩的三维数组可用立方体表示。曲线内任意两点之间的距离是:
在这里两个颜色是R1G1B1和R2G2B2。执行最接近色彩搜寻包括从一种颜色到其它颜色集合中找寻最短距离。幸运的是,在RGB颜色立方体中「比较」距离时,并不需要计算平方根部分。但是需转换的每个图素必须与设备的所有颜色相比较以发现最接近的颜色。这是个工作量相当大的工作。(尽管在8位设备上显示8位DIB也得进行最接近色彩搜寻,但它不必对每个图素都进行,它仅需对DIB色彩对照表中的每种颜色进行寻找。)
正是由于以上原因,应该避免使用SetDIBitsToDevice或StretchDIBits在8位视讯显示卡上显示16位、24位或32位DIB。DIB应转换为8位DIB,或者8位DDB,以求得更好的显示效能。实际上,您可以经由将DIB转换为DDB并使用BitBlt和StretchBlt显示图像,来加快显示任何DIB的速度。
如果在8位视讯显示器上执行Windows(或仅仅切换到8位模式来观察在显示True-ColorDIB时的效能变化),您可能会注意到另一个问题:DIB不会使用所有颜色来显示。任何在8位视讯显示器上的DIB刚好限制在以20种颜色显示。如何获得多于20种颜色是「调色盘管理器」的任务,这将在下一章提到。
最后,如果在同一台机器上执行Windows 98和Windows NT,您可能会注意到:对于同样的显示模式,Windows NT显示大型DIB花费的时间较长。这是Windows NT的客户/服务器体系结构的结果,它使大量数据在传输给API函数时耗费更多时间。解决方法是将DIB转换为DDB。而我等一下将谈到的CreateDIBSection函数对这种情况特别有用。