Marching Squares Visualized

Size
7,074 Kb
Views
26,312

How do I make an marching squares visualized?

A visualisation explaing a HTML5 Canvas implementation of the Marching Squares algorithm as it executes.. What is a marching squares visualized? How do you make a marching squares visualized? This script and codes were developed by Sakri Rosenstrom on 13 September 2022, Tuesday.

Marching Squares Visualized Previews

Marching Squares Visualized - Script Codes HTML Codes

<!DOCTYPE html>
<html >
<head> <meta charset="UTF-8"> <title>Marching Squares Visualized</title> <link rel="stylesheet" href="css/style.css">
</head>
<body> <div id="canvasContainer"></div> <script src="js/index.js"></script>
</body>
</html>

Marching Squares Visualized - Script Codes CSS Codes

html, body{	margin : 0px;	width : 100%;	height : 100%;	overflow: hidden;
}
#canvasContainer{ margin : 0px; width : 100%; height : 100%;
}

Marching Squares Visualized - Script Codes JS Codes

/* * *Currently not working on Mac/FireFox and possibly other platforms *Sorry about this :( Will see if I can fix. * * @author Sakri Rosenstrom * A visualization explaining a Marching Squares algorithm as it executes * sample implementation here : https://codepen.io/sakri/full/vIKJp * http://www.sakri.net * https://twitter.com/sakri * http://www.devstate.net * Sources for this can be found at: * https://github.com/sakri/sakriNetCommonJS */
(function (window){ var Sakri = window.Sakri || {}; window.Sakri = window.Sakri || Sakri; Sakri.Geom = {}; //================================================== //=====================::POINT::==================== //================================================== Sakri.Geom.Point = function (x,y){ this.x = isNaN(x) ? 0 : x; this.y = isNaN(y) ? 0 : y; }; Sakri.Geom.Point.prototype.clone = function(){ return new Sakri.Geom.Point(this.x,this.y); }; Sakri.Geom.Point.prototype.update = function(x, y){ this.x = isNaN(x) ? this.x : x; this.y = isNaN(y) ? this.y : y; }; Sakri.Geom.Point.prototype.add = function(x, y){ this.x += isNaN(x) ? 0 : x; this.y += isNaN(y) ? 0 : y; }; Sakri.Geom.Point.prototype.equals = function(point){ return this.x==point.x && this.y==point.y; }; Sakri.Geom.Point.prototype.toString = function(){ return "{x:"+this.x+" , y:"+this.y+"}"; }; //================================================== //===================::RECTANGLE::================== //================================================== Sakri.Geom.Rectangle = function (x, y, width, height){ this.update(x, y, width, height); }; Sakri.Geom.Rectangle.prototype.update = function(x, y, width, height){ this.x = isNaN(x) ? 0 : x; this.y = isNaN(y) ? 0 : y; this.width = isNaN(width) ? 0 : width; this.height = isNaN(height) ? 0 : height; }; Sakri.Geom.Rectangle.prototype.getRight = function(){ return this.x + this.width; }; Sakri.Geom.Rectangle.prototype.getBottom = function(){ return this.y + this.height; }; Sakri.Geom.Rectangle.prototype.getCenter = function(){ return new Sakri.Geom.Point(this.getCenterX(), this.getCenterY()); }; Sakri.Geom.Rectangle.prototype.getCenterX = function(){ return this.x + this.width/2; }; Sakri.Geom.Rectangle.prototype.getCenterY=function(){ return this.y + this.height/2; };	Sakri.Geom.Rectangle.prototype.floor = function(){	this.x = Math.floor(this.x);	this.y = Math.floor(this.y);	this.width = Math.floor(this.width);	this.height = Math.floor(this.height);	}; Sakri.Geom.Rectangle.prototype.clone = function(){ return new Sakri.Geom.Rectangle(this.x, this.y, this.width, this.height); }; Sakri.Geom.Rectangle.prototype.toString = function(){ return "Rectangle{x:"+this.x+" , y:"+this.y+" , width:"+this.width+" , height:"+this.height+"}"; };
}(window));
/** * Created by sakri on 27-1-14. */
(function (window){ var Sakri = window.Sakri || {}; window.Sakri = window.Sakri || Sakri; Sakri.CanvasTextUtil = {}; //========================================================================================= //==============::CANVAS TEXT PROPERTIES::==================================== //======================================================== Sakri.CanvasTextProperties = function(fontWeight, fontStyle, fontSize, fontFace){ this.setFontWeight(fontWeight); this.setFontStyle(fontStyle); this.setFontSize(fontSize); this.fontFace = fontFace ? fontFace : "sans-serif"; }; Sakri.CanvasTextProperties.NORMAL = "normal"; Sakri.CanvasTextProperties.BOLD = "bold"; Sakri.CanvasTextProperties.BOLDER = "bolder"; Sakri.CanvasTextProperties.LIGHTER = "lighter"; Sakri.CanvasTextProperties.ITALIC = "italic"; Sakri.CanvasTextProperties.OBLIQUE = "oblique"; Sakri.CanvasTextProperties.prototype.setFontWeight = function(fontWeight){ switch (fontWeight){ case Sakri.CanvasTextProperties.NORMAL: case Sakri.CanvasTextProperties.BOLD: case Sakri.CanvasTextProperties.BOLDER: case Sakri.CanvasTextProperties.LIGHTER: this.fontWeight = fontWeight; break; default: this.fontWeight = Sakri.CanvasTextProperties.NORMAL; } }; Sakri.CanvasTextProperties.prototype.setFontStyle = function(fontStyle){ switch (fontStyle){ case Sakri.CanvasTextProperties.NORMAL: case Sakri.CanvasTextProperties.ITALIC: case Sakri.CanvasTextProperties.OBLIQUE: this.fontStyle = fontStyle; break; default: this.fontStyle = Sakri.CanvasTextProperties.NORMAL; } }; Sakri.CanvasTextProperties.prototype.setFontSize = function(fontSize){ if(fontSize && fontSize.indexOf && fontSize.indexOf("px")>-1){ var size = fontSize.split("px")[0]; fontProperites.fontSize = isNaN(size) ? 24 : size;//24 is just an arbitrary number return; } this.fontSize = isNaN(fontSize) ? 24 : fontSize;//24 is just an arbitrary number }; Sakri.CanvasTextProperties.prototype.getFontString = function(){ return this.fontWeight + " " + this.fontStyle + " " + this.fontSize + "px " + this.fontFace; };
}(window));
/** * Created by sakri on 27-1-14. */
(function (window){ var Sakri = window.Sakri || {}; window.Sakri = window.Sakri || Sakri; Sakri.BitmapUtil = {}; //TODO : rename "canvas" to "source", if it's an img, create a canvas and draw the img into it Sakri.BitmapUtil.getFirstNonTransparentPixelTopDown = function(canvas){ var context = canvas.getContext("2d"); var y, i, rowData; for(y=0; y<canvas.height; y++){ rowData = context.getImageData(0, y, canvas.width, 1).data; for(i=0; i<rowData.length; i+=4){ if(rowData[i+0] + rowData[i+1] + rowData[i+2] + rowData[i+3] > 0){ return new Sakri.Geom.Point(i/4, y); } } } return null; };
}(window));
/** * Created by @sakri on 28-1-14. * * Somewhat Naive implementation in that there are cases where the edge detection gets stuck in an eternal loop. * This is currently "handled" by a MAX_POINTS variable. * This implementation is "good enough" for most use cases though. * */
(function (window){ var Sakri = window.Sakri || {}; window.Sakri = window.Sakri || Sakri; Sakri.MarchingSquares = {}; //Update this when working with large shapes (large bitmaps) //the "edge detection loop" stops at this figure. This is in place in the event that an infinite loop somehow appears (Should never happen). Sakri.MarchingSquares.MAX_POINTS = 10000; //This is a lookup table of all possible 4 pixel grids, used to decide "scanning positions" during the edge detection process //Zeros represent transparent pixels, Ones represent a non transparent pixel Sakri.MarchingSquares.possibleGrids = { "0011":new Sakri.Geom.Point(1,0), "1011":new Sakri.Geom.Point(1,0), "0001":new Sakri.Geom.Point(1,0), "1001":new Sakri.Geom.Point(1,0), "0100":new Sakri.Geom.Point(0,-1), "0101":new Sakri.Geom.Point(0,-1), "0111":new Sakri.Geom.Point(0,-1), "0110":new Sakri.Geom.Point(0,-1), "1100":new Sakri.Geom.Point(-1,0), "1000":new Sakri.Geom.Point(-1,0), "1101":new Sakri.Geom.Point(-1,0), "1110":new Sakri.Geom.Point(0,1), "1010":new Sakri.Geom.Point(0,1), "0010":new Sakri.Geom.Point(0,1) }; /** * Apparently there were (are?) cases where duplicate points are registered along horizontal or vertical sets * of adjacent points. I haven't been able to reproduce this, but have left this option in place for now. */ Sakri.MarchingSquares.getUniquePoints = function(points){ console.log("MarchingSquares.getUniquePoints() points.length : ",points.length); var unique = {}; var uniquePoints = []; var pointString, p, i; for(i=0; i < points.length; i++){ p = points[i]; pointString = p.x+":"+p.y; if(unique[pointString] == null){ unique[pointString] = true; uniquePoints.push(p); } } console.log("MarchingSquares.getUniquePoints() uniquePoints.length : ",uniquePoints.length); return uniquePoints; }; //source can be a Canvas or an img element. See comment above getUniquePoints concerning the checkUnique flag Sakri.MarchingSquares.getBlobOutlinePoints = function(source, checkUnique){ //Create a copy with a one pixel blank "border" in case source image/canvas has pixels which touch the border //The edge scan operates with an offset of -1,-1 meaning the returned points are accurate var canvas = document.createElement("canvas"); canvas.width = source.width + 2; canvas.height = source.height + 2; var context = canvas.getContext("2d"); context.drawImage(source,1,1); if(checkUnique){ return Sakri.MarchingSquares.getUniquePoints(Sakri.MarchingSquares.scanOutlinePoints(canvas)); }else{ return Sakri.MarchingSquares.scanOutlinePoints(canvas); } }; //this should be private, should not be called directly Sakri.MarchingSquares.scanOutlinePoints = function(canvas){ var points = []; points[0] = Sakri.BitmapUtil.getFirstNonTransparentPixelTopDown(canvas); if(points[0] == null){ return points; } points[0].add(-1, -1);//in order for the lookup to work, we move the position up and back one var context = canvas.getContext("2d"); var currentPosition = points[0]; var gridString = Sakri.MarchingSquares.getGridStringFromPoint(context, currentPosition); var next, i; for(i=1; i<Sakri.MarchingSquares.MAX_POINTS; i++){ next = Sakri.MarchingSquares.getNextEdgePoint(currentPosition, gridString); if(next.equals(points[0])){ break; } points[i] = next; currentPosition = next; gridString = Sakri.MarchingSquares.getGridStringFromPoint(context, currentPosition); } //Failsafe when the marching squares get stuck in an eternal loop. See note at the top. if(i >= Sakri.MarchingSquares.MAX_POINTS){ console.log("MarchingSquares.scanOutlinePoints Sakri.MarchingSquares.MAX_POINTS reached"); return []; } return points; }; Sakri.MarchingSquares.getGridStringFromPoint = function(context, point){ var gridString = ""; var data = context.getImageData(point.x, point.y, 2, 2).data; for(i=0; i<16; i+=4){ gridString += (data[i+0] + data[i+1] + data[i+2] + data[i+3] > 0 ? "1" : "0"); } return gridString; }; Sakri.MarchingSquares.getNextEdgePoint = function(point, gridString){ var offsetPoint = Sakri.MarchingSquares.possibleGrids[gridString]; if(point==null){ throw new Error("MarchingSquares Error : gridString:"+gridString+" , not found in possibleGrids"); } return new Sakri.Geom.Point(point.x + offsetPoint.x, point.y + offsetPoint.y); };
}(window)); //======================== //general properties for demo set up //======================== var canvas; var context; var canvasContainer; var htmlBounds; var bounds; var minimumStageWidth = 250; var minimumStageHeight = 250; var resizeTimeoutId = -1; function init(){ canvas = document.createElement('canvas'); canvas.style.position = "absolute"; context = canvas.getContext("2d"); canvasContainer = document.getElementById("canvasContainer"); canvasContainer.appendChild(canvas); window.onresize = resizeHandler; commitResize(); } function getWidth( element ){return Math.max(element.scrollWidth,element.offsetWidth,element.clientWidth );} function getHeight( element ){return Math.max(element.scrollHeight,element.offsetHeight,element.clientHeight );} //avoid running resize scripts repeatedly if a browser window is being resized by dragging function resizeHandler(){ context.clearRect(0,0,bounds.width, bounds.height); clearTimeoutsAndIntervals(); clearTimeout (resizeTimeoutId); resizeTimeoutId = setTimeout(function(){commitResize();}, 300 ); } function commitResize(){ htmlBounds = new Sakri.Geom.Rectangle(0,0, getWidth(this.canvasContainer) , getHeight(canvasContainer)); if(htmlBounds.width>=800){ canvas.width = 800; canvas.style.left = htmlBounds.getCenterX() - 400+"px"; }else{ canvas.width = htmlBounds.width; canvas.style.left ="0px"; } if(htmlBounds.height>600){ canvas.height = 600; canvas.style.top = htmlBounds.getCenterY() - 300+"px"; }else{ canvas.height = htmlBounds.height; canvas.style.top ="0px"; } bounds = new Sakri.Geom.Rectangle(0,0, canvas.width, canvas.height); context.clearRect(0,0,bounds.width, bounds.height); if(bounds.width>bounds.height){ //landscape layout marchGridsBounds.x = bounds.getCenterX(); charactersBounds.x = marchGridsBounds.x - charactersBounds.width; charactersBounds.y = marchGridsBounds.y = bounds.getCenterY() - charactersBounds.height/2; }else{ //portrait layout charactersBounds.x = marchGridsBounds.x = bounds.getCenterX() - charactersBounds.width/2; marchGridsBounds.y = bounds.getCenterY() charactersBounds.y = marchGridsBounds.y - charactersBounds.height/2 } if(bounds.width<minimumStageWidth || bounds.height<minimumStageHeight){ stageTooSmallHandler(); return; } startDemo(); } function stageTooSmallHandler(){ var warning = "Sorry, bigger screen required :("; var props = new Sakri.CanvasTextProperties(null,null,24); context.font = props.getFontString(); context.fillStyle = "#000000"; context.fillText(warning, bounds.getCenterX() - context.measureText(warning).width/2, bounds.getCenterY()-12); } //======================== //Demo specific properties //======================== var marchingSquaresTimeOutId = -1; var fontProperties = new Sakri.CanvasTextProperties(Sakri.CanvasTextProperties.BOLD, null, 40); var fillStyle = "#0b0b2e"; var characters = "ABCDEFGHIJKLMNOPQRSTUVXYZ1234567890"; var currentCharacterIndex = 0; var currentCharacterOutline; var charactersBounds = new Sakri.Geom.Rectangle(0,0,250,250); var marchGridsBounds = new Sakri.Geom.Rectangle(0,0,250,250); var characterCanvas; var characterContext; var characterScaleFactor; var marchingPixelContext; var marchingPixelCanvas; var currentScanPosition; var optionGridRect = new Sakri.Geom.Rectangle(); var optionRect = new Sakri.Geom.Rectangle(); var bigBounds = new Sakri.Geom.Rectangle(); var outline; function clearTimeoutsAndIntervals(){ clearTimeout(marchingSquaresTimeOutId); } function startDemo(){ context.clearRect(0, 0, canvas.width, canvas.height); characterCanvas = document.createElement('canvas'); marchingPixelCanvas = document.createElement("canvas"); demoNextCharacter(); } function demoNextCharacter(){ context.clearRect(0,0,canvas.width, canvas.height); characterContext = characterCanvas.getContext("2d"); characterContext.clearRect(0,0,characterCanvas.width, characterCanvas.height); characterContext.font = fontProperties.getFontString(); characterContext.textBaseline = "top"; characterCanvas.width = characterContext.measureText(characters[currentCharacterIndex]).width; characterCanvas.height = fontProperties.fontSize; characterContext = characterCanvas.getContext("2d"); characterContext.fillStyle = "#AAAAAA"; characterContext.font = fontProperties.getFontString(); characterContext.textBaseline = "top"; characterContext.fillText(characters[currentCharacterIndex],0,0); renderCharacter(); renderAllOptions(); currentCharacterIndex++; currentCharacterIndex %= characters.length; marchingPixelCanvas.width = characterCanvas.width + 2; marchingPixelCanvas.height = characterCanvas.height + 2; marchingPixelContext = marchingPixelCanvas.getContext("2d"); marchingPixelContext.drawImage(characterCanvas,1,1); outline = []; outline[0] = Sakri.BitmapUtil.getFirstNonTransparentPixelTopDown(marchingPixelCanvas); outline[0].add(-1, -1);//in order for the lookup to work, we move the position up and back one currentScanPosition = outline[0].clone(); displayCurrentScanPosition(); } function displayCurrentScanPosition(){ renderCharacter(); context.fillStyle = "#FF0000"; var position; for(var i=0;i<outline.length;i++){ position = outline[i]; context.fillRect(charactersBounds.x+position.x, charactersBounds.y+position.y, 1, 1); context.fillRect(bigBounds.x+position.x*characterScaleFactor, bigBounds.y+position.y*characterScaleFactor, characterScaleFactor, characterScaleFactor ); } //render scan grid outline context.lineWidth = 1; context.strokeStyle = "#0000FF"; context.strokeRect(bigBounds.x+(currentScanPosition.x-1)*characterScaleFactor, bigBounds.y+(currentScanPosition.y-1)*characterScaleFactor, characterScaleFactor*2, characterScaleFactor*2); marchingSquaresTimeOutId = setTimeout(scanNextOutlinePoint, 500); renderAllOptions(); } function scanNextOutlinePoint(){ //console.log("scanNextOutlinePoint()"); var gridString = Sakri.MarchingSquares.getGridStringFromPoint(marchingPixelContext, currentScanPosition); var next = Sakri.MarchingSquares.getNextEdgePoint(currentScanPosition, gridString); if(next.equals(outline[0])){ demoNextCharacter(); return; } //render next renderAllOptions(gridString); currentScanPosition = next; outline.push(next); marchingSquaresTimeOutId = setTimeout(displayCurrentScanPosition, 500); } function renderCharacter(){ context.clearRect(charactersBounds.x, charactersBounds.y, charactersBounds.width, charactersBounds.height); var width = characterContext.measureText(characters[currentCharacterIndex]).width; var height = fontProperties.fontSize; characterScaleFactor = Math.floor(Math.min(charactersBounds.width / width, charactersBounds.height / height)); context.fillStyle = "#AAAAAA"; var data = characterContext.getImageData(0,0, width, height).data; var i, x, y; bigBounds.update(charactersBounds.x + width , charactersBounds.y, width*characterScaleFactor, height*characterScaleFactor); //render character for(i=0; i<data.length; i+=4){ if(data[i]+data[i+1]+data[i+2]+data[i+3] > 0){ x = (i/4)%width; y = Math.floor((i/4)/width); context.fillRect(charactersBounds.x + x, charactersBounds.y + y, 1, 1 ); context.fillRect(bigBounds.x+x*characterScaleFactor, bigBounds.y+y*characterScaleFactor, characterScaleFactor, characterScaleFactor ); } } //render graph lines context.fillStyle = "#FFFFFF"; for(i = characterScaleFactor; i < bigBounds.height; i+=characterScaleFactor){ context.fillRect(bigBounds.x, bigBounds.y + i, bigBounds.width, 1); } for(i = characterScaleFactor; i < bigBounds.width; i+=characterScaleFactor){ context.fillRect(bigBounds.x + i , bigBounds.y, 1, bigBounds.height); } } function renderAllOptions(currentOptionString){ context.clearRect(optionGridRect.x, optionGridRect.y, optionGridRect.width, optionGridRect.height); var margin = 4; var cols = 4; var optionSize = marchGridsBounds.width/cols; optionRect = new Sakri.Geom.Rectangle(0,0,optionSize-margin, optionSize-margin); optionGridRect.width = optionSize/2.5; optionGridRect.height = optionSize/2.5; var options = []; var i, option; for(option in Sakri.MarchingSquares.possibleGrids){ options.push({string:option, point:Sakri.MarchingSquares.possibleGrids[option]}); } for(var i=0; i<options.length; i++){ optionRect.x = marchGridsBounds.x + (i%cols) * optionSize; optionRect.y = marchGridsBounds.y + Math.floor(i/cols) * optionSize; optionRect.floor(); context.strokeStyle = currentOptionString==options[i].string ? "#FF0000" : "#AAAAAA"; context.strokeRect(optionRect.x, optionRect.y, optionRect.width, optionRect.height); renderOption(options[i], optionRect, currentOptionString==options[i].string); } } function renderOption(option, rect, highLight){ context.strokeStyle = highLight ? "#FF0000" : "#666666"; context.fillStyle = highLight ? "#FF0000" : "#333333"; optionGridRect.x = rect.getCenterX() - optionGridRect.width; optionGridRect.y = rect.getCenterY() - optionGridRect.height/2; context.strokeRect(optionGridRect.x, optionGridRect.y, optionGridRect.width, optionGridRect.height); //too lazy to write a loop for this, copy paste saves the day ;) if(option.string.charAt(0)=="1"){ context.fillRect(optionGridRect.x, optionGridRect.y, optionGridRect.width/2, optionGridRect.height/2 ); } if(option.string.charAt(1)=="1"){ context.fillRect(optionGridRect.x+optionGridRect.width/2, optionGridRect.y, optionGridRect.width/2, optionGridRect.height/2 ); } if(option.string.charAt(2)=="1"){ context.fillRect(optionGridRect.x, optionGridRect.y+optionGridRect.height/2, optionGridRect.width/2, optionGridRect.height/2 ); } if(option.string.charAt(3)=="1"){ context.fillRect(optionGridRect.x+optionGridRect.width/2, optionGridRect.y+optionGridRect.height/2, optionGridRect.width/2, optionGridRect.height/2 ); } optionGridRect.x = rect.getCenterX(); context.save(); context.lineWidth = 3; context.translate(optionGridRect.getCenterX()+optionGridRect.width/10, optionGridRect.getCenterY()); switch(option.point.x+""+option.point.y){ case "01": context.rotate(Math.PI/2); break; case "-10": context.rotate(Math.PI); break; case "0-1": context.rotate(Math.PI+Math.PI/2); break; } renderArrow(optionGridRect.width/2, optionGridRect.height/2); context.restore(); } function renderArrow(width,height){ //line context.beginPath(); context.moveTo(width/10, 0); context.lineTo(width-width/10, 0); context.stroke(); context.closePath(); //arrowhead context.beginPath(); context.moveTo(width/2, -height/2+height/10); context.lineTo(width-width/10, 0); context.lineTo(width/2, height/2-height/10); context.stroke(); context.closePath(); } var readyStateCheckInterval = setInterval( function() { if (document.readyState === "complete") { clearInterval(readyStateCheckInterval); init(); } }, 10);
Marching Squares Visualized - Script Codes
Marching Squares Visualized - Script Codes
Home Page Home
Developer Sakri Rosenstrom
Username sakri
Uploaded September 13, 2022
Rating 4
Size 7,074 Kb
Views 26,312
Do you need developer help for Marching Squares Visualized?

Find the perfect freelance services for your business! Fiverr's mission is to change how the world works together. Fiverr connects businesses with freelancers offering digital services in 500+ categories. Find Developer!

Sakri Rosenstrom (sakri) Script Codes
Create amazing captions with AI!

Jasper is the AI Content Generator that helps you and your team break through creative blocks to create amazing, original content 10X faster. Discover all the ways the Jasper AI Content Platform can help streamline your creative workflows. Start For Free!