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 3

    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).

    1. 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); 
    2. 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)