Can't figure out a simple problem

I'm writing a program that analyses bitmap file and shows it on screen:
The problem is that pixels in bmp file are stored in strange way - if I try to output an image I get it flipped. I tried to fix this but I can't figure it out...(bmp.cpp line 63) Here's my code :


bmp.h
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
#include <cstdlib>
#include <stdio.h>
#include <iostream>
#include <windows.h>
using namespace std;
#ifdef _WIN32
#undef LoadBitmap
#endif

class BMP
{
      
BYTE *bmp;       // Visas bmp failas

public:
BITMAPFILEHEADER bmfh;
BITMAPINFOHEADER bmih;
RGBQUAD          *colors;  
BYTE             *bitmap;   // tik pikseliai
      
      
public:      
  BMP(const char *_fn=0);     
      
  void LoadBitmap(const char *_fn);    
  void FileHeaderInfo();
  void InfoHeaderInfo();
  
private:  
void LoadStructs();      
void ParseBitmap();      
      
      
      
      
};


bmp.cpp
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
#include "bmp.h"

BMP::BMP(const char *_fn)
{
               
if(_fn!=0)               
     LoadBitmap(_fn);
     
}

void BMP::LoadBitmap(const char *_fn)
{
     
  long e=0; 
  long r;    
  FILE *fp=fopen(_fn,"rb");
  fseek(fp,0,SEEK_END);
  e=ftell(fp);
  fseek(fp,0,SEEK_SET);

  bmp=new BYTE[e];
  r=fread(bmp,e,1,fp);
  
  if(r==0) 
     cerr <<"\n     Could not load a bitmap file\n";
  fclose(fp);
  
  LoadStructs();
  ParseBitmap();
}


void BMP::LoadStructs()
{
     
    memcpy(&bmfh,bmp,sizeof(BITMAPFILEHEADER));
    memcpy(&bmih,bmp+sizeof(BITMAPFILEHEADER),sizeof(BITMAPINFOHEADER));
    
    /* duoti vietos ir nukopinti RGBQUAD duom. */             
    colors=new RGBQUAD [bmih.biClrUsed];
    memcpy(colors,bmp+sizeof(BITMAPFILEHEADER)+sizeof(BITMAPFILEHEADER),sizeof(RGBQUAD)*bmih.biClrUsed);                   
    
    unsigned long s=bmih.biSizeImage;//bmfh.bfSize - ( sizeof(BITMAPFILEHEADER)+sizeof(BITMAPFILEHEADER)+ (sizeof(RGBQUAD)*bmih.biClrUsed) );
    BYTE *pb = bmp+sizeof(BITMAPFILEHEADER)+sizeof(BITMAPFILEHEADER)+ ( sizeof(RGBQUAD)*bmih.biClrUsed );
    bitmap=new BYTE[s]; 
    memcpy(bitmap,pb,s);

    delete []bmp;        
}

void BMP::ParseBitmap()
{
   int width=bmih.biWidth;
   int height=bmih.biHeight;
   
   BYTE *p_start = bitmap;
   BYTE *p_end=
           bitmap+bmih.biSizeImage;
   BYTE *p_mid=
           bitmap+(bmih.biSizeImage/2);   
   /* I can't figure out how to flip over the pixels... */

// I tried but...
for(int u=0,d=height;u<height/2,d>height/2;u++,d--)      
   for(BYTE *i=p_start+(u*height),*j=p_end-(d*height);i<p_start+((u*height)/2),j>p_end-((d*height)/2);i+=3,j-=3)
     {
       BYTE tmp[3];
       memcpy(tmp,i,3);
       memmove(i,j,3);
       memcpy(j,tmp,3);
     }    
            
 

   
}


void BMP::FileHeaderInfo()
{
cout <<"\nFile header\n------------------------------\n";
     cout <<"File type:             "<<bmfh.bfType<<endl;
     cout <<"File size:             "<<bmfh.bfSize<<endl;
     cout <<"Reserved1:             "<<bmfh.bfReserved1<<endl;
     cout <<"Reserved2:             "<<bmfh.bfReserved2<<endl;          
     cout <<"Offset bits:           "<<bmfh.bfOffBits<<endl;
     
}    

void BMP::InfoHeaderInfo()
{    

cout <<"\nInfo header\n------------------------------\n";
     cout <<"struct req. size:        "<<bmih.biSize<<endl;
     cout <<"Width:                   "<<bmih.biWidth<<endl;
     cout <<"Height:                  "<<bmih.biHeight<<endl;
     cout <<"Planes (==1):            "<<bmih.biPlanes<<endl;
     cout <<"Bits per pixel:          "<<bmih.biBitCount<<endl;
     cout <<"Compression:             "<<bmih.biCompression<<endl;
     cout <<"Image size:              "<<bmih.biSizeImage<<endl;
     cout <<"pixels per meter X:      "<<bmih.biXPelsPerMeter<<endl;
     cout <<"pixels per meter Y:      "<<bmih.biYPelsPerMeter<<endl;     
     cout <<"Colors used:             "<<bmih.biClrUsed<<endl;     
     cout <<"Important colors:        "<<bmih.biClrImportant<<endl;      
}


main.cpp
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
#include "bmp.h"


#if 0
int main(int argc, char *argv[])
{
    BMP bmp("C:\\hdd.bmp");
    bmp.FileHeaderInfo();
    bmp.InfoHeaderInfo();


    system("PAUSE");
    return EXIT_SUCCESS;
}
#endif
//ok, ok, there's winapi but problem isn't here
#if 1
BMP bmp;
LRESULT CALLBACK WindowProcedure (HWND, UINT, WPARAM, LPARAM);

char szClassName[ ] = "WindowsApp";

int WINAPI WinMain (HINSTANCE hThisInstance,
                    HINSTANCE hPrevInstance,
                    LPSTR lpszArgument,
                    int nFunsterStil)

{
    HWND hwnd;
    MSG messages;   
    WNDCLASSEX wincl; 

    wincl.hInstance = hThisInstance;
    wincl.lpszClassName = szClassName;
    wincl.lpfnWndProc = WindowProcedure;  
    wincl.style = CS_DBLCLKS;   
    wincl.cbSize = sizeof (WNDCLASSEX);

    wincl.hIcon = LoadIcon (NULL, IDI_APPLICATION);
    wincl.hIconSm = LoadIcon (NULL, IDI_APPLICATION);
    wincl.hCursor = LoadCursor (NULL, IDC_ARROW);
    wincl.lpszMenuName = NULL;  
    wincl.cbClsExtra = 0;    
    wincl.cbWndExtra = 0; 

    wincl.hbrBackground = (HBRUSH) COLOR_BACKGROUND;


    if (!RegisterClassEx (&wincl))
        return 0;

    hwnd = CreateWindowEx (
           0,                   
           szClassName,         
           "Windows App",       
           WS_OVERLAPPEDWINDOW, 
           CW_USEDEFAULT,       
           CW_USEDEFAULT,      
           640,
           480, 
           HWND_DESKTOP, 
           NULL,
           hThisInstance,
           NULL
           );


    ShowWindow (hwnd, nFunsterStil);

    while (GetMessage (&messages, NULL, 0, 0))
    {
        TranslateMessage(&messages);
        DispatchMessage(&messages);
    }
    return messages.wParam;
}




LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
         case WM_CREATE:{ 
                bmp.LoadBitmap("C:\\hdd.bmp");
              }break; 
         case WM_PAINT:{
             
              HDC hdc=GetDC(hwnd);

             int k=0;
              for(int i=0;i<bmp.bmih.biHeight;i++) 
                for(int j=0;j<bmp.bmih.biWidth;j++)
                  {
         
                  /* Please notice: byte1==GREEN byte2=RED byte3==BLUE  */
                        SetPixelV(hdc,j,i,RGB(bmp.bitmap[k+1],bmp.bitmap[k],bmp.bitmap[k+2]));
                        k+=3;

                  }
              ReleaseDC(hwnd,hdc);

              }break;  
        case WM_DESTROY:
            PostQuitMessage (0); 
            break;
        default:    
            return DefWindowProc (hwnd, message, wParam, lParam);
    }

    return 0;
}
#endif


Thanks for help!
For an image of dimensions w by h, the first pixel is 0;h-1, the second is 1;h-1, and so on until w-1;h-1. Then follows 0;h-2.
0;h-1 ... w-1;h-1
0;h-2 ... w-1;h-2
.
.
.
0;0 ... w-1;0

And remember than each scanline is padded to a byte-length multiple of 4.
And remember than each scanline is padded to a byte-length multiple of 4.

Image is displayed almost correctly now but there's problem with byte padding. Er... What should I do?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void BMP::ParseBitmap()
{
   int width=bmih.biWidth*3; // *3 because we want width in bytes, not in pixels
   int height=bmih.biHeight;
   
   BYTE *p_start = bitmap; // bitmap is dynamic array which stores pixels of the image
   BYTE tmp[width];
for(int u=0,d=height;u<height/2,d>height/2;u++,d--)      
    {
       BYTE *pu=p_start+ width*u;
       BYTE *pd=p_start+ width*d; 
        
       memcpy(tmp,pu,width);
       memmove(pu,pd,width);
       memcpy(pd,tmp,width);
     }    
            
}
Any ideas?
ahh... Is there a quick way to fix this or I have to shift bytes myself?
I just chimed in so I don't know exactly what it is you're doing (tl;dr... sorry).

The number of bytes between scanlines if often referred to as the "pitch". Whereas the actual number of pixels in the scanline is the "width". For calculating space between each scanline, you should never use the width, but should use the pitch instead. For copying pixels and whatnot, you would use the width.

To calculate the width, you just pad up to the next 32-bit value:

1
2
3
4
5
int pitch = ((width * bitsperpixel) + 31) / 32 * 4;
// width * bitsperpixel = bits actually used to represent the row
// + 31 = to round up to the next 32-byte block
// / 32 = the number of 32-byte blocks between scanlines
// * 4 = the number of bytes between scanlines (ie:  the pitch) 


As a bonus on Windows bitmaps, as Helios already explained, images are stored "bottom up" (ie: upside down) if the height is positive. This can be simply handled by doing 2 things:

1) making the 'first row' pointer actually point to the last row in the bitmap
2) negating the pitch

But note that if the height in the bitmap is negative, then the bitmap is stored "top down" (ie: normal) so you wouldn't do the above.


Pseudo code hurrah!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int pitch = ((width * bitsperpixel) + 31) / 32 * 4;
BYTE* firstrow = whatever_is_the_pointer_to_the_pixels;

// flip the image if the height is positive
if(height > 0)
{
  firstrow += pitch * (height - 1);
  pitch = -pitch;
}
else
  height = -height;  // keep it positive for simplicity in for loops and junk

//-------- now, any X,Y coord can be accessed easily:
PIXEL* pix = &firstrow[ ((y*pitch) + x) * bytes_per_pixel ];  // couldn't be simpler!



Like I say I don't really know what you're doing, but based on what I skimmed in the thread, this looks like what you're looking for. I hope it helps.


EDIT: forgot the bytes_per_pixel bit in the X,Y access. whoops!
Last edited on
I think that's what I was looking for but why do I get pitch and width equal? Here's my code:

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
void BMP::ParseBitmap()
{   
   int width=bmih.biWidth*(bpp/8); // width of bitmap in pixels
   int pitch = ((bmih.biWidth * bpp) + 31) / 32 * 4;      // find the pitch

   int height=bmih.biHeight;

if(width==pitch) MessageBox(0,"equal","al",64); // wtf?
   BYTE tmp_r,tmp_g,tmp_b;
   BYTE *p_start = bitmap; // variable (type of unsigned char ) bitmap is buffere where pixels are stored
   BYTE tmp[width];

/* the image may be upside down so */
if(height>0)
  {   /* flip the image */
    for(int u=0,d=height;u<=height/2,d>height/2;u++,d--)      
        {
           BYTE *pu=p_start+ width*u;
           BYTE *pd=p_start+ width*d; 

           memcpy(tmp,pu,width);
           memmove(pu,pd,width);
           memcpy(pd,tmp,width);
        }      
   
   /* put colors into right order */
    for(BYTE *i=p_start;i<p_start+bmih.biSizeImage;i+=3)
      {
         tmp_g=i[0];
         tmp_r=i[1];
         tmp_b=i[2];
       
         i[0]=tmp_r;
         i[1]=tmp_g;
         i[2]=tmp_b;     
      } 
  }   
}

And where should i use the pitch?
NOTE:
If I modify to pu here
1
2
           memcpy(tmp,pu,width);
                     memmove(pu,pd,width);

like this:
1
2
3
4
 
           int my_number=30;
           memcpy(tmp,pu+my_number,width);
           memmove(pu-my_number,pd,width);

I get a good image. The problem is that my_number depends from image width and there must be a way to find my_number...

Thanks for help again...

 
   int width=bmih.biWidth*(bpp/8); // width of bitmap in pixels 


What are you doing here?

bmih.biWidth already is the width in pixels. No need for any math. Just:

 
    int width = bmi.biWidth;


1
2
if(height>0)
  {   /* flip the image */


You really shouldn't have to flip the image. Like I said, all you really have to do is change the starting pointer and invert the pitch. Having to recopy all the pixels is a waste.

 
for(BYTE *i=p_start;i<p_start+bmih.biSizeImage;i+=3)


You can't do this. There's padding on every row so a single loop like this will not align itself properly in all circumstances. You must have nested loops: One to loop through the rows and one to loop through the columns.
Topic archived. No new replies allowed.