Related
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;
}
I have a bitmap class that has a load function for loading the bitmap from either file path or resource ID. This part works fine.
void GtBitmap::Load()
{
LPTSTR szFileName;
szFileName = (LPTSTR)m_strPath.c_str();
// Check for valid .BMP file path
if (m_strPath.size() > 0)
{
// Open .BMP file
m_pFile = fopen(m_strPath.c_str(), ("rb"));
if (m_pFile != NULL)
{
m_hBitmap = (HBITMAP)LoadImage (GetModuleHandle(NULL), szFileName, IMAGE_BITMAP, 0, 0, LR_SHARED | LR_LOADFROMFILE);
GetObject(m_hBitmap, sizeof(m_bmap), &m_bmap);
int i = 1;
}
}
else if (m_intResourceID != 0)
{
m_hBitmap = (HBITMAP)LoadImage(GetModuleHandle(NULL), MAKEINTRESOURCE(m_intResourceID), IMAGE_BITMAP, 0, 0, LR_SHARED);
GetObject(m_hBitmap, sizeof(m_bmap), &m_bmap);
int i = 1;
}
}
However, when I try to render it in my code block, the SelectObject returns null. Here is the code for that section of the painter class.
void GtPainterGDI::GtDrawBitmap(GtRectI & target, GtBitmap & bitmap, bool blnOffset)
{
GtCanvas topCv = m_arrCanvas.back();
HDC hdcMem = CreateCompatibleDC(topCv.m_hdcParent);
HBITMAP hbmOld = (HBITMAP)SelectObject(hdcMem, bitmap.m_hBitmap);
DWORD lastError = GetLastError();
bool success = BitBlt(hdcMem, target.GetLeft(), target.GetTop(),
target.Width(), target.Height(), hdcMem, 0, 0, SRCCOPY);
SelectObject(hdcMem, bitmap.m_hBitmap);
DeleteDC(hdcMem);
};
The SelectObject() returns null and the image is not drawn. I can only get the image to show up if I use a LoadImage() in that paint function. However I don't want to load the image every time I want to paint. I should be able to load the image once in the Load function or constructor of the bitmap, then use the handle in the paint function.
If anyone could please provide an example of loading an image in a constructor and then painting it elsewhere in the codes WM_PAINT or equivalent painting function I would appreciate it. The code is a new version of the GT graphical user interface library. I plan on posting a new version on codeproject in the next few days or so. I have to do some cleanup first...
Thanks in advance.
HINSTANCE parameter in LoadImage should be NULL when loading the image from file. Use GetModuleHandle(NULL) only when loading from resource.
m_hBitmap = (HBITMAP)LoadImage(NULL, m_strPath.c_str(),
IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
if (!m_hBitmap)
{
//report error
}
Also LR_SHARED is not necessary here.
When testing for file's exist, you can use std::ifstream. Example:
#include <fstream>
...
bool test = std::ifstream(m_strPath).good();
This will test for file and close the file handle right away.
Make sure to select hbmOld before deleting hdcMem:
HBITMAP hbmOld = (HBITMAP)SelectObject(hdcMem, bitmap.m_hBitmap);
BitBlt(...)
//SelectObject(hdcMem, bitmap.m_hBitmap); <<= remove this
SelectObject(hdcMem, hbmOld);
DeleteDC(hdcMem);
I met this problem when writing a minesweeper game. I used bitmap for numbers, mines and blanks. I think I have registered them correctly in the resource file
IDI_0 BITMAP "D:\\User\\Mark\\Documents\\C++\\win32\\MineSweeper\\MineSweeper\\empty.bmp"
IDI_1 BITMAP "D:\\User\\Mark\\Documents\\C++\\win32\\MineSweeper\\MineSweeper\\1.bmp"
IDI_2 BITMAP "D:\\User\\Mark\\Documents\\C++\\win32\\MineSweeper\\MineSweeper\\2.bmp"
IDI_3 BITMAP "D:\\User\\Mark\\Documents\\C++\\win32\\MineSweeper\\MineSweeper\\3.bmp"
and the header file
#define IDI_0 200
#define IDI_1 201
#define IDI_2 202
#define IDI_3 203
and I load them like this
h0 = (HBITMAP)LoadImage(hInst, MAKEINTRESOURCE(IDI_0), IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION);
h1 = (HBITMAP)LoadImage(hInst, MAKEINTRESOURCE(IDI_1), IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION);
h2 = (HBITMAP)LoadImage(hInst, MAKEINTRESOURCE(IDI_2), IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION);
h3 = (HBITMAP)LoadImage(hInst, MAKEINTRESOURCE(IDI_3), IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION);
I also checked the exe file with resourcehacker and found all bitmaps in there.
What I do not understand is that only sometimes (~50%) when I run the game pops either
Error 1812: The specified image file did not contain a resource section.
or
Error 1813: The specified resource type cannot be found.
But if I load them from files like this
h0 = (HBITMAP)LoadImage(NULL, L"D:\\User\\Mark\\Documents\\C++\\win32\\MineSweeper\\MineSweeper\\empty.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE | LR_CREATEDIBSECTION);
h1 = (HBITMAP)LoadImage(NULL, L"D:\\User\\Mark\\Documents\\C++\\win32\\MineSweeper\\MineSweeper\\1.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE | LR_CREATEDIBSECTION);
h2 = (HBITMAP)LoadImage(NULL, L"D:\\User\\Mark\\Documents\\C++\\win32\\MineSweeper\\MineSweeper\\2.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE | LR_CREATEDIBSECTION);
h3 = (HBITMAP)LoadImage(NULL, L"D:\\User\\Mark\\Documents\\C++\\win32\\MineSweeper\\MineSweeper\\3.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE | LR_CREATEDIBSECTION);
everything works fine.
Any thoughts or advice would be appreciated. Thanks!
I'd personally consider using GDI+ to load your images. You can still load from a disk-file or from the resources section of your application. It also gives you the advantage of access to all the image formats that windows supports natively (BMP, GIF, JPEG, PNG, TIFF, Exif, WMF, and EMF)
To use it, you just have to initialize GDI+ first and then perform a shutdown before program exit.
Here's the function I use for loading from disk-file:
// BMP, GIF, JPEG, PNG, TIFF, Exif, WMF, and EMF
HBITMAP mLoadImageFile(wchar_t *filename)
{
HBITMAP result = NULL;
Bitmap bitmap(filename, false);
bitmap.GetHBITMAP(0, &result);
return result;
}
If we have a resource-file that contains the following:
IDR_PNG1 RT_PNG ".\\girl.png"
IDR_JPG1 RT_JPG ".\\rssLogo.jpg"
Then we can load each of the images thusly:
HBITMAP png = loadImgResource(IDR_PNG1, L"RT_PNG");
HBITMAP jpg = loadImgResource(IDR_JPG1, L"RT_JPG");
Also the two functions I use to load from a resource. You'll note that I've hardcoded in GetModuleHandle(0) in the 2nd function - change this if you want to read from any module (dll, exe) other than the current one.
// BMP, GIF, JPEG, PNG, TIFF, Exif, WMF, and EMF
HBITMAP loadImgResource(wchar_t *pName, wchar_t *pType, HMODULE hInst)
{
Gdiplus::Bitmap *m_pBitmap;
HBITMAP result=NULL;
HRSRC hResource = FindResource(hInst, pName, pType);
if (!hResource)
return NULL;
DWORD imgSize = SizeofResource(hInst, hResource);
if (!imgSize)
return NULL;
const void *pResourceData = LockResource(LoadResource(hInst, hResource));
if (!pResourceData)
return NULL;
HANDLE m_hBuffer = GlobalAlloc(GMEM_MOVEABLE, imgSize);
if (m_hBuffer)
{
void* pBuffer = GlobalLock(m_hBuffer);
if (pBuffer)
{
CopyMemory(pBuffer, pResourceData, imgSize);
IStream* pStream = NULL;
if (CreateStreamOnHGlobal(m_hBuffer, FALSE, &pStream) == S_OK)
{
m_pBitmap = Gdiplus::Bitmap::FromStream(pStream);
pStream->Release();
if (m_pBitmap)
{
if (m_pBitmap->GetLastStatus() == Gdiplus::Ok)
{
m_pBitmap->GetHBITMAP(0, &result);
delete m_pBitmap;
}
}
}
GlobalUnlock(m_hBuffer);
}
GlobalFree(m_hBuffer);
}
return result;
}
HBITMAP loadImgResource(WORD resNum, LPWSTR pType)
{
return loadImgResource(MAKEINTRESOURCE(resNum), pType, GetModuleHandle(0));//hInst);
}
I need to load an image as a HBITMAP in C++ so I can send it to a printer.
I've tried using Gdiplus::Bitmap::FromFileand then Gdiplus::Bitmap->GetHBITMAP however the HBITMAP returned is still empty.
I can load .bmp files fine with LoadImage() but this doesn't work with PNG files.
Could anyone point me to the correct way of implementing this to work with .png (and more, if possible) file types?
EDIT: CODE
ZeroMemory(&hBitmap, sizeof(HBITMAP));
Gdiplus::Bitmap* gpBitmap = Gdiplus::Bitmap::FromFile(L"img.png");
gpBitmap->GetHBITMAP(0, &hBitmap);
EDIT 2: Solution found
HBITMAP GetHBITMAPFromImageFile(WCHAR* pFilePath)
{
GdiplusStartupInput gpStartupInput;
ULONG_PTR gpToken;
GdiplusStartup(&gpToken, &gpStartupInput, NULL);
HBITMAP result = NULL;
Gdiplus::Bitmap* bitmap = Gdiplus::Bitmap::FromFile(pFilePath, false);
if (bitmap)
{
bitmap->GetHBITMAP(Color(255, 255, 255), &result);
delete bitmap;
}
GdiplusShutdown(gpToken);
return result;
}
I try to load a bmp to my MFC Picture Control.
void CMFCAppDlg::OnBnClickedButtonload()
{
CFileDialog dlg(TRUE);
int result=dlg.DoModal();
if(result==IDOK)
{
MyBmpFile::Instance() -> setPath (dlg.GetPathName());
UpdateData(FALSE);
}
HANDLE hBitmap = LoadImage(0, MyBmpFile::Instance() -> getPath(), IMAGE_BITMAP,0,0,LR_LOADFROMFILE);
CBitmap m_bitmap;
m_bitmap.Attach((HBITMAP)hBitmap);
CDC dc, *pDC;
BITMAP bmp;
m_bitmap.LoadBitmapW(IDB_BITMAP);
m_bitmap.GetBitmap(&bmp);
pDC = this->GetDC();
dc.CreateCompatibleDC(pDC);
dc.SelectObject(m_bitmap);
pDC->BitBlt(200, 200, bmp.bmWidth, bmp.bmHeight, &dc,0 , 0, SRCCOPY);
m_bitmap.DeleteObject();
m_bitmap.Detach();
}
This code returns me an error after I select an item in dialog box. Problem is with LoadImage() it returns NULL. But actually I dont know what im doing wrong with that.
Ok, I used CImage to draw this bmp, anyway i did not solve the problem with LoadImage(). I try to make it in static way like: L"D:\\e.bmp" or _T("D:\\e.bmp") but even there problem is the same as before.
void CMFCAppDlg::OnBnClickedButtonload()
{
CFileDialog dlg(TRUE);
int result=dlg.DoModal();
if(result==IDOK)
{
MyBmpFile::Instance() -> setPath (dlg.GetPathName());
UpdateData(FALSE);
}
CImage image;
image.Load( MyBmpFile::Instance() ->getPath() );
CDC dc, *pDC;
pDC = this->GetDC();
dc.CreateCompatibleDC(pDC);
image.Draw(pDC -> GetSafeHdc(),0,0);
}
Following may be of help:
INSTANCE hInst = AfxGetInstanceHandle();
HBITMAP hBmp = (HBITMAP)LoadImage(hInst, L"path\to\file.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
Couple of things to check:
Are you using 1-byte or 2-byte characters. Use the L macro for the latter.
Is the path to your file correctly specified (e.g. could be relative to where the program happens to run from)
Can you load the file manually as a bitmap as in the example below?
Code to load a file manually as a bitmap:
CFile file;
if (file.Open(L"C:\\Tmp\\Example.bmp", CFile::modeRead))
{
// Read file header
BITMAPFILEHEADER bmfHeader;
if (file.Read((LPSTR) &bmfHeader, sizeof(bmfHeader)) == sizeof(bmfHeader))
{
// File type should be 'BM'
if (bmfHeader.bfType == ((WORD)('M' << 8)| 'B'))
{
BITMAPINFOHEADER bmiHeader;
if (file.Read((LPSTR) &bmiHeader, sizeof(bmiHeader)) == sizeof(bmiHeader))
{
int width = bmiHeader.biWidth;
int height = bmiHeader.biHeight;
}
}
}
file.Close();
}