本文來告訴大家如何在 Direct2D1 繪製基本圖形,包括線段、矩形、橢圓 ...
本文來告訴大家如何在 Direct2D1 繪製基本圖形,包括線段、矩形、橢圓
本文是一個系列
本文的組織參考Direct2D,對大神表示感謝。
在開始前先告訴大家為何需要使用 Direct2D ,雖然 WPF 也是基於 DX 進行渲染,但是 WPF 做了很多相容處理,所以沒有比直接使用 Direct2D 的性能高。經過測試,在使用下麵的所有代碼,占用 CPU 幾乎都是 0% ,因為沒有佈局、透明和事件處理,所以速度是很快。
點
在 Direct2D 使用的 點是 Point2F ,傳入的是兩個 float ,和 Point 差不多。
Point2F 也是一個結構體,所以和 Point 類型差不多
線段
線段需要使用 DrawLine ,方法的簽名
public void DrawLine(Point2F firstPoint 起始點 , Point2F secondPoint 終點, Brush brush 筆刷, float strokeWidth 線段寬度)
public unsafe void DrawLine(Point2F firstPoint, Point2F secondPoint, Brush brush, float strokeWidth, StrokeStyle strokeStyle 線段樣式)
所以使用下麵的方法就可以在 (10,10) (100,10) 畫出一條寬度為 2 的紅線
_renderTarget.DrawLine(new D2D.Point2F(10, 10), new D2D.Point2F(100, 10),
_renderTarget.CreateSolidColorBrush(new D2D.ColorF(1, 0, 0, 1)), 2);
上面的代碼運行在WPF 使用 Direct2D1 畫圖入門文章的 OnRendering 方法,為了讓大家也可以試試下麵的代碼,建議大家先去看這篇博客。
關於筆刷會在後面說
StrokeStyle
可以看到上麵線段的最後一個參數是 StrokeStyle 那麼這個參數是如何創建?在 Direct2D 有很多類都不能直接直接創建需要使用 D2DFactory 或 RenderTarget 才能創建。StrokeStyle 就需要使用 D2DFactory 進行創建。
創建 StrokeStyle 需要參數 StrokeStyleProperties,這個類的構造有兩個重載,一個是不需要參數,另一個是需要很多參數。代碼請看下麵。
public StrokeStyleProperties(CapStyle startCap, CapStyle endCap, CapStyle dashCap, LineJoin lineJoin, float miterLimit, DashStyle dashStyle, float dashOffset)
從代碼的命名大概大家也可以知道 StrokeStyleProperties 參數的意思,下麵先創建一個沒有構造函數的來創建 StrokeStyle ,請看下麵代碼
var strokeStyleProperties = new D2D.StrokeStyleProperties();
var strokeStyle = d2DFactory.CreateStrokeStyle(strokeStyleProperties);
_renderTarget.BeginDraw();
_renderTarget.DrawLine(new D2D.Point2F(10,10),new D2D.Point2F(100,10), _renderTarget.CreateSolidColorBrush(new D2D.ColorF(1, 0, 0, 1)),2, strokeStyle);
_renderTarget.EndDraw();
需要註意,創建 strokeStyle 的工廠需要和創建 RenderTarget 一樣,如果使用不一樣的工廠就會出現下麵異常。
Microsoft.WindowsAPICodePack.DirectX.Direct2D1.Direct2DException:“EndDraw has failed with error: 一起使用的對象必須創建自相同的工廠實例。 (異常來自 HRESULT:0x88990012) Tags=(0,0).”
所以需要修改WPF 使用 Direct2D1 畫圖入門文章的代碼,把 D2DFactory 寫為欄位
public MainWindow()
{
InitializeComponent();
CompositionTarget.Rendering += OnRendering;
Loaded += (s, e) =>
{
var d2DFactory = D2D.D2DFactory.CreateFactory(D2D.D2DFactoryType.Multithreaded);
var windowHandle = new WindowInteropHelper(this).Handle;
var renderTarget = d2DFactory.CreateHwndRenderTarget(new D2D.RenderTargetProperties(),
new D2D.HwndRenderTargetProperties(windowHandle,
new D2D.SizeU((uint) ActualWidth, (uint) ActualHeight),
D2D.PresentOptions.RetainContents));
_redBrush = renderTarget.CreateSolidColorBrush(new D2D.ColorF(1, 0, 0, 1));
_greenBrush = renderTarget.CreateSolidColorBrush(new D2D.ColorF(0, 1, 0, 1));
_blueBrush = renderTarget.CreateSolidColorBrush(new D2D.ColorF(0, 0, 1, 1));
_renderTarget = renderTarget;
_d2DFactory = d2DFactory;
};
}
StrokeStyleProperties
關於 StrokeStyleProperties 需要說一下,就是各個參數。
從名字可以看到 StartCap 和 EndCap 就是線段的兩端的圖形,可以選的參數
- Flat
- Square
- Round
- Triangle
具體表示是什麼,我會使用下麵的例子
Flat
平的
var strokeStyleProperties = new D2D.StrokeStyleProperties();
strokeStyleProperties.StartCap = D2D.CapStyle.Flat;
strokeStyleProperties.EndCap = D2D.CapStyle.Flat;
var strokeStyle = _d2DFactory.CreateStrokeStyle(strokeStyleProperties);
_renderTarget.BeginDraw();
_renderTarget.DrawLine(new D2D.Point2F(10,10),new D2D.Point2F(100,10), _renderTarget.CreateSolidColorBrush(new D2D.ColorF(1, 0, 0, 1)),2, strokeStyle);
_renderTarget.EndDraw();
Round
圓的
float h = 10;
strokeStyleProperties.StartCap = D2D.CapStyle.Round;
strokeStyleProperties.EndCap = D2D.CapStyle.Round;
strokeStyle = _d2DFactory.CreateStrokeStyle(strokeStyleProperties);
h += 20;
_renderTarget.BeginDraw();
_renderTarget.DrawLine(new D2D.Point2F(10, h), new D2D.Point2F(100, h), _renderTarget.CreateSolidColorBrush(new D2D.ColorF(1, 0, 0, 1)), 5, strokeStyle);
_renderTarget.EndDraw();
Square
方形
strokeStyleProperties.StartCap = D2D.CapStyle.Square;
strokeStyleProperties.EndCap = D2D.CapStyle.Square;
strokeStyle = _d2DFactory.CreateStrokeStyle(strokeStyleProperties);
h += 20;
_renderTarget.BeginDraw();
_renderTarget.DrawLine(new D2D.Point2F(10, h), new D2D.Point2F(100, h), _renderTarget.CreateSolidColorBrush(new D2D.ColorF(1, 0, 0, 1)), 5, strokeStyle);
_renderTarget.EndDraw();
Triangle
三角形
strokeStyleProperties.StartCap = D2D.CapStyle.Triangle;
strokeStyleProperties.EndCap = D2D.CapStyle.Triangle;
strokeStyle = _d2DFactory.CreateStrokeStyle(strokeStyleProperties);
h += 20;
_renderTarget.BeginDraw();
_renderTarget.DrawLine(new D2D.Point2F(10, h), new D2D.Point2F(100, h), _renderTarget.CreateSolidColorBrush(new D2D.ColorF(1, 0, 0, 1)), 5, strokeStyle);
_renderTarget.EndDraw();
DashStyle
如果需要畫虛線就可以使用 DashStyle ,虛線顯示就是使用 CapStyle
strokeStyleProperties.DashStyle = D2D.DashStyle.DashDot;
strokeStyleProperties.DashCap = D2D.CapStyle.Square;
strokeStyleProperties.DashOffset = 2;
h += 20;
strokeStyle = _d2DFactory.CreateStrokeStyle(strokeStyleProperties);
_renderTarget.BeginDraw();
_renderTarget.DrawLine(new D2D.Point2F(10, h), new D2D.Point2F(100, h),
_renderTarget.CreateSolidColorBrush(new D2D.ColorF(1, 0, 0, 1)), 5, strokeStyle);
_renderTarget.EndDraw();
大家自己試一試就知道
裡面還有屬性 LineJoin 這個不是線段可以做的,是折線才可以使用,表示兩個線段如何鏈接
矩形
畫矩形使用 DrawRectangle ,參數需要傳入 RectF 需要傳入上下左右的浮點數。
_renderTarget.DrawRectangle(new D2D.RectF(10, 10, 100, 100), brush, 10);
矩形有兩個重載
public void DrawRectangle(RectF rect, Brush brush, float strokeWidth)
public unsafe void DrawRectangle(RectF rect, Brush brush, float strokeWidth, StrokeStyle strokeStyle)
矩形的 StrokeStyle 和線段一樣。
橢圓
實際上畫圓和橢圓是一樣的,畫圓的函數有兩個重載
public void DrawEllipse(Ellipse ellipse, Brush brush, float strokeWidth)
public unsafe void DrawEllipse(Ellipse ellipse, Brush brush, float strokeWidth, StrokeStyle strokeStyle)
需要先創建 Ellipse 和筆刷。
創建 Ellipse 需要給圓心和兩個軸,下麵創建一個圓心在 (100,100) ,兩個軸都是50的橢圓。實際上就是半徑是50的圓形。
var ellipse = new D2D.Ellipse(new D2D.Point2F(100, 100), 50, 50);
這就是繪製基本的圖形。
那麼如何填充圖形?實際上所有 Draw 都有對應的 Fill 函數,除了線段。所以填充就是調用對應的 Fill 函數。
嘗試運行程式,看看這時的 CPU ,實際上是幾乎不會動,因為所有的計算都在 GPU 計算。不過程式里的代碼包括創建圖形,實際上是在 CPU 創建,但是因為速度很快,幾乎不需要計算,所以需要的時間很短。
文字
最後就是告訴大家如何繪製文字。
繪製文字需要使用 DirectWrite ,需要先創建 DWriteFactory 然後才可以繪製文本。
繪製文本有多個方式,因為需要的很多參數都不能直接創建需要使用 DWriteFactory 創建,所以這裡需要先使用下麵代碼
var dWriteFactory = DWriteFactory.CreateFactory();
創建文字有多個方法
public void DrawText(string text, TextFormat textFormat, RectF layoutRect, Brush defaultForegroundBrush)
public void DrawText(string text, TextFormat textFormat, RectF layoutRect, Brush defaultForegroundBrush, MeasuringMode measuringMode)
public void DrawText(string text, TextFormat textFormat, RectF layoutRect, Brush defaultForegroundBrush, DrawTextOptions options)
public unsafe void DrawText(string text, TextFormat textFormat, RectF layoutRect, Brush defaultForegroundBrush, DrawTextOptions options, MeasuringMode measuringMode)
public unsafe void DrawTextLayout(Point2F origin, TextLayout textLayout, Brush defaultForegroundBrush)
public unsafe void DrawTextLayout(Point2F origin, TextLayout textLayout, Brush defaultForegroundBrush, DrawTextOptions options)
因為有很多個參數,需要大家自己去試試
下麵來寫出簡單文字
需要先創建 textFormat 需要告訴使用哪個字形,和字體大小
var textFormat = dWriteFactory.CreateTextFormat("宋體", 20);
下麵就是畫出文字,文字換行可以使用\n
,複雜的換行請使用文字重載方法,這裡我就不說了
_renderTarget.BeginDraw();
_renderTarget.DrawText("lindexi 本文所有博客放在 lindexi.oschina.io \n歡迎大家來訪問\n\n這是系列博客,告訴大家如何在 WPF 使用Direct2D1", textFormat, new D2D.RectF(10, 10, 1000, 1000), brush);
_renderTarget.EndDraw();
需要說的是 Windows API Code Pack 1.1 已經很久沒更新,而且有錯誤,所以建議使用 SharpDX
參見:Using Direct2D with WPF - CodeProject
https://jeremiahmorrill.wordpress.com/2011/02/14/a-critical-deep-dive-into-the-wpf-rendering-system/