Please enable JavaScript to display the menu.
Drawing Text with System Font in UWP OpenGL ES app using Win2D

ysflight.com

[Download Sample Code]

 

2017/09/09 CMake 3.8 allows to describe .targets file as a library.  Modified accordingly.

 

I explained how you can use CMake to set up build of a UWP app using OpenGL ES 2.0 library in here.

In the next step, I tried to port my library for rendering system font into an off-screen bitmap, when I hit a wall.  If you search by keywords like "Universal Windows Platform render text", you most likely will be directed to a class called TextBlock.  I tried it, and got an error message:

The application called an interface that was marshalled for a different thread.

If you search for this error message, you can find that the error means that TextBlock must be used from the UI thread, not from the main thread.  You will find a solution suggesting to use RunAsync to use TextBlock in the UI thread.  I tried that.  But, the error message didn't go away.

I continued search for the similar situation.  If you start seeing questions but no answer on the sites like Stackoverflow.com, that's a bad sign.  There may be no solution.  What I learned was that TextBlock is a class that can only be used in the application that uses Xaml framework.

Xaml framework is for a typical smart-phone app in which you flip through the pages and do something.  It is good for limited classes of applications, but it is not for every application.  In fact, Visual Studio 2015 comes with two types of DirectX11 application templates, one without Xaml and one with Xaml.  The MSDN web site clearly states that "but using the XAML template may result in lower performance."  Therefore, Xaml is a performance penalty.  To me, the biggest problem of Xaml is I don't know what it is.  I can use an auto-generated Xaml file from a template, but I won't know what to do if Microsoft decides to change the specification of Xaml in the future.  I can learn new API functions, but I have absolutely no time to study a Windows-specific language.

Futher search implied that other people were suffering from the same problem, especially who are trying to build on a DirectX template without Xaml.  They do need a way of rendering text off-screen, but they were stuck with TextBlock class.  I found bunch of people asking this question, but didn't find an answer.  I was losing hope.  Just by luck, not so high in the search ranking, I found a library called Win2D.  Win2D is provided by Microsoft.  So, it is one of the 'official' libraries.  It was exactly a library that I was looking for.  With Win2D, it is very easy to render text off-screen.  Therefore, you can easily create a text message as a texture.  Most importantly, it is independent of Xaml.

Return of NuGet vs. CMake problem

Win2D does not come as a default library in the Visual Studio 2015 installation.  Win2D library comes as a NuGet package, just like OpenGL ES2 library.  Therefore, you need to download and set a dependency on your project.  If I do it within Visual Studio, it is easy to do so from the GUI, but in my case, it is useless unless I can do it from CMake.

I could do so for the OpenGL ES 2.0 library (ANGLE.WindowsStore).  I combined some CMake commands to find header path, library files, DLL files, and set dependencies on the targets.

However, Win2D library uses a .winmd file, which I have no idea what to do.  Only option I could do was directly modifying .vcxproj file after running CMake.  If I do so, I can simplify the CMakeLists.txt files.

What I knew was I needed to add a tag like:

<Import Project="(full-path file name for .targets file)" />

in the .vcxproj files of the projects that rely on the NuGet package.  .targets file is included in each NuGet package.  Just by adding this tag, msbuild will add include path, library files, and copies necessary DLLs to the AppX package.

By the way, I read through CMake source code, and I am pretty sure that current version of CMake never writes this <Import Project="..." /> tag for the user-specified .targets files.  I also confirmed that .targets files can be supported with very minor modifications of the CMake 3.6.2 source code.  I have submitted a feature request in CMake issue tracker.  I hope someone in the developing team agrees and adds a feature to support for .targets file.  But, making an ad-hoc modification may make future expansion difficult or impossible.  If that happens they need to cut some backward compatibility.  So, if CMake developers decide it is inappropriate, we can do nothing.

=> Then I ended up writing that part and contributed my code to CMake.

In CMake 3.8 and newer, you can describe .targets file in target_link_libraries just like a regular library.  It is much easier to deal with libraries that come in a NuGet package.

Rendering a Text String with Win2D + OpenGL ES 2.0

My goal here was to draw a text string in system font with OpenGL ES 2.0.  For that purpose, I need to render a text string on an off-screen bitmap, which then be converted to a texture.  If it is the fully-featured OpenGL, there is a convenient function called glDrawPixels, but OpenGL ES does not have such a convenient function.

By the way, I don't explain how to draw in OpenGL ES in here.  If you are interested, please look at the function called YsGLSLRenderRGBABitmap2D included in the ysgl library in the sample code.  In that function, all I am doing is register a new bitmap, draw a rectangle with GL_TRIANGLE_FAN (for OpenGL ES doesn't have GL_QUADS).

Off-screen bitmap is created in function std::vector SingleWindowAppView::RenderTextToBitmap(int &w,int &h,const char msg[]).  The bitmap is drawn on the window in Run() function.  In this code, I am making and deleting a new bitmap and a new texture every frame.  But for a practical program, you should cache the texture so that you don't have to re-create a bitmap and a texture every frame for better performance.

What you need to be careful when creating a off-screen bitmap with Win2D is the order of color components.  In Win2D, the color components of a pixel is BGRA.  OpenGL expects RGBA.  Also the first pixel in the array in Windows bitmap is at the top-left corner, while OpenGL bitmap at the bottom-left.  You need to flip the image upside down to get it upright on the window.

Another thing is you need to call cs->Flush(); or nothing will be drawn on the off-screen bitmap.  I wasted several hours to find this Flush() function.  I tried everything, but didn't get a picture.  I suspected I was drawing out of the bitmap.  I tried to give different coordinates, but no luck.  I suspected I was drawing a text as the same color as the background.  I changed the window background, bitmap background, and text colors, with no luck.  Actually, the call to clear the screen witha specific color seemed to be ignored.  Does Win2D require Xaml after all?  Cannot Win2D live with OpenGL ES?  Is my shader not work in OpenGL ES of UWP?  After trying all different things for several hours, all of a sudden, I thought about glFlush();.  Maybe Win2D has a function for flushing the drawing commands, and I was right.  I got a texture.

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <vector>

#include <agile.h>

#if !defined GL_GLEXT_PROTOTYPES
    #define GL_GLEXT_PROTOTYPES
#endif

#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
#define YS_GL_ES2
typedef double GLdouble;

#include <ysgl.h>

#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <EGL/eglplatform.h>
#include <angle_windowsstore.h>



using namespace Windows::ApplicationModel::Core;
using namespace Windows::ApplicationModel::Activation;
using namespace Windows::UI::Core;
using namespace Windows::UI::Input;
using namespace Windows::Foundation;
using namespace Windows::Foundation::Collections;
using namespace Windows::Graphics::Display;
using namespace Windows::System;

using namespace Platform;



ref class SingleWindowAppView sealed : public Windows::ApplicationModel::Core::IFrameworkView
{
public:
    typedef SingleWindowAppView THISCLASS;

private:  // Cannot have a public member variable?
    bool isWindowClosed,isWindowVisible;
    Platform::Agile<CoreWindow> hWnd;

    static THISCLASS ^CurrentView;

    EGLDisplay mEglDisplay;
    EGLContext mEglContext;
    EGLSurface mEglSurface;

    static std::vector <unsigned char> RenderTextToBitmap(int &w,int &h,const char msg[]);

public:
    static THISCLASS ^GetCurrentView(void);

    SingleWindowAppView();

    virtual void Initialize(Windows::ApplicationModel::Core::CoreApplicationView^ applicationView);
    virtual void SetWindow(Windows::UI::Core::CoreWindow^ window);
    virtual void Load(Platform::String^ entryPoint);
    virtual void Run();
    virtual void Uninitialize();

    virtual void OnActivated(CoreApplicationView^ applicationView, IActivatedEventArgs^ args);
    virtual void OnVisibilityChanged(CoreWindow^ sender, VisibilityChangedEventArgs^ args);
    virtual void OnWindowClosed(CoreWindow^ sender, CoreWindowEventArgs^ args);

private:
    void InitializeOpenGLES2(void);
    EGLBoolean InitializeEGLDisplayDefault(void);
    EGLBoolean InitializeEGLDisplay(int level);
    std::vector <EGLint> MakeDisplayAttribute(int level) const;
    void SetUpConfigAttrib(EGLint configAttrib[],int depthBit);
    void CleanUpOpenGLES2(void);
};

/* static */ SingleWindowAppView ^SingleWindowAppView::CurrentView;

/* static */ SingleWindowAppView ^SingleWindowAppView::GetCurrentView(void)
{
    return CurrentView;
}

/* static */ std::vector <unsigned char> SingleWindowAppView::RenderTextToBitmap(int &w,int &h,const char msg[])
{
    const int bmpWid=640,bmpHei=120;

    auto device=Microsoft::Graphics::Canvas::CanvasDevice::GetSharedDevice();
    auto bitmap=ref new Microsoft::Graphics::Canvas::CanvasRenderTarget(device,(float)bmpWid,(float)bmpHei,96);

    auto ds=bitmap->CreateDrawingSession();
    ds->Clear(Windows::UI::Colors::Green);

    auto txtFmt=ref new Microsoft::Graphics::Canvas::Text::CanvasTextFormat();
    txtFmt->FontSize=bitmap->ConvertPixelsToDips(48);

    std::vector <wchar_t> wMsg;
    for(int i=0; 0!=msg[i]; ++i)
    {
        wMsg.push_back(msg[i]);
    }
    wMsg.push_back(0);

    auto wStr=ref new Platform::String(wMsg.data());
    ds->DrawText(wStr,0,0,Windows::UI::Colors::Orange,txtFmt);

    ds->Flush(); // <- Don't forget this, or you get nothing.

    // Windows bitmap starts at top-left, while OpenGL bitmap at bottom-left.
    // Bitmap needs to be flipped.
    w=bmpWid;
    h=bmpHei;

    auto rawBitmap=bitmap->GetPixelBytes();
    std::vector <unsigned char> reallyRawBitmap;
    reallyRawBitmap.resize(w*h*4);
    {
        int nb=0,x=0,y=0;
        unsigned char bgra[4];
        for(auto a : rawBitmap)
        {
            bgra[nb++]=a;
            if(4==nb)
            {
                reallyRawBitmap[((h-y-1)*w+x)*4  ]=bgra[2];
                reallyRawBitmap[((h-y-1)*w+x)*4+1]=bgra[1];
                reallyRawBitmap[((h-y-1)*w+x)*4+2]=bgra[0];
                reallyRawBitmap[((h-y-1)*w+x)*4+3]=bgra[3];
                nb=0;
                ++x;
                if(w<=x)
                {
                    x=0;
                    ++y;
                }
            }
        }
    }

    return reallyRawBitmap;
}

SingleWindowAppView::SingleWindowAppView()
{
    isWindowClosed=false;
    isWindowVisible=false;

    mEglDisplay=EGL_NO_DISPLAY;
    mEglContext=EGL_NO_CONTEXT;
    mEglSurface=EGL_NO_SURFACE;

    CurrentView=this;
}

void SingleWindowAppView::Initialize(Windows::ApplicationModel::Core::CoreApplicationView^ applicationView)
{
    applicationView->Activated+=
        ref new TypedEventHandler<CoreApplicationView^, IActivatedEventArgs^>(this,&THISCLASS::OnActivated);
}
void SingleWindowAppView::SetWindow(Windows::UI::Core::CoreWindow^ window)
{
    hWnd=window;

    window->VisibilityChanged+=
        ref new TypedEventHandler<CoreWindow^,VisibilityChangedEventArgs^>(this,&THISCLASS::OnVisibilityChanged);
    window->Closed+=
        ref new TypedEventHandler<CoreWindow^,CoreWindowEventArgs^>(this,&THISCLASS::OnWindowClosed);

    InitializeOpenGLES2(); // Example initializes OpenGL ES context in here.
}
void SingleWindowAppView::Load(Platform::String^ entryPoint)
{
    YsGLSLCreateSharedRenderer();
}
void SingleWindowAppView::Run()
{
    while(!isWindowClosed)
    {
        if (isWindowVisible)
        {
            // This doesn't seem to wait for an event.  Like a PeekMessage
            CoreWindow::GetForCurrentThread()->Dispatcher->ProcessEvents(CoreProcessEventsOption::ProcessAllIfPresent);

            glClearColor(0,0,1,0);
            glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

            EGLint w=0,h=0;
            eglQuerySurface(mEglDisplay, mEglSurface, EGL_WIDTH, &w);
            eglQuerySurface(mEglDisplay, mEglSurface, EGL_HEIGHT, &h);
            if(0<w && 0<h)
            {
                glViewport(0,0,w,h);

                int bmpW,bmpH;
                auto bmp=RenderTextToBitmap(bmpW,bmpH,"Test Message");

                // In the BitmapRenderer, it just create and render a texture.
                // I don't explain OpenGL ES itself here.
                auto renderer=YsGLSLSharedBitmapRenderer();
                YsGLSLUseBitmapRenderer(renderer);
                YsGLSLRenderRGBABitmap2D(renderer,100,100,YSGLSL_HALIGN_LEFT,YSGLSL_VALIGN_TOP,bmpW,bmpH,bmp.data());
                YsGLSLEndUseBitmapRenderer(renderer);
            }

            if (eglSwapBuffers(mEglDisplay, mEglSurface) != GL_TRUE)
            {
                YsGLSLDeleteSharedRenderer();
                CleanUpOpenGLES2();
                InitializeOpenGLES2();
                YsGLSLCreateSharedRenderer();
            }
        }
        else
        {
            // This seems to wait until an event is thrown.
            CoreWindow::GetForCurrentThread()->Dispatcher->ProcessEvents(CoreProcessEventsOption::ProcessOneAndAllPending);
        }
    }

    CleanUpOpenGLES2(); // Example deletes OpenGL context here.
}
void SingleWindowAppView::Uninitialize()
{
}

void SingleWindowAppView::OnActivated(CoreApplicationView^ applicationView, IActivatedEventArgs^ args)
{
    CoreWindow::GetForCurrentThread()->Activate();
}
void SingleWindowAppView::OnVisibilityChanged(CoreWindow^ sender, VisibilityChangedEventArgs^ args)
{
    isWindowVisible=args->Visible;
}
void SingleWindowAppView::OnWindowClosed(CoreWindow^ sender, CoreWindowEventArgs^ args)
{
    isWindowClosed=true;
}

void SingleWindowAppView::InitializeOpenGLES2(void)
{
    // Cut & Pasted from OpenGL ES2 example.
    // Modified so that it selects the maximum depth bits.
    EGLint configAttributes[]=
    {
        EGL_RED_SIZE,8,
        EGL_GREEN_SIZE,8,
        EGL_BLUE_SIZE,8,
        EGL_ALPHA_SIZE,8,
        EGL_DEPTH_SIZE,24,
        EGL_STENCIL_SIZE,8,
        EGL_NONE
    };

    const EGLint contextAttributes[]=
    {
        EGL_CONTEXT_CLIENT_VERSION,2,
        EGL_NONE
    };

    const EGLint surfaceAttributes[]=
    {
        EGL_ANGLE_SURFACE_RENDER_TO_BACK_BUFFER,EGL_TRUE,
        EGL_NONE
    };

    EGLConfig config = NULL;

    // According to the example, the program should try to initialize OpenGL ES context with
    // EGL to D3D11 Feature Level 10_0+ first.  If it fails, try EGL to D3D11 Feature Level 9_3.
    // If it fails, try EGL to D3D11 Feature Level 11_0 on WARP.
    if(InitializeEGLDisplayDefault()!=EGL_TRUE &&
       InitializeEGLDisplay(0)!=EGL_TRUE &&
       InitializeEGLDisplay(1)!=EGL_TRUE &&
       InitializeEGLDisplay(2)!=EGL_TRUE)
    {
        throw Exception::CreateException(E_FAIL, L"Failed to initialize EGL");
    }

    // The sample code given with the OpenGL ES template uses 8-bit Z-buffer, 
    // which is pretty much useless.
    // Ideallly want to have 64-bit (or as precise as coordinates, in many cases 32-bit), 
    // but here get whatever available.
    EGLint numConfigs = 0;
    for(int depthBit=32; 8<=depthBit; depthBit-=8)
    {
        SetUpConfigAttrib(configAttributes,depthBit);
        if(eglChooseConfig(mEglDisplay,configAttributes,&config,1,&numConfigs)==EGL_TRUE && 0!=numConfigs)
        {
            printf("%d Depth Bits\n",depthBit);
            break;
        }
    }
    if(0==numConfigs)
    {
        throw Exception::CreateException(E_FAIL,L"Failed to choose first EGLConfig");
    }

    PropertySet^ surfaceCreationProperties = ref new PropertySet();
    surfaceCreationProperties->Insert(ref new String(EGLNativeWindowTypeProperty), hWnd.Get());

    mEglSurface=eglCreateWindowSurface(mEglDisplay,config,reinterpret_cast<IInspectable*>(surfaceCreationProperties),surfaceAttributes);
    if(mEglSurface==EGL_NO_SURFACE)
    {
        throw Exception::CreateException(E_FAIL,L"Failed to create EGL fullscreen surface");
    }

    mEglContext=eglCreateContext(mEglDisplay,config,EGL_NO_CONTEXT,contextAttributes);
    if(mEglContext==EGL_NO_CONTEXT)
    {
        throw Exception::CreateException(E_FAIL,L"Failed to create EGL context");
    }

    if(eglMakeCurrent(mEglDisplay,mEglSurface,mEglSurface,mEglContext)==EGL_FALSE)
    {
        throw Exception::CreateException(E_FAIL,L"Failed to make fullscreen EGLSurface current");
    }
}

EGLBoolean SingleWindowAppView::InitializeEGLDisplayDefault(void)
{
    // The sample code tells that eglGetPlatformDisplayEXT is use in place for eglGetDisplay to pass display attributes.
    // But, why not trying eglGetDisplay first?
    mEglDisplay=eglGetDisplay(EGL_DEFAULT_DISPLAY);
    if(EGL_NO_DISPLAY!=mEglDisplay)
    {
        auto b=eglInitialize(mEglDisplay,NULL,NULL);
        printf("Looks like the default eglGetDisplay is good enough.  Isn't it?");
        return b;
    }
    return EGL_FALSE;
}

EGLBoolean SingleWindowAppView::InitializeEGLDisplay(int level)
{
    PFNEGLGETPLATFORMDISPLAYEXTPROC eglGetPlatformDisplayEXT=
        reinterpret_cast<PFNEGLGETPLATFORMDISPLAYEXTPROC>(eglGetProcAddress("eglGetPlatformDisplayEXT"));
    if (!eglGetPlatformDisplayEXT)
    {
        throw Exception::CreateException(E_FAIL, L"Failed to get function eglGetPlatformDisplayEXT");
    }

    auto attrib=MakeDisplayAttribute(level);

    mEglDisplay=eglGetPlatformDisplayEXT(EGL_PLATFORM_ANGLE_ANGLE,EGL_DEFAULT_DISPLAY,attrib.data());
    if(mEglDisplay!=EGL_NO_DISPLAY)
    {
        return eglInitialize(mEglDisplay,NULL,NULL);
    }
    return EGL_FALSE;
}

std::vector <EGLint> SingleWindowAppView::MakeDisplayAttribute(int level) const
{
    const EGLint commonDisplayAttributePre[]=
    {
        EGL_PLATFORM_ANGLE_TYPE_ANGLE,EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE,
    };
    const EGLint commonDisplayAttributePost[]=
    {
        EGL_ANGLE_DISPLAY_ALLOW_RENDER_TO_BACK_BUFFER,EGL_TRUE,
        EGL_PLATFORM_ANGLE_ENABLE_AUTOMATIC_TRIM_ANGLE,EGL_TRUE,
        EGL_NONE,
    };

    std::vector <EGLint> attrib;
    for(auto v : commonDisplayAttributePre)
    {
        attrib.push_back(v);
    }
    switch(level)
    {
    case 0:
        break;
    case 1:  // FL9.  I don't know exactly what FL9 stands for, but as example says.
        attrib.push_back(EGL_PLATFORM_ANGLE_MAX_VERSION_MAJOR_ANGLE);
        attrib.push_back(9);
        attrib.push_back(EGL_PLATFORM_ANGLE_MAX_VERSION_MINOR_ANGLE);
        attrib.push_back(3);
        break;
    case 2:  // warp.  I don't know exactly what it means by "warp", but as example says.
        attrib.push_back(EGL_PLATFORM_ANGLE_DEVICE_TYPE_ANGLE);
        attrib.push_back(EGL_PLATFORM_ANGLE_DEVICE_TYPE_WARP_ANGLE);
        break;
    }
    for(auto v : commonDisplayAttributePost)
    {
        attrib.push_back(v);
    }
    return attrib;
}

void SingleWindowAppView::SetUpConfigAttrib(EGLint configAttrib[],int depthBit)
{
    for(int i=0; configAttrib[i]!=EGL_NONE; i+=2)
    {
        if(EGL_DEPTH_SIZE==configAttrib[i])
        {
            configAttrib[i+1]=depthBit;
        }
    }
}

void SingleWindowAppView::CleanUpOpenGLES2(void)
{
    // Cut & Pasted from OpenGL ES2 Example.
    if(mEglDisplay!=EGL_NO_DISPLAY && mEglSurface!=EGL_NO_SURFACE)
    {
        eglDestroySurface(mEglDisplay,mEglSurface);
        mEglSurface=EGL_NO_SURFACE;
    }
    if(mEglDisplay!=EGL_NO_DISPLAY && mEglContext!=EGL_NO_CONTEXT)
    {
        eglDestroyContext(mEglDisplay,mEglContext);
        mEglContext=EGL_NO_CONTEXT;
    }
    if(mEglDisplay!=EGL_NO_DISPLAY)
    {
        eglTerminate(mEglDisplay);
        mEglDisplay=EGL_NO_DISPLAY;
    }
}

ref class SingleWindowAppSource sealed : Windows::ApplicationModel::Core::IFrameworkViewSource
{
public:
    virtual Windows::ApplicationModel::Core::IFrameworkView^ CreateView()
    {
        return ref new SingleWindowAppView();
    }
};

[Platform::MTAThread]
int main(Platform::Array<Platform::String^>^)
{
    auto singleWindowAppSource = ref new SingleWindowAppSource();
    CoreApplication::Run(singleWindowAppSource);
    return 0;
}

 

Rendering a Text String with Win2D + OpenGL ES 2.0 (Fitted version)

The above code makes a big-enough off-screen bitmap and draws an image on it.  But, for practical use, you may want to have a bitmap that is just the right size for the rendered text.  I could find the solution quickly for this problem:

http://stackoverflow.com/questions/30696838/how-to-calculate-the-size-of-a-piece-of-text-in-win2d

This method works perfectly.  Only one thing is I wish I could get a required size before creating a bitmap, but CanvasTextLayont can only be created after creating a CanvasDrawingSession, which can only be created from a bitmap.  That' ok.  It is good for practical use.

All I need to modify is RenderTextBitmap function as follows.

/* static */ std::vector <unsigned char> SingleWindowAppView::RenderTextToBitmap(int &w,int &h,const char msg[])
{
    const int bmpWid=640,bmpHei=120;

    auto device=Microsoft::Graphics::Canvas::CanvasDevice::GetSharedDevice();
    auto bitmap=ref new Microsoft::Graphics::Canvas::CanvasRenderTarget(device,(float)bmpWid,(float)bmpHei,96);

    auto ds=bitmap->CreateDrawingSession();
    ds->Clear(Windows::UI::Colors::Green);

    auto txtFmt=ref new Microsoft::Graphics::Canvas::Text::CanvasTextFormat();
    txtFmt->FontSize=bitmap->ConvertPixelsToDips(48);

    std::vector <wchar_t> wMsg;
    for(int i=0; 0!=msg[i]; ++i)
    {
        wMsg.push_back(msg[i]);
    }
    wMsg.push_back(0);

    auto wStr=ref new Platform::String(wMsg.data());

    auto txtLayout=ref new Microsoft::Graphics::Canvas::Text::CanvasTextLayout(ds,wStr,txtFmt,(float)bmpWid,(float)bmpHei);
    auto rounding=Microsoft::Graphics::Canvas::CanvasDpiRounding::Floor;
    int renderX=(int)bitmap->ConvertDipsToPixels(txtLayout->LayoutBounds.X,rounding);
    int renderY=(int)bitmap->ConvertDipsToPixels(txtLayout->LayoutBounds.Y,rounding);
    int renderW=(int)bitmap->ConvertDipsToPixels(txtLayout->LayoutBounds.Width,rounding);
    int renderH=(int)bitmap->ConvertDipsToPixels(txtLayout->LayoutBounds.Height,rounding);

    ds->DrawTextLayout(txtLayout,0,0,Windows::UI::Colors::Orange);

    ds->Flush(); // <- Don't forget this, or you get nothing.

    // When you need the bitmap size of the text on the bitmap,
    // you can do it by CanvasTextLayout.
    // http://stackoverflow.com/questions/30696838/how-to-calculate-the-size-of-a-piece-of-text-in-win2d

    // Windows bitmap starts at top-left, while OpenGL bitmap at bottom-left.
    // Bitmap needs to be flipped.
    w=renderW;
    h=renderH;

    auto rawBitmap=bitmap->GetPixelBytes();
    std::vector <unsigned char> reallyRawBitmap;
    reallyRawBitmap.resize(renderW*renderH*4);
    {
        int x=0,y=0;
        for(int y=0; y<renderH; ++y)
        {
            for(int x=0; x<renderW; ++x)
            {
                if(x<bmpWid && y<bmpHei)
                {
                    // Windows -> BGRA   OpenGL -> RGBA  Need conversion.
                    reallyRawBitmap[((h-y-1)*w+x)*4  ]=rawBitmap[(y*bmpWid+x)*4+2];
                    reallyRawBitmap[((h-y-1)*w+x)*4+1]=rawBitmap[(y*bmpWid+x)*4+1];
                    reallyRawBitmap[((h-y-1)*w+x)*4+2]=rawBitmap[(y*bmpWid+x)*4];
                    reallyRawBitmap[((h-y-1)*w+x)*4+3]=rawBitmap[(y*bmpWid+x)*4+3];
                }
                else
                {
                    reallyRawBitmap[((h-y-1)*w+x)*4  ]=0;
                    reallyRawBitmap[((h-y-1)*w+x)*4+1]=0;
                    reallyRawBitmap[((h-y-1)*w+x)*4+2]=0;
                    reallyRawBitmap[((h-y-1)*w+x)*4+3]=0;
                }
            }
        }
    }

    return reallyRawBitmap;
}

 

Compiling the Sample Code

You need to have Visual Studio 2015 and CMake 3.8 or newer.  If you extract the archive directly under the user directory, for example, you need to type the following commands to open the solution file in Visual Studio 2015, and run from there.

C:\users\soji\samples\05_win2d_renderText>mkdir build

C:\users\soji\samples\05_win2d_renderText>cd build

C:\users\soji\samples\05_win2d_renderText\build>CMake .. -G "Visual Studio 14 2015" -DCMAKE_SYSTEM_NAME=WindowsStore -DCMAKE_SYSTEM_VERSION=10.0

C:\users\soji\samples\05_win2d_renderText\build>start Project.sln

 

Comments are welcome.  Send E-Mail to: 

Back to http://www.ysflight.com