Creating and writing to a bitmap.

Hi there; I've been messing with fstream for the past week as a learning experience. I created a Bitmap structure with a header, infoheader, and rgbquad substructures.I figured out how to read a bitmap and copy a bitmap using streams, with little problem.

Creating a bitmap and writing another bitmap's data to it is one thing... creating a bitmap and writing pixel data created on the fly is another thing, I guess.

A little cource code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

HEADER.H

struct  Bitmap  { 
//data members : coordinates, dimension, file name, and data stream
  short   x, y;
  short   h, w;
  string filename;
  fstream pic;
  
  struct Header {}header; //left out data members to save space
  struct Info  {}info;  //same here
  struct Color  { //this one is what I feel is causing the problem.
    unsigned char b;
    unsigned char g;
    unsigned char r;
    unsigned char a;
  }color;

  void Create(string,int, int);
  void Load(string f);
  //other members and functions here
};

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

SOURCE.CPP
void Bitmap::Create(string name, int h2, int w2)  {
  h = h2; 
  w = w2;
  filename = name;
  
  Header temp1 = {19778, ( sizeof(Color) *(w*h) )+ 54, 0, 0, 54};
  Info   temp2 = {sizeof(Bitmap::Info), w, h, 1, sizeof(Color)*8, 0,header.size - 54, 0, 0, 0, 0};
  Color  temp3 = {0,255,90, 0};
  
  header = temp1;
  info = temp2;
  color = temp3;
 
  Color *c = new Color[w*h];
  pic.open(filename.c_str(), ios::out|ios::binary);

  pic.write(reinterpret_cast<char*>(&header), sizeof(header));
  pic.write(reinterpret_cast<char*>(&info), sizeof(info));
  
  for (int f = 0; f <= w*h; ++f)  {
    c[f] = temp3;
  }
  pic.write((char*)&c,info.size); //attempting to write data to bitmap

   
}


void Bitmap::Load(string f)  {
  filename = f;
  pic.open(filename.c_str(), ios::in|ios::binary);
  pic.read(reinterpret_cast<char*>(&header), sizeof(Bitmap::Header));
  pic.read(reinterpret_cast<char*>(&info), sizeof(Bitmap::Info));
  
  h = info.height;
  w = info.width; 
}


I can load a bitmap just fine... but when I write one from scratch... not so much. Depending on how I try to remedy this issue, I get a random unhandled exception, the bitmap is corrupt, or it isn't written to like it should. (It usually comes out as noise when it should be a solid color.)

What am I doing wrong? I'm hoping it's possible to do this with C++ streams.
I need to see Header. I'm pretty sure I know what's wrong, but I'd like to see it anyway.
Last edited on
OK, here you go!

1
2
3
4
5
6
  struct Header {
    short type;                 
      int size;                       
    short reserved1, 
          reserved2;
      int offset; 


I was thinking it was an issue of padding, but I nested the structure between two #pragma packs(push and pop respectively).


You're making several assumptions in your code:
1. That the compiler will place your members in the same order as they appear in your class declaration.
2. That shorts are 16 bits long, and ints are 32 bits long.
3. That the system is little endian.

Why don't you post the first 60 bytes of your generated file in hex? Here's a free hex editor in case you need it: http://artemis.wszib.edu.pl/~mdudek/
Last edited on
I'm not sure why you don't just use the Microsoft-supplied BITMAPINFOHEADER structure. Even if you don't though, you should be very careful to use the correct types.

If you are writing non-intel-specific code, you should not be relying on byte order. I know it is very tempting to be able to just ostream::write() binary data to file directly from structure, but you really should specifically pack it. It is very much worth your time to write all the bitshifts and array packing/unpacking code. A couple of local macros sometimes help:
1
2
3
#define PACK_WORD( addr, value ) \
  *(addr)     =  (value)       & 0xFF, \
  *(addr + 1) = ((value) >> 8) & 0xFF 
Etc.

The pic fstream should be created and destroyed as it is needed, not sit around. (This is just a usage thing about STL streams.)

Please don't use variable names like tempN. Use meaningful names:
1
2
3
  Header header;
  Info   info;
  //etc 


Watch your const and unsigned correctness (you are missing them in spots).

The garbage is coming from line 25 above, which should read:

1
2
// line 25
pic.write((char*)c,info.size);


Remember, c is already a pointer to the data, so getting its address is incorrect.

Hope this helps.

[edit] Hmm, helios beat me to the punch on a lot of things... :-P
Last edited on
I did what you said, Duoas, and it worked... mostly! The first thirteen pixels were outputted and in the right color. The rest of the pixel data came out black, for some reason. I'm so happy the output was successful at all, so I don't even care!

The rest of the info you shared is very helpful, too--thanks. I have no clue how to pack binary data byte-by-byte (except fstream::put)... I guess that's something else I'll have to research.

I don't use constants much except conversion operators, where should I have used some?

helios, here's the hex data, the first 60 bytes:

1
2
42 4D E6 04 00 00 00 00 00 00 36 00 00 00 28 00 00 00 14 00 00 00 
14 00 00 00 01 00 18 00 00 00 00 00 96 CC CC CC 00 00 00 00 00 00 


This is definitely a learning experience!



Last edited on
That's 46 bytes.

In any, case, there are a few errors in that header:
* The bits per pixel (offset 28, 16 bits) are set to 24, not 32.
* The compressed size (offset 34, 32 bits) is set to 150, followed by what seem to be three uninitialized bytes.
* xscale (offset 38, 32 bits) should be set 1, IIRC.
Thanks, helios; now I finally got this to work properly.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void Bitmap::Create(string name, int h2, int w2)  {
         h = h2; 
         w = w2;
  filename = name;
  
  Header header = {19778, sizeof(Color)*w*h , 0, 0, 54};
  Info   info   = {sizeof(Info), w, h, 1, sizeof(Color)*8, 0, ( sizeof(Color) *(w*h) ), 1, 1, 0, 0};
  Color  color  = {0,255,90,0};

  vector<Color>data;
  data.assign(w*h, color);
  fstream pic;
  pic.open(filename.c_str(), ios::out|ios::binary);

  pic.write(reinterpret_cast<char*>(&header), sizeof(header));
  pic.write(reinterpret_cast<char*>(&info), sizeof(info));
  
  for (unsigned int f = 0; f < data.size(); ++f)  {
    Color pixel = data.at(f);
    pic.write((char*)&pixel,sizeof(Color));
   }
  
}


And here's the binary data for the bitmap, it should be the first 60
(offset 0-59) this time:

1
2
3
4

42 4D 76 06 00 00 00 00 00 00 36 00 00 00 28 00 00 00 14 00 00 00 14 00 00 00 01 
00 20 00 00 00 00 00 40 06 00 00 01 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 
00 FF 5A 00 00 FF                                                                


I know this whole thing was just reinventing the wheel, but I did learn a lot about working with streams and binary data. This'll help me... somewhere down the line, heh.
Last edited on
Topic archived. No new replies allowed.