Blitting between color and monochrome DCs

Date:November 14, 2006 / year-entry #384
Tags:code
Orig Link:https://blogs.msdn.microsoft.com/oldnewthing/20061114-01/?p=29013
Comments:    7
Summary:When blitting between color and monochrome DCs, The text foreground and background colors play a role. We saw earlier that when blitting from a monochrome DC to a color DC, the color black in the source turns into the destination's text color, and the color white in the source turns into the destination's background color....

When blitting between color and monochrome DCs, The text foreground and background colors play a role. We saw earlier that when blitting from a monochrome DC to a color DC, the color black in the source turns into the destination's text color, and the color white in the source turns into the destination's background color. This came in handy when we wanted to colorize a monochrome bitmap.

This trick works in reverse, too. If you blit from a color DC to a monochrome DC, then all pixels in the source that are equal to the background color will turn white, and all other pixels will turn black. In other words, GDI considers a monochrome bitmap to be black pixels on a white background.

This trick comes in handy when you want to convert a bitmap with color-keyed transparency into a color bitmap and a mask. Select the color bitmap into the DC hdcColor, and create a monochrome bitmap with the same dimensions and select it into the DC hdcMask. Then the following operations will construct the mask:

// let's say that the upper left pixel is the transparent color
COLORREF clrTransparent = GetPixel(hdcColor, 0, 0);
COLORREF clrBkPrev = SetBkColor(hdcColor, clrTransparent);
BitBlt(hdcMono, 0, 0, cx, cy, hdcColor, 0, 0, SRCCOPY);
SetBkColor(hdcColor, clrBkPrev);

We can see this in action in our scratch program by making the following changes:

void
PaintContent(HWND hwnd, PAINTSTRUCT *pps)
{
  
  HBITMAP hbmMono = CreateBitmap(100, 100, 1, 1, NULL);
  HDC hdcMono = CreateCompatibleDC(pps->hdc);
  HBITMAP hbmPrev = SelectBitmap(hdcMono, hbmMono);
  HDC hdcScreen = GetDC(NULL);

  SetBkColor(hdcScreen, GetSysColor(COLOR_DESKTOP));
  BitBlt(hdcMono, 0, 0, 100, 100, hdcScreen, 0, 0, SRCCOPY);

  SetTextColor(pps->hdc, RGB(0xFF,0,0));
  SetBkColor(pps->hdc, RGB(0,0x80,0));
  BitBlt(pps->hdc, 0, 0, 100, 100, hdcMono, 0, 0, SRCCOPY);

  ReleaseDC(NULL, hdcScreen);
  SelectBitmap(hdcMono, hbmPrev);
  DeleteDC(hdcMono);
  DeleteObject(hbmMono);
}

We start by creating a 100 × 100 monochrome bitmap and selecting it into a memory DC. This will become our mask. Next, we take a screen DC and set its background color to the desktop color and blit from the screen to the monochrome bitmap. This creates a bitmap which is white where the screen has the desktop color and black where the screen has some other color. We show off we show off this new bitmap by painting it into our client area, but just for fun, I made the foreground pixels (black pixels in the monochrome bitmap) bright red and the background pixels (white pixels in the monochrome bitmap) dark green.

Minimize your windows so the upper left corner of the desktop is visible, and turn off your wallpaper (so the desktop color actually means something). Run this program and observe a copy of your desktop drawn in the window's client area, but with your desktop color turned to green and all the other pixels turned to red.

The rest of the job of drawing a color bitmap with transparency is now comparatively straightforward. I'll leave it as an exercise. Hint: Raster operation 0x00220326 (DSna) will probably be handy.

Next time, we'll look at DIB sections as a way of doing fast color manipulation, thereby avoiding the need to perform the DSna ROP entirely.


Comments (7)
  1. Mike Dimmick says:

    I don’t normally complain about documentation but here it would have been pretty helpful to know that blitting from a monochrome DC to a colour one could be recoloured by setting the background and text colours of the destination DC. That information isn’t in MSDN, and I must have missed it when reading your blog. It isn’t even particularly clear from your linked article.

    Indeed, the naive expectation would be that it would use the original colours, i.e. white and black in the monochrome DC would be white and black in the destination DC. I’ll have to test it out on CE to see whether this bug was replicated over there ;)

    [I got the information from this MSDN article. It’s also mentioned incidentally in SetBkColor and SetTextColor. -Raymond]
  2. Bob Gray says:

    This may be slightly offtopic:

    If you were making some graphics-intensive win32 application, and had to use GDI/GDI+, would it be faster to perform things like alpha blended blits using GDI operations as you show here, or write some optimized code that does it all in one go and then displays the end result?

    Can GDI(+) take advantage of hardware accellerated 2d operations (and do any modern graphics chips even support that nowadays :))?

  3. Mike Dimmick says:

    Bob Gray: 2D accelerated operations are still around, but most modern graphics chips only support either 2D or 3D acceleration being enabled at one time. If, on Windows Vista, the Aero environment is currently turned on, the window surfaces (which are textures applied to the 3D model) are rendered in software with no 2D acceleration.

    GDI 2D acceleration is a back-and-forth process, where the device driver can register a function with GDI for performing particular operations. However, if it turns out that the requested operation is too complex, the driver can call back into GDI for GDI to perform part of the processing; GDI will then call back into the driver for suboperations of the main task. For example, the driver might register a callback with GDI for drawing a path, but it might only support simple paths. If GDI passes a complex path, the driver calls back into GDI, which then calls the driver to perform the next step of processing (which might be a textured line segment, for example).

    ClearType text rendering is, I suspect, all done by GDI – I don’t think 2D hardware is programmable enough to support the determination of the correct colours. The right colour for a pixel to get subpixel rendering right depends on both foreground and background colour, so you’d have to read back what the background colour is at that point.

    Depending on how the hardware and driver work, it might be quicker to work on the image in RAM rather than constantly transferring operations and data to the graphics card and having them processed there, and potentially having to transfer results back to the CPU. It depends whether the improvement in rendering time offsets the latency of getting data to and from the device.

  4. Mike Dimmick says:

    Annoyingly, the documentation for StretchBlt does in fact document the use of text and background colours, and did back in the October 2001 MSDN Library too.

  5. BryanK says:

    Mike, I’m not so sure that the hardware won’t support 2D and 3D acceleration at the same time.

    This may not be using the card’s 2D acceleration features, but the Linux nvidia drivers have a RenderAccel option to turn on acceleration of 2D stuff (the X11 "RENDER" extension).  I don’t know if this uses the card’s 2D acceleration or not, but since it’s there, and it is faster for some RENDER operations, I would assume it does.

    This option can be turned on at the same time as hardware OpenGL support.  And it even (seems to) work — I can run a (non-fullscreen) OpenGL program alongside a program using the RENDER extension, and as far as I can tell, they both get accelerated.

    Now maybe this depends more on the driver than the hardware.  It would also depend on the target of the 2D operations, which may explain why Aero does what you’re seeing.  Rendering OpenGL stuff to a texture hasn’t been well supported until very recently in nvidia’s driver (if it even works now; I guess I’m not sure whether it does), and the GLX texture_from_pixmap extension (to get the card to build an OpenGL texture from the contents of a window) is also quite new.

    In short: just because you can’t get the card to accelerate building a texture from a set of 2D operations doesn’t necessarily mean the card can’t do 2D and 3D acceleration at the same time.  I don’t know for sure that it can, though, either.

  6. Mike Dimmick says:

    “The background color is also used when converting bitmaps from color to monochrome and vice versa.” “The text color is also used in converting bitmaps from color to monochrome and vice versa.” That’s not exactly clear as to what happens.

    As for the other article you linked to, it’s in the archive section, it dates from 1992 and isn’t in the offline copy of MSDN Library from October 2001 that I would have been using when I originally wrote it (in 2002). A common problem with MSDN is not so much that the information isn’t there, it’s that you don’t know that it’s there. I say this as someone who has a pretty good memory, has thoroughly read “Programming Windows, Fifth Edition”, “Programming Applications for Windows, Fourth Edition”, “Programming Server-Side Applications for Windows”, “Windows Internals, Fourth Edition”, and used to read every issue of MSDN Magazine. Since I started my Windows programming career around 1999-2000, I simply didn’t see that article. I wouldn’t have described what I was doing as ‘transparency’ anyway, since I’m (potentially) changing *both* colours.

    I realise there’s a hard trade-off: you can’t link to every possible article that uses a given API, you have to be selective. I’d still classify this as ‘obscure’ and add some documentation to BitBlt (and presumably StretchBlt) to the effect that the current text and background colours are used when blitting a monochrome bitmap to a colour DC.

    [Monochrome/color conversion is also mentioned in the documentation of the BITMAP structure. I’m still not sure where the best place for this information is. Having to mention it in every Blt function is fragile. -Raymond]
  7. Mike Dimmick says:

    Well, whaddyaknow, it works on CE too. Thanks, that saves creating a couple of brushes and having to select one of them into the screen DC (which up until very recently was leaking a brush on every paint because I forgot to deselect it before deleting it and the DC – this code is double-buffered), and having to change brushes to change colours. Also saves having to explain a really obscure ternary raster op code.

    Now to have the argument about whether it’s worth making the change…

Comments are closed.


*DISCLAIMER: I DO NOT OWN THIS CONTENT. If you are the owner and would like it removed, please contact me. The content herein is an archived reproduction of entries from Raymond Chen's "Old New Thing" Blog (most recent link is here). It may have slight formatting modifications for consistency and to improve readability.

WHY DID I DUPLICATE THIS CONTENT HERE? Let me first say this site has never had anything to sell and has never shown ads of any kind. I have nothing monetarily to gain by duplicating content here. Because I had made my own local copy of this content throughout the years, for ease of using tools like grep, I decided to put it online after I discovered some of the original content previously and publicly available, had disappeared approximately early to mid 2019. At the same time, I present the content in an easily accessible theme-agnostic way.

The information provided by Raymond's blog is, for all practical purposes, more authoritative on Windows Development than Microsoft's own MSDN documentation and should be considered supplemental reading to that documentation. The wealth of missing details provided by this blog that Microsoft could not or did not document about Windows over the years is vital enough, many would agree an online "backup" of these details is a necessary endeavor. Specifics include:

<-- Back to Old New Thing Archive Index