Direct2D - 如何绘制尽可能接近 GDI 渲染的文本

Posted

技术标签:

【中文标题】Direct2D - 如何绘制尽可能接近 GDI 渲染的文本【英文标题】:Direct2D - How to draw a text as close as possible to GDI rendering 【发布时间】:2019-04-22 06:31:29 【问题描述】:

我正在使用 Embarcadero RAD Studio XE7 编译器(C++ Builder),我正在尝试提供的 Direct2D API。我的目的是创建提供与 GDI 完全相同的渲染的应用程序,但也可能使 Direct2D 提供的新功能受益,例如 Window 10 上的彩色表情符号。

在这种情况下,我写了一个小文本绘制函数,它使用 Direct2D 来完成这项工作。我还搜索了如何配置 Direct2D/DirectWrite 以绘制尽可能接近 GDI 的文本。

这导致了以下函数:

void DrawText_Direct2D(const std::wstring& text, const TRect& rect, TColor bgColor,
        TFont* pFont, TCanvas* pCanvas)

    if (!pFont || !pCanvas)
        return;

    // fill destination canvas background with provided color
    WGDIHelper::Fill(pCanvas, rect, bgColor);

    ::D2D1_RECT_F drawRect;
    drawRect.left   = rect.Left;
    drawRect.top    = rect.Top;
    drawRect.right  = rect.Right;
    drawRect.bottom = rect.Bottom;

    // get Direct2D destination canvas
    std::unique_ptr<TDirect2DCanvas> pD2DCanvas(new TDirect2DCanvas(pCanvas->Handle, rect));

    // configure Direct2D font
    pD2DCanvas->Font->Height      = pFont->Height;
    pD2DCanvas->Font->Name        = pFont->Name;
    pD2DCanvas->Font->Orientation = pFont->Orientation;
    pD2DCanvas->Font->Pitch       = pFont->Pitch;
    pD2DCanvas->Font->Style       = pFont->Style;

    // get DirectWrite text format object
    _di_IDWriteTextFormat pFormat = pD2DCanvas->Font->Handle;

    // found it?
    if (!pFormat)
        return;

    // get (or create) the DirectWrite factory
    _di_IDWriteFactory pDirectWrite = ::DWriteFactory(DWRITE_FACTORY_TYPE_SHARED);

    if (!pDirectWrite)
        return;

    // configure and apply new rendering parameters for DirectWrite
    _di_IDWriteRenderingParams renderParams;
    pDirectWrite->CreateCustomRenderingParams(1.0f, 0.0f, 0.0f, DWRITE_PIXEL_GEOMETRY_RGB,
            DWRITE_RENDERING_MODE_CLEARTYPE_GDI_NATURAL, &renderParams);
    pD2DCanvas->RenderTarget->SetTextRenderingParams(renderParams);

    // set antialiasing mode
    pD2DCanvas->RenderTarget->SetTextAntialiasMode(D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE);

    ::ID2D1SolidColorBrush* pBrush = NULL;

    WColor color(pFont->Color);

    // create solid color brush
    pD2DCanvas->RenderTarget->CreateSolidColorBrush(color.GetD2DColor(), &pBrush);

    if (!pBrush)
        return;

    // set horiz alignment
    pFormat->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_LEADING);

    // set vert alignment
    pFormat->SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_NEAR);

    // set reading direction
    pFormat->SetReadingDirection(DWRITE_READING_DIRECTION_LEFT_TO_RIGHT);

    // set word wrapping mode
    pFormat->SetWordWrapping(DWRITE_WORD_WRAPPING_WRAP);

    IDWriteInlineObject* pInlineObject = NULL;

    ::DWRITE_TRIMMING trimming;
    trimming.delimiter      = 0;
    trimming.delimiterCount = 0;
    trimming.granularity    = DWRITE_TRIMMING_GRANULARITY_NONE;

    // set text trimming
    pFormat->SetTrimming(&trimming, pInlineObject);

    try
    
        pD2DCanvas->BeginDraw();

        // draw the text
        pD2DCanvas->RenderTarget->DrawText(text.c_str(), text.length(), pFormat, drawRect, pBrush,
                D2D1_DRAW_TEXT_OPTIONS_NONE);
    
    __finally
    
        pD2DCanvas->EndDraw();
    

上面的代码是我可以做的更接近 GDI 文本绘图。但是,有一些我无法纠正的小差异。这是使用 GDI 绘制的文本的屏幕截图,并使用上述功能(注意下面的图像是拉伸的,应该以他的真实尺寸打开):

如您所见,D2D 文本比 GDI 文本更长,并且两个版本之间的几个单词,如“humanum”,明显不同。此外,几个词的抗锯齿有些不同,比如第一个“文本”词,包括后面的逗号,这会导致这些词再次显得有些模糊。

但是,绘制相似但可见差异很小的绘图对我来说不是一个可接受的选择。我需要一张没有差异的图纸,或者至少在几乎看不到差异的地方(即没有彻底比较)。

AFAIK 使用相同的字体,并将正确的 cleartype 模式应用于 DirectWrite。那么如何改进我的 Direct2D 渲染,使其看起来更接近旧的 GDI 呢?有没有办法做到这一点?

注意我已经找到并阅读了几个关于 Direct2D 和 GDI 之间文本质量差异的主题,例如

Why can't DirectX/DirectWrite/Direct2D text rendering be as sharp as GDI?

Direct2D interface and blurry text issue

不幸的是,就我而言,我已经启用了建议的选项以允许 Direct2D 像 GDI 一样绘制文本(至少我认为我这样做是正确的),但结果仍然不足以让我使用它在我的项目中。

【问题讨论】:

【参考方案1】:

我建议改用CreateGdiCompatibleTextLayout,因为Direct2D 中没有DrawTextInGdiCompatibleWay 函数变体。它不一定完全匹配 gdi 输出,但希望会更接近。

【讨论】:

是的,你所说的笨拙,CreateGdiCompatibleTextLayout 后跟 D2D DrawTextLayout 而不是 DrawText。 GDI 和 DW 之间差异的最大原因实际上是字体回退。除此之外,我们试图紧密匹配 TrueType 光栅化器指标。 感谢您的确认。为了进一步澄清,directwrite 是否会对自然和 gdi 兼容的布局使用相同的后备配置? 是的,因为 IDWriteTextLayout 在此处仅匹配 GDI 布局字形 metrics,并且无论 DWRITE_MEASURING_MODE 为何,都使用相同的后备列表。 GDI 的字体选择是 ? 层的复杂堆积,其中包含许多遗留概念,例如代码页(请参阅 FontSubstitutes 注册表项)、脚本 id(Uniscribe 脚本项)和基于基本字体的字体链接(FontLink\SystemLink 项) ,但它们之间的字体选择最大的不兼容是粗细+宽度+斜率(WPF/DWrite)与粗体/斜体(GDI/GDI+)的不同字体模型。

以上是关于Direct2D - 如何绘制尽可能接近 GDI 渲染的文本的主要内容,如果未能解决你的问题,请参考以下文章

使用 Direct2D 在非客户区绘图

在 WinForm 中使用 Direct2D

C# 使用 Direct2D 实现斜角效果

Direct2D教程Direct2D已经来了,谁是GDI的终结者?

如何使用准确的 GDI 字体大小?

GDI InvertRect 的 Direct2D 等效项