Reputation: 61
I've got an Uint8ClampedArray from ImageData(ctx.getImageData()). I grayscaled it by 0.21 * R + 0.72 * G + 0.07 * B formula. So now I have a Uint8ClampedArray, where each 4 is a rgba channels. I need to make dilation with that array, then with the result of dilation make an erosion. I was searching over the internet and found many different solutions with python and OpenCV, but nothing with JS. I know that SVG allows to use feMorphology filters with dilation and erosion, but is there something like that in canvas? Or is that possible to use Uint8ClampedArray | ImageData with svg filters? Is it possible to make own algorithm, which will make these morph operations? Thanks
Upvotes: 3
Views: 437
Reputation: 140050
Here's a general way to compose a stack of SVG filters and apply it directly on a canvas:
const DILATION_DISTANCE = 4;
const DILATION_SVG_FILTER_NAME = 'dilation';
const DILATION_SVG = `<svg xmlns="http://www.w3.org/2000/svg">
<filter id="${DILATION_SVG_FILTER_NAME}">
<feMorphology operator="dilate" radius="${DILATION_DISTANCE}" />
</filter>
</svg>`;
const DILATION_SVG_URL = `data:image/svg+xml;base64,${btoa(DILATION_SVG)}`;
const DILATION_CTX_FILTER = `url(${DILATION_SVG_URL}#${DILATION_SVG_FILTER_NAME})`;
// ...
const ctx = canvas.getContext('2d');
ctx.filter = DILATION_CTX_FILTER;
ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
data:
URL to compose the SVG instead of fetching it from elsewhere. You don't necessarily have to base-64 encode it via btoa()
. encodeURIComponent()
would also do.In the OP's particular case, you can include a <feColorMatrix>
filter before <feMorphology>
to turn the image into grayscale first:
<feColorMatrix type="saturate" values="0"/>
Assuming you're starting off with an ImageData
, you'd need to put that on an off-screen canvas first and then the draw this canvas on your target canvas:
const ofsCanvas = new OffscreenCanvas(imageData.width, imageData.height);
const ofsCtx = ofsCanvas.getContext('2d');
ofsCtx.putImageData(imageData, 0, 0);
ctx.filter = DILATION_CTX_FILTER;
ctx.drawImage(ofsCanvas, 0, 0); // You can draw a canvas on a canvas
You can also create the offscreen canvas using a DOM canvas, if browser support is an issue:
const ofsCanvas = document.createElement('canvas');
ofsCanvas.width = imageData.width;
ofsCanvas.height = imageData.height;
When done with all this, grab the dilated image's ImageData with:
const dilatedImageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
Upvotes: 0
Reputation: 61
Well, own algo: Erosion:
function erosionFilter(frame, width, height) {
const erodedFrame = new Uint8ClampedArray(frame.length);
for (let y = 1; y < height - 1; y++) {
for (let x = 1; x < width - 1; x++) {
let min = 255;
for (let j = -1; j <= 1; j++) {
for (let i = -1; i <= 1; i++) {
const value = frame[(y + j) * width + (x + i)];
if (value < min) {
min = value;
}
}
}
erodedFrame[y * width + x] = min;
}
}
return erodedFrame;
}
Dilate:
function dilationFilter(frame, width, height) {
const dilatedFrame = new Uint8ClampedArray(frame.length);
for (let y = 1; y < height - 1; y++) {
for (let x = 1; x < width - 1; x++) {
let max = 0;
for (let j = -1; j <= 1; j++) {
for (let i = -1; i <= 1; i++) {
const value = frame[(y + j) * width + (x + i)];
if (value > max) {
max = value;
}
}
}
dilatedFrame[y * width + x] = max;
}
}
return dilatedFrame;
}
frame - Uint8ClampedArray to filter, returns filtered Uint8ClampedArray
Upvotes: 2