Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ bitmap to base64

I have the following code to create a bitmap:

//raw data
PBYTE firstPixel = (PBYTE)((PBYTE)AnsiBdbRecord) + sizeof(WINBIO_BDB_ANSI_381_RECORD);

// declare other bmp structures
BITMAPFILEHEADER bmfh;
BITMAPINFOHEADER info;
RGBQUAD rq[256];
// create the grayscale palette 
for (int i = 0; i<256; i++)
{
    rq[i].rgbBlue = i;
    rq[i].rgbGreen = i;
    rq[i].rgbRed = i;
    rq[i].rgbReserved = 0;
}
//RGBQUAD bl = { 0,0,0,0 };  //black color
//RGBQUAD wh = { 0xff,0xff,0xff,0xff }; // white color

// andinitialize them to zero
memset(&bmfh, 0, sizeof(BITMAPFILEHEADER));
memset(&info, 0, sizeof(BITMAPINFOHEADER));

// fill the fileheader with data
bmfh.bfType = 0x4d42; // 0x4d42 = 'BM'
bmfh.bfReserved1 = 0;
bmfh.bfReserved2 = 0;
bmfh.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER); // + padding;
bmfh.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD);

// fill the infoheader
info.biSize = sizeof(BITMAPINFOHEADER);
info.biWidth = Width;
info.biHeight = Height;
info.biPlanes = 1; // we only have one bitplane
info.biBitCount = PixelDepth; // RGB mode is 24 bits
info.biCompression = BI_RGB;
info.biSizeImage = 0; // can be 0 for 24 bit images
info.biXPelsPerMeter = 0x0ec4; // paint and PSP use this values
info.biYPelsPerMeter = 0x0ec4;
info.biClrUsed = 0; // we are in RGB mode and have no palette
info.biClrImportant = 0; // all colors are importantenter code here

And I save it as follows:

    HANDLE file = CreateFile(bmpfile, GENERIC_WRITE, FILE_SHARE_READ,
    NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (file == NULL)
{
    DWORD dw = GetLastError();
    CloseHandle(file);
}

// write file header
if (WriteFile(file, &bmfh, sizeof(BITMAPFILEHEADER), &bwritten, NULL) == false)
{
    DWORD dw = GetLastError();
    CloseHandle(file);
}
// write infoheader
if (WriteFile(file, &info, sizeof(BITMAPINFOHEADER), &bwritten, NULL) == false)
{
    DWORD dw = GetLastError();
    CloseHandle(file);
}
//write rgbquad for black
if (WriteFile(file, &rq, sizeof(rq), &bwritten, NULL) == false)
{
    DWORD dw = GetLastError();
    CloseHandle(file);
}
// write image data
if (WriteFile(file, &firstPixel[0], imageSize, &bwritten, NULL) == false)
{
    DWORD dw = GetLastError();
    CloseHandle(file);
}

// and clean up
CloseHandle(file);

I think the above is the standard way of saving bitmap images. However, instead of saving the image, I want it to be available as BASE64 and pass it in a HTTP Post. Therefore, this question relates to this one, but I am having a lot of difficulties converting the bmp structure to BASE64. I have taken the BASE64 encoder from here, but I have no idea how to pass the BMPFILEHEADER, BMPINFOHEADER, RGBQUAD, and raw data structure as a parameter to the BASE64 encoder.

Any thoughts or pointers on how to combine the information I gathered?

UPDATE

Thanks to Roman Pustylnikov, I have gotten a bit farther already: I'm creating a struct like this:

    struct ImageBuffer
{
    BITMAPFILEHEADER bfheader;
    BITMAPINFOHEADER infobmp;
    RGBQUAD rgb[256];
    PBYTE bitmap;
};

Fill it as follows:

ImageBuffer capture;
capture.bfheader = bmfh;
capture.infobmp = info;
// create the grayscale palette 
for (int i = 0; i<256; i++)
{
    capture.rgb[i].rgbBlue = i;
    capture.rgb[i].rgbGreen = i;
    capture.rgb[i].rgbRed = i;
    capture.rgb[i].rgbReserved = 0;
}
capture.bitmap = firstPixel;

And convert it as follows:

int totalSize = sizeof(capture.bfheader) + sizeof(capture.infobmp) + sizeof(capture.rgb) + imageSize;
std::string encodedImage = base64_encode(reinterpret_cast<const unsigned char*>(&capture), totalSize);

However, it gives me an invalid bitmap. Also, when I load the bitmap from disk (the one that is generated with writefile), I get a different base64 string. I use C# code to compare the two Base64 strings:

            // generated base64 string
        string test = "Put base64string generated from C++ here";
        byte[] imageBytes = Convert.FromBase64String(test);

        // generate the same string based on the actual bmp
        byte[] data = File.ReadAllBytes(@"c:\successtest.bmp");
        string original = Convert.ToBase64String(data);

UPDATE TWO: solution

The solution can be found in the latest update of Roman Pustylnikov's answer.

like image 870
Michael Avatar asked Oct 23 '15 14:10

Michael


1 Answers

You can do it without file in the middle, using the following example as encoder/decoder:

The first approach is to create the contiguous memory and put it as an input:

int size=sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD)*256 + imageSize ;

   unsigned char * bmpBuff = new char[size];
   int i = 0;
   memcpy((void *)( &( bmpBuff[i] )), (void *) &bfheader,sizeof(BITMAPFILEHEADER) );
   i+=sizeof(BITMAPFILEHEADER);
   memcpy((void *)( &( bmpBuff[i] )), (void *) &infobmp,sizeof(BITMAPINFOHEADER) );
   i+=sizeof(BITMAPINFOHEADER);
   memcpy((void *)( &( bmpBuff[i] )), (void *) &rq,sizeof(RGBQUAD)*256 );
   i+=sizeof(RGBQUAD)*256;
   memcpy((void *)( &( bmpBuff[i] )), (void *) firstPixel, imageSize );
   std::string encodedImage = base64_encode(bmpBuff, size);

The cons of this approach is that you need to duplicate the memory.

Another approach is to handle the "lost triplet". For this we'll need to define a structure:

struct ScreenShotBuffer
{
    BITMAPFILEHEADER bfheader;
    BITMAPINFO infobmp;
    RGBQUAD rgb[256];      
};

Now here comes the tricky part. Since the encoding handles the triples of bytes, you need to handle the border between two buffers (I haven't tested this so it might contain bugs, just a general approach).

int size=sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD)*256;


unsigned char *info = einterpret_cast<const unsigned char*> &screenshotInfo;
unsigned char *img = einterpret_cast<const unsigned char*> firstPixel;

std::string encodedInfo = base64_encode(info , size);
std::string encodedLostTriplet = "";

int offset = size%3;

unsigned char lostTriplet[3];
   if (offset) {
     lostTriplet[0]=info[size-offset];
     if (offset==2) 
         lostTriplet[1] = info[size-offset+1];
     else
         lostTriplet[1] = img[0];
     lostTriplet[2] = img[2-offset];
     encodedLostTriplet = base64_encode(lostTriplet, 3);
   }
   else {
      offset=3;
   }

std::string encodedData = base64_encode(reinterpret_cast<const unsigned char*> &img[3-offset], imageSize - (3 - offset) );

std::string encodedImage = encodedInfo + lostTriplet + encodedData;

A little bit messy but should work on the same memory.

like image 127
Roman Pustylnikov Avatar answered Sep 29 '22 23:09

Roman Pustylnikov