Drawing Rules Between Lines of Text in Javascript

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>

This entry was posted in Uncategorized and tagged , , . Bookmark the permalink.

Leave a comment