Click here to Skip to main content
15,885,757 members
Articles / Programming Languages / C++
Tip/Trick

Replacing colour in Bitmap

Rate me:
Please Sign up or sign in to vote.
4.67/5 (2 votes)
20 May 2013CPOL4 min read 17.5K   349   2  
How to modify a bitmap in memory once it is read as a string.

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.

C++
HBITMAP CreateMemoryBitmap(const char* pBuffer,const COLORREF rgbMask)
{
    //go past tagBITMAPFILEHEADER , but can check bfType if you want
    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.

C++
int nPadding = (lpbih->biWidth * 3) % 4;
nPadding = nPadding ? (4 - nPadding) : nPadding;

Please refer to this source code for the function:

C++
//Modifies bitmap array by replacing transparancy colour
void Modify(HANDLE hbHandle,const COLORREF rgbColorMask)
{
    //BITMAPINFOHEADER follows BITMAPFILEHEADER is bmp file
    LPBITMAPINFOHEADER  lpbih = (LPBITMAPINFOHEADER)hbHandle ;
    //This can be done using GetRValue,GetGValue,GetBValue, too
    BYTE colorR = (BYTE)(rgbColorMask & 0xFF);
    BYTE colorG = (BYTE)((rgbColorMask & 0xff00) >> 8);
    BYTE colorB = (BYTE)((rgbColorMask & 0xff0000) >> 16);
    //Read the colour information, and depending
    //on bitcount decide the starting point of bitmap array
    if (( lpbih->biBitCount == 16) || (lpbih->biBitCount == 15))
    {
        //Convert each colour BYTE to hold information in 5 bit
        BYTE red  =  colorR >> 3 ;
        BYTE green  = colorG >>3 ;
        BYTE blue  = colorB >> 3;
        //And then make a WORD using them j = 0; 
        WORD rgbToReplaceWith = blue| (green << 5) | (red << 10);
        //Remember pixel is represented in WORD, so bitmap array is WORD array
        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) 
    {
        //just reverse colours
        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) 
    {
        //just reverse colours
        RGBTRIPLE rgbToReplaceWith ={colorB,colorG,colorR} ;
        RGBTRIPLE* ppvBits = (RGBTRIPLE*)(((LPSTR)lpbih + lpbih->biSize ));
        RGBTRIPLE refColor = ppvBits[0];
        //If width of bitmap is not divisible by 4, padding is used
        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) 
    {
        //Find index of color used for masking in color table
        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)
        {
            //Assign index of  obtained in above step to color to mask
            BYTE* ppvBits = (BYTE*)(((LPSTR)lpbih + lpbih->biSize + SizeOffSet(lpbih)));
            //Value stored is index of colour in colour table
            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)
    {
        //Find index of color used for masking in color table
        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)
        {
            //Each pixel is represented by 4 bit, one way
            //to traverse through it , is group two pixel in one BYTE
            //iterating through array, and split while checking/assigning value
            BYTE* ppvBits = (BYTE*)(((LPSTR)lpbih + lpbih->biSize + SizeOffSet(lpbih)));
            BYTE rgbToReplaceWith = ((BYTE*)(ppvBits))[0];
            //this will give index of colour in colour table for bottom-left pixel
            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) ;
                //Remember to add 1 extra iteration for odd width
                for (int j = 0 ; j <(( lpbih->biWidth / 2) + nOdd); j++)
                {
                    //check/assign first pixel in BYTE
                    //first 4-bit represent one pixel and next 4- bit represent another
                    if (((ppvBits1[j] & 0xF0) >> 4) == rgbToReplaceWith)
                    {
                        ppvBits1[j] &= 0xF;
                        ppvBits1[j] |= (BYTE)(nIndexOfMask << 4);
                    }
                    //check/assign second pixel in BYTE
                    if ((ppvBits1[j] & 0xF) == rgbToReplaceWith)
                    {
                        ppvBits1[j] &= 0xF0;
                        ppvBits1[j] |= (BYTE)(nIndexOfMask);
                    }
                }
            }
        }
    }
    else
    {
        //Not handling any other bit
        assert(0);
    }
}

About code sample

  1. This code sample handles only one type bitmap header. There are a few more.
  2. Doesn't cater for palettes.
  3. 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.

References

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
United Kingdom United Kingdom
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
-- There are no messages in this forum --