Blending Hillshade with Landcover Without Muddy Colour

Composite shaded relief underneath the landcover layer with a multiply blend rather than laying a grey hillshade over it at partial alpha, because alpha compositing mixes grey into every class and breaks the correspondence between the map and its legend.

Core Algorithm and Workflow

Three compositing strategies are commonly used, and they behave very differently:

Alpha overlay. The shade is drawn over the map at some opacity. Mathematically this interpolates each pixel toward grey by the opacity fraction, so every class loses saturation, the lightest classes darken and the darkest lighten, and the palette compresses toward the middle. Legend swatches drawn without the overlay then match nothing on the map.

Multiply, underneath. Each channel of the thematic colour is scaled by the shade value. Because the scale factor is at most one, multiplication only ever darkens, and it preserves the ratios between channels — which is to say it preserves hue. A class can become a darker version of itself but cannot become a different colour, and cannot become lighter than its legend swatch.

Soft light, underneath. Values above the neutral midpoint lighten and values below darken, so slopes facing the light brighten. This gives more three-dimensional relief but allows a class to exceed its legend swatch’s lightness, which is acceptable on imagery and questionable on a thematic map.

The workflow is the same in each case: resample the shade to the thematic grid, apply the blend with a single strength parameter, and re-validate contrast on the result.

What each blend does to a single landcover class One landcover green plotted before and after compositing. Under a 40 per cent grey alpha overlay the class loses 38 per cent of its chroma and shifts toward neutral. Under multiply it drops in lightness while chroma falls only 6 per cent. Under soft light it moves in both directions in lightness with chroma essentially unchanged. lightness chroma → source class alpha overlay — chroma −38% multiply — chroma −6% soft light — either direction Only the alpha overlay moves the class sideways, and sideways is where the legend match is lost.
Vertical movement is relief. Horizontal movement is a change to what the colour means, and only one of the three strategies produces it.

Production-Ready Python Implementation

import numpy as np


def composite_relief(thematic_rgb: np.ndarray, shade: np.ndarray,
                     mode: str = "multiply", strength: float = 0.5) -> np.ndarray:
    """Composite shaded relief under a thematic layer.

    thematic_rgb : float array (h, w, 3) in 0..1, already on the shade's grid
    shade        : float array (h, w) in 0..1; 0.5 is neutral illumination
    strength     : 0 leaves the colour untouched, 1 applies the shade fully
    """
    if thematic_rgb.ndim != 3 or thematic_rgb.shape[2] != 3:
        raise ValueError("thematic_rgb must be (h, w, 3)")
    if thematic_rgb.shape[:2] != shade.shape:
        raise ValueError("resample the thematic layer onto the shade grid first")
    if not 0.0 <= strength <= 1.0:
        raise ValueError("strength must lie in [0, 1]")

    s = shade[..., None]

    if mode == "multiply":
        # Only ever darkens: no class can exceed its legend swatch.
        factor = 1.0 - strength * (1.0 - s)
        out = thematic_rgb * factor
    elif mode == "soft_light":
        # Pegged at 0.5: darkens below, lightens above.
        light = 1.0 - (1.0 - thematic_rgb) * (1.0 - (2 * s - 1.0))
        dark = thematic_rgb * (2 * s)
        blended = np.where(s > 0.5, light, dark)
        out = thematic_rgb + strength * (blended - thematic_rgb)
    else:
        raise ValueError(f"unknown blend mode: {mode!r}")

    return np.clip(out, 0.0, 1.0)

The function refuses to resample, for the same reason given under Terrain and Hillshade Automation: choosing between nearest-neighbour and bilinear for a categorical raster is a cartographic decision, and bilinear on a landcover layer invents classes that appear in no legend.

A single strength parameter is deliberately the only tuning surface. Per-class opacity overrides are the obvious next feature and they are a trap: they make the relief inconsistent across the map, so a slope reads as steeper in forest than in grassland purely because of a style setting.

Performance Tuning and Cartographic Best Practices

  • Blend in linear light for large tonal ranges. Multiplying gamma-encoded values darkens more than the physical model implies. For print products where the tonal range matters, linearise, blend, then re-encode; for screen tiles the difference is rarely worth the cost.
  • Keep strength between 0.35 and 0.65. Below that the relief stops reading as terrain; above it the darkest shaded areas begin failing label contrast. That the usable band is narrow is a feature, not a limitation.
  • Composite once per zoom band. The composite depends only on the palette, the shade and the strength, none of which vary per tile, so it belongs in the build rather than in the render path.
  • Protect the lightest class. After multiplication, check that the palette’s lightest class is still distinguishable from paper white in the brightest illuminated areas. If it is not, lighten the class rather than reducing the strength globally.
  • Watch the water. Water bodies are flat, so shading contributes nothing there but still darkens them. Mask water out of the composite; otherwise lakes acquire a shading pattern from the DEM’s interpolation across their surface, which is entirely an artefact.
The strength band where every class still passes contrast Contrast ratio plotted against blend strength from zero to one for two classes. The darkest class starts at 5.1 and falls below the 4.5 threshold at a strength of about 0.68. The lightest class starts at 12.4 and remains well above threshold throughout, but its separation from paper white falls below the 3 to 1 graphical threshold below a strength of 0.2. The usable band between 0.3 and 0.65 is shaded. 2:1 8:1 14:1 blend strength 0.0 0.5 1.0 usable band 4.5:1 label threshold lightest class darkest class The darkest class sets the upper bound and the lightest sets the lower one, so the band is a property of the palette, not a universal figure.
Both ends of the band are contrast constraints, not aesthetic ones, which means the correct strength can be computed for a given palette rather than chosen.

Integration and Next Steps

The composite is the layer the label pass runs against, not the flat palette. Feed it to the contrast validation described in WCAG Contrast Checking for Map Layers, sampling each class in both its brightest and darkest shaded form, and let the result choose the halo strategy. In a themed product the strength belongs in the theme token set described under Theme Inheritance Systems, because a dark theme generally needs a lower strength — the shading has less headroom to darken into.

Where the shading sits in the stack A five-layer stack drawn bottom to top: basemap raster, shaded relief, thematic fill blended with multiply against the shading, boundary lines, and labels with halos. A note marks the boundary between the shading and the thematic fill as the only place the blend applies, and records that labels are measured against the blended result rather than against the flat fill. basemap raster shaded relief thematic fill — multiply against the shade boundaries labels + halos the only blend in the stack labels validate against the blended fill, not the flat one Shading below the fill is what keeps hue intact; validating above it is what keeps labels legible.
Two rules, one stack: the blend applies at exactly one boundary, and the contrast check reads the result of that blend rather than its inputs.

Frequently Asked Questions

Why does an alpha overlay wash out the palette?

Alpha compositing is a linear interpolation toward the overlay colour, so a 40 per cent grey overlay moves every class 40 per cent of the way toward grey. Saturation falls everywhere, the palette compresses toward the middle of the value range, and — critically — the legend swatches, which are drawn without the overlay, no longer match anything on the map. Multiply avoids all of this because it scales each channel by the same factor rather than mixing in a third colour, so the ratios between channels, and therefore the hue, survive.

Is soft light better than multiply?

For imagery basemaps, generally yes: soft light lightens the slopes facing the light rather than merely darkening the others, which produces more convincing relief over a surface that already carries texture. For a thematic palette with a printed legend, multiply is the safer default, because it only ever darkens and therefore no class can be pushed lighter than its legend swatch. Once a class on the map can be lighter than the same class in the key, the reader has to work out which is authoritative.

What strength value should I use?

Start at 0.5 and let the contrast check adjust it. Below roughly 0.3 the relief stops registering as terrain and becomes an unexplained tonal variation; above roughly 0.65 the darkest classes in shadow start falling under the label contrast threshold. The band is narrow enough that a single global parameter is the right interface, and wide enough that a per-palette value can be computed rather than argued about.

Do I need to re-run the palette contrast check after blending?

Yes, and it is the most commonly skipped step. The original validation compared the label colour against the flat class fill. After compositing, that fill is darker wherever terrain is shaded, so the ratio varies across the map and the worst case is the darkest shaded instance of the darkest class. Sample each class at both extremes of shading and validate both; a palette that passes on flat colour can fail on a north-facing slope, and only the composite reveals it.


Back to Terrain and Hillshade Automation