我正在使用GDI + Graphic将4000 * 3000图像绘制到屏幕上,但它确实很慢。大约需要300毫秒。我希望它只占用不到10毫秒。
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 + Bitmap对象中获取BITMAPINFO?我通过FreeImage lib做了实验,我使用FreeImageplus对象调用StretchDIBits,它绘制得非常快。但是现在我需要绘制Bitmap,并在Bitmap的位数组上编写一些算法,如果我有Bitmap对象,如何获得BITMAPINFO?这真的很烦人-___________- |
答案 0 :(得分:15)
如果你正在使用GDI +,那么TextureBrush类就是你快速渲染图像所需要的。我用它写了几个2D游戏,大约30 FPS左右。
我从来没有用C ++编写.NET代码,所以这是一个C#-ish例子:
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, ...)
}
答案 1 :(得分:3)
你的屏幕分辨率为4000 x 3000?哇!
如果没有,你应该只绘制图像的可见部分,它会快得多......
[首次评论后编辑]我的评论确实有点愚蠢,我想DrawImage会掩盖/跳过不需要的像素。
编辑后(显示StretchDIBits),我想速度差异的可能来源可能来自StretchDIBits是硬件加速的事实(“如果驱动程序不支持JPEG或PNG文件图像< / em>“是一个暗示...”而DrawImage可能是(我没有证据证明!)用C编码,依靠CPU功率而不是GPU的功能...
如果我没记错的话,DIB图像很快(尽管“设备无关”)。请参阅High Speed Win32 Animation:“使用CreateDIBSection进行高速动画”。好的,它适用于DIB与GDI,在旧的Windows版本(1996年!)中,但我认为它仍然是真的。
[编辑]也许Bitmap::GetHBITMAP函数可能会帮助您使用StretchDIBits(未经过测试......)。
答案 2 :(得分:2)
只是一个想法;而不是在绘制之前检索图像的宽度和高度,为什么不在加载图像时缓存这些值?
答案 3 :(得分:2)
探索明确将插值模式设置为NearestNeighbor的影响(在您的示例中,看起来实际上并不需要插值!但是300ms是在不需要插值时进行高质量插值的成本,因此值得一试)
要探索的另一件事是改变位图的颜色深度。
答案 4 :(得分:2)
不幸的是,当我遇到类似的问题时,我发现GDI +比GDI要慢得多,而且通常硬件加速,但现在微软已经转向WPF,他们不会再回来改进GDI +了!
所有的显卡制造商已经转向3D性能,似乎对2D加速感兴趣,并且没有关于哪些功能是硬件加速或不加硬件的明确信息来源。非常令人沮丧,因为使用GDI +在.NET中编写了一个应用程序,我不乐意改用完全不同的技术来将其加速到合理的水平。
答案 5 :(得分:2)
我不认为他们会有太大的不同,但是因为你实际上并不需要调整图像大小,所以尝试使用不会(尝试)调整大小的DrawImage重载:
DrawImage(bitmap,0,0);
就像我说的,我怀疑它会有什么不同,因为我确定 DrawImage 检查宽度和位图的高度,如果不需要调整大小,则只调用此重载。 (我希望它不会花费所有1200万像素执行任何实际工作)。
更新:我的思考错了。我已经发现了,但是大家发表的评论让我想起了我的旧答案:你希望指定目的地大小;即使它与源大小匹配:
DrawImage(bitmap, 0, 0, bitmap.GetWidth, bitmap.GetHeight);
原因是由于bitmap
的dpi与目的地的dpi之间的dpi差异。 GDI +将执行缩放以使图像出现正确的“大小”(即以英寸为单位)
自去年十月以来,我自己学到的是你真的想要绘制位图的“缓存”版本。 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);
我使用例程在按钮上绘制字形,同时考虑当前的DPI。当用户运行高dpi时,Internet Explorer团队也可以使用相同的方法绘制图像(即绘制缩放图像非常慢,因为它们使用GDI +)。
答案 6 :(得分:1)
尝试使用文件中的位图副本。某些文件的FromFile函数返回“慢”图像,但其复制速度会更快。
Bitmap *bitmap = Bitmap::FromFile("XXXX",...);
Bitmap *bitmap2 = new Bitmap(bitmap); // make copy
DrawImage(bitmap2,0,0,width,height);
答案 7 :(得分:1)
/ * 首先对不起马英语,代码部分是粗略的,但它很容易理解。 我有同样的问题,我找到了最好的解决方案。在这里。
不要使用:图形图形(hdc); graphics.DrawImage(gpBitmap,0,0);这很慢。
使用:HBITMAP的GetHBITMAP(Gdiplus :: Color(),&amp; 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;
答案 8 :(得分:0)
我做了一些研究,并且无法找到使用GDI / GDI +渲染图像的方法比
更快Graphics.DrawImage/DrawImageUnscaled
同时也很简单。
直到我发现了
ImageList.Draw(GFX,Point,Index)
是的,它真的如此快速和简单。