Effizient Bilddaten aus Resource laden
-
Ich habe ein Bitmap als Resource und benötige die Rohdaten um diese in eine OpenGL-Textur zu kopieren. Im Moment mache ich folgendes:
HBITMAP hbmp = (HBITMAP)LoadImage(GetModuleHandle(0), MAKEINTRESOURCE(MY_BITMAP_RESOURCE), IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION); BITMAP bmp; GetObject(hbmp, sizeof bmp, &bmp); COLORREF *buffer = new COLORREF [b.bmHeight * b.bmWidth]; HDC hdc = CreateCompatibleDC(0); SelectObject(hdc, hbmp); for (int x = 0; x < b.bmWidth; x++) for (int y = 0; y < b.bmHeight; y++) buffer[x+b.bmWidth*y] = GetPixel(hdc, x, bmp.bmHeight - y); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, bmp.bmWidth, bmp.bmHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer); DeleteDC(hdc); DeleteObject(hbmp); delete buffer; ...
So weit funktioniert auch alles. Es sieht nur schrecklich ineffizient aus, Devices zu erstellen (da LoadImage ein DeviceIndependentBitmap erstellt und es unnötig ist) und alle Texturen Pixel für Pixel zu kopieren. Mit GetDIBits hatte ich keinen Erfolg wegen der Parameter (was sind Scanlines?) und auch sonst scheint es kein GetAllPixels(hdc, buffer) zu geben.
Weiß jemand wie es richtig geht?
-
Du machst es doch nur einmal beim Start der Anwendung, wen kuemmert es, wie lange das dauert.
Zu deinem eigentlichen Problem: Keine Ahnung!
-
knivil schrieb:
Du machst es doch nur einmal beim Start der Anwendung, wen kuemmert es, wie lange das dauert.
Du hast dir den Code nichtmal angesehen, oder?
Texturdaten pixelweise mit GetPixel() kopieren, ja, klar, muss man so machen.
Weil's ja so lustig ist ne Minute zu warten wo es 1-2 Sekunden auch tun würden.
-
"Richtig": FindResource(), LoadResource() und LockResource() und dann die Bitmap einlesen...
-
Du hast dir den Code nichtmal angesehen, oder?
Doch, aber ich habe nicht keine Vorstellung, wie lange die einzelnen Funktionsaufrufe benoetigen.
-
knivil schrieb:
Du hast dir den Code nichtmal angesehen, oder?
Doch, aber ich habe nicht keine Vorstellung, wie lange die einzelnen Funktionsaufrufe benoetigen.
Ok, in dem Fall: GetPixel() ist so ziemlich die langsamste Lösung, die du in der kompletten Win32 API finden wirst. Wenn du nur schnell die Farbe unterm Mauszeiger haben willst, dann geht's grad, aber zum "Einlesen" eines ganzen Bildes ist GetPixel() völlig unbrauchbar...
Das is wie einen Lastwagen voller Erde bestellen und die Erde dann mit einer Pinzette abladen, statt einfach runterzukippen...
-
@dot
Ich glaube beim "die Bitmap einlesen" hat er Probleme, deswegen will er ja LoadImage() verwenden.
Ich hab' mit WinAPI und DIBs auch schon zu lange nix mehr gemacht, deswegen bin ich da auch nimmer so fit.
Eine Möglichkeit wäre vermutlich die "von Hand" rauszukopieren.@nwp3
Wenn die Bitmap 24 Bit ist, dann ist das auch relativ einfach. In dem Fall zeigtBITMAP::bmBits
auf ein Array aus "Scanlines". Jede Scanlines representiert eine Zeile im Bild und istBITMAP::bmWidthBytes
lang.BITMAP::bmWidthBytes
ist dabei grösser oder gleichBITMAP::bmWidth * 3
.
Die Pixel in einer Zeile liegen direkt hintereinander, jeweils 3 Byte pro Pixel (je ein Byte für Rot, Grün, Blau). Die Reihenfolge der Bytes weiss ich nimmer, entweder Rot, Grün, Blau oder Blau, Grün, Rot, das merk' ich mir nie. Einfach ausprobieren.Sonst könntest du noch eine andere Library verwenden um die Bilder zu laden, das hätte dann auch den Vorteil dass du nicht auf BMP beschränkt bist sondern PNG, JPEG etc. verwenden kannst. Zur Auswahl stehen viele - GDI+, FreeImage, CImg, CxImage, OpenIL (aka DevIL) und noch viele mehr die mir nicht einfallen bzw. die ich nicht kenne.
Konkret abraten würde ich nur von OpenIL, da das Ding im Zusammenhang mit Multithreading problematisch ist.
GDI+ hätte den Vorteil dass es mit Windows mitkommt, d.h. man muss nix extra einbinden (Header Files sind Teil des DSK und die DLLs werden mit Windows mit installiert). Wenn dein Projekt erstmal sowieso nur für Windows gemacht wird würde ich also GDI+ empfehlen. Ist auch halbwegs angenehm in der Anwendung.
-
hustbaer schrieb:
@dot
Ich glaube beim "die Bitmap einlesen" hat er Probleme, deswegen will er ja LoadImage() verwenden.Ja, das glaub ich auch. In dem Fall sollte er sich aber einfach anschauen, wie das geht. Es ist nicht besonders schwer und dabei auch mehr oder weniger die maximal effiziente Lösung, da die Resource im Prinzip ein memory mapped File ist und man im Idealfall den über LockResource() gefundenen Pointer praktisch direkt an glTexImage2D() weiterreichen kann (glPixelStore() macht's möglich)...
EDIT: Die moderne built-in Lösung für alle möglichen Bildformate unter Windows wäre btw WIC.
Random Code Snippet, weil ichs grad rumliegen hab (ist zwar für D3D11, aber ich denk man kann damit was anfangen;
Resource::Blob
ist im Prinzip ein Wrapper um den ganzen LockResource() Kram; Wenn man mehrere Bilder laden will, sollte man die Factory und den FormatConverter etc. natürlich dazwischen aufheben):#include <Shlwapi.h> #include <wincodec.h> // ... com_ptr<ID3D11Texture2D> TextureAtlas::createAtlasTexture(ID3D11Device* device, const char* filename, size2ui& size) { Resource::Blob blob(filename, "IMAGE"); com_ptr<IStream> stream = SHCreateMemStream(reinterpret_cast<const BYTE*>(blob.get()), blob.size()); if (!stream) throw std::runtime_error("unable to create resource stream"); com_ptr<IWICImagingFactory> factory; if (FAILED(CoCreateInstance(CLSID_WICImagingFactory, nullptr, CLSCTX_INPROC_SERVER, IID_IWICImagingFactory, reinterpret_cast<void**>(&factory)))) throw std::runtime_error("unable to create WIC factory"); com_ptr<IWICBitmapDecoder> decoder; if (FAILED(factory->CreateDecoderFromStream(stream, nullptr, WICDecodeMetadataCacheOnDemand, &decoder))) throw std::runtime_error("error creating image decoder"); com_ptr<IWICBitmapFrameDecode> frame; if (FAILED(decoder->GetFrame(0, &frame))) throw std::runtime_error("unable to decode image"); com_ptr<IWICFormatConverter> format_converter; if (FAILED(factory->CreateFormatConverter(&format_converter))) throw std::runtime_error("unable to create format converter"); if (FAILED(format_converter->Initialize(frame, GUID_WICPixelFormat32bppRGBA, WICBitmapDitherTypeNone, nullptr, 0.0, WICBitmapPaletteTypeCustom))) throw std::runtime_error("unable to convert pixel format"); if (FAILED(format_converter->GetSize(&size.width, &size.height))) throw std::runtime_error("unable to determine image size"); D3D11_TEXTURE2D_DESC atlas_desc; atlas_desc.Width = size.width; atlas_desc.Height = size.height; atlas_desc.MipLevels = 1; atlas_desc.ArraySize = 1; atlas_desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; atlas_desc.SampleDesc.Count = 1; atlas_desc.SampleDesc.Quality = 0; atlas_desc.Usage = D3D11_USAGE_DEFAULT; atlas_desc.BindFlags = D3D11_BIND_SHADER_RESOURCE; atlas_desc.CPUAccessFlags = 0; atlas_desc.MiscFlags = 0; std::unique_ptr<BYTE[]> buffer(new BYTE[size.width * size.height * 4]); if (FAILED(format_converter->CopyPixels(nullptr, size.width * 4, size.width * size.height * 4, buffer.get()))) throw std::runtime_error("unable to read pixel data"); D3D11_SUBRESOURCE_DATA data; data.pSysMem = buffer.get(); data.SysMemPitch = size.width * 4; com_ptr<ID3D11Texture2D> atlas_tex; if (FAILED(device->CreateTexture2D(&atlas_desc, &data, &atlas_tex))) throw std::runtime_error("unable to create atlas texture"); return atlas_tex; }
-
dot schrieb:
... im Idealfall den über LockResource() gefundenen Pointer praktisch direkt an glTexImage2D() weiterreichen kann (glPixelStore() macht's möglich)...
Kann man den GL_UNPACK_ALIGNMENT Wert verwenden damit OpenGL das 4-Byte Row-Alignment von BMP Files "versteht"?
Das ginge dann ja noch.Was ich aber nicht verstehe, ist, warum es da keinen GL_UNPACK_BYTES_PER_ROW Wert gibt. (Bzw. wieso GL_UNPACK_ROW_LENGTH nicht einfach in Bytes ist)
So ziemlich alle Image-Libraries die ich kenne haben einen "bytes per line" Wert im Interface. D.h. in dem Fall müsste man erst wieder in einen Zwischenpuffer kopieren, oder übersehe ich was?