I created a D3D11 device and can perform operations such as rendering pictures smoothly, but in order to also support GDI, I tried several methods:
Through swapchain -> GetBuffer(ID3D11Texture2D) -> CreateDxgiSurfaceRenderTarget -> ID2D1GdiInteropRenderTarget -> GetDC, finally get the DC. It runs normally on my Win10, but an exception report when running GetDC on Win7: _com_error.
Via swapchain -> GetBuffer(IDXGISurface1) -> GetDC, same as 1.
I suspect that the ID3D11Texture2D/IDXGISurface1 obtained by GetBuffer on Win7 will have some restrictions on the use of GDI, so I changed to dynamically create a new ID3D11Texture2D by myself, and now use DC alone/D3D11 drawing interface alone It works fine, but if I interoperate, I will find that gdi opertaion is drawn on the custom-created ID3D11Texture2D instead of the back_buffer of swapchain:
_d3d->Clear();
_d3d->DrawImage();
HDC hdc = _d3d->GetDC();
DrawRectangleByGDI(hdc);
_d3d->ReleaseDC();
_d3d->Present();
So how to do it: Whether the D3D or DC methods is drawn, they are all on the same ID3D11Texture2D? This way, it is also convenient for my CopyResource.
HRESULT CGraphRender::Resize(const UINT32& width, const UINT32& height)
{
_back_texture2d = nullptr;
_back_rendertarget_view = nullptr;
_dc_texture2d = nullptr;
_dc_render_target = nullptr;
float dpi = GetDpiFromD2DFactory(_d2d_factory);
//Backbuffer
HRESULT hr = _swap_chain->ResizeBuffers(2, width, height, DXGI_FORMAT_B8G8R8A8_UNORM, _is_gdi_compatible ? DXGI_SWAP_CHAIN_FLAG_GDI_COMPATIBLE : 0);
RETURN_ON_FAIL(hr);
hr = _swap_chain->GetBuffer(0, __uuidof(ID3D11Texture2D), (void**)&_back_texture2d);
RETURN_ON_FAIL(hr);
hr = CreateD3D11Texture2D(_d3d_device, width, height, &_dc_texture2d);
RETURN_ON_FAIL(hr);
D3D11_RENDER_TARGET_VIEW_DESC rtv;
rtv.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
rtv.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D;
rtv.Texture2D.MipSlice = 0;
hr = _d3d_device->CreateRenderTargetView(_back_texture2d, &rtv, &_back_rendertarget_view);
RETURN_ON_FAIL(hr);
...
}
HRESULT CGraphRender::Clear(float color[])
{
CComPtr<ID3D11DeviceContext> immediate_context;
_d3d_device->GetImmediateContext(&immediate_context);
if (!immediate_context)
{
return E_UNEXPECTED;
}
ID3D11RenderTargetView* ref_renderTargetView = _back_rendertarget_view;
immediate_context->OMSetRenderTargets(1, &ref_renderTargetView, nullptr);
immediate_context->ClearRenderTargetView(_back_rendertarget_view, color);
return S_OK;
}
HDC CGraphRender::GetDC()
{
if (_is_gdi_compatible)
{
CComPtr<IDXGISurface1> gdi_surface;
HRESULT hr = _dc_texture2d->QueryInterface(__uuidof(IDXGISurface1), (void**)&gdi_surface);
if (SUCCEEDED(hr))
{
HDC hdc = nullptr;
hr = gdi_surface->GetDC(TRUE, &hdc);
if (SUCCEEDED(hr))
{
return hdc;
}
}
}
return nullptr;
}
HRESULT CGraphRender::CopyTexture(ID3D11Texture2D* dst_texture, ID3D11Texture2D* src_texture, POINT* dst_topleft/* = nullptr*/, POINT* src_topleft/* = nullptr*/)
{
if (!dst_texture && !src_texture)
{
return E_INVALIDARG;
}
CComPtr<ID3D11DeviceContext> immediate_context;
_d3d_device->GetImmediateContext(&immediate_context);
if (!immediate_context)
{
return E_UNEXPECTED;
}
ID3D11Texture2D* dst_texture_real = dst_texture ? dst_texture : _dc_texture2d;
POINT dst_topleft_real = dst_topleft ? (*dst_topleft) : POINT{ 0, 0 };
ID3D11Texture2D* src_texture_real = src_texture ? src_texture : _dc_texture2d;
POINT src_topleft_real = src_topleft ? (*src_topleft) : POINT{ 0, 0 };
D3D11_TEXTURE2D_DESC src_desc = { 0 };
src_texture_real->GetDesc(&src_desc);
D3D11_TEXTURE2D_DESC dst_desc = { 0 };
dst_texture_real->GetDesc(&dst_desc);
if (!dst_topleft_real.x && !src_topleft_real.x && !dst_topleft_real.y && !src_topleft_real.y && dst_desc.Width == src_desc.Width && dst_desc.Height == src_desc.Height)
{
immediate_context->CopyResource(dst_texture_real, src_texture_real);
}
else
{
D3D11_BOX src_box;
src_box.left = min((UINT)src_topleft_real.x, (UINT)dst_topleft_real.x + dst_desc.Width);
src_box.top = min((UINT)src_topleft_real.y, (UINT)dst_topleft_real.y + dst_desc.Height);
src_box.right = min((UINT)src_box.left + src_desc.Width, (UINT)dst_topleft_real.x + dst_desc.Width);
src_box.bottom = min((UINT)src_box.top + src_desc.Height, (UINT)dst_topleft_real.y + dst_desc.Height);
src_box.front = 0;
src_box.back = 1;
ATLASSERT(src_box.left < src_box.right);
ATLASSERT(src_box.top < src_box.bottom);
immediate_context->CopySubresourceRegion(dst_texture_real, 0, dst_topleft_real.x, dst_topleft_real.y, 0, src_texture_real, 0, &src_box);
}
return S_OK;
}
I donβt think Windows 7 supports what youβre trying to do. Hereβs some alternatives.
Switch from GDI to something else that can render 2D graphics with D3D11. Direct2D is the most straightforward choice here. And DirectWrite if you want text in addition to rectangles.
If your 2D content is static or only changes rarely, you can use GDI+ to render into in-memory RGBA device context, create Direct3D11 texture with that data, and render a full-screen triangle with that texture.
You can overlay another Win32 window on top of your Direct3D 11 rendering one, and use GDI to render into that one. The GDI window on top must have WS_EX_LAYERED expended style, and you must update it with UpdateLayeredWindow API. This method is the most complicated and least reliable, though.
Related
I used DrawIconEx (GDI/D3D11 interoperability and CopyResource) to generate an ID3D11Texture2D which has many pixels with an alpha channel value of 0. this texture has been verified by D3D11_USAGE_STAGING/Map to view the pixel value and ScreenGrab save png (relevant code needs to be modified: DXGI_FORMAT_B8G8R8A8_UNORM->Use GUID_WICPixelFormat32bppBGRA instead of GUID_WICPixelFormat24bppBGR).
When I use the rendering texture method of Tutorial 5: Texturing, the alpha value of 0 pixels will be rendered as black, which is not what i want, I hope these pixels render to be transparent. What will be done to achieve the goal? Here is my relevant code:
HRESULT CGraphRender::Init()
{
...
// Create an alpha enabled blend state description.
_blend_state = nullptr;
D3D11_BLEND_DESC blendDesc;
ZeroMemory(&blendDesc, sizeof(D3D11_BLEND_DESC));
blendDesc.RenderTarget[0].BlendEnable = TRUE;
blendDesc.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC_ALPHA;
blendDesc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA;
blendDesc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD;
blendDesc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE;
blendDesc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ZERO;
blendDesc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD;
blendDesc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL;
hr = _d3d_device->CreateBlendState(&blendDesc, &_blend_state);
RETURN_ON_FAIL(hr);
....
}
HRESULT CGraphRender::Clear_3D(float color[])
{
ID3D11RenderTargetView* rtv[] = { _back_rendertarget_view };
_immediate_context->OMSetRenderTargets(_countof(rtv), rtv, nullptr);
_immediate_context->ClearRenderTargetView(_back_rendertarget_view, color);
float blendFactor[4] = { 1.f, 1.f, 1.f, 1.f };
_immediate_context->OMSetBlendState(_blend_state, blendFactor, 0xffffffff);
return S_OK;
}
The problem has been solved: Perform the OMGetBlendState(_blend_state... setting before rendering the "alpha" texture, and restore the default blendstate after rendered
HRESULT CGraphRender::DrawTexture(const std::shared_ptr<CDrawTextureShader>& texture, const RECT& dst_rect, const BOOL& is_blend_alpha)
{
CComPtr<ID3D11DeviceContext> immediate_context;
_d3d_device->GetImmediateContext(&immediate_context);
if (!immediate_context)
{
return E_UNEXPECTED;
}
if (is_blend_alpha)
{
CComPtr<ID3D11BlendState> old_blend_state;
FLOAT old_blend_factor[4] = { 0.f };
UINT old_sample_mask = 0;
immediate_context->OMGetBlendState(&old_blend_state, old_blend_factor, &old_sample_mask);
float blend_factor[4] = { 1.f, 1.f, 1.f, 1.f };
immediate_context->OMSetBlendState(_blend_state, blend_factor, 0xffffffff);
HRESULT hr = texture->Render(immediate_context, dst_rect);
immediate_context->OMSetBlendState(old_blend_state, old_blend_factor, old_sample_mask);
return hr;
}
else
{
return texture->Render(immediate_context, dst_rect);
}
}
I 'm trying to use Direct2D on a render target created by Direct3D according to this MSDN document. The idea not to use a HWND render buffer is for the application to be able to switch targets easily, so I can e.g. render to a bitmap the static painting and use direct commands for any runtime-dependent painting. The code to create the interfaces is the following:
bool CreateD2DC(HWND hh)
{
D2D& d = *this;
D3D_FEATURE_LEVEL featureLevels[] =
{
D3D_FEATURE_LEVEL_11_1,
D3D_FEATURE_LEVEL_11_0,
D3D_FEATURE_LEVEL_10_1,
D3D_FEATURE_LEVEL_10_0,
D3D_FEATURE_LEVEL_9_3,
D3D_FEATURE_LEVEL_9_2,
D3D_FEATURE_LEVEL_9_1
};
auto de = D3D_DRIVER_TYPE_HARDWARE;
if (IsCurrentSessionRemoteable())
de = D3D_DRIVER_TYPE_SOFTWARE;
D3D_FEATURE_LEVEL m_featureLevel;
D3D11CreateDevice(
nullptr, // specify null to use the default adapter
de,
0,
D3D11_CREATE_DEVICE_BGRA_SUPPORT, // optionally set debug and Direct2D compatibility flags
featureLevels, // list of feature levels this app can support
ARRAYSIZE(featureLevels), // number of possible feature levels
D3D11_SDK_VERSION,
&d.device, // returns the Direct3D device created
&m_featureLevel, // returns feature level of device created
&d.context // returns the device immediate context
);
if (!d.device)
return 0;
d.dxgiDevice = d.device;
if (!d.dxgiDevice)
return 0;
D2D1_CREATION_PROPERTIES dp;
dp.threadingMode = D2D1_THREADING_MODE::D2D1_THREADING_MODE_SINGLE_THREADED;
dp.debugLevel = D2D1_DEBUG_LEVEL::D2D1_DEBUG_LEVEL_NONE;
dp.options = D2D1_DEVICE_CONTEXT_OPTIONS::D2D1_DEVICE_CONTEXT_OPTIONS_NONE;
D2D1CreateDevice(d.dxgiDevice, dp, &d.m_d2dDevice);
if (!d.m_d2dDevice)
return 0;
d.m_d2dDevice->CreateDeviceContext(
D2D1_DEVICE_CONTEXT_OPTIONS_NONE,
&d.m_d2dContext);
if (!d.m_d2dContext)
return 0;
// Allocate a descriptor.
DXGI_SWAP_CHAIN_DESC1 swapChainDesc = { 0 };
swapChainDesc.Width = 0; // use automatic sizing
swapChainDesc.Height = 0;
swapChainDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; // this is the most common swapchain format
swapChainDesc.Stereo = false;
swapChainDesc.SampleDesc.Count = 1; // don't use multi-sampling
swapChainDesc.SampleDesc.Quality = 0;
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapChainDesc.BufferCount = 2; // use double buffering to enable flip
swapChainDesc.Scaling = DXGI_SCALING_NONE;
swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; // all apps must use this SwapEffect
swapChainDesc.Flags = 0;
d.dxgiDevice->GetAdapter(&d.dxgiAdapter);
if (!d.dxgiAdapter)
return 0;
// Get the factory object that created the DXGI device.
d.dxgiAdapter->GetParent(IID_PPV_ARGS(&d.dxgiFactory));
if (!d.dxgiFactory)
return 0;
d.dxgiFactory->CreateSwapChainForHwnd(
d.device,
hh, &swapChainDesc,
nullptr, // allow on all displays
nullptr, // allow on all displays
&d.m_swapChain);
if (!d.m_swapChain)
return 0;
d.dxgiDevice->SetMaximumFrameLatency(1);
d.m_swapChain->GetBuffer(0, IID_PPV_ARGS(&d.backBuffer));
if (!d.backBuffer)
return 0;
// Now we set up the Direct2D render target bitmap linked to the swapchain.
D2D1_BITMAP_PROPERTIES1 bitmapProperties;
bitmapProperties.bitmapOptions = D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW;
bitmapProperties.pixelFormat.alphaMode = D2D1_ALPHA_MODE_IGNORE;
bitmapProperties.pixelFormat.format = DXGI_FORMAT_B8G8R8A8_UNORM;
bitmapProperties.dpiX = 96;
bitmapProperties.dpiY = 96;
bitmapProperties.colorContext = 0;
// Direct2D needs the dxgi version of the backbuffer surface pointer.
d.m_swapChain->GetBuffer(0, IID_PPV_ARGS(&d.dxgiBackBuffer));
if (!d.dxgiBackBuffer)
return 0;
// Get a D2D surface from the DXGI back buffer to use as the D2D render target.
d.m_d2dContext->CreateBitmapFromDxgiSurface(
d.dxgiBackBuffer,
&bitmapProperties,
&d.m_d2dTargetBitmap);
if (!d.m_d2dTargetBitmap)
return 0;
// Now we can set the Direct2D render target.
d.m_d2dContext->SetTarget(d.m_d2dTargetBitmap);
return true;
}
The problem is this code:
auto de = D3D_DRIVER_TYPE_HARDWARE;
When used, Direct2D writes nothing although EndDraw() returns S_OK. If I use D3D_DRIVER_TYPE_SOFTWARE, everything works correctly.
What am I doing wrong?
I'm creating a c++ project using Embarcadero RAD Studio (10.2 Tokyo starter) and the Windows GDI to draw text, via the DrawText() function.
I recently saw that Windows 10 provides a new "Segoe UI Emoji" font, that potentially allows text functions to draw colored emojis. I found several examples using Direct2D, but none with pure GDI functions.
I also tried a simple code, like this:
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);
The output result sounds good in terms of symbols, but they are drawn in black&white, without colors, as you can see on the screenshot below:
I could not find any additional options that may allow the text to be drawn using colored symbols instead of black&white. Is there a way to activate the support of the color in GDI DrawText() function, and if yes, how to do that? Or only Direct2D may draw colored emojis?
EDITED on 30.10.2017
As the GDI cannot do the job (unfortunately, and as I thought) I publish here the Direct2D version of the above code, that worked for me.
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();
Of course this code will draw colored emojis only on the currently most recent versions of Windows 10, and above. On previous versions the text will be drawn as above (and the code may not compile).
Bonus Reading
MSDN: Color Fonts with DirectWrite, Direct2D, and Win2D
GDI does not support color fonts (even if you go the full Uniscribe route), you have to use Direct2D if you want color font support. It makes sense that the simpler GDI APIs don't support color fonts as color fonts require using OpenType tags and none of DrawText/TextOut provide that level of control, Uniscribe allows for such tags but has simply not been extended to support color fonts.
You can use DirectWrite to draw colored emojis onto a bitmap in memory DC, then BitBlt() to your destination DC.
Basically, you need to implement a custom IDWriteTextRenderer class and call IDWriteTextLayout::Draw() with your renderer, then copy the result.
In your class, you retrieve IDWriteGdiInterop from IDWriteFactory and call IDWriteGdiInterop::CreateBitmapRenderTarget() to get the bitmap render target; call IDWriteFactory::CreateMonitorRenderingParams() to get the rendering parameters, and call IDWriteFactory::CreateTextFormat() to set up your text format.
The only significant method is DrawGlyphRun(), where you get IDWriteColorGlyphRunEnumerator with IDWriteFactory2::TranslateColorGlyphRun() and with each color run, call IDWriteBitmapRenderTarget::DrawGlyphRun() to do the work for you.
Just remember to update the render target/parameters when the window size/position changes.
You may reference this MSDN documentation:
Render to a GDI Surface
https://msdn.microsoft.com/en-us/library/windows/desktop/ff485856(v=vs.85).aspx
As mentioned by #SoronelHaetir's answer above, the win32 graphics device interface (GDI) that is used when working with static window components (via WC_STATIC) doesn't support colorized fonts. In order to display colored emojis and/or "fancier" text (i.e. colored text, etc.), you'll need to use the Direct2D API.
The example above provided by original poster (OP) #Jean-Milost Reymond isn't something that is immediately able to be compiled and tried out by the reader. Also, it uses the TCanvas class, which isn't strictly needed when working directly with the Win32 API.
For people looking for a complete example that works on the bare-metal Win32 API and can be immediately copied and pasted and compiled, then here is the code that will compile in 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;
}
(The above example doesn't have perfect error handling, so it's important to audit this example code if the following is used in any project the reader is working on.)
Before attempting to compile this in Visual Studio, make sure your project has the "SubSystem" linker option set to Windows /SUBSYSTEM:WINDOWS.
Once compiled successfully, the following application window will appear:
I tested this coded example in Visual Studio 2022 Community Edition on Windows 11 with success.
Reference(s):
Creating a Simple Direct2D Application
Tutorial: Getting Started with DirectWrite
Here's my problem:
I'm working on a test project, that initializes Direct2D to draw some stuff.
For now, I am in need of creating BMP background so I looked some tutorials of loading bmp to use it with Direct2D on MSDN site. After some coding and debugging I came to the only problem I can't fully comprehend and, well, I'm stuck here. The problem is simple: I get ACCESS VIOLATION at this line:
pRenderTarget->CreateBitmapFromWicBitmap( pConverter, NULL, ppBitmap);
I fixed all issues I could find out and tested every HRESULT.
here's full code of background.cpp:
`
#include "Background.h"
using namespace D2D1;
Background::Background()
{
pIWICFactory = nullptr;
pDecoder = nullptr;
pSource = nullptr;
pStream = nullptr;
pConverter = nullptr;
ppBitmap = nullptr;
destinationWidth = 0;
destinationHeight = 0;
file_path = L"Background.bmp";
}
bool Background::init(HWND hwnd)
{
CoInitializeEx(0, COINIT_MULTITHREADED);
CoCreateInstance( CLSID_WICImagingFactory, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pIWICFactory));
pIWICFactory->CreateDecoderFromFilename(file_path, NULL, GENERIC_READ, WICDecodeMetadataCacheOnLoad, &pDecoder);
pIWICFactory->CreateStream(&pStream);
pStream->InitializeFromFilename(file_path, GENERIC_READ);
pDecoder->Initialize(pStream,WICDecodeMetadataCacheOnLoad);
pDecoder->GetFrame(0, &pSource);
pIWICFactory->CreateFormatConverter(&pConverter);
pConverter->Initialize(pSource, GUID_WICPixelFormat32bppPBGRA, WICBitmapDitherTypeNone, NULL, 0.f, WICBitmapPaletteTypeMedianCut);
pRenderTarget->CreateBitmapFromWicBitmap( pConverter, NULL, ppBitmap);
return true;
}
Background::~Background()
{
CoUninitialize();
pIWICFactory->Release();
pDecoder->Release();
pSource->Release();
pStream->Release();
pConverter->Release();
CoUninitialize();
}
`
And here's parent class "Render.cpp"
(there's no connection between how I managed class inheritance and the problem - I tried to create new project that contained only one class including both render and background)
`
#include "Render.h"
using namespace D2D1;
Render::Render()
{
pD2DFactory = nullptr;
pRenderTarget = nullptr;
pGreenBrush = nullptr;
}
bool Render::Init(HWND hwnd)
{
HRESULT hr = D2D1CreateFactory( D2D1_FACTORY_TYPE_SINGLE_THREADED, &pD2DFactory );
RECT rc;
GetClientRect(hwnd, &rc);
hr = pD2DFactory->CreateHwndRenderTarget(RenderTargetProperties(), HwndRenderTargetProperties(hwnd, SizeU( rc.right - rc.left, rc.bottom - rc.top)),&pRenderTarget);
if (SUCCEEDED(hr))
pRenderTarget->CreateSolidColorBrush(ColorF(ColorF::Green), &pGreenBrush );
else
return false;
return true;
}
bool Render::Draw(HWND hwnd)
{
RECT rc;
GetClientRect(hwnd, &rc);
pRenderTarget->BeginDraw();
pRenderTarget->FillRectangle(
RectF(
rc.left + 500.0f,
rc.top + 250.0f,
rc.right - 500.0f,
rc.bottom - 500.0f),
pGreenBrush);
HRESULT hr = pRenderTarget->EndDraw();
if (SUCCEEDED(hr))
return true;
else
return false;
}
void Render::ShutDown()
{
if (pD2DFactory)
pD2DFactory->Release();
if (pRenderTarget)
pRenderTarget->Release();
if (pGreenBrush)
pGreenBrush->Release();
}
`
From the double 'pp' I asume you declared ppBitmap; as D2D1Bitmap**, also because you pass it as-value to CreateBitmapFromWicBitmap.
If correct your solution is simple: Declare a ptrP*, not a ptrptrPP**
// in class declaration or c'tor(?)
D2D1Bitmap* pBitmap = nullptr; // instead of D2D1Bitmap**
/// at the point of creation
D2D1wndTarget->CreateBitmapFromWicBitmap(<yourConverter>, NULL, &pBitmap);
In your example you declared it as null-ptr and passed it to a function that takes an address to pointer, resulting in the function reading at 0x000000 resulting in an access violation.
I've modified the C# IWICBitmapSource.CopyPixels interface to allow both array marshaling and passing a pointer:
void CopyPixels(
WICRect prc,
uint cbStride,
uint cbBufferSize,
[Out]
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)]
byte[] pbBuffer
);
new void CopyPixels(
WICRect prc,
uint cbStride,
uint cbBufferSize,
IntPtr pbBuffer
);
I'm calling it like this
public static Bitmap FromWic(IWICBitmapSource source) {
Guid format;
source.GetPixelFormat(out format);
PixelFormat gdiFormat = ConversionUtils.GetPixelFormat(format);
uint w, h;
source.GetSize(out w, out h);
Bitmap b = new Bitmap((int)w, (int)h, gdiFormat);
BitmapData bd = b.LockBits(new Rectangle(0, 0, (int)w, (int)h),
ImageLockMode.ReadWrite, b.PixelFormat);
try {
//Copy unmanaged-to-unmanaged
source.CopyPixels(
new WICRect { X = 0, Y = 0, Width = (int)w, Height = (int)h },
(uint)bd.Stride, (uint)(bd.Stride * bd.Height), bd.Scan0);
} finally {
b.UnlockBits(bd);
}
return b;
}
The code executes without errors, and all the width, height, stride, and buffer size values are correct, but the bitmap remains black, as if WIC never touched it.
Any ideas why this could be happening? Is there something wrong with the .NET marshalling?
It seems that WPF uses a proxy method to copy data to unmanaged memory. Based on that, I found a solution. The following code works and should be extremely efficient.
[DllImport("WindowsCodecs.dll", EntryPoint = "IWICBitmapSource_CopyPixels_Proxy")]
internal static extern int CopyPixels(IWICBitmapSource bitmap, IntPtr rect, uint cbStride, uint cbBufferSize, IntPtr pvPixels);
public static Bitmap FromWic(IWICBitmapSource source) {
Guid format; //Get the WIC pixel format
source.GetPixelFormat(out format);
//Get the matching GDI format
PixelFormat gdiFormat = ConversionUtils.GetPixelFormat(format);
//If it's not GDI-supported format, convert it to one.
IWICComponentFactory factory = null;
IWICFormatConverter converter = null;
try {
if (gdiFormat == PixelFormat.Undefined) {
factory = (IWICComponentFactory)new WICImagingFactory();
converter = factory.CreateFormatConverter();
converter.Initialize(source, Consts.GUID_WICPixelFormat32bppBGRA, WICBitmapDitherType.WICBitmapDitherTypeNone, null, 0.9f, WICBitmapPaletteType.WICBitmapPaletteTypeCustom);
gdiFormat = PixelFormat.Format32bppArgb;
}
IWICBitmapSource data = converter != null ? converter : source;
//Get the dimensions of the WIC bitmap
uint w, h;
data.GetSize(out w, out h);
Bitmap b = new Bitmap((int)w, (int)h, gdiFormat);
BitmapData bd = b.LockBits(new Rectangle(0, 0, (int)w, (int)h), ImageLockMode.WriteOnly, b.PixelFormat);
try {
long result = CopyPixels(data, IntPtr.Zero, (uint)bd.Stride, (uint)(bd.Stride * bd.Height), bd.Scan0);
if (result == 0x80070057) throw new ArgumentException();
if (result < 0) throw new Exception("HRESULT " + result);
return b;
} finally {
b.UnlockBits(bd);
}
} finally {
if (converter != null) Marshal.ReleaseComObject(converter);
if (source != null) Marshal.ReleaseComObject(factory);
}
}
Note that I modified the IWICBitmapSource_CopyPixels_Proxy dllimport to use IWICBitmapSource instead of SafeMILHandle, and I modified ref Int32Rect to IntPtr, so that I could legally pass IntPtr.Zero (I get an HRESULT error if I pass in a rectangle to copy - and I know my stucture is value, I use it in other CopyPixels calls.