Question

I'm using the Windows Imaging Component library to encode a large number of images. Ideally I'd like to set up the encoder once with certain properties, and then re-use that encoder for all my images. However in all the examples I have seen, it seems an encoder is created for a single image.

I'm reading and writing from/to byte streams, not files and there could be multiple threads running simultaneously.

Here is a snippet of code:

CComPtr<IWICBitmapEncoder> pEncoder;
CComPtr<IWICBitmapFrameEncode> pBitmapFrame;
CComPtr<IPropertyBag2> pPropertyBag;
CComPtr<IWICStream> pStream;
CComPtr<IStream> pOutputStream;

HRESULT hr;
//  Setup memory stream, which is needed to stage raw image bits
if (CreateStreamOnHGlobal(NULL, TRUE, &pOutputStream) != S_OK)
{
    LogAssert(false, "Could not create pOutputStream. Err (%d)", GetLastError());
}
//Setup WIC stream which encapsulates the output stream
hr = m_pFactory->CreateStream(&pStream);

hr = pStream->InitializeFromIStream(pOutputStream);
hr = m_pFactory->CreateEncoder(GUID_ContainerFormatWmp, NULL, &pEncoder);
hr = pEncoder->Initialize(pStream, WICBitmapEncoderNoCache);
hr = pEncoder->CreateNewFrame(&pBitmapFrame, &pPropertyBag);
SetEncodingProperties(pPropertyBag);
hr = pBitmapFrame->Initialize(pPropertyBag);

Issue 1: I'm writing out to an IStream which is created using CreateStreamOnHGlobal. Can I re-use pStream and pOutputStream for multiple images? Any thread-safety issues?

Issue 2: What part of this snippet can be done once, and what part needs to repeated for different images? All the initializations seem to be tied in to each other.

Was it helpful?

Solution

All the builtin WIC objects were updated in Windows 7 to be thread-safe, as explained here: http://msdn.microsoft.com/en-us/library/ee720061%28v=vs.85%29.aspx#_multi_threaded_apartment_support

In previous Windows versions, the objects are not thread-safe, and Windows will automatically marshal calls into another thread so that it can be accessed from multiple threads. This will only work if you initialized COM properly - if you're going to access objects from multiple threads, you need to call CoInitializeEx with the multithreaded option from all threads that could access your objects, and you need to make sure CoInitializeEx returns success (S_OK or S_FALSE).

It's not safe to have two different encoders for a single IStream object at a time. Once you've called Commit on the encoder object, it's safe to use the stream elsewhere, but it doesn't really make sense to use it with another encoder. You can't have multiple images in a single file (unless the image format supports multiple frames, but then you only want a single encoder object). I suppose you could set the stream size to 0 before using it with another encoder, but allocating an HGLOBAL stream probably isn't any faster than doing that.

It doesn't make sense to create an IWICStream in your case because you already have an IStream, and this is all an encoder needs. IWICStream exists mostly as a convenience function to create a stream from a file, fixed-size memory buffer, or section of an existing stream. The reason the InitializeFromIStream method exists is that IWICStream does not support the Clone method. InitializeFromIStream is a work-around that makes it possible to get a new IStream object with an independent cursor when Clone isn't supported, but the way it does this is not (and cannot be) thread-safe. (For it to work, the underlying stream must only be accessed by one thread at a time. Normally, the encoder or decoder object will make sure of this, as long as the stream is only assigned to one encoder/decoder at a time.)

Since you're concerned about performance, you should know that writing to HGLOBAL streams in pieces, as WIC encoders are likely to do, is O(n**2), because enlarging the HGLOBAL involves copying all the existing data to a new location.

If you're going to access WIC from multiple threads before Windows 7, I would suggest using single-threaded apartments and making sure that objects you initialize in one thread are only accessed from that thread. This will save you the costs of marshaling the calls to another thread.

It should be possible to save the IPropertyBag2 object with your settings and use it to initialize all bitmap frames, as long as the encoder class is the same and you're not trying to use the same one from multiple single-threaded apartments (or a single-threaded and multi-threaded apartment).

However, I think you're far too concerned with things that will have a negligible impact on performance, when you should be more concerned with the process of writing your image data. Using a stream type that doesn't have to copy memory all the time (perhaps something that uses a fixed-size memory buffer behind the scenes but can report a smaller size when appropriate), may help if your image files will be large (though, if you're working with really large images, you should probably consider using imagemagick instead). Giving each thread entirely independent objects (as a single object will most likely only be able to do work in a single thread at a time anyway) may also help.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top