Loading an icon with KIconLoader

Given that I’ve recently been mucking about in the internals of KIconLoader and gaining more knowledge about its inner workings, I’ve learned a bit about how the icon loading mechanisms work in the KDE platform. I thought it would make for an interesting entry so here I go:

First thing to keep in mind is that there is a relevant specification regarding icon themes, the FreeDesktop.org Icon Theme Specification. Our implementation is intended to be compatible with this specification.

The basic idea of that spec is given a list of possible icon theme directory install locations (e.g. $XDG_DATA_DIRS, $KDEDIRS, etc.) and an icon theme (such as “Oxygen”), it gives definitions of how to install the icon theme metadata, including where it goes, what file names to use for the metadata, etc. It then defines how to use that icon theme metadata and a given icon name (such as “media-stop” or “dolphin”) to actually find an appropriate image to display to the user.

Each installed icon theme is described by an “index.theme” file installed at the top directory of the installed theme. This file contains obvious things like the name of the theme (including translations), but also a list of subdirectories, where each subdirectory has a specific size (e.g. 32×32) and type (fixed size, scalable, etc.). In theory each installed theme can also be spread amongst locations in the icon search prefix (so that the user can override system icons for instance, by using $HOME/.icons)

How KIconLoader works

KIconLoader is typically accessed through KIconLoader::loadIcon(), although there are helper functions such as SmallIcon(), DesktopIcon(), etc. KIconLoader is also used even if you use KIcon directly. Qt can use KIconLoader for QIcons as well when run under a KDE Plasma desktop. (Edit: Olivier Goffart from Nokia has left a comment letting me know I was incorrect, Qt does not use KIconLoader). In most all cases KIconLoader::loadIcon() is where the action is actually taking place.

Of course, first you actually have to construct a KIconLoader, and that involves the following:

  • Connecting to the shared data cache.
  • Loading a list of the cached “icon themes” (as represented by KIconTheme, and cached by KIconCache). KIconCache is one of the few (if not the only) remaining instances of KPixmapCache usage.
  • If the icon themes were not cached, they are initialized now (described below under “initializing the icon themes”).
  • Finally, some relevant metadata for each of the six possible icon groups (such as Desktop, Toolbar, MainToolbar, etc.) is loaded from the global configuration.

The icon themes themselves are hopefully not forced to initialize at this point, but it could happen in the worst-case scenario, so let’s look at what that consists of:

Initializing the icon themes

Each icon theme is described by a KIconTheme, which can deal with a single icon theme. KIconLoader uses KIconThemeNodes to manage created KIconThemes. So, for each individual KIconTheme, the following occurs:

  • If the theme is the default theme or the fallback theme (called “hicolor”) then any application-specific icons are added to the list of paths to search. This way applications can provide their own theme-specific icons.
  • In any event, the list of paths to search for our theme is then created, containing at least the KStandardDirs “icon” and “xdgdata-icon” resources, /usr/share/pixmaps, and “xdgdata-pixmap” resource (for GNOME compatibility).
  • The paths created in the first two steps are then searched, looking for the required index.theme for the requested theme, and also collating all the locations where icons for the theme are installed (recall that icons can be installed over multiple theme directories).
  • The index.theme is then read, and the list of subdirectories contained within is examined (e.g. 48×48/apps, 32×32/actions, etc.). Every possible subdirectory is verified to exist (or not) and its size requirements are read in. In KDE 4.4 my desktop would have had 106 paths to search, I’ve committed a patch for 4.5 that reduces that to approximately 78 by eliminating duplicate paths.
  • Any of the aforementioned subdirectories that contain Scalable icons then have every possible size (from the minimum to the maximum) added to the list of possible sizes for a requested size. For instance if the minimum size for a scalable directory type was 128 and the maximum was 256, then every integer size from 128 to 256 would be listed as a possibility for both 128 and 256 pixel-sized icon requests. Obviously the matching size would be preferred though.
  • Finally, the theme’s icon group definitions are read in (again, Desktop, Toolbar, etc.)

At this point KIconLoader is still initializing its themes. In order to meet the icon theme spec, KIconLoader has to support icon theme inheritance, and falling back to the “hicolor” theme. To do this, KIconLoader has to load every theme that the selected theme inherits, and when that chain is over, inherit the “hicolor” theme as well. When I typed this with the default Oxygen icon theme selected, this resulted in three total themes: “Oxygen”, “Oxygen$app”, “hicolor$app”, where $app is the currently running application. I suspect that Oxygen and Oxygen$app were meant to be joined but I’m not sure.

KIconLoader will at this point finally have the selected theme, and all inherited themes, loaded. At this point KIconLoader does one more thing: It loads all of those icon theme search paths I mentioned earlier into its own KStandardDirs under an “appicon” resource type, along with a few others.

Loading the icon

At this point KIconLoader::loadIcon() is ready to actually do something. After my changes prior to KDE SC 4.5 it searches as follows (this should be very similar to 4.4’s behavior):

  • If we are not searching for a “user” type of icon, which means that the icon is probably supposed to be already part of the desktop, then we use a function called findMatchingIconWithGenericFallbacks to look for the icon. That function uses a base function simply called findMatchingIcon, which itself searches all KIconThemes for 4 types of icons (PNG, SVGZ, SVG, XPM) first looking for “exact” matches, then for “best” matches. (The definition of “exact” depends on the icon theme, it doesn’t always mean pixel-perfect). If no icon was found, then the rightmost “dashed” chunk of the name is removed and the search done again (i.e. if no video-play-webm, then video-play would be searched, then simply video). The difference with findMatchingIconWithGenericFallbacks is that if findMatchingIcon fails, the search is done all over again, this time looking for “$name-x-generic”. findMatchingIcon is smart enough to not do the full search if it didn’t find the “-x-generic” type of icon.
  • Whew! We’re still in ::loadIcon. If findMatchingIconWithGenericFallbacks didn’t find anything, or a “user” icon is being searched for, then we use KIconLoader::iconPath instead. KIconLoader::iconPath() does a bit of work, but when looking for “user” icons is fairly simple, simply using the “appicon” resource I mentioned earlier to search for a named icon of the 4 image types allowed.
  • If no icon was found, the dreaded “unknown” icon is used (which is what led to a lot of the “question mark” icons in earlier KDE desktops).
  • Either way we have an icon now, which gets loaded and has an initial effects pass (such as desaturation for unselectable icons).
  • From there, favicons (for KHTML/Konqueror) are special-cased, and any icon overlays are applied.
  • To minimize future work for the loadIcon method, the result is added to the shared memory cache, and the icon is returned.

Needless to say, that’s a lot of work to load one simple icon (assuming the caches were empty at least). In the common case the icon you want will already be in the cache. But even that only works if the icon you want has the exact same size and effects applied as what you’re looking for. (The cache is smart enough to store more than one size of a given icon however).

Probably the biggest takeaway is to not call KIconLoader::loadIcon() if you can avoid it, or any of its moral equivalents such as KIcon or QIcon. When you do call it, save the value if you’re going to need it later, unless you’re not going to need it for much later. If at all possible, use the global KIconLoader since it has already been constructed. Don’t use weird icon sizes, always prefer a standard size since it is likely to be found during the “exact match” phase instead of requiring a “best match” second-chance. Finally, it is probably possible to simply construct a suitable cache of available icons (perhaps in a background thread) so motivated individuals are encouraged to research that possibility. ;)