I'm writing a Win32 application using C++.
In this application I'm handling the WM_PAINT message:
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
GdiplusStartup(&gdiplusToken, &gdiPlusStartup, 0);
DrawM(ps.hdc, hWnd);
EndPaint(hWnd, &ps);
break;
And in the DrawM function I'm having something like this:
void DrawMap(HDC hdc, HWND hWnd)
{
if(!isDrawn)
{
// (some calculations)
Graphics g(hdc);
Bitmap img(max_x, max_y, &g);
int zoom_factor = 50;
for(int i = 0; i< segments.size(); i++)
{
// (some math)
for(int j = 0; j < segments.at(i).point_count; j++)
// (another dose of math)
g.DrawLines(&pen, segmentPoints, segments.at(i).point_count);
delete [] segmentPoints;
}
g.Save();
isDrawn = true;
}
else
{
// here is the problem
}
}
In the code above, what I wanted to do, is to render the image once, and later on when the window is resized, moved or anything happens to it that requires repainting will not render the Bitmap from scratch, instead it should use a cached one.
The problem is that Bitmap does not allow copying (the copy constructor denies it).
Another problem is that, when I'm trying to save the image to a file or a stream I receive an "Invalid parameter" error (i.e the return code is 2):
CLSID pngClsid;
GetEncoderClsid(L"image/png", &pngClsid);
img.Save(_T("m.png"), &Gdiplus::ImageFormatPNG, NULL);
->clone() also seems that it is not working, because when I define a pointer to a Bitmap, clone the bitmap to it and in the "else" statement I use:
Graphics g(hdc);
g.DrawImage(bmpClone, 50, 50);
Nothing is rendered.
Any ideas on how to cache the Bitmap?
Clone() should work, but without seeing your code (which uses it) it's hard to know what's going on. As an alternative, another (more circuitous) approach would be to call GetHBITMAP() on the original Bitmap, store the GDI bitmap handle and then construct the new Bitmap with the Bitmap(HBITMAP, HPALETTE) constructor in future repaints.
Instead of declaring img as a local object, make it a static or a member of a class. Then it will be available at the next WM_PAINT without needing to be copied.
Related
After asking this question, I changed my code. It works, but when WM_PAINT paints the window and the cursor is moving in it, the painting is not done at the same time everywhere. Here you have a video to see it better. This is my WM_PAINT:
//TV is a struct with the dimensions of the window.
//BitmapBuffer is the bitmap containing the painting.
static int cont;
cont++;
HDC HDc;
PAINTSTRUCT ps;
HDc = BeginPaint(identifier, &ps);
Gdiplus::Graphics lienzo (HDc);
AlphaBlend(HDc, 0, 0, TV.width+4, TV.height+4, buffer, 0, 0, TV.width+4, TV.height+4, CopyInfo);
EndPaint(identifier, &ps);
Since the problem is when moving the mouse, maybe the WM_NCHITTEST message has something to do with it:
case WM_NCHITTEST:
POINTS point1 = MAKEPOINTS(lParam);
POINT point2;
point2.x = point1.x;
point2.y = point1.y;
ScreenToClient(hwnd, &point2);
if (PtInRegion(region, point2.x, point2.y))
{
if (inWindow == false) //inWindow is true when the cursor is in the window
{
inWindow = true;
TrackMouseEvent(&structure);
Repaint(); //this function repaint the buffer and after call invalidrect
}
return HTCLIENT;
}
else
{
if (inWindow == true)
{
inWindow = false;
Repaint();
}
return HTTRANSPARENT;
}
break;
Does anyone have an idea why this happens?
It's hard to see the problem, because your code doesn't define Repaint. However, you should be using InvalidateRect to tell Windows which area is being updated.
THAT SAID...
Windows is hard to program for updating images with out using a double buffering technique. This is when you draw to a memory (bitmap) then bitblt the bitmap to the screen.
You find this answer useful Reduce flicker with GDI+ and C++
INTRODUCTION:
I am building small app that displays .emf (Enhanced Windows Metafile) in a window.
If image can not fit inside window, scroll bars will be used to show the invisible parts.
Since I am dealing with metafiles, I am trying to add zooming as well.
RELEVANT INFORMATION:
I am playing an .emf file (from the disk) in memory device context (created with the CreateCompatibleDC API). Then I use BitBlt API to transfer that image into main window's client area. I am doing all this to avoid flickering.
Reading through the MSDN I found documentation about Using Coordinate Spaces and Transformations and immediately realized its potential for solving my task of scaling / scrolling the metafile.
PROBLEM:
I do not know how to use before mentioned APIs to scale / scroll metafile inside memory device context, so I can BitBlt that image into main window's device context (this is my first time tackling this type of task).
MY EFFORTS TO SOLVE THE PROBLEM:
I have experimented with XFORM matrix to achieve scaling, like this:
case WM_ERASEBKGND: // prevents flickering
return 1L;
case WM_PAINT:
{
hdc = BeginPaint(hWnd, &ps);
// get main window's client rectangle
RECT rc = { 0 };
GetClientRect(hWnd, &rc);
// fill rectangle with gray brush
// this is necessery because I have bypassed WM_ERASEBKGND,
// see above
FillRect(hdc, &rc, (HBRUSH)GetStockObject(LTGRAY_BRUSH));
// OK, here is where I tried to tamper with the APIs
// I mentioned earlier
// my goal would be to scale EMF down by half
int prevGraphicsMode = SetGraphicsMode(hdc, GM_ADVANCED);
XFORM zoomMatrix = { 0 };
zoomMatrix.eDx = 0;
zoomMatrix.eDy = 0;
zoomMatrix.eM11 = 0.5;
zoomMatrix.eM12 = 0;
zoomMatrix.eM21 = 0;
zoomMatrix.eM22 = 0.5;
// apply zooming factor
SetWorldTransform(hdc, &zoomMatrix);
// draw image
HENHMETAFILE hemf = GetEnhMetaFile(L".\\Example.emf");
PlayEnhMetaFile(hdc, hemf, &rc);
DeleteEnhMetaFile(hemf);
// restore original graphics mode
SetGraphicsMode(hdc, prevGraphicsMode);
// all done, end painting
EndPaint(hWnd, &ps);
}
return 0L;
In the above snippet, metafile was scaled properly, and was played from the top left corner of the client area.
I didn't bother with maintaining aspect ratio nor centering the image. My main goal for now was to figure out how to use XFORM matrix to scale metafile.
So far so good, at least so I thought.
I tried doing the same as above for the memory device context, but when BitBliting the image I got horrible pixelation, and BitBlitted image was not properly scaled.
Below is the small snippet that reproduces the above image:
static HDC memDC; // in WndProc
static HBITMAP bmp, bmpOld; // // in WndProc; needed for proper cleanup
case WM_CREATE:
{
HDC hdc = GetDC(hwnd);
// create memory device context
memDC = CreateCompatibleDC(hdc);
// get main window's client rectangle
RECT rc = { 0 };
GetClientRect(hwnd, &rc);
// create bitmap that we will draw on
bmp = CreateCompatibleBitmap(hdc, rc.right - rc.left, rc.bottom - rc.top);
// select bitmap into memory device context
bmpOld = (HBITMAP)SelectObject( memDC, bmp );
// fill rectangle with gray brush
FillRect(memDC, &rc, (HBRUSH)GetStockObject(LTGRAY_BRUSH));
// scale EMF down by half
int prevGraphicsMode = SetGraphicsMode(memDC, GM_ADVANCED);
XFORM zoomMatrix = { 0 };
zoomMatrix.eDx = 0;
zoomMatrix.eDy = 0;
zoomMatrix.eM11 = 0.5;
zoomMatrix.eM12 = 0;
zoomMatrix.eM21 = 0;
zoomMatrix.eM22 = 0.5;
// apply zooming factor
SetWorldTransform(memDC, &zoomMatrix);
// draw image
HENHMETAFILE hemf = GetEnhMetaFile(L".\\Example.emf");
PlayEnhMetaFile(memDC, hemf, &rc);
DeleteEnhMetaFile(hemf);
// restore original graphics mode
SetGraphicsMode(memDC, prevGraphicsMode);
// all done end paint
ReleaseDC(hwnd, hdc);
}
return 0L;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
RECT rc = {0};
GetClientRect(hwnd, &rc);
BitBlt(hdc, 0, 0,
rc.right - rc.left,
rc.bottom - rc.top,
memDC, 0, 0, SRCCOPY);
EndPaint(hwnd, &ps);
}
return 0L;
case WM_DESTROY:
SelectObject(memDC, bmpOld);
DeleteObject(bmp);
DeleteDC(memDC);
PostQuitMessage(0);
break;
After rereading carefully documentation for the BitBlit I have found the following important segment:
If other transformations exist in the source device context (and a matching transformation is not in effect in the destination device context), the rectangle in the destination device context is stretched, compressed, or rotated, as necessary.
Using ModifyWorldTransform(memDC, NULL, MWT_IDENTITY); as user Jonathan Potter suggested fixed this problem.
Those are my tries as far as scaling is concerned. Then I tried to implement scrolling by experimenting with the SetWindowOrgEx and similar APIs.
I have managed to move the image with few experiments, but I haven't fully grasped why and how that worked.
The reason for that is that I was unable to fully understand terms like window and viewport origins and similar. It is just too abstract for me at the moment. As I write this post, I am rereading again and trying to solve this on my own.
QUESTIONS:
How can I use the APIs (from the link I added above) to scale/scroll/scale and scroll metafile in memory DC, and properly BitBlt it in the main window device context?
REMARKS:
I realize that code example could be large, so I do not ask any. I do not want people to write the code for me, but to help me understand what I must do. I just need to fully grasp the concept of applying the above APIs the way I need. Therefore answers/comments can include instructions and small pseudo code, if found appropriate. I realize that the question might be broad, so I would appreciate if you could help me narrow down my problem with constructive criticism and comments.
Thank you for reading this post, offering help, and your understanding.
Best regards.
According to the SetWorldTransform documentation:
it will not be possible to reset the graphics mode for the device
context to the default GM_COMPATIBLE mode, unless the world
transformation has first been reset to the default identity
transformation
So even though you are attempting to reset the graphics mode, with this call:
SetGraphicsMode(memDC, prevGraphicsMode);
This is actually not working, because you are not first resetting the transformation to the identity transform. Adding the following line before the SetGraphicsMode call resets the transform and returns the DC to normal mapping:
ModifyWorldTransform(memDC, NULL, MWT_IDENTITY);
SetGraphicsMode(memDC, prevGraphicsMode);
I am drawing polygons (Polygon(dc, points, 3)) using in WM_PAINT event using C++. I have a big number of polygons so I am trying to implement multithreading. I am running VS2013 so I have included thread. I have created a function I want to run on a thread:
void anyFunс2(HDC dc, int index)
{
POINT points[3];
for (unsigned i = index; i < model->vertexFaces.size(); i += fs)
{
// Here we convert our points to Clip and Windowed Coordinates
// and only then fetch the results
if (Algorithms::FetchPolygons(&model->finalizedVertices[0], model->vertexFaces[i], points))
{
Polygon(dc, points, 3);
}
}
}
For instance I have three threads. I have designed the code the way where each thread renders every third element. For example first thread renders 0,3,6,9 polygons, second thread renders 1,4,7,10 and the final thread renders 2,5,8,11 polygons.
Here's my WM_PAINT event:
case WM_PAINT:
{
// Get dc here
hdc = GetDC(hWnd);
// Create a backbuffer here
bdc = CreateCompatibleDC(hdc);
// Get the screen dimensions
RECT client;
GetClientRect(hWnd, &client);
// Create bitmap
HBITMAP backBuffer = CreateCompatibleBitmap(hdc, client.right - client.left, client.bottom - client.top);
// Release it, because we no longer need it
hdc = NULL;
ReleaseDC(hWnd, hdc);
// Select the back dc as a current one and specify the screen dimensions
HPEN hPen = CreatePen(PS_SOLID, 1, RGB(0, 25, 205));
HBRUSH hBrush = CreateSolidBrush(RGB(0, 0, 55));
SelectObject(bdc, hPen);
SelectObject(bdc, hBrush);
SelectObject(bdc, backBuffer);
Rectangle(bdc, client.left, client.top, client.right, client.bottom);
// Call some threads to draw polygons on our BDC
for (unsigned i = 0; i < func_threads.size(); i++)
{
func_threads.at(i) = thread(anyFunс2, bdc, i);
}
// Wait until all threads finish their job
for (unsigned i = 0; i < func_threads.size(); i++)
{
if (func_threads[i].joinable()) func_threads[i].join();
}
// Swap buffers
hdc = BeginPaint(hWnd, &ps);
BitBlt(hdc, client.left, client.top, client.right, client.bottom, bdc, 0, 0, SRCCOPY);
EndPaint(hWnd, &ps);
// Delete all created objects from memory
DeleteObject(backBuffer);
DeleteObject(hBrush);
DeleteObject(hPen);
DeleteDC(bdc);
break;
}
As you can see I run these threads in the loop. Then I have another loop where the Join() method is located for each thread. These threads draw polygons to the same HDC (I assume). After the main thread have finished waiting for all these threads it copies everything from the back buffer to the main one. However the problem is that the object is not fully drawn. I mean not all polygons are drawn. The link to the image is attached here. Please help me, why is it happening like that?!
The short answer is that GDI simply isn't designed to support drawing from multiple threads into the same DC simultaneously.
That leaves you with a few choices. The most direct would be to use PolyPolygon to draw all your polygons (or at least large numbers of them) in a single call. This seems particularly relevant in your case--from the looks of things, you're drawing lots of triangles, so a lot of the time taken is probably just the overhead to call the Polygon function, and not really time for Polygon to execute.
Another possibility would be to create a separate back-buffer for each thread to draw into, then use BitBlit to combine those together with (for example) an OR function, so you get the same overall effect as the original drawing.
The third (and probably best) way to support drawing a large number of polygons would be to switch from using GDI to using something like DirectX or OpenGL that's designed from the ground up to support exactly that.
You can use CreateDIBsection() to draw shapes with multiple threads.
Just have each thread draw pixels directly to the DIB. Then when all the threads are complete, you can bitBlt the DIB to the screen.
I have some problem with my designs for function, and I do not find a proper solution (I am quite a beginner in C++).
Some days ago, I ask another question which is linked to this one.
So, I have a function that makes a screen capture. It works perfectly.
The fact is that I want this function to return an Image object from a class I implemented.
Basically, it is:
class Image {
public:
BYTE *bitPointer;
int width;
int height;
};
And my function is like this one:
Image screenCapture(int width, int height) {
HDC hdcTemp, hdc;
BYTE* bitPointer;
hdc = GetDC(HWND_DESKTOP);
hdcTemp = CreateCompatibleDC(hdc);
BITMAPINFO bitmap;
bitmap.bmiHeader.biSize = sizeof(bitmap.bmiHeader);
bitmap.bmiHeader.biWidth = width;
bitmap.bmiHeader.biHeight = -height;
bitmap.bmiHeader.biPlanes = 1;
bitmap.bmiHeader.biBitCount = 24;
bitmap.bmiHeader.biCompression = BI_RGB;
bitmap.bmiHeader.biSizeImage = 0;
bitmap.bmiHeader.biClrUsed = 0;
bitmap.bmiHeader.biClrImportant = 0;
HBITMAP hBitmap = CreateDIBSection(hdcTemp, &bitmap, DIB_RGB_COLORS, (void**)(&bitPointer), NULL, NULL);
SelectObject(hdcTemp, hBitmap);
BitBlt(hdcTemp, 0, 0, width, height, hdc, 0, 0, SRCCOPY);
ReleaseDC(HWND_DESKTOP, hdc);
DeleteDC(hdcTemp);
Image screen;
screen.bitPointer = bitPointer;
screen.width = width;
screen.height = height;
return screen;
}
The bitPointer created using CreateDIBSection is actually a pointer to the value of my first pixel (if I understood well).
Then, I can use a simple loop:
for (int i = 0; i >= 0; i++) {
cout << i << ": " << (int)screenCapture(1366, 768).bitPointer[0] << endl;
}
Here is my issue.
I have to free up the hBitmap by calling DeleteObject(hBitmap) otherwise I would have no more memory space (and the loop finally crash).
However, I do not know where to do it.
I would like to do it within my function but if I call DeleteObject(), then it will destroy my bitPointer too and therefore I will not be able to access pixels from my Image object returned.
In fact, I am a little confused with the fact that my bitPointer variable is a pointer. It means that I can not copy it to prevent it being destroyed after. I can not find the solution.
Two solutions I have tried:
Create a destructor calling DeleteObject() for my class Image.
It did not work because the destructor is call from the function and not only from inside my loop.
Add an attribute HBITMAP hBitmap to my class Image, and call DeleteObject() from inside my loop.
It is not really convenient, moreover I have to declare a new Image object, I can not do it anonymously like in the loop I writed.
So, I am stuck, could anyone help me please?
You can pass hBitmap as an argument to screenCapture function.
screenCapture(int width, int height, HBITMAP& hBitmap)
This way you get to destroy it when you want without interfering with Image members. You declare an "empty" hBitmap outside screenCapture function and pass it as argument. The function will use the reference to create the object and you will be able to destroy it outside of screenCapture after you are done with using bitPointer values.
If you need storing all screenshots, you need to implement a working buffer as you have finite amount of memory.
(unmanaged C++)
I already succeeded drawing PNG files to a transparent layered window that I can drag around the desktop, but now my problem is drawing text on a transparent layered window
Here's my code and my attempt at drawing text in the middle, it's important to note that i'm using the screenDC instead of using the one in WM_PAINT messages
[edit]
updated code after the comments, now i'm just trying to write text on the bitmap before getting the HBITMAP version which i need to use
this time I'm using DrawString because textout() isn't GDI+, I hope DrawString really is GDI+ lol
still doesn't work though, wonder what i'm doing wrong
void Draw() // draws a frame on the layered window AND moves it based on x and y
{
HDC screenDC( NULL ); // grab screen
HDC sourceDC( CreateCompatibleDC(screenDC) );
POINT pos = {x,y}; // drawing location
POINT sourcePos = {0,0}; // top left of image
SIZE size = {100,100}; // 100x100 image
BLENDFUNCTION blendFunction = {0};
HBITMAP bufferBitmap = {0};
Bitmap* TheBitmap = crnimage; // crnimage was already loaded earlier
// ------------important part goes here, my attempt at drawing text ------------//
Gdiplus::Graphics Gx(TheBitmap);
// Font* myFont = new Font(sourceDC);
Font myFont(L"Arial", 16);
RectF therect;
therect.Height = 20;
therect.Width = 180;
therect.X = 0;
therect.Y = 0;
StringFormat format;
format.SetAlignment(StringAlignmentCenter);
format.GenericDefault();
Gdiplus::SolidBrush GxTextBrush(Gdiplus::Color(255, 255, 0,255));
WCHAR thetext[] = L"Sample Text";
int stats = Gx.DrawString(thetext, -1, &myFont, therect, &format, &GxTextBrush);
if(stats) // DrawString returns nonzero if there is an error
msgbox(stats);
stats = Gx.DrawRectangle(&Pen(Color::Red, 3), therect);
// the rectangle and text both draw fine now
// ------------important part goes here, my attempt at drawing text ------------//
TheBitmap->GetHBITMAP(0, &bufferBitmap);
HBITMAP oldBmpSelInDC;
oldBmpSelInDC = (HBITMAP)SelectObject(sourceDC, bufferBitmap);
// some alpha blending
blendFunction.BlendOp = AC_SRC_OVER;
blendFunction.SourceConstantAlpha = wndalpha;
blendFunction.AlphaFormat = AC_SRC_ALPHA;
COLORREF colorKey( RGB(255,0,255) );
DWORD flags( ULW_ALPHA);
UpdateLayeredWindow(hWnd, screenDC, &pos, & size, sourceDC, &sourcePos,
colorKey, &blendFunction, flags);
// release buffered image from memory
SelectObject(sourceDC, oldBmpSelInDC);
DeleteDC(sourceDC);
DeleteObject(bufferBitmap);
// finally release the screen
ReleaseDC(0, screenDC);
}
I've been trying to write text on my layered window for two days now, but from those attempts I know there are several ways I can go about doing this
(unfortunately I have no idea how exactly)
The usual option I see is drawing text on a bitmap, then rendering the bitmap itself
Use Gdi+ to load a bitmap
Create a Graphics object from the bitmap
Use DrawString to write text to the bitmap
Dispose of the Graphics object
Use the bitmap Save method to save the result to a file
Apparently one can also make a graphics object from a DC, then draw text on the DC, but again i have no clue as to how to do this
The overall approach looks right, but I think you've got some problems with the DrawString call. Check out the documentation (especially the sample) on MSDN.
Gx.DrawString(thetext, 4, NULL, therect, NULL, NULL)
The third, fifth, and sixth parameters (font, format, and brush) probably need to be specified. The documentation doesn't say that they are optional. Passing NULL for these is probably causing GDI+ to treat the call as a no-op.
The second parameter should not include the terminating L'\0' in the string. It's probably safest to use -1 if your string is always terminated.