2

I'm trying to use WIC to load an image into an in-memory buffer for further processing then write it back to a file when done. Specifically:

  1. Load the image into an IWICBitmapFrameDecode.
  2. The loaded IWICBitmapFrameDecode reports that its pixel format is GUID_WICPixelFormat24bppBGR. I want to work in 32bpp RGBA, so I call WICConvertBitmapSource.
  3. Call CopyPixels on the converted frame to get a memory buffer.
  4. Write the memory buffer back into an IWICBitmapFrameEncode using WritePixels.

This results in a recognizable image, but the resulting image is mostly blueish, as if the red channel is being interpreted as blue.

If I call WriteSource to write the converted frame directly, instead of writing the memory buffer, it works. If I call CopyPixels from the original unconverted frame (and update my stride and pixel formats accordingly), it works. It's only the combination of WICConvertBitmapSource plus the use of a memory buffer (CopyPixels + WritePixels) that causes the problem, but I can't figure out what I'm doing wrong.

Here's my code.

int main() {

IWICImagingFactory *pFactory;
IWICBitmapDecoder *pDecoder = NULL;

CoInitializeEx(NULL, COINIT_MULTITHREADED);

CoCreateInstance(
    CLSID_WICImagingFactory,
    NULL,
    CLSCTX_INPROC_SERVER,
    IID_IWICImagingFactory,
    (LPVOID*)&pFactory
);

// Load the image.
pFactory->CreateDecoderFromFilename(L"input.png", NULL, GENERIC_READ, WICDecodeMetadataCacheOnDemand, &pDecoder);

IWICBitmapFrameDecode *pFrame = NULL;
pDecoder->GetFrame(0, &pFrame);

// pFrame->GetPixelFormat shows that the image is 24bpp BGR.
// Convert to 32bpp RGBA for easier processing.
IWICBitmapSource *pConvertedFrame = NULL;
WICConvertBitmapSource(GUID_WICPixelFormat32bppRGBA, pFrame, &pConvertedFrame);

// Copy the 32bpp RGBA image to a buffer for further processing.
UINT width, height;
pConvertedFrame->GetSize(&width, &height);

const unsigned bytesPerPixel = 4;
const unsigned stride = width * bytesPerPixel;
const unsigned bitmapSize = width * height * bytesPerPixel;
BYTE *buffer = new BYTE[bitmapSize];
pConvertedFrame->CopyPixels(nullptr, stride, bitmapSize, buffer);

// Insert image buffer processing here.  (Not currently implemented.)

// Create an encoder to turn the buffer back into an image file.
IWICBitmapEncoder *pEncoder = NULL;
pFactory->CreateEncoder(GUID_ContainerFormatPng, nullptr, &pEncoder);

IStream *pStream = NULL;
SHCreateStreamOnFileEx(L"output.png", STGM_WRITE | STGM_CREATE, FILE_ATTRIBUTE_NORMAL, true, NULL, &pStream);

pEncoder->Initialize(pStream, WICBitmapEncoderNoCache);

IWICBitmapFrameEncode *pFrameEncode = NULL;
pEncoder->CreateNewFrame(&pFrameEncode, NULL);

pFrameEncode->Initialize(NULL);
WICPixelFormatGUID pixelFormat = GUID_WICPixelFormat32bppRGBA;
pFrameEncode->SetPixelFormat(&pixelFormat);
pFrameEncode->SetSize(width, height);
pFrameEncode->WritePixels(height, stride, bitmapSize, buffer);

pFrameEncode->Commit();
pEncoder->Commit();
pStream->Commit(STGC_DEFAULT);

return 0;
}
Josh Kelley
  • 56,064
  • 19
  • 146
  • 246
  • First of all, make sure you are checking the ``HRESULT`` of every function that returns it. You can do that with the ``SUCCEEED`` or ``FAILED`` macros, or you can use something like [ThrowIfFailed](https://github.com/Microsoft/DirectXTK/wiki/ThrowIfFailed). – Chuck Walbourn Jul 04 '17 at 04:30
  • You are making a lot of assumptions about the runtime pixel formats here that you aren't checking. For a good example would be to look at [WICTextureLoader](https://github.com/Microsoft/DirectXTex/blob/master/WICTextureLoader/WICTextureLoader.cpp) and [ScreenGrab](https://github.com/Microsoft/DirectXTex/blob/master/ScreenGrab/ScreenGrab.cpp). For additional extensive example code for working with WIC, take a look at [DirectXTex](https://github.com/Microsoft/DirectXTex). – Chuck Walbourn Jul 04 '17 at 04:34

1 Answers1

3

The PNG encoder only supports GUID_WICPixelFormat32bppBGRA (BGR) for 32bpp as specified in PNG Native Codec official documentation. When you call it with GUID_WICPixelFormat32bppRGBA, it will not do channel switching. The pervert will just use your pixels as they were BGR, not RGB, and will not tell you there's a problem.

I don't know what you're trying to do, but in your example, you could just replace GUID_WICPixelFormat32bppRGBA by GUID_WICPixelFormat32bppBGRA in the call to WICConvertBitmapSource (and also replace the definition of the last pixelFormat variable to make sure your source code is correct, but it doesn't change anything).

PS: you can use Wic to save files, not need to create stream using another API, see my answer here: Capture screen using DirectX

Simon Mourier
  • 132,049
  • 21
  • 248
  • 298
  • To be explicit here, you must check the value of ``pixelFormat`` *after* you call ``pFrameEncode->SetPixelFormat(&pixelFormat);``. It's not necessarily the same format you gave it on input (which is why it takes a pointer to a GUID instead of just a GUID). It won't necessarily be ``GUID_WICPixelFormat32bppRGBA`` for ``PNG`` as this answer indicates. It can be something else that the codec supports. – Chuck Walbourn Jul 04 '17 at 04:35