Introduction
This article demonstrates how to modify a bitmap in memory once it is read as a string.
Background
Recently I needed to handle a bitmap received as a string and use it in an application. It wasn't just simply initializing an HBITMAP
handle, but at the same time needed to replace the colour used as background, so that it appears transparent. There is loads of information about bitmaps all over the place. I came across an article written by Dimitri Rochette.
I used this article as a starting point which explains how you can replace a colour which is in 32 bit colour format. This article is an attempt to explain how you can manipulate a bitmap array for various colour format.
Using the code
The demo application is a simple Windows application which reads a bitmap file. The first pixel in the bitmap array is used as the transparency color. RGB(255,255,255) is the colour to replace with. There are two functions, CreateMemoryBitmap
which initializes the DIB from the string, and Modify
which replaces the colour.
HBITMAP CreateMemoryBitmap(const char* pBuffer,const COLORREF rgbMask)
{
HANDLE hbHandle = (HANDLE)(pBuffer+sizeof(tagBITMAPFILEHEADER));
Modify(hbHandle,rgbMask);
HDC hDC = GetDC(NULL);
LPBITMAPINFOHEADER lpbih;
lpbih = (LPBITMAPINFOHEADER)hbHandle;
int nSize = SizeOffSet(lpbih);
HBITMAP hBitmap = CreateDIBitmap(hDC,
lpbih,
CBM_INIT,
(LPSTR)lpbih + lpbih->biSize + nSize,
(LPBITMAPINFO)lpbih,
DIB_RGB_COLORS );
ReleaseDC(NULL,hDC);
return hBitmap;
}
Before creating the bitmap, the Modify
function replaces the colour in the bitmap. This function shows how a bitmap array holds information differently for different colour formats. It handles 4 bit, 8 bit, 15/16 bit, 24 bit and 32 bit colour bitmaps. Each pixel in the bitmap can either contain colour information or index of colour from the colour table. If a pixel has the actual colour value, then it's stored in BGR as opposed to what we normally use, RGB.
We will check bitcount one by one before seeing the function together.
32-bit colour
In 32-bit colour, 8 bits are used for each colour, and the leftmost 8 bits are used for alpha channel, which we will not discuss here. So in the bitmap array, each pixel is represented by a DWORD
(32-bit). Reversing RGB values for colour used to replace transparency colour will make it in BGR format.
24-bit colour
Color information is stored in 24-bits in this format. Each pixel is represented by RGBTRIPLE
. Depending on the width of the image, padding may have to be used at the end of each line. If width is a multiple of 4, no padding is required, otherwise at the end of each line we need to add a number which will make the width divisible by 4. E.g., if width is 16 or 32, no padding is done.
16-bit colour
Colour information is stored in 16 bits here. It's either 555 or 565 format. In the example given, it's assumed it's 555. That means, out of 16 bits, the rightmost 5 bits are used to represent Blue colour intensity. Then 5 bits represent green colour intensity, followed by 5 bits of Red colour. Each pixel is represented by WORD
(16 bit). Now the COLORREF
value is a 32 bit value, its colour information needs to be transferred into 16 bit. So right shift each BYTE
from COLORREF
value by 3, and then put these values together to form a WORD
.
8-bit colour
Colour information is stored in 8-bits, and it's actually the index of the colour for that pixel from the colour table. The colour table can have up to 256 entries. Need to go through the colour table to find the index of color to replace with. And then assign that index to the pixel to replace the colour.
4-bit colour
Colour information is stored in 4-bit, and associated colour table has up to 16 enttries. As with 8-bit colour, first check index of colour , used as colour to replace with. Here as each pixel is represented by 4 bit, for going through all of them just tie 2 pixels in 8 bit, and then split them for checking and assigning index.
Padding
Bitmap array is DWORD
aligned, hence we need to consider that during array iteration for bitmaps other than 32-bit colour. E.g., for 24-bit array each pixel is 3 bytes. So we need to calculate the padding depending on the bitmap width. If the bitmap width is 16, it takes 48 bytes, and it's a multiple of 4 and no padding is required. If bitmap width is 17, it takes 51 bytes, and requires a padding of 1 byte.
int nPadding = (lpbih->biWidth * 3) % 4;
nPadding = nPadding ? (4 - nPadding) : nPadding;
Please refer to this source code for the function:
void Modify(HANDLE hbHandle,const COLORREF rgbColorMask)
{
LPBITMAPINFOHEADER lpbih = (LPBITMAPINFOHEADER)hbHandle ;
BYTE colorR = (BYTE)(rgbColorMask & 0xFF);
BYTE colorG = (BYTE)((rgbColorMask & 0xff00) >> 8);
BYTE colorB = (BYTE)((rgbColorMask & 0xff0000) >> 16);
if (( lpbih->biBitCount == 16) || (lpbih->biBitCount == 15))
{
BYTE red = colorR >> 3 ;
BYTE green = colorG >>3 ;
BYTE blue = colorB >> 3;
WORD rgbToReplaceWith = blue| (green << 5) | (red << 10);
WORD* ppvBits = (WORD*)(((LPSTR)lpbih + lpbih->biSize + SizeOffSet(lpbih)));
WORD refColor = ppvBits[0];
int nPadding =( lpbih->biWidth%2)<<1;
for (int i= 0 ;i < lpbih->biHeight; i++)
{
ppvBits = (WORD*)((LPSTR)lpbih + lpbih->biSize + i * sizeof(WORD) *
lpbih->biWidth + i * nPadding) ;
for (int j = 0 ; j < lpbih->biWidth ; j++)
{
if (ppvBits[j] == refColor)
{
ppvBits[j] = rgbToReplaceWith;
}
}
}
}
else if (lpbih->biBitCount == 32)
{
DWORD rgbToReplaceWith = RGB(colorB,colorG,colorR);
DWORD* ppvBits = (DWORD*)(((LPSTR)lpbih + lpbih->biSize ));
DWORD refColor = ppvBits[0];
for (int i=((lpbih->biWidth*lpbih->biHeight)-1);i>=0;i--)
{
if (ppvBits[i] == refColor)
{
ppvBits[i] = rgbToReplaceWith;
}
}
}
else if (lpbih->biBitCount == 24)
{
RGBTRIPLE rgbToReplaceWith ={colorB,colorG,colorR} ;
RGBTRIPLE* ppvBits = (RGBTRIPLE*)(((LPSTR)lpbih + lpbih->biSize ));
RGBTRIPLE refColor = ppvBits[0];
int nPadding = (lpbih->biWidth * 3) % 4;
nPadding = nPadding ? (4 - nPadding) : nPadding;
for ( int i = 0 ; i < lpbih->biHeight ; i++)
{
ppvBits = (RGBTRIPLE*)((LPSTR)lpbih + lpbih->biSize + i *
sizeof(RGBTRIPLE) * lpbih->biWidth + i * nPadding) ;
for (int j = 0 ; j < lpbih->biWidth ; j++)
{
if ((ppvBits[j].rgbtBlue == refColor.rgbtBlue) &&
(ppvBits[j].rgbtGreen == refColor.rgbtGreen) &&
(ppvBits[j].rgbtRed == refColor.rgbtRed))
{
ppvBits[j]= rgbToReplaceWith;
}
}
}
}
else if (lpbih->biBitCount == 8)
{
RGBQUAD* rgb = (RGBQUAD*)((LPSTR)lpbih + lpbih->biSize);
int nIndexOfMask = -1;
int nNoOfColors = lpbih->biClrUsed ? lpbih->biClrUsed : 1 << lpbih->biBitCount;
for (int i = nNoOfColors -1; i >=0 ;i--)
{
RGBQUAD rgbVal = rgb[i];
if ((rgbVal.rgbBlue == colorB) &&
(rgbVal.rgbGreen == colorG) &&
(rgbVal.rgbRed == colorR))
{
nIndexOfMask = i;
break;
}
}
if (nIndexOfMask != -1)
{
BYTE* ppvBits = (BYTE*)(((LPSTR)lpbih + lpbih->biSize + SizeOffSet(lpbih)));
BYTE rgbToReplaceWith = ppvBits[0];
int nPadding = (lpbih->biWidth % 4);
nPadding = nPadding ? (4-nPadding) : nPadding;
for ( int i = 0 ; i < lpbih->biHeight ; i++)
{
BYTE* ppvBits1 = (BYTE*)(ppvBits + i * sizeof(BYTE) * lpbih->biWidth + i * nPadding) ;
for (int j = 0 ; j < lpbih->biWidth ; j++)
{
if (ppvBits1[j] == rgbToReplaceWith)
{
ppvBits1[j] = (BYTE)(nIndexOfMask);
}
}
}
}
}
else if (lpbih->biBitCount == 4)
{
RGBQUAD* rgb = (RGBQUAD*)((LPSTR)lpbih + lpbih->biSize);
int nIndexOfMask = -1;
int nNoOfColors = lpbih->biClrUsed ? lpbih->biClrUsed : 1 << lpbih->biBitCount;
for (int i = nNoOfColors -1; i >=0 ;i--)
{
RGBQUAD rgbVal = rgb[i];
if ((rgbVal.rgbBlue == colorB) &&
(rgbVal.rgbGreen == colorG) &&
(rgbVal.rgbRed == colorR))
{
nIndexOfMask = i;
break;
}
}
if (nIndexOfMask != -1)
{
BYTE* ppvBits = (BYTE*)(((LPSTR)lpbih + lpbih->biSize + SizeOffSet(lpbih)));
BYTE rgbToReplaceWith = ((BYTE*)(ppvBits))[0];
rgbToReplaceWith = (rgbToReplaceWith & 0xF0) >> 4;
int nOdd = lpbih->biWidth % 2;
int ByteRequired = lpbih->biWidth/2 + nOdd;
int nPadding = (ByteRequired % 4);
nPadding = nPadding ? (4-nPadding) : nPadding;
for ( int i = 0 ; i < lpbih->biHeight ; i++)
{
BYTE* ppvBits1 = (BYTE*)(ppvBits + i * ByteRequired + i * nPadding) ;
for (int j = 0 ; j <(( lpbih->biWidth / 2) + nOdd); j++)
{
if (((ppvBits1[j] & 0xF0) >> 4) == rgbToReplaceWith)
{
ppvBits1[j] &= 0xF;
ppvBits1[j] |= (BYTE)(nIndexOfMask << 4);
}
if ((ppvBits1[j] & 0xF) == rgbToReplaceWith)
{
ppvBits1[j] &= 0xF0;
ppvBits1[j] |= (BYTE)(nIndexOfMask);
}
}
}
}
}
else
{
assert(0);
}
}
About code sample
- This code sample handles only one type bitmap header. There are a few more.
- Doesn't cater for palettes.
- Modify code part which reads the bitmap from disk to read a bitmap you like. Currently code uses bottom-left pixel as the reference colour, which is replaced throughout the bitmap with white. Demo application is not very intuitive but should be able to get you through the code.