與 Direct2D 互通

Win2D 實作為 Direct2D 之上的一層,並支援雙向互通。 如果你有 Win2D 物件,可以存取用來實作它的原生 Direct2D 物件。 如果你有一個 Direct2D 物件,可以查找包裝該物件的 Win2D 物件;如果原本不存在,則可以建立新的包裝物件。

Interop 允許你將 Win2D 與原生 DirectX API 混搭使用。 你可以寫一個主要使用 Win2D 的應用程式,但隨時切換到原生 DirectX——可能是為了呼叫其他需要原生介面的 API 或第三方元件。 或者你的應用程式大部分是原生 DirectX,但在特定需要額外便利性或 C# 支援的地方,可以切換到 Win2D。

互通性 API

C++/CX 互操作 API 定義於標頭 Microsoft。Graphics.Canvas.native.h:

#include <Microsoft.Graphics.Canvas.native.h>

using namespace Microsoft::Graphics::Canvas;

若要取得由 Win2D 物件封裝的原生 Direct2D 物件:

template<typename T, typename U>
Microsoft::WRL::ComPtr<T> GetWrappedResource(U^ wrapper);

template<typename T, typename U>
Microsoft::WRL::ComPtr<T> GetWrappedResource(CanvasDevice^ device, U^ wrapper);

template<typename T, typename U>
Microsoft::WRL::ComPtr<T> GetWrappedResource(CanvasDevice^ device, U^ wrapper, float dpi);

大多數類型 GetWrappedResource 可以只用 Win2D 包裝物件作為參數來呼叫。 對於某些類型(見下表),還必須傳遞裝置和/或 DPI 值。 將 GetWrappedResource 與不需要裝置或 DPI 的類型一起使用時,傳遞裝置或 DPI 並不是錯誤。

要讓 Win2D 物件包裝原生 Direct2D 物件:

template<typename WRAPPER>
WRAPPER^ GetOrCreate(IUnknown* resource);

template<typename WRAPPER>
WRAPPER^ GetOrCreate(CanvasDevice^ device, IUnknown* resource);

template<typename WRAPPER>
WRAPPER^ GetOrCreate(ID2D1Device1* device, IUnknown* resource);

template<typename WRAPPER>
WRAPPER^ GetOrCreate(CanvasDevice^ device, IUnknown* resource, float dpi);

template<typename WRAPPER>
WRAPPER^ GetOrCreate(ID2D1Device1* device, IUnknown* resource, float dpi);

GetOrCreate 如果已有包裝器實例,則回傳;若沒有,則回傳新的包裝器。 只要該包裝物件實例仍然存在,對同一個原生物件反覆呼叫此方法,每次都會回傳相同的包裝物件。 如果所有對包裝器的引用都被釋放,導致其引用計數降為零並遭到銷毀,之後任何對 GetOrCreate 的呼叫都必須建立新的包裝器。

某些類型 GetOrCreate 只需以 Direct2D 資源物件作為參數呼叫,而其他類型(見下表)則必須傳遞裝置與 DPI 值。 將 GetOrCreate 與不需要裝置或 DPI 的類型一起使用時,傳遞裝置或 DPI 並不是錯誤。 如果已經有 Win2D 包裝器,即使是通常需要的類型,也可以省略裝置和 DPI:這些參數只在建立新包裝實例時使用。

GetOrCreate 理解繼承階層,並且一律建立適當的最衍生包裝型別。 例如,如果你以帶有 GetOrCreate<CanvasBitmap>(ID2D1Bitmap1*) 旗標的 ID2D1Bitmap1 呼叫 D2D1_BITMAP_OPTIONS_TARGET,則回傳的包裝器執行個體實際上會是 CanvasRenderTarget(其衍生自 CanvasBitmap)。 反過來說,如果你以不具有 GetOrCreate<CanvasRenderTarget>(ID2D1Bitmap1*)ID2D1Bitmap1 呼叫 D2D1_BITMAP_OPTIONS_TARGET,就會擲回無效轉型例外。

極端一點來說,稱其為 GetOrCreate<Object>(IUnknown*),或甚至稱其為 GetWrappedResource<IUnknown>(Object^),都是成立的。

支援互通操作的類型

Win2D 類型 Direct2D 類型 GetOrCreate 參數 GetWrappedResource 參數
CanvasBitmap ID2D1Bitmap1 不含 D2D1_BITMAP_OPTIONS_TARGET Device -
CanvasCachedGeometry ID2D1GeometryRealization Device -
CanvasCommandList ID2D1CommandList Device -
CanvasDevice ID2D1Device1 - -
CanvasDrawingSession ID2D1DeviceContext1 - -
CanvasFontFace IDWriteFontFaceReference - -
CanvasFontSet IDWriteFontSet - -
CanvasGeometry ID2D1Geometry,或其衍生介面ID2D1PathGeometry之一,、ID2D1RectangleGeometryID2D1RoundedRectangleGeometryID2D1EllipseGeometryID2D1TransformedGeometryID2D1GeometryGroup Device -
CanvasGradientMesh ID2D1GradientMesh Device -
CanvasImageBrush ID2D1BitmapBrush1(若影像是 CanvasBitmapSourceRectanglenull),或 ID2D1ImageBrush(若其為其他任何類型的 ICanvasImage,或若 SourceRectangle is set Device 可選 DPI^1
CanvasLinearGradientBrush ID2D1LinearGradientBrush Device -
CanvasNumberSubstitution IDWriteNumberSubstitution - -
CanvasRadialGradientBrush ID2D1RadialGradientBrush Device -
CanvasRenderTarget ID2D1Bitmap1D2D1_BITMAP_OPTIONS_TARGET Device -
CanvasSolidColorBrush ID2D1SolidColorBrush Device -
CanvasStrokeStyle ID2D1StrokeStyle1 - Device
CanvasSvgDocument ID2D1SvgDocument^2 Device -
CanvasSwapChain IDXGISwapChain1 裝置、DPI -
CanvasTextFormat IDWriteTextFormat1 - -
CanvasTextLayout IDWriteTextLayout3 Device -
CanvasTextRenderingParameters IDWriteRenderingParams3 - -
CanvasTypography IDWriteTypography - -
CanvasVirtualBitmap ID2D1ImageSourceID2D1TransformedImageSource Device -
ColorManagementProfile ID2D1ColorContext Device Device
EffectTransferTable3D ID2D1LookupTable3D Device -
Microsoft.Graphics.Canvas.Effects.* (多個 Win2D 類別對應到相同的 D2D 類型) ID2D1Effect 搭配適當的 D2D1_PROPERTY_TYPE_CLSID Device 裝置,選配 DPI^1

Note

^1:選擇性 DPI 表示,對此類型呼叫 GetWrappedResource 時不指定 DPI 值也是有效的;但如果指定了 DPI,Win2D 或許可以藉由省略多餘的 DPI 補償節點,更有效率地設定效果圖。 這適用於呼叫 GetWrappedResource 一個效果,或是對具有效果的 a CanvasImageBrush 作為其來源影像。

Note

^2:使用原生 C++ 互通操作從 CanvasSvgDocument 產生 ID2D1SvgDocument 時,會忽略 ID2D1SvgDocument 的檢視區大小。

使用 C++/CX 的互通操作

以下是一個 Win2D 如何與 Direct2D 互通的範例,使用 C++:

#include <Microsoft.Graphics.Canvas.native.h>
#include <d2d1_2.h>

using namespace Microsoft::Graphics::Canvas;
using namespace Microsoft::WRL;

// Interop Win2D -> Direct2D.
CanvasDevice^ canvasDevice = ...;
CanvasBitmap^ canvasBitmap = ...;

ComPtr<ID2D1Device> nativeDevice = GetWrappedResource<ID2D1Device>(canvasDevice);
ComPtr<ID2D1Bitmap1> nativeBitmap = GetWrappedResource<ID2D1Bitmap1>(canvasBitmap);

// Interop Direct2D -> Win2D.
canvasDevice = GetOrCreate<CanvasDevice>(nativeDevice.Get());
bitmap = GetOrCreate<CanvasBitmap>(canvasDevice, nativeBitmap.Get());

Note

也可以透過 C# 以各種方式實現互通(例如使用內建的 COM/WinRT 互通、透過 CsWinRT 的 API,或使用 blittable 繫結)。 想了解更多關於 C# 手動互通的資訊,請參考 CsWinRT 的文件。

使用 C++/WinRT 進行互通

你也可以用 C++/WinRT 進行互操作,並稍作修改上述設定。 請注意,當你將 Win2D NuGet 套件加入 C++/WinRT 專案時,Win2D Windows 執行階段 元件的 C++/WinRT 標頭應該會自動產生。 不過,要互通,你仍然需要使用 Microsoft 的標頭檔。Graphics.Canvas.native.h 包含命名空間ICanvasFactoryNative中低階的 ABI 介面ABI::Microsoft::Graphics::Canvas。 介面有以下功能,你可以用來進行互通操作。

HRESULT GetOrCreate(ICanvasDevice* device, IUnknown* resource, float dpi, IInspectable** wrapper);
HRESULT GetNativeResource(ICanvasDevice* device, float dpi, REFIID iid, void** resource);

以下是一個範例,示範如何從 CanvasVirtualBitmap 建立 IWICBitmapSource,並從 IWICBitmapSource 和共用的 CanvasDevice 開始。

#include "pch.h"
#include <wincodec.h>
#include <wincodecsdk.h>
#include <winrt/Microsoft.Graphics.Canvas.h> //This defines the C++/WinRT interfaces for the Win2D Windows Runtime Components
#include <Microsoft.Graphics.Canvas.h> //This defines the low-level ABI interfaces for the Win2D Windows Runtime Components
#include <Microsoft.Graphics.Canvas.native.h> //This is for interop
#include <d2d1_3.h>

using namespace winrt::Microsoft::Graphics::Canvas;
namespace abi {
  using namespace ABI::Microsoft::Graphics::Canvas;
}

namespace winrt::Win2DInteropTest::implementation {
  CanvasVirtualBitmap CreateVirtualBitmapFromBitmapSource(com_ptr<IWICBitmapSource> const& pBitmapSource){
    CanvasDevice sharedDevice = CanvasDevice::GetSharedDevice();

    //First we need to get an ID2D1Device1 pointer from the shared CanvasDevice
    com_ptr<abi::ICanvasResourceWrapperNative> nativeDeviceWrapper = sharedDevice.as<abi::ICanvasResourceWrapperNative>();
    com_ptr<ID2D1Device1> pDevice{ nullptr };
    check_hresult(nativeDeviceWrapper->GetNativeResource(nullptr, 0.0f, guid_of<ID2D1Device1>(), pDevice.put_void()));

    //Next we need to call some Direct2D functions to create the ID2D1ImageSourceFromWic object
    com_ptr<ID2D1DeviceContext1> pContext{ nullptr };
    check_hresult(pDevice->CreateDeviceContext(D2D1_DEVICE_CONTEXT_OPTIONS_NONE, pContext.put()));
    com_ptr<ID2D1DeviceContext2> pContext2 = pContext.as<ID2D1DeviceContext2>();
    com_ptr<ID2D1ImageSourceFromWic> pImage{ nullptr };
    check_hresult(pContext2->CreateImageSourceFromWic(pBitmapSource.get(), D2D1_IMAGE_SOURCE_LOADING_OPTIONS_RELEASE_SOURCE, pImage.put()));

    //Finally we need to wrap the ID2D1ImageSourceFromWic object inside 
    com_ptr<::IInspectable> pInspectable{ nullptr };
    auto factory = winrt::get_activation_factory<CanvasDevice, abi::ICanvasFactoryNative>(); //abi::ICanvasFactoryNative is the activation factory for the CanvasDevice class
    check_hresult(factory->GetOrCreate(sharedDevice.as<abi::ICanvasDevice>().get(), pImage.as<::IUnknown>().get(), 0.0f, pInspectable.put())); //Note abi::ICanvasDevice is defined in the header Microsoft.Graphics.Canvas.h
    CanvasVirtualBitmap cvb = pInspectable.as<CanvasVirtualBitmap>();
    return cvb;
  }
}

請記得在任何 WinRT 標頭之前,先在 pch.h 中包含標頭 <unknwn.h>(這是 SDK 17763 及更新版本中的必要要求)。