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;
When saving a bitmap file from a GDI+ metafile object, the output is affected by the current display scale of the monitor.
For example, the following code sample generates these images:
with screen at 100% scaling (81x81 pixel bitmap generated).
with screen at 125% scaling (52x52 pixel bitmap generated).
#include <Windows.h>
// GDI+ libraries
#include <gdiplus.h>
#pragma comment(lib, "gdiplus.lib")
void main()
{
// Startup GDI+
ULONG_PTR gdiplusToken;
Gdiplus::GdiplusStartupInput gdiplusStartupInput;
Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, nullptr);
{
// Create a metafile
const auto dc = GetDC(nullptr);
const auto cdc = CreateCompatibleDC(dc);
Gdiplus::Metafile metafile{ L"test.emf", cdc };
auto graphics = Gdiplus::Graphics::FromImage(&metafile);
// Draw some things
Gdiplus::SolidBrush yellowBrush{ Gdiplus::Color::Yellow };
graphics->FillRectangle(&yellowBrush, 0, 0, 70, 70);
Gdiplus::SolidBrush redBrush(Gdiplus::Color::Red);
graphics->FillRectangle(&redBrush, 10, 10, 50, 50);
delete graphics; // Must be deleted before saving (is this the correct way?)
// Save as BMP (MIME type: image/bmp)
CLSID bmpEncoderId{1434252288, 6660, 4563, {154, 115, 0, 0, 248, 30, 243, 46}};
auto status = metafile.Save(L"test.bmp", &bmpEncoderId);
// Cleanup
DeleteDC(cdc);
ReleaseDC(nullptr, dc);
}
// Shutdown GDI+
Gdiplus::GdiplusShutdown(gdiplusToken);
}
I've tried adding the following in several places (as suggested in other SO answers) but this doesn't seem to change the behviour.
// Set scaling
Gdiplus::MetafileHeader mfh{};
metafile.GetMetafileHeader(&mfh);
graphics->ScaleTransform(mfh.GetDpiX() / graphics->GetDpiX(),
mfh.GetDpiY() / graphics->GetDpiY());
Is it possible to export the full bitmap image independent of what scaling is set on the monitor?
SOLUTION: This was caused by not compiling as DPI-aware.
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.