How to save HBITMAP as JPG using Win32 Imaging API (Windows Mobile 6+)?
I have some similar code that worked for JPEGs on WinCE 6.0.
The main differences are:
- I check
ImageCodecInfo::FormatID
againstImageFormatJPEG
to match an encoder - I call
SetEncoderParameters
to setENCODER_QUALITY
- I copy the source bitmap into an
IBitmapImage
then useIImage::PushIntoSink
marisks
Updated on June 28, 2022Comments
-
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 over 12 yearsThanks 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 over 12 yearsI found how to get IBitmapImage and then IImage and used IImage::PushIntoSink - everything now works fine :)