2
\$\begingroup\$

I have the following code which does 2 things:

  • Return a scanned fingerprint as base64
  • Store this print as bitmap

While the code works most of the time, sometimes an invalid bitmap is saved (I can't open the file). I think there are some improvements (padding?) that can counter this strange behavior. The 'firstPixel' is a reference that is given by the Windows Biometric Framework, as well as the Width and the Height.

PBYTE firstPixel = (PBYTE)((PBYTE)AnsiBdbRecord) + sizeof(WINBIO_BDB_ANSI_381_RECORD);

// declare bmp structures
//https://msdn.microsoft.com/en-us/library/windows/desktop/dd183391%28v=vs.85%29.aspx
//Declare pbitmapinfo and initialize
//PBITMAPINFO info;
//info = (PBITMAPINFO)LocalAlloc(LPTR, sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * 256);
//Declare fileheader
BITMAPFILEHEADER bmfh;
BITMAPINFOHEADER bmih;
RGBQUAD rq[256];
for (int rgbitem = 0; rgbitem<256; rgbitem++)
{
    rq[rgbitem].rgbBlue = BYTE(rgbitem);
    rq[rgbitem].rgbGreen = BYTE(rgbitem);
    rq[rgbitem].rgbRed = BYTE(rgbitem);
    rq[rgbitem].rgbReserved = BYTE(0);
}
// and initialize them to zero
memset(&bmfh, 0, sizeof(BITMAPFILEHEADER));
memset(&bmih, 0, sizeof(BITMAPINFOHEADER));

//---------padding
//calculate scanline 
int padding = 0;
int scanlinebytes = Width;
while ((scanlinebytes + padding) % 4 != 0)
    padding++;
//calculate new width, which will be used further instead of original Width
int paddedWidth = scanlinebytes + padding;
//calculate new imageSize
int imageSize = paddedWidth * Height;
//----------padding buffer
//a new buffer is created to copy over the pixels from the firstPixel array
//this is required for padding (to make it easier, we also copy over the original array in case no padding is required)
PBYTE paddedPixel = new BYTE[imageSize];
//set all pixels to zero, so we do not have to add padding manually
memset(paddedPixel, 0, imageSize);
long oldpos = 0;
long newpos = 0;
for (int y = 0; y < Height; y++)
{
    for (int x = 0; x < Width; x++)
    {
        oldpos = y * Width + x;     // position in original buffer
        newpos = y * paddedWidth + x;   // position in padded buffer
        paddedPixel[newpos] = firstPixel[oldpos]; //fill padded buffer
    }
}
//----------end of padding buffer
//---------end of padding

// fill the infoheader
bmih.biSize = sizeof(BITMAPINFOHEADER);
bmih.biWidth = paddedWidth;
bmih.biHeight = Height;
bmih.biPlanes = 1; // we only have one bitplane
bmih.biBitCount = PixelDepth; 
bmih.biCompression = BI_RGB;
bmih.biSizeImage = 0; 
bmih.biXPelsPerMeter = 0x0ec4; // paint and PSP use this values
bmih.biYPelsPerMeter = 0x0ec4;
bmih.biClrUsed = 0; // we are in RGB mode and have no palette
bmih.biClrImportant = 0; // all colors are important

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

//declare and initialize bitmapinfoheader based on bitmapinfo
int totalSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * 256 + imageSize;

//Put everything in a temporary buffer, and convert buffer to base64
unsigned char * bmpBuff = new unsigned char[totalSize];
int j = 0;
memcpy((void *)(&(bmpBuff[j])), (void *)&bmfh, sizeof(BITMAPFILEHEADER));
j += sizeof(BITMAPFILEHEADER);
memcpy((void *)(&(bmpBuff[j])), (void *)&bmih, sizeof(BITMAPINFOHEADER));
j += (sizeof(BITMAPINFOHEADER));
memcpy((void *)(&(bmpBuff[j])), (void *)&rq, sizeof(RGBQUAD) * 256);
j += sizeof(RGBQUAD) * 256;
memcpy((void *)(&(bmpBuff[j])), (void *)&paddedPixel[0], imageSize);
std::string encodedFile = base64_encode(bmpBuff, totalSize);

delete bmpBuff;

//Write file. 
unsigned long bwritten;
// now we open the file to write to
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, &bmih, sizeof(BITMAPINFOHEADER), &bwritten, NULL) == false)
{
    DWORD dw = GetLastError();
    CloseHandle(file);
}
//write rgbquad for black
if (WriteFile(file, &rq, + sizeof(RGBQUAD) * 256, &bwritten, NULL) == false)
{
    DWORD dw = GetLastError();
    CloseHandle(file);
}
// write image data
if (WriteFile(file, &paddedPixel[0], imageSize, &bwritten, NULL) == false)
{
    DWORD dw = GetLastError();
    CloseHandle(file);
}

// and clean up
CloseHandle(file);
return encodedFile;
}
\$\endgroup\$
  • 3
    \$\begingroup\$ Consider not only posting the functions body, but the whole function. \$\endgroup\$ – Deduplicator Oct 26 '15 at 18:52
  • 1
    \$\begingroup\$ BMP files have their pixel rows padded to 4 bytes. Your function doesn't do that. You should read this: Wikipedia BMP file format. \$\endgroup\$ – JS1 Oct 27 '15 at 1:16
  • 1
    \$\begingroup\$ This code has comments indicating that a 24bpp bitmap is being set up, but the code actually sets up an 8bpp bitmap with a grayscale palette. I found this confusing, especially considering the definition of PixelDepth was never given. \$\endgroup\$ – Christopher Oicles Oct 27 '15 at 1:38
  • \$\begingroup\$ @JS1, thanks. Added padding (see question update), and it never fails now. \$\endgroup\$ – Michael Oct 27 '15 at 8:27
  • \$\begingroup\$ @ChristopherOicles, PixelDepth always needs to be 8, otherwise I will never enter this function. You are correct that the comments indicate differently. \$\endgroup\$ – Michael Oct 27 '15 at 8:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.