Introduction
Suppose you have a database of "old" EMF/WMF documents. Wouldn't it be nice if you
could pass those documents to a GDI+ function and, magically, get better outputs?
This is legitimate, since there is a + in GDI+, and, indeed, this library is full of new
interesting features. Unfortunately, it doesn't work that way. I suspect GDI+ 1.0 to
render EMFs by just passing them to GDI, though you can change colors in a metafile by
supplying an appropriate color matrix. Anyway, I did not find a simple way to have
GDI+ 1.0 smooth lines or texts inside a metafile.
To get enhanced EMF output, I wrote a new rendering engine. It interprets the
122 types of EMF records, using GDI+ equivalent where possible, implementing what we can,
and calling GDI in last recourse. For example a
(Moveto
, Lineto
) pair is matched by
a Drawline
instruction. PolyPolygon
required a new algorithm
that calls GDI+'s DrawPolygon
. For blitting functions involving complex
ROP codes, we call GDI.
This is a good way to learn both GDI and GDI+. The purpose of this article is to
present you that work.
Licensing
Before you start using the free code, beware that EMFexplorer is released under EPL
(Equity Public License), an extension of GLPL (GNU Lesser Public License). According to
that license the library is free for free software, but commercial users should make a
donation the author. This applies to whatever part of the project you may choose to use
in your applications.
Project Modules
The EMFexplorer project contains six static libraries:
- SCEMFLib.lib contains the EMF renderer we are presenting in this article
- Many support libraries are reusable in other projects (I hope):
- SCErrorLib.lib is for error management (not too developed in this version)
- SCGenLib.lib contains generic support functions
- SCWinLib.lib contains Windows-wide support functions
- SCSharedLib.lib contains resources shared by the two demonstrators below
The project contains two demonstrators not shipped with this article, but available
on the project's web site:
- EMFexplorer.exe, a fully-fledged SDI application using the libraries
- SCEMFAx.ocx, a fully-fledged ActiveX component using the libraries
SCEMFLib library Organization
Here is a brief presentation of some classes used in the demonstration program
accompanying this article.
Class | Description |
CSCEMFImage | Actual EMF viewer that can load and save
images |
SCEMFDoc | Sub-document class allowing the library to
be document/view architecture independent |
CSCEMFdcRenderer | Actually renders images on DC |
CSCEMFmetaDCRenderer | Actually renders images into
metafiles |
CSCEMFgdiParser | Parses records and sends requests to
the actual renderer |
SCEMFRasterizer | Convenience class deriving from a
parser and holding a renderer |
Using SCEMFLib library
There two ways you can use this library:
- At high level (like a control having its own window)
- At low level (like a class drawing on part of a surface)
a) High-level use
Three steps are necessary to use the library without diving into its code:
- S1: The main app is responsible for GDI+ initialization and shutdown
- S2: The app creates one or more instances of
CSCEMFImage
, the
image viewer class
- S3: Once a viewer object is created, it can load, display, and save images;
it may even have its own contextual menu
The demo program (emfxdemo.exe) written for this article illustrates those three
steps. It is very straightforward. This is a dialog-based application doing
the followings:
- Load a document (EMF, bitmap, RTF, etc.) through a common dialog box
- Use one viewer object to display the first page of the loaded document
[S1] The class CSCGDIPlus
, declared in the file SCGDIPlus.h
of the SCWinLib static library, performs GDI+ initialization and shutdown. You may
choose to avoid this class, and use your own technique. But keep reading this section,
as it contains information shared with the next ones.
The CEmfxdemoApp.h is a classic AppWizard-generated file.
#include "SCGenInclude.h"
#include SC_INC_WINLIB(SCGDIPlus.h)
...
private:
CSCGDIPlus m_GDIPlus;
The uncommon thing here is the SC_INC_WINLIB
macro used to include
SCGDIPlus.h. It is defined in SCGenInclude.h (read this file for more information),
and is used to bypass compiler guesses about the location of the file. SCGenInclude.h
is a central point for includes management.
Note: because of such macros, before you compile the project, you must setup its
root directory (the one containing emfx_demo.dsw). If the dsw is in C:\ emfx_demo\Src,
add this directory to Visual C++'s global "Include directories".
CEmfxdemoApp.cpp is classic, except for two function calls.
In InitInstance()
we initialize GDI+ by calling:
if (!m_GDIPlus.SCInitInstance())
{
...
}
And in ExitInstance()
we shutdown GDI+ by calling:
m_GDIPlus.SCExitInstance();
[S2] Creating an image viewer
CEmfxdemoDlg.h is also a classic AppWizard-generated file. It includes SCEMFDoc.h
and SCEMFImage.h, the two header files you need to use the library. Concerning the
macro SC_INC_EMFLIB
, read the note in step S1.
#include "SCGenInclude.h"
#include SC_INC_EMFLIB(SCEMFImage.h)
#include SC_INC_EMFLIB(SCEMFDoc.h)
...
private:
SCEMFDoc m_EMFDoc;
CSCEMFImage m_CtlImage;
The viewer object m_CtlImage
needs a document to fetch pages from
(even if your application doesn't use Document/View architecture). That's why we have
an instance of SCEMFDoc
in CEmfxdemoDlg
.
In InitDialog()
, we connect the viewer to its document by calling:
m_CtlImage.SCSetEMFDoc(&m_EMFDoc);
The properties of the viewer object are either left with default values or changed
(hard-coded) in InitDialog()
. For example:
m_CtlImage.SCSetPaperColorStyle(SC_COLOR_RGBVALUE, RGB(...));
[S3] Operate an image viewer
Whenever the user attempts to load a new file, we check its type:
UINT uiFileType = m_EMFDoc.SCOpenDocument(lpszPathName);
If everything is OK, we display the first page of the new document:
CSCEMFdcRenderer::SCCleanFontCopies();
m_CtlImage.SCGotoFirstPage();
I talk about first page because the document object m_EMFDoc
will
split big RTF or TXT files into pages. But in this demo, only the first page is
displayed. Notice the call to the static method SCCleanFontCopies()
,
declared in SCEMFdcRenderer.h. This is to release resources cached for the previous
document, as the renderer may have created fonts
(see details in the 'Points of Interest' section). Also, an application must call
this function before termination, otherwise memory leaks might occur. That's why we
call it in CemfxdemoDlg::OnDestroy()
.
That's it. Doing something more sophisticated would be replicating the work already
done for EMFexplorer.exe.
VB developers (and others) may achieve the same result by using the ActiveX
component SCEMFAx.ocx.
b) Low-level use
Now suppose you want just to use the rendering engine located in the library.
That means your application already exists, and is able to display metafiles by
calling PlayEnhMetaFile()
. All you want is to replace those calls by
something else to get sharper outputs. In this case:
- The main app is still responsible for GDI+ initialization and shutdown
- Some function in your app is responsible for loading/creating EMFs
- Once an EMF handle is available, you can use the rendering engine as follows
#include "SCGenInclude.h"
#include SC_INC_EMFLIB(SCEMFRasterizer.h)
...
{
SCEMFRasterizer rasterizer;
CSCEMFdcRenderer& rRenderer = rasterizer.SCGetRenderer();
rRenderer.SCSetDrawingAttributes(m_DrawingAttributes);
rasterizer.SCBreakMetafile(hDC, hEMF, NULL, (LPRECT)&rcPlay);
}
Notice that the parameters to SCBreakMetafile()
are the same you
would pass to PlayEnhMetaFile()
.
The optional SCGetRenderer()
and
SCSetDrawingAttributes()
calls are shown here because it is likely
that you would like to control the output, otherwise you would
use PlayEnhMetaFile()
.
An example of attribute is the background color (see case a).
For more information, check the SCRasterizeEMF()
function
in SCEMFImage.cpp, as this article is just presenting the library,
not describing every class or function.
Points of Interest
Among other interesting subjects, the project addresses these problems:
- PolyPolygon: This instruction has no equivalent in GDI+ 1.0. So we had to implement
it. Check
T_SCDrawPolyPoly()
in SCDCRendPoly_i.cpp,
and SCConvertPolyPoly()
in SCGdiplusUtils.cpp. The algorithm is from
the xPDF project (Patrick Moreau, Martin P.J. Zinser, Glyph & Cog LLC).
- Accurate text positioning:
DrawString()
and AddString()
won't let you position characters as accurately and freely as ExtTextOut()
.
We have solved that problem. Check SCDrawText()
and SCAddTextInPath()
in SCDCRendTexts_i.cpp.
- Font substitution: GDI+ 1.0 is unable to recognize a temporary/private GDI TT
font installed by the application. It bogusly replies: 'NotTrueTypeFont!'
Check
SCOnChangeFont()
in SCEMFdcRenderer.cpp to see how we solved that.
- Font attribute simulation: We had also to simulate font attributes like 'underline'
and 'overstrike', as the text would simply disappear if we did nothing.
- Function map implementation: Enumerating metafile records sometimes requires
a big switch statement (in our case it would contain 122 cases). We use a function
mapping technique allowing us to avoid this and use normal C++ classes and subclasses
instead. Check the
SCBrkTarget
class (in SCGenLib),
and its descendents in SCEMFLib: SCBrkEMF
,
CSCEMFgdiParser
, CSCEMF2Text
.
- ROP2 simulation: check
SCBitmapFromHGLOBAL()
in SCGdiplusUtils.cpp.
- ROP3, ROP4 management: check
SCDrawImage()
,
SCDrawImageMsk()
in SCDCRendImages_i.cpp.
- Alpha Blend: GDI can do it; check the
SCAlphaBand
class in SCWinLib.
- Reference counting container object: check the class
SCRefdObjHolder
in SCGenLib.
- Downloading, decompressing, and displaying an EMF at once: check the
code of SCEMFAx.ocx.
Limitations
Windows 98SE users: you may attempt to use it; but,
as EMFexplorer.exe and SCEMFAx.ocx use Get/SetWorldTransform
,
they won't work; and if
you pass an EMF containing those transformations to SCEMFLib, it won't work either
(soft, gentle, EMFs and WMFs should work).
References
The full EMFexplorer source code is available for download at the following web site:
http://frazmitic.free.fr/emfexplorer/index.htm
This web site also offers a lot of EMF sets for download.
History
- Last EMFexplorer release: version 1.0.beta 2004-09-15.