0

I am rendering OSM map tiles onto a web page using HTML canvas drawImage. However where an end user has selected dark mode, I would like to reduce the luminosity of these displayed maps, yet still allow them to make sense to the user.

So far I have had moderate success, as follows:

  1. First plotting the map tile using drawImage
  2. setting globalCompositeOperation to "difference"
  3. over plotting the map tile with a white rectangle of the same size
  4. setting globalCompositeOperation back to "source-over"

But this simple colour inversion is not perhaps the best solution. Does anyone have any other suggestions.

Steve Brooker
  • 1,043
  • 11
  • 28

3 Answers3

0

You could switch to a different tile server with a different map style. Check for example "CartoDB.DarkMatter" from Leaflet Provider Demo or MapBox Light & Dark.

scai
  • 20,297
  • 4
  • 56
  • 72
  • You don't necessarily need to use Leaflet or MapBox. These are just URLs to tile servers with dark styles in case your code supports TMS or Mapbox GL. – scai Dec 02 '19 at 14:26
  • This depends on the specific map library you are referring to. Vector tile based rendering engines don't have to invert any colour, they use a completely different style for dark themed maps. Raster tile based rendering engines instead have to either switch to a different tile set or perform some magic conversions which will necessarily lead to a reduced user experience. – scai Dec 03 '19 at 14:11
0

I have found a pretty good solution to this and it is as follows:

  1. First set the canvas context filter to "hue-rotate(180deg)"
  2. Then plot the map tile on the canvas using drawImage
  3. Then set the canvas context filter to "none"
  4. The set canvas context globalCompositeOperation to "difference"
  5. Then over plot the map tile with a white rectangle of the same size
  6. Finally set canvas context globalCompositeOperation back to "source-over"
Steve Brooker
  • 1,043
  • 11
  • 28
0

Maybe someone will still find this useful, it's some code i'm using for this purpose in my tar1090 project. Negative and positive contrast are probably clear and dim is basically just a brightness modification with inverted sign.

toggle function:

function setDim(layer, state) {
    if (state) {
        layer.dimKey = layer.on('postrender', dim);
    } else {
        ol.Observable.unByKey(layer.dimKey);
    }
    OLMap.render();
}

postrender function:

function dim(evt) {
    const dim = mapDimPercentage * (1 + 0.25 * toggles['darkerColors'].state);
    const contrast = mapContrastPercentage * (1 + 0.1 * toggles['darkerColors'].state);
    if (dim > 0.0001) {
        evt.context.globalCompositeOperation = 'multiply';
        evt.context.fillStyle = 'rgba(0,0,0,'+dim+')';
        evt.context.fillRect(0, 0, evt.context.canvas.width, evt.context.canvas.height);
    } else if (dim < -0.0001) {
        evt.context.globalCompositeOperation = 'screen';
        console.log(evt.context.globalCompositeOperation);
        evt.context.fillStyle = 'rgba(255, 255, 255,'+(-dim)+')';
        evt.context.fillRect(0, 0, evt.context.canvas.width, evt.context.canvas.height);
    }
    if (contrast > 0.0001) {
        evt.context.globalCompositeOperation = 'overlay';
        evt.context.fillStyle = 'rgba(0,0,0,'+contrast+')';
        evt.context.fillRect(0, 0, evt.context.canvas.width, evt.context.canvas.height);
    } else if (contrast < -0.0001) {
        evt.context.globalCompositeOperation = 'overlay';
        evt.context.fillStyle = 'rgba(255, 255, 255,'+ (-contrast)+')';
        evt.context.fillRect(0, 0, evt.context.canvas.width, evt.context.canvas.height);
    }
    evt.context.globalCompositeOperation = 'source-over';
}

toggle function when using LayerSwitcher:

function setDimLayerSwitcher(state) {
    if (!state) {
        ol.control.LayerSwitcher.forEachRecursive(layers_group, function(lyr) {
                if (lyr.get('type') != 'base')
                return;
                ol.Observable.unByKey(lyr.dimKey);
                });
    } else {
        ol.control.LayerSwitcher.forEachRecursive(layers_group, function(lyr) {
                if (lyr.get('type') != 'base')
                return;
                lyr.dimKey = lyr.on('postrender', dim);
                });
    }
    OLMap.render();
}