How to save HBITMAP as JPG using Win32 Imaging API (Windows Mobile 6+)?

marisks picture marisks · Apr 1, 2011 · Viewed 14.2k times · Source

I have created procedures to save window screenshot to file. It works for PNG and BMP, but not for JPG (and GIF). Here is code for capturing HBITMAP:

HBITMAP Signature::getScreenHBITMAP() {
// get screen rectangle 
RECT windowRect; 
GetWindowRect(getMainWnd(), &windowRect); 

// bitmap dimensions 
int bitmap_dx = windowRect.right - windowRect.left; 
int bitmap_dy = windowRect.bottom - windowRect.top; 

// create bitmap info header 
BITMAPINFOHEADER infoHeader; 
infoHeader.biSize          = sizeof(infoHeader); 
infoHeader.biWidth         = bitmap_dx; 
infoHeader.biHeight        = bitmap_dy; 
infoHeader.biPlanes        = 1; 
infoHeader.biBitCount      = 24;
infoHeader.biCompression   = BI_RGB; 
infoHeader.biSizeImage     = 0;
infoHeader.biXPelsPerMeter = 0;
infoHeader.biYPelsPerMeter = 0;
infoHeader.biClrUsed       = 0;
infoHeader.biClrImportant  = 0;

// dibsection information 
BITMAPINFO info; 
info.bmiHeader = infoHeader; 
HDC winDC = GetWindowDC(getMainWnd()); 
HDC memDC = CreateCompatibleDC(winDC); 
BYTE* memory = 0; 
HBITMAP bitmap = CreateDIBSection(winDC, &info, DIB_RGB_COLORS, (void**)&memory, 0, 0); 
SelectObject(memDC, bitmap); 
// Copies screen upside down (as it is already upside down) - if need normal layout, change to BitBlt function call
StretchBlt(memDC, 0, 0, bitmap_dx, bitmap_dy, winDC, 0, bitmap_dy, bitmap_dx, bitmap_dy * -1, SRCCOPY); 
DeleteDC(memDC); 
ReleaseDC(getMainWnd(), winDC); 

return bitmap;
}

And here is code for image saving:

HRESULT Imaging_SaveToFile(HBITMAP handle, LPTSTR filename, LPCTSTR format){
HRESULT res;

res = CoInitializeEx(NULL, COINIT_MULTITHREADED);
if ((res == S_OK) || (res == S_FALSE)) {
    IImagingFactory* factory=NULL;
    if (CoCreateInstance(CLSID_ImagingFactory, NULL, CLSCTX_INPROC_SERVER, IID_IImagingFactory, (void**)&factory) == S_OK) {
        UINT count;
        ImageCodecInfo* imageCodecInfo=NULL;
        if (factory->GetInstalledEncoders(&count, &imageCodecInfo) == S_OK) {
            // Get the particular encoder to use
            LPTSTR formatString;
            if (wcscmp(format, L"png") == 0) {
                formatString = _T("image/png");
            } else if (wcscmp(format, L"jpg") == 0) {
                formatString = _T("image/jpeg");
            } else if (wcscmp(format, L"gif") == 0) {
                formatString = _T("image/gif");
            } else if (wcscmp(format, L"bmp") == 0) {
                formatString = _T("image/bmp");
            } else {
                CoUninitialize();
                return S_FALSE;
            }
            CLSID encoderClassId;
            if (count == 0) {
                CoUninitialize();
                return S_FALSE;
            }
            for(int i=0; i < (int)count; i++) {
                if (wcscmp(imageCodecInfo[i].MimeType, formatString) == 0) {
                    encoderClassId= imageCodecInfo[i].Clsid;
                    free(imageCodecInfo);
                    break;
                } else {
                    continue;
                }
                CoUninitialize();
                return S_FALSE;
            } 
            IImageEncoder* imageEncoder=NULL;
            if (factory->CreateImageEncoderToFile(&encoderClassId, filename, &imageEncoder) == S_OK) {
                IImageSink* imageSink = NULL;
                res = imageEncoder->GetEncodeSink(&imageSink);
                if (res != S_OK) {
                    CoUninitialize();
                    return res;
                }
                BITMAP bm;
                GetObject (handle, sizeof(BITMAP), &bm);

                ImageInfo* imageInfo = new ImageInfo();
                imageInfo->Width = bm.bmWidth;
                imageInfo->Height = bm.bmHeight;
                imageInfo->RawDataFormat = IMGFMT_MEMORYBMP; //ImageFormatMemoryBMP;
                imageInfo->Flags |= SinkFlagsTopDown | SinkFlagsFullWidth;
                // Get pixel format from hBitmap
                PixelFormatID pixelFormat;
                int numColors = 0;
                switch (bm.bmBitsPixel) {
                    case 1: {
                        pixelFormat = PixelFormat1bppIndexed;
                        numColors = 1;
                        break;
                    }
                    case 4: {
                        pixelFormat = PixelFormat4bppIndexed;
                        numColors = 16;
                        break;
                    }
                    case 8: {
                        pixelFormat = PixelFormat8bppIndexed;
                        numColors = 256;
                        break;
                    }
                    case 24: {
                        pixelFormat = PixelFormat24bppRGB;
                        break;
                    }
                    default: {
                        pixelFormat = PixelFormat32bppARGB;
                        numColors = 3; // according to MSDN 16 and 32 bpp numColors should be 3
                        break;
                    }
                }
                imageInfo->PixelFormat = pixelFormat;
                if (pixelFormat == PixelFormat32bppARGB) imageInfo->Flags |= SinkFlagsHasAlpha;
                res = imageSink->BeginSink(imageInfo, NULL);
                if (res != S_OK) {
                    CoUninitialize();
                    return res;
                }
                ColorPalette* palette = NULL;
                if (numColors > 0) {
                    palette = (ColorPalette*)malloc(sizeof(ColorPalette) + (numColors - 1) * sizeof(ARGB));
                    palette->Count = numColors;
                    for (int i=0; i<numColors; i++) {
                        int rgb = i*64;
                        int red = rgb & 0x00FF;
                        int green = (rgb >> 8) & 0x00FF;
                        int blue = (rgb >> 16) & 0x00FF;
                        palette->Entries[i] = MAKEARGB(0, red, green, blue);
                    }
                } else {
                    palette = (ColorPalette*)malloc(sizeof(ColorPalette));
                    palette->Count = 0;
                    if (pixelFormat == PixelFormat32bppARGB) palette->Flags = PALFLAG_HASALPHA;
                }
                res = imageSink->SetPalette(palette);
                if (res != S_OK) {
                    CoUninitialize();
                    return res;
                }

                BitmapData* bmData = new BitmapData();
                bmData->Height = bm.bmHeight;
                bmData->Width = bm.bmWidth;
                bmData->Scan0 = bm.bmBits;
                bmData->PixelFormat = pixelFormat;

                UINT bitsPerLine = imageInfo->Width * bm.bmBitsPixel;
                UINT bitAlignment = sizeof(LONG) * 8;
                UINT bitStride = bitAlignment * (bitsPerLine / bitAlignment);   // The image buffer is always padded to LONG boundaries
                if ((bitsPerLine % bitAlignment) != 0) bitStride += bitAlignment; // Add a bit more for the leftover values
                bmData->Stride = (bitStride / 8);

                RECT rect;
                rect.top = 0;
                rect.bottom = bm.bmHeight;
                rect.left = 0;
                rect.right = bm.bmWidth;

                res = imageSink->PushPixelData(&rect, bmData, TRUE);
                if (res != S_OK) {
                    CoUninitialize();
                    return res;
                }

                res = imageSink->EndSink(S_OK);
                if (res != S_OK) {
                    CoUninitialize();
                    return res;
                }
                imageSink->Release();
                res = imageEncoder->TerminateEncoder();
                if (res != S_OK) {
                    CoUninitialize();
                    return res;
                }
            }
        }
    }
    CoUninitialize();
} else {
    return res;
}

return res;
}

I used code from Koders.com as an example and tried to follow MSDN description of image encoding when modified this example.

Found also that others have similar issue, but with no answer:

social.msdn.microsoft.com/Forums/en-US/windowsmobiledev/thread/1c368cc1-cc5b-419e-a7d2-2a39c90ae83d/

groups.google.com/group/microsoft.public.windowsce.embedded.vc/browse_thread/thread/8cd67e16ac29627b/9242e82721c48ace?hl=hu&pli=1

I also found solution which uses GDI+ wrapper:

www.ernzo.com/LibGdiplus.aspx

www.codeproject.com/KB/windows/gdiplusandwinmobile.aspx

But I cannot use this GDI+ lib. Also I do not need whole GDI+. Tried to create similar saving procedure like in this wrapper, but with no success.

EDIT

Here is fixed and working solution (Thanks PhilMY for answer):

HRESULT Imaging_SaveToFile(HBITMAP handle, LPTSTR filename, LPCTSTR format){
HRESULT res;

res = CoInitializeEx(NULL, COINIT_MULTITHREADED);
if ((res == S_OK) || (res == S_FALSE)) {
    IImagingFactory* factory=NULL;
    if (CoCreateInstance(CLSID_ImagingFactory, NULL, CLSCTX_INPROC_SERVER, IID_IImagingFactory, (void**)&factory) == S_OK) {
        UINT count;
        ImageCodecInfo* imageCodecInfo=NULL;
        if (factory->GetInstalledEncoders(&count, &imageCodecInfo) == S_OK) {
            // Get the particular encoder to use
            LPTSTR formatString;
            if (wcscmp(format, L"png") == 0) {
                formatString = _T("image/png");
            } else if (wcscmp(format, L"jpg") == 0) {
                formatString = _T("image/jpeg");
            } else if (wcscmp(format, L"gif") == 0) {
                formatString = _T("image/gif");
            } else if (wcscmp(format, L"bmp") == 0) {
                formatString = _T("image/bmp");
            } else {
                CoUninitialize();
                return S_FALSE;
            }
            CLSID encoderClassId;
            if (count == 0) {
                CoUninitialize();
                return S_FALSE;
            }
            for(int i=0; i < (int)count; i++) {
                if (wcscmp(imageCodecInfo[i].MimeType, formatString) == 0) {
                    encoderClassId= imageCodecInfo[i].Clsid;
                    free(imageCodecInfo);
                    break;
                } else {
                    continue;
                }
                CoUninitialize();
                return S_FALSE;
            } 
            IImageEncoder* imageEncoder=NULL;
            if (factory->CreateImageEncoderToFile(&encoderClassId, filename, &imageEncoder) == S_OK) {
                IImageSink* imageSink = NULL;
                res = imageEncoder->GetEncodeSink(&imageSink);

                if (res != S_OK) {
                    CoUninitialize();
                    return res;
                }

                BITMAP bm;
                GetObject (handle, sizeof(BITMAP), &bm);
                PixelFormatID pixelFormat;
                switch (bm.bmBitsPixel) {
                    case 1: {
                        pixelFormat = PixelFormat1bppIndexed;
                        break;
                    }
                    case 4: {
                        pixelFormat = PixelFormat4bppIndexed;
                        break;
                    }
                    case 8: {
                        pixelFormat = PixelFormat8bppIndexed;
                        break;
                    }
                    case 24: {
                        pixelFormat = PixelFormat24bppRGB;
                        break;
                    }
                    default: {
                        pixelFormat = PixelFormat32bppARGB;
                        break;
                    }
                }

                BitmapData* bmData = new BitmapData();
                bmData->Height = bm.bmHeight;
                bmData->Width = bm.bmWidth;
                bmData->Scan0 = bm.bmBits;
                bmData->PixelFormat = pixelFormat;

                UINT bitsPerLine = bm.bmWidth * bm.bmBitsPixel;
                UINT bitAlignment = sizeof(LONG) * 8;
                UINT bitStride = bitAlignment * (bitsPerLine / bitAlignment);   // The image buffer is always padded to LONG boundaries
                if ((bitsPerLine % bitAlignment) != 0) bitStride += bitAlignment; // Add a bit more for the leftover values
                bmData->Stride = (bitStride / 8);

                IBitmapImage* pBitmap;
                factory->CreateBitmapFromBuffer(bmData, &pBitmap);
                IImage* pImage;
                pBitmap->QueryInterface(IID_IImage, (void**)&pImage); 
                res = pImage->PushIntoSink(imageSink);
                if (res != S_OK) {
                    CoUninitialize();
                    return res;
                }

                pBitmap->Release();
                pImage->Release();
                imageSink->Release();
                imageEncoder->TerminateEncoder();
                imageEncoder->Release();
            }
        }
    }
    CoUninitialize();
} else {
    return res;
}

return res;
}

Answer

PhilMY picture PhilMY · Apr 1, 2011

I have some similar code that worked for JPEGs on WinCE 6.0.

The main differences are:

  • I check ImageCodecInfo::FormatID against ImageFormatJPEG to match an encoder
  • I call SetEncoderParameters to set ENCODER_QUALITY
  • I copy the source bitmap into an IBitmapImage then use IImage::PushIntoSink