I have a utility method which takes in unsigned char array as argument and converts it in to a *.png image
Here is the method:
#define MIME_TYPE_PNG L"image/png"
#define RED 253
#define GREEN 254
#define BLUE 255
#define MAX_GRAY 252
/// <summary>
/// Helper methods for dealing with images
/// </summary>
class ImageUtils
{
private:
static int GetEncoderClsid(const WCHAR* format, CLSID* pClsid);
static void Internal_SaveAsPNG(BITMAPINFO *gdiBitmapInfo, void *gdiBitmapData, char* imageFilePath);
public:
static void SaveAsPNG(int displayXSize, int displayYSize, char* imageFilePath, unsigned char* imageData);
};
/// <summary>
/// Writes an 8-bit PNG file at the given filePath using the imageData and imageDimensions
/// </summary>
void ImageUtils::SaveAsPNG(int displayXSize, int displayYSize, char* imageFilePath, unsigned char* imageData)
{
// Create the info header and add custom color table to it
const int COLORNUM = 256;
BITMAPINFOHEADER infoHeader;
infoHeader.biSize = sizeof(BITMAPINFOHEADER); // bytes in structure
infoHeader.biWidth = displayXSize; // width in pixels
infoHeader.biHeight = -displayYSize; // negative height indicates top-down DIB(Device Independent Bitmap)
infoHeader.biPlanes = 1; // must be 1
infoHeader.biBitCount = 8; // bits per pixel
infoHeader.biCompression = BI_RGB; // uncompressed RGB
infoHeader.biSizeImage = displayXSize * displayYSize; // size of image in bytes
infoHeader.biXPelsPerMeter = 2500; // pixels per meter X (probably exact number doesn't matter)
infoHeader.biYPelsPerMeter = 2500; // pixels per meter Y (probably exact number doesn't matter)
infoHeader.biClrUsed = COLORNUM; // number of color indices actually used by the bitmap
infoHeader.biClrImportant = 0; // all colors are important
BITMAPINFO *petBitmapInfo = (BITMAPINFO *)(new char[sizeof(BITMAPINFOHEADER) + (COLORNUM * sizeof(RGBQUAD))]);
petBitmapInfo->bmiHeader = infoHeader;
// First 253/256 colors are grays
for (int i = 0; i < COLORNUM-3; i++)
{
petBitmapInfo->bmiColors[i].rgbBlue = (BYTE)i;
petBitmapInfo->bmiColors[i].rgbGreen = (BYTE)i;
petBitmapInfo->bmiColors[i].rgbRed = (BYTE)i;
petBitmapInfo->bmiColors[i].rgbReserved = 0;
}
// Last three colors are pure red, green and blue respectively
petBitmapInfo->bmiColors[RED].rgbRed = 255;
petBitmapInfo->bmiColors[RED].rgbGreen = 0;
petBitmapInfo->bmiColors[RED].rgbBlue = 0;
petBitmapInfo->bmiColors[RED].rgbReserved = 0;
petBitmapInfo->bmiColors[GREEN].rgbRed = 0;
petBitmapInfo->bmiColors[GREEN].rgbGreen = 255;
petBitmapInfo->bmiColors[GREEN].rgbBlue = 0;
petBitmapInfo->bmiColors[GREEN].rgbReserved = 0;
petBitmapInfo->bmiColors[BLUE].rgbRed = 0;
petBitmapInfo->bmiColors[BLUE].rgbGreen = 0;
petBitmapInfo->bmiColors[BLUE].rgbBlue = 255;
petBitmapInfo->bmiColors[BLUE].rgbReserved = 0;
Internal_SaveAsPNG(petBitmapInfo, imageData, imageFilePath);
}
/// <summary>
/// Combines the BITMAPINFO and bitMapData into an image and saves it as PNG at the given location
/// </summary>
void ImageUtils::Internal_SaveAsPNG(BITMAPINFO *gdiBitmapInfo, void *gdiBitmapData, char* imageFilePath)
{
// Initialize GDI+.
Gdiplus::GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
// Get the CLSID of the PNG encoder.
CLSID encoderClsid;
int clsidResult = GetEncoderClsid(L"image/png", &encoderClsid);
// Convert filepath to wide char
size_t filePathSize = strlen(imageFilePath) + 1;
wchar_t * imageFilePath_w = new wchar_t[filePathSize];
size_t numCharsConverted = 0;
mbstowcs_s(&numCharsConverted, imageFilePath_w, filePathSize, imageFilePath, _TRUNCATE);
// Create a bitmap object from the given header and image data, and save it as png (Bitmap class handles the compression internally)
Gdiplus::Bitmap* image = new Gdiplus::Bitmap(gdiBitmapInfo, gdiBitmapData);
Gdiplus::Status stat = image->Save(imageFilePath_w, &encoderClsid, NULL);
delete image;
Gdiplus::GdiplusShutdown(gdiplusToken);
if (stat != Gdiplus::Ok)
{
string message = "Unable to save ";
message.append(imageFilePath);
throw new exception(message.c_str());
}
}
/// <summary>
/// Queries all the available image encoders and sets pClsid to the CLSID of the requested encoder
/// </summary>
int ImageUtils::GetEncoderClsid(const WCHAR* format, CLSID* pClsid)
{
UINT numEncoders = 0;
UINT sizeImageEncoder = 0;
// How many codecs are available and what's the total size?
Gdiplus::GetImageEncodersSize(&numEncoders, &sizeImageEncoder);
// Fail if no encoders are found
if (sizeImageEncoder == 0)
return -1;
// Information of all available codecs
Gdiplus::ImageCodecInfo* codecInfo = (Gdiplus::ImageCodecInfo*)(malloc(sizeImageEncoder));
if (codecInfo == NULL)
return -1;
Gdiplus::GetImageEncoders(numEncoders, sizeImageEncoder, codecInfo);
// Find out the CLSID of the requested encoder
for (UINT j = 0; j < numEncoders; ++j)
{
if (wcscmp(codecInfo[j].MimeType, format) == 0)
{
*pClsid = codecInfo[j].Clsid;
free(codecInfo);
return j;
}
}
free(codecInfo);
return -1;
}
Problem is that images which should look like this:
After some trial and error, I found out that any image with a width which is a
multiple of 4
will render correctly, whereas all others will be sheared, as is the case in the example above where the width is 50.
Can anyone shed light on this behavior and how to resolve it?