Back to the journal
Go / Tooling

Generating app icons with Go

The image pipeline behind assetsgen, from one source image to platform specific icons and assets.

An app logo is one image until it has to go into a project. Then there are Android resource folders, an iOS asset catalog, a store image and a separate notification icon to deal with.

I built assetsgen in Go to generate those files. It handles Android launcher icons, notification icons, drawable assets, Google Play graphics and iOS app icons. The interesting part for me is how much of that work can share the same image processing code while still keeping the platform rules separate.

Resizing is only one step

A source image can have transparent space around it. It can need a background, padding or a different treatment for a notification icon. Scaling it to a list of dimensions does not address any of that.

For an Android launcher icon, the generator prepares the logo, handles the background and produces the legacy and adaptive resources. Foreground and background assets have their own output paths. The adaptive icon also needs XML that points Android at those resources.

A notification icon follows a different path. Its generator handles transparency and converts the visible parts to white before producing the density variants. Sending it through the same colour and background treatment as a launcher icon would give me the wrong kind of asset.

Those differences live in separate generators. The launcher icon code and notification icon code are good places to compare them.

Share the image operations

Underneath the generators, I have an image pipeline with operations for trimming, padding, compositing, resizing and saving. The platform code chooses which operations to apply and in what order.

There is a small asset interface behind the output step. Each asset can provide its name, calculate its dimensions and say which directory it belongs in. The resizing code can work with that interface without knowing whether it is producing an Android resource or an iOS icon.

I like having that distinction in the code. If an output size changes, I can look at the asset definitions. If trimming behaves incorrectly, I can look at the shared image operation. There is less reason to go through the command handling code for either problem.

The CLI uses urfave/cli, with bild and Go Colorful in the image processing code. Arguments and flags stay in the command layer, while the generators accept Go option structs.

Generate first, then inspect

The command names have short aliases. For example, aai generates Android launcher icons, ani generates notification icons and iai generates iOS app icons:

assetsgen aai ./logo.png
assetsgen ani ./notification.png
assetsgen iai ./logo.png

These commands generate files in assets_gen_out. Moving the output into the app is an explicit option. That gives me a chance to inspect the generated files before replacing resources in the project.

I would check the padding and transparency at this point. A logo looking fine at its original size does not tell me much about the smallest generated icon. The launcher preview and an actual notification are worth checking too.

One command for the common outputs

There is also an all command for Android launcher icons, the Play Store logo, notification icons and iOS icons:

assetsgen all ./logo.png

In the implementation, those four generators run in separate goroutines. The command waits for them to finish and joins their returned errors before attempting the optional apply step. The drawable asset generator remains a separate command.

That combined command is convenient when the same source suits all four outputs. If a notification needs a simpler silhouette, I can give its command a separate source image instead. Keeping the individual commands matters because the artwork requirements do not always line up.

The assetsgen repository has the commands and options. I would start with the individual command for the output I need, inspect the generated files, then put the chosen invocation into the project’s build scripts.