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.
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.
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).
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 BitBlit
ing the image I got horrible pixelation, and BitBlit
ted 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.
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);