GDI+ DC in memory always monochrome - c++

Working in mingw, having a terrible time creating a color DC in memory. For instance, in the following code snippet, as written, "foo_scratch.bmp" is a monochrome version of the
image (from an EMR_STRETCHDIBITS record). If instead aDC is omitted and srcDC uses the CreateDC directly, then that file has a color image.
Gdiplus::Bitmap *pbmp = NULL;
BITMAPINFO *pbitmapinfo = (BITMAPINFO *)((char *)lpEMFR + pEmr->offBmiSrc);
void *pBitsInMem = (char *)lpEMFR + pEmr->offBitsSrc;
HBITMAP hbmsrc;
HDC aDC = CreateDC("DISPLAY", "", NULL, NULL);
HDC srcDC = CreateCompatibleDC(aDC);
hbmsrc = CreateDIBitmap(
srcDC,
&(pbitmapinfo->bmiHeader),
CBM_INIT,
pBitsInMem,
pbitmapinfo,
DIB_RGB_COLORS);
if(hbmsrc){
CLSID pngClsid;
GetEncoderClsid(L"image/bmp", &pngClsid);
pbmp = Gdiplus::Bitmap::FromHBITMAP(hbmsrc,NULL);
pbmp->Save(L"C:\\Temp\\foo_scratch.bmp",&pngClsid, NULL);
This all comes to a head later when two images (hbmdst, hbmsrc) need to be put together with a bitblt operation. At present the best I have managed is monochrome. At worst the image is solid black. In this snippet the ROP has been hard coded to SRCCOPY, and I still have not succeeded in just copying the image from one HBITMAP to another. Very frustrating!
HDC dstDC = CreateCompatibleDC(aDC);
HBITMAP hbmdOld = (HBITMAP) SelectObject(dstDC, hbmdst);
HBITMAP hbmsOld = (HBITMAP) SelectObject(srcDC, hbmsrc);
GetObject(hbmsrc, sizeof(bm), &bm);
BitBlt(dstDC, 0, 0, bm.bmWidth, bm.bmHeight, srcDC, 0, 0, SRCCOPY);
SelectObject(srcDC, hbmsOld);
SelectObject(dstDC, hbmdOld);
(void) DeleteDC(dstDC);
pbmp = Gdiplus::Bitmap::FromHBITMAP(hbmdst,NULL);
pbmp->Save(L"C:\\Temp\\scratch.bmp",&pngClsid, NULL);
What am I doing wrong?
Thanks

Figured it out - wherever a bitmap is created have to use the DC directly associated with the display (or other device), not the "compatible" DC derived from the first DC.

Related

How to construct a GDI+ Bitmap object from a Device-Dependent HBITMAP

I want to use GDI+ method Image::Save() to save a DDB to a file in the following scenario:
HBITMAP hBitmap = CreateCompatibleBitmap(hDC, 200, 200) ;
...
//hBitmap is a DDB so I need to pass an HPALETTE
Gdiplus::Bitmap(hBitmap, ???HPALETTE??? ).Save(L"file.png", ...) ;
The problem is that Bitmap constructor asks for an HPALETTE when the bitmap is not a device-independent bitmap.
Where do I get the necessary HPALETTE from?
FOLLOWUP:
One of the answers suggests passing NULL as the HPALETTE parameter.
Here is a working example that does so. The result is a purely black and white image where all colors are lost.
#include <windows.h>
#include <gdiplus.h>
int main(){
using namespace Gdiplus ;
GdiplusStartupInput gdiplusStartupInput ;
ULONG_PTR gdiplusToken ;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL) ;
CLSID pngEncoder = {0x557cf406, 0x1a04, 0x11d3, {0x9a, 0x73, 0x00, 0x00, 0xf8, 0x1e, 0xf3, 0x2e} } ;
HDC dcHndl = CreateCompatibleDC(NULL) ;
HBITMAP hBitmap = CreateCompatibleBitmap(dcHndl, 200, 200) ;
SelectObject(dcHndl, hBitmap) ;
BitBlt(dcHndl, 0,0, 200,200, GetDC(NULL), 0,0, SRCCOPY|CAPTUREBLT) ;
Bitmap(hBitmap, NULL).Save(L"file.png", &pngEncoder) ;
}
First (and this is unrelated to your main question):
When creating a bitmap for screen shot, don't use a memory dc because that creates a monochrome bitmap. That's the main reason you are getting a black and white image (on my computer I just get a black image).
Don't use GetDC(0) inside another function. Every call to GetDC match have a matching ReleaseDC to avoid resource leak.
After calling BitBlt it is good practice to select hbitmap out of dc because you are basically finished drawing on dc.
The following code will work on Windows 10
int w = 800;
int h = 600;
HDC hdc = GetDC(HWND_DESKTOP);
HDC memdc = CreateCompatibleDC(hdc);
HBITMAP hbitmap = CreateCompatibleBitmap(hdc, w, h);
HBITMAP oldbmp = (HBITMAP)SelectObject(memdc, hbitmap);
BitBlt(memdc, 0, 0, w, h, hdc, 0, 0, SRCCOPY | CAPTUREBLT);
SelectObject(memdc, oldbmp);
Bitmap(hbitmap, NULL).Save(filename, &pngEncoder);
DeleteObject(hbitmap);
DeleteDC(memdc);
ReleaseDC(HWND_DESKTOP, hdc);
Back to your question regarding the documentation:
Type: HPALETTE
Handle to a GDI palette used to define the bitmap colors if hbm is not a device-independent bitmap (DIB).
In addition,
Do not pass to the Bitmap::FromHBITMAP method a GDI bitmap or a GDI palette that is currently (or was previously) selected into a device context.
The code I posted obeys only one rule, that GDI bitmap is not currently selected in to a device context (but it was previously selected).
The documentation may apply to older versions of Windows. As far as I can see MFC's CImage class does not follow all these rules. New computer displays are all 24 or 32 bit, I don't know how you would get a palette for it.
To follow the documentation to the letter, you can convert DDB to DIB section, using CreateDIBSection and GetDIBits. Use the new DIB section hbitmap_dib in Bitmap::FromHBITMAP. This will satisfy all of the conditions: hbitmap is dib, it is not (and was not) selected in to a device context.
Or, Gdiplus::Bitmap has another method Bitmap::FromBITMAPINFO. If there is no palette, you can use this code instead:
HDC hdc = GetDC(HWND_DESKTOP);
HDC memdc = CreateCompatibleDC(hdc);
HBITMAP hbitmap = CreateCompatibleBitmap(hdc, w, h);
HBITMAP oldbmp = (HBITMAP)SelectObject(memdc, hbitmap);
BitBlt(memdc, 0, 0, 800, 600, hdc, 0, 0, SRCCOPY | CAPTUREBLT);
SelectObject(memdc, oldbmp);
BITMAP bm;
GetObject(hbitmap, sizeof(bm), &bm);
int size = ((bm.bmWidth * bm.bmBitsPixel + 31) / 32) * 4 * bm.bmHeight;
BITMAPINFO info{ sizeof(info), bm.bmWidth, bm.bmHeight, 1, bm.bmBitsPixel, BI_RGB, size };
std::vector<char> bits(size);
GetDIBits(memdc, hbitmap, 0, bm.bmHeight, &bits[0], &info, DIB_RGB_COLORS);
Bitmap *bitmap = Bitmap::FromBITMAPINFO(&info, &bits[0]);
bitmap->Save(filename, &pngEncoder);
delete bitmap;
DeleteObject(hbitmap);
DeleteDC(memdc);
ReleaseDC(HWND_DESKTOP, hdc);
As CreateCompatibleBitmap remarks sate if you are dealing with color bitmaps we can also assume that hDC is a nonmemory device context (because memory device context will only create monochrome bitmaps) and the color palette used by this bitmap is the same color palette used by this device context. You can query it using GetCurrentObject method. However remarks to Bitmap.Bitmap(HBITMAP, HPALETTE) constructor state:
Do not pass to the GDI+ Bitmap::Bitmap constructor a GDI bitmap or a GDI palette that is currently (or was previously) selected into a device context.
So you can not used current device context palette directly and need to create a copy of it instead.
/// <returns>
/// Handle to palette currently selected into device context without granting ownership.
/// </returns>
_Check_return_ ::HPALETTE
Fetch_CurrentPalette(_In_ ::HDC const h_dc)
{
assert(h_dc);
::HGDIOBJ const h_palette_object{::GetCurrentObject(h_dc, OBJ_PAL)}; // not owned
assert(h_palette_object);
assert(OBJ_PAL == ::GetObjectType(h_palette_object));
// Perform unchecked conversion of generic GDI object descriptor to GDI palette descriptor.
::HPALETTE h_current_palette{}; // not owned
{
static_assert(sizeof(h_palette_object) == sizeof(h_current_palette), "wat");
::memcpy
(
::std::addressof(h_current_palette)
, ::std::addressof(h_palette_object)
, sizeof(h_current_palette)
);
}
return(h_current_palette);
}
/// <returns>
/// Handle to palette copy with granting ownership.
/// </returns>
_Check_return_ ::HPALETTE
Make_PaletteCopy(_In_ ::HPALETTE const h_palette)
{
assert(h_palette);
::UINT const first_entry_index{};
::UINT entries_count{};
::LPPALETTEENTRY p_entries{};
// Figure out how many entries palette contains.
entries_count = ::GetPaletteEntries(h_palette, first_entry_index, entries_count, p_entries);
assert(1 < entries_count);
assert(entries_count <= ::std::numeric_limits< decltype(LOGPALETTE::palNumEntries) >::max());
// This buffer will hold palette description which contains first PALETTEENTRY as last field.
// followed by the rest of PALETTEENTRY items.
::std::unique_ptr< ::std::uint8_t[] > const p_buffer
{
new ::std::uint8_t[sizeof(::LOGPALETTE) + (sizeof(::PALETTEENTRY) * (entries_count - 1u))]
};
// Perform unchecked conversion of buffer pointer to palette description pointer.
::LOGPALETTE * p_description{};
{
::std::uint8_t * const p_buffer_bytes{p_buffer.get()};
static_assert(sizeof(p_buffer_bytes) == sizeof(p_description), "wat");
::memcpy
(
::std::addressof(p_description)
, ::std::addressof(p_buffer_bytes)
, sizeof(p_description)
);
}
// Copy palette entries into buffer.
p_entries = static_cast< ::LPPALETTEENTRY >(p_description->palPalEntry);
::UINT const copied_entries_count
{
::GetPaletteEntries(h_palette, first_entry_index, entries_count, p_entries)
};
assert(copied_entries_count == entries_count);
// Create palette copy.
p_description->palVersion = 0x300; // magic
p_description->palNumEntries = static_cast< ::WORD >(copied_entries_count);
::HPALETTE const h_copied_palette{::CreatePalette(p_description)}; // owned
assert(h_copied_palette);
return(h_copied_palette);
}
::HPALETTE const hPal{Make_PaletteCopy(Fetch_CurrentPalette(hDC))}; // owned
assert(hPal);
::HBITMAP const hBitmap{::CreateCompatibleBitmap(hDC, 200, 200)}; // owned
assert(hBitmap);
{
::Gdiplus::Bitmap bmp{hBitmap, hPal};
assert(::Gdiplus::Status::Ok == bmp.GetLastStatus());
// Do something...
}
// Delete palette and bitmap after GDI+ bitmap object went out of scope.
if(FALSE == ::DeleteObject(hPal))
{
assert(false);
}
if(FALSE == ::DeleteObject(hBitmap))
{
assert(false);
}
You can pass NULL. Sample code below.
int main()
{
GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
GUID encoder = {};
GetGdiplusEncoderClsid(L"image/png", &encoder); // https://stackoverflow.com/a/5346026/104458
HDC hdc = GetDC(NULL);
HBITMAP hBitmap = CreateCompatibleBitmap(hdc, 200, 200);
Bitmap bmp(hBitmap, NULL);
bmp.Save(L"File.png", &encoder);
return 0;
}

MFC: How to copy bitmap using OLE clipboard

Now I have a CImage img, and I wanted to copy the bitmap to the OLE clipboard.
What I did is the following
HBITMAP hImage = img.Detach();
CBitmap *pBitmap = CBitmap::FromHandle(hImage);
BITMAP bm;
pBitmap->GetObject(sizeof(bm), &bm);
HBITMAP hImage = Detach();
// the bitmap src
CBitmap *pBitmap = CBitmap::FromHandle(hImage);
BITMAP bm;
pBitmap->GetObject(sizeof(bm), &bm);
// the copy (dest)
CBitmap bitmap;
bitmap.CreateBitmapIndirect(&bm);
// create mem dc and select src and dest bitmaps
CDC dcMemSrc, dcMemDest;
dcMemSrc.CreateCompatibleDC(NULL);
CBitmap* pOldBitmapSrc = dcMemSrc.SelectObject(pBitmap);
dcMemDest.CreateCompatibleDC(NULL);
CBitmap* pOldBitmapDest = dcMemDest.SelectObject(&bitmap);
// bit-blt
dcMemDest.BitBlt(0, 0, bm.bmWidth, bm.bmHeight, &dcMemSrc,
0, 0, SRCCOPY);
HBITMAP hBitmap = (HBITMAP)bitmap.Detach();
// restore mem dc
dcMemDest.SelectObject(pOldBitmapDest);
dcMemSrc.SelectObject(pOldBitmapSrc);
// Place the copy on the clipboard.
AfxOleInit();
CoInitialize(NULL);
COleDataSource* pods = new COleDataSource;
pods->CacheGlobalData(CF_BITMAP, hBitmap);
pods->SetClipboard();
CoUninitialize();
I tested the program, it just doesn't work, I tried to CTRL+V to the Windows Paint program, but it complained the data cannot be pasted in it. If I use the Legacy Clipboard(just replace the last 6 lines with ::OpenClipboard(hWnd), ::EmptyClipboard(), ::SetClipboardData(CF_BITMAP, hBitmap), ::CloseClipboard() ), it works fine. I have no experience working with COM and OLE, can anyone help? Many thanks.

winapi: from HDC to an HBITMAP

I would like to do something which I believe is fairly simple but since I am new to the winapi I am finding a lot of problems. Basically I have an HDC (which I am BitBlitting from a loaded Bitmap) and I am drawing a rectangle on it. Then I would like to BitBlt that HDC onto a new HBITMAP Object, but alas for now to no avail.
Here is my code which I have been trying to get to work for a couple of hours now
BITMAPINFO info;
Bitmap *tempbmp = Bitmap::FromFile(L"C:\\Users\\abelajc\\Pictures\\BackgroundImage.png", false);
HBITMAP loadedbackground;
tempbmp->GetHBITMAP(NULL, &loadedbackground);
HBRUSH hRed = CreateSolidBrush(RGB(255, 0, 0));
HDC pDC = GetDC(0);
HDC TmpDC = CreateCompatibleDC(pDC); //main DC on which we will paint on
HDC dcBmp = CreateCompatibleDC(TmpDC); //DC for the loadedbackground HBitmap
HGDIOBJ TmpObj2 = SelectObject(dcBmp , tempbmp); //Selecting Bitmap in DC
BitBlt(TmpDC, 0, 0, 512, 512, dcBmp, 0, 0, SRCCOPY);
SelectObject(dcBmp, TmpObj2); //Deselecting Bitmap from DC
DeleteDC(dcBmp);
RECT rectangle;
SetRect(&rectangle, 5, 5, 20, 20);
FillRect(TmpDC, &rectangle, hRed);
HDC hCompDC = CreateCompatibleDC(TmpDC);
HBITMAP hBmp = CreateCompatibleBitmap(TmpDC, 512, 512);
HBITMAP hOld = (HBITMAP)SelectObject(hCompDC, hBmp);
BitBlt(hCompDC, 0, 0, 512, 512, TmpDC, 0, 0, SRCCOPY);
SelectObject(hCompDC, hOld);
DeleteDC(hCompDC);
Bitmap *image = new Bitmap(hBmp, NULL);
I think you just need some clarification about GDI.
A DC is exactly what its name imply : a device context. It's just a context, nothing concrete. Some DCs are context to a real graphic device, some others (memory DCs) are context to a virtual graphic surface in memory. The DCs you create with CreateCompatibleDC are memory DC, but creating the DC only create the context, not the memory surface. As the MSDN documentation says :
Before an application can use a memory DC for drawing operations, it must select a bitmap of the correct width and height into the DC.
You need to associate a HBITMAP with the DC. After doing that, you can consider that drawing to the DC is essentially drawing to the bitmap. The memory DC is the 'window' to the bitmap.
Once you understand that, you will see that your program can be greatly shortened. Feel free to comment if you still have problems.

How to convert HICON to HBITMAP in VC++?

How to convert HICON to HBITMAP in VC++?
I know this is an FAQ but all the solutions I've found on Google don't work. What I need is a function which takes a parameter HICON and returns HBITMAP.
Greatest if possible to make conversion to 32-bit bitmap even the icon is 24-bit, 16-bit or 8-bit.
This is the code, I don't know where it goes wrong:
HBITMAP icon_to_bitmap(HICON Icon_Handle) {
HDC Screen_Handle = GetDC(NULL);
HDC Device_Handle = CreateCompatibleDC(Screen_Handle);
HBITMAP Bitmap_Handle =
CreateCompatibleBitmap(Device_Handle,GetSystemMetrics(SM_CXICON),
GetSystemMetrics(SM_CYICON));
HBITMAP Old_Bitmap = (HBITMAP)SelectObject(Device_Handle,Bitmap_Handle);
DrawIcon(Device_Handle, 0,0, Icon_Handle);
SelectObject(Device_Handle,Old_Bitmap);
DeleteDC(Device_Handle);
ReleaseDC(NULL,Screen_Handle);
return Bitmap_Handle;
}
this code do it:
HICON hIcon = (HICON)LoadImage(instance, MAKEINTRESOURCEW(IDI_ICON), IMAGE_ICON, width, height, 0);
ICONINFO iconinfo;
GetIconInfo(hIcon, &iconinfo);
HBITMAP hBitmap = iconinfo.hbmColor;
and this is the code in the *.rc file:
IDI_ICON ICON "example.ico"
and this is the code in the *.h file:
#define IDI_ICON 4000
HDC hDC = GetDC(NULL);
HDC hMemDC = CreateCompatibleDC(hDC);
HBITMAP hMemBmp = CreateCompatibleBitmap(hDC, x, y);
HBITMAP hResultBmp = NULL;
HGDIOBJ hOrgBMP = SelectObject(hMemDC, hMemBmp);
DrawIconEx(hMemDC, 0, 0, hIcon, x, y, 0, NULL, DI_NORMAL);
hResultBmp = hMemBmp;
hMemBmp = NULL;
SelectObject(hMemDC, hOrgBMP);
DeleteDC(hMemDC);
ReleaseDC(NULL, hDC);
DestroyIcon(hIcon);
return hResultBmp;
I don't have code readily available to share, but I think this is pretty easy. You have to create the HBITMAP, create a device context, select the bitmap into the DC (this will make the bitmap the drawing area for this DC). Finally call the DrawIcon() function to draw your icon on this DC. After that detach the bitmap from the DC and destroy the DC. Your bitmap now should be ready to go.
Update after looking at your code:
I believe the problem is in the createCompatibleBitmap call. You are asking for a bitmap compatible with the memory DC, but memory DCs start with a 1 bit/pixel bitmap selected into them. Try asking for a bitmap compatible with the screen DC instead.
Update 2: you may want to look at this question as it seems related to your problem.
I found this(similar code works for me - 32x32 icons with or without alpha data):
used CopyImage (msdn link)
HICON hICON = /*your code here*/
HBITMAP hBITMAPcopy;
ICONINFOEX IconInfo;
BITMAP BM_32_bit_color;
BITMAP BM_1_bit_mask;
// 1. From HICON to HBITMAP for color and mask separately
//.cbSize required
//memset((void*)&IconInfo, 0, sizeof(ICONINFOEX));
IconInfo.cbSize = sizeof(ICONINFOEX);
GetIconInfoEx( hICON , &IconInfo);
//HBITMAP IconInfo.hbmColor is 32bit per pxl, however alpha bytes can be zeroed or can be not.
//HBITMAP IconInfo.hbmMask is 1bit per pxl
// 2. From HBITMAP to BITMAP for color
// (HBITMAP without raw data -> HBITMAP with raw data)
// LR_CREATEDIBSECTION - DIB section will be created,
// so .bmBits pointer will not be null
hBITMAPcopy = (HBITMAP)CopyImage(IconInfo.hbmColor, IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION);
// (HBITMAP to BITMAP)
GetObject(hBITMAPcopy, sizeof(BITMAP), &BM_32_bit_color);
//Now: BM_32_bit_color.bmBits pointing to BGRA data.(.bmWidth * .bmHeight * (.bmBitsPixel/8))
// 3. From HBITMAP to BITMAP for mask
hBITMAPcopy = (HBITMAP)CopyImage(IconInfo.hbmMask, IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION);
GetObject(hBITMAPcopy, sizeof(BITMAP), &BM_1_bit_mask);
//Now: BM_1_bit_mask.bmBits pointing to mask data (.bmWidth * .bmHeight Bits!)
BM_32_bit_color bitmap may be have Alpha *channel*(each 4th byte) already set! So - check for it before u add mask bit to color data.

Crop function BitBlt(...)

I want to create a crop function in an existing engine. This is what I already have:
bool Bitmap::Crop(RECT cropArea)
{
BITMAP bm;
GetObject(m_Handle, sizeof(bm), &bm);
HDC hSrc = CreateCompatibleDC(NULL);
SelectObject(hSrc, m_Handle);
HDC hNew = CreateCompatibleDC(NULL);
HBITMAP hBmp = CreateCompatibleBitmap(hNew, bm.bmWidth, bm.bmHeight);
HBITMAP hOld = (HBITMAP)SelectObject(hNew, hBmp);
BitBlt(hNew, 0, 0, bm.bmWidth, bm.bmHeight, hSrc, 0, 0, SRCCOPY);
SelectObject(hNew, hOld);
DeleteDC(hSrc);
DeleteDC(hNew);
DeleteObject(m_Handle);
m_Handle = hBmp;
}
I want it to just copy the whole image to a new HBITMAP and replace the old with it. So I know that it works. After that it is just playing with the BitBlt parameters.
m_Handle is a HBITMAP of the class Bitmap.
The result of this code is just a black screen.
Thanks for helping me.
The function works perfectly now.
bool Bitmap::Crop(RECT cropArea)
{
HDC hSrc = CreateCompatibleDC(NULL);
SelectObject(hSrc, m_Handle);
HDC hNew = CreateCompatibleDC(hSrc);
HBITMAP hBmp = CreateCompatibleBitmap(hSrc, cropArea.right - cropArea.left, cropArea.bottom - cropArea.top);
HBITMAP hOld = (HBITMAP)SelectObject(hNew, hBmp);
bool retVal = (BitBlt(hNew, 0, 0, cropArea.right - cropArea.left, cropArea.bottom - cropArea.top, hSrc, cropArea.left, cropArea.top, SRCCOPY))?true:false;
SelectObject(hNew, hOld);
DeleteDC(hSrc);
DeleteDC(hNew);
DeleteObject(m_Handle);
m_Handle = hBmp;
return retVal;
}
Never create a compatible bitmap from a 'fresh' memory DC.
Unless that is you WANT to create a 1bpp bitmap - the default bitmap selected in a new memory DC is a 1x1 1bpp bitmap - so any compatible bitmap you create will match that.
Which does tend to result in all black output.
Your color bitmap in in hSrc, so use that dc to make the new bitmap.
Two small changes:
HBITMAP hBmp = CreateCompatibleBitmap(hNew, cropArea.right - cropArea.left, cropArea.bottom - cropArea.top);
BitBlt(hNew, 0, 0, cropArea.right - cropArea.left, cropArea.bottom - cropArea.top, hSrc, cropArea.left, cropArea.top, SRCCOPY);
You might want a little more checking to make sure the requested area falls within the size of the original bitmap.