Search code examples
javascripthtmlcolorsleaflethtml5-canvas

Recolour all pixels of a specific colour in an image overlay in Leaflet


I have an image approximately 8000x4000 pixels. This image is broken up into blobs of colour.

Here is an example of part of the image to show what it looks like:

an example of a section of image

I'm able to plot the image using leaflet pretty trivially:

var map = L.map('map', {
    crs: L.CRS.Simple,
    minZoom: -2
});

var bounds = [[0,0], [3616,8192]];

var provinces = L.imageOverlay(
    'myimage.png', 
    bounds, 
    {opacity: 0.7}
).addTo(map);

I have a translation for recolouring the image, and I want to use it to transform all pixels of a specific colour to pixels of a different specific colour. This is the problem I need help with.

var colour_mapping = {'#4287f5':'#42f5ef', '#a3911c':'#de3510', ...}

I've seen answers on stack overflow detailing how to change specific pixels of a canvas, but I don't know how best to achieve the effect working within Leaflet.

i.e. How would I replace all pixels of a specific RGB in a PNG with another RGB in javascript?


Solution

  • As you already figured yourself to be able to manipulate an image 'on the fly' you have to paint it onto a html <canvas> element first. On the other hand Leaflet's imageOverlay() method expects an URL to an actual image - so the manipulated canvas alone won't bring you too far.

    There's hope though. The canvas object offers a method called toDataURL() which returns something you can feed into imageOverlay().

    Let's break-down what you'll have to do:

    1. Create an empty Image and use it to load your map
    2. If loading of the image finished, create a canvas the size of your image
    3. Draw the image to the canvas
    4. Loop over the canvas' image data obtained via ctx.getImageData(). This will return a large array of red, green, blue and alpha values for each pixel in the canvas. As your colour_mapping object consists of hex values e.g. #4dc8c8, we first need to convert the rgb values to hex to be able to look up the object for a match. If we found a match, get the replacement color and convert the hex value to rgb to ultimately change the color.
    5. Draw the manipulated image data onto the canvas.
    6. Get the data URL using toDataURL() and finally call imageOverlay().

    Here's an example showcasing the replacement of two colors by white:

    var map = L.map('map').setView([0.5, 0.5], 9);
    
    
    var imageUrl = './js/UAZyt.png';
    imageUrl = "https://corsproxy.io/?https://i.sstatic.net/UAZyt.png"
    let image = new Image();
    image.crossOrigin = "anonymous"
    image.onload = (e) => {
      imageBounds = [
        [0, 0],
        [1, 1]
      ];
    
      let canvas = document.createElement("canvas");
      let ctx = canvas.getContext("2d");
      canvas.width = e.target.naturalWidth;
      canvas.height = e.target.naturalHeight;
      ctx.drawImage(e.target, 0, 0);
      let colour_mapping = {
        '#4dc8c8': '#ffffff',
        '#be8eff': '#ffffff'
      };
    
      let imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
      let r, g, b, hex, hex2;
      for (let a = 0; a < imageData.data.length; a += 4) {
        r = imageData.data[a];
        g = imageData.data[a + 1];
        b = imageData.data[a + 2];
        hex = "#" + ((r << 16) | (g << 8) | b).toString(16);
        if (colour_mapping[hex]) {
          hex2 = colour_mapping[hex].match(/[0-9a-f]{2}/g);
          imageData.data[a] = parseInt(hex2[0], 16);
          imageData.data[a + 1] = parseInt(hex2[1], 16);
          imageData.data[a + 2] = parseInt(hex2[2], 16);
        }
      }
      ctx.putImageData(imageData, 0, 0);
      L.imageOverlay(canvas.toDataURL(), imageBounds).addTo(map);
    }
    image.src = imageUrl;
    #map {
      height: 360px;
    }
    <script src="https://unpkg.com/[email protected]/dist/leaflet.js"></script>
    <div id="map"></div>