Last active
March 8, 2020 17:31
-
-
Save arcollector/155a8c751f65c15872fb to your computer and use it in GitHub Desktop.
Seed scanline filling algorithm in JS
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var download = function( arrayBuffer ) { | |
var $a = document.createElement( 'a' ); | |
$a.setAttribute( 'download', +new Date() + '.bmp' ); | |
$a.setAttribute( 'href', URL.createObjectURL( new Blob( [ arrayBuffer ], { type: 'application/octet-binary' } ) ) ); | |
$a.style.display = 'none'; | |
document.body.appendChild( $a ); | |
$a.click(); | |
document.body.removeChild( $a ); | |
} | |
// ************************** | |
// ************************** | |
var bmp = function( width, height, canvas ) { | |
var long2LittleEndian = function( val ) { | |
return new Uint8Array( [ val & 0x000000ff, (val & 0x0000ff00) >> 8, (val & 0x00ff0000) >> 16, (val & 0xff000000) >> 32 ] ); | |
}; | |
var extraBytes = width % 4, | |
canvaSize = height*(width*3 + extraBytes), | |
fileSize = 54 + canvaSize, | |
fz = long2LittleEndian( fileSize ), | |
w = long2LittleEndian( width ), | |
h = long2LittleEndian( height ), | |
data = new Uint8Array( fileSize ); | |
data.set( [ | |
66,77, // 'BM' | |
fz[0],fz[1],fz[2],fz[3], // file size in long in little endian | |
0,0, // always 0 | |
0,0, // always 0 | |
54,0,0,0, // canvas image starting address, 54 for 24 bits images | |
40,0,0,0, // header size, always 40 | |
w[0],w[1],w[2],w[3], // width in long in little endian | |
h[0],h[1],h[2],h[3], // height | |
1,0, // image planes, always 1 | |
24,0, // 24 bits color depth | |
0,0,0,0, // compression type, 0 for uncompressed | |
0,0,0,0, // ignore these fields | |
0,0,0,0, | |
0,0,0,0, | |
0,0,0,0, | |
0,0,0,0, | |
], 0 ); | |
var x, y, | |
i, address, | |
canvasBMP = new Uint8Array( canvaSize ); | |
for( i = 0, y = height - 1; y >= 0; y-- ) { | |
for( x = 0; x < width; x++ ) { | |
address = 4*(y*width + x); | |
canvasBMP[i++] = canvas[address + 2]; | |
canvasBMP[i++] = canvas[address + 1]; | |
canvasBMP[i++] = canvas[address]; | |
} | |
for( x = 0; x < extraBytes; x++ ) { | |
canvasBMP[i++] = 0; | |
} | |
} | |
data.set( canvasBMP, 54 ); | |
return data; | |
} | |
// ************************** | |
// ************************** | |
var RGB = function( r, g, b ) { | |
this.r = r; | |
this.g = g; | |
this.b = b; | |
}; | |
RGB.prototype.equals = function( rgb ) { | |
return this.r === rgb.r && this.g === rgb.g && this.b === rgb.b; | |
}; | |
// ************************** | |
// ************************** | |
var Canvas = function( width, height ) { | |
this.data = new Uint8Array( width * height * 4 ); | |
this.width = width; | |
this.height = height; | |
return this; | |
}; | |
Canvas.prototype.getPixel = function( x, y ) { | |
if( x < 0 || y < 0 || x >= this.width || y >= this.height ) { | |
return null; | |
} | |
var address = 4*(y*this.width + x); | |
return new RGB( this.data[address], this.data[address+1], this.data[address+2] ); | |
}; | |
Canvas.prototype.setPixel = function( x, y, rgb ) { | |
if( x < 0 || y < 0 || x >= this.width || y >= this.height ) { | |
return; | |
} | |
var address = 4*(y*this.width + x); | |
this.data[address] = rgb.r; | |
this.data[address+1] = rgb.g; | |
this.data[address+2] = rgb.b; | |
this.data[address+3] = 255; | |
}; | |
// ************************** | |
// ************************** | |
var scanlineSeedFilling = function( seedX, seedY, boundaryColor, fillColor, canvas ) { | |
var seedScanline = function( xLeft, xRight, y ) { | |
var x = xLeft, xEnter, | |
px, | |
pFlag; | |
while( x <= xRight ) { | |
// seed the scan line above | |
pFlag = false; | |
for( px = canvas.getPixel( x, y ); px && !px.equals( boundaryColor ) && !px.equals( fillColor ) && x < xRight; x++, px = canvas.getPixel( x, y ) ) { | |
pFlag = true; | |
} | |
if( pFlag ) { | |
if( x === xRight && px && !px.equals( boundaryColor ) && !px.equals( fillColor ) ) { | |
stack.push( { x: x, y: y } ); | |
} else { | |
stack.push( { x: x - 1, y: y } ); | |
} | |
} | |
// continue checking in case the span is interrupted | |
xEnter = x; | |
for( px = canvas.getPixel( x, y ); px && (px.equals( boundaryColor ) && px.equals( fillColor )) && x < xRight; x++, px = canvas.getPixel( x, y ) ); | |
// make sure that the px coordinate is incremented | |
if( x === xEnter ) { | |
x++; | |
} | |
} | |
}; | |
var stack = []; | |
stack.push( { x: seedX, y: seedY } ); | |
while( stack.length ) { | |
// get the seed px and set it to the new value | |
var popElem = stack[stack.length-1], | |
x = popElem.x, | |
y = popElem.y; | |
canvas.setPixel( x, y, fillColor ); | |
stack.length--; // pop | |
var saveX, | |
xLeft, xRight, | |
px; | |
// save the x coordinate of the seed px | |
saveX = x; | |
// fill the span to the left of the seed px | |
for( x--, px = canvas.getPixel( x, y ); px && !px.equals( boundaryColor ); x--, px = canvas.getPixel( x, y ) ) { | |
canvas.setPixel( x, y, fillColor ); | |
} | |
// save the extreme left px | |
xLeft = x + 1; | |
// fill the span to the right of the seed px | |
x = saveX; | |
for( x++, px = canvas.getPixel( x, y ); px && !px.equals( boundaryColor ); x++, px = canvas.getPixel( x, y ) ) { | |
canvas.setPixel( x, y, fillColor ); | |
} | |
// save the extreme right px | |
xRight = x - 1; | |
// check that scan line above is neither a polygon boundary nor has | |
// been previously completely filled; if not, seed the scan line | |
// start at the left edge of the scan line subspan | |
seedScanline( xLeft, xRight, y + 1 ); | |
// check that the scan line below is ot a polygon boundary | |
// nor has been previously completely filled | |
seedScanline( xLeft, xRight, y - 1 ); | |
} | |
}; | |
// ************************** | |
// ************************** | |
var canvas = new Canvas( 320, 200 ); | |
// draw a square center at origin (160,100) | |
var boundaryColor = new RGB( 255, 255, 255 ), | |
x = 0, y = 0; | |
for( ; x < 50; x++ ) { | |
canvas.setPixel( x+135, y+75, boundaryColor ); | |
canvas.setPixel( x+135, y+50+75, boundaryColor ); | |
} | |
x = 0; y = 0; | |
for( ; y < 50; y++ ) { | |
canvas.setPixel( x+135, y+75, boundaryColor ); | |
canvas.setPixel( x+50+135, y+75, boundaryColor ); | |
} | |
var fillColor = new RGB( 255, 0 ,0 ); | |
scanlineSeedFilling( 160,100, boundaryColor,fillColor, canvas ); // fill the square | |
download( bmp( canvas.width, canvas.height, canvas.data ) ); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment