Image Filters

See https://openlayers.org/en/latest/examples/image-filter.html

import React, { useState, useEffect, useCallback } from "react";
import { Map as OlMap } from "ol";
import { fromLonLat } from "ol/proj";
import "ol/ol.css";
import { Map } from "@react-ol/fiber";

export const kernels = {
  none: [0, 0, 0, 0, 1, 0, 0, 0, 0],
  sharpen: [0, -1, 0, -1, 5, -1, 0, -1, 0],
  sharpenless: [0, -1, 0, -1, 10, -1, 0, -1, 0],
  blur: [1, 1, 1, 1, 1, 1, 1, 1, 1],
  shadow: [1, 2, 1, 0, 1, 0, -1, -2, -1],
  emboss: [-2, 1, 0, -1, 1, 1, 0, 1, 2],
  edge: [0, 1, 0, 1, -4, 1, 0, 1, 0],
};

export function normalize(kernel) {
  const len = kernel.length;
  const normal = new Array(len);
  let i;
  let sum = 0;
  for (i = 0; i < len; i += 1) {
    sum += kernel[i];
  }
  if (sum <= 0) {
    normal.normalized = false;
    sum = 1;
  } else {
    normal.normalized = true;
  }
  for (i = 0; i < len; i += 1) {
    normal[i] = kernel[i] / sum;
  }
  return normal;
}

export function convolve(context, kernel) {
  const { width, height } = context.canvas;
  const size = Math.sqrt(kernel.length);
  const half = Math.floor(size / 2);
  const inputData = context.getImageData(0, 0, width, height).data;
  const output = context.createImageData(width, height);
  const outputData = output.data;
  for (let pixelY = 0; pixelY < height; pixelY += 1) {
    const pixelsAbove = pixelY * width;
    for (let pixelX = 0; pixelX < width; pixelX += 1) {
      let r = 0;
      let g = 0;
      let b = 0;
      let a = 0;
      for (let kernelY = 0; kernelY < size; kernelY += 1) {
        for (let kernelX = 0; kernelX < size; kernelX += 1) {
          const weight = kernel[kernelY * size + kernelX];
          const neighborY = Math.min(
            height - 1,
            Math.max(0, pixelY + kernelY - half)
          );
          const neighborX = Math.min(
            width - 1,
            Math.max(0, pixelX + kernelX - half)
          );
          const inputIndex = (neighborY * width + neighborX) * 4;
          r += inputData[inputIndex] * weight;
          g += inputData[inputIndex + 1] * weight;
          b += inputData[inputIndex + 2] * weight;
          a += inputData[inputIndex + 3] * weight;
        }
      }
      const outputIndex = (pixelsAbove + pixelX) * 4;
      outputData[outputIndex] = r;
      outputData[outputIndex + 1] = g;
      outputData[outputIndex + 2] = b;
      outputData[outputIndex + 3] = kernel.normalized ? a : 255;
    }
  }
  context.putImageData(output, 0, 0);
}

export const ExampleImageFilter = () => {
  const [selectedKernel, setSelectedKernel] = useState("sharpen");
  const [map, setMap] = useState(null);
  const onPostrender = useCallback(
    (event) => {
      const normalizedSelectedKernel = normalize(kernels[selectedKernel]);
      convolve(event.context, normalizedSelectedKernel);
      return true;
    },
    [selectedKernel]
  );
  useEffect(() => {
    if (!map) return;
    map.render();
  }, [map, selectedKernel]);
  return (
    <>
      <select
        value={selectedKernel}
        onChange={(e) => setSelectedKernel(e.target.value)}
      >
        <option>none</option>
        <option>sharpen</option>
        <option value="sharpenless">sharpen less</option>
        <option>blur</option>
        <option>shadow</option>
        <option>emboss</option>
        <option value="edge">edge detect</option>
      </select>
      <Map ref={setMap}>
        <olView initialCenter={fromLonLat([-120, 50])} initialZoom={6} />
        <olLayerTile onPostrender={onPostrender} args={{ preload: Infinity }}>
          <olSourceBingMaps
            imagerySet="Aerial"
            _key="AjsxIZS8gG8w-Gck9bKjBdP-7InQI8-UFHPUife_H0bScTfivLu9csMHNE_B0lGP"
          />
        </olLayerTile>
      </Map>
    </>
  );
};