Converting between IPictureDisp and System.Drawing.Image

Converting between IPictureDisp and System.Drawing.Image

This is an interesting manifestation of the gap between COM-based native servers, such as the Office client apps, and managed code. There are a number of scenarios where you need to convert image formats between the native COM-based IPictureDisp and the managed System.Drawing.Image class and its derivatives such as Bitmap. One such scenario is in providing images for custom ribbons, including ribbon images used for Outlook custom form regions.

You have several choices for performing the conversion, depending on the direction of the conversion.

The "traditional" approach is to use the GetIPictureDispFromPicture and GetPictureFromIPicture methods in the System.Windows.Forms.AxHost class. These are both protected members of the class, so you can't use them externally. For this reason, it is common to subclass the AxHost class and expose public methods that internally call the base class protected methods. This approach allows you to convert in both directions:

internalclassAxHostConverter : AxHost

{

private AxHostConverter() : base("") { }

staticpublic stdole.IPictureDisp ImageToPictureDisp(Image image)

{

return (stdole.IPictureDisp)GetIPictureDispFromPicture(image);

}

staticpublicImage PictureDispToImage(stdole.IPictureDisp pictureDisp)

{

return GetPictureFromIPicture(pictureDisp);

}

}

Note that this is a peripheral capability of the AxHost class – its main purpose is to act as a base class for derived classes that host native ActiveX controls in managed Windows.Forms controls. When you add an ActiveX control to a managed project, Visual Studio uses the aximp.exe tool to generate and compile a class derived from AxHost. For documentation, see here.

Your second option is to use OleLoadPicture or OleCreatePictureIndirect. Tom Quinn mentions this in his blog post here, and there's an old support article on the topic here. OleLoadPicture creates a new picture object and initializes it from the contents of a stream. This is equivalent to calling OleCreatePictureIndirect(NULL, ...) followed by IPersistStream::Load. You'll need to p/invoke to these native methods. As you can see from the managed declaration of OleCreatePictureIndirect below, it takes a particular struct type as its first parameter – so, you need to declare this struct type also. This method (and the real definition of the native struct) can accommodate multiple image formats, including bitmap, icon and metafile. The simplified example listed here only supports bitmap. The struct is only used when converting from an Image to an IPictureDisp – the reverse operation is simpler, being basically just a call to Image.FromHbitmap. For documentation, see here.

Your third option is to use the VB6 compatibility library, documented here. To use this, you'll need to add a reference to Microsoft.VisualBasic.Compatibility.dll, which is listed on the .NET tab of the Add References dialog (it resides in the GAC). Then, you can use the ImageToIPictureDisp and IPictureDispToImage methods in the Support class. This is obviously by far the simplest approach, although it does pull in the VB6 compatibility DLL. Internally, the VB6 compatibility code looks a lot like the second option above – using OleCreatePictureIndirect.

using Microsoft.VisualBasic.Compatibility.VB6;

internalclassVB6CompatibilityConverter

{

publicstatic stdole.IPictureDisp ImageToPictureDisp(Image image)

{

return (stdole.IPictureDisp)Support.ImageToIPictureDisp(image);

}

publicstaticImage PictureDispToImage(stdole.IPictureDisp pictureDisp)

{

returnSupport.IPictureDispToImage(pictureDisp);

}

}

Finally, you can implement IPictureDisp and IPicture yourself, documented here and here. This is fine if you just want to convert from an Image to an IPictureDisp, but doesn't help you converting in the other direction. The implementation below relies on the Image actually being a derived Bitmap type, because we call Bitmap.GetHbitmap internally. If you want to keep the support to the generic Image type, you'll have to do a lot more work to p/invoke to a bunch of undocumented GDI methods instead.

All of the methods and types described above are documented, so all of these approaches are valid. Obviously, you'll have to do some work to write the helper classes along the lines indicted above, and some require more work (and maintenance) than others, of course, but you have options to choose from.

The one thing that is missing from all the IPictureDisp-to-Image conversions is the handling of alpha channels. I haven't quite figured out how to preserve the alpha channel, although Eric assures me that some tinkering with AlphaBlend ought to work. I'll play with that another day.