Display bitmap graphics on a microcontroller using C++ - c++
I'm using a bitmap 888 to 565 format in hex format.
So I'm trying to display the bitmap on a simulator that uses SDL, with frame buffer resoultion is 16bit.
one of the bitmap data ( first row ) looks like that
0x42, 0x4D, 0xFE, 0x82, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x36, 0x0, 0x0, 0x0, 0x28, 0x0, 0x0, 0x0, 0x7B, 0x0, 0x0, 0x0, 0x5A, 0x0, 0x0, 0x0, 0x1, 0x0, 0x18, 0x0, 0x0, 0x0,
Now I'm trying to draw that bitmap using C++ on SDL, but I get garbage image with scan lines, looks like the pitch is not calculated correctly.
void Rasterizer::DrawBitmap(int w, int h, int x, int y, int transparent)
{
if (!bitmap)
return;
const uint8_t bytesPerPixel = 2;
uint16_t bytesPerRow = (bytesPerPixel * h ); // bytes Per Row including padding to 4 byte row boundary
uint16_t paddingSize = bytesPerRow - (bytesPerPixel * w); // paddingSize for each row
uint16_t pixel;
uint16_t row, column;
for (row = 0; row < h; row++) {
for (column = 0; column < w; column++) {
pixel = bitmap[row + column* bytesPerRow]<<8;
pixel |= bitmap[1+row + column* bytesPerRow] & 0xFF;
SetPixel(x+column, y+row, pixel);
}
}
}
void Rasterizer::SetPixel(int x, int y, uint16_t color)
{
m_FrameBuffer[y * m_Width + x] = color;
}
0x42, 0x4D
The first 2 bytes are B and M, that's just the bitmap file header which is 54 bytes in total. It's not part of the first row.
The size is 0x7B x 0x5A pixels
Towards the end you have 0x18 0x00 which is 24, for 24-bit bitmap, not 16-bit
So you have to skip 54 byte, and read as 24-bit
int width_in_bytes = ((width * 24 + 31) / 32) * 4 * height;
for(int row = height - 1; row >= 0; row--)
{
for(int col = 0; col < width; col++)
{
int i = row * width_in_bytes + col * 3;
unsigned char blu = bitmap[54 + i + 0];
unsigned char grn = bitmap[54 + i + 1];
unsigned char red = bitmap[54 + i + 2];
int pixel = red | ((uint16_t)grn << 8) | ((uint32_t)blu << 16);
SetPixel(row, col, pixel);
}
}
If the device is expecting 16-bit bitmap, then try to obtain 16-bit bitmap in the first place. For example when taking screen shot, Windows allows 16-bit format.
SDL supports SDL_PIXELFORMAT_RGB565 as well. GDI+ is another option if you are coding in Windows.
If your source bitmap is 24-bit, and you want to convert to 16-bit 565 format, write the formula based on the MCVE below
24-bit bitmap has color range from 0-255, whereas 16-bit has color range from 0-31 (0-63 for green in the case of 565 format). You have to normalize the color, for example by multiplying the red value by 31/255. And then shift the values to put in 16-bit integer.
16-bit bitmap format expects 3 colors (a total of 12 bytes) before the pixels start. These colors contain information about 565 format.
#include <Windows.h>
#include <stdint.h>
#include <iostream>
#include <fstream>
#include <vector>
int main()
{
HBITMAP hbitmap = (HBITMAP)LoadImage(NULL, "24bit.bmp",
IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE | LR_CREATEDIBSECTION);
if(!hbitmap)
return 0;
BITMAP bm;
GetObject(hbitmap, sizeof(bm), &bm);
if(bm.bmBitsPixel != 24)
{
DeleteObject(hbitmap);
std::cout << "Expecting 24-bit bitmap\n";
return 0;
}
BYTE *source = (BYTE*)bm.bmBits;
int w = bm.bmWidth;
int h = bm.bmHeight;
//calculate width in bytes (wb) for source and destination
DWORD wb_src = ((w * 24 + 31) / 32) * 4;
DWORD wb_dst = ((w * 16 + 31) / 32) * 4;
int size = wb_dst * h;
std::vector<BYTE> dest(size);
for(int r = 0; r < h; r++)
{
for(int c = 0; c < w; c++)
{
int src = r * wb_src + c * 3;
int dst = r * wb_dst + c * 2;
uint16_t blu = (uint16_t)(source[src + 0] * 31.f / 255.f);
uint16_t grn = (uint16_t)(source[src + 1] * 63.f / 255.f);
uint16_t red = (uint16_t)(source[src + 2] * 31.f / 255.f);
uint16_t res = (red) | (grn << 5) | (blu << 11);
memcpy(&dest[dst], &res, 2);
}
}
//prepare header files for 16-bit file
BITMAPINFOHEADER bi = { sizeof(bi), w, h, 1, 16, BI_BITFIELDS };
BITMAPFILEHEADER bf = { (WORD)'MB', 54 + 12 + wb_dst * h, 0, 0, 54 };
std::ofstream of("16bit.bmp", std::ios::binary);
if(of)
{
//add file header
of.write((char*)&bf, sizeof(bf));
of.write((char*)&bi, sizeof(bi));
//color table
COLORREF c1 = 31;
COLORREF c2 = 63 << 5;
COLORREF c3 = 31 << 11;
of.write((char*)&c1, 4);
of.write((char*)&c2, 4);
of.write((char*)&c3, 4);
//add pixels
of.write((char*)&dest[0], dest.size());
}
DeleteObject(hbitmap);
return 0;
}
Related
Changing monochrome pattern for bitmap file using current loop
I am trying to change the pattern of the current bitmap file but I am having trouble changing my nested for loop to do this. I am trying to get a bitmap file with 8 horizontal bars, 32 pixels in height, alternating black and white. What I currently get now are 64 vertical bars alternating black and white. The bitmap image dimensions are 256 pixels by 256 pixels. I have messed around with the nested for loop in my code that is responsible for storing the color white (0x0f) in my multidimensional bits array. I have noticed that if I change the white to black (0x0f to 0x00) the entire bitmap file turns black. Below is the part of the code I am focusing on to output the pattern, under that I have the entire code. // Build monochrome array of bits in image for (int i = 0; i < IMAGE_SIZE; i++) { for (int j = 0; j < IMAGE_SIZE / 8; j++) { bits[i][j] = 0x0f; } } #include <iostream> #include <fstream> #include "windows.h" using namespace std; // The following defines the size of the square image in pixels. #define IMAGE_SIZE 256 int main(int argc, char* argv[]) { BITMAPFILEHEADER bmfh; BITMAPINFOHEADER bmih; char colorTable[8] = { 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff }; // The following defines the array which holds the image. The row length // (number of columns) is the height divided by 8, since there are 8 bits // in a byte. char bits[IMAGE_SIZE][IMAGE_SIZE / 8]; // Define and open the output file. ofstream bmpOut("foo.bmp", ios::out + ios::binary); if (!bmpOut) { cout << "...could not open file, ending."; return -1; } // Initialize the bit map file header with static values. bmfh.bfType = 0x4d42; bmfh.bfReserved1 = 0; bmfh.bfReserved2 = 0; bmfh.bfOffBits = sizeof(bmfh) + sizeof(bmih) + sizeof(colorTable); bmfh.bfSize = bmfh.bfOffBits + sizeof(bits); // Initialize the bit map information header with static values. bmih.biSize = 40; bmih.biWidth = IMAGE_SIZE; bmih.biHeight = IMAGE_SIZE; bmih.biPlanes = 1; bmih.biBitCount = 1; bmih.biCompression = 0; bmih.biSizeImage = 0; bmih.biXPelsPerMeter = 2835; // magic number, see Wikipedia entry bmih.biYPelsPerMeter = 2835; bmih.biClrUsed = 0; bmih.biClrImportant = 0; // Build monochrome array of bits in image for (int i = 0; i < IMAGE_SIZE; i++) { for (int j = 0; j < IMAGE_SIZE / 8; j++) { bits[i][j] = 0x0f; } } // Write out the bit map. char* workPtr; workPtr = (char*)&bmfh; bmpOut.write(workPtr, 14); workPtr = (char*)&bmih; bmpOut.write(workPtr, 40); workPtr = &colorTable[0]; bmpOut.write(workPtr, 8); workPtr = &bits[0][0]; bmpOut.write(workPtr, IMAGE_SIZE*IMAGE_SIZE / 8); bmpOut.close(); // showing result system("mspaint foo.bmp"); // Done. return 0; }
You are not populating your array of bits correctly. You are creating a 1-bit (monochrome) bottom-up bitmap. Every individual bit in your array represents a different pixel, where a 0 bit refers to the first color and a 1 bit refers to the second color in your color table. And the first row in the bitmap image is the last row in the array, and the last row in the bitmap image is the first row in the array. 0x0F hex is 00001111 binary. You are setting every 8-bit char in your array to 0x0f, so you are creating an alternating pattern of 4 0000 bits followed by 4 1111 bits for every row, eg: 00001111 00001111 00001111 ... (for 29 more bytes) 00001111 00001111 00001111 ... (for 29 more bytes) 00001111 00001111 00001111 ... (for 29 more bytes) ... (for 253 more rows) For a 256x256 bitmap, that produces 64 alternating vertical bars that are each 4 pixels wide. To get the effect you want - 8 alternating horizontal bars that are each 32 pixels high - you need to set every bit in a given row to either all 0s or all 1s, and then you need to alternate that pattern in groups of 32 complete rows, eg: 00000000 00000000 00000000 ... (for 29 more bytes) 00000000 00000000 00000000 ... (for 29 more bytes) 00000000 00000000 00000000 ... (for 29 more bytes) ... (for 29 more rows) 11111111 11111111 11111111 ... (for 29 more bytes) 11111111 11111111 11111111 ... (for 29 more bytes) 11111111 11111111 11111111 ... (for 29 more bytes) ... (for 29 more rows) ... (repeat the above 4 more times) Try something more like this instead: // Build monochrome array of bits in image bool isWhite = false; for (int i = 0; i < IMAGE_SIZE; ) { char ch = isWhite ? 0xFF : 0x00; int row = (IMAGE_SIZE - 1) - i; // use row = i for a top-down bitmap ... for (int col = 0; col < (IMAGE_SIZE / 8); ++col) { bits[row][col] = ch; } // alternatively to the above loop: // memset(bits[row], isWhite ? 0xFF : 0x00, IMAGE_SIZE / 8); if ((++i % 32) == 0) isWhite = !isWhite; } Or: // Build monochrome array of bits in image bool isWhite = true; for (int i = 0; i < IMAGE_SIZE; ++i) { if ((i % 32) == 0) isWhite = !isWhite; int row = (IMAGE_SIZE - 1) - i; // use row = i for a top-down bitmap ... char ch = isWhite ? 0xFF : 0x00; for (int col = 0; col < (IMAGE_SIZE / 8); ++col) { bits[row][col] = ch; } // alternatively to the above loop: // memset(bits[row], isWhite ? 0xFF : 0x00, IMAGE_SIZE / 8); } Or: // Build monochrome array of bits in image for (int i = 0; i < IMAGE_SIZE; ++i) { char ch = ((i % 64) < 32) ? 0x00 : 0xFF; int row = (IMAGE_SIZE - 1) - i; // use row = i for a top-down bitmap ... for (int col = 0; col < IMAGE_SIZE / 8; ++col) { bits[row][col] = ch; } // alternatively to the above loop: // memset(bits[row], ((i % 64) < 32) ? 0x00 : 0xFF, IMAGE_SIZE / 8); } That being said, I would suggest a few additional tweaks to the rest of your code: #include <iostream> #include <fstream> #include <windows.h> //#include <string.h> // if using memset() above... // The following defines the size of the square image in pixels. #define IMAGE_SIZE 256 // The following defines the size of each row in bytes. #define BYTES_PER_ROW (IMAGE_SIZE / sizeof(BYTE)) int main() { // Define and open the output file. std::ofstream bmpOut("foo.bmp", std::ios::binary); if (!bmpOut) { std::cerr << "could not open file, ending."; return -1; } BITMAPFILEHEADER bmfh; BITMAPINFOHEADER bmih; RGBQUAD colorTable[2] = { {0x00,0x00,0x00,0x00}, {0xFF,0xFF,0xFF,0x00} }; // The following defines the array which holds the image bits. The row length // (number of columns) is the height divided by 8, since there are 8 bits // in a byte. BYTE bits[IMAGE_SIZE][BYTES_PER_ROW]; // Initialize the bitmap file header with static values. bmfh.bfType = 0x4d42; bmfh.bfReserved1 = 0; bmfh.bfReserved2 = 0; bmfh.bfOffBits = sizeof(bmfh) + sizeof(bmih) + sizeof(colorTable); bmfh.bfSize = bmfh.bfOffBits + sizeof(bits); // Initialize the bitmap information header with static values. bmih.biSize = sizeof(bmih); bmih.biWidth = IMAGE_SIZE; bmih.biHeight = IMAGE_SIZE; // positive for bottom-up, negative for top-down bmih.biPlanes = 1; bmih.biBitCount = 1; bmih.biCompression = BI_RGB; bmih.biSizeImage = 0; bmih.biXPelsPerMeter = 2835; // magic number, see Wikipedia entry bmih.biYPelsPerMeter = 2835; bmih.biClrUsed = 0; bmih.biClrImportant = 0; // Build monochrome array of bits in image, see above... // Write out the bitmap. bmpOut.write(reinterpret_cast<char*>(&bmfh), sizeof(bmfh)); bmpOut.write(reinterpret_cast<char*>(&bmih), sizeof(bmih)); bmpOut.write(reinterpret_cast<char*>(&colorTable), sizeof(colorTable)); bmpOut.write(reinterpret_cast<char*>(&bits), sizeof(bits)); if (!bmpOut) { std::cerr << "could not write file, ending."; return -1; } bmpOut.close(); // showing result ShellExecuteA(NULL, NULL, "foo.bmp", NULL, NULL, SW_SHOW); // Done. return 0; }
Why is my bmp writer not working?
I'm trying to write a simple function that saves off some image data as bitmap. The data I'm generating is 3601 x 3601. I have verified in the debugger that all the data being passed to the function is correct. I have also verified that the pad size is as I would expect for a 24 bit BMP of width 3601 (padSize = 1). The bitmap this creates is considerably larger then a bitmap I create with paint of the same dimensions meaning something here is writing too much data. I hex compared this file to a similarly sized bitmap and the headers were identical so I don't think my problem is there. I think my write command on the charbuffer must be writing more then expected, but I can't figure out why. Any help is appreciated. void SaveBMPData(string fname, unsigned char *data, int imgWid, int imgHei) { ofstream file; file.open(fname.c_str(), std::ios::out); if (file.is_open()) { //string fname = "APnormal.bmp"; cout << "INFO >> Saving BMP data: " << fname << "." << endl; //write header unsigned char fileinfo[14] = { 'B', 'M', // magic 0, 0, 0, 0, // size in bytes 0, 0, // app data 0, 0, // app data 40 + 14, 0, 0, 0 // start of data offset }; unsigned char info[40] = { 40, 0, 0, 0, // info hd size 0, 0, 0, 0, // width 0, 0, 0, 0, // heigth 1, 0, // number color planes 24, 0, // bits per pixel 0, 0, 0, 0, // compression is none 0, 0, 0, 0, // image bits size 0x13, 0x0B, 0, 0, // horz resoluition in pixel / m 0x13, 0x0B, 0, 0, // vert resolutions (0x03C3 = 96 dpi, 0x0B13 = 72 dpi) 0, 0, 0, 0, // #colors in pallete 0, 0, 0, 0, // #important colors }; int w = imgWid; int h = imgHei; int padSize = (4 - ((w * 3) % 4)) % 4; int sizeData = w*h * 3 +h*padSize; int sizeAll = sizeData + sizeof(fileinfo) + sizeof(info); fileinfo[2] = (unsigned char)(sizeAll); fileinfo[3] = (unsigned char)(sizeAll >> 8); fileinfo[4] = (unsigned char)(sizeAll >> 16); fileinfo[5] = (unsigned char)(sizeAll >> 24); info[4] = (unsigned char)(w); info[5] = (unsigned char)(w >> 8); info[6] = (unsigned char)(w >> 16); info[7] = (unsigned char)(w >> 24); info[8] = (unsigned char) (h); info[9] = (unsigned char) (h >> 8); info[10] = (unsigned char)(h >> 16); info[11] = (unsigned char)(h >> 24); info[20] = (unsigned char)(sizeData); info[21] = (unsigned char)(sizeData >> 8); info[22] = (unsigned char)(sizeData >> 16); info[23] = (unsigned char)(sizeData >> 24); file.write((char*)fileinfo, sizeof(fileinfo)); file.write((char*)info, sizeof(info)); unsigned char *charbuffer = new unsigned char[w * 3 + padSize]; for (int y = 0; y<h; y++) { for (int x = 0; x<w; x++) { unsigned char red = data[(x + y*imgWid) * 3]; unsigned char green = data[(x + y*imgWid) * 3 + 1]; unsigned char blue = data[(x + y*imgWid) * 3 + 2]; charbuffer[x * 3 ] = blue; charbuffer[x * 3 + 1] = green; charbuffer[x * 3 + 2] = red; } file.write((char *)(void*)charbuffer, w*3+padSize); } file.close(); delete [] charbuffer; } else { cout << "can't read file.\n"; } }
You need to open the file in binary mode, otherwise any pixel component with a value of 0x0a will result in a write to the file of 0x0d 0x0a. file.open(fname.c_str(), std::ios::out | std::ios::binary);
Did you save the bitmap from paint as a 24 bit bmp? I would highly suggest getting some code on the internet such as here: http://tipsandtricks.runicsoft.com/Cpp/BitmapTutorial.html and looking at/using that code. I would avoid using arrays with values and comments (info, fileinfo) and use real concrete structs to define properties. Your problem should be easy to figure out but IMO you are better off using some code already written since this has been done so many times.
Create monochrome BMP from bitset
I could need some help to figure out how to feed the proc below. I need to write a monochrome BMP file. The code below (its from: How to Save monochrome Image as bmp in windows C++ ?) looks like to be able to do this. I'm now stuck on how to convert a std::bitset or preferably boost::dynamic_bitset into this byte* format. All of my attempts so far failed, I wasn't able to write something like an 8x8 checker pattern into the BMP. The proc creates the BMP and it is readable by Photoshop, but the content is a mess. So any suggestions how to solve this are appreciated! Save1BppImage(byte* ImageData, const char* filename, long w, long h){ int bitmap_dx = w; // Width of image int bitmap_dy = h; // Height of Image // create file std::ofstream file(filename, std::ios::binary | std::ios::trunc); if(!file) return; // save bitmap file headers BITMAPFILEHEADER fileHeader; BITMAPINFOHEADER * infoHeader; infoHeader = (BITMAPINFOHEADER*) malloc(sizeof(BITMAPINFOHEADER) ); RGBQUAD bl = {0,0,0,0}; //black color RGBQUAD wh = {0xff,0xff,0xff,0xff}; // white color fileHeader.bfType = 0x4d42; fileHeader.bfSize = 0; fileHeader.bfReserved1 = 0; fileHeader.bfReserved2 = 0; fileHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + (sizeof(BITMAPINFOHEADER)); infoHeader->biSize = (sizeof(BITMAPINFOHEADER) ); infoHeader->biWidth = bitmap_dx; infoHeader->biHeight = bitmap_dy; infoHeader->biPlanes = 1; infoHeader->biBitCount = 1; infoHeader->biCompression = BI_RGB; //no compression needed infoHeader->biSizeImage = 0; infoHeader->biXPelsPerMeter = 0; infoHeader->biYPelsPerMeter = 0; infoHeader->biClrUsed = 2; infoHeader->biClrImportant = 2; file.write((char*)&fileHeader, sizeof(fileHeader)); //write bitmapfileheader file.write((char*)infoHeader, (sizeof(BITMAPINFOHEADER) )); //write bitmapinfoheader file.write((char*)&bl,sizeof(bl)); //write RGBQUAD for black file.write((char*)&wh,sizeof(wh)); //write RGBQUAD for white int bytes = (w/8) * h ; //for example for 32X64 image = (32/8)bytes X 64 = 256; file.write((const char*)ImageData, bytes); file.close(); } -edit- an naive approach of mine was something like this byte test[64]; for(unsigned int i=0; i<64; ++i) if(i % 2) test[i] = 0; else test[i] = 1; Save1BppImage(test, "C:/bitmap.bmp", 8, 8);
The code you have is very close. Here are a few thoughts about where it might be off. The bfOffBits value must include the size of the palette. fileHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + (sizeof(BITMAPINFOHEADER)) + 2*sizeof(RGBQUAD); Some software may interpret 0 as white and 1 as black, regardless of what the palette says. Even though the file format allows you to go either way, you're better off specifying the palette in that order and inverting your bits if necessary. Each row of a bitmap will start on a 4-byte boundary. If your bitmap width isn't a multiple of 32, you're going to need some padding between each row. BMP files are ordered from the bottom row to the top row, which is backwards from the way most people organize their arrays. The last two recommendations are combined to look something like this: int bytes_in = (w + 7) / 8; int bytes_out = ((w + 31) / 32) * 4; const char * zeros[4] = {0, 0, 0, 0}; for (int y = h - 1; y >= 0; --y) { file.write(((const char *)ImageData) + (y * bytes_in), bytes_in); if (bytes_out != bytes_in) file.write(zeros, bytes_out - bytes_in); }
Just for the archive, below the working version. It takes a boost bitset as input pixel storage. void bitsetToBmp(boost::dynamic_bitset<unsigned char> bitset, const char* filename, int width, int height){ //write the bitset to file as 1-bit deep bmp //bit order 0...n equals image pixels top left...bottom right, row by row //the bitset must be at least the size of width*height, this is not checked std::ofstream file(filename, std::ios::binary | std::ios::trunc); if(!file) return; // save bitmap file headers BITMAPFILEHEADER fileHeader; BITMAPINFOHEADER * infoHeader; infoHeader = (BITMAPINFOHEADER*) malloc(sizeof(BITMAPINFOHEADER) ); RGBQUAD bl = {0,0,0,0}; //black color RGBQUAD wh = {0xff,0xff,0xff,0xff}; // white color fileHeader.bfType = 0x4d42; fileHeader.bfSize = 0; fileHeader.bfReserved1 = 0; fileHeader.bfReserved2 = 0; fileHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + (sizeof(BITMAPINFOHEADER)) + 2*sizeof(RGBQUAD); infoHeader->biSize = (sizeof(BITMAPINFOHEADER) ); infoHeader->biWidth = width; infoHeader->biHeight = height; infoHeader->biPlanes = 1; infoHeader->biBitCount = 1; infoHeader->biCompression = BI_RGB; //no compression needed infoHeader->biSizeImage = 0; infoHeader->biXPelsPerMeter = 0; infoHeader->biYPelsPerMeter = 0; infoHeader->biClrUsed = 2; infoHeader->biClrImportant = 2; file.write((char*)&fileHeader, sizeof(fileHeader)); //write bitmapfileheader file.write((char*)infoHeader, (sizeof(BITMAPINFOHEADER) )); //write bitmapinfoheader file.write((char*)&bl,sizeof(bl)); //write RGBQUAD for black file.write((char*)&wh,sizeof(wh)); //write RGBQUAD for white // convert the bits into bytes and write the file int offset, numBytes = ((width + 31) / 32) * 4; byte* bytes = (byte*) malloc(numBytes * sizeof(byte)); for(int y=height - 1; y>=0; --y){ offset = y * width; memset(bytes, 0, (numBytes * sizeof(byte))); for(int x=0; x<width; ++x) if(bitset[offset++]){ bytes[x / 8] |= 1 << (7 - x % 8); }; file.write((const char *)bytes, numBytes); }; free(bytes); file.close(); } I wonder if theres a simpler/faster way to put the bits into a file? The whole bitset could instead be overhanded as array of rows to skip the subset extraction.
I have something very similiar... This approach DOES NOT treat the padding of the BMP format. So You can only make bitmaps with width multiple of 4. This is NOT a monochromatic bitmap. It's a RGB format, but you can tune it easily. This is NOT an exactly answer to you, but for sure may be useful for you. Enjoy it. void createBitmap( byte * imageData, const char * filename, int width, int height ) { BITMAPFILEHEADER bitmapFileHeader; memset( &bitmapFileHeader, 0, sizeof( bitmapFileHeader ) ); bitmapFileHeader.bfType = ( 'B' | 'M' << 8 ); bitmapFileHeader.bfOffBits = sizeof( BITMAPFILEHEADER ) + sizeof( BITMAPINFOHEADER ); bitmapFileHeader.bfSize = bitmapFileHeader.bfOffBits + width * height * 3; BITMAPINFOHEADER bitmapInfoHeader; memset( &bitmapInfoHeader, 0, sizeof( bitmapInfoHeader ) ); bitmapInfoHeader.biSize = sizeof( BITMAPINFOHEADER ); bitmapInfoHeader.biWidth = width; bitmapInfoHeader.biHeight = height; bitmapInfoHeader.biPlanes = 1; bitmapInfoHeader.biBitCount = 24; std::ofstream file( filename, std::fstream::binary ); file.write( reinterpret_cast< char * >( &bitmapFileHeader ), sizeof( bitmapFileHeader ) ); file.write( reinterpret_cast< char * >( &bitmapInfoHeader ), sizeof( bitmapInfoHeader ) ); // the pixels! file.write( imageData, width * height * 3 ); file.close(); } int main( int argc, const char * argv[] ) { int width = 12; // multiple of 4 int height = 12; byte imageData[ width * height * 3 ]; // fill imageData the way you want, this is just a sample // on how to set the pixel at any specific (X,Y) position for ( int y = 0; y < height; ++y ) { for ( int x = 0; x < width; ++x ) { int pos = 3 * ( y * width + x ); byte pixelColor = ( x == 2 && y == 2 ) ? 0x00 : 0xff; imageData[ pos ] = pixelColor; imageData[ pos + 1 ] = pixelColor; imageData[ pos + 2 ] = pixelColor; } } createBitmap( imageData, "bitmap.bmp", width, height ); return 0; } In this sample we want a white bitmap with a single black pixel at position X = 2, Y = 2. The BMP format constiders that Y grows up from bottom to top. If you have a bitmap width a pixel per bit (the real monochromatic bitmap) you can test the bits and fill the imageData. To test a bit in a byte do like myByte >> position & 1 where position is the bit you wanna test from 0 to 7.
Load 24 bit TGA
I wrote a TGA loader to load TGA files. It loads and saves 32-bit TGA files just fine but when it comes to loading and saving 24-bit, it messed up. Example TGA 24-bit file from Photoshop: My output: Any idea what is wrong with it? I did the padding the same way as my bitmap loader and it works but the TGA doesn't.. :S The code below can compile and load TGA's just in case anyone is wanting to test it. #include <iostream> #include <vector> #include <stdexcept> #include <fstream> #include <cstring> typedef union RGB { std::uint32_t Color; struct { std::uint8_t B, G, R, A; } RGBA; } *PRGB; class Tga { private: std::vector<RGB> Pixels; bool ImageCompressed; std::uint32_t width, height, size, BitsPerPixel; public: Tga(const char* FilePath); void Save(const char* FilePath); }; Tga::Tga(const char* FilePath) { std::fstream hFile(FilePath, std::ios::in | std::ios::binary); if (!hFile.is_open()){throw std::invalid_argument("File Not Found.");} std::uint8_t Header[18] = {0}; std::vector<std::uint8_t> ImageData; static std::uint8_t DeCompressed[12] = {0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}; static std::uint8_t IsCompressed[12] = {0x0, 0x0, 0xA, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}; hFile.read(reinterpret_cast<char*>(&Header), sizeof(Header)); if (!std::memcmp(DeCompressed, &Header, sizeof(DeCompressed))) { BitsPerPixel = Header[16]; width = Header[13] * 0xFF + Header[12]; height = Header[15] * 0xFF + Header[14]; size = ((width * BitsPerPixel + 31) / 32) * 4 * height; if ((BitsPerPixel != 24) && (BitsPerPixel != 32)) { hFile.close(); throw std::invalid_argument("Invalid File Format. Required: 24 or 32 Bit Image."); } ImageData.resize(size); ImageCompressed = false; hFile.read(reinterpret_cast<char*>(ImageData.data()), size); } else if (!std::memcmp(IsCompressed, &Header, sizeof(IsCompressed))) { BitsPerPixel = Header[16]; width = Header[13] * 0xFF + Header[12]; height = Header[15] * 0xFF + Header[14]; size = ((width * BitsPerPixel + 31) / 32) * 4 * height; if ((BitsPerPixel != 24) && (BitsPerPixel != 32)) { hFile.close(); throw std::invalid_argument("Invalid File Format. Required: 24 or 32 Bit Image."); } RGB Pixel = {0}; int CurrentByte = 0; std::size_t CurrentPixel = 0; ImageCompressed = true; std::uint8_t ChunkHeader = {0}; int BytesPerPixel = (BitsPerPixel / 8); ImageData.resize(width * height * sizeof(RGB)); do { hFile.read(reinterpret_cast<char*>(&ChunkHeader), sizeof(ChunkHeader)); if(ChunkHeader < 128) { ++ChunkHeader; for(int I = 0; I < ChunkHeader; ++I, ++CurrentPixel) { hFile.read(reinterpret_cast<char*>(&Pixel), BytesPerPixel); ImageData[CurrentByte++] = Pixel.RGBA.B; ImageData[CurrentByte++] = Pixel.RGBA.G; ImageData[CurrentByte++] = Pixel.RGBA.R; if (BitsPerPixel > 24) ImageData[CurrentByte++] = Pixel.RGBA.A; } } else { ChunkHeader -= 127; hFile.read(reinterpret_cast<char*>(&Pixel), BytesPerPixel); for(int I = 0; I < ChunkHeader; ++I, ++CurrentPixel) { ImageData[CurrentByte++] = Pixel.RGBA.B; ImageData[CurrentByte++] = Pixel.RGBA.G; ImageData[CurrentByte++] = Pixel.RGBA.R; if (BitsPerPixel > 24) ImageData[CurrentByte++] = Pixel.RGBA.A; } } } while(CurrentPixel < (width * height)); } else { hFile.close(); throw std::invalid_argument("Invalid File Format. Required: 24 or 32 Bit TGA File."); } hFile.close(); std::uint8_t* BuffPos = ImageData.data(); Pixels.resize(width * height); //Flip the pixels and store them in my vector.. for (std::size_t I = 0; I < height; ++I) { for (std::size_t J = 0; J < width; ++J) { Pixels[(height - 1 - I) * width + J].RGBA.B = *(BuffPos++); Pixels[(height - 1 - I) * width + J].RGBA.G = *(BuffPos++); Pixels[(height - 1 - I) * width + J].RGBA.R = *(BuffPos++); Pixels[(height - 1 - I) * width + J].RGBA.A = (BitsPerPixel > 24 ? *(BuffPos++) : 0xFF); } if(BitsPerPixel == 24) BuffPos += (-width * 3) & 3; } } void Tga::Save(const char* FilePath) { std::fstream hFile(FilePath, std::ios::out | std::ios::binary); if (!hFile.is_open()) {throw std::invalid_argument("Cannot open file for writing.");} std::vector<std::uint8_t> ImageData(size); std::uint8_t* BuffPos = ImageData.data(); //Flip it back to how it was when we loaded it.. for (std::size_t I = 0; I < height; ++I) { for (std::size_t J = 0; J < width; ++J) { //Flip The ScanLines/Rows back to normal. *(BuffPos++) = Pixels[(height - 1 - I) * width + J].RGBA.B; *(BuffPos++) = Pixels[(height - 1 - I) * width + J].RGBA.G; *(BuffPos++) = Pixels[(height - 1 - I) * width + J].RGBA.R; if (BitsPerPixel > 24) *(BuffPos++) = Pixels[(height - 1 - I) * width + J].RGBA.A; } if(BitsPerPixel == 24) BuffPos += (-width * 3) & 3; } static std::uint8_t DeCompressed[12] = {0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}; static std::uint8_t IsCompressed[12] = {0x0, 0x0, 0xA, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}; if (!ImageCompressed) { hFile.write(reinterpret_cast<char*>(&DeCompressed), sizeof(DeCompressed)); hFile.put((width & 0xFF)); hFile.put((width & 0xFF) / 0xFF); hFile.put((height & 0xFF)); hFile.put(((height & 0xFF) / 0xFF)); hFile.put(BitsPerPixel); hFile.put(0x0); hFile.write(reinterpret_cast<char*>(ImageData.data()), ImageData.size()); hFile.close(); } else { hFile.write(reinterpret_cast<char*>(&IsCompressed), sizeof(IsCompressed)); hFile.put((width & 0xFF)); hFile.put((width & 0xFF) / 0xFF); hFile.put((height & 0xFF)); hFile.put(((height & 0xFF) / 0xFF)); hFile.put(BitsPerPixel); hFile.put(0x0); } hFile.close(); } int main() { }
Your code has for sure at least a couple of problems: width = Header[13] * 0xFF + Header[12]; this is not the correct way to read a two-bytes value... 0xFF is 255 and not 256: the correct way is width = (Header[13] << 8) + Header[12]; Your code has also a different problem in the same area when writing: hFile.put((width & 0xFF)); hFile.put((width & 0xFF) / 0xFF); the code is wrong (note that for example you only consider the low 8 bits of width). A correct version would be instead hFile.put(width & 0xFF); hFile.put((width >> 8) & 0xFF);
I think the problem you have is that you're assuming a TGA file is padded, and it isn't. So your buffers are the wrong size, and you index them wrongly. That you do so symmetrically for input and output means that it almost works, but the padding byte ends up in the image, which (as it's out by one byte per line) results in a diagonal stripe up the image, alternating through the colour channels. You reads will be returning less bytes than you expected, but you're not checking. (Though 6502 is completely correct about you handling 2-byte fields incorrectly - however this particular image is less than 255 pixels wide/high, so doesn't suffer).
Writing BMP image in pure c/c++ without other libraries
In my algorithm, I need to create an information output. I need to write a boolean matrix into a bmp file. It must be a monocromic image, where pixels are white if the matrix on such element is true. Main problem is the bmp header and how to write this.
See if this works for you... In this code, I had 3 2-dimensional arrays, called red,green and blue. Each one was of size [width][height], and each element corresponded to a pixel - I hope this makes sense! FILE *f; unsigned char *img = NULL; int filesize = 54 + 3*w*h; //w is your image width, h is image height, both int img = (unsigned char *)malloc(3*w*h); memset(img,0,3*w*h); for(int i=0; i<w; i++) { for(int j=0; j<h; j++) { x=i; y=(h-1)-j; r = red[i][j]*255; g = green[i][j]*255; b = blue[i][j]*255; if (r > 255) r=255; if (g > 255) g=255; if (b > 255) b=255; img[(x+y*w)*3+2] = (unsigned char)(r); img[(x+y*w)*3+1] = (unsigned char)(g); img[(x+y*w)*3+0] = (unsigned char)(b); } } unsigned char bmpfileheader[14] = {'B','M', 0,0,0,0, 0,0, 0,0, 54,0,0,0}; unsigned char bmpinfoheader[40] = {40,0,0,0, 0,0,0,0, 0,0,0,0, 1,0, 24,0}; unsigned char bmppad[3] = {0,0,0}; bmpfileheader[ 2] = (unsigned char)(filesize ); bmpfileheader[ 3] = (unsigned char)(filesize>> 8); bmpfileheader[ 4] = (unsigned char)(filesize>>16); bmpfileheader[ 5] = (unsigned char)(filesize>>24); bmpinfoheader[ 4] = (unsigned char)( w ); bmpinfoheader[ 5] = (unsigned char)( w>> 8); bmpinfoheader[ 6] = (unsigned char)( w>>16); bmpinfoheader[ 7] = (unsigned char)( w>>24); bmpinfoheader[ 8] = (unsigned char)( h ); bmpinfoheader[ 9] = (unsigned char)( h>> 8); bmpinfoheader[10] = (unsigned char)( h>>16); bmpinfoheader[11] = (unsigned char)( h>>24); f = fopen("img.bmp","wb"); fwrite(bmpfileheader,1,14,f); fwrite(bmpinfoheader,1,40,f); for(int i=0; i<h; i++) { fwrite(img+(w*(h-i-1)*3),3,w,f); fwrite(bmppad,1,(4-(w*3)%4)%4,f); } free(img); fclose(f);
Clean C Code for Bitmap (BMP) Image Generation This code does not use any library other than stdio.h. So, it can be easily incorporated in other languages of C-Family, like- C++, C#, Java. #include <stdio.h> const int BYTES_PER_PIXEL = 3; /// red, green, & blue const int FILE_HEADER_SIZE = 14; const int INFO_HEADER_SIZE = 40; void generateBitmapImage(unsigned char* image, int height, int width, char* imageFileName); unsigned char* createBitmapFileHeader(int height, int stride); unsigned char* createBitmapInfoHeader(int height, int width); int main () { int height = 361; int width = 867; unsigned char image[height][width][BYTES_PER_PIXEL]; char* imageFileName = (char*) "bitmapImage.bmp"; int i, j; for (i = 0; i < height; i++) { for (j = 0; j < width; j++) { image[i][j][2] = (unsigned char) ( i * 255 / height ); ///red image[i][j][1] = (unsigned char) ( j * 255 / width ); ///green image[i][j][0] = (unsigned char) ( (i+j) * 255 / (height+width) ); ///blue } } generateBitmapImage((unsigned char*) image, height, width, imageFileName); printf("Image generated!!"); } void generateBitmapImage (unsigned char* image, int height, int width, char* imageFileName) { int widthInBytes = width * BYTES_PER_PIXEL; unsigned char padding[3] = {0, 0, 0}; int paddingSize = (4 - (widthInBytes) % 4) % 4; int stride = (widthInBytes) + paddingSize; FILE* imageFile = fopen(imageFileName, "wb"); unsigned char* fileHeader = createBitmapFileHeader(height, stride); fwrite(fileHeader, 1, FILE_HEADER_SIZE, imageFile); unsigned char* infoHeader = createBitmapInfoHeader(height, width); fwrite(infoHeader, 1, INFO_HEADER_SIZE, imageFile); int i; for (i = 0; i < height; i++) { fwrite(image + (i*widthInBytes), BYTES_PER_PIXEL, width, imageFile); fwrite(padding, 1, paddingSize, imageFile); } fclose(imageFile); } unsigned char* createBitmapFileHeader (int height, int stride) { int fileSize = FILE_HEADER_SIZE + INFO_HEADER_SIZE + (stride * height); static unsigned char fileHeader[] = { 0,0, /// signature 0,0,0,0, /// image file size in bytes 0,0,0,0, /// reserved 0,0,0,0, /// start of pixel array }; fileHeader[ 0] = (unsigned char)('B'); fileHeader[ 1] = (unsigned char)('M'); fileHeader[ 2] = (unsigned char)(fileSize ); fileHeader[ 3] = (unsigned char)(fileSize >> 8); fileHeader[ 4] = (unsigned char)(fileSize >> 16); fileHeader[ 5] = (unsigned char)(fileSize >> 24); fileHeader[10] = (unsigned char)(FILE_HEADER_SIZE + INFO_HEADER_SIZE); return fileHeader; } unsigned char* createBitmapInfoHeader (int height, int width) { static unsigned char infoHeader[] = { 0,0,0,0, /// header size 0,0,0,0, /// image width 0,0,0,0, /// image height 0,0, /// number of color planes 0,0, /// bits per pixel 0,0,0,0, /// compression 0,0,0,0, /// image size 0,0,0,0, /// horizontal resolution 0,0,0,0, /// vertical resolution 0,0,0,0, /// colors in color table 0,0,0,0, /// important color count }; infoHeader[ 0] = (unsigned char)(INFO_HEADER_SIZE); infoHeader[ 4] = (unsigned char)(width ); infoHeader[ 5] = (unsigned char)(width >> 8); infoHeader[ 6] = (unsigned char)(width >> 16); infoHeader[ 7] = (unsigned char)(width >> 24); infoHeader[ 8] = (unsigned char)(height ); infoHeader[ 9] = (unsigned char)(height >> 8); infoHeader[10] = (unsigned char)(height >> 16); infoHeader[11] = (unsigned char)(height >> 24); infoHeader[12] = (unsigned char)(1); infoHeader[14] = (unsigned char)(BYTES_PER_PIXEL*8); return infoHeader; }
Without the use of any other library you can look at the BMP file format. I've implemented it in the past and it can be done without too much work. Bitmap-File Structures Each bitmap file contains a bitmap-file header, a bitmap-information header, a color table, and an array of bytes that defines the bitmap bits. The file has the following form: BITMAPFILEHEADER bmfh; BITMAPINFOHEADER bmih; RGBQUAD aColors[]; BYTE aBitmapBits[]; ... see the file format for more details
this is a example code copied from https://en.wikipedia.org/wiki/User:Evercat/Buddhabrot.c void drawbmp (char * filename) { unsigned int headers[13]; FILE * outfile; int extrabytes; int paddedsize; int x; int y; int n; int red, green, blue; extrabytes = 4 - ((WIDTH * 3) % 4); // How many bytes of padding to add to each // horizontal line - the size of which must // be a multiple of 4 bytes. if (extrabytes == 4) extrabytes = 0; paddedsize = ((WIDTH * 3) + extrabytes) * HEIGHT; // Headers... // Note that the "BM" identifier in bytes 0 and 1 is NOT included in these "headers". headers[0] = paddedsize + 54; // bfSize (whole file size) headers[1] = 0; // bfReserved (both) headers[2] = 54; // bfOffbits headers[3] = 40; // biSize headers[4] = WIDTH; // biWidth headers[5] = HEIGHT; // biHeight // Would have biPlanes and biBitCount in position 6, but they're shorts. // It's easier to write them out separately (see below) than pretend // they're a single int, especially with endian issues... headers[7] = 0; // biCompression headers[8] = paddedsize; // biSizeImage headers[9] = 0; // biXPelsPerMeter headers[10] = 0; // biYPelsPerMeter headers[11] = 0; // biClrUsed headers[12] = 0; // biClrImportant outfile = fopen(filename, "wb"); // // Headers begin... // When printing ints and shorts, we write out 1 character at a time to avoid endian issues. // fprintf(outfile, "BM"); for (n = 0; n <= 5; n++) { fprintf(outfile, "%c", headers[n] & 0x000000FF); fprintf(outfile, "%c", (headers[n] & 0x0000FF00) >> 8); fprintf(outfile, "%c", (headers[n] & 0x00FF0000) >> 16); fprintf(outfile, "%c", (headers[n] & (unsigned int) 0xFF000000) >> 24); } // These next 4 characters are for the biPlanes and biBitCount fields. fprintf(outfile, "%c", 1); fprintf(outfile, "%c", 0); fprintf(outfile, "%c", 24); fprintf(outfile, "%c", 0); for (n = 7; n <= 12; n++) { fprintf(outfile, "%c", headers[n] & 0x000000FF); fprintf(outfile, "%c", (headers[n] & 0x0000FF00) >> 8); fprintf(outfile, "%c", (headers[n] & 0x00FF0000) >> 16); fprintf(outfile, "%c", (headers[n] & (unsigned int) 0xFF000000) >> 24); } // // Headers done, now write the data... // for (y = HEIGHT - 1; y >= 0; y--) // BMP image format is written from bottom to top... { for (x = 0; x <= WIDTH - 1; x++) { red = reduce(redcount[x][y] + COLOUR_OFFSET) * red_multiplier; green = reduce(greencount[x][y] + COLOUR_OFFSET) * green_multiplier; blue = reduce(bluecount[x][y] + COLOUR_OFFSET) * blue_multiplier; if (red > 255) red = 255; if (red < 0) red = 0; if (green > 255) green = 255; if (green < 0) green = 0; if (blue > 255) blue = 255; if (blue < 0) blue = 0; // Also, it's written in (b,g,r) format... fprintf(outfile, "%c", blue); fprintf(outfile, "%c", green); fprintf(outfile, "%c", red); } if (extrabytes) // See above - BMP lines must be of lengths divisible by 4. { for (n = 1; n <= extrabytes; n++) { fprintf(outfile, "%c", 0); } } } fclose(outfile); return; } drawbmp(filename);
Here is a C++ variant of the code that works for me. Note I had to change the size computation to account for the line padding. // mimeType = "image/bmp"; unsigned char file[14] = { 'B','M', // magic 0,0,0,0, // size in bytes 0,0, // app data 0,0, // app data 40+14,0,0,0 // start of data offset }; unsigned char info[40] = { 40,0,0,0, // info hd size 0,0,0,0, // width 0,0,0,0, // heigth 1,0, // number color planes 24,0, // bits per pixel 0,0,0,0, // compression is none 0,0,0,0, // image bits size 0x13,0x0B,0,0, // horz resoluition in pixel / m 0x13,0x0B,0,0, // vert resolutions (0x03C3 = 96 dpi, 0x0B13 = 72 dpi) 0,0,0,0, // #colors in pallete 0,0,0,0, // #important colors }; int w=waterfallWidth; int h=waterfallHeight; int padSize = (4-(w*3)%4)%4; int sizeData = w*h*3 + h*padSize; int sizeAll = sizeData + sizeof(file) + sizeof(info); file[ 2] = (unsigned char)( sizeAll ); file[ 3] = (unsigned char)( sizeAll>> 8); file[ 4] = (unsigned char)( sizeAll>>16); file[ 5] = (unsigned char)( sizeAll>>24); info[ 4] = (unsigned char)( w ); info[ 5] = (unsigned char)( w>> 8); info[ 6] = (unsigned char)( w>>16); info[ 7] = (unsigned char)( w>>24); info[ 8] = (unsigned char)( h ); info[ 9] = (unsigned char)( h>> 8); info[10] = (unsigned char)( h>>16); info[11] = (unsigned char)( h>>24); info[20] = (unsigned char)( sizeData ); info[21] = (unsigned char)( sizeData>> 8); info[22] = (unsigned char)( sizeData>>16); info[23] = (unsigned char)( sizeData>>24); stream.write( (char*)file, sizeof(file) ); stream.write( (char*)info, sizeof(info) ); unsigned char pad[3] = {0,0,0}; for ( int y=0; y<h; y++ ) { for ( int x=0; x<w; x++ ) { long red = lround( 255.0 * waterfall[x][y] ); if ( red < 0 ) red=0; if ( red > 255 ) red=255; long green = red; long blue = red; unsigned char pixel[3]; pixel[0] = blue; pixel[1] = green; pixel[2] = red; stream.write( (char*)pixel, 3 ); } stream.write( (char*)pad, padSize ); }
Note that the lines are saved from down to up and not the other way around. Additionally, the scanlines must have a byte-length of multiples of four, you should insert fill bytes at the end of the lines to ensure this.
I just wanted to share an improved version of Minhas Kamal's code because although it worked well enough for most applications, I had a few issues with it still. Two highly important things to remember: The code (at the time of writing) calls free() on two static arrays. This will cause your program to crash. So I commented out those lines. NEVER assume that your pixel data's pitch is always (Width*BytesPerPixel). It's best to let the user specify the pitch value. Example: when manipulating resources in Direct3D, the RowPitch is never guaranteed to be an even multiple of the byte depth being used. This can cause errors in your generated bitmaps (especially at odd resolutions such as 1366x768). Below, you can see my revisions to his code: const int bytesPerPixel = 4; /// red, green, blue const int fileHeaderSize = 14; const int infoHeaderSize = 40; void generateBitmapImage(unsigned char *image, int height, int width, int pitch, const char* imageFileName); unsigned char* createBitmapFileHeader(int height, int width, int pitch, int paddingSize); unsigned char* createBitmapInfoHeader(int height, int width); void generateBitmapImage(unsigned char *image, int height, int width, int pitch, const char* imageFileName) { unsigned char padding[3] = { 0, 0, 0 }; int paddingSize = (4 - (/*width*bytesPerPixel*/ pitch) % 4) % 4; unsigned char* fileHeader = createBitmapFileHeader(height, width, pitch, paddingSize); unsigned char* infoHeader = createBitmapInfoHeader(height, width); FILE* imageFile = fopen(imageFileName, "wb"); fwrite(fileHeader, 1, fileHeaderSize, imageFile); fwrite(infoHeader, 1, infoHeaderSize, imageFile); int i; for (i = 0; i < height; i++) { fwrite(image + (i*pitch /*width*bytesPerPixel*/), bytesPerPixel, width, imageFile); fwrite(padding, 1, paddingSize, imageFile); } fclose(imageFile); //free(fileHeader); //free(infoHeader); } unsigned char* createBitmapFileHeader(int height, int width, int pitch, int paddingSize) { int fileSize = fileHeaderSize + infoHeaderSize + (/*bytesPerPixel*width*/pitch + paddingSize) * height; static unsigned char fileHeader[] = { 0,0, /// signature 0,0,0,0, /// image file size in bytes 0,0,0,0, /// reserved 0,0,0,0, /// start of pixel array }; fileHeader[0] = (unsigned char)('B'); fileHeader[1] = (unsigned char)('M'); fileHeader[2] = (unsigned char)(fileSize); fileHeader[3] = (unsigned char)(fileSize >> 8); fileHeader[4] = (unsigned char)(fileSize >> 16); fileHeader[5] = (unsigned char)(fileSize >> 24); fileHeader[10] = (unsigned char)(fileHeaderSize + infoHeaderSize); return fileHeader; } unsigned char* createBitmapInfoHeader(int height, int width) { static unsigned char infoHeader[] = { 0,0,0,0, /// header size 0,0,0,0, /// image width 0,0,0,0, /// image height 0,0, /// number of color planes 0,0, /// bits per pixel 0,0,0,0, /// compression 0,0,0,0, /// image size 0,0,0,0, /// horizontal resolution 0,0,0,0, /// vertical resolution 0,0,0,0, /// colors in color table 0,0,0,0, /// important color count }; infoHeader[0] = (unsigned char)(infoHeaderSize); infoHeader[4] = (unsigned char)(width); infoHeader[5] = (unsigned char)(width >> 8); infoHeader[6] = (unsigned char)(width >> 16); infoHeader[7] = (unsigned char)(width >> 24); infoHeader[8] = (unsigned char)(height); infoHeader[9] = (unsigned char)(height >> 8); infoHeader[10] = (unsigned char)(height >> 16); infoHeader[11] = (unsigned char)(height >> 24); infoHeader[12] = (unsigned char)(1); infoHeader[14] = (unsigned char)(bytesPerPixel * 8); return infoHeader; }
I edited ralf's htp code so that it would compile (on gcc, running ubuntu 16.04 lts). It was just a matter of initializing the variables. int w = 100; /* Put here what ever width you want */ int h = 100; /* Put here what ever height you want */ int red[w][h]; int green[w][h]; int blue[w][h]; FILE *f; unsigned char *img = NULL; int filesize = 54 + 3*w*h; //w is your image width, h is image height, both int if( img ) free( img ); img = (unsigned char *)malloc(3*w*h); memset(img,0,sizeof(img)); int x; int y; int r; int g; int b; for(int i=0; i<w; i++) { for(int j=0; j<h; j++) { x=i; y=(h-1)-j; r = red[i][j]*255; g = green[i][j]*255; b = blue[i][j]*255; if (r > 255) r=255; if (g > 255) g=255; if (b > 255) b=255; img[(x+y*w)*3+2] = (unsigned char)(r); img[(x+y*w)*3+1] = (unsigned char)(g); img[(x+y*w)*3+0] = (unsigned char)(b); } } unsigned char bmpfileheader[14] = {'B','M', 0,0,0,0, 0,0, 0,0, 54,0,0,0}; unsigned char bmpinfoheader[40] = {40,0,0,0, 0,0,0,0, 0,0,0,0, 1,0, 24,0}; unsigned char bmppad[3] = {0,0,0}; bmpfileheader[ 2] = (unsigned char)(filesize ); bmpfileheader[ 3] = (unsigned char)(filesize>> 8); bmpfileheader[ 4] = (unsigned char)(filesize>>16); bmpfileheader[ 5] = (unsigned char)(filesize>>24); bmpinfoheader[ 4] = (unsigned char)( w ); bmpinfoheader[ 5] = (unsigned char)( w>> 8); bmpinfoheader[ 6] = (unsigned char)( w>>16); bmpinfoheader[ 7] = (unsigned char)( w>>24); bmpinfoheader[ 8] = (unsigned char)( h ); bmpinfoheader[ 9] = (unsigned char)( h>> 8); bmpinfoheader[10] = (unsigned char)( h>>16); bmpinfoheader[11] = (unsigned char)( h>>24); f = fopen("img.bmp","wb"); fwrite(bmpfileheader,1,14,f); fwrite(bmpinfoheader,1,40,f); for(int i=0; i<h; i++) { fwrite(img+(w*(h-i-1)*3),3,w,f); fwrite(bmppad,1,(4-(w*3)%4)%4,f); } fclose(f);
The best bitmap encoder is the one you do not write yourself. The file format is a lot more involved, than one might expect. This is evidenced by the fact, that all proposed answers do not create a monochrome (1bpp) bitmap, but rather write out 24bpp files, that happen to only use 2 colors. The following is a Windows-only solution, using the Windows Imaging Component. It doesn't rely on any external/3rd party libraries, other than what ships with Windows. Like every C++ program, we need to include several header files. And link to Windowscodecs.lib while we're at it: #include <Windows.h> #include <comdef.h> #include <comip.h> #include <comutil.h> #include <wincodec.h> #include <vector> #pragma comment(lib, "Windowscodecs.lib") Next up, we declare our container (a vector, of vectors! Of bool!), and a few smart pointers for convenience: using _com_util::CheckError; using container = std::vector<std::vector<bool>>; _COM_SMARTPTR_TYPEDEF(IWICImagingFactory, __uuidof(IWICImagingFactory)); _COM_SMARTPTR_TYPEDEF(IWICBitmapEncoder, __uuidof(IWICBitmapEncoder)); _COM_SMARTPTR_TYPEDEF(IWICBitmapFrameEncode, __uuidof(IWICBitmapFrameEncode)); _COM_SMARTPTR_TYPEDEF(IWICStream, __uuidof(IWICStream)); _COM_SMARTPTR_TYPEDEF(IWICPalette, __uuidof(IWICPalette)); With that all settled, we can jump right into the implementation. There's a bit of setup required to get a factory, an encoder, a frame, and get everything prepared: void write_bitmap(wchar_t const* pathname, container const& data) { // Create factory IWICImagingFactoryPtr sp_factory { nullptr }; CheckError(sp_factory.CreateInstance(CLSID_WICImagingFactory, nullptr, CLSCTX_INPROC_SERVER)); // Create encoder IWICBitmapEncoderPtr sp_encoder { nullptr }; CheckError(sp_factory->CreateEncoder(GUID_ContainerFormatBmp, nullptr, &sp_encoder)); // Create stream IWICStreamPtr sp_stream { nullptr }; CheckError(sp_factory->CreateStream(&sp_stream)); CheckError(sp_stream->InitializeFromFilename(pathname, GENERIC_WRITE)); // Initialize encoder with stream CheckError(sp_encoder->Initialize(sp_stream, WICBitmapEncoderNoCache)); // Create new frame IWICBitmapFrameEncodePtr sp_frame { nullptr }; IPropertyBag2Ptr sp_properties { nullptr }; CheckError(sp_encoder->CreateNewFrame(&sp_frame, &sp_properties)); // Initialize frame with default properties CheckError(sp_frame->Initialize(sp_properties)); // Set pixel format // SetPixelFormat() requires a pointer to non-const auto pf { GUID_WICPixelFormat1bppIndexed }; CheckError(sp_frame->SetPixelFormat(&pf)); if (!::IsEqualGUID(pf, GUID_WICPixelFormat1bppIndexed)) { // Report unsupported pixel format CheckError(WINCODEC_ERR_UNSUPPORTEDPIXELFORMAT); } // Set size derived from data argument auto const width { static_cast<UINT>(data.size()) }; auto const height { static_cast<UINT>(data[0].size()) }; CheckError(sp_frame->SetSize(width, height)); // Set palette on frame. This is required since we use an indexed pixel format. // Only GIF files support global palettes, so make sure to set it on the frame // rather than the encoder. IWICPalettePtr sp_palette { nullptr }; CheckError(sp_factory->CreatePalette(&sp_palette)); CheckError(sp_palette->InitializePredefined(WICBitmapPaletteTypeFixedBW, FALSE)); CheckError(sp_frame->SetPalette(sp_palette)); At that point everything is set up, and we have a frame to dump our data into. For 1bpp files, every byte stores the information of 8 pixels. The left-most pixel is stored in the MSB, with pixels following all the way down to the right-most pixel stored in the LSB. The code isn't entirely important; you'll be replacing that with whatever suits your needs, when you replace the data layout of your input anyway: // Write data to frame auto const stride { (width * 1 + 7) / 8 }; auto const size { height * stride }; std::vector<unsigned char> buffer(size, 127u); // Convert data to match required layout. Each byte stores 8 pixels, with the // MSB being the leftmost, the LSB the right-most. for (size_t x { 0 }; x < data.size(); ++x) { for (size_t y { 0 }; y < data[x].size(); ++y) { auto shift { x % 8 }; auto mask { 0x80 >> shift }; auto bit { mask * data[x][y] }; auto& value { buffer[y * stride + x / 8] }; value &= ~mask; value |= bit; } } CheckError(sp_frame->WritePixels(height, stride, static_cast<UINT>(buffer.size()), buffer.data())); What's left is to commit the changes to the frame and the encoder, which will ultimately write the image file to disk: // Commit frame CheckError(sp_frame->Commit()); // Commit image CheckError(sp_encoder->Commit()); } This is a test program, writing out an image to a file passed as the first command-line argument: #include <iostream> int wmain(int argc, wchar_t* argv[]) try { if (argc != 2) { return -1; } CheckError(::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED)); // Create 64x64 matrix container data(64, std::vector<bool>(64, false)); // Fill with arrow pointing towards the upper left for (size_t i { 0 }; i < data.size(); ++i) { data[0][i] = true; data[i][0] = true; data[i][i] = true; } ::write_bitmap(argv[1], data); ::CoUninitialize(); } catch (_com_error const& e) { std::wcout << L"Error!\n" << L" Message: " << e.ErrorMessage() << std::endl; } It produces the following 64x64 image (true 1bpp, 4096 pixels, 574 bytes in size):
If you get strange colors switches in the middle of your image using the above C++ function. Be sure to open the outstream in binary mode: imgFile.open(filename, std::ios_base::out | std::ios_base::binary); Otherwise windows inserts unwanted characters in the middle of your file! (been banging my head on this issue for hours) See related question here: Why does ofstream insert a 0x0D byte before 0x0A?
Here's a simple c++ bmp image file class. class bmp_img { public: constexpr static int header_size = 14; constexpr static int info_header_size = 40; constexpr static size_t bytes_per_pixel = 3; bmp_img(size_t width, size_t height) : image_px_width{ width }, image_px_height{ height }, row_width{ image_px_width * bytes_per_pixel }, row_padding{ (4 - row_width % 4) % 4 }, row_stride{ row_width + row_padding }, file_size{ header_size + info_header_size + (image_px_height * row_stride) }, image(image_px_height, std::vector<unsigned char>(row_width)) { //header file type file_header[0] = 'B'; file_header[1] = 'M'; //header file size info file_header[2] = static_cast<unsigned char>(file_size); file_header[3] = static_cast<unsigned char>(file_size >> 8); file_header[4] = static_cast<unsigned char>(file_size >> 16); file_header[5] = static_cast<unsigned char>(file_size >> 24); //header offset to pixel data file_header[10] = header_size + info_header_size; //info header size info_header[0] = info_header_size; //info header image width info_header[4] = static_cast<unsigned char>(image_px_width); info_header[5] = static_cast<unsigned char>(image_px_width >> 8); info_header[6] = static_cast<unsigned char>(image_px_width >> 16); info_header[7] = static_cast<unsigned char>(image_px_width >> 24); //info header image height info_header[8] = static_cast<unsigned char>(image_px_height); info_header[9] = static_cast<unsigned char>(image_px_height >> 8); info_header[10] = static_cast<unsigned char>(image_px_height >> 16); info_header[11] = static_cast<unsigned char>(image_px_height >> 24); //info header planes info_header[12] = 1; //info header bits per pixel info_header[14] = 8 * bytes_per_pixel; } size_t width() const { return image_px_width; } size_t height() const { return image_px_height; } void set_pixel(size_t x, size_t y, int r, int g, int b) { image[y][x * bytes_per_pixel + 2] = r; image[y][x * bytes_per_pixel + 1] = g; image[y][x * bytes_per_pixel + 0] = b; } void fill(int r, int g, int b) { for (int y = 0; y < image_px_height; ++y) { for (int x = 0; x < image_px_width; ++x) { set_pixel(x, y, r, g, b); } } } void write_to_file(const char* file_name) const { std::ofstream img_file(file_name, std::ios_base::binary | std::ios_base::out); img_file.write((char*)file_header, header_size); img_file.write((char*)info_header, info_header_size); std::vector<char> allignment(row_padding); for (int y = image_px_height - 1; y >= 0; --y) { img_file.write((char*)image[y].data(), row_width); img_file.write(allignment.data(), row_padding); } img_file.close(); } private: size_t image_px_width; size_t image_px_height; size_t row_width; size_t row_padding; size_t row_stride; size_t file_size; unsigned char file_header[header_size] = { 0 }; unsigned char info_header[info_header_size] = { 0 }; std::vector<std::vector<unsigned char>> image; };
C++ answer, flexible API, assumes little-endian system to code-golf it a bit. Note this uses the bmp native y-axis (0 at the bottom). #include <vector> #include <fstream> struct image { image(int width, int height) : w(width), h(height), rgb(w * h * 3) {} uint8_t & r(int x, int y) { return rgb[(x + y*w)*3 + 2]; } uint8_t & g(int x, int y) { return rgb[(x + y*w)*3 + 1]; } uint8_t & b(int x, int y) { return rgb[(x + y*w)*3 + 0]; } int w, h; std::vector<uint8_t> rgb; }; template<class Stream> Stream & operator<<(Stream & out, image const& img) { uint32_t w = img.w, h = img.h; uint32_t pad = w * -3 & 3; uint32_t total = 54 + 3*w*h + pad*h; uint32_t head[13] = {total, 0, 54, 40, w, h, (24<<16)|1}; char const* rgb = (char const*)img.rgb.data(); out.write("BM", 2); out.write((char*)head, 52); for(uint32_t i=0 ; i<h ; i++) { out.write(rgb + (3 * w * i), 3 * w); out.write((char*)&pad, pad); } return out; } int main() { image img(100, 100); for(int x=0 ; x<100 ; x++) { for(int y=0 ; y<100 ; y++) { img.r(x,y) = x; img.g(x,y) = y; img.b(x,y) = 100-x; } } std::ofstream("/tmp/out.bmp") << img; }
This code uses some newer C++ features. I've used it to create 8bit and 24bit bmp files. It only writes bmp files, one day we may read them too! I didn't like all the shifting and error proneess for endian safety. It could use lots more comments but the code is pretty straight forward. The supposedly run-time detection of endianness results in code being optimized away on all the compilers I tested (a while ago). endian_type.h >> Endian safe POD type. #ifndef ENDIAN_TYPE_H #define ENDIAN_TYPE_H #include <algorithm> #include <type_traits> namespace endian_type { template <typename T, bool store_as_big_endian> struct EndianType { using value_type = T; static_assert(std::is_fundamental_v<value_type>, "EndianType works for fundamental data types"); EndianType() = default; EndianType(const value_type& value) : value{ convert_to(value) } {} struct TypeAsBytes { unsigned char value[sizeof(value_type)]; }; static constexpr bool is_big_endian() { union { int ival; char cval; } uval; uval.ival = 1; return 0 == uval.cval; } static TypeAsBytes convert_to(const value_type& ivalue) { TypeAsBytes ovalue; const unsigned char* p_ivalue = (const unsigned char*)&ivalue; if (store_as_big_endian != is_big_endian()) { std::reverse_copy(p_ivalue, p_ivalue + sizeof(value_type), ovalue.value); } else { std::copy(p_ivalue, p_ivalue + sizeof(value_type), ovalue.value); } return ovalue; } static value_type convert_from(const TypeAsBytes& ivalue) { value_type ovalue; unsigned char* p_ovalue = (unsigned char*) &ovalue; const unsigned char* p_ivalue = (const unsigned char*)&ivalue; if (store_as_big_endian != is_big_endian()) { std::reverse_copy(p_ivalue, p_ivalue + sizeof(value_type), p_ovalue); } else { std::copy(p_ivalue, p_ivalue + sizeof(value_type), p_ovalue); } return ovalue; } value_type get() const { return convert_from(value); } EndianType& set(const value_type& ivalue) { value = convert_to(ivalue); return *this; } operator value_type() const { return get(); } EndianType& operator=(const value_type& ivalue) { set(ivalue); return *this; } private: TypeAsBytes value; }; template <typename T> using BigEndian = EndianType<T, true>; template <typename T> using LittleEndian = EndianType<T, false>; } // namespace endian_type #endif // ENDIAN_TYPE_H The following contains the write_bmp functions. bmp_writer.h >> the BMP writer header #ifndef BMP_WRITER #define BMP_WRITER #include "endian_type.h" #include <cctype> #include <vector> #include <fstream> namespace bmp_writer { template <typename T> using LittleEndian = endian_type::LittleEndian<T>; struct Header { char magic[2]{ 'B', 'M' }; LittleEndian<std::uint32_t> size; LittleEndian<std::uint16_t> app_data1; LittleEndian<std::uint16_t> app_data2; LittleEndian<std::uint32_t> offset; }; struct Info { LittleEndian<std::uint32_t> info_size{ 40 }; LittleEndian<std::uint32_t> width; LittleEndian<std::uint32_t> height; LittleEndian<std::uint16_t> count_colour_planes{ 1 }; LittleEndian<std::uint16_t> bits_per_pixel; LittleEndian<std::uint32_t> compression{}; LittleEndian<std::uint32_t> image_bytes_size; LittleEndian<std::uint32_t> resolution_horizontal{ 2835 }; LittleEndian<std::uint32_t> resolution_vertical{ 2835 }; LittleEndian<std::uint32_t> count_pallete_entries{ 0 }; LittleEndian<std::uint32_t> important_colours{ 0 }; }; template <std::size_t count> class Palette { public: static constexpr std::uint32_t NUM_CHANNELS = 4; using Entry = std::uint8_t[NUM_CHANNELS]; private: Palette() { for (auto i = 0; i < count; ++i) { auto& entry = table[i]; for (auto j = 0; j < NUM_CHANNELS - 1; ++j) { entry[j] = i; } } } Palette(const Palette&) = delete; Palette(const Palette&&) = delete; Palette& operator=(const Palette&) = delete; Palette& operator=(const Palette&&) = delete; public: static const Palette& get() { static const Palette palette; return palette; } Entry table[count]; }; static_assert(sizeof(Info) == 40, ""); template <typename T> void write_bmp( std::ofstream& out, std::uint32_t width, std::uint32_t height, std::uint16_t count_colour_planes, const T* data, std::uint32_t data_size ) { auto& palette = Palette<256>::get(); Header header; Info info; info.width = width; info.height = height; //info.count_colour_planes = count_colour_planes; const std::uint32_t t_per_pixel = data_size / (width * height); info.bits_per_pixel = std::uint16_t(sizeof(T) * 8 * t_per_pixel); const std::uint32_t row_len = width * sizeof(T) * t_per_pixel; // Round row up to next multiple of 4. const std::uint32_t padded_row_len = (row_len + 3) & ~3u; const std::uint32_t data_size_bytes = padded_row_len * height; info.image_bytes_size = data_size_bytes; if (count_colour_planes == 1) { header.offset = sizeof(Info) + sizeof(Header) + sizeof(palette); } else { header.offset = sizeof(Info) + sizeof(Header); } header.size = header.offset + height * padded_row_len; out.write(reinterpret_cast<const char*>(&header), sizeof(header)); out.write(reinterpret_cast<const char*>(&info), sizeof(info)); if (count_colour_planes == 1) { out.write(reinterpret_cast<const char*>(&palette), sizeof(palette)); } const char padding[3] = {}; for (int i = height; i > 0;) { --i; const char* p_row = reinterpret_cast<const char*>(data + i * width); out.write(p_row, row_len); if (padded_row_len != row_len) { out.write(padding, padded_row_len - row_len); } } }; template <typename T> void write_bmp( std::ofstream& out, std::uint32_t width, std::uint32_t height, std::uint16_t count_colour_planes, const std::vector<T>& data ) { write_bmp(out, width, height, count_colour_planes, &*data.cbegin(), data.size()); } template <typename T> void write_bmp( const std::string& outfilename, std::uint32_t width, std::uint32_t height, std::uint16_t count_colour_planes, const std::vector<T>& data ) { std::ofstream out{ outfilename, std::ios_base::binary }; if (!out) { throw std::runtime_error("Failed to open: " + outfilename); } write_bmp(out, width, height, count_colour_planes, &*data.begin(), static_cast<std::uint32_t>(data.size())); out.close(); } } // namespace #endif // BMP_WRITER And an example of use: #include "bmp_writer.h" struct PixelType { PixelType(std::uint8_t r, std::uint8_t g, std::uint8_t b) : c{ b, g, r } {} PixelType(std::uint32_t c) : c{ (c >> 16) & 0xffu, (c >> 8) & 0xffu, c & 0xffu } {} PixelType() = default; std::uint8_t c[3] = {}; }; void bmp_writer_test1() { const int size_x = 20; const int size_y = 10; std::vector<PixelType> data(size_x * size_y); // Write some pixels. data[2] = PixelType(0xff0000); // red data[10] = PixelType(0x00ff00); // green bmp_writer::write_bmp( "test_bmp_writer1.bmp", std::uint32_t(size_x), std::uint32_t(size_y), std::uint16_t(sizeof(PixelType)), data ); } void bmp_writer_test2() { const int size_x = 20; const int size_y = 10; PixelType data[size_x * size_y]; // Write some pixels. data[15] = PixelType(0xff, 0, 0); // red data[17] = PixelType(0, 0xff, 0); // green std::ofstream out{ "test_bmp_writer2.bmp", std::ios_base::binary }; if (!out) { throw std::runtime_error("Failed to open: " "test_bmp_writer2.bmp"); } bmp_writer::write_bmp( out, std::uint32_t(size_x), std::uint32_t(size_y), std::uint16_t(sizeof(PixelType)), data, sizeof(data) / sizeof PixelType ); }