Hello pyRevit Friends
Using icons for pyRevit buttons is super easy! You just drop a icon file with any size (but max 96*96) in the button folder and you are done. pyRevit does all the scaling and gives you buttons with high quality icons.
Now I´m sitting here trying to create icons for my C# plugins, and whatever I do the quality of the icons is not good. Also the transparent background gets black…
These examples use the same .png file as source!
So i wanted to find out what pyRevit does to get that sharp and detailed icons.
The interesting part is this one:
def create_bitmap(self, icon_size):
"""Resamples image and creates bitmap for the given size.
Icons are assumed to be square.
Args:
icon_size (int): icon size (width or height)
Returns:
(Imaging.BitmapSource): object containing image data at given size
"""
mlogger.debug('Creating %sx%s bitmap from: %s',
icon_size, icon_size, self.icon_file_path)
adjusted_icon_size = icon_size * 2
adjusted_dpi = DEFAULT_DPI * 2
screen_scaling = HOST_APP.proc_screen_scalefactor
self.filestream.Seek(0, IO.SeekOrigin.Begin)
base_image = Imaging.BitmapImage()
base_image.BeginInit()
base_image.StreamSource = self.filestream
base_image.DecodePixelHeight = int(adjusted_icon_size * screen_scaling)
base_image.EndInit()
self.filestream.Seek(0, IO.SeekOrigin.Begin)
image_size = base_image.PixelWidth
image_format = base_image.Format
image_byte_per_pixel = int(base_image.Format.BitsPerPixel / 8)
palette = base_image.Palette
stride = int(image_size * image_byte_per_pixel)
array_size = stride * image_size
image_data = System.Array.CreateInstance(System.Byte, array_size)
base_image.CopyPixels(image_data, stride, 0)
scaled_size = int(adjusted_icon_size * screen_scaling)
scaled_dpi = int(adjusted_dpi * screen_scaling)
bitmap_source = \
Imaging.BitmapSource.Create(scaled_size, scaled_size,
scaled_dpi, scaled_dpi,
image_format,
palette,
image_data,
stride)
return bitmap_source
But it does not help me, I can´t find out what is done here differently.
What I have tried is:
Setting button larger as 32x32, it does not help because it will just get cut of.
Setting DPI programmatically, but it does not help because there seems to be a 96 DPI limit, setting a higher number has no effect.
So I can not do anything else as setting the Icon to 32*32 and 96 DPI, which leads to the bad result.
Anyone had similar problems when scaling icons for C# Plugins? Any Ideas what I can try?
Really appreciate any advice on this topic, I´m out of ideas.
This is the my C# code for scaling.
using System;
using System.Drawing; // Add reference to System.Drawing.Common
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Media.Imaging;
using System.Windows.Interop;
using System.Windows.Media;
public static class IconImageClass
{
[DllImport("gdi32.dll")]
public static extern bool DeleteObject(IntPtr hObject);
public static Bitmap BitmapImageToBitmap(BitmapImage bitmapImage)
{
using (MemoryStream outStream = new MemoryStream())
{
BitmapEncoder enc = new BmpBitmapEncoder();
enc.Frames.Add(BitmapFrame.Create(bitmapImage));
enc.Save(outStream);
using (var bitmap = new Bitmap(outStream))
{
return new Bitmap(bitmap);
}
}
}
public static BitmapSource BitmapToBitmapSource(Bitmap bitmap)
{
IntPtr hBitmap = bitmap.GetHbitmap();
try
{
return Imaging.CreateBitmapSourceFromHBitmap(
hBitmap, IntPtr.Zero, Int32Rect.Empty,
BitmapSizeOptions.FromEmptyOptions());
}
finally
{
DeleteObject(hBitmap);
}
}
public static Bitmap ResizeImage(Image image, int width, int height)
{
var destRect = new Rectangle(0, 0, width, height);
var destImage = new Bitmap(width, height);
destImage.SetResolution(image.HorizontalResolution, image.VerticalResolution);
using (var graphics = Graphics.FromImage(destImage))
{
graphics.CompositingMode = CompositingMode.SourceCopy;
graphics.CompositingQuality = CompositingQuality.HighQuality;
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphics.SmoothingMode = SmoothingMode.HighQuality;
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
using (var wrapMode = new ImageAttributes())
{
wrapMode.SetWrapMode(WrapMode.TileFlipXY);
graphics.DrawImage(image, destRect, 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, wrapMode);
}
}
return destImage;
}
public static BitmapSource ScaledIcon(BitmapImage largeIcon, int w, int h)
{
return BitmapToBitmapSource(ResizeImage(BitmapImageToBitmap(largeIcon), w, h));
}
}
edit: I also tried to scale the icons with gimp and using them directly for my plugin, that gives me the same bad result. What magic is pyRevit doing here?