Monday 24 September 2012

Accessing Image Pixel Data in a C++/CX Windows Store App

As part of a Windows Store app I recently helped to develop, there was a requirement to perform imaging operations that manipulated the pixel values of the image. Rather than have an Image control display a BitmapImage, we displayed a WriteableBitmap instead, allowing us to manipulate an image at the pixel level. However, the task of doing this is not as straightforward as it first appeared. In particular, many of the posts concerning this on the forums were sub-optimal.

The pixel data in a WriteableBitmap is stored in the PixelBuffer property. However, this property is read-only. In managed languages, you can use the AsStream extension method to access the underlying buffer as a stream. However, to access the pixel data from a C++ app requires you to query the PixelBuffer for the IBufferByteAccess type, and then access its Buffer.

Originally we managed the lifetime of our COM variables manually, via calls to AddRef and Release. However, we then discovered the ComPtr class, which creates a smart pointer type that represents the interface specified by the template parameter. The advantage of using a ComPtr is that it automatically maintains a reference count for the underlying interface pointer and releases the interface when the reference count goes to zero, making calls to AddRef and Release redundant. The ComPtr class also provides the As method, which can be thought of as a shortcut for calling QueryInterface to retrieve a pointer to a supported interface on an object.

The free function that we developed to access pixel data is shown below. It retrieves the pixel data from the provided IBuffer object in the WriteableBitmap, and returns a pointer to it.

byte* GetPointerToPixelData(IBuffer^ buffer)
{
    // Cast to Object^, then to its underlying IInspectable interface.
    Object^ obj = buffer;
    ComPtr<IInspectable> insp(reinterpret_cast<IInspectable*>(obj));
 
    // Query the IBufferByteAccess interface.
    ComPtr<IBufferByteAccess> bufferByteAccess;
    ThrowIfFailed(insp.As(&bufferByteAccess));
 
    // Retrieve the buffer data.
    byte* pixels = nullptr;
    ThrowIfFailed(bufferByteAccess->Buffer(&pixels));
    return pixels;
}

The code converts an IBuffer object to its underlying COM interface, IBufferByteAccess. This is necessary because an IBuffer object only exposes two properties – Capacity and Length. However, an IBufferByteAccess is used to represent an IBuffer as an array of bytes, with the Buffer method returning the array of bytes.

The ThrowIfFailed function is used to convert a HRESULT failure code to a Windows Runtime exception.
inline void ThrowIfFailed(HRESULT hr)
{
    if (FAILED(hr))
    {
        throw Exception::CreateException(hr);
    }
}

To get the the pixel data from a WriteableBitmap you can invoke GetPointerToPixelData, and pass the PixelBuffer from the WriteableBitmap as a parameter (m_image is a WriteableBitmap).
byte *pPixels = GetPointerToPixelData(m_image->PixelBuffer);

In order to access and modify the pixel data you should iterate through it, performing your desired imaging operation. The code below iterates through the pixel data, changing every pixel value to 0 (black). Note that the pixel data is stored in BGRA format.
for (int y = 0; y < height; y++)
{
    for (int x = 0; x < width; x++)
    {
        pPixels[(x + y * width) * 4]     = 0; // B
        pPixels[(x + y * width) * 4 + 1] = 0; // G
        pPixels[(x + y * width) * 4 + 2] = 0; // R
        pPixels[(x + y * width) * 4 + 3] = 0; // A
    }
}

After the loops are executed, the modified pixel data will appear in the WriteableBitmap after it’s redrawn via a call to Invalidate. Alternatively, in some cases, you can use property change notification to redraw the WriteableBitmap.

Summary


The GetPointerToPixelData free function can be used to robustly and reliably get pixel data from a WriteableBitmap. The function uses the ComPtr class, which creates a smart pointer type that represents the interface specified by the template parameter. The advantage of using a ComPtr is that it automatically maintains a reference count for the underlying interface pointer and releases the interface when the reference count goes to zero.

No comments: