gdi + Graphics :: DrawImage真的很慢~~

时间:2008-11-05 09:53:15

标签: gdi+ performance drawimage

我正在使用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?这真的很烦人-___________- |

9 个答案:

答案 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)

是的,它真的如此快速和简单。