Hillshading using the Alpha Channel of an Image

From OpenStreetMap Wiki
Jump to navigation Jump to search

The usual process of doing hillshading with a slippy map is creating bitmapped tiles that have the hillshading information in them as a greyscale or height-colored image, and also the roads etc. in the same tile.

There is another way to do it though. Suppose we have a "flat" tile that has the roads and everything, but no hillshading. Then, we have another tile for the same area with only the hillshading. The crucial difference is that instead of using the greyscale value for this information, this tile has the hillshading in the alpha channel of a completely black image. So you have an all-black image that is a bit transparent in places.

Now, a web browser loads both the flat and the alpha-hillshading tile, and without needing to set additional transparency on any of those tiles, it multiplies the pixels of those two tiles together. The visual result is the same as when loading a single tile that has it all "baked in".

This approach has the advantage that you can hillshade any flat tiles of different styles, and you can just switch it off if not needed.

Different to the road data, the hillshading information is pretty much static over time (after all, the earth doesn't change much), so the CPU time for calculating the shading just needs to be sent once and can be re-used many times later.

Those alpha-hillshading tiles can be paletted PNGs which have a pretty small file size. The flat tiles on the other hand don't need additional colors (that would be required for hillshading them in place), so they can be color-reduced very well and thus also have a small file size. The downside is that the browser has to load twice as many tiles, but this might be negligible.

Technical Process

  • prepare hillshaded GeoTIFFs and a Mapnik style sheet according to Hillshading with Mapnik
  • render them with Mapnik and tile them (e.g. with generate_tiles.py)
  • invert all tiles (see #Links for scripting)
  • copy the greyscale value to the alpha channel for each tile
  • (convert the RGBA PNGs to paletted PNGs)
  • success!

Example in pure Python/PIL (courtesy Nop):

from PIL import Image as PImage
from PIL import ImageOps

dim = 256
shaded = PImage.open("shade.png")

gray = ImageOps.grayscale(shaded)
neg = ImageOps.invert(gray)

black = PImage.new('RGBA', (dim, dim))
bands = neg.split()

black.save("final.png", "png")

Alternative 100% gdal based process

In order to convert gdaldem hillshade grayscale to transparent levels of black (shade) or white (sunny side) you can use the color-relief feature of gdaldem. It has the benefit of keeping all geodata in the process.


  • a decent DEM
  • gdal
  • some computer

First: compute grayscale hillshade using gdaldem, for example:

gdaldem hillshade your_dem_file hillshade.tif

Create a shade.ramp file defining how grey level should be converted, example:

0 0 0 0 255
32 0 0 0 240
64 0 0 0 180
96 0 0 0 120
128 0 0 0 0
129 255 255 255 0
160 255 255 255 32
224 255 255 255 128
255 255 255 255 192

In the above example, dark pixels (0-128) are converted into 100% black with a decreasing alpha level. Light pixels (129-255) are converted into 100% white with increasing alpha level.

And here is the simple gdaldem color-relief to process your grayscale hillshade file:

gdaldem color-relief hillshade.tif -alpha shade.ramp hillshade-overlay.tif 

See: https://gist.github.com/cquest/8179870


  • http://mapy.cz uses this alpha-hillshading approach
  • Marcin Rudowski discovered how they do it and provided ideas for the technical process
  • the guys in #mapnik offered suggestions and help