Using the same set of button images with transparency in all Visio versions

If you write a Visio add-in that targets multiple Visio versions at the same time, and have some custom buttons with images (with transparency), you might run into trouble with that new Visio 2010 Ribbon user interface needs different “flavor” of images compared to Visio 2003 and 2007. So you’ll have to to either create two separate sets of images (one set for pre-ribbon version of Visio, the second set for the ribbon one), or to “dance around a little” and make both versions consume the same set of images. The article focuses on the second approach I ended up with.

In the old versions of Office, you could set the button picture using the “.Picture” and the “.Mask” properties of the “CommandBarButton” object. For Visio 2010, you should return the picture from your add-in, and that picture you return is expected to be PNG with transparency.

The solution described here uses single PNG source image, and creates “.Mask” and “.Picture” images for previous versions of Visio out of it. To do so it uses the “AxHost” technique described in MSDN:

// ---------------

 Bitmap picture, mask;
 PictureConvert.BitmapToPictureAndMask(Resources.MyPicture, out picture, out mask);

 button.Picture = PictureConvert.ImageToPictureDisp(picture);
 button.Mask = PictureConvert.ImageToPictureDisp(mask);

 // ---------------

 internal class PictureConvert : System.Windows.Forms.AxHost
 {
     private PictureConvert() : base("") { }

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

     static public void BitmapToPictureAndMask(Bitmap bm, out Bitmap picture, out Bitmap mask)
     {
         int w = bm.Width;
         int h = bm.Height;

         byte[] picture_data = new byte[3 * w * h];
         byte[] mask_data = new byte[3 * w * h];

         BitmapData bm_bits = 
           bm.LockBits(new Rectangle(0, 0, w, h), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);

         byte[] bits = new byte[4 * w * h];
         Marshal.Copy(bm_bits.Scan0, bits, 0, 4 * w * h);
         bm.UnlockBits(bm_bits);

         for (int y = 0; y < h; ++y)
         {
             for (int x = 0; x < w; ++x)
             {
                 int src_idx = (x + y * w) * 4;
                 int dst_idx = (x + y * w) * 3;

                 picture_data[dst_idx + 0] = bits[src_idx + 0];
                 picture_data[dst_idx + 1] = bits[src_idx + 1];
                 picture_data[dst_idx + 2] = bits[src_idx + 2];

                 byte t = (bits[src_idx + 3] < 128) ? (byte)255 : (byte)0;

                 mask_data[dst_idx + 0] = t;
                 mask_data[dst_idx + 1] = t;
                 mask_data[dst_idx + 2] = t;
             }
         }

         Rectangle rect = new Rectangle(0, 0, w, h);

         picture = new Bitmap(w, h, PixelFormat.Format24bppRgb);
         BitmapData picture_bits = picture.LockBits(rect, ImageLockMode.WriteOnly, picture.PixelFormat);
         Marshal.Copy(picture_data, 0, picture_bits.Scan0, w * h * 3);
         picture.UnlockBits(picture_bits);

         mask = new Bitmap(w, h, PixelFormat.Format24bppRgb);
         BitmapData mask_bits = mask.LockBits(rect, ImageLockMode.WriteOnly, picture.PixelFormat);
         Marshal.Copy(mask_data, 0, mask_bits.Scan0, w * h * 3);
         mask.UnlockBits(mask_bits);

     }
 }

The idea for the unmanaged (C++) version is quite similar though much more wordy; the source code for the C++ project includes the implementation of the C++ version “PictureConvert” function. The code in the sample uses the gdiplus API to construct the IPictureDisp (needed by Visio) out of the PNG images stored in the addin’s resources.

Download sample project (C++)

Download sample project (C#)

Leave a Reply