I have an image (frame.bmp, uncompressed, resolution 640*480, 3bit depth, 900KB size) stored at C:\Frame.bmp. I want to load it and put it in my program as an array a[N][M], N=479, M=639. After that to modify its elements (e.g thresholding) and create a new array B[479][639]. Finally, to store it in the path as C:\Frame2.bmp and display the new image, lets say with paint application.

My program is:

#include <stdio.h>
#include <stdlib.h>

   unsigned int** bufalloc(int N,int M);
   int threshold(unsigned int** b,unsigned int** a,unsigned int T,int N,int M);
   int fromdisk(char* imfile,unsigned int** a,int N,int M);
   int todisk(char* imfile,unsigned int** a,int N,int M);
   unsigned int** af;
   unsigned int** bf;

 int main(){
   int f1,f2,f3; 
   af = bufalloc(479,679);
     if(af==NULL) return(-100);
   bf = bufalloc(479,1024);
     if(bf==NULL) return(-100);
   f1 = fromdisk("F.bmp",af,479, 679);
   f2 = threshold(bf,af,40, 479, 679);
   f3 = todisk("tF.bmp",bf, 479, 679);
   return(0);
}
///////////////////////////////////////////////////////////////
int fromdisk(char* imfile,unsigned int** a,int N,int M){
   int i,j;
   unsigned int* buc;
   FILE* fp;
      if (a==NULL) return(-1);
      if (N<0 || M<0) return(-21);
   fp = fopen(imfile, "rb");
      if (fp==NULL) return(-300);
   buc = (unsigned int*)malloc(M*sizeof(unsigned int));
      if(buc==NULL) return(-10);
   for(i=0; i<N; i++){
      fread(buc,sizeof(unsigned int),M,fp);
      for(j=0; j<M; j++)
            a[i][j] = buc[j];
   }
   free(buc);
   fclose(fp);
   return(0);
}
///////////////////////////////////////////////////////////
int todisk(char* imfile,unsigned int** a,int N,int M){
   int k,l;
   unsigned int* buc;
   FILE* fp;
   fp = fopen(imfile, "wb");
      if (fp==NULL) return(-300);
   buc = (unsigned int*)malloc(M*sizeof(unsigned int));
      if(buc==NULL) return(-10);
        for(k=0; k<N; k++){
	   for(l=0; l<M; l++)
  		  buc[l] = a[k][l];
  	   fwrite(buc,sizeof(unsigned int),M,fp);
   }
   free(buc);
   fclose(fp);
   return(0);
}
/////////////////////////////////////////////////////////////////////////////////////
int threshold(unsigned int** b,unsigned int** a,unsigned int T,int N,int M){
   int i,j;
   for(i=0; i<N; i++){
      for(j=0; j<M; j++){
         if (a[i][j]<T)
            b[i][j]=0;
         else b[i][j]=1;
	  } 
   }
   return(0);
}
//////////////////////////////////////////
unsigned int** bufalloc(int N,int M){
   unsigned int** m;
   int r;
   m = (unsigned int**)malloc(N*sizeof(unsigned int*));
   for(r=0; r<N; r++){
      m[r] = (unsigned int*)malloc((M*sizeof(unsigned int)));
      if(m[r]==NULL)		
         return(NULL);
   }
   return(m);
}

But, it does not work corectly (the new image is now 991KB and cannot open)! What happens with the header? Any idea how it can work?

Dani AI

Generated

The corruption you see comes from treating a BMP as a raw 2D array of 4-byte ints. A 24-bit BMP stores pixels as 3 bytes (B,G,R), scanlines are padded to a 4-byte boundary, pixel rows are typically bottom-up, and there is a file/DIB header you must preserve. If you read/write unsigned int elements from offset 0, you will mangle the header, misalign rows, and grow the file.

Key fixes and checks:

  • Open files in binary mode and skip to pixel data using bfOffBits from the file header.
  • Use 8-bit channels: uint8_t per component. For 24-bit, 3 bytes/pixel in B,G,R order.
  • Compute the stride (bytes per row including padding): stride = ((width * bitsPerPixel + 31) / 32) * 4. For 640x480x24bpp, stride is 1920 (no padding), but many widths need padding.
  • Respect image origin: if biHeight > 0, rows are stored bottom-to-top; if negative, top-to-bottom.
  • Keep the headers unchanged except for biSizeImage = stride * abs(height) and bfSize = bfOffBits + biSizeImage if you regenerate pixel data.
  • Your array sizes should match the image (480x640), not 479x679, and thresholds should write either 0 or 255 to all three channels.
  • Always validate biCompression == BI_RGB (uncompressed) and handle 24bpp vs 32bpp separately.

A minimal read/modify/write workflow is: read headers, seek to bfOffBits, read each scanline of stride bytes, process pixels at offsets 3*col + {0,1,2}, write out scanlines with the same padding, and then write back the (updated) headers.

For authoritative details, see Microsoft’s docs on BMP storage layout, headers, padding, and orientation: Bitmap Storage, BITMAPFILEHEADER, and BITMAPINFOHEADER.

Recommended Answers

All 3 Replies

so... is this work for you?

#include <iostream>
#include <fstream>
using namespace std;

struct color {
	unsigned char blue;
	unsigned char green;
	unsigned char red;
};

struct image {
	char header[54];
	color data[480][640];
};

void threshold(image& Frame);

int main(int argc, char **argv) {
	ifstream input_image;
	input_image.open("C:/Frame.bmp");
	if (!input_image.is_open()) {
		cout << "Error opening C:\\Frame.bmp" << endl;
		return -1;
	}
	image Frame;
	input_image.read((char*) &Frame, sizeof(Frame));

	threshold(Frame);

	ofstream output_image;
	output_image.open("C:/Frame2.bmp");
	if (!output_image.is_open()) {
		cout << "Error opening C:\\Frame2.bmp" << endl;
		return -1;
	}
	output_image.write((char*) &Frame, sizeof(Frame));
	return 0;
}

void threshold(image& Frame) {
	for (int i = 0; i < 480; ++i) {
		for (int j = 0; j < 640; ++j) {
			if (Frame.data[i][j].blue < 40)
				Frame.data[i][j].blue = 0;
			else
				Frame.data[i][j].blue = 0xFF;
			if (Frame.data[i][j].green < 40)
				Frame.data[i][j].green = 0;
			else
				Frame.data[i][j].green = 0xFF;
			if (Frame.data[i][j].red < 40)
				Frame.data[i][j].red = 0;
			else
				Frame.data[i][j].red = 0xFF;
		}
	}
}

I know this article has been dead for years but this is exactly what i was looking for. i don't mean the treshold function, i mean the code to put a bmp into a 2-d array.

The solution given by ivailosp works fine with brand new bmp files (created with paint for example) but i can't understand why it gives problem with converted files.
With such files the code keep working, it creates Frame2.bmp but the resulting image is deformed and with different colors.

Does anybody have an idea?

the above code is horrible. It will also only work for 32-bit bitmaps. It does not handle 24-bit bitmaps which have padding. For that reason, you're going to get a deformed image.

The below code will work:

#include <iostream>
#include <fstream>

typedef struct
{
    uint8_t r, g, b, a;
} rgb32;


#pragma pack(2)
typedef struct
{
   uint16_t bfType;
   uint32_t bfSize;
   uint16_t bfReserved1;
   uint16_t bfReserved2;
   uint32_t bfOffBits;
} bitmap_file_header;
#pragma pack()


#pragma pack(2)
typedef struct
{
    uint32_t biSize;
    int32_t biWidth;
    int32_t biHeight;
    uint16_t biPlanes;
    uint16_t biBitCount;
    uint32_t biCompression;
    uint32_t biSizeImage;
    int16_t biXPelsPerMeter;
    int16_t biYPelsPerMeter;
    uint32_t biClrUsed;
    uint32_t biClrImportant;
} bitmap_info_header;
#pragma pack()


#pragma pack(2)
typedef struct
{
    bitmap_file_header fHeader;
    bitmap_info_header iHeader;
} bitmap_header;
#pragma pack()



class bitmap
{
private:
    bitmap_header header;
    uint8_t* pixels;

public:
    bitmap(const char* path);
    ~bitmap();

    void save(const char* path);

    rgb32* getPixel(uint32_t x, uint32_t y) const;
    void setPixel(rgb32* pixel, uint32_t x, uint32_t y);

    uint32_t getWidth() const;
    uint32_t getHeight() const;
};

bitmap::bitmap(const char* path) : header(), pixels(nullptr)
{
    std::ifstream file(path, std::ios::in | std::ios::binary);

    if (file)
    {
        file.read(reinterpret_cast<char*>(&header.fHeader), sizeof(header.fHeader));

        if (header.fHeader.bfType != 0x4d42)
        {
            throw std::runtime_error("Invalid format. Only bitmaps are supported.");
        }

        file.read(reinterpret_cast<char*>(&header.iHeader), sizeof(header.iHeader));

        if (header.iHeader.biCompression != 0)
        {
            throw std::runtime_error("Invalid bitmap. Only uncompressed bitmaps are supported.");
        }

        if (header.iHeader.biBitCount != 24)
        {
            throw std::runtime_error("Invalid bitmap. Only 24bit bitmaps are supported.");
        }

        file.seekg(header.fHeader.bfOffBits, std::ios::beg);

        pixels = new uint8_t[header.fHeader.bfSize - header.fHeader.bfOffBits];
        file.read(reinterpret_cast<char*>(&pixels[0]), header.fHeader.bfSize - header.fHeader.bfOffBits);


        uint8_t* temp = new uint8_t[header.iHeader.biWidth * header.iHeader.biHeight * sizeof(rgb32)];

        uint8_t* in = pixels;
        rgb32* out = reinterpret_cast<rgb32*>(temp);
        int padding = (header.iHeader.biSizeImage - header.iHeader.biWidth * header.iHeader.biHeight * 3) / header.iHeader.biHeight;

        for (int i = 0; i < header.iHeader.biHeight; ++i, in += padding)
        {
            for (int j = 0; j < header.iHeader.biWidth; ++j)
            {
                out->b = *(in++);
                out->g = *(in++);
                out->r = *(in++);
                out->a = 0xFF;
                ++out;
            }
        }

        delete[] pixels;
        pixels = temp;
    }
}

bitmap::~bitmap()
{
    delete[] pixels;
}

void bitmap::save(const char* path)
{
    std::ofstream file(path, std::ios::out | std::ios::binary);

    if (file)
    {
        file.write(reinterpret_cast<char*>(&header.fHeader), sizeof(header.fHeader));
        file.write(reinterpret_cast<char*>(&header.iHeader), sizeof(header.iHeader));
        file.seekp(header.fHeader.bfOffBits, std::ios::beg);


        uint8_t* temp = new uint8_t[header.fHeader.bfSize - header.fHeader.bfOffBits];

        uint8_t* out = temp;
        rgb32* in = reinterpret_cast<rgb32*>(pixels);
        int padding = (header.iHeader.biSizeImage - header.iHeader.biWidth * header.iHeader.biHeight * 3) / header.iHeader.biHeight;

        for (int i = 0; i < header.iHeader.biHeight; ++i, out += padding)
        {
            for (int j = 0; j < header.iHeader.biWidth; ++j)
            {
                *(out++) = in->b;
                *(out++) = in->g;
                *(out++) = in->r;
                ++in;
            }
        }

        file.write(reinterpret_cast<char*>(&temp[0]), header.fHeader.bfSize - header.fHeader.bfOffBits);
        delete[] temp;
    }
}

rgb32* bitmap::getPixel(uint32_t x, uint32_t y) const
{
    rgb32* temp = reinterpret_cast<rgb32*>(pixels);
    return &temp[(header.iHeader.biHeight - 1 - y) * header.iHeader.biWidth + x];
}

void bitmap::setPixel(rgb32* pixel, uint32_t x, uint32_t y)
{
    rgb32* temp = reinterpret_cast<rgb32*>(pixels);
    memcpy(&temp[(header.iHeader.biHeight - 1 - y) * header.iHeader.biWidth + x], pixel, sizeof(rgb32));
};

uint32_t bitmap::getWidth() const
{
    return header.iHeader.biWidth;
}

uint32_t bitmap::getHeight() const
{
    return header.iHeader.biHeight;
}

int main()
{
    bitmap bmp{"C:/Users/Brandon/Desktop/Test.bmp"};

    for (int i = 0; i < bmp.getWidth(); ++i)
    {
        rgb32 pixel = {0x0, 0, 0xff, 0};
        bmp.setPixel(&pixel, i, 0);
    }

    bmp.save("C:/Users/Brandon/Desktop/TestCopy.bmp");
}
Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.