C++, Bitmap Image Arrays and byte padding

Hello everyone

I am writing a class which loads a bitmap image into a one dimension char* array.

This class has methods to allow for resampling and cropping the image before saving the bitmap image. It works perfectly for all images in which the width is divisible by 4. However when the width is not divisible by 4 the resulting bitmap is all mixed up.

I have spent much of the day googling this problem but to no avail. I know it is a problem with making sure the scanlines are DWORD aligned, and I believe I have done that correctly. I suspect that the problem is that I need to take the padding into account during the crop for loops but am lost to how to do this.

BTW: Coding on Linux using GCC

Anyway, hope somebody can help.

Many Thanks

PS: If this is not the most appropriate forum then please forgive my intrusion.

The following code is a cut down version from my bitmap class. I have removed methods which are not needed to make reading a little easier. Sorry, still have to post lots of 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
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
#include "BMP.h"

// FIXME (nikki#1#): Portrait bug on images of specific sizes
// TODO (nikki#1#): Memory leak checks


// THIS METHOD WRITES A BITMAP FILE FROM THE CHAR ARRAY .
bool BMP::saveBMP(string fileName, string *err)
{

    FILE *filePtr;
    
    //open filename in write binary mode
    filePtr = fopen(fileName.c_str(),"wb");

    // CHECK FILE CAN BE WRITTEN
    if (filePtr == NULL) {
        *err =  "Could not write bitmap image file.\n";
        return false;
    }

    // Write the BitmapFileHeader & BitmapInfoHeader
    fwrite(&fileHeader, sizeof(BITMAPFILEHEADER), 1, filePtr);
    fwrite(&infoHeader,sizeof(BITMAPINFOHEADER), 1, filePtr);

    //move file point to the begging of bitmap data
    fseek(filePtr,fileHeader.bfOffBits,SEEK_SET);

    // ACTUALLY WRITE THE BITMAP FILE TO DISK
    fwrite(imgArray,  (infoHeader.biWidth * 3)  * infoHeader.biHeight,1, filePtr);

    fclose(filePtr);

    return true;

}

// CROPS AN IMAGE FROM THE CHAR ARRAY BASED ON X, Y, W, H SUPPLIED
// FROM THE CALLER: Returns the raw array to the caller
unsigned char * BMP::getSubImage(int x, int y, int w, int h)
{

    int width = (origWidth * 3);

    int p = 0;
    int xx = x;
    int yy =  (origHeight - (h + y)) ;

    unsigned char * tmpArray = new unsigned char [(((w+padding)*h)*3)];


    for(int m = 0; m < h; m++) {

        for(int n = 0; n < w; n++) {

            int b = (width*(yy-1)+xx*3-2)-1;
            int g = (width*(yy-1)+xx*3-1)-1;
            int r = (width*(yy-1)+xx*3)-1;

            tmpArray[p] = (char)imgArray[b];
            tmpArray[p+1] = (char)imgArray[g];
            tmpArray[p+2] = (char)imgArray[r];

            p+=3;
            xx++;
        }

        if(xx >= w) xx = x;
        yy++;
    }

    imgArray = tmpArray;
    calcHeaders(w, h, 24); // MAKE A CALL TO UPDATE THE BITMAP HEADERS

    return tmpArray;

}

// GENERIC METHOD TO UPDATE THE BITMAP HEADERS
void BMP::calcHeaders(int width, int height, int bpp)
{

    int headerSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);

//    int rowSize = (((bpp * width ) + 31) / 32) * 4;
//    int pixelArraySize = rowSize * height;
//    int fileSize = headerSize  + pixelArraySize;

    int padding = (4 - (width * 3) % 4) %4;
    int padWidth = (width * 3) + padding;

    fileHeader.bfOffBits  = headerSize;
    fileHeader.bfReserved1 = 0;
    fileHeader.bfReserved2 = 0;
    fileHeader.bfType =  0x4D42;
    fileHeader.bfSize = ((width * height) * 3 + headerSize) ;

    infoHeader.biSize = sizeof(BITMAPINFOHEADER);
    infoHeader.biWidth = width;
    infoHeader.biHeight = height;
    infoHeader.biPlanes = 1;
    infoHeader.biBitCount = bpp;
    infoHeader.biCompression = 0;
    infoHeader.biSizeImage = ((width * 3) + padding) * height;
    infoHeader.biXPelsPerMeter = 0;
    infoHeader.biYPelsPerMeter = 0;
    infoHeader.biClrUsed = 0;
    infoHeader.biClrImportant = 0;

    origWidth = width;
    origHeight = height;
}
Last edited on
I do not see where you are padding the scanlines of the bitmap. I only see you using the calculated padding value for the 'biSizeImage' value in the header... but I believe that value is only used if the bitmap is compressed.

Line 30 is where you are actually writing the bitmap data, and there you are not adding any padding between rows. Unless the padding is actually in your array, writing the entire array to the file at once will not work.

You have 2 options:

1) Add the padding to your pixel data when the image is resized.

or

2) Write the bitmap data one row at a time, adding a few padding byte between rows.


#2 is probably easier.


Also, FYI... the number of bytes per row is commonly referred to as the "pitch" whereas the number of pixels per row is the "width".

Given a bpp and a width, the pitch can be calculated as follows:

 
pitch = ((width * bitsperpixel) + 31) / 32 * 4;


With that... the padding bytes can be calculated as follows:

 
padding = pitch - (width * BYTESperpixel);



You're already pretty much doing this... so I probably didn't have to mention it. I just wanted to throw out some terminology.
Hi Disch

Thank you ever so much, I now have my class working :) I added the following lines of code to my crop method:

1
2
3
4
5
6
      if(xx >= w) {
         for(int i = 0; i < padding; i++){
              tmpArray[p] = '/0';
              p++;
       }
      }


One more question that you maybe able to answer: Is it possible to use a integer array instead of a char? I have tried this, the code compiles and runs fine but there is nothing in the array.

I appreciate your help
Last edited on
Topic archived. No new replies allowed.