Is it possible to get a pixel array (RGB / RGBA) using WinAPI tools regardless of the device? So that you do not have to manually parse the file, get an array of data (pixels) according to the bit depth (color depth) of the image. It is desirable that it works independently of RLA, color maps. Just starting to deal with WinAPI will be grateful for the help in this matter.
3 answers
You can use the Windows Imaging Component :
#include <wrl/client.h> #include <Windows.h> #include <Objbase.h> #include <shellapi.h> #include <ShObjIdl.h> #include <Shlobj.h> #include <Shlwapi.h> #include <Wincodec.h> #include <vector> #include <cstdint> ::HRESULT Verify_Succeeded(const ::HRESULT hr) { if(!SUCCEEDED(hr)) { ::exit(-1); } return hr; } ::std::vector<::std::uint8_t> Extract_Data ( const ::Microsoft::WRL::ComPtr<::IWICImagingFactory> p_factory , const ::LPCWSTR psz_file_path ) { // Π‘ΠΎΠ·Π΄Π°Π΅ΠΌ Π΄Π΅ΠΊΠΎΠ΄Π΅Ρ Π΄Π»Ρ ΡΠ°ΠΉΠ»Π°. ::Microsoft::WRL::ComPtr<::IWICBitmapDecoder> p_decoder{}; Verify_Succeeded ( p_factory->CreateDecoderFromFilename ( psz_file_path , nullptr , GENERIC_READ , WICDecodeMetadataCacheOnDemand , &p_decoder ) ); // Π‘ΠΎΠ·Π΄Π°Π΅ΠΌ Π΄Π΅ΠΊΠΎΠ΄Π΅Ρ Π΄Π»Ρ ΠΊΠ°Π΄ΡΠ° (Π² ΠΏΡΠΎΡΡΠΎΠΌ Π±ΠΈΡΠΌΠ°ΠΏΠ΅ ΡΠΎΠ»ΡΠΊΠΎ ΠΎΠ΄ΠΈΠ½ ΠΊΠ°Π΄Ρ, Ρ
ΠΎΡΡ Π² // Π΄ΡΡΠ³ΠΈΡ
Π³ΡΠ°ΡΠΈΡΠ΅ΡΠΊΠΈΡ
ΡΠΎΡΠΌΠ°ΡΠ°Ρ
ΠΈΡ
ΠΌΠΎΠΆΠ΅Ρ ΠΏΡΠΈΡΡΡΡΡΠ²ΠΎΠ²Π°ΡΡ Π½Π΅ΡΠΊΠΎΠ»ΡΠΊΠΎ). ::Microsoft::WRL::ComPtr<::IWICBitmapFrameDecode> p_frame_decode{}; Verify_Succeeded(p_decoder->GetFrame(0, &p_frame_decode)); // Π‘ΠΎΠ·Π΄Π°Π΅ΠΌ ΠΊΠΎΠ½Π²Π΅ΡΡΠ΅Ρ, ΠΊΠΎΡΠΎΡΡΠΉ ΠΏΠΎΠ·Π°Π±ΠΎΡΠΈΡΡΡ, ΡΡΠΎΠ±Ρ ΠΌΡ ΠΏΠΎΠ»ΡΡΠΈΠ»ΠΈ Π΄Π°Π½Π½ΡΠ΅ Π² // Π½ΡΠΆΠ½ΠΎΠΌ Π½Π°ΠΌ ΡΠΎΡΠΌΠ°ΡΠ΅, Π½Π΅Π·Π°Π²ΠΈΡΠΈΠΌΠΎ ΠΎΡ ΠΈΡΡ
ΠΎΠ΄Π½ΠΎΠ³ΠΎ. ::Microsoft::WRL::ComPtr<::IWICFormatConverter> p_converter{}; Verify_Succeeded(p_factory->CreateFormatConverter(&p_converter)); Verify_Succeeded ( p_converter->Initialize ( p_frame_decode.Get() , GUID_WICPixelFormat32bppBGRA , WICBitmapDitherTypeNone , nullptr , 0 , WICBitmapPaletteTypeMedianCut ) ); // ΠΠ·Π²Π»Π΅ΠΊΠ°Π΅ΠΌ ΠΌΠ°ΡΡΠΈΠ² BGRA ΠΏΠΈΠΊΡΠ΅Π»Π΅ΠΉ. ::UINT width{}; ::UINT height{}; Verify_Succeeded(p_converter->GetSize(&width, &height)); const ::UINT stride_bytes_count{width * 4}; const ::UINT buffer_bytes_count{stride_bytes_count * height}; ::std::vector<::std::uint8_t> buffer{}; buffer.resize(buffer_bytes_count); Verify_Succeeded ( p_converter->CopyPixels ( nullptr, stride_bytes_count, buffer_bytes_count, buffer.data() ) ); return buffer; } int main() { Verify_Succeeded ( ::CoInitializeEx(nullptr, COINIT_MULTITHREADED | COINIT_DISABLE_OLE1DDE) ); { ::Microsoft::WRL::ComPtr<::IWICImagingFactory> p_factory{}; Verify_Succeeded ( ::CoCreateInstance ( CLSID_WICImagingFactory , nullptr , CLSCTX_INPROC_SERVER , IID_IWICImagingFactory , &p_factory ) ); auto const raw_pixels{Extract_Data(p_factory, L"test.bmp")}; // Π΄Π΅Π»Π°Π΅ΠΌ ΡΡΠΎ-Π½ΠΈΠ±ΡΠ΄Ρ } ::CoUninitialize(); return 0; } - Thanks for the full answer) - Dmitry
I will add the classic version. In GDI for such cases there are functions CreateDIBSection and SetDIBits . I will give 2 examples of how to use them in such cases. Suppose we want to operate with pixels of 32-bit depth, no matter what format the source file (depth, compression).
Load the raster from the file.
std::ifstream file("VGALOGO.BMP", std::ios::binary); std::vector<char> src((std::istreambuf_iterator<char>(file)), (std::istreambuf_iterator<char>())); PBITMAPFILEHEADER pRes = PBITMAPFILEHEADER(&src[0]); PBITMAPINFO srcFmt = PBITMAPINFO(pRes + 1); PVOID srcBits = ((char*)pRes) + pRes->bfOffBits; BITMAPINFO myFmt = {0}; myFmt.bmiHeader.biSize = sizeof(myFmt.bmiHeader); myFmt.bmiHeader.biBitCount = 32; myFmt.bmiHeader.biCompression = BI_RGB; myFmt.bmiHeader.biWidth = srcFmt->bmiHeader.biWidth; myFmt.bmiHeader.biHeight = -srcFmt->bmiHeader.biHeight; myFmt.bmiHeader.biPlanes = 1; HDC hdc = GetDC(HWND_DESKTOP); PVOID pBmp = nullptr; hBmp = CreateDIBSection(hdc, &myFmt, DIB_RGB_COLORS, &pBmp, 0, 0); SetDIBits(hdc, hBmp, 0, srcFmt->bmiHeader.biHeight, srcBits, srcFmt, DIB_RGB_COLORS); ReleaseDC(HWND_DESKTOP, hdc);We load raster from resources.
HRSRC hRSrc = FindResource(hInstance, MAKEINTRESOURCE(IDI_SO771000), RT_RCDATA); HGLOBAL hRes = LoadResource(hInstance, hRSrc); PBITMAPFILEHEADER pRes = PBITMAPFILEHEADER(LockResource(hRes)); PBITMAPINFO srcFmt = PBITMAPINFO(pRes + 1); PVOID srcBits = ((char*)pRes) + pRes->bfOffBits; BITMAPINFO myFmt = {0}; myFmt.bmiHeader.biSize = sizeof(myFmt.bmiHeader); myFmt.bmiHeader.biBitCount = 32; myFmt.bmiHeader.biCompression = BI_RGB; myFmt.bmiHeader.biWidth = srcFmt->bmiHeader.biWidth; myFmt.bmiHeader.biHeight = -srcFmt->bmiHeader.biHeight; myFmt.bmiHeader.biPlanes = 1; HDC hdc = GetDC(HWND_DESKTOP); PVOID pBmp = nullptr; hBmp = CreateDIBSection(hdc, &myFmt, DIB_RGB_COLORS, &pBmp, 0, 0); SetDIBits(hdc, hBmp, 0, srcFmt->bmiHeader.biHeight, srcBits, srcFmt, DIB_RGB_COLORS); ReleaseDC(HWND_DESKTOP, hdc);
Actually, the difference is only in the first 3 lines. In both cases, the raster format you will operate on is specified in the myFmt structure.
CreateDIBSection creates a raster with this format, and SetDIBits does just the work you donβt want to do yourself β converts data from the original format to myFmt . LoadImage , by the way, does the same with the LR_CREATEDIBSECTION flag, but does not allow you to choose a format.
The data itself is located at pBmp . You do not need to monitor this memory block, it will be released automatically when hBmp removed.
If all operations completed successfully (I omitted the checks for simplicity), then pBmp points to the RGBQUAD array, regardless of the format of the original *.bmp .
For example, I brought this raster to the window, and gave the opportunity to draw with the mouse right in the memory of this raster:
... DIBSECTION bmp; GetObject(hBmp, sizeof(bmp), &bmp); ... case WM_MOUSEMOVE: if (wParam & MK_LBUTTON) { int x = (short)LOWORD(lParam); int y = (short)HIWORD(lParam); if ((x >= 0) && (x < bmp.dsBm.bmWidth)) { if ((y >= 0) && (y < bmp.dsBm.bmHeight)) { auto &pixel = ((RGBQUAD*)pBmp)[y * bmp.dsBm.bmWidth + x]; pixel.rgbRed = 255; pixel.rgbGreen = 0; pixel.rgbBlue = 0; InvalidateRect(hWnd, nullptr, FALSE); } } } break; case WM_PAINT: { PAINTSTRUCT ps; HDC hdc = BeginPaint(hWnd, &ps); HDC hMemDC = CreateCompatibleDC(hdc); HGDIOBJ hSysBmp = SelectObject(hMemDC, hBmp); BitBlt(hdc, 0, 0, bmp.dsBm.bmWidth, bmp.dsBm.bmHeight, hMemDC, 0, 0, SRCCOPY); SelectObject(hMemDC, hSysBmp); DeleteDC(hMemDC); EndPaint(hWnd, &ps); } break; If not everything is clear, I can attach a demo project. This VGALOGO.BMP , for example, was 4Ρ
Π±ΠΈΡΠ½ΡΠΌ with RLE compression.
You can also use GDI +, it is also written in C ++:
#include <GdiPlus.h> using namespace Gdiplus; #pragma comment(lib, "GdiPlus.lib") int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpCmdLine, int iCmdShow) { GdiplusStartupInput gdiplusStartupInput; ULONG_PTR gdiplusToken; GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL); // Π·Π°Π³ΡΡΠΆΠ°Π΅ΠΌ ΡΠ΅ΡΡΡΡΡ gdi //ΡΠΎΠ·Π΄Π°Π΅ΠΌ ΠΎΠΊΠ½ΠΎ, ΠΏΠΎΠ»ΡΡΠ°Π΅ΠΌ hwnd PAINTSTRUCT ps; HDC hdc = BeginPaint(hwnd, &ps); Bitmap bm(L"picture.bmp"); // Π³ΡΡΠ·ΠΈΠΌ ΡΠ°ΠΉΠ» Graphics temp(&bm); // ΡΠ²ΡΠ·Π°Π»ΠΈ Π³ΡΠ°ΡΠΈΡΠ΅ΡΠΊΠΈΠΉ ΠΎΠ±ΡΠ΅ΠΊΡ Ρ Π·Π°Π³ΡΡΠΆΠ΅Π½Π½ΡΠΌ bmp Color cl; temp.GetPixel(0,0, &cl); // ΠΏΠΎΠ»ΡΡΠΈΠ»ΠΈ ΡΠ²Π΅Ρ ΠΏΠΈΠΊΡΠ΅Π»Π° temp.SetPixel(1,1, cl); // ΡΡΡΠ°Π½ΠΎΠ²ΠΈΠ»ΠΈ ΡΠ²Π΅Ρ ΠΏΠΈΠΊΡΠ΅Π»Π° EndPaint(hWnd, &ps); GdiplusShutdown(gdiplusToken); // Π²ΡΠ³ΡΡΠΆΠ°Π΅ΠΌ ΡΠ΅ΡΡΡΡΡ gdi return 0; } Purely symbolic example, everything in the main function .. the only one, the speed of these methods is terrible (Set \ GetPixel)