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

14,451

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
Share:
14,451
marisks
Author by

marisks

Updated on June 28, 2022

Comments

  • marisks
    marisks over 1 year

    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;
    }
    
  • marisks
    marisks over 12 years
    Thanks for answer, but it didn't help me. Checking against FormatID works same as checking against mime type. I also tried to set encoder parameters, but no success. How do you copy bitmap into IBitmapImage? I could try IImage::PushIntoSink method. Maybe you have full example how you do saving to JPEG?
  • marisks
    marisks over 12 years
    I found how to get IBitmapImage and then IImage and used IImage::PushIntoSink - everything now works fine :)