Creating the byte array
This post describes a means of taking data in the form of raw pixels containing RGB values as well as the image height, width and the number of bits per pixel (24 in this case) and converting this into a bitmap (BMP) file.
Example Visual Studio 2010 project is downloadable from here:
www.technical-recipes.com/Downloads/BitmapRawPixels.zip
In this this post I originally described how I needed a means of representing binary array values (inkjet printhead nozzles turned ON/OFF) as a set of colored ‘squares’ or pixels, so that nozzles switched ON/OFF could be represented by two different colours – black or white. The bitmap width was always going to be 128, while the bitmap length (“SizeValue”) was one of a set of discrete set of values in the range { 1000, 1250, 1251, 1350 }. For this example I did not have to worry about padding additional values
Since then I have made some improvements to the original code sample, specifically if the image data happens to be not DWORD-aligned, then a new data array is created and padded such that there will be enough bytes to reach the next DWORD.
The original task was to create a one-dimensional array of bytes representing pixel colour data from the two-dimensional adjacency matrix provided. Each pixel value was then set to black or white, depending on the matrix array value:
BYTE* buf = new BYTE[ 128 * 3 * SizeValue ]; int c = 0; for ( int i = 0; i <; SizeValue; i++ ) { for ( int j = 0; j <; 128; j++ ) { unsigned char val = pmatrix[ i ][ j ] == 0 ? 0xFF : 0x00; buf[ c + 0 ] = (BYTE) val; buf[ c + 1 ] = (BYTE) val; buf[ c + 2 ] = (BYTE) val; c += 3; } } SaveBitmapToFile( (BYTE*) buf, 128, SizeValue, 24, "C:\\MyFolder\\image_created.bmp" ); delete [] buf;
Saving the byte data as a bitmap file
Writing the array data as a bitmap file is accomplished by the SaveBitmapToFile module. This module essentially:
i. initializes a BITMAPINFOHEADER data structure with bitmap parameters (header size, padding, height, width, etc)
ii. initializes a BITMAPFILEHEADER structure in the appropriate way
iii. Creates a file handler and writes the file, bitmap info and pixel data into it to create the new bitmap file representation:
Complete code listing
Here is the complete code listing to enable the user to:
1. Open an input bitmap file
2. Obtain the raw BYTE array, image height and image width from the input bitmap file
3. Create a new raw BYTE array, which will have space for additional padding if required
4. Save the new raw BYTE array to the new bitmap file.
#include <Windows.h> #include <algorithm> #include <memory> // Save the bitmap to a bmp file void SaveBitmapToFile( BYTE* pBitmapBits, LONG lWidth, LONG lHeight, WORD wBitsPerPixel, const unsigned long& padding_size, LPCTSTR lpszFileName ) { // Some basic bitmap parameters unsigned long headers_size = sizeof( BITMAPFILEHEADER ) + sizeof( BITMAPINFOHEADER ); unsigned long pixel_data_size = lHeight * ( ( lWidth * ( wBitsPerPixel / 8 ) ) + padding_size ); BITMAPINFOHEADER bmpInfoHeader = {0}; // Set the size bmpInfoHeader.biSize = sizeof(BITMAPINFOHEADER); // Bit count bmpInfoHeader.biBitCount = wBitsPerPixel; // Use all colors bmpInfoHeader.biClrImportant = 0; // Use as many colors according to bits per pixel bmpInfoHeader.biClrUsed = 0; // Store as un Compressed bmpInfoHeader.biCompression = BI_RGB; // Set the height in pixels bmpInfoHeader.biHeight = lHeight; // Width of the Image in pixels bmpInfoHeader.biWidth = lWidth; // Default number of planes bmpInfoHeader.biPlanes = 1; // Calculate the image size in bytes bmpInfoHeader.biSizeImage = pixel_data_size; BITMAPFILEHEADER bfh = {0}; // This value should be values of BM letters i.e 0x4D42 // 0x4D = M 0×42 = B storing in reverse order to match with endian bfh.bfType = 0x4D42; //bfh.bfType = 'B'+('M' << 8); // <<8 used to shift ‘M’ to end */ // Offset to the RGBQUAD bfh.bfOffBits = headers_size; // Total size of image including size of headers bfh.bfSize = headers_size + pixel_data_size; // Create the file in disk to write HANDLE hFile = CreateFile( lpszFileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL ); // Return if error opening file if( !hFile ) return; DWORD dwWritten = 0; // Write the File header WriteFile( hFile, &bfh, sizeof(bfh), &dwWritten , NULL ); // Write the bitmap info header WriteFile( hFile, &bmpInfoHeader, sizeof(bmpInfoHeader), &dwWritten, NULL ); // Write the RGB Data WriteFile( hFile, pBitmapBits, bmpInfoHeader.biSizeImage, &dwWritten, NULL ); // Close the file handle CloseHandle( hFile ); } BYTE* LoadBMP ( int* width, int* height, unsigned long* size, LPCTSTR bmpfile ) { BITMAPFILEHEADER bmpheader; BITMAPINFOHEADER bmpinfo; // value to be used in ReadFile funcs DWORD bytesread; // open file to read from HANDLE file = CreateFile ( bmpfile , GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL ); if ( NULL == file ) return NULL; if ( ReadFile ( file, &bmpheader, sizeof ( BITMAPFILEHEADER ), &bytesread, NULL ) == false ) { CloseHandle ( file ); return NULL; } // Read bitmap info if ( ReadFile ( file, &bmpinfo, sizeof ( BITMAPINFOHEADER ), &bytesread, NULL ) == false ) { CloseHandle ( file ); return NULL; } // check if file is actually a bmp if ( bmpheader.bfType != 'MB' ) { CloseHandle ( file ); return NULL; } // get image measurements *width = bmpinfo.biWidth; *height = abs ( bmpinfo.biHeight ); // Check if bmp iuncompressed if ( bmpinfo.biCompression != BI_RGB ) { CloseHandle ( file ); return NULL; } // Check if we have 24 bit bmp if ( bmpinfo.biBitCount != 24 ) { CloseHandle ( file ); return NULL; } // create buffer to hold the data *size = bmpheader.bfSize - bmpheader.bfOffBits; BYTE* Buffer = new BYTE[ *size ]; // move file pointer to start of bitmap data SetFilePointer ( file, bmpheader.bfOffBits, NULL, FILE_BEGIN ); // read bmp data if ( ReadFile ( file, Buffer, *size, &bytesread, NULL ) == false ) { delete [] Buffer; CloseHandle ( file ); return NULL; } // everything successful here: close file and return buffer CloseHandle ( file ); return Buffer; } std::unique_ptr<BYTE[]> CreateNewBuffer( unsigned long& padding, BYTE* pmatrix, const int& width, const int& height ) { padding = ( 4 - ( ( width * 3 ) % 4 ) ) % 4; int scanlinebytes = width * 3; int total_scanlinebytes = scanlinebytes + padding; long newsize = height * total_scanlinebytes; std::unique_ptr<BYTE[]> newbuf( new BYTE[ newsize ] ); // Fill new array with original buffer, pad remaining with zeros std::fill( &newbuf[ 0 ], &newbuf[ newsize ], 0 ); long bufpos = 0; long newpos = 0; for ( int y = 0; y < height; y++ ) { for ( int x = 0; x < 3 * width; x+=3 ) { // Determine positions in original and padded buffers bufpos = y * 3 * width + ( 3 * width - x ); newpos = ( height - y - 1 ) * total_scanlinebytes + x; // Swap R&B, G remains, swap B&R newbuf[ newpos ] = pmatrix[ bufpos + 2 ]; newbuf[ newpos + 1 ] = pmatrix[ bufpos + 1 ]; newbuf[ newpos + 2 ] = pmatrix[ bufpos ]; } } return newbuf; } int main() { int imageWidth = 0; int imageHeight = 0; unsigned long imageSize = 0; unsigned long padding = 0; // Load the bitmap file, amd put its data part into the BYTE array BYTE* bytes = LoadBMP( &imageWidth, &imageHeight, &imageSize, "C:\\MyStuff\\shaunak.BMP" ); std::reverse( bytes, bytes + imageSize ); // Determine amount of padding required, if any, and create a new BYTE array from this std::unique_ptr<BYTE[]> newbuf2 = CreateNewBuffer( padding, bytes, imageWidth, imageHeight ); // Use the new array data to create the new bitmap file SaveBitmapToFile( (BYTE*) &newbuf2[ 0 ], imageWidth, imageHeight, 24, padding, "C:\\MyStuff\\new_image.bmp" ); return 0; }
Example Usage
The program works by first loading the bitmap data from the bitmap file provided eg shaunak.BMP:
The program extracts the raw data from this and puts it into the buffer, example output of this buffer data:
It then creates the new BYTE array with any additional padding, if necessary, and the BYTE data is saved as a new bitmap file, as mentioned previously. Here it is saved as “new_image.bmp” and as expected is a duplicate of the original bitmap file:
Here is another example of its usage. Instead of obtaining raw bitmap data from an existing bitmap file, I create my own bitmap, which in this case is a simple blue square. I then call
SaveBitmapToFile
to convert this raw data to the bitmap file:
int main() { BYTE* buf = new BYTE[ 128 * 3 * 128 ]; int c = 0; for ( int i = 0; i < 128; i++ ) { for ( int j = 0; j < 128; j++ ) { buf[ c + 0 ] = (BYTE) 255; buf[ c + 1 ] = (BYTE) 0; buf[ c + 2 ] = (BYTE) 0; c += 3; } } SaveBitmapToFile( (BYTE*) buf, 128, 128, 24, 0, "C:\\MyFolder\\bluesquare.bmp" ); delete [] buf; return 0; }
And this is the bitmap image “bluesquare.bmp” created from the raw data: