I get elegant transparent hillshades via a combination gdal and convert. Compare to grey-based hillshade commonly used, such transparent hillshades are very cool because they can be placed between the map background and other upper layers (roads, buiding) to provide a 3D feels whatever the background's type and color.

How it works

The trick: Starting with a grey scale hillshade produced by gdal hillshade, the trick is to take the grey channel's values, invert each value, and flow this result into a new opacity channel. Black pixel [0,0,0] become [0,0,0,255] (opacity=255), grey pixel [120,120,120] get lower becoming [120,120,120,135] (opacity=135, aka 255-120), white pixel [255,255,255] become transparent [255,255,255,0] (opacity=0, aka 255-255) and so on. Hills' shades are opaque and black, plains progressively become (white) transparent. Conceptually, the pixel's band equation is something such :

alpha_value([0])    = 255-0 = 255;   // black pixels get full opacity
alpha_value([Grey]) = 255-Grey;
// grey pixels get 255-GreyValue opacity.
alpha_value([255])  = 255-255 = 0  // white pixels get null opacity !

For a laid back video on this approach, explained by a Photoshop designer, see Adding Shaded Relief in Photoshop (16mins).


Taking an ETOPO or SRTM derived grey-based hillshade (gdaldem hillshade crop_xl.tmp.tif shadedrelief.tmp.tif -s 111120 -z 5 -az 315 -alt 60 -compute_edges, file available to download here) as input...

...How to do the trick cited upper via gdal or an other non GIS-destructive way on such .tif files ?

Note that I wish to keep GIS properties (geolocalisation).

enter image description here enter image description here

  • 1
    Depending on how the referencing data is stored in the tiff it may be as easy as renaming the world file associated with the tiff. – Steve Apr 29 '15 at 19:18
  • @Steve: it's for .tif files generated from raster such SRTM or ETOPO after gdaldem hillshade etopo_crop.tif shadedrelief.tmp.tif -s 111120 -z 5 -az 315 -alt 60 -compute_edges – Hugolpz Apr 30 '15 at 8:58
  • I think gdal_tranlate with a combination of -b band, -mask band, -expand gray|rgb|rgba together witht the formula opacity=-(px_grey_value)+255 may be the way to go, but I never touched bands and I'am still quite clueless. – Hugolpz Apr 30 '15 at 10:26
  • added grey-based hillshade to download – Hugolpz Apr 30 '15 at 10:53
  • have you tried gdal_edit.py to rewrite the coordinate, cell size and reference system ? – radouxju Apr 30 '15 at 14:14
up vote 39 down vote accepted


Each set of 3 images below should be read such as "grey (band) + opacity (band) = transparent result". You can test these processes within minutes via the associated github hosted makefile. Process #3 is the one which I recommend, with a threshold between 170 (keeps strong shadows) and 220 (keeps all shadows). Process 3 provides the strongest shadows and avoid greying-whitening effect. Adapt the resulting layer's overall opacity as needed. The equations in --calc="<equation>" may be improved as needed as well, using gdal_calc.

For a laid back video on this approach, explained by a Photoshop designer, see Adding Shaded Relief in Photoshop (16mins).


gdaldem hillshade produces a one band grey scale file with pixels values range=[1-255], aka from the darkest shadows to the most enlighten pixel. For flat areas, px=221 (#DDDDDD). NoDataValue pixels get default nodatavalue 0, also, the darkest black in input and in output is and should be 1. With no opacity band defined, opacity is 100%.

gdaldem hillshade input.tif hillshade.tmp.tif -s 111120 -z 5 -az 315 -alt 60 -compute_edges


We want to define and control a 2nd opacity band !


We want one greyscale band -b 1, it's the hillshade. Out of gdal, it's a grey band with continuous range such as px=[1-255]. We can crop out non-relevant areas (#2), or blacken it to px=1 and rely on the opacity band (#3).

We want one opacity band -b 2, generally the invert of the hillshade or a related function of that. We can crop out non-relevant areas (#2). It must be a continuous range of opacities such px=[1-255], otherwhise there is no elegance.

gdal_calc can be use to both do math on pixels from input files A,B,C... and check boolean values such as A<220, which returns 1 (true) or 0 (false). This allow conditional calculus. If the condition is false, the related part of the equation is nullified.

1. Grey hillshade made transparent

The following provides a very good two-bands results with the standard gdal hillshade greys and whiter areas made increasingly transparent :

# hillshade px=A, opacity is its invert: px=255-A
gdal_calc.py -A ./hillshade.tmp.tif  --outfile=./opacity.tif --calc="255-A"
# assigns to relevant bands -b 1 and -b 2
gdalbuildvrt -separate ./final.vrt ./hillshade.tmp.tif ./opacity.tif

hillshade.tmp.tif #1, opacity.tif #1, final.tif

2. Optimization via pseudo-crop (-b 1 & -b 2)

2/3 of the pixels on -b 1 (greyscale) become invisible to bare eyes when the opacity -b 2 is added, yet, these pixels keeps various whiter -b 1 and low opacity -b 2 values. They can be made all white transparent [255,1] pixels, allowing a better compression rate:

# filter the color band, keep greyness of relevant shadows below limit
gdal_calc.py -A ./hillshade.tmp.tif  --outfile=./color_crop.tmp.tif \
    --calc="255*(A>220) +      A*(A<=220)"
# filter the opacity band, keep opacity of relevant shadows below limit
gdal_calc.py -A ./hillshade.tmp.tif  --outfile=./opacity_crop.tmp.tif \
    --calc="  1*(A>220) +(256-A)*(A<=220)"
# gdalbuildvrt -separate ./final.vrt ./color_crop.tmp.tif ./opacity_crop.tmp.tif
# gdal_translate -co COMPRESS=LZW -co ALPHA=YES ./final.vrt ./final_crop.tif

#2, color.tif   (cropped) #2, opacity.tif (cropped) #2, final_crop.tif

3. Further -b 1 optimization (crop + blacken)

Since we have an progressive opacity band -b 2 to rely on, we could make -b 1 pixels either white px=255 via 255*(A>220), or black px=1 via 1*(A>220).

gdal_calc.py -A ./hillshade.tmp.tif  --outfile=./color.tmp.tif \
   --calc="255*(A>220) + 1*(A<=220)"
# gdal_calc.py -A ./hillshade.tmp.tif  --outfile=./opacity_crop.tmp.tif \
#  --calc="  1*(A>220) +(256-A)*(A<=220)".
# gdalbuildvrt -separate ./final.vrt ./color.tmp.tif ./opacity_crop.tif
# gdal_translate -co COMPRESS=LZW -co ALPHA=YES ./final.vrt ./final.tif

#3, color.tif #2, opacity.tif (cropped) #3, final.tif

This result shows stronger shadows.


Create a transparent hillshade has for immediate objective to remove the plain's former grey areas and associated unwanted but ubiquitous greying-fading effect. The wished byproduct is an increased control over the final visual product. The process described remove most grey and all white pixels. The colorful background plain image will keep its chosen colors when overlayed by the transparent-to-black hillshades, only the shadowed areas will be darkened. Comparison of process #2 (left) and #3 (right) below.

Overview :

Comparison of process #2 (left) and #3 (right), general view.

Zoom, please notice the shadows :

Comparison of process #2 (left) and #3 (right), details view.

Further optimizations

White areas: One may also wish to keep the most enlightened areas to increase the 3D feels. It would literally be the symmetric of this current approach with minor threshold changes, then a merge of both outputs via gdal_calc. The plain would be 100% transparent, the darkest shadows and lightest enlighten areas opaque.

Smoothing: The input hillshade may be pre-smoothed to get a better end result, see Smoothing DEM using GRASS?

Composite hillshade (How to create composite hillshade?).

Bumped hillshade is interesting as well (description)


  • The flat area threshold in gdal hillshade output is px=221 (#DDDDDD = [221,221,221]), marking flat areas. Also, hillshade's px=221 divides the images between in-shadow slopes (A<221) and in-light slopes (A>221) pixels.
  • A processing threshold at px=[170-220] as proven good, it keeps near 100% of the eyes-noticeable shadows, which themselves barely stand for 15-35% of the relief area.
  • Filesize > Compression: final.tif out of #1, #2, #3 is ~1.3MB without compression, then ~0.3-0.16MB after compression, 80% saving !
  • Filesize > cropping: From .326KB in #1, crop color & opacity (#2) get to 310kb, blacken color (#3) get to 160kb. Cropping effect on filesize is between 5~50% reduction with threshold at px=220 and my input.
  • 2
    Encouraging +1 welcome. – Hugolpz May 2 '15 at 20:24
  • 2
    Tutorial finished. My English may be broken, feel free to improve. – Hugolpz May 5 '15 at 12:02
  • 3
    While you did have to answer yourself this thread is an excellent tutorial touching on a lot of questions regarding hillshading. Well done! – Kersten May 6 '15 at 12:11

Another way to get the same result of a non-grey canvas more suitable for combining with other layers is the 'combined' option in gdaldem.

It performs a slope and hill shade and combines the two in one operation. Areas of 0 slope are white. Areas of 90 degree slope are black for the slope shade, with some illumination added by the hillshade layer.

gdaldem hillshade -combined -compute_edges infile outfile.tif

Then use a multiply layer compositing mode to 'drape' this over other layers.

Standard/Combined hillshading

Standard Hillshading

Combined shading multiplied with OSM base layer (opacity around 50%) Combined shading multiplied with OSM base layer

gdal + convert based workflow

There is a gdal + convert solution which gives good visual results. The trouble with this solution is that convert destroys geographic informations which you then have to restore. It increase the number of action to run.

# Basic crop
gdal_translate -projwin 67 35.92 99 5 ../data/noaa/ETOPO1_Ice_g_geotiff.tif crop_xl.tmp.tif
# Grey-based hillshade
gdaldem hillshade crop_xl.tmp.tif shadedrelief.tmp.tif -s 111120 -z 5 -az 315 -alt 60 -compute_edges
# create a transparent hillshade:
convert shadedrelief.tmp.tif -fuzz 7% -fill "#FFFFFF" -opaque "#DDDDDD"  whited.jpg # makes all grey values white to lighten the filesize
convert whited.jpg -alpha copy -channel alpha -negate +channel trans.png # <=== TRICK HERE.
# Restore georeferencing & reproject            
gdal_translate -a_ullr 67 35.92 99 5 trans.png trans.tmp.gis.tif
gdalwarp -s_srs EPSG:4326 -t_srs ESPG:3857 ./trans.tmp.gis.tif ./trans_reproj.tmp.gis.tif
# Compress from 11MB to 2MB:
gdal_translate -co COMPRESS=LZW -co ALPHA=YES ./trans_reproj.tmp.gis.tif ./trans.gis.tif

For command 4 explanation, see: https://stackoverflow.com/a/23018544/1974961

Your Answer


By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.