Marching Squares Visualized
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 - 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);
Developer | Sakri Rosenstrom |
Username | sakri |
Uploaded | September 13, 2022 |
Rating | 4 |
Size | 7,074 Kb |
Views | 26,312 |
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!
Name | Size |
Circular Logic Works Because... | 3,427 Kb |
Flappy Text | 8,763 Kb |
Retro Geometry Text Effect | 14,008 Kb |
Canvas Vasarely | 3,290 Kb |
Flappy Lego | 7,262 Kb |
Ghost Text | 10,419 Kb |
Circular Wander | 11,203 Kb |
Classic text effect | 11,186 Kb |
Circular Wander Text | 9,998 Kb |
Isometric Text | 8,538 Kb |
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!
Name | Username | Size |
Click handler test | Snapson | 2,329 Kb |
A form arranged using automatic placement. | Vikasford | 2,103 Kb |
CSS Variables | Jdsteinbach | 4,759 Kb |
Adding and Removing Element | Accimeesterlin | 2,119 Kb |
A Pen by Michael Parenteau | Michaelparenteau | 2,133 Kb |
CSS Hover Effects | Alen | 3,613 Kb |
Video mute | Leon9208 | 2,131 Kb |
Simple DevTools | Deegill | 2,511 Kb |
Pure CSS Tooltips | Mobius1 | 2,271 Kb |
Iron Man SVG Loading Animation | Andythayer | 3,069 Kb |
Surf anonymously, prevent hackers from acquiring your IP address, send anonymous email, and encrypt your Internet connection. High speed, ultra secure, and easy to use. Instant setup. Hide Your IP Now!