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

Взглянув на второй пример, я бы сказал, что StretchDIBits ускоряется на GPU, в отличие, вероятно, от DrawImage. Смотрите мой обновленный ответ. Является ли использование StretchDIBits удовлетворительным решением вашей проблемы?

PhiLho 05.11.2008 15:49

Спасибо, PhiLho, но я не могу использовать StretchDIBits ~ Я просто знаю, что это быстрее, и рисование большого изображения может быть таким быстрым ~~ Я добавил проблему, почему я не могу его использовать ~~ /

user25749 05.11.2008 16:38

Возможно, вы можете преобразовать Bitmap в объект GDI с помощью Bitmap :: GetHBITMAP (см. Обновление моего ответа для ссылки).

PhiLho 05.11.2008 18:59

Я тестировал функцию GetHBITMAP, она кажется медленной при получении HBitmap из большого изображения ... -____________- b

user25749 08.11.2008 04:57
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
18
4
23 337
9
Перейти к ответу Данный вопрос помечен как решенный

Ответы 9

Ответ принят как подходящий

У вас экран с разрешением 4000 х 3000? Ух ты!

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

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

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

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

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

DrawImage не проверяет границу? Это кажется глупым, если эта функция не знает, что он рисует с экрана

user25749 05.11.2008 14:31

Bitmap :: GetHBITMAP эта функция сама занимает некоторое время

user25749 08.11.2008 04:56

@sxingfeng: DrawImage выполняет попиксельную обрезку. Для рисования больших изображений требуется больше времени. Это глупо. Это медленно.

Jared Updike 30.04.2010 23:00

Что ж, чуть более десяти лет спустя 4K UHD больше не является редкостью который, боюсь!

Vector Sigma 25.01.2021 16:04

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

Скорее всего, причина в этом. Bitmap .NET имеет самые медленные средства получения ширины / высоты за всю историю программирования; Мне несколько раз приходилось сталкиваться с тем, что при профилировании они занимают 95% ЦП.

Roman Starkov 01.02.2012 14:58

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

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

Если вы используете GDI +, класс TextureBrush - это то, что вам нужно для быстрого рендеринга изображений. Я написал с ним пару 2D-игр, получив около 30 кадров в секунду.

Я никогда не писал .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, ...)
}

должно быть: e.Graphics.FillRectangle(myBrush, ClientRectangle);, хотя один вопрос - вам нужно воссоздавать кисть каждый раз, когда растровое изображение изменяется, верно?

argh 01.06.2010 18:13

Вы правы, метода Graphics.DrawBrush нет. Я никогда не пробовал повторно использовать одну и ту же кисть после изменения соответствующего растрового изображения, поэтому я не знаю, нужно ли вам воссоздавать ее или нет. В моих играх GDI + все объекты текстуры-кисти создавались заранее при загрузке программы, где каждая текстурная кисть представляла собой отдельный кадр анимации для спрайта.

Cybis 10.06.2010 23:58

Можно выбрать методы рисования, такие как предлагаемые cybis ... но самый быстрый способ может быть реализован с использованием низкоуровневых dll с приветствием дополнительных элементов управления в коде ...

dankyy1 16.08.2010 15:10

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

Uwe Keim 06.05.2012 12:21

Я использовал TextureBrush с полупрозрачным изображением. результат был ужасен. Изображение было полностью гнилым.

Mahmood Dehghan 15.12.2013 22:07

Подтверждаю действенность этого ответа! С некоторым грязным профилированием я смог нарисовать на холсте 1922 x 743 ~ 7000 раз (в различных положениях на холсте) картинку размером 128 x 128 в исходном размере и 18 x 18 в масштабируемом размере, используя DrawImage и FillRectangle. При использовании DrawImage чистое время работы составляло 428 - 448 мс (диапазон от 5 итераций), а при использовании FillRectangle чистое время работы составляло 198 - 204 мс (диапазон от 4 итераций). Чистый рендеринг GDI + без каких-либо других уловок, и все настроено на высокое качество. Получено ~ двукратное улучшение!

Vector Sigma 15.03.2020 00:37

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

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

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

DrawImage(bitmap,0,0);

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

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

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

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

У msdn.microsoft.com/en-us/library/1bttkazd(VS.71).aspx есть подробная статья об этой статуе ..

dankyy1 16.08.2010 12:40

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

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

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

DrawImage(bitmap2,0,0,width,height);

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

Не используйте: Graphics graphics (hdc); graphics.DrawImage (gpBitmap, 0, 0); Это медленно.

Используйте: GetHBITMAP (Gdiplus :: Color (), & 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;

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

Graphics.DrawImage/DrawImageUnscaled

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

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

ImageList.Draw(GFX,Point,Index)

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

Другие вопросы по теме