The JavaScript program below will automatically draw rules between lines of text in a loaded image. It is intended as an early step in an optical character recognition system. To see it in action, copy it into a .html file and open that file in a web browser that runs JavaScript.
<html> <body> <!-- ui --> <div id="divUI"> <label><b>Image "Rulifier"</b></label> <p>Load a file, specify a background color, and click the button to add ruled lines to it.</p> <div> <label>Image to Add Ruled Lines to:</label> <input type="file" onchange="inputImageToRule_Changed(this);"></input> <div id="divDisplayImageToRule">[none]</div> </div> <div> </div> <label>Background Color RGB:</label> <input id="inputColorBackground" value="255,255,255"></input> </div> <div> <label>Color Difference Threshold:</label> <input id="inputColorDifferenceThreshold" value="0"></input> </div> <button onclick="buttonImageRule_Clicked();">Add Ruled Lines</button> </div> <div> <label>Image with Ruled Lines:</label> <div id="divDisplayImageRuled">[none]</div> </div> </div> <!-- ui ends--> <script type="text/javascript"> // ui events function buttonImageRule_Clicked() { var divDisplayImageToRule = document.getElementById("divDisplayImageToRule"); var imageToRuleAsCanvas = divDisplayImageToRule.getElementsByTagName("canvas")[0]; if (imageToRuleAsCanvas == null) { alert("No image loaded!"); return; } var inputColorBackground = document.getElementById("inputColorBackground"); var inputColorDifferenceThreshold = document.getElementById("inputColorDifferenceThreshold"); var colorBackground = inputColorBackground.value; var colorDifferenceThreshold = inputColorDifferenceThreshold.value; var imageRuledAsCanvas = new ImageAutoRulifier().ruleCanvas ( imageToRuleAsCanvas, colorBackground, colorDifferenceThreshold ); imageRuledAsCanvas.style = "border:1px solid"; var divDisplayImageRuled = document.getElementById("divDisplayImageRuled"); divDisplayImageRuled.innerHTML = ""; divDisplayImageRuled.appendChild(imageRuledAsCanvas); } function inputImageToRule_Changed(input) { var file = input.files[0]; var fileReader = new FileReader(); fileReader.onload = function(eventFileLoaded) { var imageAsDataURL = eventFileLoaded.target.result; var imageAsDOMElement = document.createElement("img"); imageAsDOMElement.onload = function(eventImageLoaded) { var imageAsCanvas = document.createElement("canvas"); imageAsCanvas.style = "border:1px solid"; imageAsCanvas.width = imageAsDOMElement.width; imageAsCanvas.height = imageAsDOMElement.height; var graphics = imageAsCanvas.getContext("2d"); graphics.drawImage(imageAsDOMElement, 0, 0); imageAsCanvas.onmousedown = function(mouseEvent) { var x = mouseEvent.x; var y = mouseEvent.y; var pixelRGBA = graphics.getImageData ( x, y, 1, 1 ).data; var pixelAsString = + pixelRGBA[0] + "," + pixelRGBA[1] + "," + pixelRGBA[2] var inputColorBackground = document.getElementById("inputColorBackground"); inputColorBackground.value = pixelAsString; } var divDisplayImageToRule = document.getElementById ( "divDisplayImageToRule" ); divDisplayImageToRule.innerHTML = ""; divDisplayImageToRule.appendChild ( imageAsCanvas ); } imageAsDOMElement.src = imageAsDataURL; } fileReader.readAsDataURL(file); } // classes function Coords(x, y) { this.x = x; this.y = y; } { Coords.prototype.clone = function() { return new Coords(this.x, this.y); } Coords.prototype.dimension = function(dimensionIndex, valueToSet) { var returnValue; if (valueToSet == null) { returnValue = (dimensionIndex == 0 ? this.x : this.y); } else { if (dimensionIndex == 0) { this.x = valueToSet; } else { this.y = valueToSet; } } return returnValue; } Coords.prototype.subtract = function(other) { this.x -= other.x; this.y -= other.y; return this; } } function ImageAutoRulifier() { // do nothing } { ImageAutoRulifier.prototype.ruleCanvas = function ( imageToRuleAsCanvas, colorBackground, colorDifferenceThreshold ) { var colorBackgroundRGB = colorBackground.split(","); var graphics = imageToRuleAsCanvas.getContext("2d"); var imageToRuleSize = new Coords ( imageToRuleAsCanvas.width, imageToRuleAsCanvas.height ); var pixelPos = new Coords(); var numberOfAxes = 2; var linesOpen = []; // hack - Start at 1 to just check the horizontal lines. for (var axis0 = 1; axis0 < numberOfAxes; axis0++) { var axis1 = 1 - axis0; var sizeAlongAxis0 = imageToRuleSize.dimension(axis0); var sizeAlongAxis1 = imageToRuleSize.dimension(axis1); for (var i = 0; i < sizeAlongAxis0; i++) { pixelPos.dimension(axis0, i); var isLineOpen = true; for (var direction = 0; direction < 2; direction++) { var jStart = (direction == 0 ? 0 : sizeAlongAxis1 - 1); var jEnd = (direction == 0 ? sizeAlongAxis1 : -1); var jStep = (direction == 0 ? 1 : -1); for (var j = jStart; j != jEnd; j += jStep) { pixelPos.dimension(axis1, j); var isPixelWithinThreshold = this.ruleCanvas_IsPixelWithinThreshold ( graphics, colorBackgroundRGB, colorDifferenceThreshold, pixelPos ); if (isPixelWithinThreshold == false) { isLineOpen = false; break; } } // end for j if (isLineOpen == false) { break; } } // end for d if (isLineOpen == true) { var lineOpen = new Line ( new Coords(0, i), new Coords(sizeAlongAxis1, i) ); linesOpen.push(lineOpen); } } // end for i } // end for a var lineGroupCurrent = []; var lineGroups = []; var linePrev = linesOpen[0]; for (var i = 1; i < linesOpen.length; i++) { var line = linesOpen[i]; var distanceBetweenLineAndPrev = line.fromPos.y - linePrev.fromPos.y; if (distanceBetweenLineAndPrev > 1) { lineGroups.push(lineGroupCurrent); lineGroupCurrent = []; } lineGroupCurrent.push(line); linePrev = line; } var linesSpaced = []; for (var g = 0; g < lineGroups.length; g++) { var lineGroup = lineGroups[g]; var indexOfLineAtCenterOfGroup = Math.floor(lineGroup.length / 2); var lineAtCenterOfGroup = lineGroup[indexOfLineAtCenterOfGroup]; linesSpaced.push(lineAtCenterOfGroup); } var linesForRules = linesSpaced; var imageRuledAsCanvas = document.createElement("canvas"); var imageRuledSize = imageToRuleSize; imageRuledAsCanvas.width = imageRuledSize.x; imageRuledAsCanvas.height = imageRuledSize.y; graphics = imageRuledAsCanvas.getContext("2d"); graphics.drawImage ( imageToRuleAsCanvas, 0, 0 ); graphics.strokeStyle = "Cyan"; for (var i = 0; i < linesForRules.length; i++) { var line = linesForRules[i]; graphics.beginPath(); graphics.moveTo(line.fromPos.x, line.fromPos.y); graphics.lineTo(line.toPos.x, line.toPos.y); graphics.stroke(); } return imageRuledAsCanvas; } ImageAutoRulifier.prototype.ruleCanvas_IsPixelWithinThreshold = function ( graphics, colorBackgroundRGB, colorDifferenceThreshold, pixelPos ) { var isPixelWithinThreshold = false; var pixelRGB = graphics.getImageData ( pixelPos.x, pixelPos.y, 1, 1 ).data; var pixelDifference = 0; var numberOfColorComponents = 3; // rgb for (var c = 0; c < numberOfColorComponents; c++) { var componentDifference = Math.abs ( pixelRGB[c] - colorBackgroundRGB[c] ); pixelDifference += componentDifference; } var isPixelWithinThreshold = (pixelDifference <= colorDifferenceThreshold); return isPixelWithinThreshold; } } function Line(fromPos, toPos) { this.fromPos = fromPos; this.toPos = toPos; } </script> </body> </html>