Home > Software engineering >  How do I convert stdole.stdPicture to a .net image in C#?
How do I convert stdole.stdPicture to a .net image in C#?

Time:03-11

Im building an Add-In for Outlook where I copy all exchange users from the global address list to the local contacts.

The problem is I want transfer the picture of the exchange user too, but exchUser.GetPicture() returns a stdole.stdPicture and I have not yet found a working solution to download or convert it into an image/jpg/...

Here the code to get the exchange User from the global address list:

private void EnumerateGAL()
{
    Outlook.AddressList gal = Application.Session.GetGlobalAddressList(); 
    if (gal != null) 
    {
        for (int i = 1; i <= gal.AddressEntries.Count - 1; i  )
        {
            Outlook.AddressEntry addrEntry = gal.AddressEntries[i];
            Outlook.ExchangeUser exchUser = addrEntry.GetExchangeUser();
                    
            if (addrEntry.AddressEntryUserType == Outlook.OlAddressEntryUserType.olExchangeUserAddressEntry
                && exchUser.CompanyName == "")
            {
                CreateContact(exchUser);
                //exchUser.GetPicture() returns stdole.stdPicture
            }
        }
    }
    return;
}

The closest solution I found, was a conversion of stdole.IPictureDisp which returns a bitmap but IPuctureDisp and stdPicture isn´t the same as I read somewhere.

public static System.Drawing.Image ConvertPicture(stdole.IPictureDisp image)
{
    int type = image.Type;
    if (type == 1)
    {
        IntPtr hPal = (IntPtr)image.hPal;
        return Image.FromHbitmap((IntPtr)image.Handle, hPal);
    }
    return null;
}

In the end I need to download the picture because I can only upload a picture to a contact with a path. So, is there a way to download a stdPicture or convert it to be able to download it?

CodePudding user response:

There are four main ways to get the job done.

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:

internal class AxHostConverter : AxHost
{
    private AxHostConverter() : base("") { }

    static public stdole.IPictureDisp ImageToPictureDisp(Image image)
    {
       return (stdole.IPictureDisp)GetIPictureDispFromPicture(image);
    }

    static public Image PictureDispToImage(stdole.IPictureDisp pictureDisp)
    {
        return GetPictureFromIPicture(pictureDisp);
    }
}

Your second option is to use OleLoadPicture or OleCreatePictureIndirect. 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.

internal class OleCreateConverter

{

    [DllImport("oleaut32.dll", EntryPoint = "OleCreatePictureIndirect",

        CharSet = CharSet.Ansi, ExactSpelling = true, PreserveSig = true)]

    private static extern int OleCreatePictureIndirect(

        [In] PictDescBitmap pictdesc, ref Guid iid, bool fOwn,

        [MarshalAs(UnmanagedType.Interface)] out object ppVoid);

    const short _PictureTypeBitmap = 1;
    [StructLayout(LayoutKind.Sequential)]
    internal class PictDescBitmap
    {
        internal int cbSizeOfStruct = Marshal.SizeOf(typeof(PictDescBitmap));
        internal int pictureType = _PictureTypeBitmap;
        internal IntPtr hBitmap = IntPtr.Zero;
        internal IntPtr hPalette = IntPtr.Zero;
        internal int unused = 0;

        internal PictDescBitmap(Bitmap bitmap)
        {
            this.hBitmap = bitmap.GetHbitmap();
        }
    }

    public static stdole.IPictureDisp ImageToPictureDisp(Image image)
    {
       if (image == null || !(image is Bitmap))
        {
            return null;
        }

        PictDescBitmap pictDescBitmap = new PictDescBitmap((Bitmap)image);
        object ppVoid = null;
        Guid iPictureDispGuid = typeof(stdole.IPictureDisp).GUID;
        OleCreatePictureIndirect(pictDescBitmap, ref iPictureDispGuid, true, out ppVoid);
        stdole.IPictureDisp picture = (stdole.IPictureDisp)ppVoid;
        return picture;
    }

    public static Image PictureDispToImage(stdole.IPictureDisp pictureDisp)
    {
        Image image = null;
        if (pictureDisp != null && pictureDisp.Type == _PictureTypeBitmap)
        {
            IntPtr paletteHandle = new IntPtr(pictureDisp.hPal);
            IntPtr bitmapHandle = new IntPtr(pictureDisp.Handle);
            image = Image.FromHbitmap(bitmapHandle, paletteHandle);
        }
        return image;
    }
}

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;

internal class VB6CompatibilityConverter
{
    public static stdole.IPictureDisp ImageToPictureDisp(Image image)
    {
        return (stdole.IPictureDisp)Support.ImageToIPictureDisp(image);
    }

    public static Image PictureDispToImage(stdole.IPictureDisp pictureDisp)
    {
        return Support.IPictureDispToImage(pictureDisp);
    }
}

Finally, you can implement IPictureDisp and IPicture yourself. 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

internal class PictureDispConverter
{
    public static stdole.IPictureDisp BitmapToPictureDisp(Bitmap bitmap)
    {
        return new PictureDispImpl(bitmap);
    }
 
    public static Image PictureDispToBitmap(stdole.IPictureDisp pictureDisp)
    {
        // TODO
        return null;
    }
}

internal class PictureDispImpl : stdole.IPictureDisp, stdole.IPicture
{
    #region Init

    [DllImport("gdi32.dll")]
    static extern void DeleteObject(IntPtr handle);

    private Bitmap _image;
    private IntPtr _handle;
 
    public PictureDispImpl(Bitmap image)
    {
        _image = image;
    }
 
    ~PictureDispImpl()
    {
        if (_handle != IntPtr.Zero)
        {
            DeleteObject(_handle);
        }
    }

    #endregion

    #region IPictureDisp Members

    public int Width
    {
        get { return _image.Width; }
    }

    public int Height
    {
        get { return _image.Height; }
    }

    public short Type
    {
        get { return 1; }
    }

    public int Handle
    {
        get
        {
            if (_handle == IntPtr.Zero)
            {
                _handle = _image.GetHbitmap();
            }
            return _handle.ToInt32();
        }
    }
 
    public int hPal
    {
        get { return 0; }
        set { }
    }
 
    public void Render(
        int hdc, int x, int y, int cx, int cy, int xSrc, int ySrc, int cxSrc, int cySrc, IntPtr prcWBounds)
    {
        Graphics graphics = Graphics.FromHdc(new IntPtr(hdc));
        graphics.DrawImage(
            _image, new Rectangle(x, y, cx, cy), xSrc, ySrc, cxSrc, cySrc, GraphicsUnit.Pixel);
    }
 
    #endregion

    #region IPicture Members
 
    public int Attributes
    {
        get { return 0; }
    }

    public int CurDC
    {
        get { return 0; }
    }

    public bool KeepOriginalFormat
    {
        get { return false; }
        set { }
    }
 
    public void PictureChanged()
    {
    }
 
    public void SaveAsFile(IntPtr pstm, bool fSaveMemCopy, out int pcbSize)
    {
        pcbSize = 0;
    }

    public void SelectPicture(int hdcIn, out int phdcOut, out int phbmpOut)
    {
        phdcOut = 0;
        phbmpOut = 0;
    }

    public void SetHdc(int hdc)
    {

    }
    #endregion
}
  • Related