Date: | November 15, 2006 / year-entry #386 |
Tags: | code |
Orig Link: | https://blogs.msdn.microsoft.com/oldnewthing/20061115-01/?p=28993 |
Comments: | 17 |
Summary: | If you create a DIB section at 8bpp or lower, then it will come with a color table. Pixels in the bitmap are represented not by their red/blue/green component values, but are instead indices into the color table. For example, a 4bpp DIB section can have up to sixteen colors in its color table. Although... |
If you create a DIB section at 8bpp or lower, then it will come with a color table. Pixels in the bitmap are represented not by their red/blue/green component values, but are instead indices into the color table. For example, a 4bpp DIB section can have up to sixteen colors in its color table. Although displays that use 8bpp or lower are considered woefully outdated nowadays, bitmaps in that format are actually quite useful precisely due to the fact that you can manipulate colors in the bitmap, not by manipulating the bits themselves, but instead by manipulating the color table. Let's demonstrate this by taking the "Gone Fishing" bitmap and converting it to grayscale. Start with our scratch program and make these changes: HBITMAP g_hbm; BOOL OnCreate(HWND hwnd, LPCREATESTRUCT lpcs) { // change path as appropriate g_hbm = (HBITMAP)LoadImage(g_hinst, TEXT("C:\\Windows\\Gone Fishing.bmp"), IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION | LR_LOADFROMFILE); if (g_hbm) { HDC hdc = CreateCompatibleDC(NULL); if (hdc) { HBITMAP hbmPrev = SelectBitmap(hdc, g_hbm); RGBQUAD rgbColors[256]; UINT cColors = GetDIBColorTable(hdc, 0, 256, rgbColors); for (UINT iColor = 0; iColor < cColors; iColor++) { BYTE b = (BYTE)((30 * rgbColors[iColor].rgbRed + 59 * rgbColors[iColor].rgbGreen + 11 * rgbColors[iColor].rgbBlue) / 100); rgbColors[iColor].rgbRed = b; rgbColors[iColor].rgbGreen = b; rgbColors[iColor].rgbBlue = b; } SetDIBColorTable(hdc, 0, cColors, rgbColors); SelectBitmap(hdc, hbmPrev); DeleteDC(hdc); } } return TRUE; } void OnDestroy(HWND hwnd) { if (g_hbm) DeleteObject(g_hbm); PostQuitMessage(0); } void PaintContent(HWND hwnd, PAINTSTRUCT *pps) { if (g_hbm) { HDC hdc = CreateCompatibleDC(NULL); if (hdc) { HBITMAP hbmPrev = SelectBitmap(hdc, g_hbm); BITMAP bm; if (GetObject(g_hbm, sizeof(bm), &bm) == sizeof(bm)) { BitBlt(pps->hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdc, 0, 0, SRCCOPY); } SelectBitmap(hdc, hbmPrev); DeleteDC(hdc); } } }
The
First, we load the bitmap as a DIB section by passing the
Notice that we were able to change the color of every single
pixel in the bitmap by modifying just 1KB of data.
(Four bytes per
Manipulating the DIB color table is how flags like
You may also have noticed that |
Comments (17)
Comments are closed. |
Reminds me of the old 320x200x8bit VGA mode. Just under 64K of framebuffer RAM required, so even the woefully inadequate DOS programs could address the entire display using one segment (at segment:offset addresses A000:0000 through A000:ffff, IIRC). And it had the same 256-entry palette (though I think the palette was composed of three bytes per entry, not four).
And of course the same effects were possible; you could modify the palette instead of the display if you needed to do a global color change.
yeah remember how you used to program effects for games like this? Like making a waterfall look like it was flowing with palette rotation?
who could forget that nifty wormhole effect in FC’s Unreal? :)
It’s a shame that the .NET analogue (manipulating Bitmap.Palette) appears to be so horribly slow to retrieve and update; to the extent that it is significantly faster to just rewrite every pixel in a 32bpp Bitmap yourself than update the palette. (By significant, I mean reducing a "100%" CPU load and half the original framerate to about 10% – this is when called in a render loop).
Where did the magic numbers come from for converting to greyscale? Is that a “standard” set of values, or something you came up with?
Yes, palette rotations rocked. :) I remember many old games that used them.
Hey, shouldn’t this post have been obvious to everybody? I am still in College and I knew this. :-D
256 colors an portray a lot of information. As long as the material is not photographic (or photorealistic), it is enough for most images. One reason that GIFs are still around!
GregM: Check out http://www.bersoft.com/bimagem/help/color_channels.htm
"The coefficients 0.299, 0.587 and 0.114 properly computed luminance for monitors having phosphors that were contemporary at the introduction of NTSC television in 1953, however, these coefficients do not accurately compute luminance for contemporary monitors."
Well, it wasn’t just games (ultime underworld 2, clearly the best game of all time, used it for fires, magic effects, and other things).
Windows 95 used it too, on the loading screen. Remember the progress banner scrolly thingy at the bottom? Pallette rotated.
Which meant that you could create cool, slightly-animated loading screens :D
I remember all the cool kids had "The Matrix" animated Windows 95/98 load screens.
Raymond, you know very well that there are a bunch of unfortunates
out there who just copy & paste the first code they see off the
‘net, then you get the problems and write about how stupid they are
some months down the road. Can’t you at least set a good example by not
assuming that Windows lives in C:Windows, please?
assuming you’re smart enough to know that I’m hard-coding for
expository purposes, in the same way that I often ignore error checking
for the same reason. -Raymond]
BryanK: The stock 320×200×8bit MCGA/VGA video mode (0x13) used the address range from A000:0000 to A000:F9FF. The 256-color palette was set by writing directly to a couple of ports on the VGA. (But you had do it during vertical blank if you didn’t want any snow!) Each palette entry occupied three bytes, but only the lowest six bits of each byte were used. If you wanted to use a standard 24-bit palette, you would have to shift every byte two bits to the right before sending it to the VGA.
That was the basic stuff. The VGA could actually do modes better than 320×200×8bit, but there was no BIOS support for them, so you’d have to program them in yourself by manipulating the VGA registers directly. The famous "Mode X" (320×240×8bit) needed more than a segment of memory to be addressed, so to get it to work you’d have to "unchain" the VGA and access the frame buffer as four parallel planes. It was actually similar to working with the stock 4-bit EGA and VGA modes. (Little known fact: VGA always used planes internally; the elegant linear frame buffer in mode 0x13 was implemented as a clever hack.)
Good times, good times.
At some point, graphics cards with very slow palette switching started appearing, killing the great palette-rotation effects – the latest games using 256 colors usually included an option to disable it (StarCraft comes to mind, and Diablo got it added on a patch).
If I had to guess, perhaps the culprit cards stored always RGB and when changing a palette entry they rescaned the whole image- wait, that wouldn’t even work at all. Perhaps they stored two copys – RGB and indexes?
Ah — I knew about the "Mode X" stuff, but never learned how to work with it. Since it’s planar and not linear, it sounds like it’s harder to work with (from the program’s POV) than 0x13 was.
And right, it’s F9FF because it isn’t a full segment (64000 bytes, not 65536). Minor little details. ;-)
I do remember the VESA interface, though — where you had to map a 64K chunk of the full large-resolution linear framebuffer into a fixed 64K segment at a time. And if you had to draw across multiple chunks of the large framebuffer, you had to call into one of the VESA software interrupts to remap the 64K window. That was also a PITA.
Good times? Well, maybe… ;-)
Colorkeying = transparency
Alphablending = translucency
My old 286/12 with VGA could change 800k of palette entries per second (which was about ten colors per scanline, if I remember correctly). Same program on a P200 with PCI-card did write 1.2 MB/s. I would bet modern graphics cards isn’t much better.
> I’m assuming you’re smart enough to know that I’m hard-coding for expository purposes
I am, but then I wouldn’t do that (hard code the path) anyway. It is
the other n% of the software developing population you have to worry
about (those who, like I said, just copy & paste source off the
web).
hard-coded path to the Gone Fishing bitmap in their app. Unless they’re
really interested in Gone Fishing, I strongly suspect they’re going to
change the hard-coded path anyway. -Raymond]
Maybe I am just being picky today but shouldn’t you add 50 to make the B/W computation round off correctly, like this: (in theory you otherwise lose half a percent of luminance)
BYTE b = (BYTE)((30 * rgbColors[iColor].rgbRed +
59 * rgbColors[iColor].rgbGreen + 11 * rgbColors[iColor].rgbBlue + 50) / 100);
Rgrds Henry
PingBack from http://blogs.msdn.com/oldnewthing/archive/2007/02/05/1604637.aspx