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>
</>
);
};