描画した円弧の始点と終点が正しくない。

適用対象: .NET Framework 3.5 Service Pack 1.NET Framework 3.5.1.NET Framework 4.5

現象


半径が非常に大きな円弧を描画する場合、設定する値によっては始点と終点が正しい位置に描画されないことがあります。

原因


Internet Explorer 等のウェブ ブラウザでは、SVG (Scalable Vector Graphics) 形式で表現された画像を読み込んで表示したり、Loose XAML (コンパイル不要なマークアップを読み込み、描画する機能) を使用して描画を行う WPF アプリケーションを実行してグラフィックスを表示することが可能です。

こうした SVG 画像の表示や Loose XAML のレンダリング エンジンには、内部的に DirectX (Direct3D9) が使用されています。
ところが DirectX (Direct3D9) の中では、単精度浮動小数点で処理を行っているため、非常に大きな数値を使用した場合に、演算の限界に達して予期しない描画となる可能性があります。
例えば Loose XAML において、Path Markup の A コマンドを用いて半径が非常に大きな円弧を描画する場合、設定する値によっては始点と終点が正しい位置に描画されない場合があります。

解決方法


グラフィックスにより複雑な描画を行う場合には、開発者は精度の限界に常に気を配る必要があります。
半径が非常に大きな円弧を描画する場合は、この限界により正しく描画されない可能性があるので、事実上線分とみなせる場合は、線分で置換するなどの処理を検討する必要があります。

状況


この動作は、単精度浮動小数点で演算処理を行うレンダリング エンジンにおける制限事項です。

詳細


再現手順
ここでは WPF アプリケーションの Loose XAML において、Path Markup の A コマンドを用いて半径が非常に大きな円弧を描画した場合の再現手順を紹介します。

以下のコードを記述し、拡張子 .xaml で保存し、Loose XAML ファイルを作成します。
このファイルを Internet Explorer 11 で表示すると、WPF のレンダリング エンジンによって、図 1のような赤い円弧と青い線分が描かれます。

1. <?xml version="1.0" encoding="utf-8"?>
2. <Canvas Background="#fff" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
3. <Canvas.RenderTransform>
4. <TransformGroup>
5. <TranslateTransform X="-26000" Y="-3000" />
6. </TransformGroup>
7. </Canvas.RenderTransform>
8. <Path Stroke="#f00" StrokeThickness="1">
9. <Path.Data>
10. <PathGeometry Figures="M 26557.35, 3320.226 A 1703595, 1703595, 0, 0, 0, 26569.29, 3290.773" />
11. </Path.Data>
12. </Path>
13. <Path Stroke="#00f" StrokeThickness="3">
14. <Path.Data>
15. <PathGeometry Figures="M 26557.35, 3320.226 L 26569.29, 3290.773" />
16. </Path.Data>
17. </Path>
18. </Canvas>

上の例では、10 行目において、PathGeometry 要素により Figures 属性中の M コマンドで指定した絶対座標 (26557.35, 3320.226) から A コマンドで指定した絶対座標 (26569.29, 3290.773) に向けて、半径が 1703595 となる赤い円弧を描いています。
また、15 行目においては、同じく M コマンドで指定した絶対座標 (26557.35, 3320.226) から、L コマンドで指定した絶対座標 (26569.29, 3290.773) に向けて青い線分を描いています。
どちらも同じ始点と終点を指定しているにも関わらず、円弧の始点と終点が大きくずれて描画されます。


図1. 円弧の始点と終点が大きくずれて描画される。
図1. 円弧の始点と終点が大きくずれて描画される。

上の例では、約 170万ピクセルの半径の円弧により、約 32 ピクセル離れた2点を結ぼうとしていることになりますが、これらの 2つの値は桁が違いすぎ、処理的に意味のある設定ではありません。

図 2で説明すると、円弧 (AM’B) と線分 (AMB) の最大乖離 (MM’) は 0.0001 ピクセル未満となるため、仮に論理上正確な描画を行ったとしても違いを見て取ることは不可能です。
そのため、このような場合は 2 点を結ぶ線分を描く方が、処理的な不確定要素を最小限に抑えることができるため望ましい対処法となります。



図 2. 円弧と線分の最大乖離。
図 2. 円弧と線分の最大乖離。