I'm using the GDI+ API just to display an image (bmp) on the screen. Here is the code of the function doing the task:
void drawImg_ArmorInfo_PupupWnd(HDC hdc) {
GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
Image* image = new Image(L"C:/Users/Darek/F2 stuff/art/intrface/armor_info_1.bmp");
int a1 = image->GetWidth();
int a2 = image->GetHeight();
Graphics graphics(hdc);
graphics.DrawImage(image, 0, 0);
delete image;
GdiplusShutdown(gdiplusToken);
}
The problem is that I'm getting an exception: Access violation writing location 0xXXXXXXXX after calling the GdiplusShutdown function. What's interesting when I comment out the
Graphics graphics(hdc);
graphics.DrawImage(image, 0, 0);
part the program runs without any problems but, of course, the image isn't drawn. When I comment out the call to GdiplusShutdown function - no problems again.
What's wrong with the code and what can be the reason of the problem here?
graphics falls out of the scope after the GdiplusShutdown so it cannot destruct correctly.
Try this:
Graphics * graphics = new Graphics(hdc);
graphics->DrawImage(image, 0, 0);
delete graphics;
Related
I'm using the GDI+ API just to display an image (bmp) on the screen. Here is the code of the function doing the task:
void drawImg_ArmorInfo_PupupWnd(HDC hdc) {
GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
Image* image = new Image(L"C:/Users/Darek/F2 stuff/art/intrface/armor_info_1.bmp");
int a1 = image->GetWidth();
int a2 = image->GetHeight();
Graphics graphics(hdc);
graphics.DrawImage(image, 0, 0);
delete image;
GdiplusShutdown(gdiplusToken);
}
The problem is that I'm getting an exception: Access violation writing location 0xXXXXXXXX after calling the GdiplusShutdown function. What's interesting when I comment out the
Graphics graphics(hdc);
graphics.DrawImage(image, 0, 0);
part the program runs without any problems but, of course, the image isn't drawn. When I comment out the call to GdiplusShutdown function - no problems again.
What's wrong with the code and what can be the reason of the problem here?
graphics falls out of the scope after the GdiplusShutdown so it cannot destruct correctly.
Try this:
Graphics * graphics = new Graphics(hdc);
graphics->DrawImage(image, 0, 0);
delete graphics;
I am unable to manage memory for Bitmap and CLSID objects I have created in a screenshot object class. Both of these are from the GDI+ library. The header lists the following private variables in Screenshot.h
#include <gdiplus.h>
#include <iostream>
#include <fstream>
#include <string>
#include "windows.h"
#pragma once
#pragma comment(lib, "gdiplus.lib")
using namespace std;
using namespace Gdiplus;
class Screenshot
{
private:
HDC dc, memdc, fontdc;
HBITMAP membit;
Bitmap* bmpPtr;
CLSID clsid;
ULONG_PTR gdiplusToken;
int GetEncoderClsid(const WCHAR* format, CLSID* pClsid);
public:
Screenshot();
~Screenshot();
void TakeScreenshot(string userAction, string winName, long xMousePos, long yMousePos, long long tStamp);
void SaveScreenshot(string filename);
void memoryManagement();
};
Then when my main program takes a screenshot, the values are filled in with TakeScreenshot(), but not yet saved to disk
void Screenshot::TakeScreenshot(//redacted for readibility) {
GdiplusStartupInput gdiplusStartupInput;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
HWND hwnd = GetDesktopWindow();
dc = ::GetDC(0);
int scaleHeight, scaleWidth = 0;
int Height = GetSystemMetrics(SM_CYVIRTUALSCREEN);
int Width = GetSystemMetrics(SM_CXVIRTUALSCREEN);
scaleHeight = Height + (0.1 * Height);
memdc = CreateCompatibleDC(dc);
membit = CreateCompatibleBitmap(dc, Width, scaleHeight);
HBITMAP bmpContainer = (HBITMAP)SelectObject(memdc, membit);
BitBlt(memdc, 0, 0, Width, Height, dc, 0, 0, SRCCOPY);
//Other code that adds fonts, etc. Does not invoke bmpPtr
bmpPtr = new Bitmap(membit, NULL);
GetEncoderClsid(L"image/jpeg", &clsid);
If the screenshot is saved, another function SaveScreenshot() uses bmpPtr->Save() and Gdiplus shutdown is called inside of it. However, some of the screenshots get popped off from a queue (STL queue) and out of memory instead of saved, as follows:
void ManageQueue(Screenshot& ssObj)
{
//If queue contains 30 screenshots, pop off first element and push new object
//Else just push new object
if (screenshotQueue.size() == MAX_SCREENSHOTS)
{
screenshotQueue.front().memoryManagement();
screenshotQueue.pop();
screenshotQueue.push(ssObj);
}
else
{
screenshotQueue.push(ssObj);
}
}
I wrote a MemoryManagement() function to perform the necessary releases and deletes before the Screenshot is popped off. This function is not called if the screenshot has been saved:
void Screenshot::memoryManagement()
{
delete bmpPtr;
delete &clsid;
ReleaseDC(NULL, memdc);
DeleteObject(fontdc);
DeleteObject(memdc);
DeleteObject(membit);
}
When either the delete on bmpPtr or clsid is called, whether it is from this function or in the deconstructor, the program is crashing. I am experiencing significant memory leaks with the program now and without running a windows equivalent of Valgrind I'm assuming it's coming from here. How can I successfully delete these objects? I will credit any answer in my source code as a contributing programmer. Please leave any suggestions for improving my question if needed.
scaleHeight = Height + (0.1 * Height);
This seems to be an attempt to fix the problem with DPI scaling. It will work if DPI settings is at 10%, but that's usually not the case. You have to make your program DPI aware through the manifest file. Use SetProcessDPIAware for a quick fix.
Don't declare dc, memdc, etc. as class members. These are GDI handles (not GDI+) which you can hold for a short time, usually during the duration of the function. You have to release them as soon as possible.
Also other variables like clsid don't need to be declared as class members. You can declare them as class member if you wish, but there is nothing to gain.
If you have a multi-monitor setup you also need SM_XVIRTUALSCREEN/Y to get the top-left corner of the monitor setup.
//call this once on start up
SetProcessDPIAware();
HDC dc = ::GetDC(0);
int x = GetSystemMetrics(SM_XVIRTUALSCREEN);
int y = GetSystemMetrics(SM_YVIRTUALSCREEN);
int Height = GetSystemMetrics(SM_CYVIRTUALSCREEN);
int Width = GetSystemMetrics(SM_CXVIRTUALSCREEN);
HDC memdc = CreateCompatibleDC(dc);
HBITMAP membit = CreateCompatibleBitmap(dc, Width, Height);
HBITMAP bmpContainer = (HBITMAP)SelectObject(memdc, membit);
BitBlt(memdc, 0, 0, Width, Height, dc, x, y, SRCCOPY);
Bitmap* bmpPtr = new Bitmap(membit, NULL);
// or just Bitmap bmp(membit, NULL);
CLSID clsid;
GetEncoderClsid(L"image/jpeg", &clsid);
bmpPtr->Save(L"output.jpg", &clsid);
//cleanup:
delete bmpPtr;
SelectObject(memdc, bmpContainer);
DeleteObject(membit);
DeleteDC(memdc);
ReleaseDC(0, dc);
The solution for this problem was to use the namespace delete instead of regular delete. Switching to this prevented the break point trigger during debug and has sealed the memory leak.
void Screenshot::memoryManagement()
{
::delete bmpPtr;
ReleaseDC(NULL, memdc);
DeleteObject(fontdc);
DeleteObject(memdc);
DeleteObject(membit);
GdiplusShutdown(gdiplusToken);
}
ive been making a c++ drawing program and i have a part where im drawing using gdi+ inside a function so i need to declare my graphics object every time i call the function.. this obviously is wrong and causes a leak somewhere so after some calls it gets slower and then suddenly stops drawing all together ,as expected.
im trying to clear my objects every time it finishes drawing but it doesnt seem to solve the issue. i thought maybe the PEN objects are part of the issue but some insight would be very helpful
my code:
void function()
{
DWORD pdwGduStartup;
GdiplusStartupInput GdiStartupInp;
GdiplusStartup(&pdwGduStartup, &GdiStartupInp, NULL);
Pen pnPen_Blue(Gdiplus::Color(255, 0, 0, 255), 2.0F);
Pen pnPen_Green(Gdiplus::Color(255, 255, 0, 255), 2.0F);
LPCSTR LGameWindow = "MyWindow";
HWND hGameWindow = FindWindow(NULL, LGameWindow);
Graphics graphics(GetDC(hGameWindow));
for (int n=10; n>0; n--)
graphics.DrawLine(&pnPen_Green,0, 0, 0, n);
GdiplusShutdown(pdwGduStartup);
graphics.Flush();
}
thanks alot!
edit: added release DC did not do anything!
DWORD pdwGduStartup;
GdiplusStartupInput GdiStartupInp;
GdiplusStartup(&pdwGduStartup, &GdiStartupInp, NULL);
Pen pnPen_Blue(Gdiplus::Color(255, 0, 0, 255), 2.0F);
Pen pnPen_Green(Gdiplus::Color(255, 255, 0, 255), 2.0F);
LPCSTR LGameWindow = "MyWindow";
HWND hGameWindow = FindWindow(NULL, LGameWindow);
HDC GDC = GetDC(hGameWindow);
Graphics graphics(GDC);
ReleaseDC(hGameWindow, GDC);
GdiplusShutdown(pdwGduStartup);
graphics.Flush();
My GDI objects still rises up to 10,000!
GdiplusShutdown must be called after all GDI+ objects are removed. In your code Pen and Graphics destructors are called after GdiplusShutdown, when the objects go out of scope.
I guess the problem is in GetDC. You should call ReleaseDC or use WTL smart pointers.
As I remember such things where easy to debug with task manager. It shows HANDLE count as well as GDI object count for a process, so you can see what lines generate new objects.
I didnt code my GDI+ right logically. in the logic first you need to create the graphics object, and only then loop draw on it. anything else is WRONG and code wont fix it!
Try something like this, use pointers that you can easily delete when done.
DWORD pdwGduStartup;
GdiplusStartupInput GdiStartupInp;
GdiplusStartup(&pdwGduStartup, &GdiStartupInp, NULL);
Pen pnPen_Blue* = new Pen(Gdiplus::Color(255, 0, 0, 255), 2.0F);
Pen pnPen_Green* = new Pen(Gdiplus::Color(255, 255, 0, 255), 2.0F);
LPCSTR LGameWindow = "MyWindow";
HWND hGameWindow = FindWindow(NULL, LGameWindow);
HDC GDC = GetDC(hGameWindow);
Graphics* g = new Graphics(GDC);
// Do stuff here
ReleaseDC(hGameWindow, GDC);
delete pnPen_Blue;
delete pnPen_Green;
g->Flush();
delete g;
delete GDC; // not sure
GdiplusShutdown(pdwGduStartup);
#include <gdiplus.h>
using namespace Gdiplus;
#pragma comment (lib,"Gdiplus.lib")
then draw some text:
GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
HDC hdc;
Font *fn = new Font(TEXT("Arial"),50);
hdc=GetDC(hWnd);
ColorBlend cb = new ColorBlend();
LinearGradientBrush *linGrBrush=new LinearGradientBrush(
Point(0, 10),
Point(200, 10),
Color(255, 255, 0, 0),
Color(255, 0, 0, 255));
Graphics *graphics=new Graphics(hdc);
PointF drawPoint = PointF(150.0F,150.0F);
SolidBrush* myBrush = new SolidBrush(Color::Black);
graphics->DrawString(L"Test text",strlen("Test text"),fn,drawPoint,linGrBrush);
GdiplusShutdown(gdiplusToken);
And had error that ColorBlend not found identifier,but seem all right. How I can fix it?
The ColorBlend class is part of the .Net Framework, as far as I can tell there is nothing by that name in GDI+ for C++.
I think the corresponding function in GDI+ is LinearGradientBrush::SetInterpolationColors
As far as I understand the .NET documentation the InterpolationColors member in GDI+ is used here with this function.
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)