gdi + Graphics :: DrawImage очень медленный ~~

17

Я использую 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, но как я могу получить BITMAPINFO из объекта Gdi + Bitmap? Я сделал эксперимент с помощью FreeImage lib, я вызываю StretchDIBits с использованием объекта FreeImageplus, он рисует очень быстро. Но теперь мне нужно нарисовать Bitmap и написать некоторый алгоритм на битов массива Bitmap, как я могу получить BITMAPINFO, если у меня есть объект Bitmap? Это очень раздражает -

задан user25749 05.11.2008 в 10:53
источник
  • Посмотрев на второй пример, я бы сказал, что StretchDIBits ускорен GPU, в отличие от, вероятно, DrawImage. См. Мой обновленный ответ. Использует ли StretchDIBits удовлетворительное решение вашей проблемы? –  PhiLho 05.11.2008 в 13:49
  • Спасибо PhiLho, но я не могу использовать StretchDIBits ~ Я просто знаю, что это быстрее, и рисовать большое изображение может быть так быстро ~~ Я добавил проблему, почему я не могу ее использовать ~~ / –  user25749 05.11.2008 в 14:38
  • Возможно, вы можете преобразовать битмап в объект GDI с помощью Bitmap :: GetHBITMAP (см. обновление моего ответа для ссылки). –  PhiLho 05.11.2008 в 16:59
  • Я тестировал функцию GetHBITMAP, кажется медленным, когда вы получаете HBitmap с большого изображения ... - –  user25749 08.11.2008 в 02:57

9 ответов

3

У вас есть экран с разрешением 4000 x 3000? Wow!

Если нет, вы должны нарисовать только видимую часть изображения, это будет намного быстрее ...

[EDIT после первого комментария] Мое замечание действительно немного глупо, я полагаю, DrawImage будет маскировать / пропускать ненужные пиксели.

После вашего редактирования (показывая StretchDIBits), я думаю, что возможный источник разницы в скорости может возникнуть из-за того, что StretchDIBits аппаратно ускорен (" Если драйвер не поддерживает изображение в формате JPEG или PNG ), тогда как DrawImage может быть (у меня нет доказательство для этого!), закодированное в C, опираясь на мощность процессора вместо одного GPU ...

Если я правильно помню, изображения DIB бывают быстрыми (несмотря на то, что они «независимы от устройства»). См. Высокоскоростная анимация Win32 : « используйте CreateDIBSection, чтобы сделать высокоскоростная анимация . Хорошо, это относится к DIB против GDI, в старой версии Windows (1996!), Но я думаю, что это все еще так.

[EDIT] Возможно, функция Bitmap :: GetHBITMAP может помочь вам для использования StretchDIBits (не проверено ...).

    
ответ дан PhiLho 05.11.2008 в 11:47
  • не будет DrawImage проверить границу? Кажется глупым, если эта функция не знает, что он выводит из экрана –  user25749 05.11.2008 в 12:31
  • Bitmap :: GetHBITMAP сама эта функция берет –  user25749 08.11.2008 в 02:56
  • строка «может быть закодирована в C» досадно неактуальна. –  Kelly Elton 14.02.2010 в 22:26
  • @sxingfeng: DrawImage делает для отсечения пикселя. Большие изображения занимают больше времени. Это глупо. Это медленно. –  Jared Updike 30.04.2010 в 21:00
15

Если вы используете GDI +, класс TextureBrush - это то, что вам нужно для быстрого рендеринга изображений. Я написал пару игр с двумя играми, получив около 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, ...)
}
    
ответ дан Cybis 05.11.2008 в 17:03
  • должно быть: e.Graphics.FillRectangle (myBrush, ClientRectangle); один вопрос, хотя - вы должны воссоздавать кисть каждый раз, когда растровое изображение меняется правильно? –  argh 01.06.2010 в 16:13
  • Вы правы, нет метода Graphics.DrawBrush. Я никогда не пробовал повторное использование одной и той же кисти после изменения соответствующего растрового изображения, поэтому я не знаю, нужно ли его воссоздать или нет. В моих играх GDI + все объекты текстурной кисти создавались спереди, когда программа загружалась, причем каждая текстурная кисть была одиночным кадром анимации для спрайта. –  Cybis 10.06.2010 в 21:58
  • методы рисования, такие как предлагаемые cybis, могут быть выбраны ... но самый быстрый способ можно выполнить, используя DLL низкого уровня с приветствием дополнительных элементов управления в коде ... –  dankyy1 16.08.2010 в 13:10
  • При использовании этих решений обратите внимание на смещение / происхождение изображения кисти и что вам нужно сделать преобразование. –  Uwe Keim 06.05.2012 в 10:21
  • Я использовал TextureBrush с полупрозрачным изображением. результат был ужасен. Изображение было совершенно гнилым. –  Mahmoodvcs 15.12.2013 в 19:07
2

Просто мысль; вместо того, чтобы извлекать ширину и высоту изображения перед рисованием, почему бы не кэшировать эти значения при загрузке изображения?

    
ответ дан Patrik Svensson 05.11.2008 в 12:54
  • Это, скорее всего, будет причиной. Растровое изображение .NET имеет самые медленные коэффициенты ширины / высоты в истории программирования; У меня они возникали, когда принимали 95% процессора в профилировании несколько раз. –  Roman Starkov 01.02.2012 в 11:58
2

Изучите влияние явной настройки интерполяционного режима на NearestNeighbor (где, в вашем примере, похоже, что интерполяция на самом деле не нужна! Но 300 мс - это вид стоимости высококачественной интерполяции, когда интерполяция не требуется, поэтому его стоит попробовать)

Еще одна вещь, которую нужно изучить, - изменить глубину цвета растрового изображения.

    
ответ дан Will 05.11.2008 в 15:00
2

К сожалению, когда у меня была аналогичная проблема, я обнаружил, что GDI +, как известно, намного медленнее, чем GDI, и, как правило, не аппаратное ускорение, но теперь Microsoft перешла к WPF, они не вернутся к улучшению GDI +!

Все производители видеокарты перешли на 3D-производительность и, похоже, не заинтересованы в 2D-ускорении, и нет четкого источника информации о том, какие функции или аппаратное ускорение или нет. Очень неприятно, потому что, написав приложение в .NET с использованием GDI +, я не рад перейти на совершенно другую технологию, чтобы ускорить ее до разумных уровней.

    
ответ дан Martin 05.11.2008 в 17:25
  • , что совершенно не имеет значения –  argh 01.06.2010 в 16:19
2

Я не думаю, что они сделают многое другое, но поскольку вам не нужно изменять размер изображения, попробуйте использовать перегрузку DrawImage , который не (пытается) изменить размер:

DrawImage(bitmap,0,0);

Как я уже сказал, я сомневаюсь, что это будет иметь какое-то значение, потому что я уверен , что DrawImage проверяет Ширина и Высота растрового изображения, и если размер не требуется, просто вызывает эту перегрузку. (я бы надеялся, что он не беспокоится о том, чтобы пройти все 12 миллионов пикселей, не выполняя никакой реальной работы).

Обновление: мои размышления ошибочны. с тех пор я узнал, но комментарии парней напомнили мне мой старый ответ: вы хотите указать размер места назначения; даже если он соответствует размеру источника:

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

Причина заключается в различиях между значениями dpi между dpi и bitmap и разрешением dpi. GDI + будет выполнять масштабирование, чтобы получить изображение, получившее правильный «размер» (т. Е. В дюймах).

То, что я узнал самостоятельно с октября прошлого года, это то, что вы действительно хотите нарисовать версию "cached" вашего растрового изображения. В 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 it cached . Затем вы передаете его в последующих вызовах, например:

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. То же самое можно было использовать командой Internet Explorer для рисования изображений, когда пользователь работает с высоким разрешением (т. Е. Очень медленно рисует увеличенные изображения, потому что они используют GDI +).

    
ответ дан Ian Boyd 24.10.2009 в 14:46
  • msdn.microsoft.com/en-us/library/1bttkazd(VS.71).aspx содержит подробную статью об этой статуе. –  dankyy1 16.08.2010 в 10:40
1

Попробуйте использовать копию Bitmap из файла. Функция FromFile на некоторых файлах возвращает «медленное» изображение, но его копия будет рисоваться быстрее.

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

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

DrawImage(bitmap2,0,0,width,height);
    
ответ дан eugeniy 30.01.2014 в 13:04
1

/ * Сначала извините за английский, а код отчасти отполирован, но его просто понять. У меня была та же проблема, и я нашел лучшее решение. Вот оно.

Не используйте графическую графику (hdc); graphics.DrawImage (gpBitmap, 0, 0); Это медленно.

Использование: GetHBITMAP (Gdiplus :: Color (), & amp; g_hBitmap) для HBITMAP и рисования с использованием моей функции ShowBitmapStretch ().

Вы можете изменить размер, и это намного быстрее! Артур Чекальский / Польша

* /

//--------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;
    
ответ дан Sator666 01.12.2015 в 03:57
0

Я провел некоторое исследование и не смог найти способ рендеринга изображений с GDI / GDI + быстрее, чем

Graphics.DrawImage/DrawImageUnscaled

и в то же время простой, как он.

Пока я не обнаружил

ImageList.Draw(GFX,Point,Index)

и да, это действительно так быстро и просто.

    
ответ дан Mostafa Saad 01.09.2017 в 21:07