Using Coordinate Spaces and Transformations to scroll and scale Enhanced Windows Metafile - 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);

Related

Create a GDI Rectangle image

I must be doing something wrong or have missed something because all I actually want to is render a rectangle into a bitmap, so that I can CreateWindowEx() on it. Does anyone know what I'm missing?
HDC hdc = GetDC(hWnd);
// Create Pen and brush for the rectangle
HPEN pn = CreatePen(style, stroke, pen);
HBRUSH br = CreateSolidBrush(brush);
// Create a compatible bitmap and DC from the window DC with the correct dimensions
HDC bm_hdc = CreateCompatibleDC(hdc);
HBITMAP hImage = CreateCompatibleBitmap(bm_hdc, sz.x, sz.y);
// Select the bitmap, pen, brush into the DC
HGDIOBJ bm_obj = SelectObject(bm_hdc, hImage);
HGDIOBJ pn_obj = SelectObject(bm_hdc, pn);
HGDIOBJ br_obj = SelectObject(bm_hdc, br);
// Draw the rectangle into the compatible DC with the bitmap selected
::Rectangle(bm_hdc, xPos, yPos, xPos + xSize, yPos + ySize);
// Restore the old selections
SelectObject(bm_hdc, br_obj);
SelectObject(bm_hdc, pn_obj);
SelectObject(bm_hdc, bm_obj);
// Delete the not needed DC, pen and brush
DeleteDC(bm_hdc);
DeleteObject(br);
DeleteObject(pn);
ReleaseDC(hWnd, hdc);
// Create the window and send a message to set the static image
HWND win = CreateWindow(TEXT("STATIC"), NULL, WS_CHILD | SS_BITMAP | WS_VISIBLE, pos.x, pos.y, sz.x, sz.y, hWnd, NULL, hInst, NULL)));
HGDIOBJ obj = (HGDIOBJ)SendMessage(win, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)hImage);
// Delete the old image
if (obj)
DeleteObject(hImage);
Hummmm... but this doesn't work... All I get is a completely black area and not the rectangle that I have drawn. Any ideas why? Do I need to create another DC and BitBlt() between device contexts?
Thanks for all the help everyone, but I've actually solved it myself and it was SUCH a silly mistake too... Consider this line...:-
::Rectangle(bm_hdc, xPos, yPos, xPos + xSize, yPos + ySize);
Nothing wrong with that at first glance, right? WRONG! If you look at my code, I create a compatible bitmap of the required size to contain my rectangle and try to render the rectangle into this bitmap (which is selected into the DC).
But... WHERE in the bitmap am I rendering? xPos and yPos are window positions of the rectangle, but I'm not rendering to the Window DC am I?!? d'oh! That's right, xPos and yPos should both be 0 because I'm rendering into a bitmap of the correct size and it's when the Window is displayed that xPos and yPos should contain screen coordinates!
Wow... what a dumb mistake and thanks for the nice spot on the HDC from the Window rather than from the compatible DC. I did know that a memory DC has a 1bit depth, but still made that classic blunder. Thanks everyone.
Try changing this line HBITMAP hImage = CreateCompatibleBitmap(bm_hdc, sz.x, sz.y); into this :
HBITMAP hImage = CreateCompatibleBitmap(hdc, sz.x, sz.y);
Paul Watt wrote excellent articles for GDI and image composition with MsImage32.dll.
I am reffering you to this article because it addresses your problem, and here are the relevant quotes and code snippets:
The memory DC is initialized with a mono-chromatic 1x1 pixel bitmap by default.
Avoid a Common Mistake
Before we get too far away from code where I showed you what you need to start running, I want to make sure you are holding this new pair of scissors safely. Do not use a Memory DC with a call to CreateCompatibleBitmap.
...
// You may be tempted to do this; DON'T:
HDC hMemDC = ::CreateCompatibleDC(hDC);
// DON'T DO THIS
// |
// V
HBITMAP hBmp = ::CreateCompatibleBitmap(hMemDC, width, height);
...
// TIP: Try to use the same DC to create
// the Bitmap which you used to create the memory DC.
Remember the part about how The memory DC is initialized with a mono-chromatic 1x1 pixel bitmap by default?!
As for the remarks of member Raymond Chen I believe he is also right, but since you said that your actual code is different this is the only thing I can see as a mistake.
Hopefully this helps.
Best regards.

Combine StretchBlt and TransparentBlt properly, so transparent bitmap can be created properly

INTRODUCTION AND RELEVANT INFORMATION:
Recently, I have asked, here in SO, a question about scaling a bitmap properly, so it can keep the quality of the picture:
Bitmap loses quality when stretched/shrinked on buttons background.
I have tried to employ a suggestion made in a comment, to use `StretchBlt, so I have made a small demo program.
It did improve the bitmaps sharpness, after I have set stretch mode to BLACKONWHITE.
I would like to try to make the portion of the bitmap, with the certain color-say black for example, transparent.
I have used TransparentBlt before, but I don't know how to do it now.
PROBLEM:
In order to preserve the sharpness of the picture, I need to StretchBlt it in the memory DC, with stretch mode being BLACKONWHITE.
The problem is that I do not know how to Blt it transparently into main window's DC.
Here is a code snippet from the demo app:
case WM_PAINT:
{
// main window's DC
hdc = BeginPaint(hWnd, &ps);
// main window's client rectangle
RECT r;
GetClientRect( hWnd, &r );
// memory DC for double buffering
HDC MemDC = CreateCompatibleDC( hdc );
// fill it with test brush
FillRect( MemDC, &r, (HBRUSH)GetStockObject( GRAY_BRUSH ) );
// select loaded bitmap into memory DC
HBITMAP old = (HBITMAP)SelectObject( MemDC, bmp );
// get bitmaps dimensions
BITMAP b;
GetObject( bmp, sizeof(BITMAP), &b );
// needed to preserve bitmap's sharpness
SetStretchBltMode( hdc, BLACKONWHITE );
StretchBlt( hdc, 0, 0, r.right - r.left, r.bottom - r.top,
MemDC, 0, 0, b.bmWidth, b.bmHeight, SRCCOPY );
/* TransparentBlt( ... ); call should go here,
so I can make portion of the bitmap transparent,
in order for the gray brush can be seen */
// cleanup
SelectObject( MemDC, old );
DeleteDC(MemDC);
EndPaint(hWnd, &ps);
}
return 0L;
break;
QUESTION:
How to modify the above code, so a bitmap can be transparent, in order for test brush to be seen ?
The original image is bellow.
I just need to use TransparentBlt( ..., RGB( 0, 0, 0 ) ); to make it transparent in black areas.
The example picture that shows result:
MY EFFORTS:
Browsing through Internet, I have found only simple tutorials, regarding double buffering.
I haven't found anything like this, but to be honest, I am inexperienced in WIN32 API, so I don't know how to phrase the question properly, in order to get better search results.
If further information is required, ask for it and I will supply it.
It is omitted to keep the question short.
You Need to create a mask use specific raster operations to copy only the Pixels were the mask is defined.
http://www.winprog.org/tutorial/transparency.html
The next code is MFC, but you can easily extract and convert the MFC objects into the Standard GDI operations.
http://www.codeproject.com/Articles/703/Drawing-Transparent-Bitmap-with-ease-with-on-the-f

MSPaint-like app writing. How to do BitBlt right?

I'm writing now simple mspaint-like program in C++ using windows.h (GDI). For my program I need only pen tool. So, I need to store somewhere main window's picture (for ex. in memory HDC and HBITMAP) to draw it after in WM_PAINT message.
When I first have to store window's HDC to my memory HDC and HBITMAP? In what message I should store window? For example, I think we can't do it in WM_CREATE because we have no window yet.
What is the difference between PatBlt and BitBlt? What should I use for my app?
How to copy window's HDC content to my memory HDC and Bitmap? I'm trying to do something like this:
LPRECT lpRect;
GetClientRect(hwnd, lpRect);
width = lpRect->right - lpRect->left;
height = lpRect->bottom - lpRect->top;
HDC hDC = GetDC(hwnd);
memoryDC = CreateCompatibleDC(hDC);
memoryBitmap = CreateCompatibleBitmap(hDC, width, height);
SelectObject(memoryDC, memoryBitmap);
PatBlt(memoryDC, 0, 0, width, height, PATCOPY);
ReleaseDC(hwnd, hDC);
But this don't work: program crashes.
How to restore window in WM_PAINT after that?
How to clear my window with white color?
1: I would recommend you lazy load your off-screen canvas as late as possible. If you need it in WM_PAINT and you haven't created it yet, create it then. If you need it at the point someone begins drawing, create it then. If it exists when you need it, then use it.
2: PatBlt fills a region of a bitmap using the device context's current brush. Brushes define patterns, which is why it's called PatBlt. BitBlt copies data from a source bitmap to a destination bitmap. You would use a BitBlt when you wanted to move the image from the off-screen bitmap to the frame buffer.
3: The lpRect parameter of GetClientRect is an output parameter. That means you have to supply the memory. In this case, GetClientRect is trying to write the rectangle to a null pointer and causing the crash.
RECT clientRect;
GetClientRect(hwnd, &clientRect);
width = clientRect.right - clientRect.left;
height = clientRect.bottom - clientRect.top;
WM_PAINT: seems to be the best place to create the memory hdc. You can do something like this
WM_PAINT:
if (!first_paint)
{
...code
first_paint = true;
}
...more code
break;

How to find out DC's dimensions?

Let's say I have a handle to device context (naturally, in Windows environment):
HDC hdc;
How can I get the width and height of it?
A device context (DC) is a structure that defines a set of graphic objects and their associated attributes, and the graphic modes that affect output.
By width and height I'm guessing you are referring to the bitmap painted ?
If so then i guess you can try the following :
BITMAP structBitmapHeader;
memset( &structBitmapHeader, 0, sizeof(BITMAP) );
HGDIOBJ hBitmap = GetCurrentObject(hDC, OBJ_BITMAP);
GetObject(hBitmap, sizeof(BITMAP), &structBitmapHeader);
//structBitmapHeader.bmWidth
//structBitmapHeader.bmHeight
I also know little about GDI, but it seems GetDeviceCaps might do the trick.
This simple piece of code I use always to get the dimensions of the rendering area, when I have only the HDC.
First, you must get a HWND from the HDC - is simple, then you can get the client rect of this HWND:
RECT rcCli;
GetClientRect(WindowFromDC(hdc), &rcCli);
// then you might have:
nWidth = rcCli.right-rcCli.left;
nHeight = rcCli.bottom-rcCli.top;
As a disclaimer, I know nothing about GDI or what you have to work with in your application. I'm just trying to be helpful if possible.
That said, I found a link which seems to suggest that it's appropriate to use GetClientRect to get the size of the drawing area:
RECT clientRect;
GetClientRect(hWnd,&clientRect);
http://www.toymaker.info/Games/html/gdi.html#winsize
You could WindowFromDC(...) to get the DC's window if it's associated with a window. You could then use #KevinK's answer to get the client rect from this.
but if get Calculator' window_dc dimension, it will failed at “GetCurrentObject” or "GetObject", i think maybe the window attribute include "ws_ex_noredirectionbitmap", how to get dismension in this case?
HDC win_dc = ::GetWindowDC(hwnd);
BITMAP bm = { 0 };
HGDIOBJ hBitmap = GetCurrentObject(win_dc, OBJ_BITMAP);
if (hBitmap)
{
if (GetObject(hBitmap, sizeof(BITMAP), &bm))
{
windc_dimension.cx = bm.bmWidth;
windc_dimension.cy = bm.bmHeight;
}
}

Creating a transparent window in C++ Win32

I'm creating what should be a very simple Win32 C++ app whose sole purpose it to ONLY display a semi-transparent PNG. The window shouldn't have any chrome, and all the opacity should be controlled in the PNG itself.
My problem is that the window doesn't repaint when the content under the window changes, so the transparent areas of the PNG are "stuck" with what was under the window when the application was initially started.
Here's the line where I setup the new window:
hWnd = CreateWindowEx(WS_EX_TOPMOST, szWindowClass, szTitle, WS_POPUP, 0, height/2 - 20, 40, 102, NULL, NULL, hInstance, 0);
For the call to RegisterClassEx, I have this set for the background:
wcex.hbrBackground = (HBRUSH)0;
Here is my handler for WM_PAINT message:
case WM_PAINT:
{
hdc = BeginPaint(hWnd, &ps);
Gdiplus::Graphics graphics(hdc);
graphics.DrawImage(*m_pBitmap, 0, 0);
EndPaint(hWnd, &ps);
break;
}
One thing to note is that the application is always docked to the left of the screen and doesn't move. But, what's underneath the application may change as the user opens, closes or moves windows under it.
When the application first starts, it looks perfect. The transparent (and simi-transparent) parts of the PNG show through perfectly. BUT, when the background underneath the application changes, the background DOESN'T update, it just stays the same from when the application first started. In fact, WM_PAINT (or WM_ERASEBKGND does not get called when the background changes).
I've been playing with this for quite a while and have gotten close to getting 100% right, but not quite there. For instance, I've tried setting the background to (HBRUSH) NULL_BRUSH and I've tried handling WM_ERASEBKGND.
What can be done to get the window to repaint when the contents under it changes?
I was able to do exactly what I wanted by using the code from Part 1 and Part 2 of this series:
Displaying a Splash Screen with C++
Part 1: Creating a HBITMAP archive
Part 2: Displaying the window archive
Those blog posts are talking about displaying a splash screen in Win32 C++, but it was almost identical to what I needed to do. I believe the part that I was missing was that instead of just painting the PNG to the window using GDI+, I needed to use the UpdateLayeredWindow function with the proper BLENDFUNCTION parameter. I'll paste the SetSplashImage method below, which can be found in Part 2 in the link above:
void SetSplashImage(HWND hwndSplash, HBITMAP hbmpSplash)
{
// get the size of the bitmap
BITMAP bm;
GetObject(hbmpSplash, sizeof(bm), &bm);
SIZE sizeSplash = { bm.bmWidth, bm.bmHeight };
// get the primary monitor's info
POINT ptZero = { 0 };
HMONITOR hmonPrimary = MonitorFromPoint(ptZero, MONITOR_DEFAULTTOPRIMARY);
MONITORINFO monitorinfo = { 0 };
monitorinfo.cbSize = sizeof(monitorinfo);
GetMonitorInfo(hmonPrimary, &monitorinfo);
// center the splash screen in the middle of the primary work area
const RECT & rcWork = monitorinfo.rcWork;
POINT ptOrigin;
ptOrigin.x = 0;
ptOrigin.y = rcWork.top + (rcWork.bottom - rcWork.top - sizeSplash.cy) / 2;
// create a memory DC holding the splash bitmap
HDC hdcScreen = GetDC(NULL);
HDC hdcMem = CreateCompatibleDC(hdcScreen);
HBITMAP hbmpOld = (HBITMAP) SelectObject(hdcMem, hbmpSplash);
// use the source image's alpha channel for blending
BLENDFUNCTION blend = { 0 };
blend.BlendOp = AC_SRC_OVER;
blend.SourceConstantAlpha = 255;
blend.AlphaFormat = AC_SRC_ALPHA;
// paint the window (in the right location) with the alpha-blended bitmap
UpdateLayeredWindow(hwndSplash, hdcScreen, &ptOrigin, &sizeSplash,
hdcMem, &ptZero, RGB(0, 0, 0), &blend, ULW_ALPHA);
// delete temporary objects
SelectObject(hdcMem, hbmpOld);
DeleteDC(hdcMem);
ReleaseDC(NULL, hdcScreen);
}
Use the SetLayeredWindowAttributesarchive function, this allows you to set a mask color that will become transparent, thus allowing the background to show through.
You will also need to configure your window with the layered flag, e.g.:
SetWindowLong(hwnd, GWL_EXSTYLE, GetWindowLong(hwnd, GWL_EXSTYLE) | WS_EX_LAYERED);
After that it's fairly simple:
// Make red pixels transparent:
SetLayeredWindowAttributes(hwnd, RGB(255,0,0), 0, LWA_COLORKEY);
When your PNG contains semi-transparent pixels that you want to blend with the background, this becomes more complicated. You could try looking at the approach in this CodeProject article:
Cool, Semi-transparent and Shaped Dialogs with Standard Controls for Windows 2000 and Above