#はじめに
以前書いた[JavaのXORModeと同じことをC#でやろうとした時の格闘記]
(https://qiita.com/kimisyo/items/7f83af14f611817c6b36)で、ControlPaintクラスのDrawReversibleLineメソッドを使ったが欠点があったため、Rectangle等と同様ネイティブメソッドを用いて書き直したメモ。
#ControlPaintクラスのDrawReversibleLineメソッドの問題
以下3点あげられる。
- 描画速度が遅い
- 描画の座標がクライアント領域をはみ出した場合でも、描画してしまう。
- 第3引数で与えた色通りに線が描画されない場合がある。
2は、クライアント領域にはみ出さないよう座標を調整すればいいのだろうが、めんどくさすぎる。
3については、以下URLの説明を参照してほしい。
https://docs.microsoft.com/ja-jp/dotnet/api/system.windows.forms.controlpaint.drawreversibleline?view=netframework-4.8
この中に以下の記載がある。
「注釈 backColorパラメーターは、背景に対して常に表示されるように、線の塗りつぶしの色を計算するために使用されます。
このメソッドの結果は、同じ行を再度描画することによって元に戻すことができます。 このメソッドを使用して線を描画することは、画面の領域を反転することと似ています。ただし、より広範な色の方がパフォーマンスが向上する点が異なります。」
意味がよく分からないが、実際に描画される色とは別のようである。
そこでネイティブAPIを用いて、前回参考にしたサイトを元に自分で実装することとした。
#コード
コードは以下のとおりである。
internal static void DrawReversibleLine(Graphics g, Point p1, Point p2, Point offset, Color color)
{
IntPtr hDC = g.GetHdc();
IntPtr pen = CreatePen(PS_SOLID, 1, ColorTranslator.ToWin32(color));
int oldROP = SetROP2(hDC, R2_NOTXORPEN);
IntPtr oldPen = SelectObject(hDC, pen);
IntPtr oldBrush = SelectObject(hDC, GetStockObject(NULL_BRUSH));
SetBkColor(hDC, ColorTranslator.ToWin32(Color.White));
p1.Offset(offset.X, offset.Y);
p2.Offset(offset.X, offset.Y);
MoveToEx(hDC, p1.X, p1.Y, IntPtr.Zero);
LineTo(hDC, p2.X, p2.Y);
SelectObject(hDC, oldPen);
SelectObject(hDC, oldBrush);
SetROP2(hDC, oldROP);
DeleteObject(pen);
g.ReleaseHdc(hDC);
}
なお、ネイティブAPIを呼び出すため、以下の通りインポートを忘れずに。
[DllImport("gdi32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool MoveToEx(IntPtr hdc, int X, int Y, IntPtr lpPoint);
[DllImport("gdi32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool LineTo(IntPtr hdc, int nXEnd, int nYEnd);
#おわりに
実装したところ、元々の3つの問題は全て解消された。めでたしめでたし。