0

      var osmSource = new ol.source.OSM({
        transition: 0
      });
      var rasterSource = new ol.source.Raster({
          sources: [osmSource],
          operation: function(pixels,data) {
                var pixel = pixels[0];

                var r = pixel[0];
                var g = pixel[1];
                var b = pixel[2];

                // CIE luminance for the RGB
                var v = 0.2126 * r + 0.7152 * g + 0.0722 * b;

                pixel[0] = v; // Red
                pixel[1] = v; // Green
                pixel[2] = v; // Blue
                //pixel[3] = 255;

                return pixel;
            }
      });
      var mapLayer = new ol.layer.Image({
          source: rasterSource
      });

      var map = new ol.Map({
        layers: [mapLayer],
        target: 'map',
        view: new ol.View({
          center: [1331819, 7906244],
          zoom: 12
        })
      });
 <link rel="stylesheet" href="https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.3.0/css/ol.css" type="text/css">
<script src="https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.3.0/build/ol.js"></script>
<div id="map" class="map"></div>

I'm trying to manipulate the color of pixels in tiles loaded from a tile source (in this case OSM) and implemented a simple test case with a conversion to a gray scale map, but for me it doesn't really work.

I have read the examples for OpenLayers v5.3 for using the Raster source to perform pixel wise operations.

  var osmSource = new ol.source.OSM();
  var rasterSource = new ol.source.Raster({
      sources: [osmSource],
      operation: function(pixels,data) {
            var pixel = pixels[0];

            var r = pixel[0];
            var g = pixel[1];
            var b = pixel[2];

            // CIE luminance for the RGB
            var v = 0.2126 * r + 0.7152 * g + 0.0722 * b;

            pixel[0] = v; // Red
            pixel[1] = v; // Green
            pixel[2] = v; // Blue
            //pixel[3] = 255;

            return pixel;
        }
  });
  var mapLayer = new ol.layer.Image({
      source: rasterSource
  });

  var map = new ol.Map({
    layers: [mapLayer],
    target: 'map',
    view: new ol.View({
      center: [1331819, 7906244],
      zoom: 12
    })
  });

Yes, I can get gray scale tiles, but sometimes they seem to be an intermediate version or something. Sometimes the loaded tiles are completely white, sometimes something in between white and the final image and when you zoom in, sometimes you just get the resized version of the previous zoom level.

Here is my full example:

https://apertum.se/iairvirodvlp/maptest_grey.htm

ortegren
  • 11
  • 1
  • 3
  • 1
    Can you place you code snippet into built-in "runnable snippet"? Here is an instructions, on how to do that: https://stackoverflow.blog/2014/09/16/introducing-runnable-javascript-css-and-html-code-snippets/ – Maris B. May 01 '19 at 13:18

1 Answers1

1

I cannot see any major problem with your demo, but the raster operation is going to exaggerate the effect of slow loading OSM sources which happens frequently when the OSM tile servers are busy. A much more efficient way of achieving grayscale is to use a global composite operation on the canvas at the postcompose event of a normal tile layer

osmLayer.on('postcompose', function (evt) {
    evt.context.globalCompositeOperation = 'color';
    evt.context.fillStyle = '#888';
    evt.context.fillRect(0, 0, evt.context.canvas.width, evt.context.canvas.height);
    evt.context.globalCompositeOperation = 'source-over';
});

      var osmSource = new ol.source.OSM({
          transition: 0
      });
      var mapLayer = new ol.layer.Tile({
          source: osmSource
      });

      mapLayer.on('postcompose', function (evt) {
          evt.context.globalCompositeOperation = 'color';
          // check browser supports globalCompositeOperation
          if (evt.context.globalCompositeOperation == 'color') {
              evt.context.fillStyle = 'rgba(255,255,255,' + grayInput.value/100 + ')';
              evt.context.fillRect(0, 0, evt.context.canvas.width, evt.context.canvas.height);
          }
          evt.context.globalCompositeOperation = 'overlay';
          // check browser supports globalCompositeOperation
          if (evt.context.globalCompositeOperation == 'overlay') {
              evt.context.fillStyle = 'rgb(' + [background,background,background].toString() + ')';
              evt.context.fillRect(0, 0, evt.context.canvas.width, evt.context.canvas.height);
          }
          evt.context.globalCompositeOperation = 'source-over';
      });

      var intensityInput = document.getElementById('intensity');
      var background = 255 - intensityInput.value;

      intensityInput.onchange = function() {
          background = 255 - intensityInput.value;
          map.render();
      };

      var grayInput = document.getElementById('gray');

      grayInput.onchange = function() {
          map.render();
      };

      var map = new ol.Map({
        layers: [mapLayer],
        target: 'map',
        view: new ol.View({
          center: [1331819, 7906244],
          zoom: 12
        })
      });
html, body, .map {
    margin: 0;
    padding: 0;
    width: 100%;
    height: 100%;
}
.map {
    margin: 0;
    padding: 0;
    width: 100%;
    height: 80%;
}
 <link rel="stylesheet" href="https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.3.0/css/ol.css" type="text/css">
<script src="https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.3.0/build/ol.js"></script>
<div id="map" class="map"></div>
<b>Gray:</b><input id="gray" type="range" min="0" max="100" step="1" value="50">
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<b>Intensity:</b><input id="intensity" type="range" min="0" max="255" step="1" value="128">
Mike
  • 16,042
  • 2
  • 14
  • 30
  • That sounds promising. I will definitely try that. I'm a bit surprised that you didn't see any problems with my example. It doesn't always fail, but when zooming I sometimes don't get the correct tile for all visible tiles. – ortegren May 01 '19 at 15:05
  • I'm seeing some issues in you sample now OSM servers are probably busier – Mike May 01 '19 at 22:02
  • Your solution works, and is faster as well, so I will use that approach instead. I was aiming at having the possibility to set the "level of grayness", which I haven't been completely successful in doing with the globalCompositeOperation, but it is good enough. – ortegren May 02 '19 at 10:31
  • 1
    Use an rgba value as fill style. The rgb values must be equal for grayscale but can be any value, the a value will determine the grayness. You might also want to use an overlay operation to intensify the result. – Mike May 02 '19 at 12:52
  • 1
    That works perfectly. Even though I still think there is something strange going on in the recommended operation of the Raster layer (my initial approach) which might be worth looking into, I would say that my problem is solved by using the globalCompositeOperation with the postcompose event instead. – ortegren May 03 '19 at 07:06
  • The `postcompose` event is only available on WebGL layers. (TileLayer does not inherit from a WebGL layer) You should listen on the `postrender` event instead. – PieterT2000 Jul 06 '23 at 15:19
  • 1
    @PieterT2000 the question used OpenLayers 5, it was `postcompose` then and changed to `postrender` for version 6 – Mike Jul 06 '23 at 16:20