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;
}
Related
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;
}
First off, this is not a duplicate. I have already read Converting 1-bit bmp file to array in C/C++ and my question is about an inconsistency I'm seeing in the formulas provided with the one that works for me.
The Issue
I am trying to read in a 1-bit Bitmap image that was created in MS Paint. I've used the code provided by other answers on this site, but there are a few things I had to change to get it to work, and I want to understand why,
Change 1: lineSize must be doubled
Original
int lineSize = (w / 8 + (w / 8) % 4);
Mine:
int lineSize = (w/ 8 + (w / 8) % 4) * 2;
Change 2: Endianness must be reversed
Original:
for(k = 0 ; k < 8 ; k++)
... (data[fpos] >> k ) & 1;
Mine:
for (int k = 7; k >= 0; --k) {
... (data[rawPos] >> k) & 1;
Full Code
NOTE: This code works. There are some changes from the original, but the core read part is the same.
vector<vector<int>> getBlackAndWhiteBmp(string filename) {
BmpHeader head;
ifstream f(filename, ios::binary);
if (!f) {
throw "Invalid file given";
}
int headSize = sizeof(BmpHeader);
f.read((char*)&head, headSize);
if (head.bitsPerPixel != 1) {
f.close();
throw "Invalid bitmap loaded";
}
int height = head.height;
int width = head.width;
// Lines are aligned on a 4-byte boundary
int lineSize = (width / 8 + (width / 8) % 4) * 2;
int fileSize = lineSize * height;
vector<unsigned char> rawFile(fileSize);
vector<vector<int>> img(head.height, vector<int>(width, -1));
// Skip to where the actual image data is
f.seekg(head.offset);
// Read in all of the file
f.read((char*)&rawFile[0], fileSize);
// Decode the actual boolean values of the pixesl
int row;
int reverseRow; // Because bitmaps are stored bottom to top for some reason
int columnByte;
int columnBit;
for (row = 0, reverseRow = height - 1; row < height; ++row, --reverseRow) {
columnBit = 0;
for (columnByte = 0; columnByte < ceil((width / 8.0)); ++columnByte) {
int rawPos = (row * lineSize) + columnByte;
for (int k = 7; k >= 0 && columnBit < width; --k, ++columnBit) {
img[reverseRow][columnBit] = (rawFile[rawPos] >> k) & 1;
}
}
}
f.close();
return img;
}
#pragma pack(1)
struct BmpHeader {
char magic[2]; // 0-1
uint32_t fileSize; // 2-5
uint32_t reserved; // 6-9
uint32_t offset; // 10-13
uint32_t headerSize; // 14-17
uint32_t width; // 18-21
uint32_t height; // 22-25
uint16_t bitsPerPixel; // 26-27
uint16_t bitDepth; // 28-29
};
#pragma pack()
Potentially relevant information:
I'm using Visual Studio 2017
I'm compiling for C++14
I'm on a Windows 10 OS
Thanks.
Both of those line size formulas are incorrect.
For example, for w = 1, (w / 8 + (w / 8) % 4) results in zero. It's still zero if you multiply by two. It's expected to be 4 for width = 1.
The correct formula for line size (or bytes per line) is
((w * bpp + 31) / 32) * 4 where bpp is bits per pixel, in this case it is 1.
By coincidence the values are sometimes the same, for some smaller width values.
See also MSDN example:
DWORD dwBmpSize = ((bmpScreen.bmWidth * bi.biBitCount + 31) / 32) * 4 * bmpScreen.bmHeight;
Also, 1-bit image has 2 palette entries, for a total of 8 bytes. It seems you are ignoring the palette and assuming that 0 is black, and 1 is white, always.
The part where you flip the bits is correct, the other code appears to be incorrect.
Lets say we have a single byte 1000 0000 This is mean to be a single row, starting with 7 zeros and ending in 1.
Your code is a bit confusing for me (but seems okay when you fix linesize). I wrote my own version:
void test(string filename)
{
BmpHeader head;
ifstream f(filename, ios::binary);
if(!f.good())
return;
int headsize = sizeof(BmpHeader);
f.read((char*)&head, headsize);
if(head.bitsPerPixel != 1)
{
f.close();
throw "Invalid bitmap loaded";
}
int height = head.height;
int width = head.width;
int bpp = 1;
int linesize = ((width * bpp + 31) / 32) * 4;
int filesize = linesize * height;
vector<unsigned char> data(filesize);
//read color table
uint32_t color0;
uint32_t color1;
uint32_t colortable[2];
f.seekg(54);
f.read((char*)&colortable[0], 4);
f.read((char*)&colortable[1], 4);
printf("colortable: 0x%06X 0x%06X\n", colortable[0], colortable[1]);
f.seekg(head.offset);
f.read((char*)&data[0], filesize);
for(int y = height - 1; y >= 0; y--)
{
for(int x = 0; x < width; x++)
{
int pos = y * linesize + x / 8;
int bit = 1 << (7 - x % 8);
int v = (data[pos] & bit) > 0;
printf("%d", v);
}
printf("\n");
}
f.close();
}
Test image:
(33 x 20 monochrome bitmap)
Output:
colortable: 0x000000 0xFFFFFF
000000000000000000000000000000000
000001111111111111111111111111110
000001111111111111111111111111110
000001111111111111111111111111110
000001111111111111111111111111110
011111111111111111111111111111110
011111111111111111111111111111110
011111111111111111111111111111110
011111111111111111111111111111110
011111111111111111111111111111110
011111111111111111111111111111110
011111111111111111111111111111110
011111111111111111111111111111110
011111111111111111111111111111110
011111111111111111111111111111110
011111111111111111111111111111110
011111111111111111111111111110010
011111111111111111111111111110010
011111111111111111111111111111110
000000000000000000000000000000000
Notice this line in above code:
int pos = y * linesize + x / 8;
int bit = 1 << (7 - x % 8);
int v = (data[pos] & bit) > 0;
printf("%d", v);
First I wrote it as
int bit = 1 << (x % 8);
But this shows the bits in the wrong order, so I had to change to 1 << (7 - x % 8) which is basically what you did also. I don't know why it's designed like that. There must be some historical reasons for it!
(above code is for little-endian machines only)
I am attempting to produce code that yields an image with 1 pixel set to black, the problem is that the output is 8 horizontal pixels of black when I attempt to set a single pixel to black. I also attempted to set the pixel above to black, but it resulted in a horizontal line 8 pixels above the original.
So my question is, how can I change my code so I end up working with bits instead of bytes in my image? I just want to be able to set a single pixel to black instead of a byte of pixels.
Here is my code:
#include <iostream>
#include <fstream>
#include "windows.h"
using namespace std;
#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 };
char bits[IMAGE_SIZE][IMAGE_SIZE];
ofstream bmpOut("foo.bmp", ios::out + ios::binary);
if (!bmpOut) {
cout << "could not open file.";
return -1;
}
bmfh.bfType = 0x4d42;
bmfh.bfReserved1 = 0;
bmfh.bfReserved2 = 0;
bmfh.bfOffBits = sizeof(bmfh) + sizeof(bmih) + sizeof(colorTable);
bmfh.bfSize = bmfh.bfOffBits + sizeof(bits);
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;
bmih.biYPelsPerMeter = 2835;
bmih.biClrUsed = 0;
bmih.biClrImportant = 0;
// Here I am initializing a white background for the bitmap image
for (int i = 0; i < IMAGE_SIZE; i++) {
for (int j = 0; j < IMAGE_SIZE; j++) {
bits[i][j] = 255;
}
}
// Here I attempt to set the most bottom left pixel to black and the pixel above it to black as well
bits[0][0] = 0;
bits[1][0] = 0;
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);
bmpOut.close();
system("mspaint foo.bmp");
return 0;
}
Here is a link to the bitmap image produced:
Scaled up for clarity
Thanks
Based on BITMAPINFOHEADER documentation it looks like you are setting your bitmap to be read as monochrome by setting bmih.biBitCount = 1. This way every pixel is represented by one bit (black or white only), not byte. Thus, to set bottom-left pixel to black, you need to do bits[0][0] = 127 (127 = 0x7F = 01111111b). To set one above it, I think you would need to do bits[1][0] = 127. In this case I also believe
char bits[IMAGE_SIZE][IMAGE_SIZE/8];
...
for (int i = 0; i < IMAGE_SIZE; i++) {
for (int j = 0; j < IMAGE_SIZE/8; j++) {
bits[i][j] = 255;
}
}
...
workPtr = &bits[0][0];
bmpOut.write(workPtr, IMAGE_SIZE*(IMAGE_SIZE/8));
would be enough.
If you intended to use different color palette, you should set different biBitCount, as described in the docs I linked to at the beginning, I guess you would want bmih.biBitCount = 8 as a healthy default for your code.
So I'm trying to export a .bmp file in C++ code, and I have it working except for one major thing: line padding. I'm not 100% sure on how line padding works, but I know I need it. My algorithm works except for the padding, I manually added padding in a hex editor to my exported image and it worked. But how do I add padding? Here is what I have:
//Size of the file in bytes
int fileSize = 54 + (3 * width * height);
//The sections of the file
unsigned char generalHeader[14] = {'B','M',0,0, 0,0,0,0, 0,0,54,0, 0,0};
unsigned char DIBHeader[40] = {40,0,0,0, 0,0,0,0, 0,0,0,0, 1,0,24,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0};
unsigned char pixelArray[1000000];
unsigned char bmpPad[3] = {0, 0, 0};
//Set the binary portion of the generalHeader, mainly just file size
generalHeader[2] = (unsigned char)(fileSize);
generalHeader[3] = (unsigned char)(fileSize >> 8);
generalHeader[4] = (unsigned char)(fileSize >> 16);
generalHeader[5] = (unsigned char)(fileSize >> 24);
//The binary variable portion of the DIB header
DIBHeader[4] = (unsigned char)(width);
DIBHeader[5] = (unsigned char)(width >> 8);
DIBHeader[6] = (unsigned char)(width >> 16);
DIBHeader[7] = (unsigned char)(width >> 24);
DIBHeader[8] = (unsigned char)(height);
DIBHeader[9] = (unsigned char)(height >> 8);
DIBHeader[10] = (unsigned char)(height >> 16);
DIBHeader[11] = (unsigned char)(height >> 24);
//Loop through all width and height places to add all pixels
int counter = 0;
for(short j = height; j >= 0; j--)
{
for(short i = 0; i < width; i++)
{
//Add all 3 RGB values
pixelArray[counter] = pixelColour[i][j].red;
pixelArray[counter] = pixelColour[i][j].green;
pixelArray[counter] = pixelColour[i][j].blue;
counter++;
}
}
//Open it
ofstream fileWorking(fileName);
//Write the sections
fileWorking.write((const char*)generalHeader, 14);
fileWorking.write((const char*)DIBHeader, 40);
fileWorking.write((const char*)pixelArray, 3 * width * height);
//NO MEMORY LEAKS 4 ME
fileWorking.close();
pixelColour is of struct data type with the 3 colours, all type unsigned char. Any help is greatly appreciated!
In your case, each row must be a multiple of 4 bytes (32 bits).
int pad = 0; // Set pad byte count per row to zero by default.
// Each row needs to be a multiple of 4 bytes.
if ((width * 3) % 4 != 0) pad = 4 - ((width * 3) % 4); // 4 - remainder(width * 3 / 4).
Padding values can contain pretty much anything, but it is best to set them to 0. When you reach the end of writing each row, just write an additional pad number of zeroes (bytes) before writing the next row.
for(short j = height; j >= 0; j--) {
for(short i = 0; i < width; i++) {
//Add all 3 RGB values
pixelArray[counter++] = pixelColour[i][j].red; // Need to advance counter.
pixelArray[counter++] = pixelColour[i][j].green;
pixelArray[counter++] = pixelColour[i][j].blue;
}
for (int padVal = 0; padVal < pad; padVal++) pixelArray[counter++] = 0; // Pad.
}
Finally, you need to write a larger file size:
fileWorking.write((const char*) pixelArray, (3 * width + pad) * height);
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.