Search code examples
c++opencvbitmappng

CreateDIBSection ERROR_TAG_NOT_FOUND when transforming a PNG type resource into a cv::Mat


I'm currently using a modified version of the following code I found here to try and convert a .png resource in my project to a HBITMAP and then into a cv::Map.

cv::Mat Resource2mat(const HMODULE hModule, const LPCSTR lpPNGName) {
    cv::Mat src;
    HRSRC found = FindResource(hModule, lpPNGName, "PNG");
    unsigned int size = SizeofResource(hModule, found);
    HGLOBAL loaded = LoadResource(hModule, found);
    void* resource_data = LockResource(loaded);

    /* Now we decode the PNG */
    vector<unsigned char> raw;
    unsigned long width, height;
    int err = decodePNG(raw, width, height, (const unsigned char*)resource_data, size);
    if (err != 0)
    {
        cout<<"\nError while decoding png splash: "<< err <<endl;
        return src;
    }

    // copy from the window device context to the bitmap device context
    BITMAPV5HEADER bmpheader = { 0 };
    bmpheader.bV5Size = sizeof(BITMAPV5HEADER);
    bmpheader.bV5Width = width;
    bmpheader.bV5Height = height;
    bmpheader.bV5Planes = 1;
    bmpheader.bV5BitCount = 32;
    bmpheader.bV5Compression = BI_BITFIELDS;
    bmpheader.bV5SizeImage = width * height * 4;
    bmpheader.bV5RedMask = 0x00FF0000;
    bmpheader.bV5GreenMask = 0x0000FF00;
    bmpheader.bV5BlueMask = 0x000000FF;
    bmpheader.bV5AlphaMask = 0xFF000000;
    bmpheader.bV5CSType = LCS_WINDOWS_COLOR_SPACE;
    bmpheader.bV5Intent = LCS_GM_BUSINESS;
    void* converted = NULL;
    HDC screen = GetDC(NULL);
    HBITMAP result = CreateDIBSection(screen, reinterpret_cast<BITMAPINFO*>(&bmpheader), DIB_RGB_COLORS, &converted, NULL, 0);
    cout << "Error Final: " << GetLastError() << endl;

    /* Copy the decoded image into the bitmap in the correct order */
    for (unsigned int y1 = height - 1, y2 = 0; y2 < height; y1--, y2++)
        for (unsigned int x = 0; x < width; x++)
        {
            *((char*)converted + 0 + 4 * x + 4 * width*y2) = raw[2 + 4 * x + 4 * width*y1]; // Blue
            *((char*)converted + 1 + 4 * x + 4 * width*y2) = raw[1 + 4 * x + 4 * width*y1]; // Green
            *((char*)converted + 2 + 4 * x + 4 * width*y2) = raw[0 + 4 * x + 4 * width*y1]; // Red
            *((char*)converted + 3 + 4 * x + 4 * width*y2) = raw[3 + 4 * x + 4 * width*y1]; // Alpha
        }

    GetDIBits(screen, result, 0, height, src.data, (BITMAPINFO *)&bmpheader, DIB_RGB_COLORS);

    cv::Mat Actual = src.clone();

    ReleaseDC(NULL, screen);
    /* Done! */
    return Actual;
}

my .rc file looks like this:

Folder tree

and the resources.h entry looks like this

definition


When running the code and hitting this line, I end up with a 2012 ( ERROR_TAG_NOT_FOUND ) error

HBITMAP result = CreateDIBSection(screen, reinterpret_cast<BITMAPINFO*>(&bmpheader), DIB_RGB_COLORS, &converted, NULL, 0);

Found it by calling GetLastError() before and after this line of code


And this is how I call this function in my int main() :

HINSTANCE BotModuleHandle = GetModuleHandle(NULL);

cout << "Attempting to load a resource: " << endl;

cv::Mat S = Resource2mat(BotModuleHandle, MAKEINTRESOURCE(103));

Thanks in advance.

Also any suggestions for a better approach for converting a PNG resource into a cv::Mat are highly appreciated


Solution

  • If all you really wish to achieve is to load the resource image into a cv::Mat, then you can do it with a much shorter function:

    cv::Mat Resource2mat(const HMODULE hModule, const LPCSTR lpPNGName)
    {
        HRSRC found = FindResource(hModule, lpPNGName, "PNG");
        unsigned int size = SizeofResource(hModule, found);
        HGLOBAL loaded = LoadResource(hModule, found);
        void* resource_data = LockResource(loaded);
    
        return cv::imdecode(cv::_InputArray(static_cast<uchar*>(resource_data), size)
            , cv::IMREAD_UNCHANGED);
    }
    

    LockResource gives you a pointer to a buffer (array of bytes) containing a PNG encoded image (as if you just read the contents of a PNG file into an array). SizeofResource gives you the size of this array in bytes.

    OpenCV provides function cv::imdecode, which can decode PNG (and other formats) images from memory buffers. There's just a small issue -- we need to pass both the pointer as well as the size in just one parameter. To do this, we can explicitly construct a temporary cv::_InputArray.


    Whole test program:

    #include <windows.h>
    #include "resource.h"
    
    #include <opencv2/opencv.hpp>
    
    
    cv::Mat Resource2mat(const HMODULE hModule, const LPCSTR lpPNGName)
    {
        HRSRC found = FindResource(hModule, lpPNGName, "PNG");
        CV_Assert(found);
        unsigned int size = SizeofResource(hModule, found);
        CV_Assert(size);
        HGLOBAL loaded = LoadResource(hModule, found);
        CV_Assert(size);
        void* resource_data = LockResource(loaded);
        CV_Assert(resource_data);
    
        return cv::imdecode(cv::_InputArray(static_cast<uchar*>(resource_data), size)
            , cv::IMREAD_UNCHANGED);
    }
    
    int main()
    {
        HINSTANCE hModule = GetModuleHandle(NULL);
        cv::Mat image(Resource2mat(hModule, MAKEINTRESOURCE(IDB_PNG1)));
        cv::imshow("Resource image", image);
        cv::waitKey();
    
        return 0;
    }
    

    Resource header resource.h:

    #define IDB_PNG1                        101
    

    Resource file pngres.rc:

    #include "resource.h"
    #include "winres.h"
    
    LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
    IDB_PNG1                PNG                     "resource.png"
    

    Running this gives me a window with the image correctly displayed:

    Program output