c++ read pixels with GetDIBits() - c++

I'm trying to create a function which is equivalent to the windows API GetPixel() function, but I want to create a bitmap of my screen and then read that buffer.
This is what I've got (Mostly copy pasted from google searches), when I run it it only prints out 0's. I think I've got most of it right, and that my issue is that I don't know how to read the BYTE variable.
So my question is, what do I need to do in order to get it to print out some random colors (R,G or B) with my for loop?
#include <Windows.h>
#include <iostream>
#include <math.h>
#include <stdio.h>
using namespace std;
int main() {
HDC hdc,hdcMem;
hdc = GetDC(NULL);
hdcMem = CreateCompatibleDC(hdc);
HBITMAP hBitmap = CreateCompatibleBitmap(hdc, 1680, 1050);
BITMAPINFO MyBMInfo = {0};
MyBMInfo.bmiHeader.biSize = sizeof(MyBMInfo.bmiHeader);
// Get the BITMAPINFO structure from the bitmap
if(0 == GetDIBits(hdcMem, hBitmap, 0, 0, NULL, &MyBMInfo, DIB_RGB_COLORS)) {
cout << "error" << endl;
}
// create the bitmap buffer
BYTE* lpPixels = new BYTE[MyBMInfo.bmiHeader.biSizeImage];
MyBMInfo.bmiHeader.biSize = sizeof(MyBMInfo.bmiHeader);
MyBMInfo.bmiHeader.biBitCount = 32;
MyBMInfo.bmiHeader.biCompression = BI_RGB;
MyBMInfo.bmiHeader.biHeight = abs(MyBMInfo.bmiHeader.biHeight);
// get the actual bitmap buffer
if(0 == GetDIBits(hdc, hBitmap, 0, MyBMInfo.bmiHeader.biHeight, (LPVOID)lpPixels, &MyBMInfo, DIB_RGB_COLORS)) {
cout << "error2" << endl;
}
for(int i = 0; i < 100; i++) {
cout << (int)lpPixels[i] << endl;
}
return 0;
}
Windows 7
C::B 13.12 (Console Application)
Compiler: mingw32-gcc
Library gdi32 linked

As agreed, I'm adding a new answer with the working code snippet (I added the missing cleanup of lpPixels). See the discussions in my previous answer and the one made by #enhzflep.
#include <Windows.h>
#include <iostream>
#include <math.h>
#include <stdio.h>
using namespace std;
HBITMAP GetScreenBmp( HDC hdc) {
// Get screen dimensions
int nScreenWidth = GetSystemMetrics(SM_CXSCREEN);
int nScreenHeight = GetSystemMetrics(SM_CYSCREEN);
// Create compatible DC, create a compatible bitmap and copy the screen using BitBlt()
HDC hCaptureDC = CreateCompatibleDC(hdc);
HBITMAP hBitmap = CreateCompatibleBitmap(hdc, nScreenWidth, nScreenHeight);
HGDIOBJ hOld = SelectObject(hCaptureDC, hBitmap);
BOOL bOK = BitBlt(hCaptureDC,0,0,nScreenWidth, nScreenHeight, hdc,0,0,SRCCOPY|CAPTUREBLT);
SelectObject(hCaptureDC, hOld); // always select the previously selected object once done
DeleteDC(hCaptureDC);
return hBitmap;
}
int main() {
HDC hdc = GetDC(0);
HBITMAP hBitmap = GetScreenBmp(hdc);
BITMAPINFO MyBMInfo = {0};
MyBMInfo.bmiHeader.biSize = sizeof(MyBMInfo.bmiHeader);
// Get the BITMAPINFO structure from the bitmap
if(0 == GetDIBits(hdc, hBitmap, 0, 0, NULL, &MyBMInfo, DIB_RGB_COLORS)) {
cout << "error" << endl;
}
// create the bitmap buffer
BYTE* lpPixels = new BYTE[MyBMInfo.bmiHeader.biSizeImage];
// Better do this here - the original bitmap might have BI_BITFILEDS, which makes it
// necessary to read the color table - you might not want this.
MyBMInfo.bmiHeader.biCompression = BI_RGB;
// get the actual bitmap buffer
if(0 == GetDIBits(hdc, hBitmap, 0, MyBMInfo.bmiHeader.biHeight, (LPVOID)lpPixels, &MyBMInfo, DIB_RGB_COLORS)) {
cout << "error2" << endl;
}
for(int i = 0; i < 100; i++) {
cout << (int)lpPixels[i];
}
DeleteObject(hBitmap);
ReleaseDC(NULL, hdc);
delete[] lpPixels;
return 0;
}

Basically, you need to have drawn some pixels in order to get back a result other than 0.
At present, the 4th line of code in your main creates an empty (blank, 0-initialized) image. You then get information about the size of this image with your first call to GetDIBits. You then get the actual (blank) pixels with your second call to GetDIBits.
To fix, just load a bitmap file from disk into your hBitmap and select this bitmap into your hdcMem.
I.e, change
HBITMAP hBitmap = CreateCompatibleBitmap(hdc, 1680, 1050);
to something like this.
HBITMAP hBitmap = (HBITMAP)LoadImage(NULL, "xpButton.bmp", IMAGE_BITMAP, 0,0, LR_LOADFROMFILE);
HBITMAP old = (HBITMAP) SelectObject(hdcMem, hBitmap);
(make sure you use a valid bmp file name. Mine exists in the same folder as the .cpp file, since this is the 'current' directory when you run via the IDE. If you wish to run via explorer, place another copy of the bmp in the same folder as your exe)
Here's the bmp I've used (which has been converted to a png after upload to SO):
And here's the first 10 iterations through the loop.
255
5
253
0
255
5
253
0
255
5
Note that the pixel at 0,0 has the colour of: rgb(253,5,255) and it's an 8bit image, so there's no alpha channel, hence it has the value 0. The pixels are stored as [BGRA], [BGRA], [BGRA], etc, etc.
I'll leave it to you to fix the (non-existant) clean-up section of your program. Windows will de-allocate the memory you've used here, but you absolutely should not get into the habit of not freeing any memory you've allocated. :)

Your code seems a bit confused. Too many snippets I guess :).
Still, you're quite close:
The first GetDIBits() call is in order to get the properties of the bitmap filled in, as the comment in your code suggests.
You are using an unnecessary MemDC for this - which is probably from a snippet that wants to do a BitBlt with the screen.
You then can use the filled in structure to get the actual bitmap pixels with the second GetDIBits() call, but what you're doing is replacing the properties with hard coded values again, making the first GetDIBits() call useless.
So: Drop the MemDC - you don't need it - and replace hdcMem with hdc in the first call to GetDIBits(), then remove all the statements that overwrite bmiHeader members after the first GetDIBits call and you should get your pixels.
Oh, and of course don't forget to call ReleaseDC()/DeleteObject() on the dc and bitmap and delete[] the buffer :)

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;
}

WinAPI: Correctly copying a HBITMAP to the clipboard

I have some difficulties trying to copy a HBITMAP to the clipboard. My HBITMAP is created from a COLORREF array and I am able to display it correctly. Here is how it is created:
COLORREF* colors = new COLORREF[imageSize[0] * imageSize[1]];
for (int i = 0; i < imageSize[1]; i++) {
for (int j = 0; j < imageSize[0]; j++) {
colors[imageSize[0] * i + j] = RGB(/* ... */);
}
}
// Create bitmap
HBITMAP hBitmap = CreateBitmap(imageSize[0], imageSize[1], 1, 32, (void*)colors);
delete[] colors;
In order to copy my bitmap to the clipboard, I use this small piece of code:
OpenClipboard(hWnd);
EmptyClipboard();
SetClipboardData(CF_BITMAP, hBitmap);
CloseClipboard();
When I execute my app, I am able to copy the bitmap and paste it somewhere, for example in MS Paint. But if I try to copy it a second time, the clipboard content can't be pasted anymore unless the first piece of code above is executed again.
In the MSDN documentation, it is said that
If SetClipboardData succeeds, the system owns the object identified by the hMem parameter.
I don't understand exactly what this means, but I guess it is the source of my problem. I found an example of a function which does what I want here, but it does not seem to use the same kind of variables. Another example, using strings this time, can be found here.
I am not too sure how to translate this last example to my case. Could you point me in the right direction?
A comment that was deleted helped me find the answer. I actually have to copy my HBITMAP to another HBITMAP before calling SetClipboardData. This way, the copied bitmap can be sent to the clipboard, and the original bitmap is kept for later.
To copy the bitmap, I used the code that can be found in Copying a Bitmap to another Bitmap. In my code, it looks like this:
// Create a new bitmap
HBITMAP hBitmap_copy = CreateBitmap(imageSize[0], imageSize[1], 1, 32, NULL);
// Copy the source bitmap to the new one
HDC srcDC = CreateCompatibleDC(GetDC(NULL));
HDC newDC = CreateCompatibleDC(GetDC(NULL));
HBITMAP srcBitmap = (HBITMAP)SelectObject(srcDC, hBitmap);
HBITMAP newBitmap = (HBITMAP)SelectObject(newDC, hBitmap_copy);
BitBlt(newDC, 0, 0, imageSize[0], imageSize[1], srcDC, 0, 0, SRCCOPY);
SelectObject(srcDC, srcBitmap);
SelectObject(newDC, newBitmap);
DeleteDC(srcDC);
DeleteDC(newDC);
// hBitmap_copy can now be copied to the clipboard
OpenClipboard(hWnd);
EmptyClipboard();
SetClipboardData(CF_BITMAP, hBitmap_copy);
CloseClipboard();
I can now copy the displayed bitmap as many times as I want!
// You can't pass hBitmap to SetClipboardData directly
OpenClipboard(NULL)
HBITMAP hBitmap = getBit(); // From somewhere
DIBSECTION ds;
::GetObject(hBitmap, sizeof(DIBSECTION), &ds);
//make sure compression is BI_RGB
ds.dsBmih.biCompression = BI_RGB;
HDC hdc = ::GetDC(NULL);
HBITMAP hbitmap_ddb = ::CreateDIBitmap(
hdc, &ds.dsBmih, CBM_INIT, ds.dsBm.bmBits, (BITMAPINFO*)&ds.dsBmih, DIB_RGB_COLORS);
::ReleaseDC(NULL, hdc);
EmptyClipboard();
SetClipboardData(CF_BITMAP, hbitmap_ddb);
CloseClipboard();

Windows Imaging Component - How to get an encoder from an HBITMAP?

I have an HBITMAP provided by a 3rd party library, and need to write it rescaled on disk as a JPEG.
I have never used WIC, so I've been following this tutorial: https://msdn.microsoft.com/en-us/library/windows/desktop/ff973956.aspx
I have created a WicBitmap from my HBITMAP.
In Listing 9 it becomes apparent that I need a decoder, but the only way to create it I have found is with IWICImagingFactory::CreateDecoderFromFilename. There is an Initialize method that receives a IStream, but I'm not sure of the correct way to use it.
Is this the correct way to save an HBITMAP to disk? If so, how can I get a decoder from my HBITMAP or WicBitmap?
IWICImagingFactory::CreateBitmapFromHBITMAP imports GDI bitmap into WIC as decoded bitmap for which you already need no decoder. That is, you are good to go with your encoding part and saving to disk.
The code snippet below does gets it done in full: 800x600 bitmap from the top left desktop corner as HBITMAP and then saved into JPEG file.
#include "stdafx.h"
#include <wincodecsdk.h>
#include <atlbase.h>
#define __C ATLENSURE_SUCCEEDED
int _tmain(int argc, _TCHAR* argv[])
{
CoInitialize(NULL);
{
HBITMAP hBitmap;
{
HDC hDc = GetDC(NULL);
hBitmap = CreateCompatibleBitmap(hDc, 800, 600);
HDC hBitmapDc = CreateCompatibleDC(hDc);
HGDIOBJ hPreviousBitmap = SelectObject(hBitmapDc, hBitmap);
BitBlt(hBitmapDc, 0, 0, 800, 600, hDc, 0, 0, SRCCOPY);
SelectObject(hBitmapDc, hPreviousBitmap);
DeleteDC(hBitmapDc);
ReleaseDC(NULL, hDc);
}
CComPtr<IWICImagingFactory> pFactory;
__C(pFactory.CoCreateInstance(CLSID_WICImagingFactory));
CComPtr<IWICBitmap> pBitmap;
__C(pFactory->CreateBitmapFromHBITMAP(hBitmap, NULL, WICBitmapIgnoreAlpha, &pBitmap));
CComPtr<IWICBitmapEncoder> pBitmapEncoder;
__C(pFactory->CreateEncoder(GUID_ContainerFormatJpeg, NULL, &pBitmapEncoder));
CComPtr<IWICStream> pFileStream;
__C(pFactory->CreateStream(&pFileStream));
__C(pFileStream->InitializeFromFilename(L"D:\\Output.jpg", GENERIC_WRITE));
__C(pBitmapEncoder->Initialize(pFileStream, WICBitmapEncoderNoCache));
CComPtr<IWICBitmapFrameEncode> pBitmapFrameEncode;
CComPtr<IPropertyBag2> pPropertyBag;
__C(pBitmapEncoder->CreateNewFrame(&pBitmapFrameEncode, &pPropertyBag));
PROPBAG2 Property;
ZeroMemory(&Property, sizeof Property);
Property.pstrName = L"ImageQuality";
CComVariant vQuality(0.85f);
__C(pPropertyBag->Write(1, &Property, &vQuality));
__C(pBitmapFrameEncode->Initialize(pPropertyBag));
__C(pBitmapFrameEncode->WriteSource(pBitmap, NULL));
__C(pBitmapFrameEncode->Commit());
__C(pBitmapEncoder->Commit());
}
CoUninitialize();
return 0;
}

C++ GDI+ memory leak in own class

I am searching for the memory leak(s) in this code.
I am new to GDI+ and I am not sure what I am doing wrong.
The class you see below gets called in a loop in my main function.
Each loop iteration I push an other vector to the function.
Everything is working fine except there is a memory leak.
I tried the program cppCheck to find the leak but it found no memory leaks :/
My last chance to fix the problem is to ask someone who has more experience than me with GDI+
Thank you very much for the help and sorry for the long code :)
#include "helper.h"
Gui::Gui(const TCHAR* fileName) {
this->fileName = fileName;
}
void Gui::drawGui(Gdiplus::Bitmap* image, std::vector<std::wstring> &vec) {
// Init graphics
Gdiplus::Graphics* graphics = Gdiplus::Graphics::FromImage(image);
Gdiplus::Pen penWhite (Gdiplus::Color::White);
Gdiplus::Pen penRed (Gdiplus::Color::Red);
Gdiplus::SolidBrush redBrush(Gdiplus::Color(255, 255, 0, 0));
penRed.SetWidth(8);
unsigned short marginTop = 15;
unsigned short marginLeft = 5;
unsigned short horizontalBarsizeStart = marginLeft + 60;
for (unsigned short iter = 0; iter < 8; iter++) {
// Draw text
std::wstring coreLabel = L"Core " + std::to_wstring(iter) + L':';
Gdiplus::Font myFont(L"Arial", 12);
Gdiplus::PointF origin(marginLeft, marginTop - 10);
graphics->DrawString(coreLabel.c_str(), coreLabel.length(), &myFont, origin, &redBrush);
// Draw CPU lines
unsigned short horizontalBarsizeEnd = horizontalBarsizeStart + std::stoi(vec.at(iter)); // 100 == Max cpu load
graphics->DrawLine(&penRed, horizontalBarsizeStart, marginTop, horizontalBarsizeEnd, marginTop);
// Draw border
Gdiplus::Rect rect(horizontalBarsizeStart, marginTop - 5, 100, 8);
graphics->DrawRectangle(&penWhite, rect);
// Next element
marginTop += 17;
}
}
bool Gui::SetColorBackgroundFromFile(std::vector<std::wstring> &vec) {
Gdiplus::GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
// Initialize GDI+.
Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
HDC hdc = GetDC(NULL);
// Load the image. Any of the following formats are supported: BMP, GIF, JPEG, PNG, TIFF, Exif, WMF, and EMF
Gdiplus::Bitmap* image = Gdiplus::Bitmap::FromFile(this->fileName, false);
if (image == NULL) {
return false;
}
// Draw the gui
this->drawGui(image, vec);
// Get the bitmap handle
HBITMAP hBitmap = NULL;
Gdiplus::Status status = image->GetHBITMAP(RGB(0, 0, 0), &hBitmap);
if (status != Gdiplus::Ok) {
return false;
}
BITMAPINFO bitmapInfo = { 0 };
bitmapInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
// Check what we got
int ret = GetDIBits(hdc, hBitmap, 0, 0, NULL, &bitmapInfo, DIB_RGB_COLORS);
if (LOGI_LCD_COLOR_WIDTH != bitmapInfo.bmiHeader.biWidth || LOGI_LCD_COLOR_HEIGHT != bitmapInfo.bmiHeader.biHeight) {
std::cout << "Oooops. Make sure to use a 320 by 240 image for color background." << std::endl;
return false;
}
bitmapInfo.bmiHeader.biCompression = BI_RGB;
bitmapInfo.bmiHeader.biHeight = -bitmapInfo.bmiHeader.biHeight; // this value needs to be inverted, or else image will show up upside/down
BYTE byteBitmap[LOGI_LCD_COLOR_WIDTH * LOGI_LCD_COLOR_HEIGHT * 4]; // we have 32 bits per pixel, or 4 bytes
// Gets the "bits" from the bitmap and copies them into a buffer
// which is pointed to by byteBitmap.
ret = GetDIBits(hdc, hBitmap, 0,
-bitmapInfo.bmiHeader.biHeight, // height here needs to be positive. Since we made it negative previously, let's reverse it again.
&byteBitmap,
(BITMAPINFO *)&bitmapInfo, DIB_RGB_COLORS);
LogiLcdColorSetBackground(byteBitmap); // Send image to LCD
// delete the image when done
if (image) {
delete image;
image = NULL;
Gdiplus::GdiplusShutdown(gdiplusToken); // Shutdown GDI+
}
return true;
}
In drawGui() you're leaking the graphics object. This line creates a new Gdiplus::Graphics object:
Gdiplus::Graphics* graphics = Gdiplus::Graphics::FromImage(image);
But nowhere do you call delete graphics to delete it once you're done with it.
In SetColorBackgroundFromFile, you're leaking the DC.
HDC hdc = GetDC(NULL);
This gets a DC for the screen, but nowhere do you call ReleaseDC(NULL, hdc); to free it.
In the same function, you are creating an HBITMAP using the following call:
Gdiplus::Status status = image->GetHBITMAP(RGB(0, 0, 0), &hBitmap);
But nowhere do you call DeleteObject(hBitmap); to free it.
You also have the problem that in case of errors, your code can return without doing necessary cleanup. E.g. if the GetHBITMAP call fails, you return immediately and will leak the image object that you created a few lines above.

Windows 7 and ScreenShot.cpp GDI+ PNG problemo

was using XP without issue for a long time. switched to 7 and trying to capture screenshots with my previously functioning code no longer works. simple concept and relatively generic code...just find the window that i call and save it as a .png. any ideas what might make this bad boy run again? can't debug with my current setup, but it makes it all the way and spits out the error message after bmp->save(...) ...couldn't save image file. edit: also a file does get created/saved, but it is blank and not written to. perhaps the bitmap encoding or GDI is screwed up?
bool CScreenShot::Snap(CString wintitle, CString file, CString& ermsg)
{
ermsg = ""; // no error message
// create screen shot bitmap
EnumWinProcStruct prm = {0, (LPSTR)(LPCTSTR)wintitle, 0};
// Find the descriptor of the window with the caption wintitle
EnumDesktopWindows(0, EnumWindowsProc, (LPARAM)&prm);
if(!prm.hwnd)
{
ermsg.Format("couldn't find window \"%s\"", wintitle);
return false;
}
// Make the window the topmost window
SetWindowPos(prm.hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);
Sleep(300);
// Get device context for the top-level window and client rect
HDC hDC = GetDC(prm.hwnd);
RECT rc;
GetClientRect(prm.hwnd, &rc);
HDC memDC = CreateCompatibleDC(hDC);
// Set the size and color depth for the screen shot image
BITMAPINFO bmpInfo;
memset(&bmpInfo, 0, sizeof(bmpInfo));
bmpInfo.bmiHeader.biSize = sizeof(bmpInfo.bmiHeader);
bmpInfo.bmiHeader.biWidth = rc.right - rc.left;
bmpInfo.bmiHeader.biHeight = rc.bottom - rc.top;
bmpInfo.bmiHeader.biPlanes = 1;
bmpInfo.bmiHeader.biBitCount = 24;
bmpInfo.bmiHeader.biCompression = BI_RGB;
bmpInfo.bmiHeader.biSizeImage = bmpInfo.bmiHeader.biWidth * bmpInfo.bmiHeader.biHeight * 3;
// Create memory buffer and perform a bit-block transfer of the color data from the window to the memory
LPVOID addr;
HBITMAP memBM = CreateDIBSection(memDC, &bmpInfo, DIB_RGB_COLORS, &addr, 0, 0);
HGDIOBJ stdBM = SelectObject(memDC, memBM);
BOOL OK = BitBlt(memDC, 0, 0, bmpInfo.bmiHeader.biWidth, bmpInfo.bmiHeader.biHeight, hDC, 0, 0, SRCCOPY);
ReleaseDC(prm.hwnd, hDC);
SetWindowPos(prm.hwnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);
// Initialize GDI+.
GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
if(GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL) != Ok)
{
ermsg.Format("couldn't start GDI+");
return false;
}
// Create a Bitmap object for work with images defined by pixel data from the GDI HBitmap and the GDI HPalette.
Bitmap* bmp = ::new Bitmap(memBM, DIB_RGB_COLORS);
SelectObject(memDC, stdBM);
DeleteObject(memBM);
DeleteDC(memDC);
// Find the encoder for "image/png" mime type
CLSID encoderClsid;
EncoderParameters encoderParameters;
GetEncoderClsid(L"image/png", &encoderClsid);
encoderParameters.Count = 0;
// Convert file name to Unicode (wide-char) string.
WCHAR fn[_MAX_PATH];
MultiByteToWideChar(CP_THREAD_ACP, MB_PRECOMPOSED, file, file.GetLength() + 1, fn, _MAX_PATH);
// Save the screen shot into the specified file using image encoder with the mime style "image/png"
if(bmp->Save(fn, &encoderClsid, &encoderParameters) != Ok)
{
ermsg.Format("couldn't save image file \"%s\"", file);
return false;
}
::delete bmp;
GdiplusShutdown(gdiplusToken);
return true;
}
The error message implies that you're trying to save the file to a folder that you don't have permission to write to. Many folders such as Program Files are now protected. Since you didn't include the path in your sample code I'm unable to determine if this is the actual problem.
Edit: Another possibility is that the Bitmap is improperly constructed which causes the Save to fail. The second parameter to the constructor is supposed to be a handle to a palette, I think DIB_RGB_COLORS would be invalid here and you should use NULL. Also there are a couple of caveats noted in the Microsoft documentation and perhaps the different OS versions react differently when you break the rules:
You are responsible for deleting the GDI bitmap and the GDI palette. However, you should not delete the GDI bitmap or the GDI palette until after the GDI+ Bitmap::Bitmap object is deleted or goes out of scope.
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.
win7 won't accept encoderParameters.Count == 0 for some reason. Set that == 1 and you should be all set.
you probably could also just remove that parameter from Save() (overloaded)