Introduction to WIC: How to use WIC to load an image, and draw it with GDI?
The Windows Imaging Component (WIC) is a flexible and robust API for working with images on Windows. The API is extensible. Third-party vendors can create new image codecs (readers and writers) to make new image formats available to all applications that use the WIC API. Here is a brief description from the MSDN:
The Windows Imaging Component (WIC) provides an extensible framework for working with images and image metadata. WIC makes it possible for independent software vendors (ISVs) and independent hardware vendors (IHVs) to develop their own image codecs and get the same platform support as standard image formats (for example, TIFF, JPEG, PNG, GIF, BMP, and HDPhoto). A single, consistent set of interfaces is used for all image processing, regardless of image format, so any application using the WIC gets automatic support for new image formats as soon as the codec is installed. The extensible metadata framework makes it possible for applications to read and write their own proprietary metadata directly to image files, so the metadata never gets lost or separated from the image.
The MSDN states the primary features of WIC as follows:
- Enables application developers to perform image processing operations on any image format through a single, consistent set of common interfaces, without requiring prior knowledge of specific image formats.
- Provides an extensible “plug and play” architecture for image codecs, pixel formats, and metadata, with automatic run-time discovery of new formats.
- Supports reading and writing of arbitrary metadata in image files, with the ability to preserve unrecognized metadata during editing.
- Preserves high bit depth image data, up to 32 bits per channel, throughout the image processing pipeline.
- Provides built-in support for most popular image formats, pixel formats, and metadata schemas.
WIC comes with the following standard built-in codecs:
- BMP (Windows Bitmap Format), BMP Specification v5.
- GIF (Graphics Interchange Format 89a), GIF Specification 89a/89m
- ICO (Icon Format)
- JPEG (Joint Photographic Experts Group), JFIF Specification 1.02
- PNG (Portable Network Graphics), PNG Specification 1.2
- TIFF (Tagged Image File Format), TIFF Specification 6.0
- Windows Media Photo, HD Photo Specification 1.0
All codecs can both load and save the specific image format, except the ICO codec which can only load icon files and not save them.
Reading all this, it looks like WIC is pretty powerful, and the best part is that it’s easy to use as I will demonstrate in this article. So, let’s get started with some code.
The first thing you need to do is include the following headers and link with Windowscodecs.lib:
#include <wincodec.h> #include <wincodecsdk.h>
WIC uses COM, however, this should not scare you away. The main thing to remember is to use a COM smart pointer which makes it much easier and prevents memory leaks. The COM smart pointer I’ll be using is CComPtr. This smart pointer is defined in atlbase.h.
To start working with WIC, you need an instance of the IWICImagingFactory. You only have to create one such instance and can use it for the entire lifetime of the application. Below is the header file of a class that wraps an IWICImagingFactory instance into a singleton class to give easy access to it from anywhere in your application.
#pragma once #include <atlbase.h> #include <wincodec.h> #include <memory> class CWICImagingFactory { public: inline static CWICImagingFactory& GetInstance() { if (nullptr == m_pInstance.get()) m_pInstance.reset(new CWICImagingFactory()); return *m_pInstance; } virtual IWICImagingFactory* GetFactory() const; protected: CComPtr<IWICImagingFactory> m_pWICImagingFactory; private: CWICImagingFactory(); // Private because singleton static std::shared_ptr<CWICImagingFactory> m_pInstance; };
This code is using the C++11 std::shared_ptr smart pointer. If your compiler does not yet support this smart pointer, you can replace it with any other smart pointer, for example boost::shared_ptr. Note that this singleton class is not threadsafe. The GetInstance() method contains a race-condition. So, if you need this singleton to be threadsafe, you’ll have to add some synchronization to the GetInstance() method.
The implementation file of the CWICImagingFactory is rather straightforward:
#include "stdafx.h" #include "WICImagingFactory.h" #include <assert.h> std::shared_ptr<CWICImagingFactory> CWICImagingFactory::m_pInstance; CWICImagingFactory::CWICImagingFactory() : m_pWICImagingFactory(nullptr) { HRESULT hr = CoCreateInstance(CLSID_WICImagingFactory, NULL, CLSCTX_INPROC_SERVER, IID_IWICImagingFactory, (LPVOID*)&m_pWICImagingFactory); assert(SUCCEEDED(hr)); } IWICImagingFactory* CWICImagingFactory::GetFactory() const { assert(m_pWICImagingFactory); return m_pWICImagingFactory; }
Now that we have access to an IWICImagingFactory instance, let’s start with writing the definition of a class called CNuonImg that uses WIC to load and render images.
#pragma once #include <atlbase.h> #include <wincodec.h> #include <wincodecsdk.h> class CNuonImg { public: CNuonImg(); virtual ~CNuonImg(); // Opens the nFrame-th frame of the given image file. // Throws HRESULT in case of failure. virtual void Open(const wchar_t* pszFile, UINT nFrame = 0); // Returns true if an image is loaded successfully, false otherwise virtual bool IsLoaded() const; // Renders the loaded image to the given device context hDC, // at position x,y and size cx, cy. // Throws HRESULT in case of failure. virtual void Render(HDC hDC, UINT x, UINT y, UINT cx, UINT cy); // Returns the width of the loaded image. virtual UINT GetWidth() const; // Returns the height of the loaded image. virtual UINT GetHeight() const; protected: virtual void Cleanup(); CComPtr<IWICBitmapDecoder> m_pDecoder; CComPtr<IWICBitmapFrameDecode> m_pFrame; CComPtr<IWICFormatConverter> m_pConvertedFrame; UINT m_nWidth; UINT m_nHeight; };
The constructor of this class initializes the data members, and initializes COM.
CNuonImg::CNuonImg() : m_pDecoder(nullptr) , m_pFrame(nullptr) , m_pConvertedFrame(nullptr) , m_nWidth(0) , m_nHeight(0) { // Initialize COM CoInitialize(nullptr); }
The destructor performs some cleanup and uninitializes COM.
CNuonImg::~CNuonImg() { Cleanup(); // Uninitialize COM CoUninitialize(); }
The Cleanup() method cleans up the previously loaded image and prepares the class to load another image.
void CNuonImg::Cleanup() { m_nWidth = m_nHeight = 0; if (m_pConvertedFrame) { m_pConvertedFrame.Release(); m_pConvertedFrame = nullptr; } if (m_pFrame) { m_pFrame.Release(); m_pFrame = nullptr; } if (m_pDecoder) { m_pDecoder.Release(); m_pDecoder = nullptr; } }
It’s not strictly required to call Cleanup() from the destructor. The class is using CComPtr to wrap all COM pointers, and the CComPtr class will automatically release the COM pointers when the destructor of the CComPtr instances is executed. However, the class needs a Cleanup() method anyway, to allow loading a second image with the same CNuonImg instance, so, we might as well call the Cleanup() method in the destructor also.
The remaining of the code will call a lot of methods on COM interfaces. It’s good coding practice to check the return value of each COM method call. To make this easier, the code uses the following little helper macro:
#define IfFailedThrowHR(expr) {HRESULT hr = (expr); if (FAILED(hr)) throw hr;}
This executes an expression expr, which returns an HRESULT. The HRESULT is checked and if it’s a failure, the HRESULT value is thrown as an exception. You should change this error handling mechanism to suit your requirements.
The Open() method is of course the most interesting method. It loads the given image. The parameter nFrame is the index of the frame that you want to load. Certain image formats, for example TIFF and GIF, support multiple frames inside a single image file. The first frame in the file has index 0.
void CNuonImg::Open(const wchar_t* pszFile, UINT nFrame/* = 0*/) { try { // Cleanup a previously loaded image Cleanup(); // Get the WIC factory from the singleton wrapper class IWICImagingFactory* pFactory = CWICImagingFactory::GetInstance().GetFactory(); assert(pFactory); if (!pFactory) throw WINCODEC_ERR_NOTINITIALIZED; // Create a decoder for the given image file IfFailedThrowHR(pFactory->CreateDecoderFromFilename( pszFile, NULL, GENERIC_READ, WICDecodeMetadataCacheOnDemand, &m_pDecoder)); // Validate the given frame index nFrame UINT nCount = 0; // Get the number of frames in this image if (SUCCEEDED(m_pDecoder->GetFrameCount(&nCount))) { if (nFrame >= nCount) nFrame = nCount - 1; // If the requested frame number is too big, default to the last frame } // Retrieve the requested frame of the image from the decoder IfFailedThrowHR(m_pDecoder->GetFrame(nFrame, &m_pFrame)); // Retrieve the image dimensions IfFailedThrowHR(m_pFrame->GetSize(&m_nWidth, &m_nHeight)); // Convert the format of the image frame to 32bppBGR IfFailedThrowHR(pFactory->CreateFormatConverter(&m_pConvertedFrame)); IfFailedThrowHR(m_pConvertedFrame->Initialize( m_pFrame, // Source frame to convert GUID_WICPixelFormat32bppBGR, // The desired pixel format WICBitmapDitherTypeNone, // The desired dither pattern NULL, // The desired palette 0.f, // The desired alpha threshold WICBitmapPaletteTypeCustom // Palette translation type )); } catch (...) { // Cleanup after something went wrong Cleanup(); // Rethrow the exception, so the client code can handle it throw; } }
The above code shows the basics of loading an image. To learn more about the different parameters to the different COM calls, consult the MSDN. Here are a couple of MSDN links to make it easy to find the right pages:
- IWICImagingFactory
- IWICImagingFactory::CreateDecoderFromFilename()
- IWICBitmapDecoder
- IWICBitmapDecoder::GetFrameCount()
- IWICBitmapDecoder::GetFrame()
- IWICBitmapFrameDecode
- IWICBitmapSource::GetSize()
- IWICImagingFactory::CreateFormatConverter()
- IWICFormatConverter
- IWICFormatConverter::Initialize()
The CNuonImg::IsLoaded(), CNuonImg::GetWidth(), and CNuonImg::GetHeigh() methods are simple one-liners.
bool CNuonImg::IsLoaded() const { return m_pConvertedFrame != nullptr; } UINT CNuonImg::GetWidth() const { return m_nWidth; } UINT CNuonImg::GetHeight() const { return m_nHeight; }
The CNuonImg::Render() method is responsible for rendering the image to the given HDC.
#define DIB_WIDTHBYTES(bits) ((((bits) + 31)>>5)<<2) void CNuonImg::Render(HDC hDC, UINT x, UINT y, UINT cx, UINT cy) { // Make sure an image has been loaded if (!IsLoaded()) throw WINCODEC_ERR_WRONGSTATE; // Get the WIC factory from the singleton wrapper class IWICImagingFactory* pFactory = CWICImagingFactory::GetInstance().GetFactory(); if (!pFactory) throw WINCODEC_ERR_NOTINITIALIZED; // Create a WIC image scaler to scale the image to the requested size CComPtr<IWICBitmapScaler> pScaler = nullptr; IfFailedThrowHR(pFactory->CreateBitmapScaler(&pScaler)); IfFailedThrowHR(pScaler->Initialize(m_pConvertedFrame, cx, cy, WICBitmapInterpolationModeFant)); // Render the image to a GDI device context HBITMAP hDIBBitmap = NULL; try { // Get a DC for the full screen HDC hdcScreen = GetDC(NULL); if (!hdcScreen) throw 1; BITMAPINFO bminfo; ZeroMemory(&bminfo, sizeof(bminfo)); bminfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); bminfo.bmiHeader.biWidth = cx; bminfo.bmiHeader.biHeight = -(LONG)cy; bminfo.bmiHeader.biPlanes = 1; bminfo.bmiHeader.biBitCount = 32; bminfo.bmiHeader.biCompression = BI_RGB; void* pvImageBits = nullptr; // Freed with DeleteObject(hDIBBitmap) hDIBBitmap = CreateDIBSection(hdcScreen, &bminfo, DIB_RGB_COLORS, &pvImageBits, NULL, 0); if (!hDIBBitmap) throw 2; ReleaseDC(NULL, hdcScreen); // Calculate the number of bytes in 1 scanline UINT nStride = DIB_WIDTHBYTES(cx * 32); // Calculate the total size of the image UINT nImage = nStride * cy; // Copy the pixels to the DIB section IfFailedThrowHR(pScaler->CopyPixels(nullptr, nStride, nImage, reinterpret_cast<BYTE*>(pvImageBits))); // Copy the bitmap to the target device context ::SetDIBitsToDevice(hDC, x, y, cx, cy, 0, 0, 0, cy, pvImageBits, &bminfo, DIB_RGB_COLORS); DeleteObject(hDIBBitmap); } catch (...) { if (hDIBBitmap) DeleteObject(hDIBBitmap); // Rethrow the exception, so the client code can handle it throw; } }
Here are some links to the MSDN documentation to learn more about the different parameters to the functions used in the above piece of code.
- IWICBitmapScaler
- IWICImagingFactory::CreateBitmapScaler()
- IWICBitmapScaler::Initialize()
- CreateDIBSection()
- IWICBitmapSource::CopyPixels()
- SetDIBitsToDevice()
Now you can start using the class to load and render images. The basic flow for rendering an image to a device context (hDC) at position (0,0) with given width and height is as follows:
try { CNuonImg img; wchar_t* filename = L"C:\\Users\\Public\\Pictures\\Sample Pictures\\Tulips.jpg"; img.Open(filename); img.Render(hDC, 0, 0, width, height); } catch (HRESULT hr) { // Handle error }
Click here to download a small MFC demo application that uses the above class to load an image.
That’s it for this first introduction to WIC. If you have suggestions for topics for future posts about WIC, let me know in the comments below.
John Schroedl said,
Wrote on October 18, 2011 @ 7:34 pm
This is some nice work. Thanks for posting it I have two questions:
1. I don’t see any reason to use shared_ptr over unique_ptr in the singleton. Am I missing something?
2. Whats a Nuon?
Marc Gregoire said,
Wrote on October 18, 2011 @ 8:52 pm
Hi John,
1. You are right, you could use std::unique_ptr instead of shared_ptr. I’m just used to use shared_ptr most of the time.
2. Nuon -> if you look at my domain name, which is NuonSoft, I guess the name CNuonImg becomes clear 🙂
Tony Teveris said,
Wrote on February 25, 2014 @ 5:59 pm
Can anyone point me in the direction of handling transparent images only using WIC and GDI?
Thanks
T
Marc – thanks for the sample and explanation
Marc Gregoire said,
Wrote on February 25, 2014 @ 7:34 pm
In CNuonImg::Open, specify GUID_WICPixelFormat32bppBGRA instead of GUID_WICPixelFormat32bppBGR.
And modify CNuonImg::Render() to use ::AlphaBlend().
See my new blog post.
ULTRON said,
Wrote on March 2, 2020 @ 4:37 am
That is good! But how to use WIC to load a GIF, and draw it with GDI?
Marc Gregoire said,
Wrote on March 3, 2020 @ 7:32 pm
GIF support animations, so you’ll have to draw multiple frames.
See here for an example: https://docs.microsoft.com/en-us/windows/win32/wic/-wic-sample-animated-gif
Daniel Barr said,
Wrote on August 17, 2020 @ 8:26 pm
There is a way to find out if there is alpha/transparency information in the image in case someone needs to. This information could be used to determine whether to use StretchBlt() or AlphaBlend() depending on the alpha information. AlphaBlend() can’t mirror images so you have to use the WIC scaler (or some other method) for mirroring when you use AlphaBlend(). StretchBlt() will mirror images but doesn’t use the alpha channel. I have not tested whether StretchBlt() is faster than using WIC scaling but I’m guessing it is.
Here is the code:
// Determine whether there is alpha data or not
GUID containerFormat;
IfFailedThrowHR(m_pDecoder->GetContainerFormat( &containerFormat ));
if (!IsEqualGUID( containerFormat, GUID_ContainerFormatGif ))
{
// Not a GIF image
// check frame decoder pixel format for alpha channel
WICPixelFormatGUID pixelFormat;
IfFailedThrowHR(m_pFrame->GetPixelFormat( &pixelFormat ));
m_bIsAlpha = ((GUID_WICPixelFormat32bppBGRA == pixelFormat)
|| (GUID_WICPixelFormat32bppPBGRA == pixelFormat)
|| (GUID_WICPixelFormat64bppRGBA == pixelFormat)
|| (GUID_WICPixelFormat64bppPRGBA == pixelFormat)
|| (GUID_WICPixelFormat64bppRGBAFixedPoint == pixelFormat)
|| (GUID_WICPixelFormat40bppCMYKAlpha == pixelFormat)
|| (GUID_WICPixelFormat80bppCMYKAlpha == pixelFormat)
|| (GUID_WICPixelFormat128bppRGBAFloat == pixelFormat)
|| (GUID_WICPixelFormat128bppPRGBAFloat == pixelFormat))
? True : False;
}
else
{
// This is a GIF image
// check image metadata for transparency
HRESULT hr;
m_bIsAlpha = False; // assume no transparency
// Create a MetadataQueryReader from the frame decoder
IWICMetadataQueryReader *pMetadataQueryReader = nullptr;
hr = m_pFrame->GetMetadataQueryReader( &pMetadataQueryReader );
if (SUCCEEDED(hr))
{
// check the TransparencyFlag property
PROPVARIANT propValue;
PropVariantInit( &propValue );
hr = pMetadataQueryReader->GetMetadataByName(
L”/grctlext/TransparencyFlag”,
&propValue );
if (SUCCEEDED(hr))
{
if ((propValue.vt == VT_BOOL) && propValue.boolVal)
{
m_bIsAlpha = True;
}
}
pMetadataQueryReader->Release();
}
}