You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Direct2D高效绘制带轮廓的文本?DrawGlyphRun怎么用?

高效渲染带轮廓效果的Direct2D文本方案

你说得对,DrawText确实在性能和效果灵活性上有局限——要实现高效的带轮廓文本渲染,DrawGlyphRun + FillGlyphRun是最优选择。这个方案不仅性能更优,还能完全控制文本的填充、轮廓细节,而且不需要依赖ATL,也不用指定字体文件路径,和你之前用CreateTextFormat的工作流程完全兼容。

核心思路

DrawText是封装好的高层API,内部会做很多额外的排版和渲染逻辑;而DrawGlyphRun是底层API,直接操作字形(Glyph)数据,能跳过冗余步骤提升性能。要实现轮廓效果,我们只需要:

  • 用IDWriteTextLayout把字符串转换成可直接渲染的字形序列(DWRITE_GLYPH_RUN)——这一步可以复用你已有的IDWriteTextFormat,不需要额外指定字体路径。
  • 先绘制文本的轮廓(用DrawGlyphRun并设置线宽),再绘制文本的填充(用FillGlyphRun),通过分层渲染实现轮廓效果。

具体实现步骤

1. 准备文本布局对象

首先用你已有的IDWriteTextFormat创建IDWriteTextLayout,它会帮我们完成文本的排版计算,生成对应的字形序列:

IDWriteTextLayout* textLayout = nullptr;
HRESULT hr = m_WriteFactory->CreateTextLayout(
    wtext,
    wcslen(wtext),
    d2d_text_format, // 复用你之前创建的文本格式
    800.0f, // 布局宽度,和你之前DrawText的Rect宽度一致
    600.0f, // 布局高度,和你之前DrawText的Rect高度一致
    &textLayout
);
if (FAILED(hr)) { /* 这里添加错误处理逻辑 */ }

2. 收集字形序列(GlyphRun)

我们需要从IDWriteTextLayout中提取所有字形信息,这里可以实现一个简单的IDWriteTextRenderer子类来收集数据:

class GlyphRunCollector : public IDWriteTextRenderer
{
public:
    std::vector<std::tuple<DWRITE_GLYPH_RUN, D2D1_POINT_2F>> glyphRuns;

    // IUnknown接口实现
    HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject) override
    {
        if (riid == __uuidof(IDWriteTextRenderer) || riid == __uuidof(IUnknown))
        {
            *ppvObject = this;
            AddRef();
            return S_OK;
        }
        *ppvObject = nullptr;
        return E_NOINTERFACE;
    }
    ULONG STDMETHODCALLTYPE AddRef() override { return InterlockedIncrement(&m_refCount); }
    ULONG STDMETHODCALLTYPE Release() override 
    { 
        ULONG count = InterlockedDecrement(&m_refCount); 
        if (count == 0) delete this; 
        return count; 
    }

    // 核心方法:收集GlyphRun和位置信息
    HRESULT STDMETHODCALLTYPE DrawGlyphRun(
        void* clientDrawingContext,
        FLOAT baselineOriginX,
        FLOAT baselineOriginY,
        DWRITE_MEASURING_MODE measuringMode,
        DWRITE_GLYPH_RUN const* glyphRun,
        DWRITE_GLYPH_RUN_DESCRIPTION const* glyphRunDescription,
        IUnknown* clientDrawingEffect
    ) override
    {
        glyphRuns.emplace_back(*glyphRun, D2D1::Point2F(baselineOriginX, baselineOriginY));
        return S_OK;
    }

    // 其他未使用的接口方法直接返回S_OK
    HRESULT STDMETHODCALLTYPE DrawUnderline(...) override { return S_OK; }
    HRESULT STDMETHODCALLTYPE DrawStrikethrough(...) override { return S_OK; }
    HRESULT STDMETHODCALLTYPE DrawInlineObject(...) override { return S_OK; }

    HRESULT STDMETHODCALLTYPE IsPixelSnappingDisabled(...) override { return S_OK; }
    HRESULT STDMETHODCALLTYPE GetCurrentTransform(...) override { return S_OK; }
    HRESULT STDMETHODCALLTYPE GetPixelsPerDip(...) override { return S_OK; }

private:
    LONG m_refCount = 0;
};

然后用这个收集器提取字形数据:

GlyphRunCollector collector;
textLayout->Draw(
    nullptr,
    &collector,
    x, // 文本起始X坐标,和你之前DrawText的x参数一致
    y  // 文本起始Y坐标,和你之前DrawText的y参数一致
);

3. 绘制带轮廓的文本

创建填充和轮廓画刷,然后先绘制轮廓、再绘制填充(保证填充覆盖轮廓内部,得到正确的视觉效果):

// 创建画刷
ID2D1SolidColorBrush* fillBrush = nullptr;
ID2D1SolidColorBrush* outlineBrush = nullptr;
m_RenderTarget->CreateSolidColorBrush(D2D1::ColorF(color.rBase(), color.gBase(), color.bBase(), color.aBase()), &fillBrush);
m_RenderTarget->CreateSolidColorBrush(D2D1::ColorF(0.0f, 0.0f, 0.0f), &outlineBrush); // 黑色轮廓,可自定义颜色

// 开始绘制
m_RenderTarget->BeginDraw();
m_RenderTarget->SetStrokeWidth(2.0f); // 设置轮廓线宽,单位是DIP,可调整

for (const auto& entry : collector.glyphRuns)
{
    const auto& glyphRun = std::get<0>(entry);
    const auto& origin = std::get<1>(entry);

    // 先绘制轮廓
    m_RenderTarget->DrawGlyphRun(
        origin,
        &glyphRun,
        outlineBrush,
        DWRITE_MEASURING_MODE_NATURAL
    );

    // 再绘制填充
    m_RenderTarget->FillGlyphRun(
        origin,
        &glyphRun,
        fillBrush,
        DWRITE_MEASURING_MODE_NATURAL
    );
}

HRESULT drawHr = m_RenderTarget->EndDraw();
if (FAILED(drawHr)) { /* 绘制错误处理 */ }

4. 资源清理

别忘了释放所有创建的COM对象,避免内存泄漏:

fillBrush->Release();
outlineBrush->Release();
textLayout->Release();
d2d_text_format->Release();
// 如果你之前创建的d2d_color不再使用,也需要释放

关键优势

  • 性能更高:直接操作字形数据,跳过DrawText的冗余封装,适合大量文本渲染场景。
  • 效果可控:可以自由调整轮廓线宽、颜色,甚至实现多层轮廓、渐变填充等高级效果。
  • 兼容原有流程:完全复用你之前的IDWriteTextFormat,不需要指定字体文件路径,只需要字体系列名称即可。

内容的提问来源于stack exchange,提问作者user14173351

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.08 23:58:12