Image to Use:
Javascript Code:
function drawBorder4conn(img, imgd)
{
var zerocanx = Math.floor(ctx.viewportWidth / 2);
var zerocany = Math.floor(ctx.viewportHeight / 2);
altImg = ctx.getImageData(0,0,ctx.viewportWidth,ctx.viewportHeight);
var altpix = altImg.data;
var pix = imgd.data;
var rowSize = ctx.viewportWidth;
var colSize = ctx.viewportHeight;
var currow, curcol, state;
var firstrow = 0, firstcol = 0;
var breakflag = 0;
//find first pixel
for (var row = 0; row < img.height; row++)
{
for (var col = 0; col < img.width; col++)
{
if(pix[4*(row*img.width+col)] > 1)
{
firstrow = row;
firstcol = col;
breakflag = 1;
};
if (breakflag) {break};
};
if (breakflag) {break};
};
h = [[0,1],[-1,0],[0,-1],[1,0]];
state = 3;
currow = firstrow;
curcol = firstcol;
var t = 2;
// console.log("Hello World 4 connected");
var counter = 0;
while(true)
{
for (var i = 0; i < 4; i++)
{
//Need to check for out of bounds
t = (state + i + 3) % 4;
if (pix[4*((currow+h[t][0])*img.width+(curcol + h[t][1])) + 1] > 1)
{
pix[4*((currow+h[t][0])*img.width+(curcol + h[t][1]))] = 0;
pix[4*((currow+h[t][0])*img.width+(curcol + h[t][1])) + 1] = 255;
pix[4*((currow+h[t][0])*img.width+(curcol + h[t][1])) + 2] = 0;
pix[4*((currow+h[t][0])*img.width+(curcol + h[t][1])) + 3] = 255;
state = t;
currow += h[t][0];
curcol += h[t][1];
break;
};
};
if (currow == firstrow && curcol == firstcol)
{
// console.log("I end");
break;
};
};
//Copy pix data to canvas
for (var row = 0; row < img.height; row++) {
for (var col = 0; col < img.width; col++) {
altpix[4*(row*rowSize + col) ] = pix[4*(row * img.width + col)];
altpix[4*(row*rowSize + col)+1 ] = pix[4*(row * img.width + col) + 1];
altpix[4*(row*rowSize + col)+2 ] = pix[4*(row * img.width + col) + 2];
altpix[4*(row*rowSize + col)+3 ] = pix[4*(row * img.width + col) + 3];
};
};
ctx.putImageData(altImg,0,0);
};
function drawBorder8conn(img, imgd)
{
var zerocanx = Math.floor(ctx.viewportWidth / 2);
var zerocany = Math.floor(ctx.viewportHeight / 2);
altImg = ctx.getImageData(0,0,ctx.viewportWidth,ctx.viewportHeight);
var altpix = altImg.data;
var pix = imgd.data;
var rowSize = ctx.viewportWidth;
var colSize = ctx.viewportHeight;
var currow, curcol, state;
var firstrow = 0, firstcol = 0;
var breakflag = 0;
//find first pixel
for (var row = 0; row < img.height; row++)
{
for (var col = 0; col < img.width; col++)
{
if(pix[4*(row*img.width+col) +1] > 1)
{
firstrow = row;
firstcol = col;
breakflag = 1;
};
if (breakflag) {break};
};
if (breakflag) {break};
};
h = [[0,1],[-1,1],[-1,0],[-1,-1],[0,-1],[1,-1],[1,0],[1,1]];
state = 6;
currow = firstrow;
curcol = firstcol;
var t = 2;
var counter = 0;
while(true)
{
for (var i = 0; i < 8; i++)
{
//Need to check for out of bounds
t = (state + i + 6) % 8;
if (pix[4*((currow+h[t][0])*img.width+(curcol + h[t][1])) + 1] > 1)
{
//console.log("pixel is on");
pix[4*((currow+h[t][0])*img.width+(curcol + h[t][1]))] = 255;
pix[4*((currow+h[t][0])*img.width+(curcol + h[t][1])) + 1] = 0;
pix[4*((currow+h[t][0])*img.width+(curcol + h[t][1])) + 2] = 255;
pix[4*((currow+h[t][0])*img.width+(curcol + h[t][1])) + 3] = 255;
state = t;
currow += h[t][0];
curcol += h[t][1];
break;
};
};
if (currow == firstrow && curcol == firstcol)
{
break;
};
};
//Copy pix data to canvas
for (var row = 0; row < img.height; row++) {
for (var col = 0; col < img.width; col++) {
altpix[4*(row*rowSize + col) ] = pix[4*(row * img.width + col)];
altpix[4*(row*rowSize + col)+1 ] = pix[4*(row * img.width + col) + 1];
altpix[4*(row*rowSize + col)+2 ] = pix[4*(row * img.width + col) + 2];
altpix[4*(row*rowSize + col)+3 ] = pix[4*(row * img.width + col) + 3];
};
};
ctx.putImageData(altImg,0,0);
};
This shows a degenerate case for the naive 4 connectivity border tracking algorithm that merely stops when it encounters the first pixel a second time. The other example for when 4 connectivity or 8 connectivity can fail is if one starts at a different first pixel value.
To preserve holes within regions one could keep track of when the color changes in magnitude and determine whether or not one is inside of the border or outside of the border. Thus maintaining the hole.
Image to use for region labeling
This code labels the regions based on 4 connectivity. There are 11 entries in the equivalence table to map each region to another.
function findRegions(img, imgd)
{
var zerocanx = Math.floor(ctx.viewportWidth / 2);
var zerocany = Math.floor(ctx.viewportHeight / 2);
altImg = ctx.getImageData(0,0,ctx.viewportWidth,ctx.viewportHeight);
var altpix = altImg.data;
var pix = imgd.data;
var rowSize = ctx.viewportWidth;
var colSize = ctx.viewportHeight;
var curregion = 0, tempregion = 0;
var h = [[-1,1],[-1,0],[-1,-1],[0,-1]];
var regionMap = [];
var regions = [];
var prevreg = [undefined,undefined,undefined,undefined], tempregion = [];
var updateFlag = true;
var j, currloc;
//regionMap[0] = 0;
console.log(regionMap.length, null, (null == 0),(regionMap[1] == undefined), regionMap[2]);
//find regions
for (var row = 0; row < img.height; row++)
{
for (var col = 0; col < img.width; col++)
{
if(pix[4*((row)*img.width+(col))] > 1)
{
curregion = regionMap.length;
// console.log(curregion);
for (var i = 0; i < h.length; i++)
{
//check if surrounding pixels belong to
//an already labeled region
if (row+h[i][0] >= 0 || col+h[i][1] >= 0 || row+h[i][0] < img.width || col+h[i][1] < img.height)
{
//console.log(regions[4*((row+h[i][0])*img.width+(col+h[i][1]))]);
if (regions[4*((row+h[i][0])*img.width+(col+h[i][1]))] != undefined)
{
updateFlag = false;
prevreg[i] = regions[4*((row+h[i][0])*img.width+(col+h[i][1]))];
//console.log(prevreg,curregion);
};
};
};
if (updateFlag)
{
regionMap[curregion] = curregion;
regions[4*((row)*img.width+(col))] = curregion;
} else {
for (var i = 0; i < prevreg.length; i++)
{
if (prevreg[i] != undefined) {
tempregion[tempregion.length] = prevreg[i];
};
};
for (var i = 0; i < tempregion.length - 1; i++)
{
j = i + 1;
if (tempregion[i] == tempregion[j])
{
//do nothing
} else if (tempregion[i] > tempregion[j])
{
regionMap[tempregion[i]] = tempregion[j];
} else {
regionMap[tempregion[j]] = tempregion[i];
};
};
regions[4*((row)*img.width+(col))] = tempregion[0];
};
updateFlag = true;
prevreg = [undefined,undefined,undefined,undefined];
tempregion = [];
};
};
};
console.log("for loop done",regionMap.length,curregion,regionMap[11]);
// console.log(curregion);
for (var i = 0; i < regionMap.length; i++) {
console.log(i,regionMap[i]);
};
var colors = [[255,0,0],[0,255,0],[0,0,255],[100,100,100],[0,50,50],[200,100,50],[50,200,50],[150,50,200],[100,200,100],[55,0,0],[100,255,0],[100,0,255],[255,255,255]];
var colormap = [];
colormap[0] = colors[0];
//Assign colors
for (var i = 0; i <= regionMap.length; i++) {
if (regionMap[i] != i) {
colormap[i] = colormap[regionMap[i]];
} else{
colormap[i] = colors[i];
};
};
var regionMapTemp = [];
curregion = 0;
//find regions
for (var row = 0; row < img.height; row++)
{
for (var col = 0; col < img.width; col++)
{
if(pix[4*((row)*img.width+(col))] > 1)
{
curregion = regionMapTemp.length;
// console.log(curregion);
for (var i = 0; i < h.length; i++)
{
//check if surrounding pixels belong to
//an already labeled region
if (row+h[i][0] >= 0 || col+h[i][1] >= 0 || row+h[i][0] < img.width || col+h[i][1] < img.height)
{
//console.log(regions[4*((row+h[i][0])*img.width+(col+h[i][1]))]);
if (regions[4*((row+h[i][0])*img.width+(col+h[i][1]))] != undefined)
{
updateFlag = false;
prevreg[i] = regions[4*((row+h[i][0])*img.width+(col+h[i][1]))];
//console.log(prevreg,curregion);
};
};
};
if (updateFlag)
{
regionMapTemp[curregion] = curregion;
//curregion++;
regions[4*((row)*img.width+(col))] = curregion;
} else {
for (var i = 0; i < prevreg.length; i++)
{
if (prevreg[i] != undefined) {
tempregion[tempregion.length] = prevreg[i];
};
};
for (var i = 0; i < tempregion.length - 1; i++)
{
j = i + 1;
if (tempregion[i] == tempregion[j])
{
//do nothing
} else if (tempregion[i] > tempregion[j])
{
regionMapTemp[tempregion[i]] = tempregion[j];
} else {
regionMapTemp[tempregion[j]] = tempregion[i];
};
};
regions[4*((row)*img.width+(col))] = tempregion[0];
};
updateFlag = true;
prevreg = [undefined,undefined,undefined,undefined];
tempregion = [];
// console.log(colormap[0],colormap[0][0],curregion -1,colormap[regionMap[curregion-1]]);
currloc = 4*((row)*img.width+(col));
//console.log("curr",curregion,"rm len",regionMap.length,"rm t len",regionMapTemp.length);
pix[4*((row)*img.width+(col)) + 0] = colormap[regionMap[regions[currloc]]][0];
pix[4*((row)*img.width+(col)) + 1] = colormap[regionMap[regions[currloc]]][1];
pix[4*((row)*img.width+(col)) + 2] = colormap[regionMap[regions[currloc]]][2];
pix[4*((row)*img.width+(col)) + 3] = 255;
//curregion += 1;
};
};
};
//Copy pix data to canvas
for (var row = 0; row < img.height; row++) {
for (var col = 0; col < img.width; col++) {
altpix[4*(row*rowSize + col) ] = pix[4*(row * img.width + col)];
altpix[4*(row*rowSize + col)+1 ] = pix[4*(row * img.width + col) + 1];
altpix[4*(row*rowSize + col)+2 ] = pix[4*(row * img.width + col) + 2];
altpix[4*(row*rowSize + col)+3 ] = pix[4*(row * img.width + col) + 3];
};
};
ctx.putImageData(altImg,0,0);
};