gdi+ Graphics::DrawImage really slow~~
Asked Answered
D

9

20

I am using a GDI+ Graphic to draw a 4000*3000 image to screen, but it is really slow. It takes about 300ms. I wish it just occupy less than 10ms.

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

//-------------------------------------------- // this part takes about 300ms, terrible!

int width = bitmap->GetWidth();
int height = bitmap->GetHeight();
DrawImage(bitmap,0,0,width,height);

//------------------------------------------

I cannot use CachedBitmap, because I want to edit the bitmap later.

How can I improve it? Or is any thing wrong?

This native GDI function also draws the image into the screen, and it just take 1 ms:

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);

//--------------------------------------------------------------

If I want to use StretchDIBits, I need to pass BITMAPINFO, But how can I get BITMAPINFO from a Gdi+ Bitmap Object? I did the experiment by FreeImage lib, I call StretchDIBits using FreeImageplus object, it draw really fast. But now I need to draw Bitmap, and write some algorithm on Bitmap's bits array, how can I get BITMAPINFO if I have an Bitmap object? It's really annoying -___________-|

Diametrically answered 5/11, 2008 at 9:53 Comment(4)
Upon looking at second example, I would say StretchDIBits is GPU accelerated, unlike, probably, DrawImage. See my updated answer. Is using StretchDIBits a satisfactory solution to your problem?Bowstring
Thanks PhiLho, but I can not use StretchDIBits~ I just know it is faster, and draw a large image can be that fast~~ I appended the problem why I can not use it~~/Diametrically
Maybe you can convert the Bitmap to GDI object with Bitmap::GetHBITMAP (see update of my answer for link).Bowstring
I tested GetHBITMAP function, it seems slow when get HBitmap from a large image... -____________-bDiametrically
B
6

You have a screen of 4000 x 3000 resolution? Wow!

If not, you should draw only the visible part of the image, it would be much faster...

[EDIT after first comment] My remark is indeed a bit stupid, I suppose DrawImage will mask/skip unneeded pixels.

After your edit (showing StretchDIBits), I guess a possible source of speed difference might come from the fact that StretchDIBits is hardware accelerated ("If the driver cannot support the JPEG or PNG file image" is a hint...) while DrawImage might be (I have no proof for that!) coded in C, relying on CPU power instead of GPU's one...

If I recall correctly, DIB images are fast (despite being "device independent"). See High Speed Win32 Animation: "use CreateDIBSection to do high speed animation". OK, it applies to DIB vs. GDI, in old Windows version (1996!) but I think it is still true.

[EDIT] Maybe Bitmap::GetHBITMAP function might help you to use StretchDIBits (not tested...).

Bowstring answered 5/11, 2008 at 10:47 Comment(5)
won't DrawImage check the boundary? It seems silly if this function do not know he is drawing out of the screenDiametrically
Bitmap::GetHBITMAP this function itself takes sometimeDiametrically
@sxingfeng: DrawImage does per pixel clipping. Bigger images take longer to draw. It is silly. It is slow.Excessive
Well, a bit over a decade later, 4K UHD is no longer that uncommon, I'm afraid!Solicit
I've had a dramatic performance improvement when implemented clipping on the visible region. It seems to be able to say that GdiPlus does not clip...Hobard
L
18

If you're using GDI+, the TextureBrush class is what you need for rendering images fast. I've written a couple of 2d games with it, getting around 30 FPS or so.

I've never written .NET code in C++, so here's a C#-ish example:

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, ...)
}
Luck answered 5/11, 2008 at 16:3 Comment(6)
should be: e.Graphics.FillRectangle(myBrush, ClientRectangle); one question though - you have to recreate brush each time the bitmap changes right?Unionist
You're right, there isn't a Graphics.DrawBrush method. I've never tried reusing the same brush after modifying the corresponding bitmap, so I don't know if you have to recreate it or not. In my GDI+ games, all of the texture-brush objects were created up front as the program loaded, where each texture brush was single frame of animation for a sprite.Luck
drawing methods like cybis offered can be choosen..but fastest way could be accomplished by using low level dlls with the welcome of extra controls in code...Tempest
If using this solutions, pay attention to the offset/origin of the brush image and that you have to do a transformation.Monkeypot
I've used TextureBrush with a half transparent image. the result was terrible. The image was completely rotten.Jacobjacoba
Attesting to the efficiency of this answer! With some dirty profiling, I have been able to draw on a 1922 x 743 canvas, ~7000 times (in various positions on the canvas), a picture of dimensions 128 x 128 in original size, and 18 x 18 in scaled size, using DrawImage and FillRectangle. Using DrawImage, net operation time was 428 - 448 ms (range from 5 iterations) and using FillRectangle, net operation time was 198 - 204 ms (range from 4 iterations). Clean GDI+ rendering with no other tricks involved and everything set to high quality. Got ~2x improvement!Solicit
B
6

You have a screen of 4000 x 3000 resolution? Wow!

If not, you should draw only the visible part of the image, it would be much faster...

[EDIT after first comment] My remark is indeed a bit stupid, I suppose DrawImage will mask/skip unneeded pixels.

After your edit (showing StretchDIBits), I guess a possible source of speed difference might come from the fact that StretchDIBits is hardware accelerated ("If the driver cannot support the JPEG or PNG file image" is a hint...) while DrawImage might be (I have no proof for that!) coded in C, relying on CPU power instead of GPU's one...

If I recall correctly, DIB images are fast (despite being "device independent"). See High Speed Win32 Animation: "use CreateDIBSection to do high speed animation". OK, it applies to DIB vs. GDI, in old Windows version (1996!) but I think it is still true.

[EDIT] Maybe Bitmap::GetHBITMAP function might help you to use StretchDIBits (not tested...).

Bowstring answered 5/11, 2008 at 10:47 Comment(5)
won't DrawImage check the boundary? It seems silly if this function do not know he is drawing out of the screenDiametrically
Bitmap::GetHBITMAP this function itself takes sometimeDiametrically
@sxingfeng: DrawImage does per pixel clipping. Bigger images take longer to draw. It is silly. It is slow.Excessive
Well, a bit over a decade later, 4K UHD is no longer that uncommon, I'm afraid!Solicit
I've had a dramatic performance improvement when implemented clipping on the visible region. It seems to be able to say that GdiPlus does not clip...Hobard
J
3

/* First sorry for ma English, and the code is partly in polish, but it's simple to understand. I had the same problem and I found the best solution. Here it is.

Dont use: Graphics graphics(hdc); graphics.DrawImage(gpBitmap, 0, 0); It is slow.

Use: GetHBITMAP(Gdiplus::Color(), &g_hBitmap) for HBITMAP and draw using my function ShowBitmapStretch().

You can resize it and it is much faster! Artur Czekalski / Poland

*/

//--------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;
Joyejoyful answered 1/12, 2015 at 2:57 Comment(0)
M
2

Just a thought; instead of retrieving the width and height of the image before drawing, why not cache these values when you load the image?

Maneuver answered 5/11, 2008 at 11:54 Comment(1)
This is very likely to be the reason. .NET's Bitmap has the slowest Width/Height getters in the history of programming; I've had them crop up as taking 95% of the CPU in profiling on several occasions.Hellhound
W
2

Explore the impact of explicitly setting the interpolation mode to NearestNeighbor (where, in your example, it looks like interpolation is not actually needed! But 300ms is the kind of cost of doing high-quality interpolation when no interpolation is needed, so its worth a try)

Another thing to explore is changing the colour depth of the bitmap.

Withrow answered 5/11, 2008 at 14:0 Comment(0)
G
2

Unfortunately when I had a similar problem, I found that GDI+ is known to be much slower than GDI and not generally hardware accelerated, but now Microsoft have moved on to WPF they will not come back to improve GDI+!

All the graphics card manufacturers have moved onto 3D performance and don't seem interested in 2D acceleration, and there's no clear source of information on which functions are or can be hardware accelerated or not. Very frustrating because having written an app in .NET using GDI+, I am not happy to change to a completely different technology to speed it up to reasonable levels.

Guam answered 5/11, 2008 at 16:25 Comment(0)
V
2

i don't think they'll make much of a different, but since you're not actually needing to resize the image, try using the overload of DrawImage that doesn't (attempt) to resize:

DrawImage(bitmap,0,0);

Like i said, i doubt it will make any difference, because i'm sure that DrawImage checks the Width and Height of the bitmap, and if there's no resizing needed, just calls this overload. (i would hope it doesn't bother going through all 12 million pixels performing no actual work).

Update: My ponderings are wrong. i had since found out, but guys comment reminded me of my old answer: you want to specify the destination size; even though it matches the source size:

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

The reason is because of dpi differences between the dpi of bitmap and the dpi of the destination. GDI+ will perform scaling to get the image to come out the right "size" (i.e. in inches)


What i've learned on my own since last October is that you really want to draw a "cached" version of your bitmap. There is a CachedBitmap class in GDI+. There are some tricks to using it. But in there end i have a function bit of (Delphi) code that does it.

The caveat is that the CachedBitmap can become invalid - meaning it can't be used to draw. This happens if the user changes resolutions or color depths (e.g. Remote Desktop). In that case the DrawImage will fail, and you have to re-created the 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;

The cachedBitmap is passed in by reference. The first call to DrawCachedBitmap it cached version will be created. You then pass it in subsequent calls, e.g.:

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 use the routine to draw glyphs on buttons, taking into account the current DPI. The same could have been used by the Internet Explorer team to draw images when the user is running high dpi (ie is very slow drawing zoomed images, because they use GDI+).

Velites answered 24/10, 2009 at 12:46 Comment(1)
msdn.microsoft.com/en-us/library/1bttkazd(VS.71).aspx has a detailed article on this statue..Tempest
A
1

Try using copy of Bitmap from file. FromFile function on some files returns "slow" image, but its copy will draw faster.

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

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

DrawImage(bitmap2,0,0,width,height);
Arteriole answered 30/1, 2014 at 12:4 Comment(0)
P
0

I have made some researching and wasn't able to find a way to render images with GDI/GDI+ more faster than

Graphics.DrawImage/DrawImageUnscaled

and at the same time simple like it.

Till I discovered

ImageList.Draw(GFX,Point,Index)

and yeah it's really so fast and simple.

Perambulate answered 1/9, 2017 at 19:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.