18

我正在使用 GDI+ Graphic 将 4000*3000 的图像绘制到屏幕上,但它真的很慢。大约需要 300 毫秒。我希望它只占用不到 10 毫秒。

Bitmap *bitmap = Bitmap::FromFile("XXXX",...);

// - - - - - - - - - - - - - - - - - - - - - - // 这部分大约需要300ms,太可怕了!

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,它绘制得非常快。但是现在我需要绘制Bitmap,并在Bitmap的位数组上写一些算法,如果我有一个Bitmap对象,我怎么能得到BITMAPINFO呢?真的很烦-_______________-|

4

9 回答 9

17

如果您使用 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, ...)
}
于 2008-11-05T16:03:48.203 回答
3

你有一个 4000 x 3000 分辨率的屏幕?哇!

如果没有,你应该只绘制图像的可见部分,它会快得多......

[第一次评论后编辑]我的评论确实有点愚蠢,我想 DrawImage 会掩盖/跳过不需要的像素。

在您的编辑(显示 StretchDIBits)之后,我猜可能速度差异的来源可能来自StretchDIBits是硬件加速的事实(“如果驱动程序不能支持 JPEG 或 PNG 文件图像”是一个提示......)而 DrawImage 可能是(我没有证据!)用 C 编码,依靠 CPU 能力而不是 GPU 的能力......

如果我没记错的话,DIB 图像很快(尽管是“设备独立的”)。参见高速 Win32 动画:“使用 CreateDIBSection 做高速动画”。好的,它适用于旧 Windows 版本(1996 年!)中的 DIB 与 GDI,但我认为它仍然是正确的。

[编辑] 也许Bitmap::GetHBITMAP函数可能会帮助您使用 StretchDIBits (未测试......)。

于 2008-11-05T10:47:53.847 回答
2

只是一个想法; 与其在绘制之前检索图像的宽度和高度,不如在加载图像时缓存这些值?

于 2008-11-05T11:54:05.007 回答
2

探索将插值模式显式设置为 NearestNeighbor 的影响(在您的示例中,看起来实际上不需要插值!但是 300 毫秒是在不需要插值时进行高质量插值的成本,因此值得尝试)

要探索的另一件事是更改位图的颜色深度。

于 2008-11-05T14:00:21.400 回答
2

不幸的是,当我遇到类似的问题时,我发现 GDI+ 比 GDI 慢得多,而且通常硬件加速也不行,但现在微软已经转向 WPF,他们不会回来改进 GDI+!

所有显卡制造商都转向 3D 性能,似乎对 2D 加速不感兴趣,并且没有明确的信息来源说明哪些功能可以或可以不进行硬件加速。非常令人沮丧,因为使用 GDI+ 在 .NET 中编写了一个应用程序,我不乐意更改为完全不同的技术以将其加速到合理的水平。

于 2008-11-05T16:25:30.047 回答
2

我认为它们不会有太大的不同,但是由于您实际上不需要调整图像大小,请尝试使用不会(尝试)调整大小的DrawImage重载:

DrawImage(bitmap,0,0);

就像我说的那样,我怀疑它会有所不同,因为我确信DrawImage检查位图的宽度高度,如果不需要调整大小,只需调用此重载。(我希望它不会打扰所有 1200 万像素而没有实际工作)。

更新:我的想法是错误的。我后来发现了,但是伙计们的评论让我想起了我的旧答案:你指定目标大小;即使它与源大小匹配:

DrawImage(bitmap, 0, 0, bitmap.GetWidth, bitmap.GetHeight);

原因是因为目的地的 dpi 和目的地的 dpi 之间的 dpi 差异bitmap。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);

考虑到当前的 DPI,我使用例程在按钮上绘制字形。当用户运行高 dpi 时,Internet Explorer 团队也可以使用相同的方法来绘制图像(即绘制缩放图像时速度非常慢,因为他们使用 GDI+)。

于 2009-10-24T12:46:52.853 回答
1

尝试使用文件中的位图副本。某些文件的 FromFile 函数返回“慢”图像,但其副本绘制速度更快。

Bitmap *bitmap = Bitmap::FromFile("XXXX",...);

Bitmap *bitmap2 = new Bitmap(bitmap); // make copy

DrawImage(bitmap2,0,0,width,height);
于 2014-01-30T12:04:12.917 回答
1

/* 首先对不起 ma 英语,代码部分是波兰语,但很容易理解。我有同样的问题,我找到了最好的解决方案。这里是。

不要使用:图形图形(hdc);graphics.DrawImage(gpBitmap, 0, 0); 它很慢。

使用:GetHBITMAP(Gdiplus::Color(), &g_hBitmap) 用于 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;
于 2015-12-01T02:57:12.327 回答
0

我进行了一些研究,但无法找到一种使用 GDI/GDI+ 更快地渲染图像的方法

Graphics.DrawImage/DrawImageUnscaled

同时也很简单。

直到我发现

ImageList.Draw(GFX,Point,Index)

是的,它真的非常快速和简单。

于 2017-09-01T19:07:13.210 回答