This example uses OpenLayers v10.9.0. The latest is v.
Edit

#### Region Growing

raster 6 regiongrowing 1 maptiler 26

#####

Threshold:

Grow a region from a seed pixel

Click a region on the map. The computed region will be red.

This example uses a `ol/source/Raster` to generate data based on another source. The raster source accepts any number of input sources (tile or image based) and runs a pipeline of operations on the input data. The return from the final operation is used as the data for the output source.

In this case, a single tiled source of imagery data is used as input. The region is calculated in a single "image" operation using the "seed" pixel provided by the user clicking on the map. The "threshold" value determines whether a given contiguous pixel belongs to the "region" - the difference between a candidate pixel's RGB values and the seed values must be below the threshold.

This example also shows how an additional function can be made available to the operation.

##### main.js

    import Map from 'ol/Map.js';
    import View from 'ol/View.js';
    import ImageLayer from 'ol/layer/Image.js';
    import TileLayer from 'ol/layer/Tile.js';
    import {fromLonLat} from 'ol/proj.js';
    import ImageTile from 'ol/source/ImageTile.js';
    import RasterSource from 'ol/source/Raster.js';

    function growRegion(inputs, data) {
      const image = inputs[0];
      let seed = data.pixel;
      const delta = parseInt(data.delta);
      if (!seed) {
        return image;
      }

      seed = seed.map(Math.round);
      const width = image.width;
      const height = image.height;
      const inputData = image.data;
      const outputData = new Uint8ClampedArray(inputData);
      const seedIdx = (seed[1] * width + seed[0]) * 4;
      const seedR = inputData[seedIdx];
      const seedG = inputData[seedIdx + 1];
      const seedB = inputData[seedIdx + 2];
      let edge = [seed];
      while (edge.length) {
        const newedge = [];
        for (let i = 0, ii = edge.length; i < ii; i++) {
          // As noted in the Raster source constructor, this function is provided
          // using the `lib` option. Other functions will NOT be visible unless
          // provided using the `lib` option.
          const next = next4Edges(edge[i]);
          for (let j = 0, jj = next.length; j < jj; j++) {
            const s = next[j][0];
            const t = next[j][1];
            if (s >= 0 && s < width && t >= 0 && t < height) {
              const ci = (t * width + s) * 4;
              const cr = inputData[ci];
              const cg = inputData[ci + 1];
              const cb = inputData[ci + 2];
              const ca = inputData[ci + 3];
              // if alpha is zero, carry on
              if (ca === 0) {
                continue;
              }
              if (
                Math.abs(seedR - cr) < delta &&
                Math.abs(seedG - cg) < delta &&
                Math.abs(seedB - cb) < delta
              ) {
                outputData[ci] = 255;
                outputData[ci + 1] = 0;
                outputData[ci + 2] = 0;
                outputData[ci + 3] = 255;
                newedge.push([s, t]);
              }
              // mark as visited
              inputData[ci + 3] = 0;
            }
          }
        }
        edge = newedge;
      }
      return {data: outputData, width: width, height: height};
    }

    function next4Edges(edge) {
      const x = edge[0];
      const y = edge[1];
      return [
        [x + 1, y],
        [x - 1, y],
        [x, y + 1],
        [x, y - 1],
      ];
    }

    const key = 'Get your own API key at https://www.maptiler.com/cloud/';
    const attributions =
      '<a href="https://www.maptiler.com/copyright/" target="_blank">&copy; MapTiler</a> ' +
      '<a href="https://www.openstreetmap.org/copyright" target="_blank">&copy; OpenStreetMap contributors</a>';

    const imagery = new TileLayer({
      source: new ImageTile({
        attributions: attributions,
        url: 'https://api.maptiler.com/maps/satellite/{z}/{x}/{y}.jpg?key=' + key,
        tileSize: 512,
        maxZoom: 20,
      }),
    });

    const raster = new RasterSource({
      sources: [imagery.getSource()],
      operationType: 'image',
      operation: growRegion,
      // Functions in the `lib` object will be available to the operation run in
      // the web worker.
      lib: {
        next4Edges: next4Edges,
      },
    });

    const rasterImage = new ImageLayer({
      opacity: 0.7,
      source: raster,
    });

    const map = new Map({
      layers: [imagery, rasterImage],
      target: 'map',
      view: new View({
        center: fromLonLat([-119.07, 47.65]),
        zoom: 11,
      }),
    });

    let coordinate;

    map.on('click', function (event) {
      coordinate = event.coordinate;
      raster.changed();
    });

    const thresholdControl = document.getElementById('threshold');

    raster.on('beforeoperations', function (event) {
      // the event.data object will be passed to operations
      const data = event.data;
      data.delta = thresholdControl.value;
      if (coordinate) {
        data.pixel = map.getPixelFromCoordinate(coordinate);
      }
    });

    function updateControlValue() {
      document.getElementById('threshold-value').innerText = thresholdControl.value;
    }
    updateControlValue();

    thresholdControl.addEventListener('input', function () {
      updateControlValue();
      raster.changed();
    });

##### index.html

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8">
        <title>Region Growing</title>
        <link rel="stylesheet" href="node_modules/ol/ol.css">
        <style>
          .map {
            width: 100%;
            height: 400px;
          }
          .map {
            cursor: pointer;
          }
          #threshold {
            margin: 0 0.6em;
          }
        </style>
      </head>
      <body>
        <div id="map" class="map"></div>
        <div>
          <label class="input-group">
            Threshold:
            <input id="threshold" type="range" min="1" max="50" value="20">
            <span id="threshold-value"></span>
          </label>
        </div>

        <script type="module" src="main.js"></script>
      </body>
    </html>

##### package.json

    {
      "name": "region-growing",
      "dependencies": {
        "ol": "10.9.0"
      },
      "devDependencies": {
        "vite": "^3.2.3"
      },
      "scripts": {
        "start": "vite",
        "build": "vite build"
      }
    }