「石鹞」はWPFのどこから来お、どのように察凊するのか



このガむドは、アプリケヌションで可胜な限り鮮明な画像を取埗しようずしおいるWPF開発者を察象ずしおいたす。 骚の骚髄ぞのグラフィックシステムWPFはベクトルですが、その䜜業の最終結果は䟝然ずしおラスタヌです。 この事実に十分な泚意を払わないず、さたざたな皮類の「石鹞」、぀たりラスタヌ化の寄生アヌティファクトが発生する可胜性がありたす。 そのような状況では、心の存圚を倱わないこずが重芁であり、その発生の理由は非垞に合理的であり、闘争の方法は非垞にシンプルで効果的です。

目次


はじめに
1.ラスタヌ画像のスケヌリング
2.ピクセルサむズの倍数ではない座暙
3.ラスタヌ画像のネむティブ解像床
4.ベクタヌ画像のラスタラむズ
5.テキストを垂盎に移動する
6. SnapsToDevicePixelsプロパティの䜿甚
7.自己描画コントロヌル
おわりに
参照資料

はじめに


ラスタラむれヌションアヌティファクトの陰湿性は、それらが目立たないずいう事実にありたす。 倚くの開発者は、1ピクセルたたは2ピクセルのサむズの欠陥に気付かないだけです。 ただし、これらのささいなこずがアプリケヌションのナヌザヌ゚クスペリ゚ンスに圱響したす。

泚意力の小さなテスト


さらに、䞊の図ず䞋の図を区別する芁因、およびそれらを陀去する方法に぀いお説明したす。 アプリケヌションにこのような問題がない堎合は、Windows蚭定でテキストずむンタヌフェむス芁玠を増やすモヌドをオンにしおみおください。ほずんどの堎合、問題が発生したす。 この機胜は、高解像床の小さな画面たたは匱芖の人だけがよく䜿甚したす。


すべおをすべお読む必芁はありたせん。むラストの衚瀺に制限するこずもできたす。 おそらくそれらを芚えおおり、アプリケヌションのグラフィック出力を明確にするために本圓に戊う必芁がある堎合は、このガむドに戻りたす。

マニュアルの各セクションには、問題の問題ずその解決方法を瀺すデモアプリケヌションが備わっおいたす。 コンパむルされたモゞュヌルずその゜ヌスコヌドVS2010プロゞェクト圢匏を含む単䞀のアヌカむブ104 Kbですべおをダりンロヌドできたす。

それでは、WPFはどこから来お、どのように察凊するのでしょうか

1.ラスタヌ画像のスケヌリング


ビットマップ画像で䜜業する堎合、がかしの最も䞀般的な原因は出力䞭のスケヌリングです。 画像の物理的なサむズず䞀臎しない画像芁玠のサむズを指定するず、結果は゜ヌスに䌌なくなりたす。 むメヌゞサむズをコンテナサむズに自動的に調敎するず、倚くの堎合、同様の結果になりたす。 この堎合、アヌティファクトが発生する理由は、あるラスタグリッドから別のラスタグリッドに画像を転送する必芁があるためです。



察抗

画像がスケヌリングコンテナヌに誀っお衚瀺された堎合は、そこから取り出す必芁がありたす。 画像の寞法が正しくない堎合は、調敎する必芁がありたす。 すべおがシンプルです。 ただし、暙準蚭定でアプリケヌションをテストする堎合に限りたす。 Windowsで拡倧フォントずむンタヌフェむス芁玠のモヌドを有効にするず、WPFアプリケヌションの出力の解像床が倉わり、仮想枬定単䜍がピクセルサむズよりも倧きくなり、ビットマップむメヌゞがストレッチによっお絶望的に砎損したす。

より倧きなフォントを䜿甚するナヌザヌ向けの画像スケヌリングが蚈画に含たれおいない堎合は、その堎で修正する必芁がありたす。 最初に、出力の珟圚の解像床ず暙準ずの関係を調べる必芁がありたす。 たずえば、次のように

public static class Render { static Render() { var flags = BindingFlags.NonPublic | BindingFlags.Static; var dpiProperty = typeof(SystemParameters).GetProperty("Dpi", flags); Dpi = (int)dpiProperty.GetValue(null, null); PixelSize = 96.0 / Dpi; } //      public static double PixelSize { get; private set; } //  public static int Dpi { get; private set; } } 

次に、出力の珟圚の解像床に応じお、暙準画像の埌継で画像の寞法を個別に蚭定できたす。 XAMLでは、そのような画像は特定のサむズを指定せずに配眮する必芁がありたす。

 public class StaticImage : Image { static StaticImage() { //    Image.SourceProperty.OverrideMetadata( typeof(StaticImage), new FrameworkPropertyMetadata(SourceChanged)); } private static void SourceChanged( DependencyObject obj, DependencyPropertyChangedEventArgs e) { var image = obj as StaticImage; if (image == null) return; //      image.Width = image.Source.Width * Render.PixelSize; image.Height = image.Source.Height * Render.PixelSize; } } 



スケヌリングを回避できない堎合、 RenderOptions.BitmapScalingModeプロパティを䜿甚しお、ケヌスに適した出力アルゎリズムを遞択するこずにより、歪みの皋床を枛らすために戊うだけです。



2.ピクセルサむズの倍数ではない座暙


蚈画倖のがかしを䜜成するかなり簡単な方法は、コンテナ内の画像を䞭倮に配眮するこずです 四角圢や他のShapeの子孫でも機胜したす。 半分の堎合、コンテナの幅は完党に半分に分割されおいたせん。

ただし、センタリングせずに実行できたす。 たずえば、 グリッドコンテナを䜿甚するず、任意の割合で自分自身をパヌツに分割できたす。 巊䞊のセルですべおが正垞であり、右䞋のセルで霧があり、残りが他の䜕かである兞型的なケヌスを次に瀺したす。

 <!--      --> <Grid Width="117" Height="117"> <Grid.RowDefinitions> <RowDefinition Height="*"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <!--    --> <Grid.Resources> <Style TargetType="Image"> <Setter Property="Width" Value="48"/> <Setter Property="Height" Value="48"/> <Setter Property="Margin" Value="5"/> <Setter Property="VerticalAlignment" Value="Top"/> <Setter Property="HorizontalAlignment" Value="Left"/> <Setter Property="Source" Value="CookingPot.png"/> </Style> </Grid.Resources> <Image Grid.Column="0" Grid.Row="0"/> <Image Grid.Column="1" Grid.Row="0"/> <Image Grid.Column="0" Grid.Row="1"/> <Image Grid.Column="1" Grid.Row="1"/> </Grid> 


キャンバスのむンデントたたはピクセルのサむズの倍数ではない䜍眮にタスクの画像を塗り付けるのに効果的です。

これらのすべおの堎合、出力䞭にスケヌリングが発生しなくおも、「石鹞」が埗られたす。 画像のピクセルの境界は、画面のピクセルの境界内に収たらず、ラスタラむズされるず塗り぀ぶされたす。


察抗

コンテナの堎合、すべおがシンプルです。UseLayoutRoundingプロパティをTrueに蚭定するず、コンテナ Windowを含む が子の䜍眮を最も近い敎数ピクセル倀に自動的に䞞めたす。 それ以倖の堎合は、䜕らかの方法で座暙をピクセルの境界に明瀺的にバむンドする必芁がありたす。

「敎数座暙」ずいう衚珟は、暙準解像床96 dpiでのみ「ピクセルのサむズで割り切れる座暙」を意味するこずに泚意しおください。他のすべおのMath.Roundは圹に立ちたせん。 䞀般的な堎合、次のように座暙を最も近いピクセル境界に䞞めるこずができたす。

 static public double SnapToPixels(double value) { value += PixelSize / 2; //  DPI    WPF- . //  1000  -   //     double //2.4 / 0.4 = 5.9999999999999991 //240.0 / 40.0 = 6.0 var div = (value * 1000) / (PixelSize * 1000); return (int)div * PixelSize; } 


3.ラスタヌ画像のネむティブ解像床


ビットマップ画像のスケヌリングの問題に初めお遭遇したずき、 Imageにスケヌリングなしの魔法の出力モヌドがある堎合は、ほずんどの堎合googleを詊行したす。 そのようなモヌドがありたす。それはStretch = "None"です 。しかし、あなたは危険にさらされおいるので、それに頌っお出力サむズの明瀺的なタスクを削陀する必芁がありたす。 ラスタヌ画像には独自の解像床があり、メタデヌタで瀺され、WPFは画像の寞法を圢成するずきにそれを考慮したす。 それが䜕であるかわからない堎合、䞀連の成功した状況䞋で、あなたは黒魔術を信じるこずができたすあなたが持っおいる画像のいく぀かは期埅どおりに描かれ、それらず完党に類䌌したものは同じ条件䞋で膚匵たたは収瞮したす。

実隓甚に次の3぀の画像をダりンロヌドしたすそれぞれ解像床が異なりたす。



画像の解像床が出力の解像床ず䞀臎しない堎合、そのサむズを読み蟌むずきに解像床の比率が乗算され、「元の」サむズでレンダリングするずきに突然スケヌリングが発生したす。 ちなみに、最初のメ゜ッドで瀺したStaticImageクラスもSource.WidthおよびSource.Heightプロパティに䟝存しおいるため、これらの歪みから保護されおいたせん。

グラフィック゚ディタヌやりォッチメンは、あなたの目の前で画像解像床を調敎するこずはありたせん。 さらに、これらのプログラムの䞀郚はたったく衚瀺されず、このパラメヌタヌを倉曎できたせん。 しかし、それは重芁です。

 <!--    ,   --> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="Auto"/> </Grid.ColumnDefinitions> <Image Grid.Column="0" Source="Man1.png" Stretch="None" Margin="5"/> <Image Grid.Column="1" Source="Man2.png" Stretch="None" Margin="5"/> <Image Grid.Column="2" Source="Man3.png" Stretch="None" Margin="5"/> </Grid> 




察抗

仮想単䜍の画像のサむズをピクセル単䜍のサむズに察応させるには、グラフィック゚ディタヌで解像床を96 dpiに蚭定する必芁がありたす。 解像床を倉曎しおも画像自䜓には䜕も起こりたせん。メタデヌタのみが倉曎されたす。

暙準ペむントはこれには適しおいたせん。もっず深刻なものが必芁になりたす。 人気のあるIrfanViewフリヌりェアでは、画像プロパティ衚瀺ダむアログホットキヌIで解像床を蚭定できたす。


同様に無料のPaint.NET゚ディタヌでは、[画像]メニュヌ、[キャンバスサむズ...]ホットキヌCtrl + Shit + Rを遞択するこずで同じ効果を埗るこずができたす。


グラフィカル゚ディタヌで解像床を䜿甚したくない、たたは䜿甚できない堎合たずえば、アプリケヌションがダりンロヌド可胜なカスタム画像で動䜜する堎合、ダりンロヌドした画像の解像床をプログラムで倉曎できたす。 32ビットむメヌゞのダりンロヌド関数の䟋を次に瀺したす。

 // Image.Source    96 dpi BitmapSource ConvertBitmapTo96DPI(string path) { var uri = new Uri(path); var bitmapImage = new BitmapImage(uri); int width = bitmapImage.PixelWidth; int height = bitmapImage.PixelHeight; int stride = width * 4; // 4    var pixelData = new byte[stride * height]; bitmapImage.CopyPixels(pixelData, stride, 0); return BitmapSource.Create( width, height, 96, 96, PixelFormats.Bgra32, bitmapImage.Palette, pixelData, stride); } 


4.ベクタヌ画像のラスタラむズ


「石鹞」の出珟の最初の3぀の理由の説明から、WPFのcビットマップには1぀の問題があるずいう十分に根拠のある意芋があるかもしれたせん。 実際、ベクタヌ環境で䜜業するには、ベクタヌ画像を䜿甚する方がはるかに自然です。 最初の実隓では、SVGからXAMLぞの倉換は䞇胜薬のようであり、サむズずピクセルに぀いお考える必芁がなくなりたした。 悲しいかな、これはそうではありたせん。 真倜䞭になるず、キャリッゞはカボチャに倉わり、ベクトル画像は衚瀺甚にラスタラむズされたす。

出力に含たれるピクセルが少ないほど、アヌティファクトが倚くなりたす。 48ピクセル以䞋のサむズの画像これはデスクトップアプリケヌションのすべおのグラフィックスのほが80ですでは、状況は次のように瞮退したすベクタヌ画像は1぀の解像床のみで正しくラスタラむズされ、それ以倖の堎合に最適化されたす。 準備された間違ったサむズでベクトルアむコンを衚瀺するず、容赊ないアンチ゚むリアシングはあなたを埅たせたせん。



察抗

堎合によっおは、画像サむズを単玔に増やすだけで察応できたす。 たずえば、ツヌルバヌのボタンには32x32ピクセルのサむズの画像を䜿甚し、コンテキストメニュヌには25x25のアむコンを䜿甚したす。 ただし、ベクトルアむコンのラスタラむズ方法が本圓に重芁な堎合は、特定の解像床に最適化する必芁がありたす。画像の必芁な詳现は、出力ラスタのピクセル境界ず䞀臎する必芁がありたす。

5.テキストを垂盎に移動する


テキストを衚瀺するずき、WPFはいく぀かのシャヌプ化手法を䜿甚したす。 テキストは静的ですが、その䜍眮ず遞択されたラスタヌ化モヌド.NET Framework 4.0以降は可胜な限り明確に芋えたす。 垂盎方向に移動するず、特定のせん断倀で「sharpilka」が急激にオフになり、その埌埐々にオンに戻りたす。

次に、テキストアニメヌションが抌されたずきにボタンに衚瀺される停のがかし効果の䟋を瀺したす。

 <Button VerticalAlignment="Top"> <Button.Template> <ControlTemplate TargetType="Button"> <Border Width="255" Height="40" BorderThickness="1 0 1 1" CornerRadius="0 0 10 10" BorderBrush="#FF202020" Background="#FFF7941D"> <StackPanel Name="Panel" Orientation="Horizontal"> <Label Content="     " Foreground="#FF202020" VerticalAlignment="Center" Margin="20 0 0 0" Padding="0"/> </StackPanel> </Border> <ControlTemplate.Triggers> <Trigger Property="IsPressed" Value="True"> <Setter TargetName="Panel" Property="Margin" Value="3 1 -3 -1"/> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Button.Template> </Button> 


この䟋のがけ具合はかなり䞍思議です。 どうやら、䞊の境界線の欠劂、 StackPanelでのテキストのネスト実際の状況ではボタンにも画像がありたした、およびテキストの1〜2ピクセル䞋ぞの急激なシフトの組み合わせが機胜しおいるようです。

察抗

.NET Framework 4.0以降では、 TextOptions属性を䜿甚しお、 IdealずDisplayの 2぀のテキストラスタラむズモヌドから遞択できたす。 これにより、䞍快ながかし効果がわずかに枛少したす。 フレヌムワヌクの以前のバヌゞョンでは、ラスタラむズモヌドは理想モヌドに察応しおいたす。文字はピクセルグリッドに収たり、ラスタラむズされたす。 衚瀺モヌドでは、䞭間凊理が䜿甚されたす。氎平テキストは垞にピクセルに明確に添付され、同じ文字が同じようにラスタラむズされたす。 テキスト出力モヌドの詳现に぀いおは、 こちらずこちらをご芧ください 。

急なテキスト移動のがかし効果は、実隓宀で簡単に再珟できたす。 ピクセルのサむズの倍数ではない䜍眮に垂盎に移動するだけで十分です。 以䞋は、3぀のテキストブロックの平行移動の䟋です。 同じ出力モヌドの䞊の2぀のブロックは、異なっおがやけおいたす。 重芁なのはシフト倀ではなく、ラスタグリッドに察するテキストの䜍眮です。


テキストを移動するずいう単なる事実は、必ずしも「動的ながかし」に぀ながりたせん。 この効果が発生するず、行党䜓に圱響したす。 残念ながら、開発者にはこの効果を制埡する手段がありたせん。 堎合によっおは、ピクセルの境界に沿っおブロックの座暙を揃えるか、経隓的にシフトの量を遞択するこずにより、その発生を回避できたす。

6. SnapsToDevicePixelsプロパティの䜿甚


Rectangle 、 Ellipse 、 Line 、 Path 、 Borderなどの基本的な芖芚芁玠を䜿甚する堎合、ピクセルサむズの倍数ではない座暙で衚瀺するず、垂盎線ず氎平線のがかしが確実に衚瀺されたす。 指定された芁玠を䜿甚しお構築された画像の䟋を次に瀺したす。

 <!--      --> <Grid HorizontalAlignment="Center" VerticalAlignment="Center"> <Grid.RowDefinitions> <RowDefinition Height="10"/> <RowDefinition Height="20"/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="25"/> <ColumnDefinition Width="6"/> </Grid.ColumnDefinitions> <!--  (  ,  ) --> <Ellipse Grid.Column="0" Grid.Row="0" Grid.RowSpan="2" Fill="Black" Width="10" Height="10" VerticalAlignment="Top" Margin="15 5 0 0"/> <Line Grid.RowSpan="2" X1="10" X2="20" Y1="1" Y2="11" Stroke="Black"/> <Line Grid.ColumnSpan="2" Grid.RowSpan="2" X1="30" X2="20" Y1="1" Y2="11" Stroke="Black" /> <!--    (   ) --> <Border Grid.ColumnSpan="2" Grid.Row="1" Background="#FFF7941D"/> <Rectangle Grid.Column="0" Grid.Row="1" Fill="White" RadiusX="3" RadiusY="3" Margin="2.5"/> <!--  (  1,    ) --> <Line Grid.Column="1" Grid.Row="1" StrokeThickness="1" Stroke="Black" X1="0" X2="4" Y1="3" Y2="3"/> <Line Grid.Column="1" Grid.Row="1" StrokeThickness="1" Stroke="Black" X1="0" X2="4" Y1="5" Y2="5"/> <Line Grid.Column="1" Grid.Row="1" StrokeThickness="1" Stroke="Black" X1="0" X2="4" Y1="7" Y2="7"/> </Grid> 


以䞋の図では、デモ画像を䜿甚したコントロヌルが0.2ピクセルのステップで移動したす。 たず、圌はこれを各軞に沿っお個別に行い、次に円匧に沿っお行いたす。 移動のフェヌズに応じお、コントロヌルのロヌカル座暙グリッドは、物理的なラスタグリッドに異なっお重ねられたす。



テレビの倖偎の境界がはっきりしおいるこずもあれば、画面の境界がはっきりしおいるこずもありたすが、同時に起こるこずはありたせん。 ボタンは氎平たたは垂盎のいずれかではっきりしおおり、倚くの堎合、䞡方の軞でがやけおいたす。 任意の組み合わせのアンテナは少し滑らかであり、このため耇雑ではありたせん。

察抗

SnapsToDevicePixelsプロパティをTrueに蚭定するこずにより、ピクセルバむンディングのコントロヌルを有効にできたす これを行うには、ルヌトグリッドでこの属性を蚭定するだけです。 結果はより安定したす。



ただし、画像は移動しおも䞍倉ではなく、理想的にはピクセルにスナップされたせん。 テレビ画面は䞡方の軞で1ピクセル以内で揺れ、ボタンは垞にがやけおいたす。

SnapsToDevicePixelsプロパティをTrueに蚭定するず、レンダリング時に芖芚芁玠が画面ピクセルの境界内に収たるようになりたす。 各コントロヌルは、異なる熱意ず異なる方法でこれを行うよう努めたす。 たずえば、 Image 、 Label、およびTextBlockは、この属性ずはたったく無関係です。 ゜ヌスゞオメトリが成功した堎合のみ、 ラむンはピクセルにヒットしたす。 反察に、 Rectangleはズボンから飛び出し、垞にピクセルにヒットしたす。

より安定したピクセルスナップを行うには、元の画像を調敎する必芁がありたす。
  1. ボタンを衚す線のすべおのY座暙に0.5を远加しお、ボタンの゚ッゞがコントロヌルスペヌスのピクセルグリッドず䞀臎するようにしたす。
  2. TV画面の敎数、たずえば2をむンデントしお、バむンディングを最も近いピクセル境界に揺らさないようにしたす。



ずころで、これらのアクションは、96 dpiの暙準解像床でのみ明確で安定した出力を取埗するのに圹立ちたすが、残りは混乱ず動揺を残したす。 任意の解像床でピクセルを取埗するには、「自己描画コントロヌル」セクションの掚奚事項を参照する必芁がありたすコントロヌルのサむズは、ピクセルの物理的なサむズに基づいお、倖出先で調敎する必芁がありたす。

7.自己描画コントロヌル


芖芚芁玠でOnRenderをオヌバヌラップし、 DrawingContextを䜿甚しお自分で描画する堎合、前の方法のShape継承者ずたったく同じラスタ化の問題がありたすが、 SnapsToDevicePixels機胜を自分で実装する必芁がありたす。 もちろん、必芁な堎合。 あなたはこのようなこずをするこずはできたせん

 public class Washer : FrameworkElement { public Washer() { _brush = new SolidColorBrush(Color.FromRgb(247, 148, 29)); _brush.Freeze(); _pen = new Pen(Brushes.Black, 1); _pen.Freeze(); } protected override void OnRender(DrawingContext dc) { // dc.DrawLine(_pen, new Point(1, 21), new Point(4, 21)); dc.DrawLine(_pen, new Point(12, 21), new Point(15, 21)); // var rect = new Rect(0, 0, 16, 21); dc.DrawRectangle(_brush, null, rect); // dc.DrawLine(_pen, new Point(12, 1), new Point(12, 4)); dc.DrawLine(_pen, new Point(14, 1), new Point(14, 4)); // dc.DrawEllipse(Brushes.White, _pen, new Point(8, 11), 5, 5); //   rect = new Rect(10, 10, 4, 2); dc.DrawRectangle(Brushes.White, _pen, rect); } private Pen _pen; private Brush _brush; } 

䞎えられた座暙はすべお敎数であるずいう事実にもかかわらず、制埡空間内のピクセルグリッド䞊の重ね合わせは次のように発生したす。


ペンの幅は、座暙で指定された線から䞡方向にカりントされたす。 ペンの半分がピクセルの幅で完党に分割されおいない堎合、指定された座暙がピクセルの境界にぎったり合っおいおも、ラむンの゚ッゞはピクセルの境界内に収たりたせん。 画像が耇雑な堎合、画像が動かないこずが刀明する堎合がありたすが、すべおのコンポヌネントがクリアであるこずを実珟するこずは機胜したせん。 この図は、0.2ピクセル単䜍での画像の動きを瀺しおいたす。



察抗

WPFは、ピクセルにスナップするための特別なツヌルを提䟛したす- ガむドラむン 。 コントロヌルをレンダリングするための䞀連のアクションを圢成する段階でこれがOnRenderメ゜ッドの機胜です 、コントロヌル空間の垂盎および氎平座暙を指定できたす。これは、出力時にピクセルの境界に正確にフィットする必芁がありたす。


コヌドでは、次のようになりたす OnRenderメ゜ッドのみ 

 protected override void OnRender(DrawingContext dc) { double halfPen = _pen.Thickness / 2; // var snapX = new double[] { 1, 12 }; var snapY = new double[] { 21 + halfPen }; dc.PushGuidelineSet(new GuidelineSet(snapX, snapY)); dc.DrawLine(_pen, new Point(1, 21), new Point(4, 21)); dc.DrawLine(_pen, new Point(12, 21), new Point(15, 21)); dc.Pop(); // snapX = new double[] { 0, }; snapY = new double[] { 21 }; dc.PushGuidelineSet(new GuidelineSet(snapX, snapY)); var rect = new Rect(0, 0, 16, 21); dc.DrawRectangle(_brush, null, rect); dc.Pop(); // snapX = new double[] { 12 - halfPen }; snapY = new double[] { 1 }; dc.PushGuidelineSet(new GuidelineSet(snapX, snapY)); dc.DrawLine(_pen, new Point(12, 1), new Point(12, 4)); dc.DrawLine(_pen, new Point(14, 1), new Point(14, 4)); dc.Pop(); // snapX = new double[] { 3 - halfPen }; snapY = new double[] { 6 - halfPen }; dc.PushGuidelineSet(new GuidelineSet(snapX, snapY)); dc.DrawEllipse(Brushes.White, _pen, new Point(8, 11), 5, 5); dc.Pop(); //   snapX = new double[] { 10 - halfPen }; snapY = new double[] { 10 - halfPen }; dc.PushGuidelineSet(new GuidelineSet(snapX, snapY)); rect = new Rect(10, 10, 4, 2); dc.DrawRectangle(Brushes.White, _pen, rect); dc.Pop(); } 

ガむドを䜿甚するには、珟圚の出力解像床を知る必芁はありたせん。 コントロヌルのロヌカル座暙に適切に配眮するだけで十分です。 䞊蚘の䟋は、96 dpiの暙準解像床でのみ正しく機胜したす。96dpiでは、ペンの幅がピクセルサむズず䞀臎したす。 他の解像床で明確な境界線を実珟するには、各偎面にガむドを割り圓おる必芁がありたす。



重芁なニュアンスは、ガむドがスタックを介しおDrawingContextず察話するず同時に、ガむドが含たれるシェむプだけでなく、珟圚の出力党䜓に圱響を䞎えるこずです。 そのため、この䟋では、2本の平行線を揃えるために、各軞に1぀のガむドのみが䜿甚されおいたす。 䜿甚されおいるすべおのガむドを収集し、それらを䞀床にスタックにプッシュするず、結果は悲惚なものになりたす。 競合のため、それらの䞀郚のみが機胜し、残りは無芖されたす。

ガむドに沿ったピクセル境界ぞの䜍眮合わせは䞡方向で実行されるため、異なる状況では、画像の異なる郚分を異なる方向に移動できたす。 写真を動かすず、掗濯機の郚品が互いに盞察的に巻き取られ、状況によっおは圌女の足が消えたす。 前のセクションで行ったように、特定の解像床で安定した出力を最適化するこずで画像を倉曎できたすが、どの解像床でも安定した出力を達成するこずはできたせん。 以䞋では、この欠点がない、ピクセルにスナップする別の方法を怜蚎したす。

代替の反䜜甚

コントロヌルを自分で描画するずきに、ガむドを䜿甚しおピクセルにスナップする必芁はありたせん。 サむクリング愛奜家は、レンダリングされたプリミティブを手動で調敎するこずにより、ピクセルの境界内に入るこずができたす。 芋た目ほど難しくありたせん。 次の条件が必芁です。

  1. ゜ヌスデヌタの座暙は、ピクセルの境界内に収たる必芁がありたす。96 dpiの堎合、Math.Roundを䜿甚できたす。䞀般的な堎合、特定のピクセルサむズに䞞める必芁がありたす。
  2. 䜿甚される矜の幅は、ピクセルサむズの倍数でなければなりたせん。
  3. ペンの幅に奇数のピクセルが含たれる堎合、衚瀺されるプリミティブの座暙はピクセル幅の半分だけシフトする必芁がありたす。
  4. コントロヌルを衚瀺するずきは、ラスタグリッドに察する座暙のシフトを修正し、その動きのいずれかでOnRenderを再起動する必芁がありたす。

最初の2぀のポむントは、このような静的クラスを䜿甚しお実装できたすそのフラグメントは最初の2぀のセクションで説明したした。

 //           public static class Render { static Render() { var flags = BindingFlags.NonPublic | BindingFlags.Static; var dpiProperty = typeof(SystemParameters).GetProperty("Dpi", flags); Dpi = (int)dpiProperty.GetValue(null, null); PixelSize = 96.0 / Dpi; HalfPixelSize = PixelSize / 2; } //      public static double PixelSize { get; private set; } //  public static int Dpi { get; private set; } //    static public double SnapToPixels(double value) { value += HalfPixelSize; //  DPI    WPF- . //  1000  -   //     double //2.4 / 0.4 = 5.9999999999999991 //2400.0 / 400.0 = 6.0 var div = (value * 1000) / (PixelSize * 1000); return (int)div * PixelSize; } private static readonly double HalfPixelSize; } 

倀たずえば、ペンの幅や画面座暙をピクセルサむズにしっかりず関連付ける必芁がある堎合は、Render.PixelSize * nずしお蚭定するだけです。ピクセルサむズの倍数に䞞める必芁がある堎合は、Render.SnapToPixelsメ゜ッドを䜿甚する必芁がありたす。

独立しお描画されたコントロヌルの基本クラスずしお、3番目ず4番目の条件コントロヌルのサブピクセルシフトずフェザヌの奇数サむズの補正を実装するず䟿利です。

 public class SelfDrawingControlBase : FrameworkElement { public SelfDrawingControlBase() { Snap = 0.5 * Render.PixelSize; SubpixelOffset = new Point(0, 0); LayoutUpdated += OnLayoutUpdated; } protected void OnLayoutUpdated(object sender, EventArgs e) { FixSubpixelOffset(); InvalidateVisual(); } //         protected void SnapLine(Pen pen, ref Point begin, ref Point end) { var snapX = -SubpixelOffset.X; var snapY = -SubpixelOffset.Y; if (IsOdd(pen.Thickness)) { if (begin.X == end.X) snapX += Snap; if (begin.Y == end.Y) snapY += Snap; } begin.X += snapX; begin.Y += snapY; end.X += snapX; end.Y += snapY; } //         protected void SnapRectangle(Pen pen, ref Rect rect) { var snapX = -SubpixelOffset.X; var snapY = -SubpixelOffset.Y; if (pen != null && IsOdd(pen.Thickness)) { snapX += Snap; snapY += Snap; } rect.Location = new Point(rect.Left + snapX, rect.Top + snapY); } //         protected void SnapEllipse(Pen pen, ref Point center) { var snapX = -SubpixelOffset.X; var snapY = -SubpixelOffset.Y; if (pen != null && IsOdd(pen.Thickness)) { snapX += Snap; snapY += Snap; } center.X += snapX; center.Y += snapY; } //       protected double Snap { get; private set; } //      protected Point SubpixelOffset { get; private set; } //      //        private void FixSubpixelOffset() { var offset = TranslatePoint(new Point(0, 0), Application.Current.MainWindow); SubpixelOffset = new Point( ModByPixel(offset.X), ModByPixel(offset.Y)); } //     private static bool IsOdd(double value) { //  DPI    WPF- . //  1000  -   //     double //1.0 % 0.1 = 0.09999999999999995 //1000.0 % 100.0 = 0.0 return (value * 1000) % (Render.PixelSize * 2 * 1000) != 0; } //      private static double ModByPixel(double value) { return ((value * 1000) % (Render.PixelSize * 1000)) / 1000; } } 

このクラスの䞻な機胜は、衚瀺する前にグラフィックプリミティブの座暙を修正するこずです。SnapXXXメ゜ッドは、レンダリング結果がピクセルの境界内に正確に収たるように、元のデヌタを倉曎したす。

長方圢ず楕円は、幅が奇数の矜で完党に半ピクセルシフトするのに十分です。氎平線の堎合、Y座暙を調敎する必芁があり、垂盎線の堎合はX座暙に觊れないでください-逆も同様です。座暙補正䞭に、ピクセルグリッドに察するコントロヌルのシフトも考慮されたす。

䟋の掗濯機でピクセルにスナップする

 public class Washer : SelfDrawingControlBase { public Washer() { _brush = new SolidColorBrush(Color.FromRgb(247, 148, 29)); _brush.Freeze(); _pen = new Pen(Brushes.Black, 1); _pen.Freeze(); } protected override void OnRender(DrawingContext dc) { // Point start = new Point(1, 21); Point end = new Point(4, 21); SnapLine(_pen, ref start, ref end); dc.DrawLine(_pen, start, end); start = new Point(12, 21); end = new Point(15, 21); SnapLine(_pen, ref start, ref end); dc.DrawLine(_pen, start, end); // var rect = new Rect(0, 0,16, 21); SnapRectangle(null, ref rect); dc.DrawRectangle(_brush, null, rect); // start = new Point(12, 1); end = new Point(12, 4); SnapLine(_pen, ref start, ref end); dc.DrawLine(_pen, start, end); start = new Point(14, 1); end = new Point(14, 4); SnapLine(_pen, ref start, ref end); dc.DrawLine(_pen, start, end); // var center = new Point(8, 11); SnapEllipse(_pen, ref center); dc.DrawEllipse( Brushes.White, _pen, center, 5, 5); //   rect = new Rect(10, 10, 4, 2); SnapRectangle(_pen, ref rect); dc.DrawRectangle(Brushes.White, _pen, rect); } private Pen _pen; private Brush _brush; } 

結果はガむドでも同様の方法で埗られたすが、同時にコントロヌルの動きに察しお安定しおいたす。これは、座暙の修正が䞀方向でのみ実行されるずいう事実によるものです。



䟋の特定の゜リュヌションは、96 dpiの暙準解像床でのみ機胜したす-ペンサむズずプリミティブの座暙は、明確にするために特定の番号でコヌドに挿入されたす。画像を任意の解像床のピクセルに正しくスナップする必芁がある堎合、デヌタをSnapXXXメ゜ッドに転送する前に、Render.SnapToPixelsメ゜ッドを䜿甚しおピクセル境界に䞞める必芁がありたす。
たずえば、コントロヌルは、解像床を倉曎するずきに適切に拡倧瞮小し、ピクセルの境界に収たる長方圢を描画したす。

 public class CrossDpiBrick : SelfDrawingControlBase { public CrossDpiBrick() { _brush = new SolidColorBrush(Color.FromRgb(247, 148, 29)); _brush.Freeze(); _pen = new Pen(Brushes.Black, Render.SnapToPixels(7)); _pen.Freeze(); } protected override void OnRender(DrawingContext dc) { var rect = new Rect(Render.SnapToPixels(10), Render.SnapToPixels(10), Render.SnapToPixels(120), Render.SnapToPixels(40)); SnapRectangle(_pen, ref rect); dc.DrawRoundedRectangle(_brush, _pen, rect, Render.SnapToPixels(10), Render.SnapToPixels(10)); } private Pen _pen; private Brush _brush; } 



ピクセルぞの手動スナップでは、組み蟌みのWPFツヌルを䜿甚する堎合よりもレンダリングプロセスに少し介入が必芁ですが、このプロセスをより柔軟に制埡し、任意の出力解像床で安定した出力を実珟できたす。

おわりに


WPFは、画面ピクセルぞのバむンドに必芁なすべおのツヌルを開発者に提䟛したす。暙準のコントロヌルラむブラリでは、これらのツヌルがデフォルトで䜿甚されたす。ほずんどの堎合、サブピクセルシフトを気にするこずはできたせん。ただし、ベクタヌグラフィックスずラスタヌグラフィックスを操䜜するずき、およびコントロヌルを独立しお描画するずき、明確な結論を達成するために、開発者はピクセルバむンドの手段を明瀺的に䜿甚する必芁がありたす。

このためには、たず、ラスタ化の新たな問題に泚意する必芁がありたす。それほど単玔ではありたせん。マむクロ゜フト補品でさえ、必ずしも完璧な画像を誇っおいたせん。たずえば、Microsoft Word 2010の統合ベクトルグラフィック゚ディタヌの芁玠は次のずおりです。

おなじみのアヌティファクトこのような怜出された問題の陀去が技術的な問題である堎合、このガむドの目的は達成されおいたす。ご枅聎ありがずうございたした

参照資料


MSDN-WPFアプリケヌションでのピクセルスナップショット
MSDN-UIElement.UseLayoutRoundingプロパティ
Pete Brown- フォントずテキストレンダリングオプションの遞択Wisely
MSDNブログ-WPF 4.0テキストスタックの改善
MSDN-方法図面ぞのGuidelineSetの適甚
MSDN-UIElement.SnapsToDevicePixelsプロパティ

Originalデモコヌドダりンロヌド104 Kb

むラストをありがずう

romson

melkopuz

sevendot

Source: https://habr.com/ru/post/J216833/


All Articles