WPF基于物理像素繪制圖形
WPF中有一個DrawingContext類,該類提供了很多畫法方法,例如DrawLine,DrawText,DrawRectangle等。開發(fā)者使用它們可以方便地進行圖形繪制。不過,在使用DrawingContext過程中,我發(fā)現(xiàn)使用DawLine方法畫出的線條在某些部分有些模糊。這個問題的解決,需要一些計算機圖形學(xué)方面的知識,使用的方法并不是很復(fù)雜。不過,這個方法所涉及到的一些過程有些令人費解(好吧,我沒有專門學(xué)過計算機圖形學(xué)),本文是我在實踐中的一些嘗試和經(jīng)驗的總結(jié)。
還是從一個例子開始吧:
從FrameworkElement繼承一個MyRectangleElement,然后重寫OnRender方法如下:
protected override void OnRender(DrawingContext drawingContext) { Pen pen = new Pen(Brushes.Black, 1); Rect rect = new Rect(20,20, 50, 60); drawingContext.DrawRectangle(null, pen, rect); }
然后在Window上呈現(xiàn)MyRectangleElement,效果(右下角有放大鏡)如下:
通過放大圖形,能夠很清晰地看到線條是不均勻的,在宏觀視覺效果上感覺模糊,看上去很不舒服。于是,兩個問題就產(chǎn)生了:
- 為什么會出現(xiàn)這樣的問題?
- 如何解決?
怎么辦?MSDN,百度,Google一起上!皇天不負苦心人,現(xiàn)在,謎底來到了我們的面前:
1、為什么會出現(xiàn)這樣的效果呢?
WPF呈現(xiàn)引擎的反鋸齒系統(tǒng)將那些沒有在物理像素系統(tǒng)上的線擴展到了多個像素上。這里涉及到以下三個概念:
- 物理像素系統(tǒng):與物理圖形設(shè)備相關(guān)??梢院唵蔚乩斫鉃橐粋€像素點的二維矩陣;
- 邏輯像素系統(tǒng):我們在畫法方法中所使用的定位系統(tǒng);
- 反鋸齒效果:一種計算機圖形學(xué)上的算法,使得圖形邊緣更光滑;
聲明:以上三個概念的解釋是根據(jù)我個人的理解,不一定準確、嚴謹。如果您發(fā)現(xiàn)什么不對的地方,還望不吝指正。
為了更好地幫助您理解這個過程,這里給出一個示意圖來解釋一下:
上圖中的淡藍色網(wǎng)格可以視為“物理像素系統(tǒng)”,深色的圖案是您畫出的線條。可以很明顯地看出,這條線的四條邊都不在“物理像素系統(tǒng)”上,因此,“反鋸齒系統(tǒng)”會將此線條的四條邊擴展到相應(yīng)的“物理像素系統(tǒng)”上。于是,本文最開始的情景便出現(xiàn)了。
2、如何解決這個問題?
針對不同的問題上下文,WPF給出了與之相應(yīng)的解決方案。據(jù)我所知,有如下幾個:
- 1> 對于UIElement及其子類,使用SnapsToDevicePixels屬性
對于從UIElement繼承的類型,比如Control,通過將SnapsToDevicePixels設(shè)置為true可以得到清晰的圖像,該屬性的默認值為false。FrameworkElement從UIElement繼承的時候,給這個屬性賦予了一個Inherited元數(shù)據(jù)。如此一來,只要您在FrameworkElement Tree的根結(jié)點上將此屬性設(shè)置為true,那么整個FrameworkElement Tree的繪制都將變得清晰起來。
- 2> 對于自定義畫法,使用GuidelineSet
SnapsToDevicePixels屬性對于WPF Control來說是有用的,但是對本文的問題無能為力,于是,嗯,GuidelineSet橫空出世!對于這個類,MSDN只是給出了一個用法的示例,從這個例子中我只能看到GuidelineSet可以這么用,但為什么是這樣用就沒有答案了。而且,有點離譜、神奇的是:MSDN上關(guān)于這點上一個示意圖內(nèi)容上有錯。如下圖所示:
圖下部的左黑框是用了GuidelineSet后出現(xiàn)的結(jié)果,右邊才是沒有使用的結(jié)果。這兩個圖的位置應(yīng)該互換一下。
我們必須回答這個問題:如果在(x1,y1) (x2,y2)處畫一條線該如何在DrawingContext上使用GuidelineSet,以保證畫法是清晰的呢?
這個問題讓我著實納悶了許久(原因本文第一段已經(jīng)交代),不過,經(jīng)過不斷地嘗試和思考,最終我找到了答案:
Guideline其實是圖形設(shè)備在呈現(xiàn)時用來把邏輯像素點對齊到物理像素點的參考量。 使用它告訴圖形設(shè)備你希望哪些邏輯像素點被對齊到物理像素點上。
聲明:以上概念的解釋是根據(jù)我個人的理解,不一定準確、嚴謹。如果您發(fā)現(xiàn)什么不對的地方,還望不吝指正。
下面,我將使用一個簡單的示例來演示如何使用GuidelineSet,以及它所帶來的效果。在這個示例中,我們使用DrawingContext的DrawLine方法繪制一個10×10的網(wǎng)格,相關(guān)代碼如下:
首先,定義畫法所用到的常量
internal static class DrawingConstants { public static readonly int Rows = 10; public static readonly int Columms = 10; public static readonly double PenThickness = 1.0; public static readonly double HalfOfPenThickness = PenThickness/2; }
然后,定義NormalDrawingElement(使用一般畫法):
class NormalDrawingElement : FrameworkElement { protected override void OnRender(System.Windows.Media.DrawingContext drawingContext) { base.OnRender(drawingContext); double xOffset = Math.Floor(this.RenderSize.Width / DrawingConstants.Columms); double yOffset = Math.Floor(this.RenderSize.Height / DrawingConstants.Rows); double xLineWidth = Math.Floor(this.RenderSize.Width); double yLineHeight = Math.Floor(this.RenderSize.Height); DrawingContext dct = drawingContext; Pen blackPen = new Pen(Brushes.Black, DrawingConstants.PenThickness); blackPen.Freeze(); //Draw the horizontal lines Point x = new Point(0, 0); Point y = new Point(xLineWidth, 0); for (int i = 0; i <= DrawingConstants.Rows; i++) { dct.DrawLine(blackPen, x, y); x.Offset(0, yOffset); y.Offset(0, yOffset); } //Draw the vertical lines x = new Point(0, 0); y = new Point(0, yLineHeight); for (int i = 0; i <= DrawingConstants.Columms; i++) { dct.DrawLine(blackPen, x, y); x.Offset(xOffset, 0); y.Offset(xOffset, 0); } } }
定義GuidelineSetDrawingElement(使用GuidelineSet):
class GuidelineSetDrawingElement : FrameworkElement { protected override void OnRender(System.Windows.Media.DrawingContext drawingContext) { base.OnRender(drawingContext); double xOffset = Math.Floor(this.RenderSize.Width / DrawingConstants.Columms); double yOffset = Math.Floor(this.RenderSize.Height / DrawingConstants.Rows); double xLineWidth = Math.Floor(this.RenderSize.Width); double yLineHeight = Math.Floor(this.RenderSize.Height); DrawingContext dct = drawingContext; Pen blackPen = new Pen(Brushes.Black, DrawingConstants.PenThickness); blackPen.Freeze(); //Draw the horizontal lines Point x = new Point(0, 0); Point y = new Point(xLineWidth, 0); for (int i = 0; i <= DrawingConstants.Rows; i++) { dct.PushGuidelineSet(new GuidelineSet(null, new double[] { y.Y - DrawingConstants.HalfOfPenThickness, y.Y + DrawingConstants.HalfOfPenThickness})); dct.DrawLine(blackPen, x, y); dct.Pop(); x.Offset(0, yOffset); y.Offset(0, yOffset); } //Draw the vertical lines x = new Point(0, 0); y = new Point(0, yLineHeight); for (int i = 0; i <= DrawingConstants.Columms; i++) { dct.PushGuidelineSet(new GuidelineSet(new double[] { x.X + DrawingConstants.HalfOfPenThickness, x.X - DrawingConstants.HalfOfPenThickness}, null)); dct.DrawLine(blackPen, x, y); dct.Pop(); x.Offset(xOffset, 0); y.Offset(xOffset, 0); } } }
這個畫法和上一個畫法的區(qū)別僅僅在于以下兩點:
- 對于水平方向的線,我期望它的兩個水平邊緣是和”物理像素系統(tǒng)“對齊的;
- 對于垂直方向的線,我期望它的兩個垂直邊緣是和”物理像素系統(tǒng)“對齊的。
最后,我們將這兩個Element呈現(xiàn)出來:
<Window x:Class="GuidelineSetDemo.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:loc="clr-namespace:GuideLineSetDemo" Title="MainWindow" Height="350" Width="525"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="*" /> <ColumnDefinition Width="*" /> </Grid.ColumnDefinitions> <Grid Grid.Column="0"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <TextBlock Text="Normal Drawing" Margin="3" HorizontalAlignment="Center"/> <loc:NormalDrawingElement Margin="10" Grid.Row="1"/> </Grid> <Grid Grid.Column="1"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <TextBlock Text="Dawing with GuidelineSet" Margin="3" HorizontalAlignment="Center"/> <loc:GuidelineSetDrawingElement Margin="10" Grid.Row="1"/> </Grid> </Grid> </Window>
運行后的效果如下:
另外,使用GuidelineSet的時候需要注意以下幾個細節(jié):
- 1、Push和pop一般情況下要成對(其實當(dāng)您調(diào)用DrawingContext上的PushXXX方法后,都要考慮是否有與之對應(yīng)的Pop方法調(diào)用);
- 2、GuidelineSet只對水平或者垂直線有用;
- 3、使用GuidelineSet后,您所繪制圖形的位置或大小可能和最初的設(shè)定有細微的差別。
您可以從這里下載本文最后一個示例的源代碼。
到此這篇關(guān)于WPF基于物理像素繪制圖形的文章就介紹到這了。希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
使用VS2010 C#開發(fā)ActiveX控件(下),完整代碼打包下載
我們介紹了開發(fā)、打包、發(fā)布、使用ActiveX控件的全過程。在演示程序中,我們沒有調(diào)用串口通信和讀卡器Dll程序,由于我們讀卡器的原始Dll是使用其它語言進行開發(fā)的,對C#來說,是非托管代碼,因此我們還需要在代碼級別進行非托管代碼的安全性設(shè)置2011-05-05Winform控件Picture實現(xiàn)圖片拖拽顯示效果
這篇文章主要為大家詳細介紹了Winform控件Picture實現(xiàn)圖片拖拽顯示效果,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-09-09HashTable、HashSet和Dictionary的區(qū)別點總結(jié)
在本篇文章里小編給大家整理的是關(guān)于HashTable、HashSet和Dictionary的區(qū)別點,需要的朋友們可以學(xué)習(xí)下。2020-03-03C# winform 請求http的實現(xiàn)(get,post)
本文主要介紹了C# winform 請求http的實現(xiàn)(get,post),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-06-06