C++: Grayscale bitmap header and live painting + opencv image processing - c++

I am trying to display live images coming from a monochrome camera (Adimec N5A/CXP, with GenIcam standard).
From an example coming from the supplier (but in RGB 24), I am more or less able to display the image but the color rendering is very strange (colors and shadows instead of grayscale). I guess I did something wrong in the bitmap header declaration:
bitmapInfo = (LPBITMAPINFO)malloc(sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD));
bitmapInfo->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bitmapInfo->bmiHeader.biPlanes = 1;
bitmapInfo->bmiHeader.biBitCount = 8; // 24
bitmapInfo->bmiHeader.biCompression = BI_RGB;
bitmapInfo->bmiHeader.biSizeImage = 0;
bitmapInfo->bmiHeader.biXPelsPerMeter = 0;
bitmapInfo->bmiHeader.biYPelsPerMeter = 0;
bitmapInfo->bmiHeader.biClrUsed = 256;
bitmapInfo->bmiHeader.biClrImportant = 0;
bitmapInfo->bmiHeader.biWidth = (LONG)width;
bitmapInfo->bmiHeader.biHeight = -(LONG)height;
/*
RGBQUAD* bmiColors = (RGBQUAD*)(bitmapInfo->bmiColors);
for (size_t index = 0; index < 256; ++index)
{
bmiColors[index].rgbBlue = (BYTE)index;
bmiColors[index].rgbGreen = (BYTE)index;
bmiColors[index].rgbRed = (BYTE)index;
bmiColors[index].rgbReserved = 0;
}
*/
I found in bmiColors field of BITMAPINFO structure that the 'biClrUsed' should be set to 256. Then I do not know if I need to write a block to describe 'bmiColors'. I would like to use only one byte per pixel instead of the r,g and b components.
Then further in the program (in the function "OnPaint"), it uses the function "SetDIBitsToDevice" to display in a window previously created. The image pointer is first retrieved:
unsigned char *imagePtr = liveState.currentBuffer->getInfo<unsigned char *>(liveState.grabber, gc::BUFFER_INFO_BASE);
Then the image is displayed:
::SetDIBitsToDevice(dc, 0, 0, (DWORD)liveState.width, (DWORD)liveState.height, 0, 0, 0, (UINT)liveState.height, imagePtr, liveState.bitmapInfo, DIB_RGB_COLORS);
I don't know what to put instead of DIB_RGB_COLORS as the last parameter. I only found another value for this parameter that is DIB_PAL_COLORS. I guess there should be an option for grayscale?
This is the first step of my program... if you have any suggestion on how to push the image pointer into an opencv container I would also be very happy :-).
Many thanks in advance !

It seems you were quite close. The way to display grayscale images is to use a palette. This is simply 256 RGB entries representing all the shades between black and white:
std::vector<RGBQUAD> pal(256);
for (int32_t i(0); i < 256; ++i) {
pal[i].rgbRed = pal[i].rgbGreen = pal[i].rgbBlue = i;
pal[i].rgbReserved = 0;
}
First of all, you need to allocate enough memory to hold BITMAPINFOHEADER as well as 256 RGBQUAD entries defining the palette to use.
int32_t const bmi_size(sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * 256);
Allocate the structure. I put it on stack using _alloca, so I don't need to worry about cleanup.
BITMAPINFO* bmi(static_cast<BITMAPINFO*>(alloca(bmi_size)));
You need to set the following members of BITMAPINFOHEADER, the rest can be left as zeros.
bmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmi->bmiHeader.biWidth = static_cast<LONG>(width);
bmi->bmiHeader.biHeight = static_cast<LONG>(-height);
bmi->bmiHeader.biPlanes = 1;
bmi->bmiHeader.biBitCount = 8;
bmi->bmiHeader.biCompression = BI_RGB;
Note: Since we have a complete 256 entry palette, biClrUsed can be left set to 0. From docs:
If this value is zero, the bitmap uses the maximum number of colors corresponding to the value of the biBitCount member...
Next, set up the palette (that's basically the bit of your code that's commented out).
for (uint32_t i(0); i < 256; ++i) {
if (pal.size() > i) {
bmi->bmiColors[i] = pal[i];
} else {
bmi->bmiColors[i].rgbRed
= bmi->bmiColors[i].rgbGreen
= bmi->bmiColors[i].rgbBlue
= bmi->bmiColors[i].rgbReserved = 0;
}
}
Note: The above code is from a generic paletted image rendering function. For smaller palettes it fills the unused colours with black. I suppose this could be refactored to use fewer entries along with biClrUsed set to appropriate value.
Now the bitmap header is ready. In your case, the call to SetDIBitsToDevice would still use DIB_RGB_COLORS since "The color table contains literal RGB values."
I use CreateDIBitmap to create a DDB, which I can later render using BitBlt.
HBITMAP bitmap = ::CreateDIBitmap(dc
, &bmi->bmiHeader
, CBM_INIT
, data // Pointer to raw pixel data
, bmi
, DIB_RGB_COLORS);

Related

Convert FreeType GlyphSlot Bitmap To Vulkan BGRA

I'm trying to convert a FreeType GlyphSlot Bitmap to Vulkan BGRA format.
void DrawText(const std::string &text) {
// WIDTH & HEIGHT == dst image dimensions
FT_GlyphSlot Slot = face->glyph;
buffer.resize(WIDTH*HEIGHT*4);
int dst_Pitch = WIDTH * 4;
for (auto c : text) {
FT_Error error = FT_Load_Char(face, c, FT_LOAD_RENDER);
if (error) {
printf("FreeType: Load Char Error\n");
continue;
}
auto char_width = Slot->bitmap.width;
auto char_height = Slot->bitmap.rows;
uint8_t* src = Slot->bitmap.buffer;
uint8_t* startOfLine = src;
for (int y = 0; y < char_height; ++y) {
src = startOfLine;
for (int x = 0; x < char_width; ++x) {
// y * dst_Pitch == Destination Image Row
// x * 4 == Destination Image Column
int dst = (y*dst_Pitch) + (x*4);
// Break if we have no more space to draw on our
// destination texture.
if (dst + 4 > buffer.size()) { break; }
auto value = *src;
src++;
buffer[dst] = 0xff; // +0 == B
buffer[dst+1] = 0xff; // +1 == G
buffer[dst+2] = 0xff; // +2 == R
buffer[dst+3] = value; // +3 == A
}
startOfLine += Slot->bitmap.pitch;
}
}
}
This is giving me garbled output. I'm not sure what I need to do to properly convert to Vulkan B8G8R8A8. I feel like moving from left to right in the buffer we write to our Vulkan texture is incorrect and maybe Vulkan is expecting I add the pixels into the buffer in a different way?
I understand this code will write each letter on top of one another, I will implement taking advantage of Slot->advance after I can properly draw at least a single letter.
One problem is that you resize buffer with every character (which will leave the previous data at the start of the newly allocated space) but when storing the data for the new character c you overwrite the start of the buffer since dst is 0. You probably want to set dst the buffer.size() from before the resize call.
int dst = /*previous buffer size*/;
The issue was due to the fact that I had VkImageCreateInfo tiling set to VK_IMAGE_TILING_OPTIMAL. After changing it to VK_IMAGE_TILING_LINEAR I received the correct output.
Taken straight from https://www.khronos.org/registry/vulkan/specs/1.1-extensions/man/html/VkImageTiling.html
VK_IMAGE_TILING_OPTIMAL specifies optimal tiling (texels are laid out
in an implementation-dependent arrangement, for more optimal memory
access).
VK_IMAGE_TILING_LINEAR specifies linear tiling (texels are laid out in
memory in row-major order, possibly with some padding on each row).
While I may not be rendering garbage now, my letters are still backwards and seemingly drawing from right to left instead of left to right.
You can see the green 'the' in the top right corner.

Image from SQL Database to CImage

I'm struggling at the moment and hope that someone can help me.
I have to get an Image out of a SQL database (like with SQLGetData) and than convert that data to a CImage so I can view it in my program.
Thanks for any help!
SQLGetData(m_Hstmt, col, SQL_C_BINARY, BinaryPtr, 0, &cbData)
The problem can be reduced to loading a CImage from a byte array, since that is what you get from SQLGetData.
You did not indicate whether you mean to use ATL or MFC, but in both cases it is a little bit awkward as there is no such thing as a public ::LoadFromBuffer function.
This answer should do:
https://stackoverflow.com/a/6759701/1132334
It explains how to create a bitmap structure from a byte buffer and construct a CImage from there.
It is going to be tricky if you need to handle different picture formats. In this case, write the raw bytes to a memory mapped file and then use the CImage::Load(IStream*) overload.
EDIT: its all been done before... https://stackoverflow.com/a/14035492/1132334 and https://stackoverflow.com/a/38710933/1132334
Thanks for all the replies so far. Acoording to #dlatikay answer, this is my code so far. But I'm not sure about the types and somehow my Image stayes black (when I save it to file system)
heres my code so far.
SQLLEN cbData;
CImage image;
BYTE* imgBits;
m_Rc = SQLGetData(m_Hstmt, 1, SQL_C_BINARY, imgBits, 0, &cbData);
if (SQL_SUCCEEDED(m_Rc))
{
width = 317;
height = 159;
BITMAPINFOHEADER bmInfohdr;
// Create the header info
bmInfohdr.biSize = sizeof(BITMAPINFOHEADER);
bmInfohdr.biWidth = width;
bmInfohdr.biHeight = -height;
bmInfohdr.biPlanes = 1;
bmInfohdr.biBitCount = 8 * 8;
bmInfohdr.biCompression = BI_RGB;
bmInfohdr.biSizeImage = width*height * 8;
bmInfohdr.biXPelsPerMeter = 0;
bmInfohdr.biYPelsPerMeter = 0;
bmInfohdr.biClrUsed = 0;
bmInfohdr.biClrImportant = 0;
BITMAPINFO bmInfo;
bmInfo.bmiHeader = bmInfohdr;
bmInfo.bmiColors[0].rgbBlue = 255;
// Allocate some memory and some pointers
unsigned char * p24Img = new unsigned char[width*height * 3];
BYTE *pTemp, *ptr;
pTemp = (BYTE*)imgBits;
ptr = p24Img;
// Convert image from RGB to BGR
for (DWORD index = 0; index < width*height; index++)
{
unsigned char r = *(pTemp++);
unsigned char g = *(pTemp++);
unsigned char b = *(pTemp++);
*(ptr++) = b;
*(ptr++) = g;
*(ptr++) = r;
}
// Create the CImage
image.Create(width, height, 8, NULL);
image.Save(_T("c:\\temp\\image1.bmp")); // for testing
}

How to convert Windows Bitmap into Actionscript Bitmap in C++

to circumvent some (a lot) of problems with the Actionscript Camera API on Windows 8 Systems,
I decided to create a native extension to deal with the camera.
Right now, the camera part and all the glue to communicate with the AIR Runtime is actually working, so clicking on a button in AIR will open a new Windows window that will return a System::Drawing::Bitmap.
My task would be now to
a) Create a FREBitmapData object and
b) Fill in the BitmapData from the Windows Bitmap.
Should be easy, I thought, many days ago... As I'm not really familiar with C++ I didn't get this to work at all.
Here's what I tried so far:
bmp = form1->bitmap; // bmp is a handle to the System::Drawing::Bitmap returned from the external window
// Lock the bitmap's bits.
Rectangle rect = Rectangle(0, 0, bmp->Width, bmp->Height);
System::Drawing::Imaging::BitmapData^ bmpData = bmp->LockBits(rect, System::Drawing::Imaging::ImageLockMode::ReadWrite, bmp->PixelFormat);
// Get the address of the first line.
IntPtr ptr = bmpData->Scan0;
// Declare an array to hold the bytes of the bitmap.
// This code is specific to a bitmap with 24 bits per pixels.
int inputLength = Math::Abs(bmpData->Stride) * bmp->Height;
array<Byte>^ input = gcnew array<Byte>(inputLength);
// Copy the RGB values into the array.
System::Runtime::InteropServices::Marshal::Copy(ptr, input, 0, inputLength);
// Unlock the bits.
bmp->UnlockBits(bmpData);
// Create a FREByteArray to hold the data.
// Don't know, if this is necessary
FREObject* outputObject;
FREByteArray* outputBytes = new FREByteArray;
outputBytes->length = inputLength;
outputBytes->bytes = (uint8_t *) &input;
FREAcquireByteArray(outputObject, outputBytes);
// now copy it over
memcpy(outputBytes->bytes, &input, inputLength);
FREReleaseByteArray(outputObject);
// we create a new instance of BitmapData here,
// as we cannot simply pass it over in the args,
// because we don't know it's correct size at extension creation
FREObject* width;
FRENewObjectFromUint32(bmp->Width, width);
FREObject* height;
FRENewObjectFromUint32(bmp->Height, height);
FREObject* transparent;
FRENewObjectFromBool(uint32_t(0), transparent);
FREObject* fillColor;
FRENewObjectFromUint32(uint32_t(0xFFFFFF), fillColor);
FREObject obs[4] = { width, height, transparent, fillColor };
// we create some Actionscript Intsances here, we want to send back
FREObject* asBmpObj;
FRENewObject("BitmapData", 4, obs, asBmpObj, NULL);
// Now we have our AS bitmap data, copy bytes over
FREBitmapData* asData;
FREAcquireBitmapData(asBmpObj, asData);
// Now what? asData->bits32 won't accept array<Bytes> or any other value I've tried.
return asBmpObj;
The basic idea was:
a) find out the size and bit-depth of the original Win Bitmap (size is determinded by cam resolution picked in the Camera window)
b) write it's bytes to an array. Convert to 32 bits as necessary. (Still missing any idea.)
c) create AS Bitmap of the same size. Bit-depth must always be 32.
d) copy over array to AS Bitmap.
But I just can't achieve this.
Any advice? Thank you!
I don't think the following straight copy will work.
// Copy the RGB values into the array.
System::Runtime::InteropServices::Marshal::Copy(ptr, input, 0, inputLength);
You have to convert pixel by pixel. I don't know how to convert it to FREBitmapData. Here are the examples you can following on msdn
I finally figured it out:
the code below doesn't deal with the 24to32 bit conversion though, but it actually works in my application quite well, so I thought, i might share it:
FREObject launch(FREContext ctx, void* funcData, uint32_t argc, FREObject argv[])
{
System::Drawing::Bitmap^ windowsBitmap;
SKILLCamControl::CamControlForm^ form1;
form1 = gcnew SKILLCamControl::CamControlForm();
DialogResult dr;
// Show testDialog as a modal dialog and determine if DialogResult = OK.
dr = form1->ShowDialog();
if (dr == DialogResult::OK) {
windowsBitmap = form1->bitmap;
int bmpW = windowsBitmap->Width;
int bmpH = windowsBitmap->Height;
// we create a new instance of BitmapData here,
// as we cannot simply pass it over in the args,
// because we don't know it's correct size at extension creation
FREObject width;
FRENewObjectFromUint32( uint32_t(bmpW), &width);
FREObject height;
FRENewObjectFromUint32( uint32_t(bmpH), &height);
FREObject transparent;
FRENewObjectFromBool( uint32_t(0), &transparent);
FREObject fillColor;
FRENewObjectFromUint32( uint32_t(0xFF0000), &fillColor);
FREObject obs[4] = { width, height, transparent, fillColor };
FREObject freBitmap;
FRENewObject((uint8_t *)"flash.display.BitmapData", 4, obs, &freBitmap , NULL);
FREBitmapData2 freBitmapData;
FREAcquireBitmapData2(freBitmap, &freBitmapData);
// is inverted?
if (&freBitmapData.isInvertedY != (uint32_t*)(0) ) windowsBitmap->RotateFlip(RotateFlipType::RotateNoneFlipY);
int pixelSize = 4;
//Rect rect( 0, 0, freBitmap.width, freBitmap.height );
System::Drawing::Rectangle rect(0, 0, bmpW, bmpH);
BitmapData^ windowsBitmapData = windowsBitmap->LockBits(rect, ImageLockMode::ReadOnly, PixelFormat::Format32bppArgb);
for (int y = 0; y < bmpH ; y++)
{
//get pixels from each bitmap
byte* oRow = (byte*)windowsBitmapData->Scan0.ToInt32() + (y * windowsBitmapData->Stride);
byte* nRow = (byte*)freBitmapData.bits32 + (y * freBitmapData.lineStride32 * 4);
for (int x = 0; x < bmpW ; x++)
{
// set pixels
nRow[x * pixelSize] = oRow[x * pixelSize]; //B
nRow[x * pixelSize + 1] = oRow[x * pixelSize + 1]; //G
nRow[x * pixelSize + 2] = oRow[x * pixelSize + 2]; //R
}
}
// Free resources
FREReleaseBitmapData(freBitmap);
FREInvalidateBitmapDataRect(freBitmap, 0, 0, bmpW, bmpH);
windowsBitmap->UnlockBits(windowsBitmapData);
delete windowsBitmapData;
delete windowsBitmap;
return freBitmap;
}
else if (dr == DialogResult::Cancel)
{
return NULL;
}
return NULL;
}
I dont use C++ myself so this is not a full answer but just something to consider...
Bitmap data is universal raw pixel data. It should be passable within different software. Unless you are actually creating .BMP files with header etc??
...that will return a System::Drawing::Bitmap does this mean you have a bitmap's data held by C++ (as raw uncompressed RGBA pixels)? If so then just either put that inside a byteArray and send to AS3 or a if you can get that bitmap copied to the Windows clipboard then use AS3 to read from clipboard into a new AS3 Bitmap.
these might help you:
AS3: Copy image from clipboard
AS3: Serialize Bitmaps : Scroll down to the section ByteArray to BitmapData (for this to work you must first store the C++ bitmap bytes as a file call it what you want, example tempIMG.dat or myPIc.bin or whatever since file extension does not really matter just that you need a loadable URL).

What's the best way to read texture info from an image file and get to the pixels using SDL2?

You can create a texture in SDL2 using CreateTexture() and then get access to the pixels in that texture using LockTexture(). But in order to do so you need to have passed the SDL_TEXTUREACCESS_STREAMING flag to the CreateTexture call.
There's a fairly standard helper library for loading images called SDL_image. I use it to read image files into textures (textures are graphics card resident images for the casual observer). I'm currently loading my textures using IMG_LoadTexture(). My problem is I can't see how to set the SDL_TEXTUREACCESS_STREAMING flag in this case. So I can't get pixel data for textures loaded with SDL_image?
The reason I want to get to the pixels is to extract nine-patch data from them. (I may well end up having 9 textures). So I only need this info once at the start and I only need to read the texture data, not write it. I'd also like to use preexisting image file reading libraries if at all possible.
So the question is: What's the best way to read texture info from an image file and get to the pixels using SDL2?
Decided it was better to do it with surfaces and then convert to textures. This code works. It only does the top left corner of the nine-patch to keep it simple. (This also ignores the complication of the nine patch sizing info in the first row and column of the nine patch image).
typedef unsigned char byte_t;
NinePatch::NinePatch(const string fname, SDL_Renderer * renderer) {
// get the surface and the bits per pixel
SDL_Surface * surface = IMG_Load(fname.c_str());
int bytes_per_pixel = surface->format->BytesPerPixel;
// keep things simple by only looking at 4 byte/pixel nine-patches
if (bytes_per_pixel != 4) {
log_msg("Loading " + fname +
" expecting pixel data to be 4 but it has: " +
to_string(bytes_per_pixel));
exit(1);
}
// offsets into the surface that divide the surface into a nine-patch
unsigned int left, right, top, bottom;
// find the widths we need by looking at the top row of pixels
byte_t * ptr = (byte_t*)surface->pixels;
uint32_t pixel, last_pixel = 0;
for (int i = 0; i < surface->w; i++) {
// we know they're 4 byte pixels cause otherwise we don't get here.
pixel = *(uint32_t*)ptr;
// look for "edges" in the top row of pixel data
if (pixel > last_pixel) {
left = i;
}
else if (pixel < last_pixel) {
right = i;
}
last_pixel = pixel;
// get the next pixel across
ptr += bytes_per_pixel;
}
// find the heights we need by looking at the left column of pixels
ptr = (byte_t*)surface->pixels;
last_pixel = 0;
for (int i = 0; i < surface->h; i++) {
// we know they're 4 byte pixels cause otherwise we don't get here.
pixel = *(uint32_t*)ptr;
// look for "edges" in the left column of pixel data
if (pixel > last_pixel) {
top = i;
}
else if (pixel < last_pixel) {
bottom = i;
}
last_pixel = pixel;
// get the next pixel down
ptr += bytes_per_pixel * surface->w;
}
// SDL interprets each pixel as a 32-bit number, so our masks
// must depend on the endianness (byte order) of the machine
Uint32 rmask, gmask, bmask, amask;
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
rmask = 0xff000000;
gmask = 0x00ff0000;
bmask = 0x0000ff00;
amask = 0x000000ff;
#else
rmask = 0x000000ff;
gmask = 0x0000ff00;
bmask = 0x00ff0000;
amask = 0xff000000;
#endif
const uint32_t unused_flags = 0;
const int pixel_size = 32; // in bits
// scratch surface we use for breaking the nine-patch
// surface into little textures.
SDL_Surface * s;
SDL_Rect src_rect;
// create a surface to hold the top left corner
s = SDL_CreateRGBSurface(unused_flags, left, top,
pixel_size, rmask, gmask, bmask, amask);
// copy part of the nine-patch image surface into the new surface
src_rect.x = 0;
src_rect.y = 0;
src_rect.w = left;
src_rect.h = top;
SDL_BlitSurface(surface, &src_rect, s, NULL);
// convert the new corner surface into a texture
top_left_texture = SDL_CreateTextureFromSurface(renderer, s);
// free the scratch surface
SDL_FreeSurface(s);
}

Create CImage from Byte array

I need to create a CImage from a byte array (actually, its an array of unsigned char, but I can cast to whatever form is necessary). The byte array is in the form "RGBRGBRGB...". The new image needs to contain a copy of the image bytes, rather than using the memory of the byte array itself.
I have tried many different ways of achieving this -- including going through various HBITMAP creation functions, trying to use BitBlt -- and nothing so far has worked.
To test whether the function works, it should pass this test:
BYTE* imgBits;
int width;
int height;
int Bpp; // BYTES per pixel (e.g. 3)
getImage(&imgBits, &width, &height, &Bpp); // get the image bits
// This is the magic function I need!!!
CImage img = createCImage(imgBits, width, height, Bpp);
// Test the image
BYTE* data = img.GetBits(); // data should now have the same data as imgBits
All implementations of createCImage() so far have ended up with data pointing to an empty (zero filled) array.
CImage supports DIBs quite neatly and has a SetPixel() method so you could presumably do something like this (uncompiled, untested code ahead!):
CImage img;
img.Create(width, height, 24 /* bpp */, 0 /* No alpha channel */);
int nPixel = 0;
for(int row = 0; row < height; row++)
{
for(int col = 0; col < width; col++)
{
BYTE r = imgBits[nPixel++];
BYTE g = imgBits[nPixel++];
BYTE b = imgBits[nPixel++];
img.SetPixel(row, col, RGB(r, g, b));
}
}
Maybe not the most efficient method but I should think it is the simplest approach.
Use memcpy to copy the data, then SetDIBits or SetDIBitsToDevice depending on what you need to do. Take care though, the scanlines of the raw image data are aligned on 4-byte boundaries (IIRC, it's been a few years since I did this) so the data you get back from GetDIBits will never be exactly the same as the original data (well it might, depending on the image size).
So most likely you will need to memcpy scanline by scanline.
Thanks everyone, I managed to solve it in the end with your help. It mainly involved #tinman and #Roel's suggestion to use SetDIBitsToDevice(), but it involved a bit of extra bit-twiddling and memory management, so I thought I'd share my end-point here.
In the code below, I assume that width, height and Bpp (Bytes per pixel) are set, and that data is a pointer to the array of RGB pixel values.
// Create the header info
bmInfohdr.biSize = sizeof(BITMAPINFOHEADER);
bmInfohdr.biWidth = width;
bmInfohdr.biHeight = -height;
bmInfohdr.biPlanes = 1;
bmInfohdr.biBitCount = Bpp*8;
bmInfohdr.biCompression = BI_RGB;
bmInfohdr.biSizeImage = width*height*Bpp;
bmInfohdr.biXPelsPerMeter = 0;
bmInfohdr.biYPelsPerMeter = 0;
bmInfohdr.biClrUsed = 0;
bmInfohdr.biClrImportant = 0;
BITMAPINFO bmInfo;
bmInfo.bmiHeader = bmInfohdr;
bmInfo.bmiColors[0].rgbBlue=255;
// Allocate some memory and some pointers
unsigned char * p24Img = new unsigned char[width*height*3];
BYTE *pTemp,*ptr;
pTemp=(BYTE*)data;
ptr=p24Img;
// Convert image from RGB to BGR
for (DWORD index = 0; index < width*height ; index++)
{
unsigned char r = *(pTemp++);
unsigned char g = *(pTemp++);
unsigned char b = *(pTemp++);
*(ptr++) = b;
*(ptr++) = g;
*(ptr++) = r;
}
// Create the CImage
CImage im;
im.Create(width, height, 24, NULL);
HDC dc = im.GetDC();
SetDIBitsToDevice(dc, 0,0,width,height,0,0, 0, height, p24Img, &bmInfo, DIB_RGB_COLORS);
im.ReleaseDC();
delete[] p24Img;
Here is a simpler solution. You can use GetPixelAddress(...) instead of all this BITMAPHEADERINFO and SedDIBitsToDevice. Another problem I have solved was with 8-bit images, which need to have the color table defined.
CImage outImage;
outImage.Create(width, height, channelCount * 8);
int lineSize = width * channelCount;
if (channelCount == 1)
{
// Define the color table
RGBQUAD* tab = new RGBQUAD[256];
for (int i = 0; i < 256; ++i)
{
tab[i].rgbRed = i;
tab[i].rgbGreen = i;
tab[i].rgbBlue = i;
tab[i].rgbReserved = 0;
}
outImage.SetColorTable(0, 256, tab);
delete[] tab;
}
// Copy pixel values
// Warining: does not convert from RGB to BGR
for ( int i = 0; i < height; i++ )
{
void* dst = outImage.GetPixelAddress(0, i);
const void* src = /* put the pointer to the i'th source row here */;
memcpy(dst, src, lineSize);
}