GDI - 我能在DrawText中使用新的Windows 10 Segoe UI Emoji彩色字体吗?

12

我正在使用Embarcadero RAD Studio(10.2 Tokyo Starter)和Windows GDI创建一个C++项目来绘制文本,使用DrawText()函数。

我最近看到,Windows 10提供了一个新的"Segoe UI Emoji"字体,可能允许文本函数绘制彩色表情符号。我找到了几个使用Direct2D的示例,但没有纯GDI函数的示例。

我还尝试过像这样的简单代码:

HDC hDC = ::GetDC(Handle);

std::auto_ptr<TCanvas> pCanvas(new TCanvas());
pCanvas->Handle = hDC;

pCanvas->Brush->Color = clWhite;
pCanvas->Brush->Style = bsSolid;
pCanvas->FillRect(TRect(0, 0, ClientWidth, ClientHeight));

const std::wstring text = L"Test        ";

TRect textRect(10, 10, ClientWidth - 10, ClientHeight - 10);

hFont = ::CreateFont(-40,
                      0, 
                      0,
                      0,
                      FW_DONTCARE,
                      FALSE,
                      FALSE,
                      FALSE,
                      DEFAULT_CHARSET,
                      OUT_OUTLINE_PRECIS,
                      CLIP_DEFAULT_PRECIS,
                      CLEARTYPE_QUALITY,
                      VARIABLE_PITCH,
                      L"Segoe UI Emoji");

::SelectObject(hDC, hFont);

::DrawTextW(hDC,
            text.c_str(),
            text.length(),
            &textRect,
            DT_LEFT | DT_TOP | DT_SINGLELINE);

::DeleteObject(hFont);

输出结果的符号看起来不错,但它们是以黑白方式绘制的,没有颜色,正如您在下面的屏幕截图中看到的:

enter image description here

我找不到任何其他选项,可以使文本使用彩色符号而不是黑白符号进行绘制。是否有一种方法可以激活 GDI DrawText() 函数中的颜色支持?如果有,怎么做呢?或者只有 Direct2D 可以绘制带颜色的表情符号吗?

编辑于 2017 年 10 月 30 日

由于 GDI 无法完成任务(不幸的是,就像我想的那样),因此我在此发布了上述代码的 Direct2D 版本,这对我很有效。

const std::wstring text = L"Test        ";

HDC hDC = ::GetDC(Handle);

std::auto_ptr<TCanvas> pGDICanvas(new TCanvas());
pGDICanvas->Handle = hDC;

pGDICanvas->Brush->Color = clWhite;
pGDICanvas->Brush->Style = bsSolid;
pGDICanvas->FillRect(TRect(0, 0, ClientWidth, ClientHeight));

::D2D1_RECT_F textRect;
textRect.left   = 10;
textRect.top    = 10;
textRect.right  = ClientWidth  - 10;
textRect.bottom = ClientHeight - 10;

std::auto_ptr<TDirect2DCanvas> pCanvas(new TDirect2DCanvas(hDC, TRect(0, 0, ClientWidth, ClientHeight)));

// configure Direct2D font
pCanvas->Font->Size        = 40;
pCanvas->Font->Name        = L"Segoe UI Emoji";
pCanvas->Font->Orientation = 0;
pCanvas->Font->Pitch       = System::Uitypes::TFontPitch::fpVariable;
pCanvas->Font->Style       = TFontStyles();

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

if (!pFormat)
    return;

pCanvas->RenderTarget->SetTextAntialiasMode(D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE);

::D2D1_COLOR_F color;
color.r = 0.0f;
color.g = 0.0f;
color.b = 0.0f;
color.a = 1.0f;

::ID2D1SolidColorBrush* pBrush = NULL;

// create solid color brush, use pen color if rect is completely filled with outline
pCanvas->RenderTarget->CreateSolidColorBrush(color, &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_NO_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);

pCanvas->BeginDraw();

pCanvas->RenderTarget->DrawText(text.c_str(), text.length(), pFormat, textRect, pBrush,
            D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT);

pCanvas->EndDraw();

当然,这段代码只会在当前最新版本的Windows 10及以上版本上绘制彩色表情符号。在早期版本中,文本将像上面一样绘制(并且该代码可能无法编译)。

额外阅读


1
{btsdaf} - Adrian McCarthy
谢谢您的回答,不幸的是这正是我所想的。 - Jean-Milost Reymond
1
这并不排除您可以使用GDI手动完成的可能性。我想可能有一种方法可以将各个颜色图层实例化为不同的字体。然后,您需要设置文本颜色,为当前图层绘制文本,并重复每个颜色的步骤。 - Adrian McCarthy
再次感谢您的回答。事实上,我已经找到了一种解决GDI的方法,以获得我想要的效果。然而,我的代码很复杂,我有兴趣使用新的Windows 10功能,这似乎可以做到完全相同的事情,以避免重复发明轮子。不幸的是,似乎唯一实现这一点的方法是重写我的代码,改用Direct2D而不是GDI。 - Jean-Milost Reymond
很有趣的是,GDI不支持它,因为Unicode已经提供了一种选择加入表情符号渲染(而不是文本渲染)的方法;您可以使用U+FE0F *"选择器"。这样可以让您指定您确实想要表情符号。它也可以反过来使用,您可以使用U+FE0E选择器来指定文本渲染。你会认为GDI的人会看到"哦,他指定了U+FE0F,他一定非常喜欢表情符号。"*¯(°_o)/¯ - Ian Boyd
@Ian Boyd:Windows团队为了向后兼容性而保持GDI的工作。我认为他们已经很长时间没有添加新功能了。DirectWrite是目前支持花式排版的API。 - Adrian McCarthy
3个回答

10

GDI不支持彩色字体(即使您使用完整的Uniscribe路径),如果需要支持彩色字体,则必须使用Direct2D。简单的GDI API不支持彩色字体是有道理的,因为彩色字体需要使用OpenType标记,而DrawText/TextOut都没有提供那种级别的控制。尽管Uniscribe允许使用这些标记,但它并未扩展以支持彩色字体。


4
您可以使用DirectWrite绘制彩色表情符号到内存DC中的位图,然后使用BitBlt()复制到目标DC中。
基本上,您需要实现一个自定义的IDWriteTextRenderer类,并调用带有您的渲染器的IDWriteTextLayout::Draw(),然后复制结果。
在您的类中,您从IDWriteFactory检索IDWriteGdiInterop并调用IDWriteGdiInterop::CreateBitmapRenderTarget()以获取位图渲染目标;调用IDWriteFactory::CreateMonitorRenderingParams()以获取渲染参数,并调用IDWriteFactory::CreateTextFormat()以设置文本格式。
唯一重要的方法是DrawGlyphRun(),其中使用IDWriteFactory2::TranslateColorGlyphRun()获取IDWriteColorGlyphRunEnumerator,并对于每个颜色运行,调用IDWriteBitmapRenderTarget::DrawGlyphRun()来完成工作。
请记住,在窗口大小/位置更改时更新渲染目标/参数。
您可以参考此MSDN文档:
渲染到 GDI 表面 https://msdn.microsoft.com/en-us/library/windows/desktop/ff485856(v=vs.85).aspx

0

@SoronelHaetir在上面的回答中提到的,当使用静态窗口组件(通过WC_STATIC)时,用于工作的win32图形设备接口(GDI)不支持彩色字体。为了显示彩色表情符号和/或“更漂亮”的文本(即彩色文本等),您需要使用Direct2D API。

原帖作者(OP)@Jean-Milost Reymond提供的示例不能立即由读者编译和尝试。此外,它使用TCanvas类,在直接使用Win32 API时并不是必需的。

对于那些寻找可以立即复制、粘贴和编译的完整示例的人来说,下面是可以在Visual Studio中编译的代码:

#define WIN32_LEAN_AND_MEAN     // Exclude rarely-used stuff from Windows headers

// Windows Header Files
#include <windows.h>

// C RunTime Header Files
#include <stdlib.h>
#include <malloc.h>
#include <memory.h>
#include <tchar.h>
#include <wchar.h>
#include <math.h>

#include <d2d1.h>
#include <d2d1helper.h>
#include <dwrite.h>
#include <wincodec.h>
#include <string>
#include <cassert>

#pragma comment(lib, "d2d1.lib")
#pragma comment(lib, "Dwrite.lib")

HWND WindowHandle                       = nullptr;
IDWriteFactory * DWriteFactory          = nullptr;
ID2D1Factory * Direct2dFactory          = nullptr;
ID2D1HwndRenderTarget * RenderTarget    = nullptr;
ID2D1SolidColorBrush * TextBlackBrush   = nullptr;

const std::wstring DISPLAY_TEXT         = L"Test        ";

template<class Interface>
inline void SafeRelease (Interface ** ppInterfaceToRelease);
LRESULT CALLBACK WndProc (HWND hwnd,
                          UINT message,
                          WPARAM wParam,
                          LPARAM lParam);
HRESULT CreateDeviceIndependentResources ();
HRESULT InitInstance (HINSTANCE hInstance, int nCmdShow);
void DiscardDeviceResources ();
HRESULT OnRender ();
HRESULT CreateDeviceResources ();

template<class Interface>
inline void SafeRelease (Interface ** ppInterfaceToRelease)
{
    if (*ppInterfaceToRelease != NULL)
    {
        (*ppInterfaceToRelease)->Release ();

        (*ppInterfaceToRelease) = NULL;
    }
}

HRESULT OnRender ()
{
    HRESULT Result                  = S_OK;
    D2D1_SIZE_F RenderCanvasArea    = { 0 };
    IDWriteTextFormat * TextFormat  = nullptr;
    D2D1_RECT_F TextCanvasArea      = { 0 };

    Result = CreateDeviceResources ();

    if (SUCCEEDED (Result))
    {
        RenderTarget->BeginDraw ();

        RenderCanvasArea = RenderTarget->GetSize ();

        RenderTarget->Clear (D2D1::ColorF (D2D1::ColorF::White));

        if (SUCCEEDED (Result))
        {
            Result = DWriteFactory->CreateTextFormat (L"Segoe UI",
                                                      nullptr,
                                                      DWRITE_FONT_WEIGHT_REGULAR,
                                                      DWRITE_FONT_STYLE_NORMAL,
                                                      DWRITE_FONT_STRETCH_NORMAL,
                                                      25.0f,
                                                      L"en-us",
                                                      &TextFormat);

            TextFormat->SetTextAlignment (DWRITE_TEXT_ALIGNMENT_LEADING);
            TextFormat->SetParagraphAlignment (DWRITE_PARAGRAPH_ALIGNMENT_NEAR);
            TextFormat->SetReadingDirection (DWRITE_READING_DIRECTION_LEFT_TO_RIGHT);
            TextFormat->SetWordWrapping (DWRITE_WORD_WRAPPING_WRAP);

            if (SUCCEEDED (Result) &&
                TextFormat != nullptr)
            {
                TextCanvasArea = D2D1::RectF (0,
                                              0,
                                              RenderCanvasArea.width,
                                              RenderCanvasArea.height);

                RenderTarget->DrawTextW (DISPLAY_TEXT.c_str (),
                                         static_cast <UINT32> (DISPLAY_TEXT.size ()),
                                         TextFormat,
                                         TextCanvasArea,
                                         TextBlackBrush,
                                         D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT);
            }
        }

        Result = RenderTarget->EndDraw ();
    }

    if (Result == D2DERR_RECREATE_TARGET)
    {
        DiscardDeviceResources ();

        Result = S_OK;
    }

    return Result;
}

HRESULT CreateDeviceResources ()
{
    HRESULT Result  = S_OK;
    RECT rc         = { 0 };

    if (!RenderTarget)
    {
        GetClientRect (WindowHandle,
                       &rc);

        D2D1_SIZE_U size = D2D1::SizeU (rc.right - rc.left,
                                        rc.bottom - rc.top);

        // Create a Direct2D render target.
        Result = Direct2dFactory->CreateHwndRenderTarget (D2D1::RenderTargetProperties (),
                                                          D2D1::HwndRenderTargetProperties (WindowHandle, size),
                                                          &RenderTarget);

        if (SUCCEEDED (Result))
        {
            // Create a blue brush.
            Result = RenderTarget->CreateSolidColorBrush (D2D1::ColorF (D2D1::ColorF::Black),
                                                          &TextBlackBrush);
        }
    }

    return Result;
}

void DiscardDeviceResources ()
{
    SafeRelease (&RenderTarget);
    SafeRelease (&TextBlackBrush);
}

HRESULT InitInstance (HINSTANCE hInstance,
                      int nCmdShow)
{
    HRESULT Result = S_OK;

    // Create the window.
    WindowHandle = CreateWindow (L"D2DTextDemo",
                                 L"Direct2D Text Demo Application",
                                 WS_OVERLAPPEDWINDOW,
                                 CW_USEDEFAULT,
                                 CW_USEDEFAULT,
                                 600,
                                 200,
                                 nullptr,
                                 nullptr,
                                 hInstance,
                                 nullptr);

    if (WindowHandle == nullptr)
    {
        Result = E_POINTER;
    }
    else
    {
        ShowWindow (WindowHandle,
                    nCmdShow);
        UpdateWindow (WindowHandle);
    }

    return Result;
}

HRESULT CreateDeviceIndependentResources ()
{
    HRESULT Result = S_OK;

    Result = D2D1CreateFactory (D2D1_FACTORY_TYPE_SINGLE_THREADED,
                                &Direct2dFactory);

    if (SUCCEEDED (Result))
    {
        Result = DWriteCreateFactory (DWRITE_FACTORY_TYPE_SHARED,
                                      __uuidof (IDWriteFactory),
                                      reinterpret_cast <IUnknown **> (&DWriteFactory));
    }

    return Result;
}

LRESULT CALLBACK WndProc (HWND hwnd,
                          UINT message,
                          WPARAM wParam,
                          LPARAM lParam)
{
    LRESULT Result = 0;

    switch (message)
    {
        case WM_SIZE:
        {
            UINT width = LOWORD (lParam);
            UINT height = HIWORD (lParam);

            if (RenderTarget != nullptr)
            {
                // Note: This method can fail, but it's okay to ignore the
                // error here, because the error will be returned again
                // the next time EndDraw is called.
                RenderTarget->Resize (D2D1::SizeU (width,
                                                    height));
            }
        }
        break;

        case WM_DISPLAYCHANGE:
        {
            InvalidateRect (hwnd, nullptr, FALSE);
        }
        break;

        case WM_PAINT:
        {
            OnRender ();
            ValidateRect (hwnd,
                            nullptr);
        }
        break;

        case WM_DESTROY:
        {
            PostQuitMessage (0);
            Result = 1;
        }
        break;

        default:
        {
            Result = DefWindowProc (hwnd,
                                    message,
                                    wParam,
                                    lParam);
        }
        break;
    }

    return Result;
}

int APIENTRY wWinMain (_In_ HINSTANCE hInstance,
                       _In_opt_ HINSTANCE hPrevInstance,
                       _In_ LPWSTR lpCmdLine,
                       _In_ int nCmdShow)
{
    UNREFERENCED_PARAMETER (hInstance);
    UNREFERENCED_PARAMETER (hPrevInstance);
    UNREFERENCED_PARAMETER (lpCmdLine);
    UNREFERENCED_PARAMETER (nCmdShow);

    HRESULT ExitCode    = S_OK;
    MSG NextMessage     = { 0 };
    WNDCLASSEX wcex     = { 0 };
    ATOM WindowClassId  = 0;

    wcex.cbSize         = sizeof (WNDCLASSEX);
    wcex.style          = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc    = WndProc;
    wcex.cbClsExtra     = 0;
    wcex.cbWndExtra     = sizeof (LONG_PTR);
    wcex.hInstance      = hInstance;
    wcex.hbrBackground  = nullptr;
    wcex.lpszMenuName   = nullptr;
    wcex.hCursor        = LoadCursor (nullptr, IDI_APPLICATION);
    wcex.lpszClassName  = L"D2DTextDemo";

    if (SUCCEEDED (CoInitialize (nullptr)))
    {
        WindowClassId = RegisterClassEx (&wcex);

        if (WindowClassId == 0)
        {
            ExitCode = HRESULT_FROM_WIN32 (GetLastError ());
        }

        if (SUCCEEDED (ExitCode))
        {
            ExitCode = CreateDeviceIndependentResources ();
        }

        if (SUCCEEDED (ExitCode))
        {
            ExitCode = InitInstance (hInstance,
                                     nCmdShow);
        }

        if (SUCCEEDED (ExitCode))
        {
            while (GetMessage (&NextMessage,
                                nullptr,
                                0,
                                0))
            {
                TranslateMessage (&NextMessage);
                DispatchMessage (&NextMessage);
            }
        }

        CoUninitialize ();

        SafeRelease (&Direct2dFactory);
        SafeRelease (&DWriteFactory);
        SafeRelease (&RenderTarget);
    }

    return ExitCode;
}

(上面的示例没有完美的错误处理,因此如果读者在任何项目中使用以下代码,则重要的是审计此示例代码。)

在尝试在Visual Studio中编译之前,请确保您的项目已将“子系统”链接器选项设置为 Windows /SUBSYSTEM:WINDOWSWindows Subsystem Linker Option in Visual Studio

成功编译后,将出现以下应用程序窗口: The end result of compiling and executing the example code.

我在Windows 11上的Visual Studio 2022 Community Edition中成功测试了这个示例代码。

参考文献:


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接