4

背景が大きなビットマップである (OnPaint から呼び出される) 自分自身を描画する多くのオブジェクトの完全にカスタム描画されたグラフィックスを持つユーザー コントロールがあります。ズームとパン機能が組み込まれており、キャンバスに描画されるオブジェクトのすべての座標はビットマップ座標です。

したがって、ユーザー コントロールの幅が 1000 ピクセルで、ビットマップの幅が 1500 ピクセルで、200% ズームでズームしている場合、いつでもビットマップの幅の 1/3 しか見えません。また、ビットマップのポイント 100,100 から始まる長方形を持つオブジェクトは、画面の左端までスクロールすると、ポイント 200,200 に表示されます。

基本的に私がする必要があるのは、再描画が必要なものだけを再描画する効率的な方法を作成することです。たとえば、オブジェクトを移動する場合、そのオブジェクトの古いクリップ四角形を領域に追加し、そのオブジェクトの新しいクリップ四角形を同じ領域に結合してから、Invalidate(region) を呼び出してこれら 2 つの領域を再描画できます。

ただし、この方法では、オブジェクトを Invalidate に渡す前に、常にオブジェクトのビットマップ座標を画面座標に変換する必要があります。PaintEventArgs の ClipRectangle は、他のウィンドウが私のものを無効にするときの画面座標にあると常に想定する必要があります。

ビットマップから画面座標に変換する必要がないように、Region.Transform および Region.Translate 機能を利用する方法はありますか? 画面座標での PaintEventArgs の受信を妨げないようにするには? 複数のリージョンを使用する必要がありますか、それともこれをすべて行うためのより良い方法はありますか?

私が今行っていることのサンプルコード:

invalidateRegion.Union(BitmapToScreenRect(SelectedItem.ClipRectangle));

SelectedItem.UpdateEndPoint(endPoint);

invalidateRegion.Union(BitmapToScreenRect(SelectedItem.ClipRectangle));

this.Invalidate(invalidateRegion);

そして、OnPaint() で...

protected override void OnPaint(PaintEventArgs e)
{
    invalidateRegion.Union(e.ClipRectangle);

    e.Graphics.SetClip(invalidateRegion, CombineMode.Union);
    e.Graphics.Clear(SystemColors.AppWorkspace);

    e.Graphics.TranslateTransform(AutoScrollPosition.X + CanvasBounds.X, AutoScrollPosition.Y + CanvasBounds.Y);

    DrawCanvas(e.Graphics, _ratio);

    e.Graphics.ResetTransform();

    e.Graphics.ResetClip();

    invalidateRegion.MakeEmpty();
}
4

1 に答える 1

10

多くの人がこの質問を見ているので、私は先に進み、私の現在の知識の中で最善を尽くして答えます.

PaintEventArgs で提供される Graphics クラスは、無効化要求によって常にハードクリップされます。これは通常、オペレーティング システムによって行われますが、コードによって行うこともできます。

このクリップをリセットしたり、これらのクリップ境界から脱出したりすることはできませんが、その必要はありません。ペイントするときは、どうしてもパフォーマンスを最大化する必要がない限り、通常、どのようにクリッピングされているかを気にする必要はありません。

グラフィックス クラスは、コンテナーのスタックを使用して、クリッピングと変換を適用します。Graphics.BeginContainer と Graphics.EndContainer を使用して、このスタックを自分で拡張できます。コンテナーを開始するたびに、Transform または Clip に加えた変更は一時的なものであり、BeginContainer の前に構成された以前の Transform または Clip の後に適用されます。したがって、本質的に、OnPaint イベントを取得すると、それは既にクリップされており、新しいコンテナーにいるため、クリップが表示されず (Clip 領域または ClipRect は無限として表示されます)、それらから抜け出すことはできません。クリップ境界。

ビジュアル オブジェクトの状態が変化した場合 (たとえば、マウスまたはキーボード イベントやデータ変更への反応など)、通常は、コントロール全体を再描画する Invalidate() を呼び出すだけで問題ありません。Windows は、CPU 使用率が低い瞬間に OnPaint を呼び出します。通常、Invalidate() への各呼び出しは、常に OnPaint イベントに対応するとは限りません。Invalidate は、次のペイントの前に複数回呼び出される可能性があります。したがって、データ モデル内の 10 個のプロパティが一度に変更された場合、プロパティの変更ごとに Invalidate を安全に 10 回呼び出すことができ、OnPaint イベントを 1 回だけトリガーする可能性があります。

Update() と Refresh() の使用には注意が必要であることに気付きました。これらは同期 OnPaint を直ちに強制します。これらは、単一のスレッド操作 (おそらくプログレス バーの更新) 中に描画するのに役立ちますが、間違ったタイミングで使用すると、過剰で不必要な描画につながる可能性があります。

シーンを再描画する際のパフォーマンスを向上させるためにクリップの四角形を使用する場合は、集約されたクリップ領域を自分で追跡する必要はありません。Windows がこれを行います。無効化が必要な長方形または領域を無効化し、通常どおりにペイントします。たとえば、ペイントしているオブジェクトが移動された場合、古い境界と新しい境界を無効にするたびに、新しい場所にペイントするだけでなく、元の背景を再ペイントする必要があります。また、ペン ストロークのサイズなども考慮する必要があります。

また、Hans Passant が述べたように、高解像度画像のビットマップ形式として常に 32bppPArgb を使用してください。画像を「高パフォーマンス」として読み込む方法のコード スニペットを次に示します。

public static Bitmap GetHighPerformanceBitmap(Image original)
{
    Bitmap bitmap;

    bitmap = new Bitmap(original.Width, original.Height, PixelFormat.Format32bppPArgb);
    bitmap.SetResolution(original.HorizontalResolution, original.VerticalResolution);

    using (Graphics g = Graphics.FromImage(bitmap))
    {
        g.DrawImage(original, new Rectangle(new Point(0, 0), bitmap.Size), new Rectangle(new Point(0, 0), bitmap.Size), GraphicsUnit.Pixel);
    }

    return bitmap;
}
于 2012-11-19T19:18:45.483 に答える