///////////////////////////////////////////////////////////////////////////// // Name: src/msw/graphicsd2d.cpp // Purpose: Implementation of Direct2D Render Context // Author: Pana Alexandru // Created: 2014-05-20 // Copyright: (c) 2014 wxWidgets development team // Licence: wxWindows licence ///////////////////////////////////////////////////////////////////////////// #include "wx/wxprec.h" #if wxUSE_GRAPHICS_DIRECT2D // Minimum supported client: Windows 8 and Platform Update for Windows 7 #define wxD2D_DEVICE_CONTEXT_SUPPORTED 0 // We load these functions at runtime from the d2d1.dll. // However, since they are also used inside the d2d1.h header we must provide // implementations matching the exact declarations. These defines ensures we // are not violating the ODR rule. #define D2D1CreateFactory wxD2D1CreateFactory #define D2D1MakeRotateMatrix wxD2D1MakeRotateMatrix #define D2D1MakeSkewMatrix wxD2D1MakeSkewMatrix #define D2D1IsMatrixInvertible wxD2D1IsMatrixInvertible #define D2D1InvertMatrix wxD2D1InvertMatrix #if wxD2D_DEVICE_CONTEXT_SUPPORTED #define D3D11CreateDevice wxD3D11CreateDevice #endif // There are clashes between the names of the member fields and parameters // in the standard d2d1helper.h header resulting in C4458 with VC14, // so disable this warning for this file as there is no other way to // avoid it. #ifdef __VISUALC__ #pragma warning(push) #pragma warning(disable:4458) // declaration of 'xxx' hides class member #endif #include "wx/msw/private/graphicsd2d.h" #ifdef __MINGW64_TOOLCHAIN__ #ifndef DWRITE_E_NOFONT #define DWRITE_E_NOFONT _HRESULT_TYPEDEF_(0x88985002L) #endif #endif #if wxD2D_DEVICE_CONTEXT_SUPPORTED #include #include #include #endif #ifdef __VISUALC__ #pragma warning(pop) #endif #ifdef __BORLANDC__ #pragma hdrstop #endif #include // for FLT_MAX, FLT_MIN #ifndef WX_PRECOMP #include "wx/dc.h" #include "wx/dcclient.h" #include "wx/dcmemory.h" #include "wx/image.h" #include "wx/module.h" #include "wx/window.h" #include "wx/msw/private.h" #endif // !WX_PRECOMP #include "wx/graphics.h" #include "wx/dynlib.h" #include "wx/msw/private/comptr.h" #include "wx/private/graphics.h" #include "wx/stack.h" #include "wx/sharedptr.h" // This must be the last header included to only affect the DEFINE_GUID() // occurrences below but not any GUIDs declared in the standard files included // above. #include // Generic error message for a failed direct2d operation #define wxFAILED_HRESULT_MSG(result) \ wxString::Format("Direct2D failed with HRESULT %x", (result)) // Checks a HRESULT value for success, otherwise displays an error message and // returns from the enclosing function. #define wxCHECK_HRESULT_RET(result) \ wxCHECK_RET(SUCCEEDED(result), wxFAILED_HRESULT_MSG(result)) #define wxCHECK2_HRESULT_RET(result, returnValue) \ wxCHECK2_MSG(SUCCEEDED(result), return returnValue, \ wxFAILED_HRESULT_MSG(result)) // Variation of wxCHECK_HRESULT_RET for functions which must return a pointer #define wxCHECK_HRESULT_RET_PTR(result) wxCHECK2_HRESULT_RET(result, NULL) // Checks the precondition of wxManagedResourceHolder::AcquireResource, namely // that it is bound to a manager. #define wxCHECK_RESOURCE_HOLDER_PRE() \ { \ if (IsResourceAcquired()) return; \ wxCHECK_RET(IsBound(), \ "Cannot acquire a native resource without being bound to a manager"); \ } // Checks the postcondition of wxManagedResourceHolder::AcquireResource, namely // that it was successful in acquiring the native resource. #define wxCHECK_RESOURCE_HOLDER_POST() \ wxCHECK_RET(m_nativeResource != NULL, "Could not acquire native resource"); // Helper class used to check for direct2d availability at runtime and to // dynamically load the required symbols from d2d1.dll and dwrite.dll class wxDirect2D { public: enum wxD2DVersion { wxD2D_VERSION_1_0, wxD2D_VERSION_1_1, wxD2D_VERSION_NONE }; static bool Initialize() { if (!m_initialized) { m_hasDirect2DSupport = LoadLibraries(); m_initialized = true; } return m_hasDirect2DSupport; } static bool HasDirect2DSupport() { Initialize(); return m_hasDirect2DSupport; } static wxD2DVersion GetDirect2DVersion() { return m_D2DRuntimeVersion; } private: static bool LoadLibraries() { if ( !m_dllDirect2d.Load(wxT("d2d1.dll"), wxDL_VERBATIM | wxDL_QUIET) ) return false; if ( !m_dllDirectWrite.Load(wxT("dwrite.dll"), wxDL_VERBATIM | wxDL_QUIET) ) return false; #if wxD2D_DEVICE_CONTEXT_SUPPORTED if (!m_dllDirect3d.Load(wxT("d3d11.dll"), wxDL_VERBATIM | wxDL_QUIET)) return false; #endif #define wxLOAD_FUNC(dll, name) \ name = (name##_t)dll.RawGetSymbol(#name); \ if ( !name ) \ return false; wxLOAD_FUNC(m_dllDirect2d, D2D1CreateFactory); wxLOAD_FUNC(m_dllDirect2d, D2D1MakeRotateMatrix); wxLOAD_FUNC(m_dllDirect2d, D2D1MakeSkewMatrix); wxLOAD_FUNC(m_dllDirect2d, D2D1IsMatrixInvertible); wxLOAD_FUNC(m_dllDirect2d, D2D1InvertMatrix); wxLOAD_FUNC(m_dllDirectWrite, DWriteCreateFactory); #if wxD2D_DEVICE_CONTEXT_SUPPORTED wxLOAD_FUNC(m_dllDirect3d, D3D11CreateDevice); #endif m_D2DRuntimeVersion = wxD2D_VERSION_1_0; return true; } public: typedef HRESULT (WINAPI *D2D1CreateFactory_t)(D2D1_FACTORY_TYPE, REFIID, CONST D2D1_FACTORY_OPTIONS*, void**); static D2D1CreateFactory_t D2D1CreateFactory; typedef void (WINAPI *D2D1MakeRotateMatrix_t)(FLOAT, D2D1_POINT_2F, D2D1_MATRIX_3X2_F*); static D2D1MakeRotateMatrix_t D2D1MakeRotateMatrix; typedef void (WINAPI *D2D1MakeSkewMatrix_t)(FLOAT, FLOAT, D2D1_POINT_2F, D2D1_MATRIX_3X2_F*); static D2D1MakeSkewMatrix_t D2D1MakeSkewMatrix; typedef BOOL (WINAPI *D2D1IsMatrixInvertible_t)(const D2D1_MATRIX_3X2_F*); static D2D1IsMatrixInvertible_t D2D1IsMatrixInvertible; typedef BOOL (WINAPI *D2D1InvertMatrix_t)(D2D1_MATRIX_3X2_F*); static D2D1InvertMatrix_t D2D1InvertMatrix; typedef HRESULT (WINAPI *DWriteCreateFactory_t)(DWRITE_FACTORY_TYPE, REFIID, IUnknown**); static DWriteCreateFactory_t DWriteCreateFactory; #if wxD2D_DEVICE_CONTEXT_SUPPORTED typedef HRESULT (WINAPI *D3D11CreateDevice_t)(IDXGIAdapter*, D3D_DRIVER_TYPE, HMODULE, UINT, CONST D3D_FEATURE_LEVEL*, UINT, UINT, ID3D11Device**, D3D_FEATURE_LEVEL*, ID3D11DeviceContext**); static D3D11CreateDevice_t D3D11CreateDevice; #endif private: static bool m_initialized; static bool m_hasDirect2DSupport; static wxD2DVersion m_D2DRuntimeVersion; static wxDynamicLibrary m_dllDirect2d; static wxDynamicLibrary m_dllDirectWrite; #if wxD2D_DEVICE_CONTEXT_SUPPORTED static wxDynamicLibrary m_dllDirect3d; #endif }; // define the members bool wxDirect2D::m_initialized = false; bool wxDirect2D::m_hasDirect2DSupport = false; wxDirect2D::wxD2DVersion wxDirect2D::m_D2DRuntimeVersion = wxD2D_VERSION_NONE; wxDynamicLibrary wxDirect2D::m_dllDirect2d; wxDynamicLibrary wxDirect2D::m_dllDirectWrite; #if wxD2D_DEVICE_CONTEXT_SUPPORTED wxDynamicLibrary wxDirect2D::m_dllDirect3d; #endif // define the (not yet imported) functions wxDirect2D::D2D1CreateFactory_t wxDirect2D::D2D1CreateFactory = NULL; wxDirect2D::D2D1MakeRotateMatrix_t wxDirect2D::D2D1MakeRotateMatrix = NULL; wxDirect2D::D2D1MakeSkewMatrix_t wxDirect2D::D2D1MakeSkewMatrix = NULL; wxDirect2D::D2D1IsMatrixInvertible_t wxDirect2D::D2D1IsMatrixInvertible = NULL; wxDirect2D::D2D1InvertMatrix_t wxDirect2D::D2D1InvertMatrix = NULL; wxDirect2D::DWriteCreateFactory_t wxDirect2D::DWriteCreateFactory = NULL; #if wxD2D_DEVICE_CONTEXT_SUPPORTED wxDirect2D::D3D11CreateDevice_t wxDirect2D::D3D11CreateDevice = NULL; #endif // define the interface GUIDs DEFINE_GUID(wxIID_IWICImagingFactory, 0xec5ec8a9, 0xc395, 0x4314, 0x9c, 0x77, 0x54, 0xd7, 0xa9, 0x35, 0xff, 0x70); DEFINE_GUID(wxIID_ID2D1Factory, 0x06152247, 0x6f50, 0x465a, 0x92, 0x45, 0x11, 0x8b, 0xfd, 0x3b, 0x60, 0x07); DEFINE_GUID(wxIID_IDWriteFactory, 0xb859ee5a, 0xd838, 0x4b5b, 0xa2, 0xe8, 0x1a, 0xdc, 0x7d, 0x93, 0xdb, 0x48); DEFINE_GUID(wxIID_IWICBitmapSource, 0x00000120, 0xa8f2, 0x4877, 0xba, 0x0a, 0xfd, 0x2b, 0x66, 0x45, 0xfb, 0x94); DEFINE_GUID(GUID_WICPixelFormat32bppPBGRA, 0x6fddc324, 0x4e03, 0x4bfe, 0xb1, 0x85, 0x3d, 0x77, 0x76, 0x8d, 0xc9, 0x10); DEFINE_GUID(GUID_WICPixelFormat32bppBGR, 0x6fddc324, 0x4e03, 0x4bfe, 0xb1, 0x85, 0x3d, 0x77, 0x76, 0x8d, 0xc9, 0x0e); #if wxD2D_DEVICE_CONTEXT_SUPPORTED DEFINE_GUID(IID_IDXGIDevice, 0x54ec77fa, 0x1377, 0x44e6, 0x8c, 0x32, 0x88, 0xfd, 0x5f, 0x44, 0xc8, 0x4c); #endif #ifndef CLSID_WICImagingFactory DEFINE_GUID(CLSID_WICImagingFactory, 0xcacaf262, 0x9370, 0x4615, 0xa1, 0x3b, 0x9f, 0x55, 0x39, 0xda, 0x4c, 0xa); #endif // Implementation of the Direct2D functions HRESULT WINAPI wxD2D1CreateFactory( D2D1_FACTORY_TYPE factoryType, REFIID riid, CONST D2D1_FACTORY_OPTIONS *pFactoryOptions, void **ppIFactory) { if (!wxDirect2D::Initialize()) return S_FALSE; return wxDirect2D::D2D1CreateFactory( factoryType, riid, pFactoryOptions, ppIFactory); } void WINAPI wxD2D1MakeRotateMatrix( FLOAT angle, D2D1_POINT_2F center, D2D1_MATRIX_3X2_F *matrix) { if (!wxDirect2D::Initialize()) return; wxDirect2D::D2D1MakeRotateMatrix(angle, center, matrix); } void WINAPI wxD2D1MakeSkewMatrix( FLOAT angleX, FLOAT angleY, D2D1_POINT_2F center, D2D1_MATRIX_3X2_F *matrix) { if (!wxDirect2D::Initialize()) return; wxDirect2D::D2D1MakeSkewMatrix(angleX, angleY, center, matrix); } BOOL WINAPI wxD2D1IsMatrixInvertible( const D2D1_MATRIX_3X2_F *matrix) { if (!wxDirect2D::Initialize()) return FALSE; return wxDirect2D::D2D1IsMatrixInvertible(matrix); } BOOL WINAPI wxD2D1InvertMatrix( D2D1_MATRIX_3X2_F *matrix) { if (!wxDirect2D::Initialize()) return FALSE; return wxDirect2D::D2D1InvertMatrix(matrix); } #if wxD2D_DEVICE_CONTEXT_SUPPORTED HRESULT WINAPI wxD3D11CreateDevice( IDXGIAdapter* pAdapter, D3D_DRIVER_TYPE DriverType, HMODULE Software, UINT Flags, CONST D3D_FEATURE_LEVEL* pFeatureLevels, UINT FeatureLevels, UINT SDKVersion, ID3D11Device** ppDevice, D3D_FEATURE_LEVEL* pFeatureLevel, ID3D11DeviceContext** ppImmediateContext) { if (!wxDirect2D::Initialize()) return S_FALSE; return wxDirect2D::D3D11CreateDevice( pAdapter, DriverType, Software, Flags, pFeatureLevels, FeatureLevels, SDKVersion, ppDevice, pFeatureLevel, ppImmediateContext); } #endif static IWICImagingFactory* gs_WICImagingFactory = NULL; IWICImagingFactory* wxWICImagingFactory() { if (gs_WICImagingFactory == NULL) { HRESULT hr = CoCreateInstance( CLSID_WICImagingFactory, NULL, CLSCTX_INPROC_SERVER, wxIID_IWICImagingFactory, (LPVOID*)&gs_WICImagingFactory); wxCHECK_HRESULT_RET_PTR(hr); } return gs_WICImagingFactory; } static ID2D1Factory* gs_ID2D1Factory = NULL; ID2D1Factory* wxD2D1Factory() { if (!wxDirect2D::Initialize()) return NULL; if (gs_ID2D1Factory == NULL) { D2D1_FACTORY_OPTIONS factoryOptions = {D2D1_DEBUG_LEVEL_NONE}; // According to // https://msdn.microsoft.com/en-us/library/windows/desktop/ee794287(v=vs.85).aspx // the Direct2D Debug Layer is only available starting with Windows 8 // and Visual Studio 2012. #if defined(__WXDEBUG__) && defined(__VISUALC__) && wxCHECK_VISUALC_VERSION(11) if ( wxGetWinVersion() >= wxWinVersion_8 ) { factoryOptions.debugLevel = D2D1_DEBUG_LEVEL_WARNING; } #endif //__WXDEBUG__ HRESULT hr = wxDirect2D::D2D1CreateFactory( D2D1_FACTORY_TYPE_SINGLE_THREADED, wxIID_ID2D1Factory, &factoryOptions, reinterpret_cast(&gs_ID2D1Factory) ); wxCHECK_HRESULT_RET_PTR(hr); } return gs_ID2D1Factory; } static IDWriteFactory* gs_IDWriteFactory = NULL; IDWriteFactory* wxDWriteFactory() { if (!wxDirect2D::Initialize()) return NULL; if (gs_IDWriteFactory == NULL) { wxDirect2D::DWriteCreateFactory( DWRITE_FACTORY_TYPE_SHARED, wxIID_IDWriteFactory, reinterpret_cast(&gs_IDWriteFactory) ); } return gs_IDWriteFactory; } extern WXDLLIMPEXP_DATA_CORE(wxGraphicsPen) wxNullGraphicsPen; extern WXDLLIMPEXP_DATA_CORE(wxGraphicsBrush) wxNullGraphicsBrush; // We use the notion of a context supplier because the context // needed to create Direct2D resources (namely the RenderTarget) // is itself device-dependent and might change during the lifetime // of the resources which were created from it. template class wxContextSupplier { public: typedef C ContextType; virtual C GetContext() = 0; }; typedef wxContextSupplier wxD2DContextSupplier; // A resource holder manages a generic resource by acquiring // and releasing it on demand. class wxResourceHolder { public: // Acquires the managed resource if necessary (not already acquired) virtual void AcquireResource() = 0; // Releases the managed resource virtual void ReleaseResource() = 0; // Checks if the resources was previously acquired virtual bool IsResourceAcquired() = 0; // Returns the managed resource or NULL if the resources // was not previously acquired virtual void* GetResource() = 0; virtual ~wxResourceHolder() {} }; class wxD2DResourceManager; class wxD2DManagedObject { public: virtual void Bind(wxD2DResourceManager* manager) = 0; virtual void UnBind() = 0; virtual bool IsBound() = 0; virtual wxD2DResourceManager* GetManager() = 0; virtual ~wxD2DManagedObject() {} }; class wxManagedResourceHolder : public wxResourceHolder, public wxD2DManagedObject { public: virtual ~wxManagedResourceHolder() {} }; // A Direct2D resource manager handles the device-dependent // resource holders attached to it by requesting them to // release their resources when the API invalidates. // NOTE: We're using a list because we expect to have multiple // insertions but very rarely a traversal (if ever). WX_DECLARE_LIST(wxManagedResourceHolder, wxManagedResourceListType); #include WX_DEFINE_LIST(wxManagedResourceListType); class wxD2DResourceManager: public wxD2DContextSupplier { public: void RegisterResourceHolder(wxManagedResourceHolder* resourceHolder) { m_resources.push_back(resourceHolder); } void UnregisterResourceHolder(wxManagedResourceHolder* resourceHolder) { m_resources.remove(resourceHolder); } void ReleaseResources() { wxManagedResourceListType::iterator it; for (it = m_resources.begin(); it != m_resources.end(); ++it) { (*it)->ReleaseResource(); } // Check that all resources were released for (it = m_resources.begin(); it != m_resources.end(); ++it) { wxCHECK_RET(!(*it)->IsResourceAcquired(), "One or more device-dependent resources failed to release"); } } virtual ~wxD2DResourceManager() { while (!m_resources.empty()) { m_resources.front()->ReleaseResource(); m_resources.front()->UnBind(); } } private: wxManagedResourceListType m_resources; }; // A Direct2D resource holder manages device dependent resources // by storing any information necessary for acquiring the resource // and releasing the resource when the API invalidates it. template class wxD2DResourceHolder: public wxManagedResourceHolder { public: wxD2DResourceHolder() : m_resourceManager(NULL) { } virtual ~wxD2DResourceHolder() { UnBind(); ReleaseResource(); } bool IsResourceAcquired() wxOVERRIDE { return m_nativeResource != NULL; } void* GetResource() wxOVERRIDE { return GetD2DResource(); } wxCOMPtr& GetD2DResource() { if (!IsResourceAcquired()) { AcquireResource(); } return m_nativeResource; } void AcquireResource() wxOVERRIDE { wxCHECK_RESOURCE_HOLDER_PRE(); DoAcquireResource(); wxCHECK_RESOURCE_HOLDER_POST(); } void ReleaseResource() wxOVERRIDE { m_nativeResource.reset(); } wxD2DContextSupplier::ContextType GetContext() { return m_resourceManager->GetContext(); } void Bind(wxD2DResourceManager* manager) wxOVERRIDE { if (IsBound()) return; m_resourceManager = manager; m_resourceManager->RegisterResourceHolder(this); } void UnBind() wxOVERRIDE { if (!IsBound()) return; m_resourceManager->UnregisterResourceHolder(this); m_resourceManager = NULL; } bool IsBound() wxOVERRIDE { return m_resourceManager != NULL; } wxD2DResourceManager* GetManager() wxOVERRIDE { return m_resourceManager; } protected: virtual void DoAcquireResource() = 0; private: wxD2DResourceManager* m_resourceManager; protected: wxCOMPtr m_nativeResource; }; // Used as super class for graphics data objects // to forward the bindings to their internal resource holder. class wxD2DManagedGraphicsData : public wxD2DManagedObject { public: void Bind(wxD2DResourceManager* manager) wxOVERRIDE { GetManagedObject()->Bind(manager); } void UnBind() wxOVERRIDE { GetManagedObject()->UnBind(); } bool IsBound() wxOVERRIDE { return GetManagedObject()->IsBound(); } wxD2DResourceManager* GetManager() wxOVERRIDE { return GetManagedObject()->GetManager(); } virtual wxD2DManagedObject* GetManagedObject() = 0; ~wxD2DManagedGraphicsData() {} }; D2D1_CAP_STYLE wxD2DConvertPenCap(wxPenCap cap) { switch (cap) { case wxCAP_ROUND: return D2D1_CAP_STYLE_ROUND; case wxCAP_PROJECTING: return D2D1_CAP_STYLE_SQUARE; case wxCAP_BUTT: return D2D1_CAP_STYLE_FLAT; case wxCAP_INVALID: return D2D1_CAP_STYLE_FLAT; } wxFAIL_MSG("unknown pen cap"); return D2D1_CAP_STYLE_FLAT; } D2D1_LINE_JOIN wxD2DConvertPenJoin(wxPenJoin join) { switch (join) { case wxJOIN_BEVEL: return D2D1_LINE_JOIN_BEVEL; case wxJOIN_MITER: return D2D1_LINE_JOIN_MITER; case wxJOIN_ROUND: return D2D1_LINE_JOIN_ROUND; case wxJOIN_INVALID: return D2D1_LINE_JOIN_MITER; } wxFAIL_MSG("unknown pen join"); return D2D1_LINE_JOIN_MITER; } D2D1_DASH_STYLE wxD2DConvertPenStyle(wxPenStyle dashStyle) { switch (dashStyle) { case wxPENSTYLE_SOLID: return D2D1_DASH_STYLE_SOLID; case wxPENSTYLE_DOT: return D2D1_DASH_STYLE_DOT; case wxPENSTYLE_LONG_DASH: return D2D1_DASH_STYLE_DASH; case wxPENSTYLE_SHORT_DASH: return D2D1_DASH_STYLE_DASH; case wxPENSTYLE_DOT_DASH: return D2D1_DASH_STYLE_DASH_DOT; case wxPENSTYLE_USER_DASH: return D2D1_DASH_STYLE_CUSTOM; // NB: These styles cannot be converted to a D2D1_DASH_STYLE // and must be handled separately. case wxPENSTYLE_TRANSPARENT: wxFALLTHROUGH; case wxPENSTYLE_INVALID: wxFALLTHROUGH; case wxPENSTYLE_STIPPLE_MASK_OPAQUE: wxFALLTHROUGH; case wxPENSTYLE_STIPPLE_MASK: wxFALLTHROUGH; case wxPENSTYLE_STIPPLE: wxFALLTHROUGH; case wxPENSTYLE_BDIAGONAL_HATCH: wxFALLTHROUGH; case wxPENSTYLE_CROSSDIAG_HATCH: wxFALLTHROUGH; case wxPENSTYLE_FDIAGONAL_HATCH: wxFALLTHROUGH; case wxPENSTYLE_CROSS_HATCH: wxFALLTHROUGH; case wxPENSTYLE_HORIZONTAL_HATCH: wxFALLTHROUGH; case wxPENSTYLE_VERTICAL_HATCH: return D2D1_DASH_STYLE_SOLID; } wxFAIL_MSG("unknown pen style"); return D2D1_DASH_STYLE_SOLID; } D2D1_COLOR_F wxD2DConvertColour(wxColour colour) { return D2D1::ColorF( colour.Red() / 255.0f, colour.Green() / 255.0f, colour.Blue() / 255.0f, colour.Alpha() / 255.0f); } D2D1_ANTIALIAS_MODE wxD2DConvertAntialiasMode(wxAntialiasMode antialiasMode) { switch (antialiasMode) { case wxANTIALIAS_NONE: return D2D1_ANTIALIAS_MODE_ALIASED; case wxANTIALIAS_DEFAULT: return D2D1_ANTIALIAS_MODE_PER_PRIMITIVE; } wxFAIL_MSG("unknown antialias mode"); return D2D1_ANTIALIAS_MODE_ALIASED; } #if wxD2D_DEVICE_CONTEXT_SUPPORTED bool wxD2DCompositionModeSupported(wxCompositionMode compositionMode) { if (compositionMode == wxCOMPOSITION_CLEAR || compositionMode == wxCOMPOSITION_INVALID) { return false; } return true; } D2D1_COMPOSITE_MODE wxD2DConvertCompositionMode(wxCompositionMode compositionMode) { switch (compositionMode) { case wxCOMPOSITION_SOURCE: return D2D1_COMPOSITE_MODE_SOURCE_COPY; case wxCOMPOSITION_OVER: return D2D1_COMPOSITE_MODE_SOURCE_OVER; case wxCOMPOSITION_IN: return D2D1_COMPOSITE_MODE_SOURCE_IN; case wxCOMPOSITION_OUT: return D2D1_COMPOSITE_MODE_SOURCE_OUT; case wxCOMPOSITION_ATOP: return D2D1_COMPOSITE_MODE_SOURCE_ATOP; case wxCOMPOSITION_DEST_OVER: return D2D1_COMPOSITE_MODE_DESTINATION_OVER; case wxCOMPOSITION_DEST_IN: return D2D1_COMPOSITE_MODE_DESTINATION_IN; case wxCOMPOSITION_DEST_OUT: return D2D1_COMPOSITE_MODE_DESTINATION_OUT; case wxCOMPOSITION_DEST_ATOP: return D2D1_COMPOSITE_MODE_DESTINATION_ATOP; case wxCOMPOSITION_XOR: return D2D1_COMPOSITE_MODE_XOR; case wxCOMPOSITION_ADD: return D2D1_COMPOSITE_MODE_PLUS; // unsupported composition modes case wxCOMPOSITION_DEST: wxFALLTHROUGH; case wxCOMPOSITION_CLEAR: wxFALLTHROUGH; case wxCOMPOSITION_INVALID: return D2D1_COMPOSITE_MODE_SOURCE_COPY; } wxFAIL_MSG("unknown composition mode"); return D2D1_COMPOSITE_MODE_SOURCE_COPY; } #endif // wxD2D_DEVICE_CONTEXT_SUPPORTED // Direct2D 1.1 introduces a new enum for specifying the interpolation quality // which is only used with the ID2D1DeviceContext::DrawImage method. #if wxD2D_DEVICE_CONTEXT_SUPPORTED D2D1_INTERPOLATION_MODE wxD2DConvertInterpolationMode(wxInterpolationQuality interpolationQuality) { switch (interpolationQuality) { case wxINTERPOLATION_DEFAULT: wxFALLTHROUGH; case wxINTERPOLATION_NONE: wxFALLTHROUGH; case wxINTERPOLATION_FAST: return D2D1_INTERPOLATION_MODE_NEAREST_NEIGHBOR; case wxINTERPOLATION_GOOD: return D2D1_INTERPOLATION_MODE_LINEAR; case wxINTERPOLATION_BEST: return D2D1_INTERPOLATION_MODE_CUBIC; } wxFAIL_MSG("unknown interpolation quality"); return D2D1_INTERPOLATION_MODE_NEAREST_NEIGHBOR; } #endif // wxD2D_DEVICE_CONTEXT_SUPPORTED D2D1_BITMAP_INTERPOLATION_MODE wxD2DConvertBitmapInterpolationMode(wxInterpolationQuality interpolationQuality) { switch (interpolationQuality) { case wxINTERPOLATION_DEFAULT: wxFALLTHROUGH; case wxINTERPOLATION_NONE: wxFALLTHROUGH; case wxINTERPOLATION_FAST: wxFALLTHROUGH; case wxINTERPOLATION_GOOD: return D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR; case wxINTERPOLATION_BEST: return D2D1_BITMAP_INTERPOLATION_MODE_LINEAR; } wxFAIL_MSG("unknown interpolation quality"); return D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR; } D2D1_RECT_F wxD2DConvertRect(const wxRect& rect) { return D2D1::RectF(rect.GetLeft(), rect.GetTop(), rect.GetRight(), rect.GetBottom()); } wxCOMPtr wxD2DConvertRegionToGeometry(ID2D1Factory* direct2dFactory, const wxRegion& region) { // Build the array of geometries HRESULT hr; int i; ID2D1Geometry** geometries; int rectCount; if ( region.IsEmpty() ) { // Empty region is skipped by iterator // so we have to create it in a special way. rectCount = 1; geometries = new ID2D1Geometry*[rectCount]; geometries[0] = NULL; hr = direct2dFactory->CreateRectangleGeometry( D2D1::RectF(0.0F, 0.0F, 0.0F, 0.0F), (ID2D1RectangleGeometry**)(&geometries[0])); wxFAILED_HRESULT_MSG(hr); } else { // Count the number of rectangles which compose the region wxRegionIterator regionIterator(region); rectCount = 0; while(regionIterator++) rectCount++; geometries = new ID2D1Geometry*[rectCount]; regionIterator.Reset(region); i = 0; while(regionIterator) { geometries[i] = NULL; wxRect rect = regionIterator.GetRect(); rect.SetWidth(rect.GetWidth() + 1); rect.SetHeight(rect.GetHeight() + 1); hr = direct2dFactory->CreateRectangleGeometry( wxD2DConvertRect(rect), (ID2D1RectangleGeometry**)(&geometries[i])); wxFAILED_HRESULT_MSG(hr); i++; ++regionIterator; } } // Create a geometry group to hold all the rectangles wxCOMPtr resultGeometry; hr = direct2dFactory->CreateGeometryGroup( D2D1_FILL_MODE_WINDING, geometries, rectCount, &resultGeometry); wxFAILED_HRESULT_MSG(hr); // Cleanup temporaries for (i = 0; i < rectCount; ++i) { geometries[i]->Release(); } delete[] geometries; return wxCOMPtr(resultGeometry); } class wxD2DOffsetHelper { public: wxD2DOffsetHelper(wxGraphicsContext* g) : m_context(g) { if (m_context->ShouldOffset()) { m_context->Translate(0.5, 0.5); } } ~wxD2DOffsetHelper() { if (m_context->ShouldOffset()) { m_context->Translate(-0.5, -0.5); } } private: wxGraphicsContext* m_context; }; bool operator==(const D2D1::Matrix3x2F& lhs, const D2D1::Matrix3x2F& rhs) { return lhs._11 == rhs._11 && lhs._12 == rhs._12 && lhs._21 == rhs._21 && lhs._22 == rhs._22 && lhs._31 == rhs._31 && lhs._32 == rhs._32; } //----------------------------------------------------------------------------- // wxD2DMatrixData declaration //----------------------------------------------------------------------------- class wxD2DMatrixData : public wxGraphicsMatrixData { public: wxD2DMatrixData(wxGraphicsRenderer* renderer); wxD2DMatrixData(wxGraphicsRenderer* renderer, const D2D1::Matrix3x2F& matrix); virtual wxGraphicsObjectRefData* Clone() const wxOVERRIDE; void Concat(const wxGraphicsMatrixData* t) wxOVERRIDE; void Set(wxDouble a = 1.0, wxDouble b = 0.0, wxDouble c = 0.0, wxDouble d = 1.0, wxDouble tx = 0.0, wxDouble ty = 0.0) wxOVERRIDE; void Get(wxDouble* a = NULL, wxDouble* b = NULL, wxDouble* c = NULL, wxDouble* d = NULL, wxDouble* tx = NULL, wxDouble* ty = NULL) const wxOVERRIDE; void Invert() wxOVERRIDE; bool IsEqual(const wxGraphicsMatrixData* t) const wxOVERRIDE; bool IsIdentity() const wxOVERRIDE; void Translate(wxDouble dx, wxDouble dy) wxOVERRIDE; void Scale(wxDouble xScale, wxDouble yScale) wxOVERRIDE; void Rotate(wxDouble angle) wxOVERRIDE; void TransformPoint(wxDouble* x, wxDouble* y) const wxOVERRIDE; void TransformDistance(wxDouble* dx, wxDouble* dy) const wxOVERRIDE; void* GetNativeMatrix() const wxOVERRIDE; D2D1::Matrix3x2F GetMatrix3x2F() const; private: D2D1::Matrix3x2F m_matrix; }; //----------------------------------------------------------------------------- // wxD2DMatrixData implementation //----------------------------------------------------------------------------- wxD2DMatrixData::wxD2DMatrixData(wxGraphicsRenderer* renderer) : wxGraphicsMatrixData(renderer) { m_matrix = D2D1::Matrix3x2F::Identity(); } wxD2DMatrixData::wxD2DMatrixData(wxGraphicsRenderer* renderer, const D2D1::Matrix3x2F& matrix) : wxGraphicsMatrixData(renderer), m_matrix(matrix) { } wxGraphicsObjectRefData* wxD2DMatrixData::Clone() const { return new wxD2DMatrixData(GetRenderer(), m_matrix); } void wxD2DMatrixData::Concat(const wxGraphicsMatrixData* t) { // Elements of resulting matrix are modified in-place in SetProduct() // so multiplied matrices cannot be the instances of the resulting matrix. // Note that parameter matrix (t) is the multiplicand. const D2D1::Matrix3x2F m1(static_cast(t)->m_matrix); const D2D1::Matrix3x2F m2(m_matrix); m_matrix.SetProduct(m1, m2); } void wxD2DMatrixData::Set(wxDouble a, wxDouble b, wxDouble c, wxDouble d, wxDouble tx, wxDouble ty) { m_matrix._11 = a; m_matrix._12 = b; m_matrix._21 = c; m_matrix._22 = d; m_matrix._31 = tx; m_matrix._32 = ty; } void wxD2DMatrixData::Get(wxDouble* a, wxDouble* b, wxDouble* c, wxDouble* d, wxDouble* tx, wxDouble* ty) const { *a = m_matrix._11; *b = m_matrix._12; *c = m_matrix._21; *d = m_matrix._22; *tx = m_matrix._31; *ty = m_matrix._32; } void wxD2DMatrixData::Invert() { m_matrix.Invert(); } bool wxD2DMatrixData::IsEqual(const wxGraphicsMatrixData* t) const { return m_matrix == static_cast(t)->m_matrix; } bool wxD2DMatrixData::IsIdentity() const { return m_matrix.IsIdentity(); } void wxD2DMatrixData::Translate(wxDouble dx, wxDouble dy) { m_matrix = D2D1::Matrix3x2F::Translation(dx, dy) * m_matrix; } void wxD2DMatrixData::Scale(wxDouble xScale, wxDouble yScale) { m_matrix = D2D1::Matrix3x2F::Scale(xScale, yScale) * m_matrix; } void wxD2DMatrixData::Rotate(wxDouble angle) { m_matrix = D2D1::Matrix3x2F::Rotation(wxRadToDeg(angle)) * m_matrix; } void wxD2DMatrixData::TransformPoint(wxDouble* x, wxDouble* y) const { D2D1_POINT_2F result = m_matrix.TransformPoint(D2D1::Point2F(*x, *y)); *x = result.x; *y = result.y; } void wxD2DMatrixData::TransformDistance(wxDouble* dx, wxDouble* dy) const { D2D1::Matrix3x2F noTranslationMatrix = m_matrix; noTranslationMatrix._31 = 0; noTranslationMatrix._32 = 0; D2D1_POINT_2F result = noTranslationMatrix.TransformPoint(D2D1::Point2F(*dx, *dy)); *dx = result.x; *dy = result.y; } void* wxD2DMatrixData::GetNativeMatrix() const { return (void*)&m_matrix; } D2D1::Matrix3x2F wxD2DMatrixData::GetMatrix3x2F() const { return m_matrix; } const wxD2DMatrixData* wxGetD2DMatrixData(const wxGraphicsMatrix& matrix) { return static_cast(matrix.GetMatrixData()); } //----------------------------------------------------------------------------- // wxD2DPathData declaration //----------------------------------------------------------------------------- bool operator==(const D2D1_POINT_2F& lhs, const D2D1_POINT_2F& rhs) { return lhs.x == rhs.x && lhs.y == rhs.y; } class wxD2DPathData : public wxGraphicsPathData { public : // ID2D1PathGeometry objects are device-independent resources created // from a ID2D1Factory. This means we can safely create the resource outside // (the wxD2DRenderer handles this) and store it here since it never gets // thrown away by the GPU. wxD2DPathData(wxGraphicsRenderer* renderer, ID2D1Factory* d2dFactory); ~wxD2DPathData(); void SetFillMode(D2D1_FILL_MODE fillMode) { m_fillMode = fillMode; } D2D1_FILL_MODE GetFillMode() const { return m_fillMode; } ID2D1PathGeometry* GetPathGeometry(); // This closes the geometry sink, ensuring all the figures are stored inside // the ID2D1PathGeometry. Calling this method is required before any draw operation // involving a path. void Flush(); wxGraphicsObjectRefData* Clone() const wxOVERRIDE; // begins a new subpath at (x,y) void MoveToPoint(wxDouble x, wxDouble y) wxOVERRIDE; // adds a straight line from the current point to (x,y) void AddLineToPoint(wxDouble x, wxDouble y) wxOVERRIDE; // adds a cubic Bezier curve from the current point, using two control points and an end point void AddCurveToPoint(wxDouble cx1, wxDouble cy1, wxDouble cx2, wxDouble cy2, wxDouble x, wxDouble y) wxOVERRIDE; // adds an arc of a circle centering at (x,y) with radius (r) from startAngle to endAngle void AddArc(wxDouble x, wxDouble y, wxDouble r, wxDouble startAngle, wxDouble endAngle, bool clockwise) wxOVERRIDE; // gets the last point of the current path, (0,0) if not yet set void GetCurrentPoint(wxDouble* x, wxDouble* y) const wxOVERRIDE; // adds another path void AddPath(const wxGraphicsPathData* path) wxOVERRIDE; // closes the current sub-path void CloseSubpath() wxOVERRIDE; // returns the native path void* GetNativePath() const wxOVERRIDE; // give the native path returned by GetNativePath() back (there might be some deallocations necessary) void UnGetNativePath(void* WXUNUSED(p)) const wxOVERRIDE {} // transforms each point of this path by the matrix void Transform(const wxGraphicsMatrixData* matrix) wxOVERRIDE; // gets the bounding box enclosing all points (possibly including control points) void GetBox(wxDouble* x, wxDouble* y, wxDouble* w, wxDouble *h) const wxOVERRIDE; bool Contains(wxDouble x, wxDouble y, wxPolygonFillMode fillStyle = wxODDEVEN_RULE) const wxOVERRIDE; // appends an ellipsis as a new closed subpath fitting the passed rectangle void AddCircle(wxDouble x, wxDouble y, wxDouble r) wxOVERRIDE; // appends an ellipse void AddEllipse(wxDouble x, wxDouble y, wxDouble w, wxDouble h) wxOVERRIDE; private: void EnsureGeometryOpen(); void EnsureSinkOpen(); void EnsureFigureOpen(const D2D1_POINT_2F& pos); void EndFigure(D2D1_FIGURE_END figureEnd); ID2D1Geometry* GetFullGeometry(D2D1_FILL_MODE fillMode) const; bool IsEmpty() const; bool IsStateSafeForFlush() const; struct GeometryStateData { bool m_isCurrentPointSet; D2D1_POINT_2F m_currentPoint; bool m_isFigureOpen; D2D1_POINT_2F m_figureStart; bool m_isFigureLogStartSet; D2D1_POINT_2F m_figureLogStart; }; void SaveGeometryState(GeometryStateData& data) const; void RestoreGeometryState(const GeometryStateData& data); private : wxCOMPtr m_pathGeometry; wxCOMPtr m_geometrySink; wxCOMPtr m_direct2dfactory; mutable wxCOMPtr m_combinedGeometry; wxVector m_pTransformedGeometries; bool m_currentPointSet; D2D1_POINT_2F m_currentPoint; bool m_figureOpened; D2D1_POINT_2F m_figureStart; bool m_figureLogStartSet; D2D1_POINT_2F m_figureLogStart; bool m_geometryWritable; D2D1_FILL_MODE m_fillMode; }; //----------------------------------------------------------------------------- // wxD2DPathData implementation //----------------------------------------------------------------------------- wxD2DPathData::wxD2DPathData(wxGraphicsRenderer* renderer, ID2D1Factory* d2dFactory) : wxGraphicsPathData(renderer), m_direct2dfactory(d2dFactory), m_currentPointSet(false), m_currentPoint(D2D1::Point2F(0.0f, 0.0f)), m_figureOpened(false), m_figureStart(D2D1::Point2F(0.0f, 0.0f)), m_figureLogStartSet(false), m_figureLogStart(D2D1::Point2F(0.0f, 0.0f)), m_geometryWritable(true), m_fillMode(D2D1_FILL_MODE_ALTERNATE) { m_direct2dfactory->CreatePathGeometry(&m_pathGeometry); // To properly initialize path geometry there is also // necessary to open at least once its geometry sink. m_pathGeometry->Open(&m_geometrySink); } wxD2DPathData::~wxD2DPathData() { Flush(); for( size_t i = 0; i < m_pTransformedGeometries.size(); i++ ) { m_pTransformedGeometries[i]->Release(); } } ID2D1PathGeometry* wxD2DPathData::GetPathGeometry() { return m_pathGeometry; } wxD2DPathData::wxGraphicsObjectRefData* wxD2DPathData::Clone() const { wxD2DPathData* newPathData = new wxD2DPathData(GetRenderer(), m_direct2dfactory); newPathData->EnsureGeometryOpen(); // Only geometry with closed sink can be // transferred to another geometry object with // ID2D1PathGeometry::Stream() so we have to check // if actual transfer succeeded. // Transfer geometry to the new geometry sink. HRESULT hr = m_pathGeometry->Stream(newPathData->m_geometrySink); wxASSERT_MSG( SUCCEEDED(hr), wxS("Current geometry is in invalid state") ); if ( FAILED(hr) ) { delete newPathData; return NULL; } // Copy the collection of transformed geometries. ID2D1TransformedGeometry* pTransformedGeometry; for ( size_t i = 0; i < m_pTransformedGeometries.size(); i++ ) { pTransformedGeometry = NULL; hr = m_direct2dfactory->CreateTransformedGeometry( m_pTransformedGeometries[i], D2D1::Matrix3x2F::Identity(), &pTransformedGeometry); wxASSERT_MSG( SUCCEEDED(hr), wxFAILED_HRESULT_MSG(hr) ); newPathData->m_pTransformedGeometries.push_back(pTransformedGeometry); } // Copy positional data. GeometryStateData curState; SaveGeometryState(curState); newPathData->RestoreGeometryState(curState); return newPathData; } void wxD2DPathData::Flush() { if (m_geometrySink != NULL) { if ( m_figureOpened ) { m_geometrySink->EndFigure(D2D1_FIGURE_END_OPEN); m_figureOpened = false; } if( m_geometryWritable ) { HRESULT hr = m_geometrySink->Close(); wxCHECK_HRESULT_RET(hr); m_geometryWritable = false; } } } void wxD2DPathData::EnsureGeometryOpen() { if (!m_geometryWritable) { wxCOMPtr newPathGeometry; HRESULT hr; hr = m_direct2dfactory->CreatePathGeometry(&newPathGeometry); wxCHECK_HRESULT_RET(hr); m_geometrySink.reset(); hr = newPathGeometry->Open(&m_geometrySink); wxCHECK_HRESULT_RET(hr); if (m_pathGeometry != NULL) { hr = m_pathGeometry->Stream(m_geometrySink); wxCHECK_HRESULT_RET(hr); } m_pathGeometry = newPathGeometry; m_geometryWritable = true; } } void wxD2DPathData::EnsureSinkOpen() { EnsureGeometryOpen(); if (m_geometrySink == NULL) { HRESULT hr = m_pathGeometry->Open(&m_geometrySink); wxCHECK_HRESULT_RET(hr); } } void wxD2DPathData::EnsureFigureOpen(const D2D1_POINT_2F& pos) { EnsureSinkOpen(); if (!m_figureOpened) { m_figureStart = pos; m_geometrySink->BeginFigure(m_figureStart, D2D1_FIGURE_BEGIN_FILLED); m_figureOpened = true; m_currentPoint = m_figureStart; } } void wxD2DPathData::EndFigure(D2D1_FIGURE_END figureEnd) { if (m_figureOpened) { // Ensure that sub-path being closed contains at least one point. if( figureEnd == D2D1_FIGURE_END_CLOSED ) m_geometrySink->AddLine(m_currentPoint); if( figureEnd == D2D1_FIGURE_END_OPEN || !m_figureLogStartSet || m_figureLogStart == m_figureStart ) { // If figure will remain open or if its logical startpoint // is not used or if it is the same as the actual // startpoint then we can end the figure in a standard way. m_geometrySink->EndFigure(figureEnd); } else { // If we want to end and close the figure for which // logical startpoint is not the same as actual startpoint // we have to fill the gap between the actual and logical // endpoints on our own. m_geometrySink->AddLine(m_figureLogStart); m_geometrySink->EndFigure(D2D1_FIGURE_END_OPEN); m_figureStart = m_figureLogStart; } m_figureOpened = false; m_figureLogStartSet = false; // If the figure is closed then current point // should be moved to the beginning of the figure. if( figureEnd == D2D1_FIGURE_END_CLOSED ) m_currentPoint = m_figureStart; } } ID2D1Geometry* wxD2DPathData::GetFullGeometry(D2D1_FILL_MODE fillMode) const { // Our final path geometry is represented by geometry group // which contains all transformed geometries plus current geometry. // We have to store pointers to all transformed geometries // as well as pointer to the current geometry in the auxiliary array. const size_t numGeometries = m_pTransformedGeometries.size(); ID2D1Geometry** pGeometries = new ID2D1Geometry*[numGeometries+1]; for( size_t i = 0; i < numGeometries; i++ ) pGeometries[i] = m_pTransformedGeometries[i]; pGeometries[numGeometries] = m_pathGeometry; // And use this array as a source to create geometry group. m_combinedGeometry.reset(); HRESULT hr = m_direct2dfactory->CreateGeometryGroup(fillMode, pGeometries, numGeometries+1, &m_combinedGeometry); wxFAILED_HRESULT_MSG(hr); delete []pGeometries; return m_combinedGeometry; } bool wxD2DPathData::IsEmpty() const { return !m_currentPointSet && !m_figureOpened && m_pTransformedGeometries.size() == 0; } bool wxD2DPathData::IsStateSafeForFlush() const { // Only geometry with not yet started figure // or with started but empty figure can be fully // restored to its initial state after invoking Flush(). if( !m_figureOpened ) return true; D2D1_POINT_2F actFigureStart = m_figureLogStartSet ? m_figureLogStart : m_figureStart; return m_currentPoint == actFigureStart; } void wxD2DPathData::SaveGeometryState(GeometryStateData& data) const { data.m_isFigureOpen = m_figureOpened; data.m_isFigureLogStartSet = m_figureLogStartSet; data.m_isCurrentPointSet = m_currentPointSet; data.m_currentPoint = m_currentPoint; data.m_figureStart = m_figureStart; data.m_figureLogStart = m_figureLogStart; } void wxD2DPathData::RestoreGeometryState(const GeometryStateData& data) { if( data.m_isFigureOpen ) { // If the figure has to be re-started at the startpoint // which is not the current point then we have to start it // physically at the current point but with storing also its // logical startpoint to use it later on to close the figure, // if necessary. // Ending and closing the figure using this proxy startpoint // is only a simulation of regular closure and figure can behave // in a slightly different way than figure closed with physical // startpoint so this action should be avoided if only possible. D2D1_POINT_2F actFigureStart = data.m_isFigureLogStartSet ? data.m_figureLogStart : data.m_figureStart; if ( !(data.m_currentPoint == actFigureStart) ) { m_figureLogStart = actFigureStart; m_figureLogStartSet = true; EnsureFigureOpen(data.m_currentPoint); } else { EnsureFigureOpen(actFigureStart); } } else { m_figureOpened = false; } m_currentPointSet = data.m_isCurrentPointSet; m_currentPoint = data.m_isCurrentPointSet ? data.m_currentPoint : D2D1::Point2F(0.0F, 0.0F); } void wxD2DPathData::MoveToPoint(wxDouble x, wxDouble y) { // Close current sub-path (leaving the figure as is). EndFigure(D2D1_FIGURE_END_OPEN); // Store new current point m_currentPoint = D2D1::Point2F(x, y); m_currentPointSet = true; } // adds a straight line from the current point to (x,y) void wxD2DPathData::AddLineToPoint(wxDouble x, wxDouble y) { // If current point is not yet set then // this function should behave as MoveToPoint. if( !m_currentPointSet ) { MoveToPoint(x, y); return; } EnsureFigureOpen(m_currentPoint); m_geometrySink->AddLine(D2D1::Point2F(x, y)); m_currentPoint = D2D1::Point2F(x, y); } // adds a cubic Bezier curve from the current point, using two control points and an end point void wxD2DPathData::AddCurveToPoint(wxDouble cx1, wxDouble cy1, wxDouble cx2, wxDouble cy2, wxDouble x, wxDouble y) { // If no current point is set then this function should behave // as if preceded by a call to MoveToPoint(cx1, cy1). if( !m_currentPointSet ) MoveToPoint(cx1, cy1); EnsureFigureOpen(m_currentPoint); D2D1_BEZIER_SEGMENT bezierSegment = { { (FLOAT)cx1, (FLOAT)cy1 }, { (FLOAT)cx2, (FLOAT)cy2 }, { (FLOAT)x, (FLOAT)y } }; m_geometrySink->AddBezier(bezierSegment); m_currentPoint = D2D1::Point2F(x, y); } // adds an arc of a circle centering at (x,y) with radius (r) from startAngle to endAngle void wxD2DPathData::AddArc(wxDouble x, wxDouble y, wxDouble r, wxDouble startAngle, wxDouble endAngle, bool clockwise) { double angle; // For the sake of compatibility normalize angles the same way // as it is done in Cairo. if ( clockwise ) { // If endAngle < startAngle it needs to be progressively // increased by 2*M_PI until endAngle > startAngle. if ( endAngle < startAngle ) { while ( endAngle <= startAngle ) { endAngle += 2.0*M_PI; } } angle = endAngle - startAngle; } else { // If endAngle > startAngle it needs to be progressively // decreased by 2*M_PI until endAngle < startAngle. if ( endAngle > startAngle ) { while ( endAngle >= startAngle ) { endAngle -= 2.0*M_PI; } } angle = startAngle - endAngle; } wxPoint2DDouble start = wxPoint2DDouble(cos(startAngle) * r, sin(startAngle) * r); wxPoint2DDouble end = wxPoint2DDouble(cos(endAngle) * r, sin(endAngle) * r); // To ensure compatibility with Cairo an initial // line segment to the beginning of the arc needs // to be added to the path. if ( m_figureOpened ) { AddLineToPoint(start.m_x + x, start.m_y + y); } else if ( m_currentPointSet ) { EnsureFigureOpen(m_currentPoint); AddLineToPoint(start.m_x + x, start.m_y + y); } else { MoveToPoint(start.m_x + x, start.m_y + y); EnsureFigureOpen(m_currentPoint); } D2D1_SWEEP_DIRECTION sweepDirection = clockwise ? D2D1_SWEEP_DIRECTION_CLOCKWISE : D2D1_SWEEP_DIRECTION_COUNTER_CLOCKWISE; D2D1_SIZE_F size = D2D1::SizeF((FLOAT)r, (FLOAT)r); if ( angle >= 2.0*M_PI ) { // In addition to arc we need to draw full circle(s). // Remarks: // 1. Parity of the number of the circles has to be // preserved because this matters when path would be // filled with wxODDEVEN_RULE flag set (using // D2D1_FILL_MODE_ALTERNATE mode) when number of the // edges is counted. // 2. ID2D1GeometrySink::AddArc() doesn't work // with 360-degree arcs so we need to construct // the circle from two halves. D2D1_ARC_SEGMENT circleSegment1 = { D2D1::Point2((FLOAT)(x - start.m_x), (FLOAT)(y - start.m_y)), // end point size, // size 0.0f, // rotation sweepDirection, // sweep direction D2D1_ARC_SIZE_SMALL // arc size }; D2D1_ARC_SEGMENT circleSegment2 = { D2D1::Point2((FLOAT)(x + start.m_x), (FLOAT)(y + start.m_y)), // end point size, // size 0.0f, // rotation sweepDirection, // sweep direction D2D1_ARC_SIZE_SMALL // arc size }; int numCircles = (int)(angle / (2.0*M_PI)); numCircles = (numCircles - 1) % 2 + 1; for( int i = 0; i < numCircles; i++ ) { m_geometrySink->AddArc(circleSegment1); m_geometrySink->AddArc(circleSegment2); } // Reduce the angle to [0..2*M_PI) range. angle = fmod(angle, 2.0*M_PI); } D2D1_ARC_SIZE arcSize = angle > M_PI ? D2D1_ARC_SIZE_LARGE : D2D1_ARC_SIZE_SMALL; D2D1_POINT_2F endPoint = D2D1::Point2((FLOAT)(end.m_x + x), (FLOAT)(end.m_y + y)); D2D1_ARC_SEGMENT arcSegment = { endPoint, // end point size, // size 0.0f, // rotation sweepDirection, // sweep direction arcSize // arc size }; m_geometrySink->AddArc(arcSegment); m_currentPoint = endPoint; } // appends an ellipsis as a new closed subpath fitting the passed rectangle void wxD2DPathData::AddCircle(wxDouble x, wxDouble y, wxDouble r) { AddEllipse(x - r, y - r, r * 2, r * 2); } // appends an ellipse void wxD2DPathData::AddEllipse(wxDouble x, wxDouble y, wxDouble w, wxDouble h) { if ( w <= 0.0 || h <= 0.0 ) return; // Calculate radii const wxDouble rx = w / 2.0; const wxDouble ry = h / 2.0; MoveToPoint(x + w, y + ry); // Open new subpath EnsureFigureOpen(m_currentPoint); D2D1_ARC_SEGMENT arcSegmentLower = { D2D1::Point2((FLOAT)(x), (FLOAT)(y + ry)), // end point D2D1::SizeF((FLOAT)(rx), (FLOAT)(ry)), // size 0.0f, D2D1_SWEEP_DIRECTION_CLOCKWISE, D2D1_ARC_SIZE_SMALL }; m_geometrySink->AddArc(arcSegmentLower); D2D1_ARC_SEGMENT arcSegmentUpper = { D2D1::Point2((FLOAT)(x + w), (FLOAT)(y + ry)), // end point D2D1::SizeF((FLOAT)(rx), (FLOAT)(ry)), // size 0.0f, D2D1_SWEEP_DIRECTION_CLOCKWISE, D2D1_ARC_SIZE_SMALL }; m_geometrySink->AddArc(arcSegmentUpper); CloseSubpath(); } // gets the last point of the current path, (0,0) if not yet set void wxD2DPathData::GetCurrentPoint(wxDouble* x, wxDouble* y) const { if (x != NULL) *x = m_currentPoint.x; if (y != NULL) *y = m_currentPoint.y; } // adds another path void wxD2DPathData::AddPath(const wxGraphicsPathData* path) { wxD2DPathData* pathSrc = const_cast(static_cast(path)); // Nothing to do if geometry of appended path is not initialized. if ( pathSrc->m_pathGeometry == NULL || pathSrc->m_geometrySink == NULL ) return; // Because only closed geometry (with closed sink) // can be transferred to another geometry object with // ID2D1PathGeometry::Stream() so we have to close it // before any operation and to re-open afterwards. // Unfortunately, to close the sink it is also necessary // to end the figure it contains, if it was open. // After re-opening the geometry we should also re-start // the figure (if it was open) and restore its state but // it seems there is no straightforward way to do so // if the figure is not empty. // // So, only if appended path has a sub-path closed or // has an empty sub-path open there is possible to restore // its state after appending it to the current path and only // in this case the operation doesn't introduce side effects. // Nothing to do if appended path is empty. if ( pathSrc->IsEmpty() ) return; // Save positional and auxiliary data // of the appended path and its geometry. GeometryStateData curStateSrc; pathSrc->SaveGeometryState(curStateSrc); // Close appended geometry. pathSrc->Flush(); // Close current geometry (leaving the figure as is). Flush(); HRESULT hr; ID2D1TransformedGeometry* pTransformedGeometry = NULL; // Add current geometry to the collection transformed geometries. hr = m_direct2dfactory->CreateTransformedGeometry(m_pathGeometry, D2D1::Matrix3x2F::Identity(), &pTransformedGeometry); wxCHECK_HRESULT_RET(hr); m_pTransformedGeometries.push_back(pTransformedGeometry); // Add to the collection transformed geometries from the appended path. for ( size_t i = 0; i < pathSrc->m_pTransformedGeometries.size(); i++ ) { pTransformedGeometry = NULL; hr = m_direct2dfactory->CreateTransformedGeometry( pathSrc->m_pTransformedGeometries[i], D2D1::Matrix3x2F::Identity(), &pTransformedGeometry); wxCHECK_HRESULT_RET(hr); m_pTransformedGeometries.push_back(pTransformedGeometry); } // Clear and reopen current geometry. m_pathGeometry.reset(); EnsureGeometryOpen(); // Transfer appended geometry to the current geometry sink. hr = pathSrc->m_pathGeometry->Stream(m_geometrySink); wxCHECK_HRESULT_RET(hr); // Apply to the current path positional data from the appended path. // This operation fully sets geometry to the required state // only if it represents geometry without started figure // or with started but empty figure. RestoreGeometryState(curStateSrc); // Reopen appended geometry. pathSrc->EnsureGeometryOpen(); // Restore its positional data. // This operation fully restores geometry to the required state // only if it represents geometry without started figure // or with started but empty figure. pathSrc->RestoreGeometryState(curStateSrc); } // closes the current sub-path void wxD2DPathData::CloseSubpath() { // If we have a sub-path open by call to MoveToPoint(), // which doesn't open a new figure by itself, // we have to open a new figure now to get a required 1-point path. if ( !m_figureOpened && m_currentPointSet ) { EnsureFigureOpen(m_currentPoint); } // Close sub-path and close the figure. if ( m_figureOpened ) { EndFigure(D2D1_FIGURE_END_CLOSED); MoveToPoint(m_figureStart.x, m_figureStart.y); } } void* wxD2DPathData::GetNativePath() const { return GetFullGeometry(GetFillMode()); } void wxD2DPathData::Transform(const wxGraphicsMatrixData* matrix) { // Unfortunately, it looks there is no straightforward way to apply // transformation to the current underlying path geometry // (ID2D1PathGeometry object) "in-place" (ie. transform it and use // for further graphics operations, including next transformations too). // Some simple methods offered by D2D are not useful for these purposes: // 1. ID2D1Factory::CreateTransformedGeometry() converts ID2D1PathGeometry // object to ID2D1TransformedGeometry object but ID2D1TransformedGeometry // inherits from ID2D1Geometry (not from ID2D1PathGeometry) // and hence cannot be used for further path operations. // 2. ID2D1Geometry::CombineWithGeometry() which could be used to get final // path geometry by combining empty geometry with transformed geometry // doesn't offer any combine mode which would produce a "sum" of geometries // (D2D1_COMBINE_MODE_UNION produces kind of outline). Moreover the result // is stored in ID2D1SimplifiedGeometrySink not in ID2DGeometrySink. // So, it seems that ability to transform the wxGraphicsPath // (several times) and still use it after this operation(s) // can be achieved (only?) by using a geometry group object // (ID2D1GeometryGroup) this way: // 1. After applying transformation to the current path geometry with // ID2D1Factory::CreateTransformedGeometry() the result is stored // in the collection of transformed geometries (an auxiliary array) // and after that a new (empty) geometry is open (in the same state // as just closed one) and this geometry is used as a current one // for further graphics operations. // 2. Since above steps are done at every transformation so our effective // geometry will be a superposition of all previously transformed // geometries stored in the collection (array) and the current // operational geometry. // 3. If there is necessary to use this combined effective geometry // in any operation then ID2D1GeometryGroup created with // ID2D1Factory::CreateGeometryGroup() from the collection // of stored geometries will act as a proxy geometry. const D2D1::Matrix3x2F* m = static_cast(matrix->GetNativeMatrix()); // Save current positional data. GeometryStateData curState; SaveGeometryState(curState); // We need to close the geometry what requires also to end a figure // (if started). This ended figure should be re-started in its initial // state when all path processing is done but due to the Direct2D // constraints this can be fully done only if open figure was empty. // So, Transform() can be safely called if path doesn't contain the open // sub-path or if open sub-path is empty. // Close current geometry. Flush(); HRESULT hr; ID2D1TransformedGeometry* pTransformedGeometry; // Apply given transformation to all previously stored geometries too. for( size_t i = 0; i < m_pTransformedGeometries.size(); i++ ) { pTransformedGeometry = NULL; hr = m_direct2dfactory->CreateTransformedGeometry(m_pTransformedGeometries[i], m, &pTransformedGeometry); wxCHECK_HRESULT_RET(hr); m_pTransformedGeometries[i]->Release(); m_pTransformedGeometries[i] = pTransformedGeometry; } // Transform current geometry and add the result // to the collection of transformed geometries. pTransformedGeometry = NULL; hr = m_direct2dfactory->CreateTransformedGeometry(m_pathGeometry, m, &pTransformedGeometry); wxCHECK_HRESULT_RET(hr); m_pTransformedGeometries.push_back(pTransformedGeometry); // Clear and reopen current geometry. m_pathGeometry.reset(); EnsureGeometryOpen(); // Restore the figure with transformed positional data. // This operation fully restores geometry to the required state // only if IsStateSafeForFlush() returns true. curState.m_currentPoint = m->TransformPoint(curState.m_currentPoint); curState.m_figureLogStart = m->TransformPoint(curState.m_figureLogStart); curState.m_figureStart = m->TransformPoint(curState.m_figureStart); RestoreGeometryState(curState); } void wxD2DPathData::GetBox(wxDouble* x, wxDouble* y, wxDouble* w, wxDouble *h) const { D2D1_RECT_F bounds; ID2D1Geometry *curGeometry = GetFullGeometry(GetFillMode()); HRESULT hr = curGeometry->GetBounds(D2D1::Matrix3x2F::Identity(), &bounds); wxCHECK_HRESULT_RET(hr); // Check if bounds are empty if ( bounds.left > bounds.right ) { bounds.left = bounds.top = bounds.right = bounds.bottom = 0.0F; } if (x) *x = bounds.left; if (y) *y = bounds.top; if (w) *w = bounds.right - bounds.left; if (h) *h = bounds.bottom - bounds.top; } bool wxD2DPathData::Contains(wxDouble x, wxDouble y, wxPolygonFillMode fillStyle) const { BOOL result; D2D1_FILL_MODE fillMode = (fillStyle == wxODDEVEN_RULE) ? D2D1_FILL_MODE_ALTERNATE : D2D1_FILL_MODE_WINDING; ID2D1Geometry *curGeometry = GetFullGeometry(fillMode); curGeometry->FillContainsPoint(D2D1::Point2F(x, y), D2D1::Matrix3x2F::Identity(), &result); return result != FALSE; } wxD2DPathData* wxGetD2DPathData(const wxGraphicsPath& path) { return static_cast(path.GetGraphicsData()); } // This utility class is used to read a color value with the format // PBGRA from a byte stream and to write a color back to the stream. // It's used in conjunction with the IWICBitmapSource or IWICBitmap // pixel data to easily read and write color values. struct wxPBGRAColor { wxPBGRAColor(BYTE* stream) : b(*stream), g(*(stream + 1)), r(*(stream + 2)), a(*(stream + 3)) {} wxPBGRAColor(const wxColor& color) : b(color.Blue()), g(color.Green()), r(color.Red()), a(color.Alpha()) {} bool IsBlack() const { return r == 0 && g == 0 && b == 0; } void Write(BYTE* stream) const { *(stream + 0) = b; *(stream + 1) = g; *(stream + 2) = r; *(stream + 3) = a; } BYTE b, g, r, a; }; wxCOMPtr wxCreateWICBitmap(const WXHBITMAP sourceBitmap, bool hasAlpha = false) { HRESULT hr; wxCOMPtr wicBitmap; hr = wxWICImagingFactory()->CreateBitmapFromHBITMAP(sourceBitmap, NULL, WICBitmapUseAlpha, &wicBitmap); wxCHECK2_HRESULT_RET(hr, wxCOMPtr(NULL)); wxCOMPtr converter; hr = wxWICImagingFactory()->CreateFormatConverter(&converter); wxCHECK2_HRESULT_RET(hr, wxCOMPtr(NULL)); WICPixelFormatGUID pixelFormat = hasAlpha ? GUID_WICPixelFormat32bppPBGRA : GUID_WICPixelFormat32bppBGR; hr = converter->Initialize( wicBitmap, pixelFormat, WICBitmapDitherTypeNone, NULL, 0.f, WICBitmapPaletteTypeMedianCut); wxCHECK2_HRESULT_RET(hr, wxCOMPtr(NULL)); return wxCOMPtr(converter); } wxCOMPtr wxCreateWICBitmap(const wxBitmap& sourceBitmap, bool hasAlpha = false) { return wxCreateWICBitmap(sourceBitmap.GetHBITMAP(), hasAlpha); } // WIC Bitmap Source for creating hatch patterned bitmaps class wxHatchBitmapSource : public IWICBitmapSource { public: wxHatchBitmapSource(wxBrushStyle brushStyle, const wxColor& color) : m_brushStyle(brushStyle), m_color(color), m_refCount(0l) { } virtual ~wxHatchBitmapSource() {} HRESULT STDMETHODCALLTYPE GetSize(__RPC__out UINT *width, __RPC__out UINT *height) wxOVERRIDE { if (width != NULL) *width = 8; if (height != NULL) *height = 8; return S_OK; } HRESULT STDMETHODCALLTYPE GetPixelFormat(__RPC__out WICPixelFormatGUID *pixelFormat) wxOVERRIDE { if (pixelFormat != NULL) *pixelFormat = GUID_WICPixelFormat32bppPBGRA; return S_OK; } HRESULT STDMETHODCALLTYPE GetResolution(__RPC__out double *dpiX, __RPC__out double *dpiY) wxOVERRIDE { if (dpiX != NULL) *dpiX = 96.0; if (dpiY != NULL) *dpiY = 96.0; return S_OK; } HRESULT STDMETHODCALLTYPE CopyPalette(__RPC__in_opt IWICPalette* WXUNUSED(palette)) wxOVERRIDE { return WINCODEC_ERR_PALETTEUNAVAILABLE; } HRESULT STDMETHODCALLTYPE CopyPixels( const WICRect* WXUNUSED(prc), UINT WXUNUSED(stride), UINT WXUNUSED(bufferSize), BYTE *buffer) wxOVERRIDE { // patterns are encoded in a bit map of size 8 x 8 static const unsigned char BDIAGONAL_PATTERN[8] = { 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80 }; static const unsigned char FDIAGONAL_PATTERN[8] = { 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 }; static const unsigned char CROSSDIAG_PATTERN[8] = { 0x81, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x81 }; static const unsigned char CROSS_PATTERN[8] = { 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0xFF }; static const unsigned char HORIZONTAL_PATTERN[8] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF }; static const unsigned char VERTICAL_PATTERN[8] = { 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01 }; switch (m_brushStyle) { case wxBRUSHSTYLE_BDIAGONAL_HATCH: CopyPattern(buffer, BDIAGONAL_PATTERN); break; case wxBRUSHSTYLE_CROSSDIAG_HATCH: CopyPattern(buffer, CROSSDIAG_PATTERN); break; case wxBRUSHSTYLE_FDIAGONAL_HATCH: CopyPattern(buffer, FDIAGONAL_PATTERN); break; case wxBRUSHSTYLE_CROSS_HATCH: CopyPattern(buffer, CROSS_PATTERN); break; case wxBRUSHSTYLE_HORIZONTAL_HATCH: CopyPattern(buffer, HORIZONTAL_PATTERN); break; case wxBRUSHSTYLE_VERTICAL_HATCH: CopyPattern(buffer, VERTICAL_PATTERN); break; default: break; } return S_OK; } // Implementations adapted from: "Implementing IUnknown in C++" // http://msdn.microsoft.com/en-us/library/office/cc839627%28v=office.15%29.aspx HRESULT STDMETHODCALLTYPE QueryInterface(REFIID referenceId, void** object) wxOVERRIDE { if (!object) { return E_INVALIDARG; } *object = NULL; if (referenceId == IID_IUnknown || referenceId == wxIID_IWICBitmapSource) { *object = (LPVOID)this; AddRef(); return NOERROR; } return E_NOINTERFACE; } ULONG STDMETHODCALLTYPE AddRef(void) wxOVERRIDE { InterlockedIncrement(&m_refCount); return m_refCount; } ULONG STDMETHODCALLTYPE Release(void) wxOVERRIDE { wxCHECK_MSG(m_refCount > 0, 0, "Unbalanced number of calls to Release"); ULONG refCount = InterlockedDecrement(&m_refCount); if (m_refCount == 0) { delete this; } return refCount; } private: // Copies an 8x8 bit pattern to a PBGRA byte buffer void CopyPattern(BYTE* buffer, const unsigned char* pattern) const { static const wxPBGRAColor transparent(wxTransparentColour); int k = 0; for (int i = 0; i < 8; ++i) { for (int j = 7; j >= 0; --j) { bool isColorBit = (pattern[i] & (1 << j)) > 0; (isColorBit ? m_color : transparent).Write(buffer + k); k += 4; } } } private: // The hatch style produced by this bitmap source const wxBrushStyle m_brushStyle; // The colour of the hatch const wxPBGRAColor m_color; // Internally used to implement IUnknown's reference counting ULONG m_refCount; }; // RAII class hosting a WIC bitmap lock used for writing // pixel data to a WICBitmap class wxBitmapPixelWriteLock { public: wxBitmapPixelWriteLock(IWICBitmap* bitmap) { // Retrieve the size of the bitmap UINT w, h; bitmap->GetSize(&w, &h); WICRect lockSize = {0, 0, (INT)w, (INT)h}; // Obtain a bitmap lock for exclusive write bitmap->Lock(&lockSize, WICBitmapLockWrite, &m_pixelLock); } IWICBitmapLock* GetLock() { return m_pixelLock; } private: wxCOMPtr m_pixelLock; }; class wxD2DBitmapResourceHolder : public wxD2DResourceHolder { public: wxD2DBitmapResourceHolder(const wxBitmap& sourceBitmap) : m_sourceBitmap(sourceBitmap) { } const wxBitmap& GetSourceBitmap() const { return m_sourceBitmap; } protected: void DoAcquireResource() wxOVERRIDE { ID2D1RenderTarget* renderTarget = GetContext(); HRESULT hr; if(m_sourceBitmap.GetMask()) { int w = m_sourceBitmap.GetWidth(); int h = m_sourceBitmap.GetHeight(); wxCOMPtr colorBitmap = wxCreateWICBitmap(m_sourceBitmap); wxCOMPtr maskBitmap = wxCreateWICBitmap(m_sourceBitmap.GetMask()->GetMaskBitmap()); wxCOMPtr resultBitmap; wxWICImagingFactory()->CreateBitmap( w, h, GUID_WICPixelFormat32bppPBGRA, WICBitmapCacheOnLoad, &resultBitmap); BYTE* colorBuffer = new BYTE[4 * w * h]; BYTE* maskBuffer = new BYTE[4 * w * h]; BYTE* resultBuffer; hr = colorBitmap->CopyPixels(NULL, w * 4, 4 * w * h, colorBuffer); hr = maskBitmap->CopyPixels(NULL, w * 4, 4 * w * h, maskBuffer); { wxBitmapPixelWriteLock lock(resultBitmap); UINT bufferSize = 0; hr = lock.GetLock()->GetDataPointer(&bufferSize, &resultBuffer); static const wxPBGRAColor transparentColor(wxTransparentColour); // Create the result bitmap for (int i = 0; i < w * h * 4; i += 4) { wxPBGRAColor color(colorBuffer + i); wxPBGRAColor mask(maskBuffer + i); if (mask.IsBlack()) { transparentColor.Write(resultBuffer + i); } else { color.a = 255; color.Write(resultBuffer + i); } } } hr = renderTarget->CreateBitmapFromWicBitmap(resultBitmap, 0, &m_nativeResource); wxCHECK_HRESULT_RET(hr); delete[] colorBuffer; delete[] maskBuffer; } else { wxCOMPtr bitmapSource = wxCreateWICBitmap(m_sourceBitmap, m_sourceBitmap.HasAlpha()); hr = renderTarget->CreateBitmapFromWicBitmap(bitmapSource, 0, &m_nativeResource); } } private: const wxBitmap m_sourceBitmap; }; //----------------------------------------------------------------------------- // wxD2DBitmapData declaration //----------------------------------------------------------------------------- class wxD2DBitmapData : public wxGraphicsBitmapData, public wxD2DManagedGraphicsData { public: typedef wxD2DBitmapResourceHolder NativeType; wxD2DBitmapData(wxGraphicsRenderer* renderer, const wxBitmap& bitmap) : wxGraphicsBitmapData(renderer), m_bitmapHolder(bitmap) {} wxD2DBitmapData(wxGraphicsRenderer* renderer, const void* pseudoNativeBitmap) : wxGraphicsBitmapData(renderer), m_bitmapHolder(*static_cast(pseudoNativeBitmap)) {} // returns the native representation void* GetNativeBitmap() const wxOVERRIDE; wxCOMPtr GetD2DBitmap(); wxD2DManagedObject* GetManagedObject() wxOVERRIDE { return &m_bitmapHolder; } private: NativeType m_bitmapHolder; }; //----------------------------------------------------------------------------- // wxD2DBitmapData implementation //----------------------------------------------------------------------------- void* wxD2DBitmapData::GetNativeBitmap() const { return (void*)&m_bitmapHolder; } wxCOMPtr wxD2DBitmapData::GetD2DBitmap() { return m_bitmapHolder.GetD2DResource(); } wxD2DBitmapData* wxGetD2DBitmapData(const wxGraphicsBitmap& bitmap) { return static_cast(bitmap.GetRefData()); } // Helper class used to create and safely release a ID2D1GradientStopCollection from wxGraphicsGradientStops class wxD2DGradientStopsHelper : public wxD2DResourceHolder { public: wxD2DGradientStopsHelper(const wxGraphicsGradientStops& gradientStops) { const int stopCount = gradientStops.GetCount(); m_gradientStops.reserve(stopCount); for ( int i = 0; i < stopCount; ++i ) { D2D1_GRADIENT_STOP stop; stop.position = gradientStops.Item(i).GetPosition(); stop.color = wxD2DConvertColour(gradientStops.Item(i).GetColour()); m_gradientStops.push_back(stop); } } protected: void DoAcquireResource() wxOVERRIDE { wxCHECK_RET(!m_gradientStops.empty(), "No gradient stops provided"); HRESULT hr = GetContext()->CreateGradientStopCollection(&m_gradientStops[0], m_gradientStops.size(), D2D1_GAMMA_2_2, D2D1_EXTEND_MODE_CLAMP, &m_nativeResource); wxCHECK_HRESULT_RET(hr); } private: wxVector m_gradientStops; }; template class wxD2DBrushResourceHolder : public wxD2DResourceHolder { public: wxD2DBrushResourceHolder(const wxBrush& brush) : m_sourceBrush(brush) {} virtual ~wxD2DBrushResourceHolder() {} protected: const wxBrush m_sourceBrush; }; class wxD2DSolidBrushResourceHolder : public wxD2DBrushResourceHolder { public: wxD2DSolidBrushResourceHolder(const wxBrush& brush) : wxD2DBrushResourceHolder(brush) {} protected: void DoAcquireResource() wxOVERRIDE { wxColour colour = m_sourceBrush.GetColour(); HRESULT hr = GetContext()->CreateSolidColorBrush(wxD2DConvertColour(colour), &m_nativeResource); wxCHECK_HRESULT_RET(hr); } }; class wxD2DBitmapBrushResourceHolder : public wxD2DBrushResourceHolder { public: wxD2DBitmapBrushResourceHolder(const wxBrush& brush) : wxD2DBrushResourceHolder(brush) {} protected: void DoAcquireResource() wxOVERRIDE { // TODO: cache this bitmap wxD2DBitmapResourceHolder bitmap(*(m_sourceBrush.GetStipple())); bitmap.Bind(GetManager()); HRESULT result = GetContext()->CreateBitmapBrush( bitmap.GetD2DResource(), D2D1::BitmapBrushProperties( D2D1_EXTEND_MODE_WRAP, D2D1_EXTEND_MODE_WRAP, D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR), &m_nativeResource); wxCHECK_HRESULT_RET(result); } }; class wxD2DHatchBrushResourceHolder : public wxD2DBrushResourceHolder { public: wxD2DHatchBrushResourceHolder(const wxBrush& brush) : wxD2DBrushResourceHolder(brush) {} protected: void DoAcquireResource() wxOVERRIDE { wxCOMPtr hatchBitmapSource(new wxHatchBitmapSource(m_sourceBrush.GetStyle(), m_sourceBrush.GetColour())); wxCOMPtr bitmap; HRESULT hr = GetContext()->CreateBitmapFromWicBitmap(hatchBitmapSource, &bitmap); wxCHECK_HRESULT_RET(hr); hr = GetContext()->CreateBitmapBrush( bitmap, D2D1::BitmapBrushProperties( D2D1_EXTEND_MODE_WRAP, D2D1_EXTEND_MODE_WRAP, D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR), &m_nativeResource); wxCHECK_HRESULT_RET(hr); } }; class wxD2DLinearGradientBrushResourceHolder : public wxD2DResourceHolder { public: struct LinearGradientInfo { const wxDouble x1; const wxDouble y1; const wxDouble x2; const wxDouble y2; const wxGraphicsGradientStops stops; const wxGraphicsMatrix matrix; LinearGradientInfo(wxDouble& x1_, wxDouble& y1_, wxDouble& x2_, wxDouble& y2_, const wxGraphicsGradientStops& stops_, const wxGraphicsMatrix& matrix_) : x1(x1_), y1(y1_), x2(x2_), y2(y2_), stops(stops_), matrix(matrix_) {} }; wxD2DLinearGradientBrushResourceHolder(wxDouble& x1, wxDouble& y1, wxDouble& x2, wxDouble& y2, const wxGraphicsGradientStops& stops, const wxGraphicsMatrix& matrix) : m_linearGradientInfo(x1, y1, x2, y2, stops, matrix) {} protected: void DoAcquireResource() wxOVERRIDE { wxD2DGradientStopsHelper helper(m_linearGradientInfo.stops); helper.Bind(GetManager()); HRESULT hr = GetContext()->CreateLinearGradientBrush( D2D1::LinearGradientBrushProperties( D2D1::Point2F(m_linearGradientInfo.x1, m_linearGradientInfo.y1), D2D1::Point2F(m_linearGradientInfo.x2, m_linearGradientInfo.y2)), helper.GetD2DResource(), &m_nativeResource); wxCHECK_HRESULT_RET(hr); if (! m_linearGradientInfo.matrix.IsNull()) { D2D1::Matrix3x2F matrix = wxGetD2DMatrixData(m_linearGradientInfo.matrix)->GetMatrix3x2F(); matrix.Invert(); m_nativeResource->SetTransform(matrix); } } private: const LinearGradientInfo m_linearGradientInfo; }; class wxD2DRadialGradientBrushResourceHolder : public wxD2DResourceHolder { public: struct RadialGradientInfo { const wxDouble x1; const wxDouble y1; const wxDouble x2; const wxDouble y2; const wxDouble radius; const wxGraphicsGradientStops stops; const wxGraphicsMatrix matrix; RadialGradientInfo(wxDouble x1_, wxDouble y1_, wxDouble x2_, wxDouble y2_, wxDouble r, const wxGraphicsGradientStops& stops_, const wxGraphicsMatrix& matrix_) : x1(x1_), y1(y1_), x2(x2_), y2(y2_), radius(r), stops(stops_), matrix(matrix_) {} }; wxD2DRadialGradientBrushResourceHolder(wxDouble& x1, wxDouble& y1, wxDouble& x2, wxDouble& y2, wxDouble& r, const wxGraphicsGradientStops& stops, const wxGraphicsMatrix& matrix) : m_radialGradientInfo(x1, y1, x2, y2, r, stops, matrix) {} protected: void DoAcquireResource() wxOVERRIDE { wxD2DGradientStopsHelper helper(m_radialGradientInfo.stops); helper.Bind(GetManager()); wxDouble xo = m_radialGradientInfo.x1 - m_radialGradientInfo.x2; wxDouble yo = m_radialGradientInfo.y1 - m_radialGradientInfo.y2; HRESULT hr = GetContext()->CreateRadialGradientBrush( D2D1::RadialGradientBrushProperties( D2D1::Point2F(m_radialGradientInfo.x1, m_radialGradientInfo.y1), D2D1::Point2F(xo, yo), m_radialGradientInfo.radius, m_radialGradientInfo.radius), helper.GetD2DResource(), &m_nativeResource); wxCHECK_HRESULT_RET(hr); if (! m_radialGradientInfo.matrix.IsNull()) { D2D1::Matrix3x2F matrix = wxGetD2DMatrixData(m_radialGradientInfo.matrix)->GetMatrix3x2F(); matrix.Invert(); m_nativeResource->SetTransform(matrix); } } private: const RadialGradientInfo m_radialGradientInfo; }; //----------------------------------------------------------------------------- // wxD2DBrushData declaration //----------------------------------------------------------------------------- class wxD2DBrushData : public wxGraphicsObjectRefData, public wxD2DManagedGraphicsData { public: wxD2DBrushData(wxGraphicsRenderer* renderer, const wxBrush brush); wxD2DBrushData(wxGraphicsRenderer* renderer); void CreateLinearGradientBrush(wxDouble x1, wxDouble y1, wxDouble x2, wxDouble y2, const wxGraphicsGradientStops& stops, const wxGraphicsMatrix& matrix = wxNullGraphicsMatrix); void CreateRadialGradientBrush(wxDouble startX, wxDouble startY, wxDouble endX, wxDouble endY, wxDouble radius, const wxGraphicsGradientStops& stops, const wxGraphicsMatrix& matrix = wxNullGraphicsMatrix); ID2D1Brush* GetBrush() const { return (ID2D1Brush*)(m_brushResourceHolder->GetResource()); } wxD2DManagedObject* GetManagedObject() wxOVERRIDE { return m_brushResourceHolder.get(); } private: wxSharedPtr m_brushResourceHolder; }; //----------------------------------------------------------------------------- // wxD2DBrushData implementation //----------------------------------------------------------------------------- wxD2DBrushData::wxD2DBrushData(wxGraphicsRenderer* renderer, const wxBrush brush) : wxGraphicsObjectRefData(renderer), m_brushResourceHolder(NULL) { if (brush.GetStyle() == wxBRUSHSTYLE_SOLID) { m_brushResourceHolder = new wxD2DSolidBrushResourceHolder(brush); } else if (brush.IsHatch()) { m_brushResourceHolder = new wxD2DHatchBrushResourceHolder(brush); } else { m_brushResourceHolder = new wxD2DBitmapBrushResourceHolder(brush); } } wxD2DBrushData::wxD2DBrushData(wxGraphicsRenderer* renderer) : wxGraphicsObjectRefData(renderer), m_brushResourceHolder(NULL) { } void wxD2DBrushData::CreateLinearGradientBrush( wxDouble x1, wxDouble y1, wxDouble x2, wxDouble y2, const wxGraphicsGradientStops& stops, const wxGraphicsMatrix& matrix) { m_brushResourceHolder = new wxD2DLinearGradientBrushResourceHolder( x1, y1, x2, y2, stops, matrix); } void wxD2DBrushData::CreateRadialGradientBrush( wxDouble startX, wxDouble startY, wxDouble endX, wxDouble endY, wxDouble radius, const wxGraphicsGradientStops& stops, const wxGraphicsMatrix& matrix) { m_brushResourceHolder = new wxD2DRadialGradientBrushResourceHolder( startX, startY, endX, endY, radius, stops, matrix); } wxD2DBrushData* wxGetD2DBrushData(const wxGraphicsBrush& brush) { return static_cast(brush.GetGraphicsData()); } bool wxIsHatchPenStyle(wxPenStyle penStyle) { return penStyle >= wxPENSTYLE_FIRST_HATCH && penStyle <= wxPENSTYLE_LAST_HATCH; } wxBrushStyle wxConvertPenStyleToBrushStyle(wxPenStyle penStyle) { switch(penStyle) { case wxPENSTYLE_BDIAGONAL_HATCH: return wxBRUSHSTYLE_BDIAGONAL_HATCH; case wxPENSTYLE_CROSSDIAG_HATCH: return wxBRUSHSTYLE_CROSSDIAG_HATCH; case wxPENSTYLE_FDIAGONAL_HATCH: return wxBRUSHSTYLE_FDIAGONAL_HATCH; case wxPENSTYLE_CROSS_HATCH: return wxBRUSHSTYLE_CROSS_HATCH; case wxPENSTYLE_HORIZONTAL_HATCH: return wxBRUSHSTYLE_HORIZONTAL_HATCH; case wxPENSTYLE_VERTICAL_HATCH: return wxBRUSHSTYLE_VERTICAL_HATCH; default: break; } return wxBRUSHSTYLE_SOLID; } //----------------------------------------------------------------------------- // wxD2DPenData declaration //----------------------------------------------------------------------------- class wxD2DPenData : public wxGraphicsObjectRefData, public wxD2DManagedGraphicsData { public: wxD2DPenData(wxGraphicsRenderer* renderer, ID2D1Factory* direct2dFactory, const wxGraphicsPenInfo& info); void CreateStrokeStyle(ID2D1Factory* const direct2dfactory); ID2D1Brush* GetBrush(); FLOAT GetWidth(); ID2D1StrokeStyle* GetStrokeStyle(); wxD2DManagedObject* GetManagedObject() wxOVERRIDE { return m_stippleBrush->GetManagedObject(); } private: // We store the original pen description for later when we need to recreate // the device-dependent resources. const wxGraphicsPenInfo m_penInfo; // A stroke style is a device-independent resource. // Describes the caps, miter limit, line join, and dash information. wxCOMPtr m_strokeStyle; // Drawing outlines with Direct2D requires a brush for the color or stipple. wxSharedPtr m_stippleBrush; // The width of the stroke FLOAT m_width; }; //----------------------------------------------------------------------------- // wxD2DPenData implementation //----------------------------------------------------------------------------- wxD2DPenData::wxD2DPenData( wxGraphicsRenderer* renderer, ID2D1Factory* direct2dFactory, const wxGraphicsPenInfo& info) : wxGraphicsObjectRefData(renderer), m_penInfo(info), m_width(info.GetWidth()) { CreateStrokeStyle(direct2dFactory); wxBrush strokeBrush; if (m_penInfo.GetStyle() == wxPENSTYLE_STIPPLE) { strokeBrush.SetStipple(m_penInfo.GetStipple()); strokeBrush.SetStyle(wxBRUSHSTYLE_STIPPLE); } else if(wxIsHatchPenStyle(m_penInfo.GetStyle())) { strokeBrush.SetStyle(wxConvertPenStyleToBrushStyle(m_penInfo.GetStyle())); strokeBrush.SetColour(m_penInfo.GetColour()); } else { strokeBrush.SetColour(m_penInfo.GetColour()); strokeBrush.SetStyle(wxBRUSHSTYLE_SOLID); } switch ( m_penInfo.GetGradientType() ) { case wxGRADIENT_NONE: m_stippleBrush = new wxD2DBrushData(renderer, strokeBrush); break; case wxGRADIENT_LINEAR: m_stippleBrush = new wxD2DBrushData(renderer); m_stippleBrush->CreateLinearGradientBrush( m_penInfo.GetX1(), m_penInfo.GetY1(), m_penInfo.GetX2(), m_penInfo.GetY2(), m_penInfo.GetStops(), m_penInfo.GetMatrix()); break; case wxGRADIENT_RADIAL: m_stippleBrush = new wxD2DBrushData(renderer); m_stippleBrush->CreateRadialGradientBrush( m_penInfo.GetStartX(), m_penInfo.GetStartY(), m_penInfo.GetEndX(), m_penInfo.GetEndY(), m_penInfo.GetRadius(), m_penInfo.GetStops(), m_penInfo.GetMatrix()); break; } } void wxD2DPenData::CreateStrokeStyle(ID2D1Factory* const direct2dfactory) { D2D1_CAP_STYLE capStyle = wxD2DConvertPenCap(m_penInfo.GetCap()); D2D1_LINE_JOIN lineJoin = wxD2DConvertPenJoin(m_penInfo.GetJoin()); D2D1_DASH_STYLE dashStyle = wxD2DConvertPenStyle(m_penInfo.GetStyle()); int dashCount = 0; FLOAT* dashes = NULL; if (dashStyle == D2D1_DASH_STYLE_CUSTOM) { dashCount = m_penInfo.GetDashCount(); dashes = new FLOAT[dashCount]; for (int i = 0; i < dashCount; ++i) { dashes[i] = m_penInfo.GetDash()[i]; } } direct2dfactory->CreateStrokeStyle( D2D1::StrokeStyleProperties(capStyle, capStyle, capStyle, lineJoin, 0, dashStyle, 0.0f), dashes, dashCount, &m_strokeStyle); delete[] dashes; } ID2D1Brush* wxD2DPenData::GetBrush() { return m_stippleBrush->GetBrush(); } FLOAT wxD2DPenData::GetWidth() { return m_width; } ID2D1StrokeStyle* wxD2DPenData::GetStrokeStyle() { return m_strokeStyle; } wxD2DPenData* wxGetD2DPenData(const wxGraphicsPen& pen) { return static_cast(pen.GetGraphicsData()); } class wxD2DFontData : public wxGraphicsObjectRefData { public: wxD2DFontData(wxGraphicsRenderer* renderer, const wxFont& font, const wxRealPoint& dpi, const wxColor& color); wxCOMPtr CreateTextLayout(const wxString& text) const; wxD2DBrushData& GetBrushData() { return m_brushData; } wxCOMPtr GetTextFormat() const { return m_textFormat; } wxCOMPtr GetFont() { return m_font; } private: // The native, device-independent font object wxCOMPtr m_font; // The native, device-independent font object wxCOMPtr m_textFormat; // We use a color brush to render the font wxD2DBrushData m_brushData; bool m_underlined; bool m_strikethrough; }; wxD2DFontData::wxD2DFontData(wxGraphicsRenderer* renderer, const wxFont& font, const wxRealPoint& dpi, const wxColor& color) : wxGraphicsObjectRefData(renderer), m_brushData(renderer, wxBrush(color)), m_underlined(font.GetUnderlined()), m_strikethrough(font.GetStrikethrough()) { HRESULT hr; wxCOMPtr gdiInterop; hr = wxDWriteFactory()->GetGdiInterop(&gdiInterop); wxCHECK_HRESULT_RET(hr); LOGFONTW logfont; int n = GetObjectW(font.GetHFONT(), sizeof(logfont), &logfont); wxCHECK_RET( n > 0, wxS("Failed to obtain font info") ); // Ensure the LOGFONT object contains the correct font face name if (logfont.lfFaceName[0] == L'\0') { // The length of the font name must not exceed LF_FACESIZE TCHARs, // including the terminating NULL. wxString name = font.GetFaceName().Mid(0, WXSIZEOF(logfont.lfFaceName)-1); for (unsigned int i = 0; i < name.Length(); ++i) { logfont.lfFaceName[i] = name.GetChar(i); } logfont.lfFaceName[name.Length()] = L'\0'; } hr = gdiInterop->CreateFontFromLOGFONT(&logfont, &m_font); if ( hr == DWRITE_E_NOFONT ) { // It was attempted to create DirectWrite font from non-TrueType GDI font. return; } wxCHECK_RET( SUCCEEDED(hr), wxString::Format("Failed to create font '%s' (HRESULT = %x)", logfont.lfFaceName, hr) ); wxCOMPtr fontFamily; hr = m_font->GetFontFamily(&fontFamily); wxCHECK_HRESULT_RET(hr); wxCOMPtr familyNames; hr = fontFamily->GetFamilyNames(&familyNames); wxCHECK_HRESULT_RET(hr); UINT32 length; hr = familyNames->GetStringLength(0, &length); wxCHECK_HRESULT_RET(hr); wchar_t* name = new wchar_t[length+1]; hr = familyNames->GetString(0, name, length+1); wxCHECK_HRESULT_RET(hr); FLOAT fontSize = (FLOAT)(!dpi.y ? font.GetPixelSize().GetHeight() : (font.GetFractionalPointSize() * dpi.y / 72.0f)); hr = wxDWriteFactory()->CreateTextFormat( name, NULL, m_font->GetWeight(), m_font->GetStyle(), m_font->GetStretch(), fontSize, L"en-us", &m_textFormat); delete[] name; wxCHECK_HRESULT_RET(hr); } wxCOMPtr wxD2DFontData::CreateTextLayout(const wxString& text) const { static const FLOAT MAX_WIDTH = FLT_MAX; static const FLOAT MAX_HEIGHT = FLT_MAX; HRESULT hr; wxCOMPtr textLayout; hr = wxDWriteFactory()->CreateTextLayout( text.c_str(), text.length(), m_textFormat, MAX_WIDTH, MAX_HEIGHT, &textLayout); wxCHECK2_HRESULT_RET(hr, wxCOMPtr(NULL)); DWRITE_TEXT_RANGE textRange = { 0, (UINT32) text.length() }; if (m_underlined) { textLayout->SetUnderline(true, textRange); } if (m_strikethrough) { textLayout->SetStrikethrough(true, textRange); } return textLayout; } wxD2DFontData* wxGetD2DFontData(const wxGraphicsFont& font) { return static_cast(font.GetGraphicsData()); } // A render target resource holder exposes methods relevant // for native render targets such as resize class wxD2DRenderTargetResourceHolder : public wxD2DResourceHolder { public: // This method is called when an external event signals the underlying DC // is resized (e.g. the resizing of a window). Some implementations can leave // this method empty, while others must adjust the render target size to match // the underlying DC. virtual void Resize() { } // We use this method instead of the one provided by the native render target // because Direct2D 1.0 render targets do not accept a composition mode // parameter, while the device context in Direct2D 1.1 does. This way, we make // best use of the capabilities of each render target. // // The default implementation works for all render targets, but the D2D 1.0 // render target holders shouldn't need to override it, since none of the // 1.0 render targets offer a better version of this method. virtual void DrawBitmap(ID2D1Bitmap* bitmap, const D2D1_RECT_F& srcRect, const D2D1_RECT_F& destRect, wxInterpolationQuality interpolationQuality, wxCompositionMode WXUNUSED(compositionMode)) { m_nativeResource->DrawBitmap( bitmap, destRect, 1.0f, wxD2DConvertBitmapInterpolationMode(interpolationQuality), srcRect); } // We use this method instead of the one provided by the native render target // because some contexts might require writing to a buffer (e.g. an image // context), and some render targets might require additional operations to // be executed (e.g. the device context must present the swap chain) virtual HRESULT Flush() { return m_nativeResource->Flush(); } // Composition is not supported at in D2D 1.0, and we only allow for: // wxCOMPOSITION_DEST - which is essentially a no-op and is handled // externally by preventing any draw calls. // wxCOMPOSITION_OVER - which copies the source over the destination using // alpha blending. This is the default way D2D 1.0 // draws images. virtual bool SetCompositionMode(wxCompositionMode compositionMode) { if (compositionMode == wxCOMPOSITION_DEST || compositionMode == wxCOMPOSITION_OVER) { // There's nothing we can do but notify the caller the composition // mode is supported return true; } return false; } }; #if wxUSE_IMAGE class wxD2DImageRenderTargetResourceHolder : public wxD2DRenderTargetResourceHolder { public: wxD2DImageRenderTargetResourceHolder(wxImage* image, ID2D1Factory* factory) : m_resultImage(image), m_factory(factory) { } HRESULT Flush() wxOVERRIDE { HRESULT hr = m_nativeResource->Flush(); FlushRenderTargetToImage(); return hr; } ~wxD2DImageRenderTargetResourceHolder() { FlushRenderTargetToImage(); } protected: void DoAcquireResource() wxOVERRIDE { HRESULT hr; // Create a compatible WIC Bitmap hr = wxWICImagingFactory()->CreateBitmap( m_resultImage->GetWidth(), m_resultImage->GetHeight(), GUID_WICPixelFormat32bppPBGRA, WICBitmapCacheOnDemand, &m_wicBitmap); wxCHECK_HRESULT_RET(hr); // Copy contents of source image to the WIC bitmap. const int width = m_resultImage->GetWidth(); const int height = m_resultImage->GetHeight(); WICRect rcLock = { 0, 0, width, height }; IWICBitmapLock *pLock = NULL; hr = m_wicBitmap->Lock(&rcLock, WICBitmapLockWrite, &pLock); wxCHECK_HRESULT_RET(hr); UINT rowStride = 0; hr = pLock->GetStride(&rowStride); if ( FAILED(hr) ) { pLock->Release(); wxFAILED_HRESULT_MSG(hr); return; } UINT bufferSize = 0; BYTE *pBmpBuffer = NULL; hr = pLock->GetDataPointer(&bufferSize, &pBmpBuffer); if ( FAILED(hr) ) { pLock->Release(); wxFAILED_HRESULT_MSG(hr); return; } const unsigned char *imgRGB = m_resultImage->GetData(); // source RGB buffer const unsigned char *imgAlpha = m_resultImage->GetAlpha(); // source alpha buffer for( int y = 0; y < height; y++ ) { BYTE *pPixByte = pBmpBuffer; for ( int x = 0; x < width; x++ ) { unsigned char r = *imgRGB++; unsigned char g = *imgRGB++; unsigned char b = *imgRGB++; unsigned char a = imgAlpha ? *imgAlpha++ : 255; // Premultiply RGB values *pPixByte++ = (b * a + 127) / 255; *pPixByte++ = (g * a + 127) / 255; *pPixByte++ = (r * a + 127) / 255; *pPixByte++ = a; } pBmpBuffer += rowStride; } pLock->Release(); // Create the render target hr = m_factory->CreateWicBitmapRenderTarget( m_wicBitmap, D2D1::RenderTargetProperties( D2D1_RENDER_TARGET_TYPE_SOFTWARE, D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED)), &m_nativeResource); wxCHECK_HRESULT_RET(hr); } private: void FlushRenderTargetToImage() { const int width = m_resultImage->GetWidth(); const int height = m_resultImage->GetHeight(); WICRect rcLock = { 0, 0, width, height }; IWICBitmapLock *pLock = NULL; HRESULT hr = m_wicBitmap->Lock(&rcLock, WICBitmapLockRead, &pLock); wxCHECK_HRESULT_RET(hr); UINT rowStride = 0; hr = pLock->GetStride(&rowStride); if ( FAILED(hr) ) { pLock->Release(); wxFAILED_HRESULT_MSG(hr); return; } UINT bufferSize = 0; BYTE *pBmpBuffer = NULL; hr = pLock->GetDataPointer(&bufferSize, &pBmpBuffer); if ( FAILED(hr) ) { pLock->Release(); wxFAILED_HRESULT_MSG(hr); return; } WICPixelFormatGUID pixelFormat; hr = pLock->GetPixelFormat(&pixelFormat); if ( FAILED(hr) ) { pLock->Release(); wxFAILED_HRESULT_MSG(hr); return; } wxASSERT_MSG( pixelFormat == GUID_WICPixelFormat32bppPBGRA || pixelFormat == GUID_WICPixelFormat32bppBGR, wxS("Unsupported pixel format") ); // Only premultiplied ARGB bitmaps are supported. const bool hasAlpha = pixelFormat == GUID_WICPixelFormat32bppPBGRA; unsigned char* destRGB = m_resultImage->GetData(); unsigned char* destAlpha = m_resultImage->GetAlpha(); for( int y = 0; y < height; y++ ) { BYTE *pPixByte = pBmpBuffer; for ( int x = 0; x < width; x++ ) { wxPBGRAColor color = wxPBGRAColor(pPixByte); unsigned char a = hasAlpha ? color.a : 255; // Undo premultiplication for ARGB bitmap *destRGB++ = (a > 0 && a < 255) ? ( color.r * 255 ) / a : color.r; *destRGB++ = (a > 0 && a < 255) ? ( color.g * 255 ) / a : color.g; *destRGB++ = (a > 0 && a < 255) ? ( color.b * 255 ) / a : color.b; if ( destAlpha ) *destAlpha++ = a; pPixByte += 4; } pBmpBuffer += rowStride; } pLock->Release(); } private: wxImage* m_resultImage; wxCOMPtr m_wicBitmap; ID2D1Factory* m_factory; }; #endif // wxUSE_IMAGE class wxD2DHwndRenderTargetResourceHolder : public wxD2DRenderTargetResourceHolder { public: typedef ID2D1HwndRenderTarget* ImplementationType; wxD2DHwndRenderTargetResourceHolder(HWND hwnd, ID2D1Factory* factory) : m_hwnd(hwnd), m_factory(factory) { } void Resize() wxOVERRIDE { RECT clientRect; GetClientRect(m_hwnd, &clientRect); D2D1_SIZE_U hwndSize = D2D1::SizeU( clientRect.right - clientRect.left, clientRect.bottom - clientRect.top); D2D1_SIZE_U renderTargetSize = GetRenderTarget()->GetPixelSize(); if (hwndSize.width != renderTargetSize.width || hwndSize.height != renderTargetSize.height) { GetRenderTarget()->Resize(hwndSize); } } protected: void DoAcquireResource() wxOVERRIDE { wxCOMPtr renderTarget; HRESULT result; RECT clientRect; GetClientRect(m_hwnd, &clientRect); D2D1_SIZE_U size = D2D1::SizeU( clientRect.right - clientRect.left, clientRect.bottom - clientRect.top); result = m_factory->CreateHwndRenderTarget( D2D1::RenderTargetProperties(), D2D1::HwndRenderTargetProperties(m_hwnd, size), &renderTarget); if (FAILED(result)) { wxFAIL_MSG("Could not create Direct2D render target"); } renderTarget->SetTransform(D2D1::Matrix3x2F::Identity()); m_nativeResource = renderTarget; } private: // Converts the underlying resource pointer of type // ID2D1RenderTarget* to the actual implementation type ImplementationType GetRenderTarget() { return static_cast(GetD2DResource().get()); } private: HWND m_hwnd; ID2D1Factory* m_factory; }; #if wxD2D_DEVICE_CONTEXT_SUPPORTED class wxD2DDeviceContextResourceHolder : public wxD2DRenderTargetResourceHolder { public: wxD2DDeviceContextResourceHolder(ID2D1Factory* factory, HWND hwnd) : m_factory(NULL), m_hwnd(hwnd) { HRESULT hr = factory->QueryInterface(IID_ID2D1Factory1, (void**)&m_factory); wxCHECK_HRESULT_RET(hr); } void DrawBitmap(ID2D1Bitmap* bitmap, const D2D1_RECT_F& srcRect, const D2D1_RECT_F& destRect, wxInterpolationQuality interpolationQuality, wxCompositionMode compositionMode) wxOVERRIDE { D2D1_POINT_2F offset = D2D1::Point2(destRect.left, destRect.top); m_context->DrawImage(bitmap, offset, srcRect, wxD2DConvertInterpolationMode(interpolationQuality), wxD2DConvertCompositionMode(compositionMode)); } HRESULT Flush() wxOVERRIDE { HRESULT hr = m_nativeResource->Flush(); DXGI_PRESENT_PARAMETERS params = { 0 }; m_swapChain->Present1(1, 0, ¶ms); return hr; } protected: // Adapted from http://msdn.microsoft.com/en-us/library/windows/desktop/hh780339%28v=vs.85%29.aspx void DoAcquireResource() wxOVERRIDE { HRESULT hr; // This flag adds support for surfaces with a different color channel ordering than the API default. // You need it for compatibility with Direct2D. UINT creationFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT; // This array defines the set of DirectX hardware feature levels this app supports. // The ordering is important and you should preserve it. // Don't forget to declare your app's minimum required feature level in its // description. All apps are assumed to support 9.1 unless otherwise stated. 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 }; // Create the DX11 API device object, and get a corresponding context. wxCOMPtr device; wxCOMPtr context; hr = D3D11CreateDevice( NULL, // specify null to use the default adapter D3D_DRIVER_TYPE_HARDWARE, 0, creationFlags, // 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, &device, // returns the Direct3D device created &m_featureLevel, // returns feature level of device created &context); // returns the device immediate context wxCHECK_HRESULT_RET(hr); // Obtain the underlying DXGI device of the Direct3D11 device. hr = device->QueryInterface(IID_IDXGIDevice, (void**)&m_dxgiDevice); wxCHECK_HRESULT_RET(hr); // Obtain the Direct2D device for 2-D rendering. hr = m_factory->CreateDevice(m_dxgiDevice, &m_device); wxCHECK_HRESULT_RET(hr); // Get Direct2D device's corresponding device context object. hr = m_device->CreateDeviceContext( D2D1_DEVICE_CONTEXT_OPTIONS_NONE, &m_context); wxCHECK_HRESULT_RET(hr); m_nativeResource = m_context; AttachSurface(); } private: void AttachSurface() { HRESULT hr; // Allocate a descriptor. DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {0}; swapChainDesc.Width = 0; swapChainDesc.Height = 0; swapChainDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; swapChainDesc.Stereo = false; swapChainDesc.SampleDesc.Count = 1; swapChainDesc.SampleDesc.Quality = 0; swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; swapChainDesc.BufferCount = 2; swapChainDesc.Scaling = DXGI_SCALING_STRETCH; swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; swapChainDesc.Flags = 0; // Identify the physical adapter (GPU or card) this device is runs on. wxCOMPtr dxgiAdapter; hr = m_dxgiDevice->GetAdapter(&dxgiAdapter); wxCHECK_HRESULT_RET(hr); // Get the factory object that created the DXGI device. wxCOMPtr dxgiFactory; hr = dxgiAdapter->GetParent(IID_PPV_ARGS(&dxgiFactory)); wxCHECK_HRESULT_RET(hr); // Get the final swap chain for this window from the DXGI factory. hr = dxgiFactory->CreateSwapChainForHwnd( m_dxgiDevice, m_hwnd, &swapChainDesc, NULL, // allow on all displays NULL, &m_swapChain); wxCHECK_HRESULT_RET(hr); // Ensure that DXGI doesn't queue more than one frame at a time. hr = m_dxgiDevice->SetMaximumFrameLatency(1); wxCHECK_HRESULT_RET(hr); // Get the backbuffer for this window which is be the final 3D render target. wxCOMPtr backBuffer; hr = m_swapChain->GetBuffer(0, IID_PPV_ARGS(&backBuffer)); wxCHECK_HRESULT_RET(hr); FLOAT dpiX, dpiY; m_factory->GetDesktopDpi(&dpiX, &dpiY); // Now we set up the Direct2D render target bitmap linked to the swapchain. // Whenever we render to this bitmap, it is directly rendered to the // swap chain associated with the window. D2D1_BITMAP_PROPERTIES1 bitmapProperties = D2D1::BitmapProperties1( D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW, D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_IGNORE), dpiX, dpiY); // Direct2D needs the dxgi version of the backbuffer surface pointer. wxCOMPtr dxgiBackBuffer; hr = m_swapChain->GetBuffer(0, IID_PPV_ARGS(&dxgiBackBuffer)); wxCHECK_HRESULT_RET(hr); // Get a D2D surface from the DXGI back buffer to use as the D2D render target. hr = m_context->CreateBitmapFromDxgiSurface( dxgiBackBuffer.get(), &bitmapProperties, &m_targetBitmap); wxCHECK_HRESULT_RET(hr); // Now we can set the Direct2D render target. m_context->SetTarget(m_targetBitmap); } ~wxD2DDeviceContextResourceHolder() { DXGI_PRESENT_PARAMETERS params = { 0 }; m_swapChain->Present1(1, 0, ¶ms); } private: ID2D1Factory1* m_factory; HWND m_hwnd; D3D_FEATURE_LEVEL m_featureLevel; wxCOMPtr m_dxgiDevice; wxCOMPtr m_device; wxCOMPtr m_context; wxCOMPtr m_targetBitmap; wxCOMPtr m_swapChain; }; #endif class wxD2DDCRenderTargetResourceHolder : public wxD2DRenderTargetResourceHolder { public: wxD2DDCRenderTargetResourceHolder(ID2D1Factory* factory, HDC hdc, D2D1_ALPHA_MODE alphaMode) : m_factory(factory), m_hdc(hdc), m_alphaMode(alphaMode) { } protected: void DoAcquireResource() wxOVERRIDE { wxCOMPtr renderTarget; D2D1_RENDER_TARGET_PROPERTIES renderTargetProperties = D2D1::RenderTargetProperties( D2D1_RENDER_TARGET_TYPE_DEFAULT, D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, m_alphaMode)); HRESULT hr = m_factory->CreateDCRenderTarget( &renderTargetProperties, &renderTarget); wxCHECK_HRESULT_RET(hr); // We want draw on the entire device area. // GetClipBox() retrieves logical size of DC // what is what we need to pass to BindDC. RECT r; int status = ::GetClipBox(m_hdc, &r); wxCHECK_RET( status != ERROR, wxS("Error retrieving DC dimensions") ); hr = renderTarget->BindDC(m_hdc, &r); wxCHECK_HRESULT_RET(hr); renderTarget->SetTransform( D2D1::Matrix3x2F::Translation(-r.left, -r.top)); m_nativeResource = renderTarget; } private: ID2D1Factory* m_factory; HDC m_hdc; D2D1_ALPHA_MODE m_alphaMode; }; // The null context has no state of its own and does nothing. // It is only used as a base class for the lightweight // measuring context. The measuring context cannot inherit from // the default implementation wxD2DContext, because some methods // from wxD2DContext require the presence of a "context" // (render target) in order to acquire various device-dependent // resources. Without a proper context, those methods would fail. // The methods implemented in the null context are fundamentally no-ops. class wxNullContext : public wxGraphicsContext { public: wxNullContext(wxGraphicsRenderer* renderer) : wxGraphicsContext(renderer) {} void GetTextExtent(const wxString&, wxDouble*, wxDouble*, wxDouble*, wxDouble*) const wxOVERRIDE {} void GetPartialTextExtents(const wxString&, wxArrayDouble&) const wxOVERRIDE {} void Clip(const wxRegion&) wxOVERRIDE {} void Clip(wxDouble, wxDouble, wxDouble, wxDouble) wxOVERRIDE {} void ResetClip() wxOVERRIDE {} void GetClipBox(wxDouble*, wxDouble*, wxDouble*, wxDouble*) wxOVERRIDE {} void* GetNativeContext() wxOVERRIDE { return NULL; } bool SetAntialiasMode(wxAntialiasMode) wxOVERRIDE { return false; } bool SetInterpolationQuality(wxInterpolationQuality) wxOVERRIDE { return false; } bool SetCompositionMode(wxCompositionMode) wxOVERRIDE { return false; } void BeginLayer(wxDouble) wxOVERRIDE {} void EndLayer() wxOVERRIDE {} void Translate(wxDouble, wxDouble) wxOVERRIDE {} void Scale(wxDouble, wxDouble) wxOVERRIDE {} void Rotate(wxDouble) wxOVERRIDE {} void ConcatTransform(const wxGraphicsMatrix&) wxOVERRIDE {} void SetTransform(const wxGraphicsMatrix&) wxOVERRIDE {} wxGraphicsMatrix GetTransform() const wxOVERRIDE { return wxNullGraphicsMatrix; } void StrokePath(const wxGraphicsPath&) wxOVERRIDE {} void FillPath(const wxGraphicsPath&, wxPolygonFillMode) wxOVERRIDE {} void DrawBitmap(const wxGraphicsBitmap&, wxDouble, wxDouble, wxDouble, wxDouble) wxOVERRIDE {} void DrawBitmap(const wxBitmap&, wxDouble, wxDouble, wxDouble, wxDouble) wxOVERRIDE {} void DrawIcon(const wxIcon&, wxDouble, wxDouble, wxDouble, wxDouble) wxOVERRIDE {} void PushState() wxOVERRIDE {} void PopState() wxOVERRIDE {} void Flush() wxOVERRIDE {} protected: void DoDrawText(const wxString&, wxDouble, wxDouble) wxOVERRIDE {} }; class wxD2DMeasuringContext : public wxNullContext { public: wxD2DMeasuringContext(wxGraphicsRenderer* renderer) : wxNullContext(renderer) {} void GetTextExtent(const wxString& str, wxDouble* width, wxDouble* height, wxDouble* descent, wxDouble* externalLeading) const wxOVERRIDE { GetTextExtent(wxGetD2DFontData(m_font), str, width, height, descent, externalLeading); } void GetPartialTextExtents(const wxString& text, wxArrayDouble& widths) const wxOVERRIDE { GetPartialTextExtents(wxGetD2DFontData(m_font), text, widths); } static void GetPartialTextExtents(wxD2DFontData* fontData, const wxString& text, wxArrayDouble& widths) { for (unsigned int i = 0; i < text.Length(); ++i) { wxDouble width; GetTextExtent(fontData, text.SubString(0, i), &width, NULL, NULL, NULL); widths.push_back(width); } } static void GetTextExtent(wxD2DFontData* fontData, const wxString& str, wxDouble* width, wxDouble* height, wxDouble* descent, wxDouble* externalLeading) { wxCOMPtr textLayout = fontData->CreateTextLayout(str); wxCOMPtr font = fontData->GetFont(); DWRITE_TEXT_METRICS textMetrics; textLayout->GetMetrics(&textMetrics); DWRITE_FONT_METRICS fontMetrics; font->GetMetrics(&fontMetrics); FLOAT ratio = fontData->GetTextFormat()->GetFontSize() / (FLOAT)fontMetrics.designUnitsPerEm; if (width != NULL) *width = textMetrics.widthIncludingTrailingWhitespace; if (height != NULL) *height = textMetrics.height; if (descent != NULL) *descent = fontMetrics.descent * ratio; if (externalLeading != NULL) *externalLeading = wxMax(0.0f, (fontMetrics.ascent + fontMetrics.descent) * ratio - textMetrics.height); } }; //----------------------------------------------------------------------------- // wxD2DContext declaration //----------------------------------------------------------------------------- class wxD2DContext : public wxGraphicsContext, wxD2DResourceManager { public: // Create the context for the given HWND, which may be associated (if it's // non-null) with the given wxWindow. wxD2DContext(wxGraphicsRenderer* renderer, ID2D1Factory* direct2dFactory, HWND hwnd, wxWindow* window = NULL); // Create the context for the given HDC which may be associated (if it's // non-null) with the given wxDC. wxD2DContext(wxGraphicsRenderer* renderer, ID2D1Factory* direct2dFactory, HDC hdc, const wxDC* dc = NULL, D2D1_ALPHA_MODE alphaMode = D2D1_ALPHA_MODE_IGNORE); #if wxUSE_IMAGE wxD2DContext(wxGraphicsRenderer* renderer, ID2D1Factory* direct2dFactory, wxImage& image); #endif // wxUSE_IMAGE wxD2DContext(wxGraphicsRenderer* renderer, ID2D1Factory* direct2dFactory, void* nativeContext); ~wxD2DContext(); void Clip(const wxRegion& region) wxOVERRIDE; void Clip(wxDouble x, wxDouble y, wxDouble w, wxDouble h) wxOVERRIDE; void ResetClip() wxOVERRIDE; void GetClipBox(wxDouble* x, wxDouble* y, wxDouble* w, wxDouble* h) wxOVERRIDE; // The native context used by wxD2DContext is a Direct2D render target. void* GetNativeContext() wxOVERRIDE; bool SetAntialiasMode(wxAntialiasMode antialias) wxOVERRIDE; bool SetInterpolationQuality(wxInterpolationQuality interpolation) wxOVERRIDE; bool SetCompositionMode(wxCompositionMode op) wxOVERRIDE; void BeginLayer(wxDouble opacity) wxOVERRIDE; void EndLayer() wxOVERRIDE; void Translate(wxDouble dx, wxDouble dy) wxOVERRIDE; void Scale(wxDouble xScale, wxDouble yScale) wxOVERRIDE; void Rotate(wxDouble angle) wxOVERRIDE; void ConcatTransform(const wxGraphicsMatrix& matrix) wxOVERRIDE; void SetTransform(const wxGraphicsMatrix& matrix) wxOVERRIDE; wxGraphicsMatrix GetTransform() const wxOVERRIDE; void StrokePath(const wxGraphicsPath& p) wxOVERRIDE; void FillPath(const wxGraphicsPath& p , wxPolygonFillMode fillStyle = wxODDEVEN_RULE) wxOVERRIDE; void DrawRectangle(wxDouble x, wxDouble y, wxDouble w, wxDouble h) wxOVERRIDE; void DrawRoundedRectangle(wxDouble x, wxDouble y, wxDouble w, wxDouble h, wxDouble radius) wxOVERRIDE; void DrawEllipse(wxDouble x, wxDouble y, wxDouble w, wxDouble h) wxOVERRIDE; void DrawBitmap(const wxGraphicsBitmap& bmp, wxDouble x, wxDouble y, wxDouble w, wxDouble h) wxOVERRIDE; void DrawBitmap(const wxBitmap& bmp, wxDouble x, wxDouble y, wxDouble w, wxDouble h) wxOVERRIDE; void DrawIcon(const wxIcon& icon, wxDouble x, wxDouble y, wxDouble w, wxDouble h) wxOVERRIDE; void PushState() wxOVERRIDE; void PopState() wxOVERRIDE; void GetTextExtent( const wxString& str, wxDouble* width, wxDouble* height, wxDouble* descent, wxDouble* externalLeading) const wxOVERRIDE; void GetPartialTextExtents(const wxString& text, wxArrayDouble& widths) const wxOVERRIDE; bool ShouldOffset() const wxOVERRIDE; void SetPen(const wxGraphicsPen& pen) wxOVERRIDE; void Flush() wxOVERRIDE; void GetDPI(wxDouble* dpiX, wxDouble* dpiY) const wxOVERRIDE; wxD2DContextSupplier::ContextType GetContext() wxOVERRIDE { return GetRenderTarget(); } private: void Init(); void DoDrawText(const wxString& str, wxDouble x, wxDouble y) wxOVERRIDE; void EnsureInitialized(); HRESULT CreateRenderTarget(); void AdjustRenderTargetSize(); void ReleaseDeviceDependentResources(); ID2D1RenderTarget* GetRenderTarget() const; void SetClipLayer(ID2D1Geometry* clipGeometry); private: enum LayerType { CLIP_LAYER, OTHER_LAYER }; struct LayerData { LayerType type; D2D1_LAYER_PARAMETERS params; wxCOMPtr layer; wxCOMPtr geometry; D2D1_MATRIX_3X2_F transformMatrix; }; struct StateData { // A ID2D1DrawingStateBlock represents the drawing state of a render target: // the anti aliasing mode, transform, tags, and text-rendering options. // The context owns these pointers and is responsible for releasing them. wxCOMPtr drawingState; // We need to store also current layers. wxStack layers; }; private: ID2D1Factory* m_direct2dFactory; wxSharedPtr m_renderTargetHolder; wxStack m_stateStack; wxStack m_layers; ID2D1RenderTarget* m_cachedRenderTarget; D2D1::Matrix3x2F m_initTransform; // Clipping box bool m_isClipBoxValid; double m_clipX1, m_clipY1, m_clipX2, m_clipY2; private: wxDECLARE_NO_COPY_CLASS(wxD2DContext); }; //----------------------------------------------------------------------------- // wxD2DContext implementation //----------------------------------------------------------------------------- wxD2DContext::wxD2DContext(wxGraphicsRenderer* renderer, ID2D1Factory* direct2dFactory, HWND hwnd, wxWindow* window) : wxGraphicsContext(renderer, window), m_direct2dFactory(direct2dFactory), #if wxD2D_DEVICE_CONTEXT_SUPPORTED m_renderTargetHolder(new wxD2DDeviceContextResourceHolder(direct2dFactory, hwnd)) #else m_renderTargetHolder(new wxD2DHwndRenderTargetResourceHolder(hwnd, direct2dFactory)) #endif { RECT r = wxGetWindowRect(hwnd); m_width = r.right - r.left; m_height = r.bottom - r.top; Init(); } wxD2DContext::wxD2DContext(wxGraphicsRenderer* renderer, ID2D1Factory* direct2dFactory, HDC hdc, const wxDC* dc, D2D1_ALPHA_MODE alphaMode) : wxGraphicsContext(renderer, dc->GetWindow()), m_direct2dFactory(direct2dFactory), m_renderTargetHolder(new wxD2DDCRenderTargetResourceHolder(direct2dFactory, hdc, alphaMode)) { if ( dc ) { const wxSize dcSize = dc->GetSize(); m_width = dcSize.GetWidth(); m_height = dcSize.GetHeight(); } Init(); } #if wxUSE_IMAGE wxD2DContext::wxD2DContext(wxGraphicsRenderer* renderer, ID2D1Factory* direct2dFactory, wxImage& image) : wxGraphicsContext(renderer), m_direct2dFactory(direct2dFactory), m_renderTargetHolder(new wxD2DImageRenderTargetResourceHolder(&image, direct2dFactory)) { m_width = image.GetWidth(); m_height = image.GetHeight(); Init(); } #endif // wxUSE_IMAGE wxD2DContext::wxD2DContext(wxGraphicsRenderer* renderer, ID2D1Factory* direct2dFactory, void* nativeContext) : wxGraphicsContext(renderer), m_direct2dFactory(direct2dFactory) { m_renderTargetHolder = *((wxSharedPtr*)nativeContext); m_width = 0; m_height = 0; Init(); } void wxD2DContext::Init() { m_cachedRenderTarget = NULL; m_composition = wxCOMPOSITION_OVER; m_renderTargetHolder->Bind(this); m_enableOffset = true; m_isClipBoxValid = false; m_clipX1 = m_clipY1 = m_clipX2 = m_clipY2 = 0.0; EnsureInitialized(); } wxD2DContext::~wxD2DContext() { // Remove all layers from the stack of layers. while ( !m_layers.empty() ) { LayerData ld = m_layers.top(); m_layers.pop(); GetRenderTarget()->PopLayer(); ld.layer.reset(); ld.geometry.reset(); } HRESULT result = GetRenderTarget()->EndDraw(); wxCHECK_HRESULT_RET(result); ReleaseResources(); } ID2D1RenderTarget* wxD2DContext::GetRenderTarget() const { return m_cachedRenderTarget; } void wxD2DContext::Clip(const wxRegion& region) { wxCOMPtr clipGeometry = wxD2DConvertRegionToGeometry(m_direct2dFactory, region); SetClipLayer(clipGeometry); } void wxD2DContext::Clip(wxDouble x, wxDouble y, wxDouble w, wxDouble h) { wxCOMPtr clipGeometry; HRESULT hr = m_direct2dFactory->CreateRectangleGeometry( D2D1::RectF(x, y, x + w, y + h), &clipGeometry); wxCHECK_HRESULT_RET(hr); SetClipLayer(clipGeometry); } void wxD2DContext::SetClipLayer(ID2D1Geometry* clipGeometry) { EnsureInitialized(); wxCOMPtr clipLayer; HRESULT hr = GetRenderTarget()->CreateLayer(&clipLayer); wxCHECK_HRESULT_RET(hr); LayerData ld; ld.type = CLIP_LAYER; ld.params = D2D1::LayerParameters(D2D1::InfiniteRect(), clipGeometry, wxD2DConvertAntialiasMode(m_antialias)); ld.layer = clipLayer; ld.geometry = clipGeometry; // We need to store CTM to be able to re-apply // the layer at the original position later on. GetRenderTarget()->GetTransform(&ld.transformMatrix); GetRenderTarget()->PushLayer(ld.params, clipLayer); // Store layer parameters. m_layers.push(ld); m_isClipBoxValid = false; } void wxD2DContext::ResetClip() { wxStack layersToRestore; // Remove all clipping layers from the stack of layers. while ( !m_layers.empty() ) { LayerData ld = m_layers.top(); m_layers.pop(); if ( ld.type == CLIP_LAYER ) { GetRenderTarget()->PopLayer(); ld.layer.reset(); ld.geometry.reset(); continue; } GetRenderTarget()->PopLayer(); // Save non-clipping layer layersToRestore.push(ld); } HRESULT hr = GetRenderTarget()->Flush(); wxCHECK_HRESULT_RET(hr); // Re-apply all remaining non-clipping layers. // First, save current transformation matrix. D2D1_MATRIX_3X2_F currTransform; GetRenderTarget()->GetTransform(&currTransform); while ( !layersToRestore.empty() ) { LayerData ld = layersToRestore.top(); layersToRestore.pop(); // Restore layer at original position. GetRenderTarget()->SetTransform(&ld.transformMatrix); GetRenderTarget()->PushLayer(ld.params, ld.layer); // Store layer parameters. m_layers.push(ld); } // Restore current transformation matrix. GetRenderTarget()->SetTransform(&currTransform); m_isClipBoxValid = false; } void wxD2DContext::GetClipBox(wxDouble* x, wxDouble* y, wxDouble* w, wxDouble* h) { if ( !m_isClipBoxValid ) { // To obtain actual clipping box we have to start with rectangle // covering the entire render target and interesect with this rectangle // all clipping layers. Bounding box of the final geometry // (being intersection of all clipping layers) is a clipping box. HRESULT hr; wxCOMPtr rectGeometry; hr = m_direct2dFactory->CreateRectangleGeometry( D2D1::RectF(0.0F, 0.0F, (FLOAT)m_width, (FLOAT)m_height), &rectGeometry); wxCHECK_HRESULT_RET(hr); wxCOMPtr clipGeometry(rectGeometry); wxStack layers(m_layers); while( !layers.empty() ) { LayerData ld = layers.top(); layers.pop(); if ( ld.type == CLIP_LAYER ) { // If current geometry is empty (null region) // or there is no intersection between geometries // then final result is "null" rectangle geometry. FLOAT area; hr = ld.geometry->ComputeArea(ld.transformMatrix, &area); wxCHECK_HRESULT_RET(hr); D2D1_GEOMETRY_RELATION geomRel; hr = clipGeometry->CompareWithGeometry(ld.geometry, ld.transformMatrix, &geomRel); wxCHECK_HRESULT_RET(hr); if ( area <= FLT_MIN || geomRel == D2D1_GEOMETRY_RELATION_DISJOINT ) { wxCOMPtr nullGeometry; hr = m_direct2dFactory->CreateRectangleGeometry( D2D1::RectF(0.0F, 0.0F, 0.0F, 0.0F), &nullGeometry); wxCHECK_HRESULT_RET(hr); clipGeometry.reset(); clipGeometry = nullGeometry; break; } wxCOMPtr pathGeometryClip; hr = m_direct2dFactory->CreatePathGeometry(&pathGeometryClip); wxCHECK_HRESULT_RET(hr); wxCOMPtr pGeometrySink; hr = pathGeometryClip->Open(&pGeometrySink); wxCHECK_HRESULT_RET(hr); hr = clipGeometry->CombineWithGeometry(ld.geometry, D2D1_COMBINE_MODE_INTERSECT, ld.transformMatrix, pGeometrySink); wxCHECK_HRESULT_RET(hr); hr = pGeometrySink->Close(); wxCHECK_HRESULT_RET(hr); pGeometrySink.reset(); clipGeometry = pathGeometryClip; pathGeometryClip.reset(); } } // Final clipping geometry is given in device coordinates // so we need to transform its bounds to logical coordinates. D2D1::Matrix3x2F currTransform; GetRenderTarget()->GetTransform(&currTransform); currTransform.Invert(); D2D1_RECT_F bounds; // First check if clip region is empty. FLOAT clipArea; hr = clipGeometry->ComputeArea(currTransform, &clipArea); wxCHECK_HRESULT_RET(hr); if ( clipArea <= FLT_MIN ) { bounds.left = bounds.top = bounds.right = bounds.bottom = 0.0F; } else { // If it is not empty then get it bounds. hr = clipGeometry->GetBounds(currTransform, &bounds); wxCHECK_HRESULT_RET(hr); } m_clipX1 = bounds.left; m_clipY1 = bounds.top; m_clipX2 = bounds.right; m_clipY2 = bounds.bottom; m_isClipBoxValid = true; } if ( x ) *x = m_clipX1; if ( y ) *y = m_clipY1; if ( w ) *w = m_clipX2 - m_clipX1; if ( h ) *h = m_clipY2 - m_clipY1; } void* wxD2DContext::GetNativeContext() { return &m_renderTargetHolder; } void wxD2DContext::StrokePath(const wxGraphicsPath& p) { if (m_composition == wxCOMPOSITION_DEST) return; wxD2DOffsetHelper helper(this); EnsureInitialized(); AdjustRenderTargetSize(); wxD2DPathData* pathData = wxGetD2DPathData(p); pathData->Flush(); if (!m_pen.IsNull()) { wxD2DPenData* penData = wxGetD2DPenData(m_pen); penData->Bind(this); ID2D1Brush* nativeBrush = penData->GetBrush(); GetRenderTarget()->DrawGeometry((ID2D1Geometry*)pathData->GetNativePath(), nativeBrush, penData->GetWidth(), penData->GetStrokeStyle()); } } void wxD2DContext::FillPath(const wxGraphicsPath& p , wxPolygonFillMode fillStyle) { if (m_composition == wxCOMPOSITION_DEST) return; EnsureInitialized(); AdjustRenderTargetSize(); wxD2DPathData* pathData = wxGetD2DPathData(p); pathData->SetFillMode(fillStyle == wxODDEVEN_RULE ? D2D1_FILL_MODE_ALTERNATE : D2D1_FILL_MODE_WINDING); pathData->Flush(); if (!m_brush.IsNull()) { wxD2DBrushData* brushData = wxGetD2DBrushData(m_brush); brushData->Bind(this); GetRenderTarget()->FillGeometry((ID2D1Geometry*)pathData->GetNativePath(), brushData->GetBrush()); } } bool wxD2DContext::SetAntialiasMode(wxAntialiasMode antialias) { if (m_antialias == antialias) { return true; } GetRenderTarget()->SetAntialiasMode(wxD2DConvertAntialiasMode(antialias)); m_antialias = antialias; return true; } bool wxD2DContext::SetInterpolationQuality(wxInterpolationQuality interpolation) { // Since different versions of Direct2D have different enumerations for // interpolation quality, we deffer the conversion to the method which // does the actual drawing. m_interpolation = interpolation; return true; } bool wxD2DContext::SetCompositionMode(wxCompositionMode compositionMode) { if (m_composition == compositionMode) return true; if (m_renderTargetHolder->SetCompositionMode(compositionMode)) { // the composition mode is supported by the render target m_composition = compositionMode; return true; } return false; } void wxD2DContext::BeginLayer(wxDouble opacity) { EnsureInitialized(); wxCOMPtr layer; HRESULT hr = GetRenderTarget()->CreateLayer(&layer); wxCHECK_HRESULT_RET(hr); LayerData ld; ld.type = OTHER_LAYER; ld.params = D2D1::LayerParameters(D2D1::InfiniteRect(), NULL, D2D1_ANTIALIAS_MODE_PER_PRIMITIVE, D2D1::IdentityMatrix(), opacity); ld.layer = layer; // We need to store CTM to be able to re-apply // the layer at the original position later on. GetRenderTarget()->GetTransform(&ld.transformMatrix); GetRenderTarget()->PushLayer(ld.params, layer); // Store layer parameters. m_layers.push(ld); } void wxD2DContext::EndLayer() { wxStack layersToRestore; // Temporarily remove all clipping layers // above the first standard layer // and next permanently remove this layer. while ( !m_layers.empty() ) { LayerData ld = m_layers.top(); m_layers.pop(); if ( ld.type == CLIP_LAYER ) { GetRenderTarget()->PopLayer(); layersToRestore.push(ld); continue; } // We found a non-clipping layer to remove. GetRenderTarget()->PopLayer(); ld.layer.reset(); break; } if ( m_layers.empty() ) { HRESULT hr = GetRenderTarget()->Flush(); wxCHECK_HRESULT_RET(hr); } // Re-apply all stored clipping layers. // First, save current transformation matrix. D2D1_MATRIX_3X2_F currTransform; GetRenderTarget()->GetTransform(&currTransform); while ( !layersToRestore.empty() ) { LayerData ld = layersToRestore.top(); layersToRestore.pop(); if ( ld.type == CLIP_LAYER ) { // Restore layer at original position. GetRenderTarget()->SetTransform(&ld.transformMatrix); GetRenderTarget()->PushLayer(ld.params, ld.layer); } else { wxFAIL_MSG( wxS("Invalid layer type") ); } // Store layer parameters. m_layers.push(ld); } // Restore current transformation matrix. GetRenderTarget()->SetTransform(&currTransform); } void wxD2DContext::Translate(wxDouble dx, wxDouble dy) { wxGraphicsMatrix translationMatrix = CreateMatrix(); translationMatrix.Translate(dx, dy); ConcatTransform(translationMatrix); } void wxD2DContext::Scale(wxDouble xScale, wxDouble yScale) { wxGraphicsMatrix scaleMatrix = CreateMatrix(); scaleMatrix.Scale(xScale, yScale); ConcatTransform(scaleMatrix); } void wxD2DContext::Rotate(wxDouble angle) { wxGraphicsMatrix rotationMatrix = CreateMatrix(); rotationMatrix.Rotate(angle); ConcatTransform(rotationMatrix); } void wxD2DContext::ConcatTransform(const wxGraphicsMatrix& matrix) { D2D1::Matrix3x2F localMatrix = wxGetD2DMatrixData(GetTransform())->GetMatrix3x2F(); D2D1::Matrix3x2F concatMatrix = wxGetD2DMatrixData(matrix)->GetMatrix3x2F(); D2D1::Matrix3x2F resultMatrix; resultMatrix.SetProduct(concatMatrix, localMatrix); wxGraphicsMatrix resultTransform; resultTransform.SetRefData(new wxD2DMatrixData(GetRenderer(), resultMatrix)); SetTransform(resultTransform); } void wxD2DContext::SetTransform(const wxGraphicsMatrix& matrix) { EnsureInitialized(); D2D1::Matrix3x2F m; m.SetProduct(wxGetD2DMatrixData(matrix)->GetMatrix3x2F(), m_initTransform); GetRenderTarget()->SetTransform(&m); m_isClipBoxValid = false; } wxGraphicsMatrix wxD2DContext::GetTransform() const { D2D1::Matrix3x2F transformMatrix; if (GetRenderTarget() != NULL) { GetRenderTarget()->GetTransform(&transformMatrix); if ( m_initTransform.IsInvertible() ) { D2D1::Matrix3x2F invMatrix = m_initTransform; invMatrix.Invert(); D2D1::Matrix3x2F m; m.SetProduct(transformMatrix, invMatrix); transformMatrix = m; } } else { transformMatrix = D2D1::Matrix3x2F::Identity(); } wxD2DMatrixData* matrixData = new wxD2DMatrixData(GetRenderer(), transformMatrix); wxGraphicsMatrix matrix; matrix.SetRefData(matrixData); return matrix; } void wxD2DContext::DrawBitmap(const wxGraphicsBitmap& bmp, wxDouble x, wxDouble y, wxDouble w, wxDouble h) { if (m_composition == wxCOMPOSITION_DEST) return; wxD2DBitmapData* bitmapData = wxGetD2DBitmapData(bmp); bitmapData->Bind(this); D2D1_SIZE_F imgSize = bitmapData->GetD2DBitmap()->GetSize(); m_renderTargetHolder->DrawBitmap( bitmapData->GetD2DBitmap(), D2D1::RectF(0, 0, imgSize.width, imgSize.height), D2D1::RectF(x, y, x + w, y + h), GetInterpolationQuality(), GetCompositionMode()); } void wxD2DContext::DrawBitmap(const wxBitmap& bmp, wxDouble x, wxDouble y, wxDouble w, wxDouble h) { wxGraphicsBitmap graphicsBitmap = CreateBitmap(bmp); DrawBitmap(graphicsBitmap, x, y, w, h); } void wxD2DContext::DrawIcon(const wxIcon& icon, wxDouble x, wxDouble y, wxDouble w, wxDouble h) { DrawBitmap(wxBitmap(icon), x, y, w, h); } void wxD2DContext::PushState() { EnsureInitialized(); StateData state; m_direct2dFactory->CreateDrawingStateBlock(&state.drawingState); GetRenderTarget()->SaveDrawingState(state.drawingState); state.layers = m_layers; m_stateStack.push(state); } void wxD2DContext::PopState() { wxCHECK_RET(!m_stateStack.empty(), wxS("No state to pop")); // Remove all layers from the stack of layers. while ( !m_layers.empty() ) { LayerData ld = m_layers.top(); m_layers.pop(); GetRenderTarget()->PopLayer(); ld.layer.reset(); ld.geometry.reset(); } // Retrieve state data. StateData state; state = m_stateStack.top(); m_stateStack.pop(); // Restore all saved layers. wxStack layersToRestore; // We have to restore layers on the stack from "bottom" to "top", // so we have to create a "reverted" stack. while ( !state.layers.empty() ) { LayerData ld = state.layers.top(); state.layers.pop(); layersToRestore.push(ld); } // And next set layers from the top of "reverted" stack. while ( !layersToRestore.empty() ) { LayerData ld = layersToRestore.top(); layersToRestore.pop(); // Restore layer at original position. GetRenderTarget()->SetTransform(&ld.transformMatrix); GetRenderTarget()->PushLayer(ld.params, ld.layer); // Store layer parameters. m_layers.push(ld); } // Restore drawing state. GetRenderTarget()->RestoreDrawingState(state.drawingState); m_isClipBoxValid = false; } void wxD2DContext::GetTextExtent( const wxString& str, wxDouble* width, wxDouble* height, wxDouble* descent, wxDouble* externalLeading) const { wxCHECK_RET(!m_font.IsNull(), wxS("wxD2DContext::GetTextExtent - no valid font set")); wxD2DMeasuringContext::GetTextExtent( wxGetD2DFontData(m_font), str, width, height, descent, externalLeading); } void wxD2DContext::GetPartialTextExtents(const wxString& text, wxArrayDouble& widths) const { wxCHECK_RET(!m_font.IsNull(), wxS("wxD2DContext::GetPartialTextExtents - no valid font set")); wxD2DMeasuringContext::GetPartialTextExtents( wxGetD2DFontData(m_font), text, widths); } bool wxD2DContext::ShouldOffset() const { if (!m_enableOffset) { return false; } int penWidth = 0; if (!m_pen.IsNull()) { penWidth = wxGetD2DPenData(m_pen)->GetWidth(); penWidth = wxMax(penWidth, 1); } return (penWidth % 2) == 1; } void wxD2DContext::DoDrawText(const wxString& str, wxDouble x, wxDouble y) { wxCHECK_RET(!m_font.IsNull(), wxS("wxD2DContext::DrawText - no valid font set")); if (m_composition == wxCOMPOSITION_DEST) return; wxD2DFontData* fontData = wxGetD2DFontData(m_font); fontData->GetBrushData().Bind(this); wxCOMPtr textLayout = fontData->CreateTextLayout(str); // Render the text GetRenderTarget()->DrawTextLayout( D2D1::Point2F(x, y), textLayout, fontData->GetBrushData().GetBrush()); } void wxD2DContext::EnsureInitialized() { if (!m_renderTargetHolder->IsResourceAcquired()) { m_cachedRenderTarget = m_renderTargetHolder->GetD2DResource(); GetRenderTarget()->GetTransform(&m_initTransform); GetRenderTarget()->BeginDraw(); } else { m_cachedRenderTarget = m_renderTargetHolder->GetD2DResource(); } } void wxD2DContext::SetPen(const wxGraphicsPen& pen) { wxGraphicsContext::SetPen(pen); if (!m_pen.IsNull()) { EnsureInitialized(); wxD2DPenData* penData = wxGetD2DPenData(pen); penData->Bind(this); } } void wxD2DContext::AdjustRenderTargetSize() { m_renderTargetHolder->Resize(); // Currently GetSize() can only be called when using MSVC because gcc // doesn't handle returning aggregates by value as done by D2D libraries, // see https://gcc.gnu.org/bugzilla/show_bug.cgi?id=64384. Not updating the // size is not great, but it's better than crashing. #ifdef __VISUALC__ D2D1_SIZE_F renderTargetSize = m_renderTargetHolder->GetD2DResource()->GetSize(); m_width = renderTargetSize.width; m_height = renderTargetSize.height; #endif // __VISUALC__ } void wxD2DContext::ReleaseDeviceDependentResources() { ReleaseResources(); } void wxD2DContext::DrawRectangle(wxDouble x, wxDouble y, wxDouble w, wxDouble h) { if (m_composition == wxCOMPOSITION_DEST) return; wxD2DOffsetHelper helper(this); EnsureInitialized(); AdjustRenderTargetSize(); D2D1_RECT_F rect = { (FLOAT)x, (FLOAT)y, (FLOAT)(x + w), (FLOAT)(y + h) }; if (!m_brush.IsNull()) { wxD2DBrushData* brushData = wxGetD2DBrushData(m_brush); brushData->Bind(this); GetRenderTarget()->FillRectangle(rect, brushData->GetBrush()); } if (!m_pen.IsNull()) { wxD2DPenData* penData = wxGetD2DPenData(m_pen); penData->Bind(this); GetRenderTarget()->DrawRectangle(rect, penData->GetBrush(), penData->GetWidth(), penData->GetStrokeStyle()); } } void wxD2DContext::DrawRoundedRectangle(wxDouble x, wxDouble y, wxDouble w, wxDouble h, wxDouble radius) { if (m_composition == wxCOMPOSITION_DEST) return; wxD2DOffsetHelper helper(this); EnsureInitialized(); AdjustRenderTargetSize(); D2D1_RECT_F rect = { (FLOAT)x, (FLOAT)y, (FLOAT)(x + w), (FLOAT)(y + h) }; D2D1_ROUNDED_RECT roundedRect = { rect, (FLOAT)radius, (FLOAT)radius }; if (!m_brush.IsNull()) { wxD2DBrushData* brushData = wxGetD2DBrushData(m_brush); brushData->Bind(this); GetRenderTarget()->FillRoundedRectangle(roundedRect, brushData->GetBrush()); } if (!m_pen.IsNull()) { wxD2DPenData* penData = wxGetD2DPenData(m_pen); penData->Bind(this); GetRenderTarget()->DrawRoundedRectangle(roundedRect, penData->GetBrush(), penData->GetWidth(), penData->GetStrokeStyle()); } } void wxD2DContext::DrawEllipse(wxDouble x, wxDouble y, wxDouble w, wxDouble h) { if (m_composition == wxCOMPOSITION_DEST) return; wxD2DOffsetHelper helper(this); EnsureInitialized(); AdjustRenderTargetSize(); D2D1_ELLIPSE ellipse = { { (FLOAT)(x + w / 2), (FLOAT)(y + h / 2) }, // center point (FLOAT)(w / 2), // radius x (FLOAT)(h / 2) // radius y }; if (!m_brush.IsNull()) { wxD2DBrushData* brushData = wxGetD2DBrushData(m_brush); brushData->Bind(this); GetRenderTarget()->FillEllipse(ellipse, brushData->GetBrush()); } if (!m_pen.IsNull()) { wxD2DPenData* penData = wxGetD2DPenData(m_pen); penData->Bind(this); GetRenderTarget()->DrawEllipse(ellipse, penData->GetBrush(), penData->GetWidth(), penData->GetStrokeStyle()); } } void wxD2DContext::Flush() { wxStack layersToRestore; // Temporarily remove all layers from the stack of layers. while ( !m_layers.empty() ) { LayerData ld = m_layers.top(); m_layers.pop(); GetRenderTarget()->PopLayer(); // Save layer data. layersToRestore.push(ld); } HRESULT hr = m_renderTargetHolder->Flush(); if ( hr == (HRESULT)D2DERR_RECREATE_TARGET ) { ReleaseDeviceDependentResources(); } else { wxCHECK_HRESULT_RET(hr); } // Re-apply all layers. // First, save current transformation matrix. D2D1_MATRIX_3X2_F currTransform; GetRenderTarget()->GetTransform(&currTransform); while ( !layersToRestore.empty() ) { LayerData ld = layersToRestore.top(); layersToRestore.pop(); // Restore layer at original position. GetRenderTarget()->SetTransform(&ld.transformMatrix); GetRenderTarget()->PushLayer(ld.params, ld.layer); // Store layer parameters. m_layers.push(ld); } // Restore current transformation matrix. GetRenderTarget()->SetTransform(&currTransform); } void wxD2DContext::GetDPI(wxDouble* dpiX, wxDouble* dpiY) const { if ( GetWindow() ) { const wxSize dpi = GetWindow()->GetDPI(); if ( dpiX ) *dpiX = dpi.x; if ( dpiY ) *dpiY = dpi.y; } else { FLOAT x, y; GetRenderTarget()->GetDpi(&x, &y); if ( dpiX ) *dpiX = x; if ( dpiY ) *dpiY = y; } } //----------------------------------------------------------------------------- // wxD2DRenderer declaration //----------------------------------------------------------------------------- class wxD2DRenderer : public wxGraphicsRenderer { public : wxD2DRenderer(); virtual ~wxD2DRenderer(); wxGraphicsContext* CreateContext(const wxWindowDC& dc) wxOVERRIDE; wxGraphicsContext* CreateContext(const wxMemoryDC& dc) wxOVERRIDE; #if wxUSE_PRINTING_ARCHITECTURE wxGraphicsContext* CreateContext(const wxPrinterDC& dc) wxOVERRIDE; #endif #if wxUSE_ENH_METAFILE wxGraphicsContext* CreateContext(const wxEnhMetaFileDC& dc) wxOVERRIDE; #endif wxGraphicsContext* CreateContextFromNativeContext(void* context) wxOVERRIDE; wxGraphicsContext* CreateContextFromNativeWindow(void* window) wxOVERRIDE; wxGraphicsContext * CreateContextFromNativeHDC(WXHDC dc) wxOVERRIDE; wxGraphicsContext* CreateContext(wxWindow* window) wxOVERRIDE; #if wxUSE_IMAGE wxGraphicsContext* CreateContextFromImage(wxImage& image) wxOVERRIDE; #endif // wxUSE_IMAGE wxGraphicsContext* CreateMeasuringContext() wxOVERRIDE; wxGraphicsPath CreatePath() wxOVERRIDE; wxGraphicsMatrix CreateMatrix( wxDouble a = 1.0, wxDouble b = 0.0, wxDouble c = 0.0, wxDouble d = 1.0, wxDouble tx = 0.0, wxDouble ty = 0.0) wxOVERRIDE; wxGraphicsPen CreatePen(const wxGraphicsPenInfo& info) wxOVERRIDE; wxGraphicsBrush CreateBrush(const wxBrush& brush) wxOVERRIDE; wxGraphicsBrush CreateLinearGradientBrush( wxDouble x1, wxDouble y1, wxDouble x2, wxDouble y2, const wxGraphicsGradientStops& stops, const wxGraphicsMatrix& matrix = wxNullGraphicsMatrix) wxOVERRIDE; wxGraphicsBrush CreateRadialGradientBrush( wxDouble startX, wxDouble startY, wxDouble endX, wxDouble endY, wxDouble radius, const wxGraphicsGradientStops& stops, const wxGraphicsMatrix& matrix = wxNullGraphicsMatrix) wxOVERRIDE; // create a native bitmap representation wxGraphicsBitmap CreateBitmap(const wxBitmap& bitmap) wxOVERRIDE; #if wxUSE_IMAGE wxGraphicsBitmap CreateBitmapFromImage(const wxImage& image) wxOVERRIDE; wxImage CreateImageFromBitmap(const wxGraphicsBitmap& bmp) wxOVERRIDE; #endif wxGraphicsFont CreateFont(const wxFont& font, const wxColour& col) wxOVERRIDE; wxGraphicsFont CreateFont( double sizeInPixels, const wxString& facename, int flags = wxFONTFLAG_DEFAULT, const wxColour& col = *wxBLACK) wxOVERRIDE; virtual wxGraphicsFont CreateFontAtDPI(const wxFont& font, const wxRealPoint& dpi, const wxColour& col) wxOVERRIDE; // create a graphics bitmap from a native bitmap wxGraphicsBitmap CreateBitmapFromNativeBitmap(void* bitmap) wxOVERRIDE; // create a sub-image from a native image representation wxGraphicsBitmap CreateSubBitmap(const wxGraphicsBitmap& bitmap, wxDouble x, wxDouble y, wxDouble w, wxDouble h) wxOVERRIDE; wxString GetName() const wxOVERRIDE; void GetVersion(int* major, int* minor, int* micro) const wxOVERRIDE; ID2D1Factory* GetD2DFactory(); private: wxCOMPtr m_direct2dFactory; private : wxDECLARE_DYNAMIC_CLASS_NO_COPY(wxD2DRenderer); }; //----------------------------------------------------------------------------- // wxD2DRenderer implementation //----------------------------------------------------------------------------- wxIMPLEMENT_DYNAMIC_CLASS(wxD2DRenderer,wxGraphicsRenderer); static wxD2DRenderer *gs_D2DRenderer = NULL; wxGraphicsRenderer* wxGraphicsRenderer::GetDirect2DRenderer() { if (!wxDirect2D::Initialize()) return NULL; if (!gs_D2DRenderer) { gs_D2DRenderer = new wxD2DRenderer(); } return gs_D2DRenderer; } wxD2DRenderer::wxD2DRenderer() : m_direct2dFactory(wxD2D1Factory()) { if ( m_direct2dFactory.get() == NULL ) { wxFAIL_MSG("Could not create Direct2D Factory."); } } wxD2DRenderer::~wxD2DRenderer() { m_direct2dFactory.reset(); } wxGraphicsContext* wxD2DRenderer::CreateContext(const wxWindowDC& dc) { return new wxD2DContext(this, m_direct2dFactory, dc.GetHDC(), &dc); } wxGraphicsContext* wxD2DRenderer::CreateContext(const wxMemoryDC& dc) { wxBitmap bmp = dc.GetSelectedBitmap(); wxASSERT_MSG( bmp.IsOk(), wxS("Should select a bitmap before creating wxGraphicsContext") ); return new wxD2DContext(this, m_direct2dFactory, dc.GetHDC(), &dc, bmp.HasAlpha() ? D2D1_ALPHA_MODE_PREMULTIPLIED : D2D1_ALPHA_MODE_IGNORE); } #if wxUSE_PRINTING_ARCHITECTURE wxGraphicsContext* wxD2DRenderer::CreateContext(const wxPrinterDC& WXUNUSED(dc)) { wxFAIL_MSG("not implemented"); return NULL; } #endif #if wxUSE_ENH_METAFILE wxGraphicsContext* wxD2DRenderer::CreateContext(const wxEnhMetaFileDC& WXUNUSED(dc)) { wxFAIL_MSG("not implemented"); return NULL; } #endif wxGraphicsContext* wxD2DRenderer::CreateContextFromNativeContext(void* nativeContext) { return new wxD2DContext(this, m_direct2dFactory, nativeContext); } wxGraphicsContext* wxD2DRenderer::CreateContextFromNativeWindow(void* window) { return new wxD2DContext(this, m_direct2dFactory, (HWND)window); } wxGraphicsContext* wxD2DRenderer::CreateContextFromNativeHDC(WXHDC dc) { return new wxD2DContext(this, m_direct2dFactory, (HDC)dc); } wxGraphicsContext* wxD2DRenderer::CreateContext(wxWindow* window) { return new wxD2DContext(this, m_direct2dFactory, (HWND)window->GetHWND(), window); } #if wxUSE_IMAGE wxGraphicsContext* wxD2DRenderer::CreateContextFromImage(wxImage& image) { return new wxD2DContext(this, m_direct2dFactory, image); } #endif // wxUSE_IMAGE wxGraphicsContext* wxD2DRenderer::CreateMeasuringContext() { return new wxD2DMeasuringContext(this); } wxGraphicsPath wxD2DRenderer::CreatePath() { wxGraphicsPath p; p.SetRefData(new wxD2DPathData(this, m_direct2dFactory)); return p; } wxGraphicsMatrix wxD2DRenderer::CreateMatrix( wxDouble a, wxDouble b, wxDouble c, wxDouble d, wxDouble tx, wxDouble ty) { wxD2DMatrixData* matrixData = new wxD2DMatrixData(this); matrixData->Set(a, b, c, d, tx, ty); wxGraphicsMatrix matrix; matrix.SetRefData(matrixData); return matrix; } wxGraphicsPen wxD2DRenderer::CreatePen(const wxGraphicsPenInfo& info) { if ( info.GetStyle() == wxPENSTYLE_TRANSPARENT ) { return wxNullGraphicsPen; } else { wxGraphicsPen p; wxD2DPenData* penData = new wxD2DPenData(this, m_direct2dFactory, info); p.SetRefData(penData); return p; } } wxGraphicsBrush wxD2DRenderer::CreateBrush(const wxBrush& brush) { if ( !brush.IsOk() || brush.GetStyle() == wxBRUSHSTYLE_TRANSPARENT ) { return wxNullGraphicsBrush; } else { wxGraphicsBrush b; b.SetRefData(new wxD2DBrushData(this, brush)); return b; } } wxGraphicsBrush wxD2DRenderer::CreateLinearGradientBrush( wxDouble x1, wxDouble y1, wxDouble x2, wxDouble y2, const wxGraphicsGradientStops& stops, const wxGraphicsMatrix& matrix) { wxD2DBrushData* brushData = new wxD2DBrushData(this); brushData->CreateLinearGradientBrush(x1, y1, x2, y2, stops, matrix); wxGraphicsBrush brush; brush.SetRefData(brushData); return brush; } wxGraphicsBrush wxD2DRenderer::CreateRadialGradientBrush( wxDouble startX, wxDouble startY, wxDouble endX, wxDouble endY, wxDouble radius, const wxGraphicsGradientStops& stops, const wxGraphicsMatrix& matrix) { wxD2DBrushData* brushData = new wxD2DBrushData(this); brushData->CreateRadialGradientBrush(startX, startY, endX, endY, radius, stops, matrix); wxGraphicsBrush brush; brush.SetRefData(brushData); return brush; } // create a native bitmap representation wxGraphicsBitmap wxD2DRenderer::CreateBitmap(const wxBitmap& bitmap) { wxD2DBitmapData* bitmapData = new wxD2DBitmapData(this, bitmap); wxGraphicsBitmap graphicsBitmap; graphicsBitmap.SetRefData(bitmapData); return graphicsBitmap; } // create a graphics bitmap from a native bitmap wxGraphicsBitmap wxD2DRenderer::CreateBitmapFromNativeBitmap(void* bitmap) { wxD2DBitmapData* bitmapData = new wxD2DBitmapData(this, bitmap); wxGraphicsBitmap graphicsBitmap; graphicsBitmap.SetRefData(bitmapData); return graphicsBitmap; } #if wxUSE_IMAGE wxGraphicsBitmap wxD2DRenderer::CreateBitmapFromImage(const wxImage& image) { return CreateBitmap(wxBitmap(image)); } wxImage wxD2DRenderer::CreateImageFromBitmap(const wxGraphicsBitmap& bmp) { return static_cast(bmp.GetNativeBitmap()) ->GetSourceBitmap().ConvertToImage(); } #endif wxGraphicsFont wxD2DRenderer::CreateFont(const wxFont& font, const wxColour& col) { return CreateFontAtDPI(font, wxRealPoint(), col); } wxGraphicsFont wxD2DRenderer::CreateFont( double sizeInPixels, const wxString& facename, int flags, const wxColour& col) { // Use the same DPI as wxFont will use in SetPixelSize, so these cancel // each other out and we are left with the actual pixel size. ScreenHDC hdc; wxRealPoint dpi(::GetDeviceCaps(hdc, LOGPIXELSX), ::GetDeviceCaps(hdc, LOGPIXELSY)); return CreateFontAtDPI( wxFontInfo(wxSize(sizeInPixels, sizeInPixels)).AllFlags(flags).FaceName(facename), dpi, col); } wxGraphicsFont wxD2DRenderer::CreateFontAtDPI(const wxFont& font, const wxRealPoint& dpi, const wxColour& col) { wxD2DFontData* fontData = new wxD2DFontData(this, font, dpi, col); if ( !fontData->GetFont() ) { // Apparently a non-TrueType font is given and hence // corresponding DirectWrite font couldn't be created. delete fontData; return wxNullGraphicsFont; } wxGraphicsFont graphicsFont; graphicsFont.SetRefData(fontData); return graphicsFont; } // create a sub-image from a native image representation wxGraphicsBitmap wxD2DRenderer::CreateSubBitmap(const wxGraphicsBitmap& bitmap, wxDouble x, wxDouble y, wxDouble w, wxDouble h) { typedef wxD2DBitmapData::NativeType* NativeBitmap; wxBitmap sourceBitmap = static_cast(bitmap.GetNativeBitmap())->GetSourceBitmap(); return CreateBitmap(sourceBitmap.GetSubBitmap(wxRect(x, y, w, h))); } wxString wxD2DRenderer::GetName() const { return "direct2d"; } void wxD2DRenderer::GetVersion(int* major, int* minor, int* micro) const { if (wxDirect2D::HasDirect2DSupport()) { if (major) *major = 1; if (minor) { switch(wxDirect2D::GetDirect2DVersion()) { case wxDirect2D::wxD2D_VERSION_1_0: *minor = 0; break; case wxDirect2D::wxD2D_VERSION_1_1: *minor = 1; break; case wxDirect2D::wxD2D_VERSION_NONE: // This is not supposed to happen, but we handle this value in // the switch to ensure that we'll get warnings if any new // values, not handled here, are added to the enum later. *minor = -1; break; } } if (micro) *micro = 0; } } ID2D1Factory* wxD2DRenderer::GetD2DFactory() { return m_direct2dFactory; } ID2D1Factory* wxGetD2DFactory(wxGraphicsRenderer* renderer) { return static_cast(renderer)->GetD2DFactory(); } // ---------------------------------------------------------------------------- // Module ensuring all global/singleton objects are destroyed on shutdown. // ---------------------------------------------------------------------------- class wxDirect2DModule : public wxModule { public: wxDirect2DModule() { } virtual bool OnInit() wxOVERRIDE { HRESULT hr = ::CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); // RPC_E_CHANGED_MODE is not considered as an error // - see remarks for wxOleInitialize(). return SUCCEEDED(hr) || hr == RPC_E_CHANGED_MODE; } virtual void OnExit() wxOVERRIDE { if ( gs_WICImagingFactory ) { gs_WICImagingFactory->Release(); gs_WICImagingFactory = NULL; } if ( gs_IDWriteFactory ) { gs_IDWriteFactory->Release(); gs_IDWriteFactory = NULL; } if ( gs_D2DRenderer ) { delete gs_D2DRenderer; gs_D2DRenderer = NULL; } if ( gs_ID2D1Factory ) { gs_ID2D1Factory->Release(); gs_ID2D1Factory = NULL; } ::CoUninitialize(); } private: wxDECLARE_DYNAMIC_CLASS(wxDirect2DModule); }; wxIMPLEMENT_DYNAMIC_CLASS(wxDirect2DModule, wxModule); #endif // wxUSE_GRAPHICS_DIRECT2D