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 и написать некоторый алгоритм в битовых массивах битов, как я могу получить BITMAPINFO, если у меня есть объект Bitmap? Это очень раздражает -___________- |
Ответы
Ответ 1
У вас есть экран с разрешением 4000 x 3000? Wow!
Если нет, вы должны нарисовать только видимую часть изображения, это будет намного быстрее...
[EDIT после первого комментария] Мое замечание действительно немного глупо, я полагаю, DrawImage будет маскировать/пропускать ненужные пиксели.
После вашего редактирования (показывая StretchDIBits), я думаю, что возможный источник разницы в скорости может возникнуть из-за того, что StretchDIBits аппаратное ускорение ( "Если драйвер не поддерживает изображение в формате JPEG или PNG" - это подсказка...), в то время как DrawImage может быть (у меня нет доказательств для этого!), закодированных на C, опираясь на мощность процессора вместо GPU one...
Если я правильно помню, изображения DIB бывают быстрыми (несмотря на то, что они "независимы от устройства" ). См. Высокоскоростная анимация Win32: "используйте CreateDIBSection для высокоскоростной анимации". Хорошо, это относится к DIB против GDI, в старой версии Windows (1996!), Но я думаю, что это все еще так.
[EDIT] Возможно, Функция Bitmap:: GetHBITMAP может помочь вам использовать StretchDIBits (не проверено...).
Ответ 2
Если вы используете GDI +, класс TextureBrush - это то, что вам нужно для быстрого рендеринга изображений. Я написал пару игр с двумя играми, получив около 30 FPS или около того.
Я никогда не писал .NET-код на С++, поэтому здесь пример С#:
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, ...)
}
Ответ 3
Просто мысль; вместо того, чтобы извлекать ширину и высоту изображения перед рисованием, почему бы не кэшировать эти значения при загрузке изображения?
Ответ 4
Изучите влияние явной настройки интерполяционного режима на NearestNeighbor (где, в вашем примере, похоже, что интерполяция на самом деле не нужна! Но 300 мс - это вид стоимости высококачественной интерполяции, когда интерполяция не требуется, поэтому его стоит попробовать)
Еще одна вещь, которую нужно изучить, - изменить глубину цвета растрового изображения.
Ответ 5
К сожалению, когда у меня была аналогичная проблема, я обнаружил, что GDI +, как известно, намного медленнее, чем GDI, и, как правило, не аппаратное ускорение, но теперь Microsoft перешла к WPF, они не вернутся к улучшению GDI +!
Все производители графических карт перешли на 3D-производительность и, похоже, не заинтересованы в 2D-ускорении, и нет четкого источника информации о том, какие функции или аппаратное ускорение или нет. Очень неприятно, потому что, написав приложение в .NET с использованием GDI +, я не рад перейти на совершенно другую технологию, чтобы ускорить ее до разумных уровней.
Ответ 6
Я не думаю, что они сделают многое другое, но поскольку вам не нужно изменять размер изображения, попробуйте использовать перегрузку 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 +).
Ответ 7
Попробуйте использовать копию Bitmap из файла. Функция FromFile на некоторых файлах возвращает "медленное" изображение, но его копия будет рисоваться быстрее.
Bitmap *bitmap = Bitmap::FromFile("XXXX",...);
Bitmap *bitmap2 = new Bitmap(bitmap); // make copy
DrawImage(bitmap2,0,0,width,height);
Ответ 8
/*
Сначала извините за английский, а код отчасти отполирован, но это просто понять.
У меня была та же проблема, и я нашел лучшее решение. Вот оно.
Не используйте графическую графику (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;
Ответ 9
Я провел некоторое исследование и не смог найти способ рендеринга изображений с GDI/GDI + быстрее, чем
Graphics.DrawImage/DrawImageUnscaled
и в то же время простой, как он.
Пока я не обнаружил
ImageList.Draw(GFX,Point,Index)
и да, это действительно так быстро и просто.