fastest way to copy array to wxBitmap

If you are using the main C++ distribution of wxWidgets, Feel free to ask any question related to wxWidgets development here. This means questions regarding to C++ and wxWidgets, not compile problems.
dalmatian101
In need of some credit
In need of some credit
Posts: 7
Joined: Sun Sep 29, 2019 3:39 am

fastest way to copy array to wxBitmap

Post by dalmatian101 » Mon May 18, 2020 4:35 am

Hi,

The wxWidget documentation says:
" wxBitmap is intended to be a wrapper of whatever is the native image format that is quickest/easiest to draw to a DC or to be the target of the drawing operations performed on a wxMemoryDC"

I am using the following code snippet inside loop (as in the wxWidget documentation) to copy uint8_t* rawData to wxBitmap:

Code: Select all

	uint8_t* rawData = ...;
 	...
	wxBitmap bmp(width, height, 24);
	wxNativePixelData data(bmp);
	wxNativePixelData::Iterator p(data);
	...
        p.Red() = rawData[i++];
        p.Green() = rawData[i++];
        p.Blue() = rawData[i++];
        ...
 
I found that the copy operation using loop iterator took quite a lot of cpu time, especially when the image size is very large. Is there any other way to directly copy the rawData to wxBitmap instead of using loop iterator for speeding up the process?

For information, the rawData is a raw pixel data acquired from a camera, which is then converted into RGB format before copied to wxBitmap. Some drawing operation and scaling are performed before displaying the final results.

Thanks.

PB
Part Of The Furniture
Part Of The Furniture
Posts: 2478
Joined: Sun Jan 03, 2010 5:45 pm

Re: fastest way to copy array to wxBitmap

Post by PB » Mon May 18, 2020 8:14 am

I don't think that there is portable way to do that any faster than using the pixel iterator.

Unlike wxImage, wxBitmap is a wrapper for a native bitmap and the way it stores its data depends not only on the platform but also on its parameters (e.g., DIB vs DDB on Windows).

EDIT
At least on MSW and with MSVC, there is a HUGE difference between the Debug and Release configurations. On my system, the code below takes almost 2 s in the Debug configuration but only about 30 ms in the Release one.

Code: Select all

#include <wx/wx.h>
#include <wx/rawbmp.h>
#include <wx/stopwatch.h>

class MyApp : public wxApp
{
public:
    bool OnInit() override
    {
        const size_t imageWidth    = 4000;
        const size_t imageHeight   = 4000;
        const size_t bytesPerPixel = 3; // RGB, no alpha
        const size_t RGBDataSize = imageWidth * imageHeight * bytesPerPixel;
        wxByte* RGBData = new wxByte[RGBDataSize];
        wxBitmap bitmap(imageWidth, imageHeight, bytesPerPixel * 8);
        wxNativePixelData pixelData(bitmap);

        memset(RGBData, 255, RGBDataSize);
        
        if ( pixelData )
        {
            const wxByte* currentRGB = RGBData;
            wxStopWatch stopWatch;

            for ( int row = 0; row < pixelData.GetHeight(); ++row )
            {
                wxNativePixelData::Iterator iterator(pixelData);

                iterator.MoveTo(pixelData, 0, row);
                for ( int col = 0; col < pixelData.GetWidth(); ++col, ++iterator )
                {
                    iterator.Red()   = *currentRGB++;
                    iterator.Green() = *currentRGB++;
                    iterator.Blue()  = *currentRGB++;
                }
            }
            wxLogMessage("Converting raw RGB data to wxBitmap took %ld ms.", stopWatch.Time());

            wxInitAllImageHandlers();
            bitmap.SaveFile("TestBitmap.png", wxBITMAP_TYPE_PNG);
        }
        else
            wxLogError("Could not create wxNativePixelData.");

        delete[] RGBData;
        return false;
    }
}; wxIMPLEMENT_APP(MyApp);

dalmatian101
In need of some credit
In need of some credit
Posts: 7
Joined: Sun Sep 29, 2019 3:39 am

Re: fastest way to copy array to wxBitmap

Post by dalmatian101 » Mon May 18, 2020 2:43 pm

Thanks for the reply and the speed test.
I just thought whether it is possible to use direct memory copy such as memcpy to shorten the copy time.

On my system using MSW and MSVC release mode I got about 30 to 50 ms for 4000 x 3000 pixels with the pixel iterator.

User avatar
doublemax
Moderator
Moderator
Posts: 14992
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: fastest way to copy array to wxBitmap

Post by doublemax » Mon May 18, 2020 4:30 pm

I just thought whether it is possible to use direct memory copy such as memcpy to shorten the copy time.
If you can request the data from the camera in BGR format, you could (ab-)use the pixel iterators to at least copy the data line-by-line using memcpy. Of course this would be platform dependent.
Use the source, Luke!

dalmatian101
In need of some credit
In need of some credit
Posts: 7
Joined: Sun Sep 29, 2019 3:39 am

Re: fastest way to copy array to wxBitmap

Post by dalmatian101 » Wed May 20, 2020 6:22 am

If you can request the data from the camera in BGR format, you could (ab-)use the pixel iterators to at least copy the data line-by-line using memcpy. Of course this would be platform dependent.
Could you provide more hint on how to achieve that? and which platform that would be?

Thanks.

PB
Part Of The Furniture
Part Of The Furniture
Posts: 2478
Joined: Sun Jan 03, 2010 5:45 pm

Re: fastest way to copy array to wxBitmap

Post by PB » Wed May 20, 2020 7:53 am

FWIW, I tried the conversion using MSW-specific code, which requires data stored as BGR instead of RGB and DWORD aligned image rows. It seems to be almost 40% faster.
setdibbits.png
setdibbits.png (7.75 KiB) Viewed 499 times

Code: Select all

#include <wx/wx.h>
#include <wx/rawbmp.h>
#include <wx/stopwatch.h>

struct RGBData
{
    size_t width{0}, height{0};
    size_t bytesPerPixel{3};
    wxByte* bits{nullptr};
};

bool ConvertRGBTowxBitmapPortable(const RGBData& data, wxBitmap& bitmap)
{
    wxCHECK(data.bytesPerPixel * 8 == 24, false);

    const wxByte* currentRGB = data.bits;
    wxBitmap result(data.width, data.height, data.bytesPerPixel * 8);
    wxNativePixelData pixelData(result);

    if ( !pixelData )
        return false;

    for ( int row = 0; row < pixelData.GetHeight(); ++row )
    {
        wxNativePixelData::Iterator iterator(pixelData);

        iterator.MoveTo(pixelData, 0, row);
        for ( int col = 0; col < pixelData.GetWidth(); ++col, ++iterator )
        {
            iterator.Red()   = *currentRGB++;
            iterator.Green() = *currentRGB++;
            iterator.Blue()  = *currentRGB++;
        }
    }

    bitmap = result;
    return bitmap.IsOk();
}

#ifndef __WXMSW__
    #define ConvertRGBTowxBitmapMSW ConvertRGBTowxBitmapPortable
#else

// data must be stored as BGR, not RGB!
bool ConvertRGBTowxBitmapMSW(const RGBData& data, wxBitmap& bitmap)
{
    wxCHECK(data.bytesPerPixel * 8 == 24, false);
    wxCHECK(reinterpret_cast<uintptr_t>(data.bits) % 2 == 0, false);
    wxCHECK(data.width % 2 == 0, false);

    wxBitmap result(data.width, data.height, 24);

    if ( !result.IsOk() || !result.IsDIB() )
        return false;

    HDC hScreenDC = ::GetDC(nullptr);
    int oldICCMMode = 0;
    BITMAPINFO bitmapInfo{0};
    bool success;

    bitmapInfo.bmiHeader.biSize          = sizeof(BITMAPINFO) - sizeof(RGBQUAD);
    bitmapInfo.bmiHeader.biWidth         = result.GetWidth();
    bitmapInfo.bmiHeader.biHeight        = 0 - result.GetHeight();
    bitmapInfo.bmiHeader.biPlanes        = 1;
    bitmapInfo.bmiHeader.biBitCount      = 24;
    bitmapInfo.bmiHeader.biCompression   = BI_RGB;

    oldICCMMode = ::SetICMMode(hScreenDC, ICM_OFF);
    success = ::SetDIBits(hScreenDC, result.GetHBITMAP(), 0, result.GetHeight(),
                    data.bits, &bitmapInfo, DIB_RGB_COLORS) != 0;

    if ( oldICCMMode != 0 )
        ::SetICMMode(hScreenDC, oldICCMMode);
    ::ReleaseDC(nullptr, hScreenDC);

    if ( !success )
    {
        return false;
    }

    bitmap = result;
    return bitmap.IsOk();
}
#endif // #ifndef __WXMSW__

typedef bool (*RGB2wxBitmapFn)(const RGBData&, wxBitmap&);

bool TimeConversion(RGB2wxBitmapFn conversionFunction,
                    const RGBData& data, size_t runCount,
                    long& totalTimeInMs)
{
    wxStopWatch stopWatch;
    wxBitmap bitmap;

    for ( size_t i = 0; i < runCount; ++i )
    {
        if ( !conversionFunction(data, bitmap) )
            return false;
    }

    totalTimeInMs = stopWatch.Time();
    return true;
}

class MyApp : public wxApp
{
public:
    bool OnInit() override
    {
        const size_t runCount      = 100;
        const size_t imageWidth    = 4000;
        const size_t imageHeight   = 4000;
        const size_t bytesPerPixel = 3; // no alpha
        const size_t RGBDataSize = imageWidth * imageHeight * bytesPerPixel;

        RGBData rgbData;
        bool portableSuccess, MSWSuccess;
        long totalPortableTime = 0, totalMSWTime = 0;

        rgbData.width = imageWidth; rgbData.height = imageHeight;
        rgbData.bytesPerPixel = bytesPerPixel;
        rgbData.bits = new wxByte[RGBDataSize];
        memset(rgbData.bits, 255, RGBDataSize);

        portableSuccess = TimeConversion(ConvertRGBTowxBitmapPortable, rgbData, runCount, totalPortableTime);
        MSWSuccess = TimeConversion(ConvertRGBTowxBitmapMSW, rgbData, runCount, totalMSWTime);

        if ( portableSuccess && MSWSuccess )
        {
            long avgPortableTime = totalPortableTime / runCount;
            long avgMSWTime = totalMSWTime / runCount;

            wxLogMessage("Run count: %zu\n"
                         "ConvertRGBTowxBitmapPortable(): %ld ms total, %ld ms per run\n"
                         "ConvertRGBTowxBitmapMSW(): %ld ms total, %ld ms per run\n"
                         "Total MSW / Portable: %.2f%%",
                         runCount,
                         totalPortableTime, avgPortableTime,
                         totalMSWTime, avgMSWTime,
                         ((double)totalMSWTime / totalPortableTime) * 100);
        }
        else
            wxLogError("Conversion failed!");

        delete[] rgbData.bits;

        return false;
    }
}; wxIMPLEMENT_APP(MyApp);
EDIT: Fixed possible HDC leak in MSW code.
Last edited by PB on Wed May 20, 2020 2:33 pm, edited 3 times in total.

User avatar
doublemax
Moderator
Moderator
Posts: 14992
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: fastest way to copy array to wxBitmap

Post by doublemax » Wed May 20, 2020 9:52 am

Nice one PB!

Any idea why the difference isn't bigger? I would have expected the MSW specific version to be faster by several factors.
Use the source, Luke!

PB
Part Of The Furniture
Part Of The Furniture
Posts: 2478
Joined: Sun Jan 03, 2010 5:45 pm

Re: fastest way to copy array to wxBitmap

Post by PB » Wed May 20, 2020 11:56 am

No idea? I tried turning off color management but with no effect.

I am no expert on bitmaps or speed optimization. But I believe some time is lost reverting rows: Windows bitmap rows are by default stored bottom-to-top, if you do not like it you have to request it (creating the bitmap with negative height).

For example, in the code below, the code inside #ifdef 1 takes about the same time as the code above using SetDIBBits(), i.e., about 60% of the portable code.
However, the incorrect code inside #else takes about 42% of the portable code.

Code: Select all

// data must be stored as BGR, not RGB!
bool ConvertRGBTowxBitmapMSW2(const RGBData& data, wxBitmap& bitmap)
{
    wxCHECK(data.bytesPerPixel * 8 == 24, false);
    wxCHECK(reinterpret_cast<uintptr_t>(data.bits) % 2 == 0, false);
    wxCHECK(data.width % 2 == 0, false);

    wxBitmap result(data.width, data.height, 24);
    BITMAP bmp{0};

    if ( !result.IsOk() || !result.IsDIB() )
        return false;
    if ( !::GetObject(result.GetHBITMAP(), sizeof(bmp), &bmp) )
        return false;

    wxCHECK(bmp.bmWidth == result.GetWidth() && bmp.bmHeight == result.GetHeight()
            && bmp.bmBitsPixel == 24 && bmp.bmPlanes == 1, false );

#if 1
    // we cannot just copy the whole block because the DIB
    // wouble be bottom-to-top
    const size_t rowSize = data.width * 3;
    wxByte* dest = static_cast<wxByte*>(bmp.bmBits) + (data.height - 1) * rowSize;
    wxByte* src = data.bits;

    for ( size_t i = 0; i < data.height; ++i )
    {
        memcpy(dest, src, rowSize);
        dest -= rowSize;
        src  += rowSize;
    }
#else
    // the result will be bottom-to-top
    memcpy(bmp.bmBits, data.bits, data.height * data.width * 3);
#endif

    bitmap = result;
    return bitmap.IsOk();
}
EDIT
For some reason, this simple native code is at least as slow as the portable one and I have no idea why (that "bitmap.InitFromHBITMAP()" call makes no difference)...

Code: Select all

// data must be stored as BGR, not RGB!
bool ConvertRGBTowxBitmapMSW3(const RGBData& data, wxBitmap& bitmap)
{
    wxCHECK(data.bytesPerPixel * 8 == 24, false);
    wxCHECK(reinterpret_cast<uintptr_t>(data.bits) % 2 == 0, false);
    wxCHECK(data.width % 2 == 0, false);

    BITMAPINFO bitmapInfo{0};
    HBITMAP hBitmap{nullptr};
    HDC hScreenDC = ::GetDC(nullptr);

    if ( !hScreenDC )
        return false;

    bitmapInfo.bmiHeader.biSize          = sizeof(BITMAPINFO) - sizeof(RGBQUAD);
    bitmapInfo.bmiHeader.biWidth         = data.width;
    bitmapInfo.bmiHeader.biHeight        = 0 - data.height;
    bitmapInfo.bmiHeader.biPlanes        = 1;
    bitmapInfo.bmiHeader.biBitCount      = 24;
    bitmapInfo.bmiHeader.biCompression   = BI_RGB;

    hBitmap = ::CreateDIBitmap(hScreenDC, &bitmapInfo.bmiHeader,
        CBM_INIT, data.bits, &bitmapInfo, DIB_RGB_COLORS);
    ::ReleaseDC(nullptr, hScreenDC);

    if ( !hBitmap )
        return false;

    return bitmap.InitFromHBITMAP(hBitmap, data.width, data.height, 24);
}

dalmatian101
In need of some credit
In need of some credit
Posts: 7
Joined: Sun Sep 29, 2019 3:39 am

Re: fastest way to copy array to wxBitmap

Post by dalmatian101 » Fri May 22, 2020 2:34 am

Hi PB and doublemax,

Thanks for the native sample codes!
With the native codes on my system I also got about 60% time of the portable code.

PB
Part Of The Furniture
Part Of The Furniture
Posts: 2478
Joined: Sun Jan 03, 2010 5:45 pm

Re: fastest way to copy array to wxBitmap

Post by PB » Fri May 22, 2020 6:10 am

I still do not feel right about the MSW-code I posted: it seems to work but it should not.

If I understand the MSW bitmap docs correctly, they state that the length of the row needs to be DWORD-aligned, so if necessary, the end of the rows must be padded.

However 24-bit data I used in my code above certainly are not like that?

dalmatian101
In need of some credit
In need of some credit
Posts: 7
Joined: Sun Sep 29, 2019 3:39 am

Re: fastest way to copy array to wxBitmap

Post by dalmatian101 » Fri May 22, 2020 7:24 am

Hi PB,

In your posted code, the width of the image is 4000 pixel * 3 (24bit) = 12000 bytes, which is DWORD aligned (dividable by 4). Isn't it?

PB
Part Of The Furniture
Part Of The Furniture
Posts: 2478
Joined: Sun Jan 03, 2010 5:45 pm

Re: fastest way to copy array to wxBitmap

Post by PB » Fri May 22, 2020 8:23 am

The docs says (emphasis mine)
A DIB consists of two distinct parts: a BITMAPINFO structure describing the dimensions and colors of the bitmap, and an array of bytes defining the pixels of the bitmap. The bits in the array are packed together, but each scan line must be padded with zeros to end on a LONG data-type boundary.
As each pixel takes 3 bytes, this requirement may not be satisfied? Unlike with 16 or 32 bpp, with 24 bpp a byte may neet be added to each line? But I may be missing something...

But perhaps all your images have a number of pixels per line that it satisfies the condition (widthInPixels % 4 == 0)? If the images are coming e.g. from a camera that may be the cause, if the image size can be arbitrary then you have to deal with it.

dalmatian101
In need of some credit
In need of some credit
Posts: 7
Joined: Sun Sep 29, 2019 3:39 am

Re: fastest way to copy array to wxBitmap

Post by dalmatian101 » Fri May 22, 2020 8:48 am

... each scan line must be padded with zeros to end on a LONG data-type boundary.
I think if the length of the scan line is already DWORD-aligned, it is not necessary to be padded with zeros anymore.
If the width is 4000 pixel and each pixel takes 3 bytes, then each scan line has size of 12000 bytes which is already DWORD-aligned.
But perhaps all your images have a number of pixels per line that it satisfies the condition (widthInPixels % 4 == 0)? If the images are coming e.g. from a camera that may be the cause, if the image size can be arbitrary then you have to deal with it.
Yes, you are right. If the number of pixels per line does not satisfy the condition to be DWORD-aligned, I have to deal with the padding.

PB
Part Of The Furniture
Part Of The Furniture
Posts: 2478
Joined: Sun Jan 03, 2010 5:45 pm

Re: fastest way to copy array to wxBitmap

Post by PB » Fri May 22, 2020 9:02 am

If you have to deal with the padding, in such cases it may be easier and not any slower to just call the "portable" version instead of rearranging the data.

BTW, if you could tell the library providing the data to provide them as BGR instead of RGB, I would profile the whole "getbitmap + convertbitmap" block, so you can be sure the cost of converting the colors was not offset to the library providing the data.

And if you still need more speed; perhaps you could check if the library can provide the bitmap as bottom-to-top rows (at no additional cost), using a single memcpy (see the #else block in the ConvertRGBTowxBitmapMSW2()) gets the MSW-native conversion to about 40% time of the portable one.

User avatar
doublemax
Moderator
Moderator
Posts: 14992
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: fastest way to copy array to wxBitmap

Post by doublemax » Fri May 22, 2020 1:22 pm

I can't test the code myself at the moment, what happens if you move the creation of the target bitmap out of the inner loop? I would expect this takes a significant amount of time, too.
Use the source, Luke!

Post Reply