offset \ˈȯf-ˌset\ noun

a force or influence that makes an opposing force ineffective or less effective

GIMP and Leaflet

I needed to run the Leaflet library in JavaScript using a custom tileset for a small video game Vesna and I are working on. Leaflet's usage is pretty straightforward and the details on using it can be seen in the official documentation. The bare minimum is just to create a new map, assign it some custom tiles and point it to an element in the webpage (a div with an id "map"), for example like this:

var map = L.map('map').setView([0.0, -0.0], 4);
L.tileLayer('./{z}/{x}/{y}.jpeg', {
    maxZoom: 4
}).addTo(map);

You need to have a custom tileset in the proper location for this to work and the tileset needs to be created from an image. Every tile is placed in specific x and y coordinates on the 2d plane and the zoom level it is in. Preparing an image for this to work requires slicing it and copying the tiles to their respective locations. I decided to automate the process.

Considering I dabble with various 2D imaging applications and program in Python, GIMP seemed like the perfect choice here since it has a mechanism for creating plug-ins with Python. Some people will create the image in GIMP itself and just run the plug-in.

To start off, you want to have the biggest resolution of the initial image. Mine is a square image 4096 pixels in width and height. Using tiles of 256 pixels width and height it can achieve 4 zoom levels. When you go down, 3 is 2048, 2 is 1024, 1 is 512 and zoom level 0 is 256, just like the original tile size. Because it's exponential, it quickly drains the resources so you might want to go small.

The algorithm for this prepares the image for usage in the resolution required for the chosen zoom level just in case. The output is a JPEG quality image of 90%. After that, it goes from the set zoom level, makes the tiles for it in the specified x and y coordinates using loops, scales the original image down and loops again until it reaches zoom level 0.

This algorithm runs on my system for quite some time if the zoom level is big. I tried to parallelize it, but I had problems:

  1. You cannot pickle the image in GIMP and pass it to another process. It ends up being of "None" type and you cannot pass a reference to the worker with GIMP.
  2. If you skip GIMP altogether and use PIL or Pillow, you can parallelize it on multiple cores, but heavy disk I/O just kills it as far as I can see. Accessing an image from a temporary folder is slower in this case so I tried the third option.
  3. Pickling with PIL or Pillow works. However, pickling an object of 16384x16384 pixels and distributing it to workers brings my system to a crawl. It just exhausts the RAM completely, starts consuming the swap and exhausts it as well.

In the end, I just gave up and decided that half an hour (or less, if the image is not that big) is not so bad. I committed the resulting GIMP plug-in for exporting the Leaflet tiles in the repository on Bitbucket. You can contribute, if you'd like, but if not, installing it is not a problem. I tested it on Ubuntu, GIMP versions 2.8 and 2.9 for now. You need to put the .py file in your plug-ins directory, which is usually in your home folder, under .config/GIMP/2.9/plug-ins (if you're on 2.9 version). After copying the file, make sure it's executable and run GIMP. It will show in the filters section, under Leaflet submenu. Open an image first and activate the plug-in. Using it is a pretty straightforward process.