10

GDI+ で保持モード描画アプリケーションを開発しています。アプリケーションは、単純な形状をキャンバスに描画し、基本的な編集を実行できます。これを行う計算は最後のバイトに最適化されており、問題ではありません。組み込みの Controlstyles.DoubleBuffer を使用しているパネルに描画しています。

大きなモニター (私の場合は HD) でアプリを最大化して実行すると、問題が発生します。(大きな) キャンバスの 1 つの角から対角線上にあるもう一方の角まで線を引こうとすると、遅延が始まり、CPU が高くなります。

アプリの各グラフィカル オブジェクトにはバウンディング ボックスがあります。したがって、最大化されたアプリの 1 つのコーナーから対角線の反対側のコーナーに向かう線のバウンディング ボックスを無効にすると、そのバウンディング ボックスは実質的にキャンバスと同じ大きさになります。ユーザーが線を描いているとき、境界ボックスの無効化はこのように mousemove イベントで発生し、明確なラグが表示されます。このラグは、キャンバス上のオブジェクトが線のみの場合にも存在します。

これをさまざまな方法で最適化しようとしました。より短い線を引くと、CPU とラグが減少します。Invalidate() を削除して他のすべてのコードを保持すると、アプリは高速になります。バウンディングボックスの代わりに Region (図にまたがるだけ) を使用して無効化すると、同じように遅くなります。バウンディング ボックスを、背中合わせに配置された小さなボックスの範囲に分割して、無効化領域を減らした場合、目に見えるパフォーマンスの向上は見られません。

したがって、私はここで途方に暮れています。無効化をスピードアップするにはどうすればよいですか?

余談ですが、Paint.Net と Mspaint の両方に同じ欠点があります。ただし、Word と PowerPoint は、上記のように遅延も CPU 負荷もまったく発生せずに線を描くことができるようです。したがって、望ましい結果を達成することは可能ですが、問題はどのようにですか?

4

4 に答える 4

9

線のような基本的な表示項目の場合、描画サイクルごとに境界全体を絶対に無効にする必要がある場合は、それらをいくつかの部分に分割することを検討する必要があります。

これは、バウンディング ボックスで指定したように、GDI+ (および GDI 自体) が長方形の領域を無効にするためです。これは、水平線と垂直線をテストして、勾配が表示領域の縦横比に似ている線と比較して、自分で確認できます。

では、キャンバスが 640x480 だとしましょう。0,0 から 639,479 までの線を引くと、Invalidate() は、上部の 0,0 から 639,0 まで、下部の 0,479 から 639,479 までの領域全体を無効にします。たとえば、0,100 から 639,100 までの水平線は、わずか 1 ピクセルの高さの長方形になります。

リージョンはグループ化された水平範囲のセットとして扱われるため、リージョンにもまったく同じ問題があります。したがって、ある角から別の角に向かう大きな対角線の場合、設定した境界ボックスに一致させるために、領域は各垂直線上のすべてのピクセル セットまたは境界ボックス全体のいずれかを指定する必要があります。

したがって、解決策として、非常に大きな行がある場合は、それを 4 分の 1 または 8 分の 1 に分割すると、パフォーマンスが大幅に向上するはずです。上記の例をもう一度見てみると、2 つの部分に半分に分割すると、無効化された領域の合計が 0.0 x 319,239 プラス 320,240 x 639,479 に減少します。

これは、四半期分割の視覚的な例です。ピンクの部分が無効です。残念ながら、SO では画像や複数のリンクを投稿することはできませんが、すべてを説明するにはこれで十分です。

(ラインは四分の一に分割され、無効化された領域の合計は表面の 1/4 です)

対角線を横切って引かれた線の後ろに彫られた 4 つの同じサイズのボックスを含む 640x480 のエクステント

または、境界ボックスを指定する代わりに、更新が必要な領域に一致するアイテムの部分のみを描画するように、更新を書き直すことを検討することもできます。それは、描画された更新に参加する必要があるオブジェクトの数に大きく依存します。特定のフレームに何千ものオブジェクトがある場合、無効化されたすべての領域を無視して、シーン全体を再描画することを検討できます。

于 2009-06-07T21:29:40.700 に答える
2

無効化を実際にスピードアップすることはできません。遅い理由は、WM_PAINTイベントをメッセージキューに送信するためです。その後、それはフィルタリングされ、最終的にはOnPaintが呼び出されます。あなたがする必要があるのは、MouseMoveイベント中にあなたのコントロールで直接ペイントすることです。

私が行うコントロールでは、ある程度の流動的なアニメーションが必要です。私のOnPaintイベントは、通常、PaintMe関数のみを呼び出します。そうすれば、その関数を使用していつでもコントロールを再描画できます。

于 2009-06-11T04:56:45.790 に答える
1

明確にするために:ユーザーは直線を描いていますか、それともあなたの線は実際にはマウスポイントを結ぶ一連の線分ですか?線が原点から現在のマウス ポイントまでの直線である場合は、Invalidate() を使用せず、代わりに XOR ブラシを使用して取り消し可能な線を描画し、前の線を元に戻します。ユーザーが描画を終了したときにのみ無効になります。

小さな線分をたくさん描いている場合は、最新の線分の境界ボックスを無効にするだけです。

于 2009-06-05T19:15:20.797 に答える
1

実際のキャンバスに「更新を投稿」する別のスレッドを作成するのはどうですか。

Image paintImage;
private readonly object paintObject = new object();
public _ctor() { paintImage = new Image(); }

override OnPaint(PaintEventArgs pea) {
    if (needUpdate) {
        new Thread(updateImage).Start();
    }
    lock(paintObject) {
        pea.DrawImage(image, 0, 0, Width, Height);
    }
}

public void updateImage() {
    // don't draw to paintImage directly (it might cause threading issues)
    Image img = new Image();
    using (Graphics g = img.GetGraphics()) {
        foreach (PaintableObject po in renderObjects) {
            g.DrawObject(po);
        }
    }
    lock(paintObject){
        using (Graphics g = paintImage.GetGraphics()) {
            g.DrawImage(img, 0, 0, g.Width, g.Height);
        }
    }
    needUpdate = false;
}

単なるアイデアなので、コードをテストしていません;-)

于 2009-06-05T20:04:55.937 に答える