Я использую 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? Это действительно раздражает -___________- |
Спасибо, PhiLho, но я не могу использовать StretchDIBits ~ Я просто знаю, что это быстрее, и рисование большого изображения может быть таким быстрым ~~ Я добавил проблему, почему я не могу его использовать ~~ /
Возможно, вы можете преобразовать Bitmap в объект GDI с помощью Bitmap :: GetHBITMAP (см. Обновление моего ответа для ссылки).
Я тестировал функцию GetHBITMAP, она кажется медленной при получении HBitmap из большого изображения ... -____________- b





У вас экран с разрешением 4000 х 3000? Ух ты!
В противном случае вы должны нарисовать только видимую часть изображения, это будет намного быстрее ...
[ИЗМЕНИТЬ после первого комментария] Мое замечание действительно немного глупо, я полагаю, DrawImage будет маскировать / пропускать ненужные пиксели.
После вашего редактирования (показывающего StretchDIBits), я предполагаю, что возможный источник разницы в скорости может быть связан с тем, что StretchDIBits имеет аппаратное ускорение («Если драйвер не поддерживает изображение файла JPEG или PNG» - это намек ...), а DrawImage может (у меня нет доказательств для этого!) закодирован на C, полагаясь на мощность процессора вместо мощности графического процессора ...
Если я правильно помню, изображения DIB бывают быстрыми (несмотря на то, что они «не зависят от устройства»). См. Высокоскоростная анимация Win32: «используйте CreateDIBSection для высокоскоростной анимации». Хорошо, это применимо к DIB по сравнению с GDI в старой версии Windows (1996!), Но я думаю, что это все еще верно.
[EDIT] Возможно, функция Bitmap :: GetHBITMAP может помочь вам использовать StretchDIBits (не проверено ...).
DrawImage не проверяет границу? Это кажется глупым, если эта функция не знает, что он рисует с экрана
Bitmap :: GetHBITMAP эта функция сама занимает некоторое время
@sxingfeng: DrawImage выполняет попиксельную обрезку. Для рисования больших изображений требуется больше времени. Это глупо. Это медленно.
Что ж, чуть более десяти лет спустя 4K UHD больше не является редкостью который, боюсь!
Просто мысль; почему бы не кешировать эти значения при загрузке изображения вместо того, чтобы получать ширину и высоту изображения перед рисованием?
Скорее всего, причина в этом. Bitmap .NET имеет самые медленные средства получения ширины / высоты за всю историю программирования; Мне несколько раз приходилось сталкиваться с тем, что при профилировании они занимают 95% ЦП.
Изучите влияние явной установки режима интерполяции на 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);, хотя один вопрос - вам нужно воссоздавать кисть каждый раз, когда растровое изображение изменяется, верно?
Вы правы, метода Graphics.DrawBrush нет. Я никогда не пробовал повторно использовать одну и ту же кисть после изменения соответствующего растрового изображения, поэтому я не знаю, нужно ли вам воссоздавать ее или нет. В моих играх GDI + все объекты текстуры-кисти создавались заранее при загрузке программы, где каждая текстурная кисть представляла собой отдельный кадр анимации для спрайта.
Можно выбрать методы рисования, такие как предлагаемые cybis ... но самый быстрый способ может быть реализован с использованием низкоуровневых dll с приветствием дополнительных элементов управления в коде ...
При использовании этого решения обратите внимание на смещение / начало изображения кисти и на то, что вы нужно сделать преобразование.
Я использовал TextureBrush с полупрозрачным изображением. результат был ужасен. Изображение было полностью гнилым.
Подтверждаю действенность этого ответа! С некоторым грязным профилированием я смог нарисовать на холсте 1922 x 743 ~ 7000 раз (в различных положениях на холсте) картинку размером 128 x 128 в исходном размере и 18 x 18 в масштабируемом размере, используя DrawImage и FillRectangle. При использовании DrawImage чистое время работы составляло 428 - 448 мс (диапазон от 5 итераций), а при использовании FillRectangle чистое время работы составляло 198 - 204 мс (диапазон от 4 итераций). Чистый рендеринг GDI + без каких-либо других уловок, и все настроено на высокое качество. Получено ~ двукратное улучшение!
К сожалению, когда у меня возникла аналогичная проблема, я обнаружил, что 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 есть подробная статья об этой статуе ..
Попробуйте использовать копию растрового изображения из файла. Функция 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)
и да, это действительно так быстро и просто.
Взглянув на второй пример, я бы сказал, что StretchDIBits ускоряется на GPU, в отличие, вероятно, от DrawImage. Смотрите мой обновленный ответ. Является ли использование StretchDIBits удовлетворительным решением вашей проблемы?