gdi+ performance drawimage

gdi+Graphics:: DrawImage realmente lento ~~



performance (9)

Estoy usando un gráfico GDI + para dibujar una imagen de 4000 * 3000 en la pantalla, pero es muy lento. Tarda unos 300ms. Ojalá ocupe menos de 10 ms.

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

//-------------------------------------------- // esta parte toma unos 300ms, ¡terrible!

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

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

No puedo usar CachedBitmap porque quiero editar el mapa de bits más tarde.

¿Cómo puedo mejorarlo? ¿O hay algo mal?

Esta función GDI nativa también dibuja la imagen en la pantalla, y solo toma 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);

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

Si quiero usar StretchDIBits, necesito pasar BITMAPINFO, pero ¿cómo puedo obtener BITMAPINFO desde un objeto GDI + Bitmap? Hice el experimento con FreeImage lib, llamé a StretchDIBits usando el objeto FreeImageplus, dibujó realmente rápido. Pero ahora necesito dibujar Bitmap y escribir algún algoritmo en la matriz de bits de Bitmap, ¿cómo puedo obtener BITMAPINFO si tengo un objeto Bitmap? Es realmente molesto -___________- |


Solo un pensamiento; en lugar de recuperar el ancho y alto de la imagen antes de dibujar, ¿por qué no almacenar en caché estos valores cuando carga la imagen?


¿Tienes una pantalla de resolución de 4000 x 3000? ¡Guauu!

Si no, debes dibujar solo la parte visible de la imagen, sería mucho más rápido ...

[EDITAR después del primer comentario] Mi observación es realmente un poco estúpida, supongo DrawImage enmascara / salta píxeles innecesarios.

Después de su edición (que muestra StretchDIBits), supongo que una posible fuente de diferencia de velocidad puede provenir del hecho de que StretchDIBits está acelerado por hardware (" Si el controlador no admite la imagen de archivo JPEG o PNG " es una pista ...) mientras DrawImage podría ser (¡no tengo pruebas para eso!) codificado en C, confiando en la potencia de la CPU en lugar de la de la GPU ...

Si recuerdo correctamente, las imágenes DIB son rápidas (a pesar de ser "independiente del dispositivo"). Consulte Animación de alta velocidad Win32 : " use CreateDIBSection para hacer una animación de alta velocidad ". OK, se aplica a DIB vs. GDI, en la versión anterior de Windows (1996!) Pero creo que sigue siendo cierto.

[EDITAR] Tal vez la función Bitmap :: GetHBITMAP pueda ayudarte a usar StretchDIBits (no probado ...).


Explore el impacto de establecer explícitamente el modo de interpolación a NearestNeighbor (donde, en su ejemplo, ¡parece que la interpolación no es realmente necesaria!) Pero 300ms es el tipo de costo de hacer interpolación de alta calidad cuando no se necesita interpolación, por lo que vale la pena tratar)

Otra cosa para explorar es cambiar la profundidad de color del mapa de bits.


Lamentablemente, cuando tuve un problema similar, descubrí que se sabe que GDI + es mucho más lento que GDI y, en general, no se aceleró el hardware, pero ahora que Microsoft pasó a WPF, ¡no volverán a mejorar GDI +!

Todos los fabricantes de tarjetas gráficas se han movido al rendimiento 3D y no parecen interesados ​​en la aceleración 2D, y no hay una fuente clara de información sobre qué funciones son o pueden ser aceleradas o no. Muy frustrante porque al haber escrito una aplicación en .NET usando GDI +, no estoy contento de cambiar a una tecnología completamente diferente para acelerarla hasta niveles razonables.


Si está utilizando GDI +, la clase TextureBrush es lo que necesita para renderizar imágenes rápidamente. He escrito un par de juegos en 2d con eso, obteniendo alrededor de 30 FPS más o menos.

Nunca he escrito el código .NET en C ++, así que aquí hay un ejemplo de 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, ...) }


No creo que sean muy diferentes, pero como no necesitas cambiar el tamaño de la imagen, intenta usar la sobrecarga de DrawImage que no intenta cambiar el tamaño:

DrawImage(bitmap,0,0);

Como dije, dudo que vaya a hacer ninguna diferencia, porque estoy seguro de que DrawImage verifica el ancho y la altura del mapa de bits, y si no se necesita cambiar el tamaño, simplemente llama a esta sobrecarga. (Espero que no se moleste en recorrer los 12 millones de píxeles que no realizan ningún trabajo real).

Actualización: mis reflexiones son incorrectas. Desde entonces, me enteré, pero los chicos comentaron me recordaron mi respuesta anterior: quieres especificar el tamaño del destino; a pesar de que coincide con el tamaño de la fuente:

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

El motivo se debe a las diferencias de ppp entre el ppp de bitmap de bitmap y el ppp del destino. GDI + realizará una escala para que la imagen salga del "tamaño" correcto (es decir, en pulgadas)

Lo que aprendí por mi cuenta desde el pasado mes de octubre es que realmente quieres dibujar una versión "en caché" de tu mapa de bits. Hay una clase CachedBitmap en GDI +. Hay algunos trucos para usarlo. Pero al final tengo un bit de función de (Delphi) que lo hace.

La advertencia es que CachedBitmap puede CachedBitmap ser válido, lo que significa que no se puede usar para dibujar. Esto sucede si el usuario cambia las resoluciones o las profundidades de color (por ejemplo, Escritorio remoto). En ese caso, DrawImage fallará y tendrá que volver a crear 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;

El cachedBitmap se pasa por referencia. Se DrawCachedBitmap primera llamada a DrawCachedBitmap en la versión en caché . Luego lo pasa en llamadas posteriores, por ejemplo:

Image imgPrintInvoice = new Image.FromFile("printer.png"); CachedBitmap imgPrintInvoiceCached = null; ... int glyphSize = 16 * (GetCurrentDpi() / 96); DrawCachedBitmap(imgPrintInvoice , ref imgPrintInvoiceCached , graphics, 0, 0, glyphSize, glyphSize);

utilizo la rutina para dibujar glifos en los botones, teniendo en cuenta el DPI actual. Lo mismo podría haber sido utilizado por el equipo de Internet Explorer para dibujar imágenes cuando el usuario está ejecutando altas ppp (es decir, es muy lento dibujar imágenes ampliadas, porque usan GDI +).


Trate de usar una copia de Bitmap del archivo. La función FromFile en algunos archivos devuelve una imagen "lenta", pero su copia dibujará más rápido.

Bitmap *bitmap = Bitmap::FromFile("XXXX",...); Bitmap *bitmap2 = new Bitmap(bitmap); // make copy DrawImage(bitmap2,0,0,width,height);


/ * Lo siento por ma English, y el código está parcialmente en polaco, pero es simple de entender. Tuve el mismo problema y encontré la mejor solución. Aquí está.

No utilizar: gráficos gráficos (hdc); graphics.DrawImage (gpBitmap, 0, 0); Es lento.

Use: GetHBITMAP (Gdiplus :: Color (), & g_hBitmap) para HBITMAP y dibuje usando mi función ShowBitmapStretch ().

¡Puedes redimensionarlo y es mucho más rápido! Artur Czekalski / Polonia

* /

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


Hice algunas investigaciones y no pude encontrar una forma de renderizar imágenes con GDI / GDI + más rápido que

Graphics.DrawImage/DrawImageUnscaled

y al mismo tiempo simple como él.

Hasta que descubrí

ImageList.Draw(GFX,Point,Index)

y sí, es realmente tan rápido y simple.