gdi+ Graphics::DrawImage は本当に遅い~~
-
06-07-2019 - |
質問
GDI+ グラフィックを使用して 4000*3000 の画像を画面に描画していますが、非常に遅いです。約 300ms かかります。占有時間が 10ms 未満であればいいのですが。
Bitmap *bitmap = Bitmap::FromFile("XXXX",...);
// - - - - - - - - - - - - - - - - - - - - - - // この部分ひどい300ミリ秒かかります!
int width = bitmap->GetWidth();
int height = bitmap->GetHeight();
DrawImage(bitmap,0,0,width,height);
//------------------------------------------
後でビットマップを編集したいため、CachedBitmap は使用できません。
どうすれば改善できますか?それとも何か問題があるのでしょうか?
このネイティブ GDI 関数も画像を画面に描画しますが、所要時間はわずか 1 ミリ秒です。
SetStretchBltMode(hDC, COLORONCOLOR);
StretchDIBits(hDC, rcDest.left, rcDest.top,
rcDest.right-rcDest.left, rcDest.bottom-rcDest.top,
0, 0, width, height,
BYTE* dib, dibinfo, DIB_RGB_COLORS, SRCCOPY);
//--------------------------------------------------------------
StretchDIBits を使用したい場合は、BITMAPINFO を渡す必要がありますが、Gdi+ ビットマップ オブジェクトから BITMAPINFO を取得するにはどうすればよいでしょうか?私はFreeImage libで実験を行い、FreeImageplusオブジェクトを使用してStretchDIBitsを呼び出しました。非常に高速に描画されます。しかし、今度はビットマップを描画し、ビットマップのビット配列に何らかのアルゴリズムを記述する必要があります。ビットマップ オブジェクトがある場合、BITMAPINFO を取得するにはどうすればよいでしょうか?本当に迷惑です -____________-|
解決
4000 x 3000の解像度の画面がありますか?わあ!
そうでない場合は、画像の可視部分のみを描画する必要があります。それははるかに高速です...
[最初のコメントの後に編集]私の発言は確かに少し愚かです。DrawImageは不要なピクセルをマスク/スキップすると思います。
編集(StretchDIBitsを表示)後、速度差の原因として考えられるのは、 StretchDIBits はハードウェアアクセラレーションです(" ドライバーがJPEGまたはPNGファイルイメージをサポートできない場合"はヒントです...) DrawImageはCでコーディングされている可能性があります(その証拠はありません!)、GPUの代わりにCPUパワーに依存しています...
正しく思い出せば、DIBイメージは高速です(「デバイスに依存しない」にもかかわらず)。 高速Win32アニメーションを参照してください:" CreateDIBSectionを使用して高速アニメーションを行う"。 OK、古いWindowsバージョン(1996!)のDIBとGDIに適用されますが、それはまだ真実だと思います。
[編集]たぶん Bitmap :: GetHBITMAP 関数は、StretchDIBitsの使用に役立つ場合があります(テストされていません...)。
他のヒント
GDI +を使用している場合、TextureBrushクラスは画像を高速でレンダリングするために必要なものです。私はそれを使って2Dゲームをいくつか書いて、約30 FPSを取得しました。
.NETコードをC ++で記述したことはないので、ここにC#風の例を示します。
Bitmap bmp = new Bitmap(...)
TextureBrush myBrush = new TextureBrush(bmp)
private void Paint(object sender, PaintEventArgs e):
{
//Don't draw the bitmap directly.
//Only draw TextureBrush inside the Paint event.
e.Graphics.FillRectangle(myBrush, ...)
}
考えてみてください。描画する前に画像の幅と高さを取得する代わりに、画像を読み込むときにこれらの値をキャッシュしてみませんか?
補間モードを明示的にNearestNeighborに設定することの影響を調べます(この例では、補間は実際には必要ないように見えます!試してみる価値あり)
別の検討事項は、ビットマップの色深度を変更することです。
残念なことに、同様の問題が発生したとき、GDI +はGDIよりもかなり遅く、一般にハードウェアアクセラレーションではないことがわかっていますが、MicrosoftはWPFに移行し、GDI +を改善するために戻ってきません!
すべてのグラフィックスカードメーカーは3Dパフォーマンスに移行しており、2Dアクセラレーションには興味がないようです。また、ハードウェアアクセラレーションの有無に関する明確な情報源はありません。 GDI +を使用して.NETでアプリを記述したため、非常にイライラします。合理的なレベルにスピードアップするために、まったく異なるテクノロジーに変更することはできません。
私はそれらが大きく異なるとは思わないが、実際に画像のサイズを変更する必要はないので、 DrawImage (サイズ変更を試行しない)
DrawImage(bitmap,0,0);
言ったように、 DrawImage が Width とビットマップの高さ。サイズ変更が必要ない場合は、このオーバーロードを呼び出します。 (私はそれがすべての1200万ピクセルを実際の作業を実行することを気にしないことを願っています)。
更新:私の熟考が間違っています。それ以来、私は知っていましたが、みんなコメントで私の昔の答えを思い出しました。宛先のサイズを指定したい 。ソースのサイズと一致していても:
DrawImage(bitmap, 0, 0, bitmap.GetWidth, bitmap.GetHeight);
その理由は、 bitmap
のdpiと宛先のdpiのdpiの違いによるものです。 GDI +はスケーリングを実行して、画像を適切な「サイズ」で表示します。 (つまりインチ)
昨年10月から私が学んだことは、ビットマップの"キャッシュ済み" バージョンを本当に描きたいということです。 GDI +には CachedBitmap
クラスがあります。それを使用するいくつかのトリックがあります。しかし、そこには、それを行う(Delphi)コードの機能ビットがあります。
注意点は、 CachedBitmap
が無効になる可能性があることです。つまり、描画に使用することはできません。これは、ユーザーが解像度や色深度を変更した場合に発生します(リモートデスクトップなど)。その場合、 DrawImage
は失敗し、 CachedBitmap
を再作成する必要があります:
class procedure TGDIPlusHelper.DrawCachedBitmap(image: TGPImage;
var cachedBitmap: TGPCachedBitmap;
Graphics: TGPGraphics; x, y: Integer; width, height: Integer);
var
b: TGPBitmap;
begin
if (image = nil) then
begin
//i've chosen to not throw exceptions during paint code - it gets very nasty
Exit;
end;
if (graphics = nil) then
begin
//i've chosen to not throw exceptions during paint code - it gets very nasty
Exit;
end;
//Check if we have to invalidate the cached image because of size mismatch
//i.e. if the user has "zoomed" the UI
if (CachedBitmap <> nil) then
begin
if (CachedBitmap.BitmapWidth <> width) or (CachedBitmap.BitmapHeight <> height) then
FreeAndNil(CachedBitmap); //nil'ing it will force it to be re-created down below
end;
//Check if we need to create the "cached" version of the bitmap
if CachedBitmap = nil then
begin
b := TGDIPlusHelper.ResizeImage(image, width, height);
try
CachedBitmap := TGPCachedBitmap.Create(b, graphics);
finally
b.Free;
end;
end;
if (graphics.DrawCachedBitmap(cachedBitmap, x, y) <> Ok) then
begin
//The calls to DrawCachedBitmap failed
//The API is telling us we have to recreate the cached bitmap
FreeAndNil(cachedBitmap);
b := TGDIPlusHelper.ResizeImage(image, width, height);
try
CachedBitmap := TGPCachedBitmap.Create(b, graphics);
finally
b.Free;
end;
graphics.DrawCachedBitmap(cachedBitmap, x, y);
end;
end;
cachedBitmap
は参照によって渡されます。 キャッシュバージョンの DrawCachedBitmap
への最初の呼び出しが作成されます。その後、次の呼び出しで渡します。例:
Image imgPrintInvoice = new Image.FromFile("printer.png");
CachedBitmap imgPrintInvoiceCached = null;
...
int glyphSize = 16 * (GetCurrentDpi() / 96);
DrawCachedBitmap(imgPrintInvoice , ref imgPrintInvoiceCached , graphics,
0, 0, glyphSize, glyphSize);
iは、ルーチンを使用して、現在のDPIを考慮して、ボタンにグリフを描画します。 Internet Explorerチームでも、ユーザーが高dpiを実行しているときに画像を描画するために同じものを使用できました(GDI +を使用しているため、ズーム画像の描画が非常に遅い)。
ファイルからビットマップのコピーを使用してみてください。一部のファイルのFromFile関数は&quot; slow&quot;を返します。画像ですが、そのコピーはより速く描画されます。
Bitmap *bitmap = Bitmap::FromFile("XXXX",...);
Bitmap *bitmap2 = new Bitmap(bitmap); // make copy
DrawImage(bitmap2,0,0,width,height);
/*最初にMA英語で申し訳ありません。コードは部分的にポリッシュですが、理解するのは簡単です。私も同じ問題を抱えていましたが、最良の解決策を見つけました。ここにあります。
使用しないでください:グラフィックスグラフィックス(hdc);グラフィックス.DrawImage(gpBitmap, 0, 0);遅いです。
使用:HBITMAP の GetHBITMAP(Gdiplus::Color(), &g_hBitmap) を実行し、関数 ShowBitmapStretch() を使用して描画します。
サイズ変更も可能で、はるかに高速です。Artur Czekalski / ポーランド
*/
//--------Global-----------
Bitmap *g_pGDIBitmap; //for loading picture
int gRozXOkna, gRozYOkna; //size of working window
int gRozXObrazu, gRozYObrazu; //Size of picture X,Y
HBITMAP g_hBitmap = NULL; //for displaying on window
//------------------------------------------------------------------------------
int ShowBitmapStretch(HDC hdc, HBITMAP hBmp, int RozX, int RozY, int RozXSkal, int RozYSkal, int PozX, int PozY)
{
if (hBmp == NULL) return -1;
HDC hdc_mem = CreateCompatibleDC(hdc); //utworzenie kontekstu pamięciowego
if (NULL == hdc_mem) return -2;
//Trzeba połączyć BMP z hdc_mem, tzn. umieścić bitmapę w naszym kontekście pamięciowym
if (DeleteObject(SelectObject(hdc_mem, hBmp)) == NULL) return -3;
SetStretchBltMode(hdc, COLORONCOLOR); //important! for smoothness
if (StretchBlt(hdc, PozX, PozY, RozXSkal, RozYSkal, hdc_mem, 0, 0, RozX, RozY, SRCCOPY) == 0) return -4;
if (DeleteDC(hdc_mem) == 0) return -5;
return 0; //OK
}
//---------------------------------------------------------------------------
void ClearBitmaps(void)
{
if (g_hBitmap) { DeleteObject(g_hBitmap); g_hBitmap = NULL; }
if (g_pGDIBitmap) { delete g_pGDIBitmap; g_pGDIBitmap = NULL; }
}
//---------------------------------------------------------------------------
void MyOpenFile(HWND hWnd, szFileName)
{
ClearBitmaps(); //Important!
g_pGDIBitmap = new Bitmap(szFileName); //load a picture from file
if (g_pGDIBitmap == 0) return;
//---Checking if picture was loaded
gRozXObrazu = g_pGDIBitmap->GetWidth();
gRozYObrazu = g_pGDIBitmap->GetHeight();
if (gRozXObrazu == 0 || gRozYObrazu == 0) return;
//---Uworzenie bitmapy do wyświatlaia; DO IT ONCE HERE!
g_pGDIBitmap->GetHBITMAP(Gdiplus::Color(), &g_hBitmap); //creates a GDI bitmap from this Bitmap object
if (g_hBitmap == 0) return;
//---We need to force the window to redraw itself
InvalidateRect(hWnd, NULL, TRUE);
UpdateWindow(hWnd);
}
//---------------------------------------------------------------------------
void MyOnPaint(HDC hdc, HWND hWnd) //in case WM_PAINT; DO IT MANY TIMES
{
if (g_hBitmap)
{
double SkalaX = 1.0, SkalaY = 1.0; //scale
if (gRozXObrazu > gRozXOkna || gRozYObrazu > gRozYOkna || //too big picture, więc zmniejsz;
(gRozXObrazu < gRozXOkna && gRozYObrazu < gRozYOkna)) //too small picture, można powiększyć
{
SkalaX = (double)gRozXOkna / (double)gRozXObrazu; //np. 0.7 dla zmniejszania; FOR DECREASE
SkalaY = (double)gRozYOkna / (double)gRozYObrazu; //np. 1.7 dla powiększania; FOR INCREASE
if (SkalaY < SkalaX) SkalaX = SkalaY; //ZAWSZE wybierz większe skalowanie, czyli mniejszą wartość i utaw w SkalaX
}
if (ShowBitmapStretch(hdc, g_hBitmap, gRozXObrazu, gRozYObrazu, (int)(gRozXObrazu*SkalaX), (int)(gRozYObrazu*SkalaX), 0, 0, msg) < 0) return;
調査を行いましたが、GDI / GDI +で画像をレンダリングする方法を見つけることができませんでした
Graphics.DrawImage/DrawImageUnscaled
同時にそのような単純な。
発見するまで
ImageList.Draw(GFX,Point,Index)
そしてええ、本当に高速でシンプルです。