Click here to Skip to main content
15,867,568 members
Articles / Multimedia / GDI+
Article

Presenting EMFexplorer, a GDI+ experiment

Rate me:
Please Sign up or sign in to vote.
4.90/5 (28 votes)
29 Sep 20047 min read 128.5K   3.2K   49   21
High quality EMF rendering, using GDI+

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.

ClassDescription
CSCEMFImageActual EMF viewer that can load and save images
SCEMFDocSub-document class allowing the library to be document/view architecture independent
CSCEMFdcRendererActually renders images on DC
CSCEMFmetaDCRendererActually renders images into metafiles
CSCEMFgdiParserParses records and sends requests to the actual renderer
SCEMFRasterizerConvenience 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)
...
{// rasterizer must be detached from hDC before you reuse DC
    SCEMFRasterizer rasterizer;
    CSCEMFdcRenderer& rRenderer = rasterizer.SCGetRenderer(); // optional
    rRenderer.SCSetDrawingAttributes(m_DrawingAttributes); // optional

    // This is equivalent to an attachment
    rasterizer.SCBreakMetafile(hDC,  hEMF,  NULL,  (LPRECT)&rcPlay);
 
    // DC detachment point
}

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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
France France
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralMy vote of 5 Pin
tiger5491013-May-13 14:47
tiger5491013-May-13 14:47 
Questionhow to add new EMF records to an exist Windows EMF File? Pin
david_cloud10-Jan-13 21:50
david_cloud10-Jan-13 21:50 
GeneralMy vote of 5 Pin
Manoj Kumar Choubey18-Feb-12 0:10
professionalManoj Kumar Choubey18-Feb-12 0:10 
GeneralBuilder C++ Pin
jcurru22-Mar-10 2:32
jcurru22-Mar-10 2:32 
GeneralMemory Leak Pin
rohan.arora1415-Oct-09 22:01
rohan.arora1415-Oct-09 22:01 
QuestionLicense status... Pin
Member 96476420-Jan-09 3:19
Member 96476420-Jan-09 3:19 
GeneralAnother way to solve the problem with no coding works [modified] Pin
raoqu16-Dec-07 22:03
raoqu16-Dec-07 22:03 
GeneralMarvelous your my guru ^_^ Pin
achainard4-May-10 22:36
achainard4-May-10 22:36 
GeneralLicensure requirements Pin
Stephen Muccione11-Apr-07 17:19
Stephen Muccione11-Apr-07 17:19 
QuestionWhy not use the Metfile class? Pin
mahendren14-Jan-07 21:30
mahendren14-Jan-07 21:30 
AnswerRe: Why not use the Metfile class? Pin
Member 15069668-Jul-09 6:44
Member 15069668-Jul-09 6:44 
Questionalso for windows spool files? Pin
rob tillaart12-Dec-05 6:21
rob tillaart12-Dec-05 6:21 
AnswerRe: also for windows spool files? Pin
Smith Charles12-Dec-05 8:10
Smith Charles12-Dec-05 8:10 
Generalcompiler errors Pin
wei30-Jun-05 4:04
wei30-Jun-05 4:04 
GeneralEMRSMALLTEXTOUT Pin
User 592418-Feb-05 14:36
User 592418-Feb-05 14:36 
GeneralRe: EMRSMALLTEXTOUT Pin
Smith Charles10-Feb-05 10:19
Smith Charles10-Feb-05 10:19 
GeneralRefresh problem Pin
bianwenkui23-Jan-05 15:56
bianwenkui23-Jan-05 15:56 
GeneralRe: Refresh problem Pin
Smith Charles24-Jan-05 2:01
Smith Charles24-Jan-05 2:01 
GeneralBeautiful! Pin
Shog912-Oct-04 12:20
sitebuilderShog912-Oct-04 12:20 
GeneralRe: Beautiful! Pin
Smith Charles13-Oct-04 12:30
Smith Charles13-Oct-04 12:30 
GeneralSome Hints Pin
Smith Charles14-Oct-04 11:48
Smith Charles14-Oct-04 11:48 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.