image-js
Version:
Image processing and manipulation in JavaScript
107 lines • 3.98 kB
JavaScript
import assert from 'node:assert';
import { getBitSafe, getCoordsFromIndex, } from "./utils/getExternalContourUtils.js";
/**
* Possible directions.
*/
const directions = [
{ dc: -1, dr: 0 }, // left
{ dc: -1, dr: -1 }, // up-left
{ dc: 0, dr: -1 }, // up
{ dc: 1, dr: -1 }, // up-right
{ dc: 1, dr: 0 }, // right
{ dc: 1, dr: 1 }, // down-right
{ dc: 0, dr: 1 }, // down
{ dc: -1, dr: 1 }, // down-left
];
// Lookup table for directions' index.
const directionIndexLookup = new Int8Array(9).fill(-1);
for (let i = 0; i < directions.length; i++) {
const { dc, dr } = directions[i];
directionIndexLookup[(dr + 1) * 3 + (dc + 1)] = i;
}
/**
* Return an array with the coordinates of the pixels that are on the border of the mask using Moore's tracing algorithm.
* The reference is the top-left corner of the ROI.
* @param mask - Mask to process.
* @returns The array of border pixels.
*/
export function getContourMoore(mask) {
let index = 0;
const contour = [];
const visited = new Uint8Array(mask.width * mask.height);
while (mask.getBitByIndex(index) !== 1 && index !== mask.size) {
index++;
}
if (index === mask.size)
return contour;
const [row, col] = getCoordsFromIndex(index, mask.width);
const startingPoint = { column: col, row };
let currentPoint = startingPoint;
visited[index] = 1;
contour.push(currentPoint);
const firstNext = findNextPoint(mask, currentPoint, { column: col - 1, row });
if (!firstNext) {
return contour;
}
let startingBacktrackPoint = firstNext.prevPoint;
let backtrackPoint = firstNext.prevPoint;
currentPoint = firstNext.currPoint;
while (true) {
index = currentPoint.row * mask.width + currentPoint.column;
if (!visited[index]) {
visited[index] = 1;
contour.push(currentPoint);
}
const next = findNextPoint(mask, currentPoint, backtrackPoint);
assert.ok(next, 'Next border point is undefined.');
backtrackPoint = next.prevPoint;
currentPoint = next.currPoint;
if (currentPoint.column === startingPoint.column &&
currentPoint.row === startingPoint.row) {
if (backtrackPoint.column === startingBacktrackPoint.column &&
backtrackPoint.row === startingBacktrackPoint.row) {
break;
}
else {
startingBacktrackPoint = backtrackPoint;
}
}
}
return contour;
}
/**
* Finds next point to trace.
* @param mask - Mask in check.
* @param current - Current border point.
* @param previous - Last non-border point to start search from.
* @returns Object with new border point and last non-border point.
*/
function findNextPoint(mask, current, previous) {
let prevPoint = previous;
// Vector from current back to previous
const backDc = previous.column - current.column;
const backDr = previous.row - current.row;
const startIndex = directionIndexLookup[(backDr + 1) * 3 + (backDc + 1)];
// Scan clockwise starting from that index.
for (let i = 0; i < directions.length; i++) {
const { dc, dr } = directions.at((startIndex + i) % directions.length);
const newCol = current.column + dc;
const newRow = current.row + dr;
if (getBitSafe(mask, newCol, newRow) === 1) {
// Stores found border point and last non-border point that
// was found. Will be used as a starting point in the next
// iteration.
return {
prevPoint,
currPoint: { column: newCol, row: newRow },
};
}
else {
prevPoint = { column: newCol, row: newRow };
}
}
// Is triggered only if the point has no neighbours. So it is undefined
// only if mask is made of one pixel.
return undefined;
}
//# sourceMappingURL=getContourMoore.js.map