65.9K
CodeProject 正在变化。 阅读更多。
Home

介绍 EMFexplorer,一个 GDI+ 实验

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.90/5 (26投票s)

2004年9月30日

7分钟阅读

viewsIcon

131850

downloadIcon

3205

使用 GDI+ 实现高质量的 EMF 渲染

引言

假设您有一个“旧”的 EMF/WMF 文档数据库。如果您能将这些文档传递给 GDI+ 函数并获得更好的输出,那不是很棒吗?这是合法的,因为 GDI+ 中有一个“+”,而且这个库确实充满了新的有趣功能。不幸的是,事情并非如此。我怀疑 GDI+ 1.0 只是通过将其传递给 GDI 来渲染 EMF,尽管您可以通过提供适当的颜色矩阵来更改元文件中的颜色。总之,我没有找到一种简单的方法让 GDI+ 1.0 在元文件中平滑线条或文本。

为了获得增强的 EMF 输出,我编写了一个新的渲染引擎。它解释了 122 种 EMF 记录类型,尽可能使用 GDI+ 等效项,实现我们能做的,并在最后求助于 GDI。例如,(Moveto, Lineto) 对与 Drawline 指令匹配。PolyPolygon 需要一个新算法,该算法调用 GDI+ 的 DrawPolygon。对于涉及复杂 ROP 代码的图像复制功能,我们调用 GDI。

这是学习 GDI 和 GDI+ 的好方法。本文的目的是向您介绍这项工作。

许可

在您开始使用免费代码之前,请注意 EMFexplorer 是在 EPL(Equity Public License)下发布的,这是 GLPL(GNU Lesser Public License)的扩展。根据该许可,该库对自由软件免费,但商业用户应向作者捐款。这适用于您可能选择在应用程序中使用的项目的任何部分。

项目模块

EMFexplorer 项目包含六个静态库

  • SCEMFLib.lib 包含我们在本文中介绍的 EMF 渲染器
  • 许多支持库可以在其他项目中重复使用(我希望)
    • SCErrorLib.lib 用于错误管理(在此版本中不那么发达)
    • SCGenLib.lib 包含通用支持函数
    • SCWinLib.lib 包含 Windows 范围的支持函数
    • SCSharedLib.lib 包含下面两个演示者共享的资源

该项目包含两个演示者,未随本文提供,但可在项目网站上找到

  • EMFexplorer.exe,一个使用这些库的完整 SDI 应用程序
  • SCEMFAx.ocx,一个使用这些库的完整 ActiveX 组件

SCEMFLib 库组织

以下是对本文随附演示程序中使用的一些类的简要介绍。

描述
CSCEMFImage实际的 EMF 查看器,可以加载和保存图像
SCEMFDoc子文档类,允许库独立于文档/视图架构
CSCEMFdcRenderer实际在 DC 上渲染图像
CSCEMFmetaDCRenderer实际将图像渲染到元文件中
CSCEMFgdiParser解析记录并将请求发送到实际渲染器
SCEMFRasterizer从解析器派生的方便类,并持有渲染器

使用 SCEMFLib 库

您可以通过两种方式使用此库

  • 高级(例如,具有自己窗口的控件)
  • 低级(例如,在表面的一部分上绘制的类)

a) 高级用法

无需深入研究其代码即可使用该库需要三个步骤

  • S1:主应用程序负责 GDI+ 的初始化和关闭
  • S2:应用程序创建一或多个 CSCEMFImage 实例,即图像查看器类
  • S3:创建查看器对象后,它可以加载、显示和保存图像;它甚至可以拥有自己的上下文菜单

本文编写的演示程序 (emfxdemo.exe) 说明了这三个步骤。它非常简单。这是一个基于对话框的应用程序,执行以下操作

  • 通过通用对话框加载文档(EMF、位图、RTF 等)
  • 使用一个查看器对象显示加载文档的第一页

[S1] SCWinLib 静态库的 SCGDIPlus.h 文件中声明的 CSCGDIPlus 类执行 GDI+ 的初始化和关闭。您可以选择避免使用此类,并使用自己的技术。但请继续阅读本节,因为它包含与后续章节共享的信息。

CEmfxdemoApp.h 是经典的 AppWizard 生成的文件。

#include "SCGenInclude.h"
#include SC_INC_WINLIB(SCGDIPlus.h)
...
private:
    CSCGDIPlus m_GDIPlus;

这里不常见的是用于包含 SCGDIPlus.h 的 SC_INC_WINLIB 宏。它定义在 SCGenInclude.h 中(有关更多信息,请阅读此文件),用于绕过编译器对文件位置的猜测。SCGenInclude.h 是包含管理的一个中心点。

注意:由于存在此类宏,在编译项目之前,您必须设置其根目录(包含 emfx_dsw 的目录)。如果 dsw 位于 C:\ emfx_demo\Src,请将此目录添加到 Visual C++ 的全局“包含目录”中。

CEmfxdemoApp.cpp 是经典的,除了两个函数调用。在 InitInstance() 中,我们通过调用来初始化 GDI+

    
    if (!m_GDIPlus.SCInitInstance())
    {
...
    }

ExitInstance() 中,我们通过调用来关闭 GDI+

    m_GDIPlus.SCExitInstance();

[S2] 创建图像查看器

CEmfxdemoDlg.h 也是经典的 AppWizard 生成的文件。它包含使用库所需的两个头文件 SCEMFDoc.h 和 SCEMFImage.h。关于 SC_INC_EMFLIB 宏,请阅读 S1 步骤中的注释。

#include "SCGenInclude.h"
#include SC_INC_EMFLIB(SCEMFImage.h)
#include SC_INC_EMFLIB(SCEMFDoc.h)
...
private:
    SCEMFDoc        m_EMFDoc;
    CSCEMFImage    m_CtlImage;

查看器对象 m_CtlImage 需要一个文档来获取页面(即使您的应用程序不使用文档/视图架构)。这就是为什么我们在 CEmfxdemoDlg 中有一个 SCEMFDoc 的实例。在 InitDialog() 中,我们通过调用将查看器连接到其文档

    m_CtlImage.SCSetEMFDoc(&m_EMFDoc);

查看器对象的属性要么保留默认值,要么在 InitDialog() 中更改(硬编码)。例如

    m_CtlImage.SCSetPaperColorStyle(SC_COLOR_RGBVALUE, RGB(...));

[S3] 操作图像查看器

每当用户尝试加载新文件时,我们都会检查其类型

    UINT uiFileType = m_EMFDoc.SCOpenDocument(lpszPathName);

如果一切正常,我们显示新文档的第一页

    CSCEMFdcRenderer::SCCleanFontCopies();
    m_CtlImage.SCGotoFirstPage();

我提到第一页是因为文档对象 m_EMFDoc 会将大的 RTF 或 TXT 文件分割成页面。但在演示中,只显示第一页。请注意对静态方法 SCCleanFontCopies() 的调用,它在 SCEMFdcRenderer.h 中声明。这是为了释放为前一个文档缓存的资源,因为渲染器可能创建了字体(有关详细信息,请参阅“关注点”部分)。此外,应用程序必须在终止前调用此函数,否则可能会发生内存泄漏。这就是为什么我们在 CemfxdemoDlg::OnDestroy() 中调用它。

就是这样。做更复杂的事情将是复制 EMFexplorer.exe 已经完成的工作。

VB 开发人员(以及其他开发者)可以通过使用 ActiveX 组件 SCEMFAx.ocx 来获得相同的结果。

b) 低级用法

现在假设您只想使用库中的渲染引擎。这意味着您的应用程序已经存在,并且能够通过调用 PlayEnhMetaFile() 来显示元文件。您所要做的就是用其他东西替换这些调用,以获得更清晰的输出。在这种情况下

  • 主应用程序仍然负责 GDI+ 的初始化和关闭
  • 您的应用程序中的某个函数负责加载/创建 EMF
  • 一旦 EMF 句柄可用,您就可以如下使用渲染引擎
#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
}

请注意,传递给 SCBreakMetafile() 的参数与传递给 PlayEnhMetaFile() 的参数相同。可选的 SCGetRenderer()SCSetDrawingAttributes() 调用在此处显示,因为您可能希望控制输出,否则您将使用 PlayEnhMetaFile()。属性的一个示例是背景颜色(请参阅 a 部分)。

有关更多信息,请查看 SCEMFImage.cpp 中的 SCRasterizeEMF() 函数,因为本文只是介绍库,而不是描述每个类或函数。

关注点

在其他有趣的主题中,该项目解决了以下问题

  • PolyPolygon:GDI+ 1.0 中没有等效的指令。因此,我们必须实现它。请查看 SCDCRendPoly_i.cpp 中的 T_SCDrawPolyPoly() 和 SCGdiplusUtils.cpp 中的 SCConvertPolyPoly()。该算法来自 xPDF 项目(Patrick Moreau, Martin P.J. Zinser, Glyph & Cog LLC)。
  • 精确的文本定位:DrawString()AddString() 不能像 ExtTextOut() 那样精确自由地定位字符。我们已经解决了这个问题。请查看 SCDCRendTexts_i.cpp 中的 SCDrawText()SCAddTextInPath()
  • 字体替换:GDI+ 1.0 无法识别应用程序安装的临时/私有 GDI TT 字体。它会错误地回复:“NotTrueTypeFont!”请查看 SCEMFdcRenderer.cpp 中的 SCOnChangeFont() 了解我们如何解决这个问题。
  • 字体属性模拟:我们还必须模拟“下划线”和“删除线”等字体属性,因为如果不做任何事情,文本将完全消失。
  • 函数映射实现:枚举元文件记录有时需要一个大的 switch 语句(在我们的例子中,它包含 122 个 case)。我们使用一种函数映射技术,使我们能够避免这种情况,而是使用普通的 C++ 类和子类。请查看 SCGenLib 中的 SCBrkTarget 类及其在 SCEMFLib 中的后代:SCBrkEMFCSCEMFgdiParserCSCEMF2Text
  • ROP2 模拟:请查看 SCGdiplusUtils.cpp 中的 SCBitmapFromHGLOBAL()
  • ROP3、ROP4 管理:请查看 SCDCRendImages_i.cpp 中的 SCDrawImage()SCDrawImageMsk()
  • Alpha 混合:GDI 可以做到;请查看 SCWinLib 中的 SCAlphaBand 类。
  • 引用计数容器对象:请查看 SCGenLib 中的 SCRefdObjHolder 类。
  • 一次性下载、解压缩和显示 EMF:请查看 SCEMFAx.ocx 的代码。

限制

Windows 98SE 用户:您可以尝试使用它;但是,由于 EMFexplorer.exe 和 SCEMFAx.ocx 使用 Get/SetWorldTransform,它们将无法正常工作;如果您将包含这些转换的 EMF 传递给 SCEMFLib,它也将无法正常工作(温和、平缓的 EMF 和 WMF 应该可以正常工作)。

参考文献

完整的 EMFexplorer 源代码可在以下网站下载

http://frazmitic.free.fr/emfexplorer/index.htm

该网站还提供大量 EMF 套件供下载。

历史

  • 最后发布的 EMFexplorer:版本 1.0.beta 2004-09-15。
© . All rights reserved.