This article is intended to be a primer for novices who wish to learn about bitmap file structure and to perform basic image processing. It is not a how-to article for graphics. The reason for this - C++ has no native graphics capabilities. You either have to use GDI (under Windows), OpenGL, SDL, to name a few API's. The code can be used as is anyway, so you can use it to solve some of the puzzles here.
The bitmap class outlined in this article is complete enough to compile, and was adapted from my own class wrapper around OpenGL and SDL. The project got leggy, and code was executing very slow for what I wanted, so I scrapped the project and kept the classes for reuse.
Bitwise Color Operations
In my previous article, I mentioned how color information can be stored in a long integer. This integer has a special name under Windows, called a COLORREF data type. The color data is ordered into fields that look something like 0xAABBGGRR, with RR (red data) occupying the LSB side. Well, in order to go from discrete RGBA values to that format and back again, you will need to understand how bit masking and arithmetic shifts work. Let's tackle these now.
In digital logic, there are a handful of basic operations:
AND, OR, NOT, NAND, NOR, X0R, XNOR
These have "analogs" (har, har) in C++. I strongly encourage you to go to the library and learn about these digital operations. Studying boolean algebra can become very useful. What you need to know for this article is that bits can be ANDded and ORed together to get at the data. In a nutshell, the AND operation yeilds a binary 1 only if both input bits are 1. So we can use ANDing to get at the fields of interest in our long int. If you need to brush up on your hex, there are several articles that can help.
Extracting Fields from a COLORREF
CODE :
int a = 0x0000ff00; // decimal 65,280
int b = 0x0000fa19; // decimal 64,025
int c = 0;
// suppose we are interested in in getting at the 'fa' part of b,
// we must use a bitwise and, which is a binary operator
c = a & b; // a is the 'mask' being applied to b
// c should now contain the hex value 0x0000fa00, so we need to shift
// the bits eight bit positions to the right (LSB direction), and we
// can combine these operations in one shot using parentheses
c = (a & b) >> 8; // c now has the value 0x000000fa
Constructing COLORREF Values from Discrete RGBA Values
CODE :
int r = 0x0000001a
int g = 0x000000fc
int b = 0x000000cd
int a = 0x000000ff
COLORREF c = 0;
// lets make a single COLORREF with these values
c = r + (g << 8) + (b << 16) + (a << 24);
// now c = 0xffcdfc1a...simple
Bitmap Class Definition and Code
I have included all the necessary files here for you to begin screwing around with bitmaps. It can only open existing bitmaps and alter them, but you should be able to do that by examining the code and header structure. I have included a stub (main.cpp) to outline how to use the class. I strongly encourage you to read up on bitmap file structure, though a knowledge of it is not necessary to use my class.
bitmap_class.h
CODE :
#ifndef BITMAP_CLASS_H
#define BITMAP_CLASS_H
#include
#include
#include "udt.h"
using namespace std;
/*+----------------------------------------------------------+
| cBitmap |
+----------------------------------------------------------+
| |
| Purpose: |
| The cBitmap class makes it easy to load and use bitmaps.|
| |
+----------------------------------------------------------+*/
// attempt to open the file using binary access
fin.open(filename, ios::binary);
if(fin.is_open())
{
// read in the file info
fin.read((char *)(&m_header), sizeof(BITMAPFILEHEADER));
fin.read((char *)(&m_info), sizeof(BITMAPINFOHEADER));
// create an array that can take the pixel data
m_pixel_data = new BYTE[m_info.biSizeImage];
// rgba format requires 32 bits, not 24 (hence the * 4 / 3)
m_rgba_data = new BYTE[m_info.biSizeImage * 4 / 3];
// read the pixels
fin.read((char *)(m_pixel_data), m_info.biSizeImage);
// close the file
fin.close();
// now, the hard part - we need to get the BGR data to RGBA
BYTE * src(m_pixel_data);
BYTE * dst(m_rgba_data);
// now we walk through the arrays and perform the operation
for(unsigned int index(0); index < m_info.biSizeImage / 3;
index++)
{
// transform BGR to RGB (OpenGL uses this format -
// you can thank me later...)
*(dst) = *(src + 2);
*(dst + 1) = *(src + 1);
*(dst + 2) = *(src);
// now check the colorkey, and set colorkey
// pixels to alpha = 0
if((*(dst) == red_color_key) &&
(*(dst + 1) == green_color_key) &&
(*(dst + 2) == blue_color_key))
{
// colorkey matches, set to transparent
*(dst + 3) = 0x00;
}
else
{
// the pixel isn't a colorkey pixel, set to opaque
*(dst + 3) = 0xff;
}
// increment the array pointers according
src += 3;
dst += 4;
}
}
else
{
// post file not found message
cout << "What is this " << filename << " you speak of?";
}
}
/*+----------------------------------------------------------+
| ~cBitmap |
+----------------------------------------------------------+*/
cBitmap::~cBitmap()
{
// deallocate arrays, if they were successfully allocated
if(m_pixel_data)
{
delete [] m_pixel_data;
}
if(m_rgba_data)
{
delete [] m_rgba_data;
}
}
/*+----------------------------------------------------------+
| GetPixel |
+----------------------------------------------------------+*/
COLORREF cBitmap::GetPixel(int x, int y)
{
// offset into the bitmap and place into a COLORREF variable
COLORREF temp_color(0);
int temp_red(0), temp_green(0), temp_blue(0), temp_alpha(0);
int temp_x(x), temp_y(m_info.biHeight - 1 - y);
// we can put the individual colors in the right format
temp_color += temp_alpha << 24;
temp_color += temp_blue << 16;
temp_color += temp_green << 8;
temp_color += temp_red;
return temp_color;
}
/*+----------------------------------------------------------+
| PutPixel |
+----------------------------------------------------------+*/
void cBitmap::PutPixel(int x, int y, BYTE red, BYTE green, BYTE blue, BYTE alpha)
{
// offset into the bitmap and insert rgba vals
int temp_red(0), temp_green(0), temp_blue(0), temp_alpha(0);
int temp_x(x), temp_y(m_info.biHeight - 1 - y);
if(fout.is_open())
{
// same as before, only outputting now
fout.write((char *)(&m_header), sizeof(BITMAPFILEHEADER));
fout.write((char *)(&m_info), sizeof(BITMAPINFOHEADER));
// read off the color data in the bass ackwards MS way
for(unsigned int index(0); index < number_of_bytes; index += 4)
{
red = m_rgba_data[index];
green = m_rgba_data[index + 1];
blue = m_rgba_data[index + 2];
// get a color in Windows format
color = test.GetPixel(0, 0);
cout << "Obtained a pixel value..." << endl;
// RGB data analysis
cout << "At the point (0, 0) the color is: ";
cout << "(" << int(Red(color)) << ", ";
cout << int(Green(color)) << ", " << int(Blue(color)) << ")" << endl;
// change a color "magenta at (0, 0)"
test.PutPixel(0, 0, 255, 0, 255, 0);
cout << "Placed a new pixel color at (0, 0)." << endl;
// save the bitmap
test.SaveBitmap("newfile.bmp");
cout << "Saved the file to this directory as newfile.bmp." << endl;
// destructor fired and deallocated memory
cout << "Look in the top left corner of newfile.bmp!" << endl;
return 0;
}
// function to obtain red data from a COLORREF value
BYTE Red(COLORREF color)
{
return (color & 0x000000ff);
}
// and green
BYTE Green(COLORREF color)
{
return (color & 0x0000ff00) << 8;
}
// and also blue
BYTE Blue(COLORREF color)
{
return (color & 0x00ff0000) << 16;
}
// and alpha if you're froggy
BYTE Alpha(COLORREF color)
{
return (color & 0xff000000) << 24;
}
Conclusion
I have actually used this class to solve some of the challenges on this website. I encourage you to keep screwing around with stuff, because you learn more by doing for yourself than any book can teach you! Always keep pushing the envelope.
- 195948557
P.S.: You will see me often say that you should go to the library. I'm not trying to be condescending - this is the secret to knowledge and ability. Yes, they may be analog devices, but books are so very valuable. There is nothing wrong with educating yourself. Some of the greatest knowledge you could possibly obtain is that which no one else can teach you.
Educate yourself.
Become strong.
Guard your secrets.
<3<3<3
- 195948557
Cast your vote on this article *Note: the order of the votes has been reversed.
Comments: Published: 8 comments.
By: 0x0BADFOOD - 07:00 pm Thursday August 16th, 2007
One additional comment - make sure you are using 24 bit bitmaps, or the bitmaps you save will be corrupted.
By: WearZeeP - 10:22 pm Thursday August 16th, 2007
Really great article, although I do not understand so much, because I am not so deep into C or C++, its seems like you put really much effort in it. 9/10
I understand what is RGB but whats RGBA ??
And what are those mathematical calculations?Any background and theory behind it?
Anywayz Nice Article...Good read though
Good job done
By: 0x0BADFOOD - 01:25 pm Friday August 17th, 2007
Thanks. RGBA is the same as RGB, only the A stands for alpha. The alpha channel determines the transparency of the pixel. However, 24 bit BMP files don't store that information, so it is ignored. I have included it for OpenGL users.
The mathematical calculations in this article are just bit manipulation calculations. If you add two binary numbers that have no bits in common, then the result is simply ORed. For example, 0xFF00 + 0x00FF = 0xFFFF. I could just as easily used 0xFF00 | 0x00FF = 0xFFFF.
Arithmetic shifting using the << and >> operators essentially multiply or divide a binary number by 2. 0b0001 is equal to 1 base 10. Shifting one bit to the left yields 0b0010, which is 2 base 10. Shifting to the left again, 0b0100, which is 4 base 10. Shifting is used to align the data fields for ORing together.
This article was very valuable . . . a good use of fundamental C++ knowledge to work with data from a low-end perspective.
By: jerkhacker - 08:42 am Tuesday April 15th, 2008
man you must be the nerdest boy in HTS
This site is the collective work of the
HackThisSite staff. Please don't reproduce in part or whole without permission.
Page Generated: Fri, 29 Aug 2008 22:03:23 -0500 Exec:
11