Icon lookup on Linux may not be what you expect
April 11, 2022

There is a difference between what developers expect icon directories to do, what the actual freedesktop's specification states they should do, and what the actual implementation of the specification does.

Usually developers are told, install your fixed size n by n icons into /usr/share/hicolor/nxn/apps/ then if you want to provide a scalable icon that works for all other sizes install an svg file into /usr/share/hicolor/scalable/apps/. This is exactly what Blender does, it installs the following files:

/usr/share/icons/hicolor/16x16/apps/blender.png
/usr/share/icons/hicolor/22x22/apps/blender.png
/usr/share/icons/hicolor/24x24/apps/blender.png 
/usr/share/icons/hicolor/32x32/apps/blender.png
/usr/share/icons/hicolor/48x48/apps/blender.png
/usr/share/icons/hicolor/256x256/apps/blender.png
/usr/share/icons/hicolor/scalable/apps/blender.svg

Now let's ask for a very large 512x512 icon using GTK.

int main () {
    GtkIconTheme *icon_theme = gtk_icon_theme_get_default();

    GtkIconInfo *found_info =
        gtk_icon_theme_lookup_icon(icon_theme, "blender", 512, 0);

    char *fname = gtk_icon_info_get_filename(found_info);
    printf("Chosen file: %s\n", fname);
}

What do we get? /usr/share/icons/hicolor/256x256/apps/blender.png

Which is clearly not what we expected from the intuition that 'the scalable icon will match whenever the requested size does not exist'. This happens because this is not how the icon specification says the scalable/apps folder should be interpreted. What the specification says instead (in broader terms) is: look all directories under hicolor containing a file named blender*{.png|.svg|.xpm} find out the directory type and depending on it match with the file in the directory that is closest to the requested size. Notice how this says nothing about the naming of the directory, so the scalable word in scalable/apps does not mean anything to the specification. What actually matters is the directory type of scalable/apps, and what it means to be close to it.

The directory type is specific to each theme (Hicolor in this case) and is stored in the file /usr/share/icons/hicolor/index.theme along with other properties that define the meaning of closeness to it. Let's take a look at the directories we care about.

[256x256/apps]
MinSize=64
Size=256
MaxSize=256
Context=Applications
Type=Scalable

[scalable/apps]
MinSize=1
Size=128
MaxSize=256
Context=Applications
Type=Scalable

Notice how both of them are of type Scalable, this means they specify a range of valid sizes, and any request for a size in this range should match files inside it. If the size is not in range, then take the distance to the range and choose the one with the smallest distance.

Which range is closer?, as we can see, we have a tie. Both ranges are at 128 of the requested size (yes, distance to a range is measured until the closest limit of the range, not the Size property, not the center of the range).

And what does the specification tell us about a tie?. Take the first directory that appears in the Directories property of the section [Icon Theme]  of the index.theme file. This section is different from the ones shown before, it is not related to a directory but instead contains information about the theme in general. It's name, a description, and what is important for us, a list of directories in the order in which they should be scanned. Sadly for us, in this list 256x256/apps appears before scalable/apps.

This tie solving mechanism works fine if we for example lookup the blender icon of size 256,  in this case we get the icon inside 256x256/apps instead of the scalable one, even though the distance to both would be 0.

This problem does not happen just for Blender. For example Libreoffice provides a 512x512 non scalable icon placed inside a scalable directory with range 64-512, and also a scalable icon inside scalable/apps with range 1-256. In this case we will never get the scalable icon. Any request between 256 and 512 will match the 512x512 file that should not be scaled.

So, as application developers, how can we reliably know which icons we install will be used? sadly, we can't. At least not in a distribution independent way. It all depends on the content of the index.theme file and even though the freedesktop specification provides a template for the Hicolor theme, this one is not being used elementary OS, and will probably be different between distributions.

Now, one problem with the icon specification is that it does not provide any other mechanism to match files. Maybe for this reason GTK added the non-specified feature of saying scalable icons have size -1. This gives us a way to force getting a scalable icon through their API, but is again dependent on the fact that this version of the index.theme file sets all scalable folders (in the sense that their path contains the scalable word, not that they are of Scalable type) to a range that starts at 1, making size -1 match with them all the time.