Scaling a Bitmap for the Large and Small Image Icons

Every time I created a ribbon button, I was faced with the task of creating appropriately scaled icons for it to populate the PushButton large and small image icon properties LargeImage and Image.

They seem to expect a 32 x 32 and 16 x 16 icon, respectively.

I finally solved that once and for all by implementing a couple of methods to perform automatic bitmap scaling:

Here they are one by one:

BitmapImageToBitmap

/// <summary>
/// Convert a BitmapImage to Bitmap
/// </summary>
static Bitmap BitmapImageToBitmap( 
  BitmapImage bitmapImage )
{
  //BitmapImage bitmapImage = new BitmapImage(
  // new Uri("../Images/test.png", UriKind.Relative));

  usingMemoryStream outStream = new MemoryStream() )
  {
    BitmapEncoder enc = new BmpBitmapEncoder();
    enc.Frames.Add( BitmapFrame.Create( bitmapImage ) );
    enc.Save( outStream );
    Bitmap bitmap = new Bitmap( outStream );

    return new Bitmap( bitmap );
  }
}

BitmapToBitmapSource

[System.Runtime.InteropServices.DllImport"gdi32.dll" )]
public static extern bool DeleteObject( IntPtr hObject );

/// <summary>
/// Convert a Bitmap to a BitmapSource
/// </summary>
static BitmapSource BitmapToBitmapSource( Bitmap bitmap )
{
  IntPtr hBitmap = bitmap.GetHbitmap();

  BitmapSource retval;

  try
  {
    retval = Imaging.CreateBitmapSourceFromHBitmap(
      hBitmap, IntPtr.Zero, Int32Rect.Empty,
      BitmapSizeOptions.FromEmptyOptions() );
  }
  finally
  {
    DeleteObject( hBitmap );
  }
  return retval;
}

ResizeImage

/// <summary>
/// Resize the image to the specified width and height.
/// </summary>
/// <param name="image">The image to resize.</param>
/// <param name="width">The width to resize to.</param>
/// <param name="height">The height to resize to.</param>
/// <returns>The resized image.</returns>
static Bitmap ResizeImage(
  Image image, 
  int width,
  int height )
{
  var destRect = new System.Drawing.Rectangle( 
    0, 0, width, height );

  var destImage = new Bitmap( width, height );

  destImage.SetResolution( image.HorizontalResolution, 
    image.VerticalResolution );

  usingvar g = Graphics.FromImage( destImage ) )
  {
    g.CompositingMode = CompositingMode.SourceCopy;
    g.CompositingQuality = CompositingQuality.HighQuality;
    g.InterpolationMode = InterpolationMode.HighQualityBicubic;
    g.SmoothingMode = SmoothingMode.HighQuality;
    g.PixelOffsetMode = PixelOffsetMode.HighQuality;

    usingvar wrapMode = new ImageAttributes() )
    {
      wrapMode.SetWrapMode( WrapMode.TileFlipXY );
      g.DrawImage( image, destRect, 0, 0, image.Width, 
        image.Height, GraphicsUnit.Pixel, wrapMode );
    }
  }
  return destImage;
}

ScaledIcon

ScaledIcon simply calls the three helper methods defined above to return a scaled version of the input image:

/// <summary>
/// Scale down large icon to desired size for Revit 
/// ribbon button, e.g., 32 x 32 or 16 x 16
/// </summary>
static BitmapSource ScaledIcon( 
  BitmapImage large_icon,
  int w,
  int h )
{
  return BitmapToBitmapSource( ResizeImage( 
    BitmapImageToBitmap( large_icon ), w, h ) );
}

Usage Sample

Within the external application PopulatePanel method, simply read the embedded resource icon image and apply ScaledIcon to it to populate the large and small image properties with appropriately scaled images:

  BitmapImage bmi = new BitmapImagenew Uri( 
    "icons/cmdx.png"UriKind.Relative ) );

  PushButton pb = p.AddItem( new PushButtonData( 
    "Command""Command", path, "CmdX" ) ) as PushButton;

  pb.ToolTip = "Do something fantastic";
  pb.LargeImage = ScaledIcon( bmi, 32, 32 );
  pb.Image = ScaledIcon( bmi, 16, 16 );

Embedded icon resource