Multi-Resolution Asset Workflow Automation

Now that we know how to do multi-resolution and asset scaling, I will show you how to aid your software stack in exporting multi-resolution assets.

The raster part is easy. You work with high resolution and scale down.

I simply use ImageMagick for this because I am not at all happy with the quality of the downscaling implementations provided by other tools. Of course, maybe they got better in time, but I do not find that worthy of occasional testing.

This task usually requires too much manual involvement, so I will not provide a batch method either.

convert -resize 50% /large/asset.png /medium/asset.png
convert -resize 25% /large/asset.png /small/asset.png


For the vector assets, we can go for the comfort of a custom Inkscape extension.

Our extension will be placed in Extensions > Export > Sprite, and the UI will look like this:

Directory is the root visual asset directory in your project. In Twiniwt, that is ~/dev/twiniwt/Resources/res.

If you activate context, you can select if the element is a UI or Game element.

When disabled, the extension will export below assets:


When Context is enabled, and UI Element selected, the extension will export below assets:


That’s all. Almost all our production assets in Twiniwt are exported using this simple tool.

You can download the extension as a zip file. I am placing it in Public Domain. Place the contents in the extensions subdirectory of your inkscape home directory. In GNU/Linux, this is ~/.config/inkscape/extensions/. It is probably the same in Mac OSX. Unfortunately, I haven’t even tested this extension in Windows, but I am sure it will work out-of-the-box once you locate it.

However, learning how to modify it, and writing your own production tools is more important.


An Inkscape extension with a GUI requires two files. An XML formatted “.inx” file, and the actual implementation module.

Let’s first write the interface and specify the meta data.
Those belong to our sprite.inx file.

<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension xmlns="">
 <dependency type="extension">org.inkscape.output.svg.inkscape</dependency>
 <dependency type="executable" location="extensions"></dependency>
 <dependency type="executable" location="extensions"></dependency>
 <param name="directory" type="string" _gui-text="Directory to save images to:">~/</param>
 <param name="image" type="string" _gui-text="Image name (without extension):">sprite</param>
 <param name="has_context" type="boolean" _gui-text="Context:">false</param>
 <param name="context" type="optiongroup" _gui-text="Select context:" appearance="minimal">
 <_option value="ui">UI Element</_option>
 <_option value="game">Game Element</_option>
 <effect needs-live-preview="false">
 <submenu _name="Export"/>
 <command reldir="extensions" interpreter="python"></command>

Now, the actual implementation, named . The boilerplate:

#!/usr/bin/env python

import os
import sys
    from subprocess import Popen, PIPE
    bsubprocess = True
    bsubprocess = False
import inkex

The class and the option parsers.

class Sprite(inkex.Effect):
    def __init__(self):
        self.OptionParser.add_option("--directory", action="store",
                                        type="string", dest="directory",
                                        default=None, help="")

        self.OptionParser.add_option("--image", action="store",
                                        type="string", dest="image",
                                        default=None, help="")

        self.OptionParser.add_option("--has_context", action="store",
                                        type="string", dest="has_context",
                                        default=None, help="")

        self.OptionParser.add_option("--context", action="store",
                                        type="string", dest="context",
                                        default=None, help="")

The utility methods.

    def get_filename_parts(self):
        if self.options.image == "" or self.options.image is None:
            inkex.errormsg("Please enter an image name")
        return (, self.options.image)

    def check_dir_exists(self, dir):
        if not os.path.isdir(dir):

Exporter for one asset:

    def export_sprite(self, filename, dpi):
        svg_file = self.args[-1]
        command = "inkscape -e \"%s\" -d \"%s\" \"%s\" " % (filename, dpi, svg_file)
        if bsubprocess:
            p = Popen(command, shell=True, stdout=PIPE, stderr=PIPE)
            return_code = p.wait()
            f = p.stdout
            err = p.stderr
            _, f, err = os.open3(command)

Exporter for all requested resolutions:

    def export_sprites(self, assets):
        dirname, filename = self.get_filename_parts()
        output_files = list()
        if dirname == '' or dirname == None:
            dirname = './'
        dirname = os.path.expanduser(dirname)
        dirname = os.path.expandvars(dirname)
        dirname = os.path.abspath(dirname)
        if dirname[-1] != os.path.sep:
            dirname += os.path.sep
        for directory, scale in assets.items():
            dpi = 96 * scale
            asset_dirname = dirname + directory + os.path.sep
            if self.options.has_context == 'true':
                asset_dirname = asset_dirname + self.options.context + os.path.sep
            f = asset_dirname + filename + ".png"
            self.export_sprite(f, dpi)
        inkex.errormsg("The sprites have been saved as:" + "\n\n" + "\n".join(output_files))

This is where we define the resolutions we ask for. Change assets for your own needs if you like.

    def effect(self):
        assets = {"small": 1, "medium": 2, "large": 4}

The entry point to the extension.

if __name__ == "__main__":
    e = Sprite()

If anything in this post confuses you, please check out the how to do multi-resolution and asset scaling post. Also, please reply in comments below if you have any questions, ideas or fixes to the extension.

Good luck!