ipp - Converting float matrix to rgb - c++

I have the following code written to have IPP resize my matrix:
#include "ipp_mx.h"
#include "ipp.h"
#include "stdafx.h"
#define IPPCALL(name) name
int main()
{
IppiSize srcSize = { 3,3 };
float srcImage[9] =
{ 20, 40, 30,
35, 55, 70,
100, 30, 20 };
float* src = new float[srcSize.width*srcSize.height];
for (int i = 0; i < srcSize.width*srcSize.height; i++) {
src[i] = srcImage[i];
}
double xFactor = 10; double yFactor = 10;
int numChannels = 1;
int bytesPerPixel = 4;
int srcStep = srcSize.width*bytesPerPixel*numChannels;
IppiRect srcRoi = { 0, 0, srcSize.width, srcSize.width };
float* dest = new float[srcSize.width*srcSize.height*xFactor*yFactor];
IppiSize destSize = { srcSize.width*xFactor, srcSize.height*yFactor };
int destStep = destSize.width*bytesPerPixel*numChannels;
IppiRect destRoi = { 0, 0, destSize.width, destSize.width };
double xShift = 0; double yShift = 0;
int interpolation = 1; //nearest neighbour
int bufSize;
IPPCALL(ippiResizeGetBufSize)(srcRoi, destRoi, 1, interpolation, &bufSize);
unsigned char* buffer = new unsigned char[bufSize];
IPPCALL(ippiResizeSqrPixel_32f_C1R)(src, srcSize, srcStep, srcRoi, dest, destStep, destRoi, xFactor, yFactor, xShift, yShift, interpolation, buffer);
return 0;
}
Is there an IPP function I can use that now converts this float matrix dest to an RGB24 format, given a colour map?
I know I can do it by hand in a for loop, but the raw matrices I want to work with are much larger and for loops may not cut it.

The technique I found to work consists of 3 steps:
Convert/truncate the float value to unsigned char - in my case the input values are within the 8 bit range and I don't care about the decimal numbers.
Convert the unsigned char value to 3 channel RGB gray which essentially assigns the same input values to all 3 channels.
Construct a palette to map 3 channel values to another 3 channel values.
Pass the palette and the input value to a lookup table function.
This is demonstrated on the code below. Note that my palette was setup to assign green for values under 30 and blue for values greater or equals than 30.
unsigned char** GeneratePalette()
{
unsigned char red[256];
unsigned char green[256];
unsigned char blue[256];
for(int value = 0; value < 256; value++)
{
if(value < 30)
{
red[value] = 0;
green[value] = 255;
blue[value] = 0;
}
else
{
red[value] = 0;
green[value] = 0;
blue[value] = 255;
}
}
unsigned char* table[3] = { red, green, blue };
return table;
}
void Test()
{
unsigned char** palette = GeneratePalette();
IppiSize srcSize = { 2,1 };
float src[2] = { 54, 19 };
unsigned char truncated[2];
IPPCALL(ippiConvert_32f8u_C1R)(src, srcSize.width * sizeof(float), truncated, srcSize.width * sizeof(unsigned char), srcSize, ippRndZero);
unsigned char copied[6] = {0};
IPPCALL(ippiGrayToRGB_8u_C1C3R)(truncated, srcSize.width * sizeof(unsigned char), copied, srcSize.width * sizeof(unsigned char) * 3, srcSize);
unsigned char dest[6];
IPPCALL(ippiLUTPalette_8u_C3R)(copied, 6, dest, 6, srcSize, palette, 8);
}
int main()
{
Test();
return 0;
}
In the end, this was not very efficient and working on a single for loop was faster.

Related

how do I fill the gaps between pixel in bmp file after loading the pixel array into the array with padded rows

I wrote a program in c++ to draw the pixel of bmp image into the console using SetPixel windows function, but after loading the pixel array into the array the image got printed on the console with gaps between the pixels. Thanks in advance for your help!
This is the output of the printed image on the console.
This is the original Image I provided to it.
As you can see here the image width also changes after the print on the console.
// bmp bitmap
#include <stdlib.h>
#include <stdio.h>
#include <windows.h>
using namespace std;
#pragma pack(1)
struct BitmapFileHeader {
unsigned short type;
unsigned int size;
unsigned short reserved1;
unsigned short reserved2;
unsigned int offset;
};
#pragma pack(0)
unsigned char grayScale(unsigned char r, unsigned char g, unsigned char b) {
return ((r + g + b) / 3);
}
int main() {
char *data;
FILE *filePointer;
int **ImageArray;
BitmapFileHeader *bmp = (struct BitmapFileHeader*)malloc(sizeof(struct BitmapFileHeader));
BITMAPINFOHEADER *BitmapInfoHeader = (BITMAPINFOHEADER*)malloc(sizeof(BITMAPINFOHEADER));
HWND console = GetConsoleWindow();
HDC context = ::GetDC(console) ;
filePointer = fopen("tom.bmp", "rb");
if(!filePointer) {
perror("");
}
fread(reinterpret_cast<BitmapFileHeader*>(bmp), sizeof(BitmapFileHeader), 1, filePointer);
fread(reinterpret_cast<BITMAPINFOHEADER*>(BitmapInfoHeader), sizeof(BITMAPINFOHEADER), 1, filePointer);
if(BitmapInfoHeader->biSize == 40 && BitmapInfoHeader->biCompression == BI_BITFIELDS) {
printf("This types of image uses Extra bit masks\n");
}
// row pading
int RowSize = ((BitmapInfoHeader->biBitCount * BitmapInfoHeader->biWidth + 31) / 32) * 4;
int PixelArraySize = RowSize * BitmapInfoHeader->biHeight;
int height = BitmapInfoHeader->biHeight * 5;
int width = BitmapInfoHeader->biWidth * 5;
printf("RowSize: %d PixelArraySize: %d\n", RowSize, PixelArraySize);
ImageArray = (int**)malloc(sizeof(int*)*height);
// memory allocation
for(int i = 0; i < height; i++)
ImageArray[i] = (int*)malloc(sizeof(int)*width);
data = (char*)malloc(PixelArraySize);
fseek(filePointer, bmp->offset, SEEK_SET);
// set image into array
for(int ii = 0; ii < height; ii+=3) {
fread(data, RowSize, 3, filePointer);
for(int jj = 0; jj < width; jj+=3) {
ImageArray[ii][jj] = grayScale(data[jj+2], data[jj+1], data[jj]);
SetPixel(context, -jj+1000, -ii+500, RGB(data[jj+2], data[jj+1], data[jj]));
}
}
fclose(filePointer);
return 0;
}
here is the code, which I wrote.
A pixel is described by three bytes, one for each RGB channel. You are dealing with two indices here: The index of the pixel in the row data and the position of the pixel in width direction. You place the pixel and access the row data with the same index.
So:
for (int jj = 0; jj < width; jj++) { // jj: position
int kk = 3 * jj; // kk: data index
ImageArray[ii][jj] = grayScale(data[kk + 2], data[kk + 1], data[kk]);
SetPixel(context, -jj + 1000, -ii + 500, RGB(data[kk + 2], data[kk + 1], data[kk]));
}
The vertical gaps, i.e. the blank lines, come from incrementing by 3, where you should just increment by 1. (You have no "data index" here, because you read your data row-wide for the current row ii.)
If you want to enlarge your image, as the multiplication of width and height by 5 suggests, you must add a third index: You now have two positions, the source and target positions. This will be easier if you separate your loops: Create ImageArray of the source image in a first nested loop, then draw your scaled target image to the console with a loop over the target oordinates:
int scale = 5;
int ww = scale * w;
int hh = scale * h;
// read ImageArray
for (int y = 0; y < h; y++) {
fread(data, RowSize, 3, filePointer);
for (int x = 0; x < w; x++) {
ImageArray[y][x] = ...;
SetPixel(context, -jj+1000, -ii+500, RGB(data[jj+2], data[jj+1], data[jj]));
}
}
for (int yy = 0; yy < hh; yy++) {
fread(data, RowSize, 3, filePointer);
for (int xx = 0; xx < ww; xx++) {
int x = xx / scale;
int y = yy / scale;
SetPixel(context, yy, xx, ImageArray[y][x]);
}
}
(Here, single letters re source values, double leters are target values.)

How to correct horizontal flip function to prevent it from altering images' colors

stb_image.h provides a method to flip an image vertically and it works fine. I tried to implement an horizontal flip aka mirror but it alters the image colors.
On pictures that only have 3 colors you could get bluish or reddish or even magenta colored pictures instead of their actual colors. It's the same if we're talking about JPEG or PNG images, you get the same strange results. Curiously if you flip that very same image vertically, its colors look normal.
I've tried testing pretty much any function you could find here and the code I'm providing you with has been the only one that got me close to my actual goal.
// Function I've been trying to implement to enable Horizontal Flip
static void stbi_horizontal_flip(void *image, int w, int h, int bytes_per_pixel)
{
size_t line_bytes = (size_t)w * bytes_per_pixel;
stbi_uc temp[line_bytes];
stbi_uc *bytes = (stbi_uc *)image;
Debug() << line_bytes;
for (int col = 0; col < h; col++) {
stbi_uc *line = bytes + col * line_bytes;
memcpy(&temp, line, line_bytes);
for (int row = 0; row < line_bytes; row++) {
line[row] = temp[line_bytes - row - bytes_per_pixel];
}
}
stbi_horizontally_flip_on_load = false;
}
// stb_image's function for Vertical Flip
static void stbi__vertical_flip(void *image, int w, int h, int bytes_per_pixel)
{
int row;
size_t bytes_per_row = (size_t)w * bytes_per_pixel;
stbi_uc temp[2048];
stbi_uc *bytes = (stbi_uc *)image;
for (row = 0; row < (h>>1); row++) {
stbi_uc *row0 = bytes + row * bytes_per_row;
stbi_uc *row1 = bytes + (h - row - 1) * bytes_per_row;
size_t bytes_left = bytes_per_row;
while (bytes_left) {
size_t bytes_copy = (bytes_left < sizeof(temp)) ? bytes_left : sizeof(temp);
memcpy(temp, row0, bytes_copy);
memcpy(row0, row1, bytes_copy);
memcpy(row1, temp, bytes_copy);
row0 += bytes_copy;
row1 += bytes_copy;
bytes_left -= bytes_copy;
}
}
}
static unsigned char *stbi__load_and_postprocess_8bit(stbi__context *s, int *x, int *y, int *comp, int req_comp)
{
stbi__result_info ri;
void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 8);
if (result == NULL) return NULL;
if (ri.bits_per_channel != 8) {
STBI_ASSERT(ri.bits_per_channel == 16);
result = stbi__convert_16_to_8((stbi__uint16 *) result, *x, *y, req_comp == 0 ? *comp : req_comp);
ri.bits_per_channel = 8;
}
// #TODO: move stbi__convert_format to here
if (stbi_horizontally_flip_on_load) {
int channels = req_comp ? req_comp : *comp;
stbi_horizontal_flip(result, *x, *y, channels * sizeof(stbi_uc));
}
if (stbi__vertically_flip_on_load) {
int channels = req_comp ? req_comp : *comp;
stbi__vertical_flip(result, *x, *y, channels * sizeof(stbi_uc));
}
return (unsigned char *) result;
}
STBIDEF stbi_uc *stbi_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp)
{
unsigned char *result;
stbi__context s;
stbi__start_file(&s,f);
result = stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp);
if (result) {
// need to 'unget' all the characters in the IO buffer
fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR);
}
return result;
}
STBIDEF stbi_uc *stbi_load(char const *filename, int *x, int *y, int *comp, int req_comp)
{
FILE *f = stbi__fopen(filename, "rb");
unsigned char *result;
if (!f) return stbi__errpuc("can't fopen", "Unable to open file");
result = stbi_load_from_file(f,x,y,comp,req_comp);
fclose(f);
return result;
}
STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp)
{
stbi__context s;
stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user);
return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp);
}
void Gosu::load_image_file(Gosu::Bitmap& bitmap, const string& filename)
{
Buffer buffer;
load_file(buffer, filename);
load_image_file(bitmap, buffer.front_reader());
}
void Gosu::load_image_file(Gosu::Bitmap& bitmap, Reader input)
{
bool needs_color_key = is_bmp(input);
stbi_io_callbacks callbacks;
callbacks.read = read_callback;
callbacks.skip = skip_callback;
callbacks.eof = eof_callback;
int x, y, n;
stbi_uc* bytes = stbi_load_from_callbacks(&callbacks, &input, &x, &y, &n, STBI_rgb_alpha);
if (bytes == nullptr) {
throw runtime_error("Cannot load image: " + string(stbi_failure_reason()));
}
bitmap.resize(x, y);
printf("Channels %d, Gosu Color size %d, unsigned char size %d, bytes array size %d",
n, sizeof(Gosu::Color), sizeof(stbi_uc), sizeof(bytes));
// Output: Channels 3 or 4, Gosu Color size 4, unsigned char size 1, bytes array 8
memcpy(bitmap.data(), bytes, x * y * sizeof(Gosu::Color));
stbi_image_free(bytes);
if (needs_color_key) apply_color_key(bitmap, Gosu::Color::FUCHSIA);
}```
// Output: Channels 3 or 4, Gosu Color size 4, unsigned char size 1, bytes array 8
That is what I got back from stb_image, but I'd prefer to get an 8bit array instead. Even so what actually matters is to get rid of that unexpected color change.
Thanks to Igor's comment I could focus on my immediate problem and not long after I came up with the code I've posted below.
What I've been wondering since I finally could flip the images horizontally was why the other methods I found either on the web or as part of image processors' code didn't work as expected. O_o? Sometimes I copied and pasted them only changing some variables' names or types to match stb_image's and they still failed either to compile or display a decent result.
By the way, I tried before to subtract positions to the right value to no avail but it made me think some of them could be used as nice color blend effects. XD
// Horizontal Flip by Kyonides Arkanthes shared under GPLv2 or v3
static void stbi_kyon_horizontal_flip(void *image, int w, int h, int bytes_per_pixel)
{
size_t line_bytes = (size_t)w * bytes_per_pixel;
stbi_uc temp[line_bytes];
stbi_uc *bytes = (stbi_uc *)image;
int lpos, rpos;
for (int col = 0; col < h; col++) {
stbi_uc *line = bytes + col * line_bytes;
memcpy(&temp, line, line_bytes);
for (int row = 0; row < w; row++) {
lpos = row * bytes_per_pixel;
rpos = line_bytes - row * bytes_per_pixel - 1;
line[lpos] = temp[rpos - 3];
line[lpos + 1] = temp[rpos - 2];
line[lpos + 2] = temp[rpos - 1];
line[lpos + 3] = temp[rpos];
}
}
stbi_kyon_horizontally_flip_on_load = false;
}```
You just reversed the order of RGBA, you try to use this, I tested, the effect is normal.
for (int row = 0; row < Qimg2.width(); row++) {
lpos = row * bytes_per_pixel;
rpos = line_bytes - row * bytes_per_pixel - 1;
line[lpos] = temp[rpos - 2];
line[lpos + 1] = temp[rpos - 1];
line[lpos + 2] = temp[rpos - 3];
line[lpos + 3] = temp[rpos];
}

How do I create a dynamic array of arrays (of arrays)?

I'm trying to create a dynamic array of arrays (of arrays). But for some reason the data gets corrupted. I'm using the data to generate a texture in a OpenGL application.
The following code works fine:
unsigned char imageData[64][64][3];
for (int i = 0; i < 64; i++)
{
for (int j = 0; j < 64; j++)
{
unsigned char r = 0, g = 0, b = 0;
if (i < 32)
{
if (j < 32)
r = 255;
else
b = 255;
}
else
{
if (j < 32)
g = 255;
}
imageData[i][j][0] = r;
imageData[i][j][1] = g;
imageData[i][j][2] = b;
}
std::cout << std::endl;
}
glTexImage2D(target, 0, GL_RGB, 64, 64, 0, GL_RGB, GL_UNSIGNED_BYTE, imageData);
Problem is, I want to be able to create a texture of any size (not just 64*64). So I'm trying this:
unsigned char*** imageData = new unsigned char**[64]();
for (int i = 0; i < 64; i++)
{
imageData[i] = new unsigned char*[64]();
for (int j = 0; j < 64; j++)
{
imageData[i][j] = new unsigned char[3]();
unsigned char r = 0, g = 0, b = 0;
if (i < 32)
{
if (j < 32)
r = 255;
else
b = 255;
}
else
{
if (j < 32)
g = 255;
}
imageData[i][j][0] = r;
imageData[i][j][1] = g;
imageData[i][j][2] = b;
}
std::cout << std::endl;
}
glTexImage2D(target, 0, GL_RGB, 64, 64, 0, GL_RGB, GL_UNSIGNED_BYTE, imageData);
But that doesn't work, the image gets all messed up so I assume I'm creating the array of arrays (of arrays) incorrectly? What am I doing wrong?
Also, I guess I should be using vectors instead. But how can I cast the vector of vectors of vectors data into a (void *) ?
This line contains multiple bugs:
unsigned char* pixel = &(imageData[(y * height) + x]);
You should multiply x by height and add y. And there's also the fact that each pixel is actually 3 bytes. Some issues that led to this bug in your code (and will lead to to others)
You should also be using std::vector. You can call std::vector::data to get a pointer to the underlying data to interface to C API's.
You should have a class that represents a pixel. This will handle the offsetting correctly and give things names and made the code clearer.
Whenever you are working with a multi dimensional array that you encode into a single dimensional one, you should try to carefully write an access function that takes care of indexing so you can test it separately.
(end bulleted list... oh SO).
struct Pixel {
unsigned char red;
unsigned char blue;
unsigned char green;
};
struct TwoDimPixelArray {
TwoDimArray(int width, int height)
: m_width(width), m_height(height)
{
m_vector.resize(m_width * m_height);
}
Pixel& get(int x, int y) {
return m_vector[x*height + y];
}
Pixel* data() { return m_vector.data(); }
private:
int m_width;
int m_height;
std::vector<Pixel> m_vector;
}
int width = 64;
int height = 64;
TwoDimPixelArray imageData(width, height);
for (int x = 0; x != width ; ++ x) {
for (int y = 0; y != height ; ++y) {
auto& pixel = imageData.get(x, y);
// ... pixel.red = something, pixel.blue = something, etc
}
}
glTexImage2D(target, 0, GL_RGB, 64, 64, 0, GL_RGB, GL_UNSIGNED_BYTE, imageData.data());
You need to use continuous memory for it to work with opengl.
My solution is inspired by previous answers, with a different indexing system
unsigned char* imageData = new unsigned char[width*height*3];
unsigned char r, g, b;
const unsigned int row_size_bytes = width * 3;
for( unsigned int x = 0; x < width; x++ ) {
unsigned int current_row_offset_bytes = x * 3;
for( unsigned int y = 0; y < height; y++ ) {
unsigned int one_dim_offset = y * row_size_bytes + current_row_offset_bytes
unsigned char* pixel = &(imageData[one_dim_offset]);
pixel[0] = r;
pixel[1] = g;
pixel[2] = b;
}
}
Unfortunnately it's untested, but i'm confident assuming sizeof(char) is 1.

Finding a small bmp file in a bigger bmp file

I want to find a small bmp file from another bigger bmp file (the bigger one is captured from screen and called Sample.bmp , the small bmp file is called Button.bmp . Thing is the when comparing the images the file can't be found anywhere.
the compare code :
for (int i=0;i<SCREEN_WIDTH-width;++i)
{
for (int j=0;j<SCREEN_HEIGHT-height;++j)
{
boolean isequal = true;
for(int qqq=i;qqq<i+width;++qqq)
{
for (int kkk=j;kkk<j+height;++kkk)
{
if (PI[qqq][kkk]!=NPI[qqq-i][kkk-j]) isequal = false;
if (isequal == false)
{
qqq = i + width + 1;
kkk = j + height + 1;
}
}
}
if (isequal==true)
{
MidX = i;
MidY = j;
return;
}
}
}
note : Screen_width and Screen_height are for the bigger image and width and height are for the smaller one
Full Code:
void readBMP()
{
int i;
FILE* f = fopen("Sample.bmp", "rb");
unsigned char info[54];
fread(info, sizeof(unsigned char), 54, f); // read the 54-byte header
// extract image height and width from header
int width = *(int*)&info[18];
int height = *(int*)&info[22];
int size = 3 * width * height;
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);
for(int qq=0;qq<SCREEN_WIDTH;++qq)
for (int kk=0;kk<SCREEN_HEIGHT;++kk)
{
PI[qq][kk][0] = data[kk * width + qq];
PI[qq][kk][1] = data[kk * width + qq + 1];
PI[qq][kk][2] = data[kk * width + qq + 2];
}
}
void FindImageInScreen(char* FileName)
{
FILE* f = fopen(FileName, "rb");
unsigned char info[54];
fread(info, sizeof(unsigned char), 54, f); // read the 54-byte header
// extract image height and width from header
int width = *(int*)&info[18];
int height = *(int*)&info[22];
int size = 3 * width * height;
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);
for(int qq=0;qq<width;++qq)
for (int kk=0;kk<height;++kk)
{
NPI[qq][kk][0] = data[kk * width + qq];
NPI[qq][kk][1] = data[kk * width + qq + 1];
NPI[qq][kk][2] = data[kk * width + qq + 2];
}
for (int i=0;i<SCREEN_WIDTH-width;++i)
{
for (int j=0;j<SCREEN_HEIGHT-height;++j)
{
boolean isequal = true;
for(int qqq=i;qqq<i+width;++qqq)
{
for (int kkk=j;kkk<j+height;++kkk)
{
if (PI[qqq][kkk][0]!=NPI[qqq-i][kkk-j][0]) isequal = false;
if (isequal == false)
{
qqq = i + width + 1;
kkk = j + height + 1;
}
}
}
if (isequal==true)
{
MidX = i;
MidY = j;
return;
}
}
}
MidX = -1;
MidY = -1;
return;
}
definition of arrays (added because of request) , This is before functions execute :
PI = new unsigned int**[SCREEN_WIDTH];
for (int i=0;i<SCREEN_WIDTH;++i)
PI[i] = new unsigned int*[SCREEN_HEIGHT];
for (int i=0;i<SCREEN_WIDTH;++i)
for (int j=0;j<SCREEN_HEIGHT;++j)
PI[i][j] = new unsigned int[3];
NPI = new unsigned int**[SCREEN_WIDTH];
for (int i=0;i<SCREEN_WIDTH;++i)
NPI[i] = new unsigned int*[SCREEN_HEIGHT];
for (int i=0;i<SCREEN_WIDTH;++i)
for (int j=0;j<SCREEN_HEIGHT;++j)
NPI[i][j] = new unsigned int[3];
The First function executes then the second. and sorry for some bad programming because I did thousands of changes to make it work!
PI[qq][kk][0] = data[kk * width + qq];
From how PI and NPI are filled in, it appears that they are 3-dimensional arrays (it would help if you included their definition in the code sample). But
if (PI[qqq][kkk]!=NPI[qqq-i][kkk-j]) isequal = false;
which is only indexing 2 dimensions of each. PI[a][b] is the address of the array containing PI[a][b][0..2] and will certainly never match the address of NPI[x][y], so this statement is always returning false I expect.
Lets get you started. Here is a better LoadBMP.
Yours, among other thing, read the size, and uses SCREEN_HEIGHT anyway.
Using this for loading both images is probably easier.
#include <vector>
#include <cstdio>
#include <string>
using namespace std;
typedef unsigned char UC;
struct RGB { UC r,g,b; };
bool operator == ( const RGB& p1, const RGB& p2 ) { return p1.r==p2.r && p1.g==p2.g && p1.b==p2.b; }
struct BMP
{
int width;
int height;
vector<RGB> pixels;
RGB& Pix(int x,int y) { return pixels[ y*width + x ]; }
};
void LoadBMP( BMP& bmp, const char* filename )
{
FILE* f = fopen(filename, "rb");
UC info[54];
fread(info, 1, 54, f); // read the 54-byte header
// extract image height and width from header
bmp.width = *(int*) (info+18);
bmp.height = *(int*) (info+22);
// scanlines are always multiple of 4, padded with 0-3 bytes
int scanlinesize = 3*bmp.width;
while( scanlinesize % 4 ) ++scanlinesize;
int size = scanlinesize * bmp.height;
UC* data = new UC[size];
fread(data, 1, size, f);
fclose(f);
bmp.pixels.clear();
bmp.pixels.reserve(bmp.height*bmp.width);
for(int yy=0;yy<bmp.height;++yy)
{
UC* p = data+scanlinesize*yy;
for (int xx=0;xx<bmp.width;++xx)
{
RGB rgb;
rgb.b = *p++;
rgb.g = *p++;
rgb.r = *p++;
bmp.pixels.push_back(rgb);
}
}
delete[] data;
}

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
);
}