Scale/Create images for button icons

Scale/Create images for button icons

gerhard.pawlat
Contributor Contributor
1,926 Views
8 Replies
Message 1 of 9

Scale/Create images for button icons

gerhard.pawlat
Contributor
Contributor

Hello Revit Friends 🙂

After using pyRevit for a long time I´m now creating my first C#-Plugins.
I have big trouble with creating icons because I can´t manage to make them look good in 32x32 pixels. In pyRevit i just used larger images and the magically got scaled with high quality.

For example, if i want to make an icon in gimp from this image:

 

I´m ending up with something like that:


gerhardpawlat_1-1710714237985.png

 

And this looks awful in revit. I also tried the different interpolation options I have in gimp but none is giving a good result.

I´d really appreciate any advice on which methods or tools i should use to create higher quality icons.


Thanks for your help in advance!

Best regards!

0 Likes
Accepted solutions (1)
1,927 Views
8 Replies
Replies (8)
Message 2 of 9

moturi.magati.george
Autodesk
Autodesk

Hi @gerhard.pawlat,

Most Revit icons have a size of 16x16 or 32x32. The icons are meant to be used on buttons, panels, Combo box etc. This means that they will not render on full screen of your application. 


Here is a documentation on how you can use them in Revit: https://help.autodesk.com/view/RVT/2022/ENU/?guid=Revit_API_Revit_API_Developers_Guide_Introduction_...

  Moturi George,     Developer Advocacy and Support,  ADN Open
0 Likes
Message 3 of 9

gerhard.pawlat
Contributor
Contributor

Hello @moturi.magati.george  and thanks for you reply.

As you can see in my screenshot i resized the icon to 32x32 (96dpi) but this leads to a quality that is not usable and far from the quality of native revit icons and also far from the quality of pyrevit icons.

So there must be some tricks how to scale an icon with better quality.

pyRevit takes images with a maximum of 96x96 pixels ant then scales the programmatically and even sets the dpi corresponding to the display dpi.

If no one has a tip for me how I can better scale down my icon I will have to rebuild the method pyrevit uses:

 

 

ICON_SMALL = 16
ICON_MEDIUM = 24
ICON_LARGE = 32

DEFAULT_DPI = 96

DEFAULT_TOOLTIP_IMAGE_FORMAT = '.png'
DEFAULT_TOOLTIP_VIDEO_FORMAT = '.swf'
if not EXEC_PARAMS.doc_mode and HOST_APP.is_newer_than(2019, or_equal=True):
    DEFAULT_TOOLTIP_VIDEO_FORMAT = '.mp4'


def argb_to_brush(argb_color):
    # argb_color is formatted as #AARRGGBB
    a = r = g = b = "FF"
    try:
        b = argb_color[-2:]
        g = argb_color[-4:-2]
        r = argb_color[-6:-4]
        if len(argb_color) > 7:
            a = argb_color[-8:-6]
        return Media.SolidColorBrush(Media.Color.FromArgb(
                Convert.ToInt32("0x" + a, 16),
                Convert.ToInt32("0x" + r, 16),
                Convert.ToInt32("0x" + g, 16),
                Convert.ToInt32("0x" + b, 16)
                )
            )
    except Exception as color_ex:
        mlogger.error("Bad color format %s | %s", argb_color, color_ex)


def load_bitmapimage(image_file):
    """Load given png file.

    Args:
        image_file (str): image file path

    Returns:
        (Imaging.BitmapImage): bitmap image object
    """
    bitmap = Imaging.BitmapImage()
    bitmap.BeginInit()
    bitmap.UriSource = Uri(image_file)
    bitmap.CacheOption = Imaging.BitmapCacheOption.OnLoad
    bitmap.CreateOptions = Imaging.BitmapCreateOptions.IgnoreImageCache
    bitmap.EndInit()
    bitmap.Freeze()
    return bitmap


# Helper classes and functions -------------------------------------------------
class PyRevitUIError(PyRevitException):
    """Common base class for all pyRevit ui-related exceptions."""
    pass


class ButtonIcons(object):
    """pyRevit ui element icon.

    Upon init, this type reads the given image file into an io stream and
    releases the os lock on the file.

    Args:
        image_file (str): image file path to be used as icon

    Attributes:
        icon_file_path (str): icon image file path
        filestream (IO.FileStream): io stream containing image binary data
    """
    def __init__(self, image_file):
        self.icon_file_path = image_file
        self.check_icon_size()
        self.filestream = IO.FileStream(image_file,
                                        IO.FileMode.Open,
                                        IO.FileAccess.Read)

    @staticmethod
    def recolour(image_data, size, stride, color):
        # FIXME: needs doc, and argument types
        # ButtonIcons.recolour(image_data, image_size, stride, 0x8e44ad)
        step = stride / size
        for i in range(0, stride, step):
            for j in range(0, stride, step):
                idx = (i * size) + j
                # R = image_data[idx+2]
                # G = image_data[idx+1]
                # B = image_data[idx]
                # luminance = (0.299*R + 0.587*G + 0.114*B)
                image_data[idx] = color >> 0 & 0xff       # blue
                image_data[idx+1] = color >> 8 & 0xff     # green
                image_data[idx+2] = color >> 16 & 0xff    # red

    def check_icon_size(self):
        """Verify icon size is within acceptable range."""
        image = System.Drawing.Image.FromFile(self.icon_file_path)
        image_size = max(image.Width, image.Height)
        if image_size > 96:
            mlogger.warning('Icon file is too large. Large icons adversely '
                            'affect the load time since they need to be '
                            'processed and adjusted for screen scaling. '
                            'Keep icons at max 96x96 pixels: %s',
                            self.icon_file_path)

    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

    @property
    def small_bitmap(self):
        """Resamples image and creates bitmap for size :obj:`ICON_SMALL`.

        Returns:
            (Imaging.BitmapSource): object containing image data at given size
        """
        return self.create_bitmap(ICON_SMALL)

    @property
    def medium_bitmap(self):
        """Resamples image and creates bitmap for size :obj:`ICON_MEDIUM`.

        Returns:
            (Imaging.BitmapSource): object containing image data at given size
        """
        return self.create_bitmap(ICON_MEDIUM)

    @property
    def large_bitmap(self):
        """Resamples image and creates bitmap for size :obj:`ICON_LARGE`.

        Returns:
            (Imaging.BitmapSource): object containing image data at given size
        """
        return self.create_bitmap(ICON_LARGE)

 

 



0 Likes
Message 4 of 9

gerhard.pawlat
Contributor
Contributor

Here is a comparison of a pyRevit button (top) and my C# button (bottom).

The pyRevit button icon is much sharper and has rounder cornerns than my C# button icon.

gerhardpawlat_0-1710766296794.png

 

0 Likes
Message 5 of 9

jeremy_tammik
Alumni
Alumni

Does the blog post by The Building Coder on Scaling a Bitmap for the Large and Small Image Icons help?

  

http://thebuildingcoder.typepad.com/blog/2018/05/scaling-a-bitmap-for-the-large-and-small-image-icon...

  

Jeremy Tammik Developer Advocacy and Support + The Building Coder + Autodesk Developer Network + ADN Open
Message 6 of 9

gerhard.pawlat
Contributor
Contributor

Hello @jeremy_tammik and thanks for you reply.

 

This link is helpful, but unfortunately not regarding scaling quality issues.
I also tried to add setting of the DPI programmatically but it also had no effect, it seems that 96 dpi is the max input. I can decrease the quality by using lower numbers but i can not increase quality with higher numbers.
So for now I can not find out what pyRevit does better.

Now I have another issue, transparent png background is displayed black in revit 😞

gerhardpawlat_0-1710780335764.png

 

gerhardpawlat_1-1710780342096.png

 

0 Likes
Message 7 of 9

jeremy_tammik
Alumni
Alumni

So for now I can not find out what pyRevit does better.

  

Yes you can! pyRevit is open source.

  

Jeremy Tammik Developer Advocacy and Support + The Building Coder + Autodesk Developer Network + ADN Open
Message 8 of 9

ricaun
Advisor
Advisor
Accepted solution

You cannot fit more pixels in a 32x32, if you scale down a big image with a lot of detail gonna look not good. Usually, you need to create in that size to make feel right inside Revit.

 

pyRevit is doing some magic to show a bigger image, and after recreating it in C#.

 

Here are 3 images inside Revit with sizes 512x512, 96x96, and 32x32.

 

Revit Image Scale Dpi.PNG


In the end, the code scales the image 512x512 to the dpi 1536, 96x96 to the dpi 288 and 32x32 stays with dpi 96. This forces the image to appear with the size 32x32 but with more pixels.

 

I don't think that's a good idea to use this scale dpi, but here is the code if you want to play with it.

 

Luiz Henrique Cassettari

ricaun.com - Revit API Developer

AppLoader EasyConduit WireInConduit ConduitMaterial CircuitName ElectricalUtils

Message 9 of 9

gerhard.pawlat
Contributor
Contributor

Hello @ricaun 

 

Thank you so much for taking the time to explain and create the code! 🙂

I understand that an icon should ideally be created for the size it will be used, but this is very interresting and I will see if i will use it.

For now i got it working with 96*96 pixels! Still have to figure out the black background thing though 🙂


gerhardpawlat_0-1710805542166.png