I just started creating a bitmap image file loader and just from the wiki, I concluded the offset from the beginning of the file to the image data was 50 bytes. Therefor, I set the data to 50. Here is the original image:
Now when I load the image with the following code:
std::fstream file;
file.open(fileName, std::fstream::binary | std::fstream::in);
if (file.fail()) std::cout << "Couldn't open: `" << fileName << "`\n";
GLchar * data;
file.seekg(0, file.end);
int length = file.tellg();
file.seekg(0, file.beg);
data = new GLchar[length];
file.read(data, length);
if(file)
std::cout << "all characters read successfully.\n";
else
std::cout << "error: only " << file.gcount() << " could be read";
GLchar sec = data[1];
std::cout << data[0] << data[1] << "= ";
switch (sec) {
case 'M':std::cout << "Windows 3.1x"; break;
case 'A':std::cout << "OS/2 struct bitmap array"; break;
case 'I':std::cout << "OS/2 struct color icon"; break;
case 'P':std::cout << "OS/2 const color pointer"; break;
case 'C':std::cout << "OS/2 struct icon"; break;
}
int headerOffset = 50;
std::cout << "\n\n~~ "<< *(GLuint *)&data[10];
width = *(GLuint *)&data[18];
height = *(GLuint *)&data[22];
int bpp = *(int *)&data[28];
int compressionMethod = *(int *)&data[30];
std::cout << "\nDimensions: " << width << "x" << height << "\n";
std::cout << "Bits per pixel: " << bpp;
std::cout << "\nCompression Method: " << compressionMethod << "\n";
//start of pixel array - 50
unsigned char *pixels = new unsigned char[width*height * 3];
file.seekg(headerOffset + 40);
file.read((char *)pixels, width*height*3);
unsigned char tmpRGB = 0; // Swap buffer
for (unsigned long i = 0; i < width * height * 3; i += 3)
{
tmpRGB = pixels[i];
pixels[i] = pixels[i + 2];
pixels[i + 2] = tmpRGB;
}
glGenTextures(1, &texture); // Generate a texture
glBindTexture(GL_TEXTURE_2D, texture); // Bind that texture temporarily
GLint mode = GL_RGB; // Set the mode
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D, 0, mode, width, height, 0, mode, GL_UNSIGNED_BYTE, pixels);
delete[] pixels;
delete[] data;
file.close();
std::cout << "\n\n\n";
This code is with the assumption that the offset to the image array is 50
With this assumption: Here is what the image produces-
Now, after some research I learned that
The offset, i.e. starting address, of the byte where the bitmap image data (pixel array) can be found. Has an offset of 10 into the file. So I then decided to change the
GLuint offset = 50;
to the following
GLuint offset = *(GLuint *)&data[10];
When I do this, however, the colors get switched to the wrong order. Here is an image:
Here is an explanation of the problem: The original image goes, from top to bottom, blue-green-red-white-gray. The first image I rendered adheres to this. The second one (the one that finds the offset from the code) doesn't. Can anyone explain why this happening?
Without looking at too much code, it seems that you did not take into account that the image is stored in Little Endian:
For 24-bit pixels, a pixel in memory looks like this: 0xBBGGRR, i.e., the mistake might be that you have written code assuming that a pixel
is written in Big endian, like this: 0xRRGGBB.
That means that, even though we think of a pixel as 3 bytes of RGB values (1B of Red, 1B of green and 1B of blue), the bytes are swapped in memory.
EDIT:
That explains why only the blues are swapped with reds, while greens and other colors stayed the same; because the colours that were not swapped have a simetrical hex value both in Big endian and Little endian notation.
This is the sample from when i was writing bitmap loader
cpp file
#include "BMP.h"
void LoadBMP(const std::string fn, std::vector<Color>& image, unsigned int &width, unsigned int &height)
{
std::ifstream fin(fn.c_str(), std::ios::binary);
char signature[2];
fin.read(signature, sizeof(signature));
if (signature[0] == 'B' && signature[1] == 'M') {
BMPFileHeader fileheader;
BMPInfoHeader infoheader;
fin.read((char*)&fileheader, sizeof(fileheader));
fin.read((char*)&infoheader, sizeof(infoheader));
width = infoheader.width;
height = infoheader.height;
fin.seekg(fileheader.offset, fin.beg);
int PaddingBytesPerRow = (4 - ((infoheader.width * 3) % 4)) % 4;
Pixels pxl;
int cc = 0;
image.resize(infoheader.width*infoheader.height);
for (unsigned int y = 0; y < infoheader.height; y++) {
for (unsigned int x = 0; x < infoheader.width; x++) {
fin.read((char*)&pxl, sizeof(pxl));
cc = x + ((infoheader.height - 1) - y) * infoheader.width;
image[cc].SetR(pxl.r);
image[cc].SetG(pxl.g);
image[cc].SetB(pxl.b);
}
fin.seekg(PaddingBytesPerRow, fin.cur);
}
}
fin.close();
}
h file
#pragma once
#include <string>
#include "Graphics.h" // color array
#include <fstream>
#include <vector>
struct BMPFileHeader
{
unsigned int size;
unsigned short reserved1, reserved2;
unsigned int offset;
};
struct BMPInfoHeader
{
unsigned int HeaderSize;
unsigned int width, height;
unsigned short planes;
unsigned short bits;
unsigned int compression;
unsigned int imagesize;
int xResolution, yResolution;
unsigned int nColors;
unsigned int importantColors;
};
struct Pixels {
unsigned char b, g, r;
};
void LoadBMP(const std::string fn,std::vector<Color>& image, unsigned int &width,unsigned int &height);
i used structures for bitmap so there will by no magic values and everything will be loaded from files into structures and then use those values calculating padding and pushing pixels into vector , i have tested this code and its working fine
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.
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).
Is there a way to read .pfm files in OpenCV?
Thank you very much for any suggestions!
PFM is an uncommon image format and I don't know why the Middlebury dataset chose to use that, probably because it uses floating point values.
Anyway I was able to read the images with OpenCV:
import numpy as np
import cv2
groundtruth = cv2.imread('disp0.pfm', cv2.IMREAD_UNCHANGED)
Note the IMREAD_UNCHANGED flag. Somehow it is able to read all the correct values even if OpenCV does not support it.
But wait a minute: inf values are commonly used to set INVALID pixel disparity, so to properly display the image you should do:
# Remove infinite value to display
groundtruth[groundtruth==np.inf] = 0
# Normalize and convert to uint8
groundtruth = cv2.normalize(groundtruth, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8)
# Show
cv2.imshow("groundtruth", groundtruth)
cv2.waitKey(0)
cv2.destroyAllWindows()
Based on the description of the ".pfm" file formate (see http://netpbm.sourceforge.net/doc/pfm.html), I wrote the following read/write functions, which only depend standard C/C++ library. It is proved to work well on reading/writing the pfm file, like, the ground truth disparity ".pfm" files from MiddleBury Computer Vision (see http://vision.middlebury.edu/stereo/submit3/).
#ifndef _PGM_H_
#define _PGM_H_
#include <fstream>
#include <iostream>
#include <algorithm>
#include <string>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <bitset> /*std::bitset<32>*/
#include <cstdio>
enum PFM_endianness { BIG, LITTLE, ERROR};
class PFM {
public:
PFM();
inline bool is_little_big_endianness_swap(){
if (this->endianess == 0.f) {
std::cerr << "this-> endianness is not assigned yet!\n";
exit(0);
}
else {
uint32_t endianness = 0xdeadbeef;
//std::cout << "\n" << std::bitset<32>(endianness) << std::endl;
unsigned char * temp = (unsigned char *)&endianness;
//std::cout << std::bitset<8>(*temp) << std::endl;
PFM_endianness endianType_ = ((*temp) ^ 0xef == 0 ?
LITTLE : (*temp) ^ (0xde) == 0 ? BIG : ERROR);
// ".pfm" format file specifies that:
// positive scale means big endianess;
// negative scale means little endianess.
return ((BIG == endianType_) && (this->endianess < 0.f))
|| ((LITTLE == endianType_) && (this->endianess > 0.f));
}
}
template<typename T>
T * read_pfm(const std::string & filename) {
FILE * pFile;
pFile = fopen(filename.c_str(), "rb");
char c[100];
if (pFile != NULL) {
fscanf(pFile, "%s", c);
// strcmp() returns 0 if they are equal.
if (!strcmp(c, "Pf")) {
fscanf(pFile, "%s", c);
// atoi: ASCII to integer.
// itoa: integer to ASCII.
this->width = atoi(c);
fscanf(pFile, "%s", c);
this->height = atoi(c);
int length_ = this->width * this->height;
fscanf(pFile, "%s", c);
this->endianess = atof(c);
fseek(pFile, 0, SEEK_END);
long lSize = ftell(pFile);
long pos = lSize - this->width*this->height * sizeof(T);
fseek(pFile, pos, SEEK_SET);
T* img = new T[length_];
//cout << "sizeof(T) = " << sizeof(T);
fread(img, sizeof(T), length_, pFile);
fclose(pFile);
/* The raster is a sequence of pixels, packed one after another,
* with no delimiters of any kind. They are grouped by row,
* with the pixels in each row ordered left to right and
* the rows ordered bottom to top.
*/
T* tbimg = (T *)malloc(length_ * sizeof(T));// top-to-bottom.
//PFM SPEC image stored bottom -> top reversing image
for (int i = 0; i < this->height; i++) {
memcpy(&tbimg[(this->height - i - 1)*(this->width)],
&img[(i*(this->width))],
(this->width) * sizeof(T));
}
if (this->is_little_big_endianness_swap()){
std::cout << "little-big endianness transformation is needed.\n";
// little-big endianness transformation is needed.
union {
T f;
unsigned char u8[sizeof(T)];
} source, dest;
for (int i = 0; i < length_; ++i) {
source.f = tbimg[i];
for (unsigned int k = 0, s_T = sizeof(T); k < s_T; k++)
dest.u8[k] = source.u8[s_T - k - 1];
tbimg[i] = dest.f;
//cout << dest.f << ", ";
}
}
delete[] img;
return tbimg;
}
else {
std::cout << "Invalid magic number!"
<< " No Pf (meaning grayscale pfm) is missing!!\n";
fclose(pFile);
exit(0);
}
}
else {
std::cout << "Cannot open file " << filename
<< ", or it does not exist!\n";
fclose(pFile);
exit(0);
}
}
template<typename T>
void write_pfm(const std::string & filename, const T* imgbuffer,
const float & endianess_) {
std::ofstream ofs(filename.c_str(), std::ifstream::binary);
// ** 1) Identifier Line: The identifier line contains the characters
// "PF" or "Pf". PF means it's a color PFM.
// Pf means it's a grayscale PFM.
// ** 2) Dimensions Line:
// The dimensions line contains two positive decimal integers,
// separated by a blank. The first is the width of the image;
// the second is the height. Both are in pixels.
// ** 3) Scale Factor / Endianness:
// The Scale Factor / Endianness line is a queer line that jams
// endianness information into an otherwise sane description
// of a scale. The line consists of a nonzero decimal number,
// not necessarily an integer. If the number is negative, that
// means the PFM raster is little endian. Otherwise, it is big
// endian. The absolute value of the number is the scale
// factor for the image.
// The scale factor tells the units of the samples in the raster.
// You use somehow it along with some separately understood unit
// information to turn a sample value into something meaningful,
// such as watts per square meter.
ofs << "Pf\n"
<< this->width << " " << this->height << "\n"
<< endianess_ << "\n";
/* PFM raster:
* The raster is a sequence of pixels, packed one after another,
* with no delimiters of any kind. They are grouped by row,
* with the pixels in each row ordered left to right and
* the rows ordered bottom to top.
* Each pixel consists of 1 or 3 samples, packed one after another,
* with no delimiters of any kind. 1 sample for a grayscale PFM
* and 3 for a color PFM (see the Identifier Line of the PFM header).
* Each sample consists of 4 consecutive bytes. The bytes represent
* a 32 bit string, in either big endian or little endian format,
* as determined by the Scale Factor / Endianness line of the PFM
* header. That string is an IEEE 32 bit floating point number code.
* Since that's the same format that most CPUs and compiler use,
* you can usually just make a program use the bytes directly
* as a floating point number, after taking care of the
* endianness variation.
*/
int length_ = this->width*this->height;
this->endianess = endianess_;
T* tbimg = (T *)malloc(length_ * sizeof(T));
// PFM SPEC image stored bottom -> top reversing image
for (int i = 0; i < this->height; i++) {
memcpy(&tbimg[(this->height - i - 1)*this->width],
&imgbuffer[(i*this->width)],
this->width * sizeof(T));
}
if (this->is_little_big_endianness_swap()) {
std::cout << "little-big endianness transformation is needed.\n";
// little-big endianness transformation is needed.
union {
T f;
unsigned char u8[sizeof(T)];
} source, dest;
for (int i = 0; i < length_; ++i) {
source.f = tbimg[i];
for (size_t k = 0, s_T = sizeof(T); k < s_T; k++)
dest.u8[k] = source.u8[s_T - k - 1];
tbimg[i] = dest.f;
//cout << dest.f << ", ";
}
}
ofs.write((char *)tbimg, this->width*this->height * sizeof(T));
ofs.close();
free(tbimg);
}
inline float getEndianess(){return endianess;}
inline int getHeight(void){return height;}
inline int getWidth(void){return width;}
inline void setHeight(const int & h){height = h;}
inline void setWidth(const int & w){width = w;}
private:
int height;
int width;
float endianess;
};
#endif /* PGM_H_ */
Forgive me to leave lots of useless comments in the code.
A simple example shows the write/read:
int main(){
PFM pfm_rw;
string temp = "img/Motorcycle/disp0GT.pfm";
float * p_disp_gt = pfm_rw.read_pfm<float>(temp);
//int imgH = pfm_rw.getHeight();
//int imgW = pfm_rw.getWidth();
//float scale = pfm_rw.getEndianess();
string temp2 = "result/Motorcycle/disp0GT_n1.pfm";
pfm_rw.write_pfm<float>(temp2, p_disp_gt, -1.0f);
return 1;
}
As far as I know, OpenCV doesn't support to read PFM files directly.
You can refer to the code snippet here for a simple PFM reader, which will enable you to read PFM files into COLOR *data with COLOR defined as follows:
typedef struct {
float r;
float g;
float b;
} COLOR;
I have a text file being saved by a matrix library containing a 2D matrix as such:
1 0 0
6 0 4
0 1 1
Where each number is represented with a colored pixel. I am looking for some insight as to how I'd go about solving this problem. If any more information is required, do not hesitate to ask.
EDIT: Another approach I've tried is: fwrite(&intmatrix, size,1, bmp_ptr); where I pass in the matrix pointer, which does not seem to output a readable BMP file. The value of size is the rows*cols of course, and the type of matrix is arma::Mat<int> which is a matrix from the Armadillo Linear Algebra Library.
EDIT II: Reading this indicated that my size should probably be rows*cols*4 given the size of the rows if I am not mistaken, any guidance on this point as well would be great.
Here's an app which generates a text file of random integers, reads them back, and writes them to disk as a (roughly square) 32-bit-per-pixel .BMP image.
Note, I made a number of assumptions on things like the format of the original text file, the range of numbers, etc., but they are documented in the code. With this working example you should be able to tweak them easily, if necessary.
// IntToBMP.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <cstdint>
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <random>
#include <ctime>
#include <memory>
#pragma pack( push, 1 )
struct BMP
{
BMP();
struct
{
uint16_t ID;
uint32_t fileSizeInBytes;
uint16_t reserved1;
uint16_t reserved2;
uint32_t pixelArrayOffsetInBytes;
} FileHeader;
enum class CompressionMethod : uint32_t { BI_RGB = 0x00,
BI_RLE8 = 0x01,
BI_RLE4 = 0x02,
BI_BITFIELDS = 0x03,
BI_JPEG = 0x04,
BI_PNG = 0x05,
BI_ALPHABITFIELDS = 0x06 };
struct
{
uint32_t headerSizeInBytes;
uint32_t bitmapWidthInPixels;
uint32_t bitmapHeightInPixels;
uint16_t colorPlaneCount;
uint16_t bitsPerPixel;
CompressionMethod compressionMethod;
uint32_t bitmapSizeInBytes;
int32_t horizontalResolutionInPixelsPerMeter;
int32_t verticalResolutionInPixelsPerMeter;
uint32_t paletteColorCount;
uint32_t importantColorCount;
} DIBHeader;
};
#pragma pack( pop )
BMP::BMP()
{
//Initialized fields
FileHeader.ID = 0x4d42; // == 'BM' (little-endian)
FileHeader.reserved1 = 0;
FileHeader.reserved2 = 0;
FileHeader.pixelArrayOffsetInBytes = sizeof( FileHeader ) + sizeof( DIBHeader );
DIBHeader.headerSizeInBytes = 40;
DIBHeader.colorPlaneCount = 1;
DIBHeader.bitsPerPixel = 32;
DIBHeader.compressionMethod = CompressionMethod::BI_RGB;
DIBHeader.horizontalResolutionInPixelsPerMeter = 2835; // == 72 ppi
DIBHeader.verticalResolutionInPixelsPerMeter = 2835; // == 72 ppi
DIBHeader.paletteColorCount = 0;
DIBHeader.importantColorCount = 0;
}
void Exit( void )
{
std::cout << "Press a key to exit...";
std::getchar();
exit( 0 );
}
void MakeIntegerFile( const std::string& integerFilename )
{
const uint32_t intCount = 1 << 20; //Generate 1M (2^20) integers
std::unique_ptr< int32_t[] > buffer( new int32_t[ intCount ] );
std::mt19937 rng;
uint32_t rngSeed = static_cast< uint32_t >( time( NULL ) );
rng.seed( rngSeed );
std::uniform_int_distribution< int32_t > dist( INT32_MIN, INT32_MAX );
for( size_t i = 0; i < intCount; ++i )
{
buffer[ i ] = dist( rng );
}
std::ofstream writeFile( integerFilename, std::ofstream::binary );
if( !writeFile )
{
std::cout << "Error writing " << integerFilename << ".\n";
Exit();
}
writeFile << buffer[ 0 ];
for( size_t i = 1; i < intCount; ++i )
{
writeFile << " " << buffer[ i ];
}
}
int _tmain(int argc, _TCHAR* argv[]) //Replace with int main( int argc, char* argv[] ) if you're not under Visual Studio
{
//Assumption: 32-bit signed integers
//Assumption: Distribution of values range from INT32_MIN through INT32_MAX, inclusive
//Assumption: number of integers contained in file are unknown
//Assumption: source file of integers is a series of space-delimitied strings representing integers
//Assumption: source file's contents are valid
//Assumption: non-rectangular numbers of integers yield non-rectangular bitmaps (final scanline may be short)
// This may cause some .bmp parsers to fail; others may pad with 0's. For simplicity, this implementation
// attempts to render square bitmaps.
const std::string integerFilename = "integers.txt";
const std::string bitmapFilename = "bitmap.bmp";
std::cout << "Creating file of random integers...\n";
MakeIntegerFile( integerFilename );
std::vector< int32_t >integers; //If quantity of integers being read is known, reserve or resize vector or use array
//Read integers from file
std::cout << "Reading integers from file...\n";
{ //Nested scope will release ifstream resource when no longer needed
std::ifstream readFile( integerFilename );
if( !readFile )
{
std::cout << "Error reading " << integerFilename << ".\n";
Exit();
}
std::string number;
while( readFile.good() )
{
std::getline( readFile, number, ' ' );
integers.push_back( std::stoi( number ) );
}
if( integers.size() == 0 )
{
std::cout << "No integers read from " << integerFilename << ".\n";
Exit();
}
}
//Construct .bmp
std::cout << "Constructing .BMP...\n";
BMP bmp;
size_t intCount = integers.size();
bmp.DIBHeader.bitmapSizeInBytes = intCount * sizeof( integers[ 0 ] );
bmp.FileHeader.fileSizeInBytes = bmp.FileHeader.pixelArrayOffsetInBytes + bmp.DIBHeader.bitmapSizeInBytes;
bmp.DIBHeader.bitmapWidthInPixels = static_cast< uint32_t >( ceil( sqrt( intCount ) ) );
bmp.DIBHeader.bitmapHeightInPixels = static_cast< uint32_t >( ceil( intCount / static_cast< float >( bmp.DIBHeader.bitmapWidthInPixels ) ) );
//Write integers to .bmp file
std::cout << "Writing .BMP...\n";
{
std::ofstream writeFile( bitmapFilename, std::ofstream::binary );
if( !writeFile )
{
std::cout << "Error writing " << bitmapFilename << ".\n";
Exit();
}
writeFile.write( reinterpret_cast< char * >( &bmp ), sizeof( bmp ) );
writeFile.write( reinterpret_cast< char * >( &integers[ 0 ] ), bmp.DIBHeader.bitmapSizeInBytes );
}
//Exit
Exit();
}
Hope this helps.
If you choose the right image format this is very easy. PGM has an ASCII variant that looks almost exactly like your matrix, but with a header.
P2
3 3
6
1 0 0
6 0 4
0 1 1
Where P2 is the magic for ASCII PGM, the size is 3x3 and 6 is the maxval. I chose 6 because that was the maximum value you presented, which makes 6 white (while 0 is black). In a typical PGM that's 255, which is consistent with an 8-bit grayscale image.
PPM is almost as simple, it just has 3 color components per pixel instead of 1.
You can operate on these images with anything that takes PPM (netpbm, ImageMagick, GIMP, etc). You can resave them as binary PPMs which are basically the same size as an equivalent BMP.
To output a readable BMP file, you need to put a header first:
#include <WinGDI.h>
DWORD dwSizeInBytes = rows*cols*4; // when your matrix contains RGBX data)
// fill in the headers
BITMAPFILEHEADER bmfh;
bmfh.bfType = 0x4D42; // 'BM'
bmfh.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + dwSizeInBytes;
bmfh.bfReserved1 = 0;
bmfh.bfReserved2 = 0;
bmfh.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
BITMAPINFOHEADER bmih;
bmih.biSize = sizeof(BITMAPINFOHEADER);
bmih.biWidth = cols;
bmih.biHeight = rows;
bmih.biPlanes = 1;
bmih.biBitCount = 32;
bmih.biCompression = BI_RGB;
bmih.biSizeImage = 0;
bmih.biXPelsPerMeter = 0;
bmih.biYPelsPerMeter = 0;
bmih.biClrUsed = 0;
bmih.biClrImportant = 0;
Now before you write your color information, just write the bitmap header
fwrite(&bmfh, sizeof(bmfh),1, bmp_ptr);
fwrite(&bmih, sizeof(bmih),1, bmp_ptr);
And finally the color information:
fwrite(&intmatrix, size, sizeof(int), bmp_ptr);
Note, that the block size is sizeof(int), as your matrix doesn't contain single characters, but integers for each value. Depending on the content of your matrix, it might be a good idea to convert the values to COLORREF values (Check the RGB macro, which can be found in WinGDI.h, too)
I've rewritten and commented the answer from https://stackoverflow.com/a/2654860/586784. I hope you find it clear enough.
#include <cstddef>
#include <armadillo>
#include <map>
#include <cstdio>
#include <cassert>
///Just a tiny struct to bundle three values in range [0-255].
struct Color{
Color(unsigned char red, unsigned char green, unsigned char blue)
: red(red),green(green),blue(blue)
{}
///Defualt constructed Color() is black.
Color()
: red(0),green(0),blue(0)
{}
///Each color is represented by a combination of red, green, and blue.
unsigned char red,green,blue;
};
int main(int argc,char**argv)
{
///The width of the image. Replace with your own.
std::size_t w = 7;
///The height of the image. Replace with your own
std::size_t h = 8;
///http://arma.sourceforge.net/docs.html#Mat
///The Armadillo Linear Algebra Library Mat constructor is of the following
/// signature: mat(n_rows, n_cols).
arma::Mat<int> intmatrix(h,w);
///Fill out matrix, replace this with your own.
{
///Zero fill matrix
for(std::size_t i=0; i<h; ++i)
for(std::size_t j=0;j<w; ++j)
intmatrix(i,j) = 0;
intmatrix(0,3) = 1;
intmatrix(1,3) = 1;
intmatrix(2,2) = 6;
intmatrix(2,4) = 6;
intmatrix(3,2) = 4;
intmatrix(3,4) = 4;
intmatrix(4,1) = 6;
intmatrix(4,2) = 6;
intmatrix(4,3) = 6;
intmatrix(4,4) = 6;
intmatrix(4,5) = 6;
intmatrix(5,1) = 1;
intmatrix(5,2) = 1;
intmatrix(5,3) = 1;
intmatrix(5,4) = 1;
intmatrix(5,5) = 1;
intmatrix(6,0) = 4;
intmatrix(6,6) = 4;
intmatrix(7,0) = 6;
intmatrix(7,6) = 6;
}
///Integer to color associations. This is a map
///that records the meanings of the integers in the matrix.
///It associates a color with each integer.
std::map<int,Color> int2color;
///Fill out the color associations. Replace this with your own associations.
{
///When we see 0 in the matrix, we will use this color (red-ish).
int2color[0] = Color(255,0,0);
///When we see 0 in the matrix, we will use this color (green-ish).
int2color[1] = Color(0,255,0);
///When we see 0 in the matrix, we will use this color (blue-ish).
int2color[4] = Color(0,0,255);
///When we see 0 in the matrix, we will use this color (grey-ish).
int2color[6] = Color(60,60,60);
}
///The file size will consist of w*h pixels, each pixel will have an RGB,
/// where each color R,G,B is 1 byte, making the data part of the file to
/// be of size 3*w*h. In addition there is a header to the file which will
/// take of 54 bytes as we will see.
std::size_t filesize = 54 + 3*w*h;
///We make an array of 14 bytes to represent one part of the header.
///It is filled out with some default values, and we will fill in the
///rest momentarily.
unsigned char bmpfileheader[14] = {'B','M', 0,0,0,0, 0,0, 0,0, 54,0,0,0};
///The second part of the header is 40 bytes; again we fill it with some
///default values, and will fill in the rest soon.
unsigned char bmpinfoheader[40] = {40,0,0,0, 0,0,0,0, 0,0,0,0, 1,0, 24,0};
///We will now store the filesize,w,h into the header.
///We can't just write them to the file directly, because different platforms
///encode their integers in different ways. This is called "endianness"
///or "byte order". So we chop our integers up into bytes, and put them into
///the header byte-by-byte in the way we need to.
///Encode the least significant 8 bits of filesize into this byte.
///Because sizeof(unsigned char) is one byte, and one byte is eight bits,
///when filesize is casted to (unsigned char) only the least significant
///8 bits are kept and stored into the byte.
bmpfileheader[ 2] = (unsigned char)(filesize );
///... Now we shift filesize to the right 1 byte, meaning and trunctate
///that to its least significant 8 bits. This gets stored in the next
///byte.
bmpfileheader[ 3] = (unsigned char)(filesize>> 8);
///...
bmpfileheader[ 4] = (unsigned char)(filesize>>16);
///Encodes the most significant 8 bits of filesize into this byte.
bmpfileheader[ 5] = (unsigned char)(filesize>>24);
///Now we will store w (the width of the image) in the same way,
/// but into the byte [5-8] in bmpinfoheader.
bmpinfoheader[ 4] = (unsigned char)( w );
bmpinfoheader[ 5] = (unsigned char)( w>> 8);
bmpinfoheader[ 6] = (unsigned char)( w>>16);
bmpinfoheader[ 7] = (unsigned char)( w>>24);
///Now we will store h (the width of the image) in the same way,
/// but into the byte [9-12] in bmpinfoheader.
bmpinfoheader[ 8] = (unsigned char)( h );
bmpinfoheader[ 9] = (unsigned char)( h>> 8);
bmpinfoheader[10] = (unsigned char)( h>>16);
bmpinfoheader[11] = (unsigned char)( h>>24);
///Now we open the output file
FILE* f = fopen("img.bmp","wb");
///First write the bmpfileheader to the file. It is 14 bytes.
///The 1 means we are writing 14 elements of size 1.
///Remember, bmpfileheader is an array which is basically
///the same thing as saying it is a pointer to the first element
///in an array of contiguous elements. We can thus say:
///write 14 bytes, starting from the spot where bmpfileheader points
///to.
fwrite(bmpfileheader,1,14,f);
///Then write the bmpinfoheader, which is 40 bytes, in the same way.
fwrite(bmpinfoheader,1,40,f);
///Now we write the data.
///For each row (there are h rows), starting from the last, going
///up to the first.
///We iterate through the rows in reverse order here,
///apparently in the BMP format, the image
///is stored upside down.
for(std::size_t i=h-1; i != std::size_t(-1); --i)
{
///For each column in the row,
for(std::size_t j=0; j<w; ++j)
{
///We retreive the integer of the matrix at (i,j),
///and assert that there is a color defined for it.
assert (int2color.count(intmatrix(i,j)) != 0
&& "Integer in matrix not defined in int2color map");
///We somehow get the color for pixel (i,j).
///In our case, we get it from the intmatrix, and looking
///up the integer's color.
Color color = int2color[intmatrix(i,j)];
///Now the colors are written in reverse order: BGR
///We write the color using fwrite, by taking a pointer
///of the (unsigned char), which is the same thing as
///an array of length 1. Then we write the byte.
///First for blue,
fwrite(&color.blue,1,1,f);
///Same for green,
fwrite(&color.green,1,1,f);
///Finally red.
fwrite(&color.red,1,1,f);
}
///Now we do some padding, from 0-3 bytes, depending in the width.
unsigned char bmppad[3] = {0,0,0};
fwrite(bmppad,1,(4-(w*3)%4)%4,f);
}
///Free the file.
fclose(f);
return 0;
}
Is you problem seeing the matrix as an image or writing an image from your code ?
In the former case, just do as Ben Jackson said
In the later case, you want to pass the address of the data pointer of the Arm::Matrix, and using fwrite assumes that Arm::Matrix holds it's data as a contiguous memory array
[edit]
brief look at armadillo doc also tells that data is stored in column-major mode, but BMP assumes row-major mode, so your image will look flipped
[edit2]
Using Armadillo Matrix functions, it's even simpler
// assume A is a matrix
// and maxVal is the maximum int value in you matrix (you might scale it to maxVal = 255)
std::ofstream outfile("name.pgm");
oufile << "P2 " << sd::endl << a.n_rows << " " << a.n_cols << std::endl << maxVal << std::endl;
outfile << a << std::endl;
outfile.close();