📄 screen_resolution.htm
字号:
<html><body background="../di2001.jpg"><head><meta NAME="GENERATOR" CONTENT="Microsoft FrontPage 3.0"><title>Untitled Document</title></head><body BGCOLOR="#ffffff"><font COLOR="#FF7317" SIZE="+4"><p></font><font size="6" color="#0000FF"><strong>Detecting Screen Resolution and (Correct) Color Depth</strong></font><font COLOR="#FF7317" SIZE="+4"> <br><br></font><i><font COLOR="#000000">Davide Marcato<br><br></font></i><b><font COLOR="#FF7317" SIZE="-1">The Windows API function GetDeviceCaps() provides comprehensive information about device context capabilities, including screen resolution and color depth. Unfortunately, the information isn't always right. Davide provides a workaround for this problem.<br><br></font></b><font COLOR="#000000">One of the big advantages of GUI programming for Windows is the layered mechanism that allows GUI applications to treat graphic elements as abstract objects, regardless of the intrinsic capabilities of the hardware devices or the user's configuration choices. This support is fine for most daily tasks, such as typical window painting and simple device-independent bitmap operations. But in certain cases, the high level of abstraction is too restrictive, and programmers need a way to obtain exhaustive information about the real capabilities of the devices they're going to deal with.<br><br></font><b><font COLOR="#FF7317">The power of GetDeviceCaps()<br></font></b><font COLOR="#000000">The API function for this purpose is GetDeviceCaps(), which is actually just a gateway between the application and the device driver. The following is its prototype as reported in wingdi.h:<br><br></font></p><table WIDTH="375" BORDER="0" CELLSPACING="2" CELLPADDING="0"> <tr> <td WIDTH="100%"><pre><b><tt>int GetDeviceCaps(HDC hdc, int nIndex); </tt></b></pre> </td> </tr></table><p><br><font COLOR="#000000"><br>The first parameter is the device context associated with the device in investigation, while the second represents the value to investigate. There's a long list of available items in the Win32 SDK documentation; I'll be focusing on just a few in this article.<br><br>For our purposes, the two most relevant character-istics of the display device are its resolution (horizontally and vertically) and the number of distinct colors it's capable of showing. These values can be retrieved by passing HORZRES, VERTRES, and BITSPIXEL, respectively, as the second parameter to GetDeviceCaps(). Notice that despite its name, the NUMCOLORS constant requests the number of entries in the device's color table; hence, it's meaningful only in "palettized" video modes. On the other hand, BITSPIXEL returns the number of bits necessary to describe the color of one pixel. Determining the actual number of colors is as easy as calculating the power of 2 whose exponent is the returned value.<br><br>The following straightforward C code snippet detects screen resolution and color depth:<br><br></font></p><table WIDTH="375" BORDER="0" CELLSPACING="2" CELLPADDING="0"> <tr> <td WIDTH="100%"><pre><b><tt>/* Screen dc initialization */</tt></b><b><tt>HDC screenDC;</tt></b><b><tt>int colorBits, xRes, yRes;</tt></b><b><tt>screenDC = CreateDC("DISPLAY", NULL, NULL, NULL);</tt></b><b><tt>/* Retrieval of capabilities from the driver */</tt></b><b><tt>colorBits = GetDeviceCaps(screenDC, BITSPIXEL);</tt></b><b><tt>xRes = GetDeviceCaps(screenDC, HORZRES);</tt></b><b><tt>yRes = GetDeviceCaps(screenDC, VERTRES);</tt></b><b><tt>/* Clean-up */</tt></b><b><tt>DeleteDC(dc);</tt></b></pre> </td> </tr></table><p><br><font COLOR="#000000"><br>Unfortunately, that's simply too easy to be true. That code works nicely in all cases, except when a 32K-color video mode is set. In that situation, GetDeviceCaps() returns 16 instead of the expected value 15 (2 <sup>15</sup> is exactly 32,768).<br><br>Although the difference between 32K and 64K color configurations (both referred to as "high-color modes") is generally judged minor, the dithering algorithm applied by Windows when it comes to rendering 64K-color bitmaps on 15-bit devices might notably degrade the appearance of the resulting image. So, how can you detect a 32K color situation and distinguish it from a 64K situation?<br><br></font><b><font COLOR="#FF7317">Exploit SetPixel()'s side benefits<br></font></b><font COLOR="#000000">After thinking about this problem for quite a while, I recalled what makes the SetPixelV() API significantly faster than SetPixel(). The former just sets a pixel on a device context to a specified RGB color, while the latter goes on to return the RGB value that the function could actually set the pixel to, which might be different from the requested color if a perfect match wasn't possible. Though this feature might seem useless, you can use it to solve the problem of GetDeviceCaps() returning 16 bits for a 15-bit color mode. If you try to set the color of a pixel to a supplied RGB value and compare the returned COLORREF to the color you try, you can determine whether that color is supported on that device. Just put the previous algorithm in a loop where the supplied RGB combination varies and the device is the video card, count how many times the comparison evaluates to true, and you're done.<br><br>Obviously, calling SetPixel() 2 <sup>24</sup> times would require an unreasonable amount of time, but there's no need to iterate among all possible values. Comparing each color component separately (first red, then green, then blue) produces the same effect and reduces the number of iterations to an acceptable value of 255, thus preserving performances. After all, you're not going to put this code into a time-critical loop, are you?<br><br>I've encapsulated this workaround into C functions intended as a shortcut to detect the screen resolution and the real number of bits-per-pixel adopted by the current video mode. You can download them along with the sample code that uses them in </font><aHREF="screen.zip">download file SCREEN.ZIP</a><font COLOR="#000000"> and try this yourself.<br><br>GetScrResolution() is just a comfortable wrapper around GetDeviceCaps(HORZRES) and GetDeviceCaps(VERTRES):<br><br></font></p><table WIDTH="445" HEIGHT="176" BORDER="0" CELLSPACING="2" CELLPADDING="0"> <tr> <td WIDTH="100%"><pre><b><tt>BOOL GetScrResolution(WORD* pWidth, WORD* pHeight)</tt></b><b><tt>{</tt></b><b><tt> HDC screenDC;</tt></b><b><tt> screenDC = CreateDC("DISPLAY", NULL, NULL, NULL);</tt></b><b><tt> if (!screenDC)</tt></b><b><tt> return FALSE;</tt></b><b><tt> </tt></b><b><tt> *pWidth = GetDeviceCaps(screenDC, HORZRES);</tt></b><b><tt> *pHeight = GetDeviceCaps(screenDC, VERTRES);</tt></b><b><tt> </tt></b><b><tt> DeleteDC(screenDC);</tt></b><b><tt> return TRUE;</tt></b><b><tt>}</tt></b></pre> </td> </tr></table><p><br><font COLOR="#000000"><br>GetScrColorDepth() calls GetDeviceCaps(BITSPIXEL), but when the API returns 16 bpp, it uses GetScrRGBBitsPerPixel(), which in turn counts the number of red, green, and blue tones displayable. If they all equate to 32, the API return code 16 is obviously incorrect and gets discarded in favor of 15, thus masking the Windows imprecision:<br><br></font></p><table WIDTH="465" HEIGHT="319" BORDER="0" CELLSPACING="2" CELLPADDING="0"> <tr> <td WIDTH="100%"><pre><b><tt>BYTE GetScrColorDepth()</tt></b><b><tt>{</tt></b><b><tt> HDC screenDC;</tt></b><b><tt> BYTE numOfBits;</tt></b><b><tt> screenDC = CreateDC("DISPLAY", NULL, NULL, NULL);</tt></b><b><tt> if (!screenDC)</tt></b><b><tt> return 0;</tt></b><b><tt> numOfBits = GetDeviceCaps(screenDC, BITSPIXEL);</tt></b><b><tt> DeleteDC(screenDC);</tt></b><b><tt> if (numOfBits == 16)</tt></b><b><tt> {</tt></b><b><tt> /* If 64K colors detected, check </tt></b><b><tt> if they were really 32K */</tt></b><b><tt> WORD red, green, blue;</tt></b><b><tt> GetScrRGBBitsPerPixel(&red, &green, &blue);</tt></b><b><tt> if (green == 32 && green == 32 && blue == 32) </tt></b><b><tt> /* 32*32*32 = 2^15 colors */</tt></b><b><tt> numOfBits = 15;</tt></b><b><tt> }</tt></b><b><tt> return numOfBits;</tt></b><b><tt>}</tt></b></pre> </td> </tr></table><p><br><font COLOR="#000000"><br>GetScrRGBBitsPerPixel() works just as described earlier, looping through 255 shades of gray to test the number of red, green, and blue values that the device supports:<br><br></font></p><table WIDTH="479" HEIGHT="787" BORDER="0" CELLSPACING="2" CELLPADDING="0"> <tr> <td WIDTH="100%"><pre><b><tt>BOOL GetScrRGBBitsPerPixel(WORD* pRedBits, </tt></b><b><tt> WORD* pGreenBits, </tt></b><b><tt> WORD* pBlueBits)</tt></b><b><tt>{ </tt></b><b><tt> BOOL isError = FALSE; </tt></b><b><tt> HDC screenDC, memDC; </tt></b><b><tt> HBITMAP bmp = NULL; </tt></b><b><tt> HBITMAP bmpOld = NULL; </tt></b><b><tt> </tt></b><b><tt> *pRedBits = *pGreenBits = *pBlueBits = 1; </tt></b><b><tt> screenDC = CreateDC("DISPLAY", NULL, NULL, NULL);</tt></b><b><tt> memDC = CreateCompatibleDC(NULL); </tt></b><b><tt> bmp = CreateCompatibleBitmap(screenDC, 1, 1);</tt></b><b><tt> isError = screenDC && memDC && bmp;</tt></b><b><tt> if (!isError)</tt></b><b><tt> goto CleanUp; </tt></b><b><tt> /* Sometimes a goto is a quick and readable </tt></b><b><tt> way to handle errors... */</tt></b><b><tt> bmpOld = (HBITMAP)SelectObject(memDC, bmp); </tt></b><b><tt> {</tt></b><b><tt> COLORREF oldColor;</tt></b><b><tt> COLORREF curColor = RGB(255, 255, 255); </tt></b><b><tt> int n;</tt></b><b><tt> for (n = 255; n >= 0; --n) </tt></b><b><tt> { </tt></b><b><tt> oldColor = curColor; </tt></b><b><tt> curColor = SetPixel(memDC, 0, 0, </tt></b><b><tt> RGB(n, n, n));</tt></b><b><tt> isError = curColor;</tt></b><b><tt> if (isError == CLR_INVALID)</tt></b><b><tt> {</tt></b><b><tt> isError = TRUE;</tt></b><b><tt> goto CleanUp; </tt></b><b><tt> }</tt></b><b><tt> /* Count R, G, & B perfect matches now */ </tt></b><b><tt> if (GetRValue(curColor) < GetRValue(oldColor))</tt></b><b><tt> ++(*pRedBits); </tt></b><b><tt> if (GetGValue(curColor) < GetGValue(oldColor))</tt></b><b><tt> ++(*pGreenBits); </tt></b><b><tt> if (GetBValue(curColor) < GetBValue(oldColor))</tt></b><b><tt> ++(*pBlueBits); </tt></b><b><tt> } </tt></b><b><tt> }</tt></b><b><tt> CleanUp: </tt></b><b><tt> if (bmpOld)</tt></b><b><tt> DeleteObject(bmpOld);</tt></b><b><tt> if (bmp)</tt></b><b><tt> DeleteObject(bmp);</tt></b><b><tt> if (isError)</tt></b><b><tt> *pRedBits = *pGreenBits = *pBlueBits = 0; </tt></b><b><tt> if (screenDC)</tt></b><b><tt> DeleteDC(screenDC);</tt></b><b><tt> if (memDC)</tt></b><b><tt> DeleteDC(memDC);</tt></b><b><tt> return !isError; </tt></b><b><tt>}</tt></b></pre> </td> </tr></table><p><br><font COLOR="#000000"><br>By the way, GetScrRGBBitsPerPixel() is not only the core of the workaround mechanism I've described above, but also a nice auxiliary function for determining how many red, green, and blue colors are available. For example, when there are really 16 bits of color, which color gets 6 bits, rather than the 5 the other two use? My tests show it's generally the green component that benefits.<br><br></font><b><font COLOR="#FF7317">The ScreenDetectSample application<br></font></b><font COLOR="#000000">To test the ScreenDetectLib library, I've written a sample application as a C console application called ScreenDetectSample. It simply shows the values returned by each of the functions and the number of bits per pixel returned by GetDeviceCaps(BITSPIXEL) for comparison purposes. </font><a HREF="screen1.gif">Figure 1</a><fontCOLOR="#FFFF00"> </font><font COLOR="#000000">shows the output on a 32K-color video mode. <br><br>The API function erroneously reports 16 bits-per-pixel while my library function (thanks to GetScrRGBBitsPerPixel() and its workaround) correctly reports 15 bits-per-pixel.<br><br></font><b><font COLOR="#FF7317">Conclusion<br></font></b><font COLOR="#000000">Windows is a very complex environment, and, like any complex environment, it has some inconsistencies that sometimes lead the programmer to confusion. Even tricky situations can often be solved by sitting down and thinking. Don't waste your efforts hitting the locked front door; an open window might be just beside you.<br><br></font><b><font COLOR="#FF7317">Download </font><a HREF="screen.zip">SCREEN.ZIP</a><fontCOLOR="#FF7317"> <br><br></font></b><i><font COLOR="#000000" SIZE="-1">Davide Marcato is a freelance international consultant in Windows and Internet development. He's a technical writer for several magazines in Italy and the United States. marcato@programmers.net, </font><font SIZE="-1"><aHREF="http://www.programmers.net/artic/Marcato">http://www.programmers.net/artic/Marcato</a></font><fontCOLOR="#000000" SIZE="-1">. </font></i></p></body></html>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -