Pawel
Pawel

Reputation: 1386

How to properly Release BitmapBuffer from SoftwareBitmap (UWP)?

I struggle to properly release the BitmapBuffer locked with SoftwareBitmap.LockBuffer() method in C++/CX code on UWP.

The base code looks as below (it's a OpenCV bridge sample from Microsoft available here.

bool OpenCVHelper::GetPointerToPixelData(SoftwareBitmap^ bitmap, unsigned char** pPixelData, unsigned int* capacity)
{
   BitmapBuffer^ bmpBuffer = bitmap->LockBuffer(BitmapBufferAccessMode::ReadWrite);
   IMemoryBufferReference^ reference = bmpBuffer->CreateReference();

   ComPtr<IMemoryBufferByteAccess> pBufferByteAccess;
   if ((reinterpret_cast<IInspectable*>(reference)->QueryInterface(IID_PPV_ARGS(&pBufferByteAccess))) != S_OK)
   {
    return false;
   }

   if (pBufferByteAccess->GetBuffer(pPixelData, capacity) != S_OK)
   {
    return false;
   }
   return true;
}

This buffer (pPixelData) is then used to initialize cv:Mat object (shallow copy). And is never released.

Consecutive call to LockBuffer() on the same SoftwareBitmap object raises exception:

Platform::AccessDeniedException ^ at memory location 0x00000002CEEFDCC0. HRESULT:0x80070005 Access is denied. WinRT information: Bitmap shared lock taken

How to properly release this buffer? Especially in C++/CX?

I tried to keep the reference to release it when no more needed. In C++/CX Dispose() or Close() methods are inaccessible and compiler advises to call destructor instead:

BitmapBuffer^ bmpBuffer = nullptr;
// ... get the buffer, use it
//((IDisposable^)bmpBuffer)->Dispose();
bmpBuffer->~BitmapBuffer();

But it doesn't work (does nothing). Destructor is being invoked but another call to LockBuffer() raises the same error as before.

Upvotes: 5

Views: 1056

Answers (2)

escape-llc
escape-llc

Reputation: 1331

I have used the above GetPointerToPixelData (and the TryConvert) successfully in a Windows Store app (12 months now), so maybe in the years since OP, the framework has improved, or maybe I'm using it differently.

However, this code always "bothered" me, so here is my journey putting the world to rights....

If we were using these directly in managed C#, all the examples clearly show using on both the BitmapBuffer and the IMemoryBufferReference so calling the IDispose ASAP seems to me to be obligatory. The documentation even admonishes you to not hold onto these very long!

Now that I understand how these APIs work better, IMHO the UWP sample code works "by accident" (it's a sample after all, so no judgment), so I finally decided to re-implement so everything is scoped properly (with RAII) and uses a closure for the "action", so now we also clean up if an Exception occurs in the handler.

This version also accounts for the Stride which is important when you are dealing with GREY images, which my app handles (got tired of calling cvtColor on everything...)

Disclaimer: I know enough C++/CX to be dangerous; this can probably be improved by someone who knows the "rules" better.

Edited: modified stride handling to use the ctor overload. Switched to void. Switched to const closure parameter. Added error control.

void OpenCVHelper::UseBitmap(SoftwareBitmap^ sb, BitmapBufferAccessMode bbam, std::function<void(const Mat&)> handler) {
    // invokes IDispose (via delete) at end-of-scope
    disposer<BitmapBuffer^> bb(sb->LockBuffer(bbam));
    IMemoryBufferReference^ imbr = bb->CreateReference();
    disposer<IMemoryBufferReference^> xx(imbr);
    ComPtr<IMemoryBufferByteAccess> pmbba;
    bool error = true;
    if ((reinterpret_cast<IInspectable*>(imbr)->QueryInterface(IID_PPV_ARGS(&pmbba))) == S_OK) {
        unsigned char* pPixels = nullptr;
        unsigned int capacity = 0;
        if (pmbba->GetBuffer(&pPixels, &capacity) == S_OK) {
            size_t stride = Mat::AUTO_STEP;
            int width = sb->PixelWidth;
            int height = sb->PixelHeight;
            if (sb->BitmapPixelFormat == BitmapPixelFormat::Gray8 && bb->GetPlaneCount() > 0) {
                BitmapPlaneDescription bpd = bb->GetPlaneDescription(0);
                stride = (size_t)bpd.Stride;
                width = bpd.Width;
                height = bpd.Height;
            }
            int type = TypeFor(sb->BitmapPixelFormat);
            Mat mat(height, width, type, (void*)pPixels, stride);
            try {
                handler(mat);
                error = false;
            }
            catch(cv::Exception cex) {
                throw ref new Platform::FailureException(std_to_managed((std::string)cex.msg));
            }
        }
    }
    if(error) throw ref new Platform::FailureException("Failed to attach the SoftwareBitmap");
}

The TypeFor write your own or hard-code BGRA8. If you only care about BGRA8 the Stride stuff is unnecessary; BGRA is 4-byte aligned. Also note we need to use Platform exceptions back to managed-land.

I should have been able to use std::unique_ptr to get it to invoke the operator delete for me, but it doesn't work with ref classes.

I also had trouble with getting the finally helper to work with ref classes (ref classes are taint) so built an abbreviated version to handle the lack of try/finally in C++ and went the RAII route (as required). Here is disposer:

template <class F>
class disposer {
public:
    explicit disposer(F inst) noexcept :f_(inst) {};
    ~disposer() noexcept {
        delete f_;
    }
    F operator->() const { return f_; }
private:
    F f_;
};

Setting a breakpoint verifies that delete is called with correct order and timing.

For extra credit, here is a use case; most "operations" require 2 bitmaps, one for input and one for output, so here is a helper function that takes a closure that receives both Mat& instances:

void OpenCVHelper::Use2Bitmaps(Windows::Graphics::Imaging::SoftwareBitmap^ sb1,
    Windows::Graphics::Imaging::BitmapBufferAccessMode bbam1,
    Windows::Graphics::Imaging::SoftwareBitmap^ sb2,
    Windows::Graphics::Imaging::BitmapBufferAccessMode bbam2,
    std::function<void(const cv::Mat&, const cv::Mat&)> handler) {
    OpenCVHelper::UseBitmap(sb1, bbam1, [&](const Mat& mat1) {
        OpenCVHelper::UseBitmap(sb2, bbam2, [&handler, &mat1](const Mat& mat2) {
            handler(mat1, mat2);
        });
    });
}

And finally, here is an actual use case calling CV APIs:

void OpenCVHelper::Linear(
    Windows::Graphics::Imaging::SoftwareBitmap^ input,
    double alpha, double beta,
    Windows::Graphics::Imaging::SoftwareBitmap^ output) {
    OpenCVHelper::Use2Bitmaps(input, BitmapBufferAccessMode::Read, output, BitmapBufferAccessMode::ReadWrite, [&](const Mat& src, const Mat& dst) {
        src.convertTo(dst, -1, alpha, beta);
    });
}

Upvotes: 0

Sunteen Wu
Sunteen Wu

Reputation: 10627

How to properly Release BitmapBuffer from SoftwareBitmap (UWP)?

After BitmapBuffer and IMemoryBufferReference completing their work, these objects could be closed by invoking delete expression. More details please check Destructors. For example:

SoftwareBitmap^ bitmap = ref new SoftwareBitmap(
    BitmapPixelFormat::Bgra8,
    100,
    200,
    BitmapAlphaMode::Premultiplied);
BitmapBuffer^ bmpBuffer = bitmap->LockBuffer(BitmapBufferAccessMode::ReadWrite); 
IMemoryBufferReference^ reference = bmpBuffer->CreateReference();
delete reference;
delete bmpBuffer; 
BitmapBuffer^ bmpBuffer2 = bitmap->LockBuffer(BitmapBufferAccessMode::ReadWrite);

As above code snippet showed, after delete the BitmapBuffer object, you could lock the buffer again successfully.

Upvotes: 0

Related Questions