Building Thumbnail Providers

Windows Vista makes greater use of file-specific thumbnail images than earlier versions of Microsoft Windows. Windows Vista uses them in all views, in dialogs, and for any file type that provides them. Thumbnail display has also changed. Now, a continuous spectrum of user-selectable sizes is available rather than the discrete sizes such as Icons and Thumbnails provided in Windows XP.

The IThumbnailProvider interface has been introduced to make providing a thumbnail easier and more straightforward than in the past, when IExtractImage or IExtractImage2 would have been used instead. Note, however, that existing code that uses IExtractImage or IExtractImage2 is still valid under Windows Vista.

The ThumbnailHandler Sample

The ThumbnailHandler sample dissected in this section is included in the Windows Software Development Kit (SDK). Its default install location is C:\Program Files\Microsoft SDKs\Windows\v6.0\Samples\WinUI\Shell\AppShellIntegration\ThumbnailProvider. However, the bulk of the code is included here as well.

The ThumbnailHandler sample demonstrates the implementation of a thumbnail handler for a new file type registered with a .recipe extension. The sample illustrates the use of the different thumbnail provider APIs to register thumbnail extraction Component Object Model (COM) servers for custom file types. This topic walks you through the sample code, highlighting coding choices and guidelines.

IThumbnailProvider must always be implemented in concert with one of these interfaces:

There are cases where initialization with streams is not possible. In scenarios where your thumbnail provider does not implement IInitializeWithStream, it must opt out of running in the isolated process where the system indexer places it by default when there is a change to the stream. To opt out of the process isolation feature, set the following registry value.

HKEY_CLASSES_ROOT

CLSID

  • {66742402-F9B9-11D1-A202-0000F81FEDEE}

If you implement IInitializeWithStream and do a stream-based initialization, your provider is more secure and reliable. Typically, disabling process isolation is only intended for legacy handlers; avoid disabling this feature for any new code. IInitializeWithStream should be the first choice whenever possible.

Because the image file in the sample is not embedded in the .recipe file and so is not a part of its file stream, IInitializeWithItem is used. The implementation of the IInitializeWithItem::Initialize method simply passes its parameters to private class variables.

IThumbnailProvider has only one method—GetThumbnail—that is called with the largest desired size of the image, in pixels. Although the parameter is called cx, this is used as the maximum size of both the x and y dimensions. If the retrieved thumbnail is not square, then the longer axis is limited by cx and the aspect ratio of the original image respected.

On exit, GetThumbnail provides a handle to the retrieved image. It also provides a value that indicates the color format of the image and whether it has valid alpha information.

The GetThumbnail implementation in the sample begins with a call to the private _GetImageName method.

HRESULT CRecipeThumbHandler::GetThumbnail(UINT cx, HBITMAP *phbmp, WTS_ALPHATYPE *pdwAlpha)
{
    HRESULT hr = E_FAIL;

    _bstr_t bstrImageName;
    hr = _GetImageName(cx, &bstrImageName);

The .recipe file type is simply an XML file registered as a unique file extension. It includes an element called Picture that provides the relative path and file name of the image to use as the thumbnail for this particular .recipe file. The Picture element consists of the Source attribute that specifies the relative path of the image file, and an optional Size attribute.

Size has two values, Small and Large. This allows you to provide multiple Picture nodes with separate images. The image retrieved then depends on the maximum size value (cx) provided in the call to GetThumbnail. Because Windows never sizes the image any larger than its maximum size, different images can be provided for different resolutions. However, for simplicity, the sample omits the Size attribute and provides only one image for all situations.

The _GetImageName method, whose implementation is shown here, uses XML Document Object Model (DOM) APIs to retrieve the Picture node. From that node it extracts the image path and name (here "images\steaks.jpg") from the Source attribute.

HRESULT CRecipeThumbHandler::_GetImageName(UINT cx, _bstr_t *pbstrOut)
{
    HRESULT hr = E_FAIL;

    try
    {
        // Get the image name by parsing the XML in the .recipe file.
        IStreamPtr spStream;
        hr = _spsi->BindToHandler(NULL, BHID_SFObject, IID_PPV_ARGS(&spStream));
        if (SUCCEEDED(hr))
        {
            MSXML2::IXMLDOMDocumentPtr spDomDoc;
            hr = spDomDoc.CreateInstance(__uuidof(MSXML2::DOMDocument60));
            if (SUCCEEDED(hr))
            {
                hr = E_FAIL;

                if (VARIANT_TRUE == spDomDoc->load(static_cast<IUnknown *>(spStream)))
                {
                    MSXML2::IXMLDOMNodePtr spNodePicture;

                    if (cx <= 48)
                    {
                        spNodePicture = spDomDoc->selectSingleNode(c_szSmallXPath);
                    }
                    else
                    {
                        spNodePicture = spDomDoc->selectSingleNode(c_szLargeXPath);
                    }

                    if (!spNodePicture)
                    {
                        spNodePicture = spDomDoc->selectSingleNode(c_szNoSizeXPath);
                    }

                    if (spNodePicture)
                    {
                        MSXML2::IXMLDOMElementPtr spElement;
                        if (SUCCEEDED(spNodePicture->QueryInterface(&spElement)))
                        {
                            _bstr_t bstrImageName = spElement->getAttribute(L"Source").bstrVal;

                            if (SysStringLen(bstrImageName) > 0)
                            {
                                *pbstrOut = bstrImageName;
                                hr = S_OK;
                            }
                        }
                    }
                }
            }
        }
    }
    catch (_com_error &e)
    {
        hr = e.Error();
    }

    return hr;
}

GetThumbnail then passes the retrieved string to _GetImageStream.

HRESULT CRecipeThumbHandler::GetThumbnail(UINT cx, HBITMAP *phbmp, WTS_ALPHATYPE *pdwAlpha)
{
    HRESULT hr = E_FAIL;

    _bstr_t bstrImageName;
    hr = _GetImageName(cx, &bstrImageName);

    if (SUCCEEDED(hr))
    {
        IStreamPtr spStream;
        hr = _GetImageStream(bstrImageName, &spStream);

The _GetImageStream method, whose implementation is shown here, converts the path to a relative pointer to an item identifier list (PIDL) through IShellFolder::ParseDisplayName. It then binds the image file named by the PIDL to a stream.

HRESULT CRecipeThumbHandler::_GetImageStream(const _bstr_t &bstrImageName, IStream **ppStream)
{
    IShellItemPtr spsiParent;
    HRESULT hr = _spsi->GetParent(&spsiParent);
    
    if (SUCCEEDED(hr))
    {
        IShellFolderPtr spsf;
        hr = spsiParent->BindToHandler(NULL, BHID_SFObject, IID_PPV_ARGS(&spsf));

        if (SUCCEEDED(hr))
        {
            PIDLIST_RELATIVE pidlChild;
            ULONG cchEaten;
            hr = spsf->ParseDisplayName(NULL, NULL, bstrImageName, 
                                        &cchEaten, &pidlChild, NULL);

            if (SUCCEEDED(hr))
            {
                hr = spsf->BindToObject(pidlChild, NULL, IID_PPV_ARGS(ppStream));
                CoTaskMemFree(pidlChild);
            }
        } 
    }
    
    return hr;
}

GetThumbnail then uses Windows GDI+ APIs to extract a bitmap from the stream and get a handle to that bitmap. The alpha information is set, GDI+ is properly exited, and the method terminates successfully.

HRESULT CRecipeThumbHandler::GetThumbnail(UINT cx, HBITMAP *phbmp, WTS_ALPHATYPE *pdwAlpha)
{
    HRESULT hr = E_FAIL;

    _bstr_t bstrImageName;
    hr = _GetImageName(cx, &bstrImageName);

    if (SUCCEEDED(hr))
    {
        IStreamPtr spStream;
        hr = _GetImageStream(bstrImageName, &spStream);

        if (SUCCEEDED(hr))
        {
            hr = E_FAIL;
            using namespace Gdiplus;
            ULONG_PTR token;
            GdiplusStartupInput input;
            if (Ok == GdiplusStartup(&token, &input, NULL))
            {
                // Create a bitmap object for the image file to be shown 
                // as the thumbnail.
                Bitmap *pbitMap = Bitmap::FromStream(spStream, false);

                if (pbitMap)
                {
                    Color color(32, 32, 32);
                    // Returns an HBITMAP object of the image file.
                    if (Ok == pbitMap->GetHBITMAP(color, phbmp))
                    {
                        *pdwAlpha = WTSAT_RGB;
                        hr = S_OK;
                    }

                    delete pbitMap;
                }

                GdiplusShutdown(token);
            }
        }
    }

    return hr;
}