Long ago I wrote a function in C to write a bmp-header. I used binary operators to parse the uint_16's and uint_32's directly into a char[54] array, which took care of endianess and portability.
I've been learning C++ for a while now, and am now trying to rewrite that function in C++-Style using std::ofstream instead of FILE*. The c++ code I produced looks bad and doesn't deal with endianess. Is there a better, or more proper, way to do the C++-header? (preferably one that is portable and deals with endianess)
It currently looks like this:
void writeHeader(int width, int height, std::ofstream file)
{
char bmpSign[2] = {'B', 'M'};
uint filesize = width*height*3+54;
uint reserved = 0;
uint headersize = 54;
uint infoHeader = 40;
ushort colors = 1;
ushort bitsPerPixel = 24;
uint compression = 0;
uint imgSize = width*height*3;
uint xPels = 0;
uint yPels = 0;
uint usedColors = 0;
uint impColors = 0;
file.write(bmpSign, 2*sizeof(char));
file.write(reinterpret_cast<char*>(filesize), sizeof(int));
file.write(reinterpret_cast<char*>(reserved), sizeof(int));
file.write(reinterpret_cast<char*>(headersize), sizeof(int));
file.write(reinterpret_cast<char*>(infoHeader), sizeof(int));
file.write(reinterpret_cast<char*>(width), sizeof(int));
file.write(reinterpret_cast<char*>(height), sizeof(int));
file.write(reinterpret_cast<char*>(colors), sizeof(ushort));
file.write(reinterpret_cast<char*>(bitsPerPixel), sizeof(ushort));
file.write(reinterpret_cast<char*>(compression), sizeof(int));
file.write(reinterpret_cast<char*>(imgSize), sizeof(int));
file.write(reinterpret_cast<char*>(xPels), sizeof(int));
file.write(reinterpret_cast<char*>(yPels), sizeof(int));
file.write(reinterpret_cast<char*>(usedColors), sizeof(int));
file.write(reinterpret_cast<char*>(impColors), sizeof(int));
}
Thanks!
The C-Version of the header:
void parseInt16(uint16_t mem_head, uint8_t file_head[54], int offset)
{
file_head[offset] = (mem_head & 0x00ff);
file_head[offset + 1] = (mem_head & 0xff00) >> 8;
}
void parseInt32(uint32_t mem_head, uint8_t file_head[54], int offset)
{
file_head[offset] = (mem_head & 0x000000ff);
file_head[offset + 1] = (mem_head & 0x0000ff00) >> 8;
file_head[offset + 2] = (mem_head & 0x00ff0000) >> 16;
file_head[offset + 3] = (mem_head & 0xff000000) >> 24;
}
void writeHeader(int width, int height, FILE* bitmap_destination)
{
uint8_t bmp_header[54];
bmp_header[0] = 'B'; bmp_header[1] = 'M';
//parsing the 32 or 16 bit numbers into byte form, little-endian.
//function syntax: (value, Array of Bytes, Offset)
parseInt32(width * height * 3 + 54, bmp_header, 2); //filesize
parseInt32(0, bmp_header, 6); //reserved by software (0)
parseInt32(54, bmp_header, 10 ); //headerOffset / Header size
parseInt32(40, bmp_header, 14 ); //infoHeaderSize
parseInt32(width, bmp_header, 18); //Width in pixels
parseInt32(height, bmp_header, 22); //Height in pixels
parseInt16(1, bmp_header, 26); //Colors (1 per standard)
parseInt16(24, bmp_header, 28); //bitsPerPixel (3 bytes/p)
parseInt32(0, bmp_header, 30); //Compression (0 = none)
parseInt32(width * height * 3, bmp_header, 34); //Image Size(bytes)
parseInt32(0, bmp_header, 38); //xPelsPerMeter
parseInt32(0, bmp_header, 42); //yPelsPerMeter
parseInt32(0, bmp_header, 46); //Colors used (0 for all)
parseInt32(0, bmp_header, 50); //important Colors (0 for all)
fwrite(bmp_header, 1, 54, bitmap_destination); //writing the header to disk
}
The code that you have for dealing with endianness of a platform should be reused. That code is independent of whether you use FILE* or std::fstream to write the data out.
The only line you need to change is
fwrite(bmp_header, 1, 54, bitmap_destination);
That can be replaced by:
file.write(reinterpret_cast<char*>(bmp_header), 54);
Related
I am trying to take a bitmap image and get the RGB values of the pixels. What I currently have will open the bitmap file and read the pixel data:
#define _CRT_SECURE_NO_DEPRECATE
#include "findColor.h"
#include <vector>
#include <iostream>
int findColor(std::string path) {
std::vector<std::string> averageColor; //Will hold the average hex color of each image in order.
std::string currentImage;
currentImage = path + std::to_string(i) + ".btm";
FILE* f = fopen(currentImage.c_str(), "rb");
unsigned char info[54]; //Bitmap header is 54 bytes
fread(info, sizeof(unsigned char), 54, f); //reading the header
// extract image height and width from header
int width, height;
memcpy(&width, info + 18, sizeof(int));
memcpy(&height, info + 22, sizeof(int));
int heightSign = 1;
if (height < 0) {
heightSign = -1;
}
int size = 3 * width * height; //size of image in bytes. 3 bytes per pixel.
unsigned char* data = new unsigned char[size]; // allocate 3 bytes per pixel
fread(data, sizeof(unsigned char), size, f); // read the rest of the data at once
fclose(f); //close image.
for (i = 0; i < size; i += 3) //Flip the image data? It is stored as BGR flipping it to RGB?
{
unsigned char tmp = data[i-33];
data[i] = data[i + 2];
data[i + 2] = tmp;
}
return 0;
}
I really don't know where to go from here. Any responses will be appreciated.
I hope you can help me. I've got this function that I use to save bitmap images but I'm having trouble with the file name. I would like to save the image with the current date on the file name but how I'm currently doing it it's giving me the "An invalid parameter was passed to a function that considers invalid parameters fatal" error and the program crashes.
Here is the function that gets the name from the main program:
void DoSave(char *Name)
{
time_t rawTime;
struct tm *timeInfo;
char buffer[100];
string date;
time(&rawTime);
timeInfo = localtime(&rawTime);
strftime(buffer, 100, " %c", timeInfo);
date = buffer;
string url("Images/");
string name(Name);
string extension(".bmp");
string path = url + name + date + extension;
// Open the file for writing
FILE *f = fopen((const char*)&path, "wb");
// Save the image as bmp file
BmpHelper::SaveTo8bppBmpFile(f, (LONG)m_ImageSizeX, (LONG)m_ImageSizeY, (unsigned char*)videoImage);
fclose(f);
}
Here is the function that saves the bitmaps
void BmpHelper::SaveTo8bppBmpFile(FILE *output, LONG width, LONG height, unsigned char *buffer)
{
f8bppHeader_.bfSize = (DWORD)(s8bppHeader_ + width * height);
i8bppHeader_.biWidth = (LONG)width;
i8bppHeader_.biHeight = -(LONG)height;
fwrite(&f8bppHeader_, sizeof(BITMAPFILEHEADER), 1, output);
fwrite(&i8bppHeader_, sizeof(BITMAPINFOHEADER), 1, output); // I get the error in this line
fwrite(p8bpp, sizeof(RGBQUAD), 256, output);
fwrite(buffer, width, height, output);
}
Not sure you need this bit but I'll add it as well.
void BmpHelper::Init8bppHeaders()
{
f8bppHeader_.bfType = 'MB'; // Will be inverted during the fwrite
s8bppHeader_ = (DWORD)(sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + 256 * sizeof(RGBQUAD) + 256 * sizeof(RGBQUAD));
f8bppHeader_.bfReserved1 = 0;
f8bppHeader_.bfReserved2 = 0;
f8bppHeader_.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + 256 * sizeof(RGBQUAD);
i8bppHeader_.biSize = sizeof(BITMAPINFOHEADER);
i8bppHeader_.biPlanes = 1;
i8bppHeader_.biBitCount = 8;
i8bppHeader_.biCompression = BI_RGB;
i8bppHeader_.biSizeImage = 0;
i8bppHeader_.biXPelsPerMeter = 0;
i8bppHeader_.biYPelsPerMeter = 0;
i8bppHeader_.biClrUsed = 0;
i8bppHeader_.biClrImportant = 0;
for (size_t index = 0; index < 256; ++index)
{
p8bpp[index].rgbBlue = (BYTE)index;
p8bpp[index].rgbGreen = (BYTE)index;
p8bpp[index].rgbRed = (BYTE)index;
p8bpp[index].rgbReserved = 0;
}
}
If I remove the date from the path it works all right but it only saves the file with the url + name given and file format. I need the date to differentiate between files. Can you guys spot anything wrong with the code? Thanks in advance.
Your date/time string includes : characters. Windows file names do not accept any of of these characters \/:*?"<>|. You have to replace : with a different character.
Also your code does not account for bitmap padding. It works only if bitmap width in bytes is a multiple of 4. Use the following formula to calculate size (instead of width * height):
int size = ((width * bpp + 31) / 32) * 4 * height;
s8bppHeader_ = (DWORD)... is a typo! bfSize is not calculated properly.
You can use C++ functions std::fstream instead of mixing C++ and C
Example:
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <ctime>
#include <windows.h>
...
std::time_t t = std::time(nullptr);
char date[100];
std::strftime(date, sizeof(date), " %c", std::localtime(&t));
for(size_t i = 0, len = strlen(date); i < len; i++)
if(date[i] == ':')
date[i] = ','; //use a different character
char *Name = "_abc";
string url("c:\\test\\");
string name(Name);
string extension(".bmp");
string path = url + name + date + extension;
int width = i8bppHeader_.biWidth;
int height = i8bppHeader_.biHeight;
int bpp = i8bppHeader_.biBitCount;
int size = ((width * bpp + 31) / 32) * 4 * height;
f8bppHeader_.bfType = 'MB'; // Will be inverted during the fwrite
f8bppHeader_.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER)
+ 256 * sizeof(RGBQUAD) + size;
f8bppHeader_.bfReserved1 = 0;
f8bppHeader_.bfReserved2 = 0;
f8bppHeader_.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER)
+ 256 * sizeof(RGBQUAD);
ofstream fout(path, ios::binary);
fout.write((char*)&f8bppHeader_, sizeof(f8bppHeader_));
fout.write((char*)&i8bppHeader_, sizeof(i8bppHeader_));
fout.write((char*)p8bpp, 256 * sizeof(RGBQUAD));
fout.write((char*)buffer, size);
With friends we're trying to write app to work with BMP files and we're going to make it as simple as it could be for us, because we're just starting to learn C and C++. Copying was going good with new real size of lines but now I wanted to add grayscale effect and got another problem: the right side of the picture is moved to the left - check out pictures. What's causing this problem?
#include <iostream>
#include <fstream>
#include <stdio.h>
#include <unistd.h>
using namespace std;
void ReadBMP()
{
FILE* f = fopen("test2.bmp", "rb");
FILE* w = fopen("zapis.bmp", "wb");
if(f == NULL)
throw "Argument Exception";
unsigned char info[54];
fread(info, sizeof(unsigned char), 54, f);
fwrite(info, sizeof(unsigned char), 54, w);
int width = *(int*)&info[18];
int height = *(int*)&info[22];
cout << endl;
cout << "Width: " << width << endl;
cout << "Height: " << height << endl;
int realwidth = 3*width+(4 - ((3*width)%4))%4;
int volume = height * realwidth;
unsigned char* data = new unsigned char[volume];
fwrite(info, sizeof(unsigned char), 54, w);
fread(data, sizeof(unsigned char), volume, f);
unsigned char color = 0;
for(int i = 0; i < volume; i+=3)
{
color = 0;
color+=data[i]*0.114;
color+=data[i+1]*0.587;
color+=data[i+2]*0.299;
data[i] = color;
data[i+1] = color;
data[i+2] = color;
}
fwrite(data, sizeof(unsigned char), volume, w);
fclose(f);
fclose(w);
delete(data);
}
int main()
{
ReadBMP();
return 0;
}
Input image
Output image
Your formula for the size of the image data is wrong. First you need to find the pitch, by multiplying the width by the bytes per pixel (3 for a 24-bit image), and then rounding up to the nearest multiple of 4. Then multiply the pitch by the height;
int byte_width = width * 3;
int pitch = byte_width + (4 - byte_width % 4) % 4;
int volume = pitch * height;
unsigned char info[54];
fread(info, sizeof(unsigned char), 54, f);
// fwrite(info, sizeof(unsigned char), 54, w); --- comment this line !!!!!!!
int width = *(int*)&info[18];
int height = *(int*)&info[22];
You're writing header to file twice unnecessarily.
As I see in my own code (that was written about 20 years ago) each line of the image is complemented by 0 or more empty bytes to align the start byte. It seems, your calculate wrong alignment.
Just copy & paste here:
unsigned short paddingSize;
unsigned short bitsPerLine = width * bitsPerPixel;
if(1 == bitsPerPixel || 4 == bitsPerPixel)
{
if(bitsPerLine % 8)
bitsPerLine += 8;
paddingSize = (bitsPerLine/8) % 2;
}
else if(8 == bitsPerPixel)
paddingSize = 0x0003 & ~((bitsPerLine/8) % 4 - 1);
else
paddingSize = (bitsPerLine/8) % 2;
Real size of each line is calculatedSize + paddingSize where calculatedSize is exact size of line in bytes i.e. ceil(bitsPerLine/8) or (bitsPerLine + 7)/8 ic C/C++.
What I can say about the code is it's debugged and it works. But I don't remember why all these checks here.
So, as the title states, I'm having trouble exporting a .bmp (24-bit bmp) with C++. I am doing it as a school project type thing, and I need some help. To learn how .BMPs work I looked at the wikipedia page, and I got some help from here, but I still can't figure it out. Here is what I have:
//Export the map as a .bmp
void PixelMap::exportMap(const char* fileName)
{
//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[] = "";
//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);
int picSize = 3 * width * height;
DIBHeader[20] = (unsigned char)(picSize);
DIBHeader[21] = (unsigned char)(picSize << 8);
DIBHeader[22] = (unsigned char)(picSize << 16);
DIBHeader[23] = (unsigned char)(picSize << 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;
counter++;
pixelArray[counter] = pixelColour[i, j].green;
counter++;
pixelArray[counter] = pixelColour[i, j].blue;
counter++;
}
}
//Open it
ofstream fileWorking(fileName);
//Write the sections
fileWorking << generalHeader;
fileWorking << DIBHeader;
fileWorking << pixelArray;
//NO MEMORY LEAKS 4 ME
fileWorking.close();
}
This is part of a class called 'PixelMap,' basically a frame buffer or surface. The PixelMap has the variables 'width,' 'height,' and the struct array 'pixelColour.' (The struct containing 3 chars called 'red' 'green' and 'blue') If you would like to see the class, here it is. (It's just a skeleton, trying to get the .bmp down first)
//This is a pixel map, mainly for exporting BMPs
class PixelMap
{
public:
//The standard pixel variables
int width;
int height;
Colour pixelColour[];
//The constructor will set said variables
PixelMap(int Width, int Height);
//Manipulate pixels
void setPixel(int X, int Y, char r, char g, char b);
//Export the map
void exportMap(const char* fileName);
};
(Colour is the struct)
So my problem here is that when I try to run this, I get this:
So pixelArray, the array of colours to be exported gets corrupted. I assume this has to do with not being properly given a size, but I try to assign it's proper value (3 * width * height (3 being RGB)) but it says that it needs to be a constant value.
Any help with this issue is greatly appreciated!
Instead of
unsigned char pixelArray[] = "";
you could use:
std::vector<unsigned char> pixelArray(3*width*height,0);
This declares a vector with 3*width*height elements, initialized to 0. You can access the elements using the same syntax you've used for the array version (except, as pointed out in comments, you'll have to take care to write the binary values correctly to the output file).
I have a binary data file that contains 2d and 3d coordinates in such order:
uint32 numberOfUVvectors;
2Dvec uv[numberOfUVvectors];
uint32 numberOfPositionVectors;
3Dvec position[numberOfPositionVectors];
uint32 numberOfNormalVectors;
3Dvec normal[numberOfNormalVectors];
2Dvec and 3Dvec are structs composed from 2 and 3 floats respectively.
At first, I read all these values using the "usual" way:
in.read(reinterpret_cast<char *>(&num2d), sizeof(uint32));
2Dvectors.reserve(num2d); // It's for an std::vector<2DVec> 2Dvectors();
for (int i = 0; i < num2d; i++){
2Dvec 2Dvector;
in.read(reinterpret_cast<char *>(&2Dvector), sizeof(2DVec));
2Dvectors.push_back(2Dvector);
}
It worked fine, but it was painfully slow (there can be more than 200k entries in a file and with so many read calls, the hdd access became a bottleneck). I decided to read the entire file into a buffer at once:
in.seekg (0, in.end);
int length = in.tellg();
in.seekg (0, in.beg);
char * buffer = new char [length];
is.read (buffer,length);
The reading is way faster now, but here's the question: how to parse that char buffer back into integers and structs?
To answer your specific question:
unsigned char * pbuffer = (unsigned char *)buffer;
uint32 num2d = *((uint32 *)pbuffer);
pbuffer += sizeof(uint32);
if(num2d)
{
2Dvec * p2Dvec = (2Dvec *)pbuffer;
2Dvectors.assign(p2Dvec, p2Dvec + num2d);
pbuffer += (num2d * sizeof(2Dvec));
}
uint32 numpos = *((uint32 *)pbuffer);
pbuffer += sizeof(uint32);
if(numpos)
{
3Dvec * p3Dvec = (3Dvec *)pbuffer;
Pos3Dvectors.assign(p3Dvec, p3Dvec + numpos);
pbuffer += (numpos * sizeof(3Dvec));
}
uint32 numnorm = *((uint32 *)pbuffer);
pbuffer += sizeof(uint32);
if(numnorm)
{
3Dvec * p3Dvec = (3Dvec *)pbuffer;
Normal3Dvectors.assign(p3Dvec, p3Dvec + numnorm);
pbuffer += (numnorm * sizeof(3Dvec));
}
// do not forget to release the allocated buffer
A an even faster way would be:
in.read(reinterpret_cast<char *>(&num2d), sizeof(uint32));
if(num2d)
{
2Dvectors.resize(num2d);
2Dvec * p2Dvec = &2Dvectors[0];
in.read(reinterpret_cast<char *>(&p2Dvec), num2d * sizeof(2Dvec));
}
//repeat for position & normal vectors
Use memcpy with the appropriate sizes and start values
or cast the values (example):
#include <iostream>
void copy_array(void *a, void const *b, std::size_t size, int amount)
{
std::size_t bytes = size * amount;
for (int i = 0; i < bytes; ++i)
reinterpret_cast<char *>(a)[i] = static_cast<char const *>(b)[i];
}
int main()
{
int a[10], b[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
copy_array(a, b, sizeof(b[0]), 10);
for (int i = 0; i < 10; ++i)
std::cout << a[i] << ' ';
}