A Simple Data Transcoder in JavaScript

The JavaScript program below converts data encoded in UTF8, Base64, or hexadecimal format into one of the other encodings. To see the code in action, copy it into a .html file and open that file in a text encoder that runs JavaScript. Or, for an online version, visit “https://thiscouldbebetter.neocities.org/datatranscoder.html”.

DataTranscoder.png


<html>
<body>

<div id="divUI">

	<label><b>Data Transcoder</b></label><br />

	<label>Data to Encode:</label><br />
	<textarea id="textareaDataToEncode" cols="40" rows="10"></textarea><br />

	<label>From:</label>
	<select id="selectEncodingFrom">
		<option>Base64</option>
		<option>Hexadecimal</option>
		<option selected>UTF8</option>
	</select>
	<label>To:</label>
	<select id="selectEncodingTo">
		<option selected>Base64</option>
		<option>Hexadecimal</option>
		<option>UTF8</option>
	</select><br />

	<button onclick="buttonEncode_Clicked();">v Encode v</button>
	<button onclick="buttonDecode_Clicked();">^ Decode ^</button><br />

	<label>Data Encoded:</label><br />
	<textarea id="textareaDataEncoded" cols="40" rows="10"></textarea><br />

</div>

<script type="text/javascript">

// ui event handlers

function buttonDecode_Clicked()
{
	var selectEncodingFrom = document.getElementById
	(
		"selectEncodingFrom"
	);

	var selectEncodingTo = document.getElementById
	(
		"selectEncodingTo"
	);

	var textareaDataToEncode = document.getElementById
	(
		"textareaDataToEncode"
	);

	var textareaDataEncoded = document.getElementById
	(
		"textareaDataEncoded"
	);

	var encodingNameFrom = selectEncodingFrom.value;
	var encodingFrom = Encoding.Instances[encodingNameFrom];

	var encodingNameTo = selectEncodingTo.value;
	var encodingTo = Encoding.Instances[encodingNameTo];

	var dataToEncode = textareaDataEncoded.value;
	var dataAsBytes = encodingTo.decodeToBytes(dataToEncode);
	var dataEncoded = encodingFrom.encodeBytes(dataAsBytes);

	textareaDataToEncode.value = dataEncoded;
}

function buttonEncode_Clicked()
{
	var selectEncodingFrom = document.getElementById
	(
		"selectEncodingFrom"
	);

	var selectEncodingTo = document.getElementById
	(
		"selectEncodingTo"
	);

	var textareaDataToEncode = document.getElementById
	(
		"textareaDataToEncode"
	);

	var textareaDataEncoded = document.getElementById
	(
		"textareaDataEncoded"
	);

	var encodingNameFrom = selectEncodingFrom.value;
	var encodingFrom = Encoding.Instances[encodingNameFrom];

	var encodingNameTo = selectEncodingTo.value;
	var encodingTo = Encoding.Instances[encodingNameTo];

	var dataToEncode = textareaDataToEncode.value;
	var dataAsBytes = encodingFrom.decodeToBytes(dataToEncode);
	var dataEncoded = encodingTo.encodeBytes(dataAsBytes);

	textareaDataEncoded.value = dataEncoded;
}

// classes

function Encoding(name, decodeToBytes, encodeBytes)
{
	this.name = name;
	this.decodeToBytes = decodeToBytes;
	this.encodeBytes = encodeBytes;
}
{
	Encoding.Instances = new Encoding_Instances();

	function Encoding_Instances()
	{
		this.Base64 = new Encoding
		(
			"Base64",
			function decodeToBytes(dataToDecode)
			{
				var dataAsBinaryString = atob(dataToDecode);
				var dataAsBytes = [];
				for (var i = 0; i < dataAsBinaryString.length; i++)
				{
					var byte = dataAsBinaryString.charCodeAt(i);
					dataAsBytes.push(byte);
				}
				return dataAsBytes;
			},
			function encodeBytes(bytesToEncode)
			{
				var dataAsBinaryString = "";
				for (var i = 0; i < bytesToEncode.length; i++)
				{
					var byte = bytesToEncode[i];
					var byteAsChar = String.fromCharCode(byte);
					dataAsBinaryString += byteAsChar;
				}
				var returnValue = btoa(dataAsBinaryString);
				return returnValue;
			}
		);

		this.Hexadecimal = new Encoding
		(
			"Hexadecimal",
			function decodeToBytes(dataToDecode)
			{
				var returnValues = []
				for (var i = 0; i < dataToDecode.length; i += 2)
				{
					var byteAsHexadecimal = dataToDecode.substr(i, 2);
					var byte = parseInt(byteAsHexadecimal, 16);
					returnValues.push(byte);
				}
				return returnValues;
			},
			function encodeBytes(bytesToEncode)
			{
				var returnValue = "";

				for (var i = 0; i < bytesToEncode.length; i++)
				{
					var byte = bytesToEncode[i];
					var byteAsString = byte.toString(16);
					returnValue += byteAsString;
				}

				return returnValue;
			}
		);

		this.UTF8 = new Encoding
		(
			"UTF8",
			function decodeToBytes(dataToDecode)
			{
				var returnValues = []
				for (var i = 0; i < dataToDecode.length; i++)
				{
					var byte = dataToDecode.charCodeAt(i);
					returnValues.push(byte);
				}
				return returnValues;

			},
			function encodeBytes(bytesToEncode)
			{
				var returnValue = "";

				for (var i = 0; i < bytesToEncode.length; i++)
				{
					var byte = bytesToEncode[i];
					var char = String.fromCharCode(byte);
					returnValue += char;
				}
				return returnValue;
			}
		);
	}
}

</script>

</body>
</html>

Advertisements
Posted in Uncategorized | Tagged , , , , | Leave a comment

Exploring the SVG Vector Image File Format in JavaScript

The JavaScript program below accepts a vector-based “path” in JSON format and converts that path to the SVG format. The output can then be viewed in a program that understands the SVG format, such as most modern web browsers.

The program currently only supports very simple paths with straight edges. It is meant primarily as a basis for further work.

UPDATE 2017/09/05 – I have added support for curved edges, dynamic canvas sizing, and drawing of “handles”. I also modified the code to use absolute vertex positions rather than relative ones.

SVGCreator.png


<html>
<body>
 
<div id="divUI">
	<label>Path as JSON:</label>
	<button onclick="buttonNew_Clicked();">New</button>
	<button onclick="buttonDemo_Clicked();">Demo</button><br />
	<textarea id="textareaPathAsJSON" cols="60" rows="30">
	</textarea><br />
	<button onclick="buttonDraw_Clicked();">Draw Path</button>
	<input id="checkboxDrawHandles" type="checkbox">Draw Handles</input><br />
	<div id="divDisplay"></div>
	<button onclick="buttonConvert_Clicked();">Convert JSON to SVG</button><br />
	<label>Path as SVG:</label><br />
	<textarea id="textareaPathAsSVG" cols="60" rows="10"></textarea><br />
	 
</div>
 
<script type="text/javascript">
 
// ui event handlers
 
function buttonConvert_Clicked()
{
	var textareaPathAsJSON = document.getElementById("textareaPathAsJSON");
	var pathAsStringJSON = textareaPathAsJSON.value;
	try
	{
		var path = Path.fromStringJSON(pathAsStringJSON);
		var pathAsStringSVG = path.toStringSVG();
		var textareaPathAsSVG = document.getElementById("textareaPathAsSVG");
		textareaPathAsSVG.value = pathAsStringSVG;
	}
	catch (ex)
	{
		alert("Invalid JSON!");
	}
}
 
function buttonDemo_Clicked()
{
	var path = Path.demo();
	var pathAsStringJSON = path.toStringJSON();
	var textareaPathAsJSON = document.getElementById("textareaPathAsJSON");
	textareaPathAsJSON.value = pathAsStringJSON;	
}

function buttonDraw_Clicked()
{
	var textareaPathAsJSON = document.getElementById("textareaPathAsJSON");
	var pathAsStringJSON = textareaPathAsJSON.value;
	var checkboxDrawHandles = document.getElementById("checkboxDrawHandles");
	var showHandles = checkboxDrawHandles.checked;

	try
	{
		var path = Path.fromStringJSON(pathAsStringJSON);
		var pathBounds = 
			Bounds.new().ofPoints(path.vertices.select("pos"));
		var displaySize = pathBounds.max.add(pathBounds.min);
		var display = new Display(displaySize);
		display.initialize();
		display.drawPath(path);
		if (showHandles == true)
		{
			display.drawPathHandles(path);
		}
	}
	catch (ex)
	{
		alert("Invalid JSON!");
	}
}

function buttonNew_Clicked()
{
	var path = new Path([], false, null, "#000000");
	var pathAsStringJSON = path.toStringJSON();
	var textareaPathAsJSON = document.getElementById("textareaPathAsJSON");
	textareaPathAsJSON.value = pathAsStringJSON;	
}
 
// classes

function ArrayExtensions()
{
	// extension class
}
{
	Array.prototype.select = function(memberName)
	{
		var returnValues = [];

		for (var i = 0; i < this.length; i++)
		{
			var element = this[i];
			var member = element[memberName];
			returnValues.push(member);
		}

		return returnValues;
	}
}

function Bounds(min, max)
{
	this.min = min;
	this.max = max;
	this._size = new Coords();
}
{
	Bounds.new = function()
	{
		return new Bounds(new Coords(), new Coords());
	}

	Bounds.prototype.ofPoints = function(points)
	{
		var point = points[0];
		this.min.overwriteWith(point);
		this.max.overwriteWith(point);
		for (var i = 0; i < points.length; i++)
		{
			point = points[i];
			if (point.x < this.min.x)
			{
				this.min.x = point.x;
			}
			if (point.x > this.max.y)
			{
				this.max.x = point.x;
			}
			if (point.y < this.min.y)
			{
				this.min.y = point.y;
			}
			if (point.y > this.max.y)
			{
				this.max.y = point.y;
			}
		}

		return this;
	}

	Bounds.prototype.size = function()
	{
		return this._size.overwriteWith(this.max).subtract(this.min);
	}
}
 
function Coords(x, y)
{
	this.x = x;
	this.y = y;
}
{
	Coords.prototype.add = function(other)
	{
		this.x += other.x;
		this.y += other.y;
		return this;
	}

	Coords.prototype.clone = function()
	{
		return new Coords(this.x, this.y);
	}

	Coords.prototype.overwriteWith = function(other)
	{
		this.x = other.x;
		this.y = other.y;
		return this;
	}
 
	Coords.prototype.subtract = function(other)
	{
		this.x -= other.x;
		this.y -= other.y;
		return this;
	}
}
 
function Display(size)
{
	this.size = size;
}
{
	Display.prototype.drawPath = function(path, showHandles)
	{
		this.graphics.beginPath();

		var vertices = path.vertices;

		var vertex = vertices[0];
		var vertexPos = vertex.pos;
		this.graphics.moveTo(vertexPos.x, vertexPos.y);

		for (var v = 0; v < vertices.length; v++)
		{
			vertex = vertices[v];
			var vertexPos = vertex.pos;
			var vertexCurveControlPoints = vertex.curveControlPoints;

			var vNext = (v >= vertices.length - 1 ? 0 : v + 1); 
			var vertexNext = vertices[vNext];

			var vertexNextPos = vertexNext.pos;

			if (vertexCurveControlPoints == null)
			{				
				this.graphics.lineTo
				(
					vertexNextPos.x,
					vertexNextPos.y
				);
			}
			else
			{
				var curveControlPoint0 = vertexCurveControlPoints[0];
				var curveControlPoint1 = vertexCurveControlPoints[1];				

				this.graphics.bezierCurveTo
				(
					curveControlPoint0.x,
					curveControlPoint0.y,
					curveControlPoint1.x,
					curveControlPoint1.y,
					vertexNextPos.x,
					vertexNextPos.y
				);
			}
		}

		if (path.isClosed == true)
		{
			this.graphics.closePath();
		}

		if (path.colorFill != null)
		{
			this.graphics.fillStyle = path.colorFill;
			this.graphics.fill();
		}

		if (path.colorBorder != null)
		{
			this.graphics.strokeStyle = path.colorBorder;
			this.graphics.stroke();
		}
	}

	Display.prototype.drawPathHandles = function(path)
	{
		var handleSizeHalf = 3;
		var handleSize = handleSizeHalf * 2;

		var vertices = path.vertices;

		for (var v = 0; v < vertices.length; v++)
		{
			var vertex = vertices[v];
			var vertexPos = vertex.pos;
			var vertexCurveControlPoints = vertex.curveControlPoints;

			var vNext = (v >= vertices.length - 1 ? 0 : v + 1); 
			var vertexNext = vertices[vNext];
			var vertexNextPos = vertexNext.pos;

			this.graphics.strokeStyle = "Gray";
			this.graphics.strokeRect
			(
				vertexPos.x - handleSizeHalf,
				vertexPos.y - handleSizeHalf,
				handleSize,
				handleSize
			);

			if (vertexCurveControlPoints != null)
			{
				var curveControlPoint0 = vertexCurveControlPoints[0];
				var curveControlPoint1 = vertexCurveControlPoints[1];
				
				this.graphics.beginPath();
				this.graphics.arc
				(
					curveControlPoint0.x, curveControlPoint0.y, 
					handleSizeHalf,
					0, 2 * Math.PI
				);
				this.graphics.stroke();

				this.graphics.beginPath();
				this.graphics.moveTo(vertexPos.x, vertexPos.y);
				this.graphics.lineTo(curveControlPoint0.x, curveControlPoint0.y);
				this.graphics.stroke();

				this.graphics.beginPath();
				this.graphics.arc
				(
					curveControlPoint1.x, curveControlPoint1.y, 
					handleSizeHalf,
					0, 2 * Math.PI
				);
				this.graphics.stroke();

				this.graphics.beginPath();
				this.graphics.moveTo(vertexNextPos.x, vertexNextPos.y);
				this.graphics.lineTo(curveControlPoint1.x, curveControlPoint1.y);
				this.graphics.stroke();
			}
		}
	}
 
	Display.prototype.initialize = function()
	{
		var canvas = document.createElement("canvas");
		canvas.width = this.size.x;
		canvas.height = this.size.y;
 
		var divDisplay = document.getElementById("divDisplay");
		divDisplay.innerHTML = "";
		divDisplay.appendChild(canvas);
 
		this.graphics = canvas.getContext("2d");
	}
}
 
function Path(vertices, isClosed, colorFill, colorBorder)
{
	this.vertices = vertices;
	this.isClosed = isClosed;
	this.colorFill = colorFill;
	this.colorBorder = colorBorder;
}
{
	// static methods

	Path.demo = function()
	{
		var path = new Path
		(
			[ 
				new PathVertex( new Coords(50, 25) ),
				new PathVertex
				( 
					new Coords(75, 75), // pos
					// curveControlPoints
					[
						new Coords(60, 60),
						new Coords(40, 60),
					]
				),
				new PathVertex( new Coords(25, 75) ),
			],
			true, // isClosed
			"#00ffff", // colorFill
			"#000000" // colorBorder
		);

		return path;
	}

	// instance methods

	// serializable
 
	Path.fromStringJSON = function(pathAsStringJSON)
	{
		var path = JSON.parse(pathAsStringJSON);
		path.__proto__ = Path.prototype;
		var vertices = path.vertices;
		for (var i = 0; i < vertices.length; i++)
		{
			var vertex = vertices[i];
			vertex.pos.__proto__ = Coords.prototype;
			var curveControlPoints = vertex.curveControlPoints;
			if (curveControlPoints != null)
			{
				for (var ccp = 0; ccp < 2; ccp++)
				{
					var curveControlPoint = curveControlPoints[ccp];
					curveControlPoint.__proto__ = Coords.prototype;
				}
			}
			vertex.__proto__ = PathVertex.prototype;
		}
		return path;
	}
 
	Path.prototype.toStringJSON = function()
	{
		var returnValue = JSON.stringify(this, null, 4);
		return returnValue;
	}
 
	// svg
 
	Path.prototype.toStringSVG = function()
	{
		var pathAsSVG = "<?xml version='1.0' encoding='UTF-8' standalone='no'?>\n";
 
		pathAsSVG += "<svg xmlns='http://www.w3.org/2000/svg'>\n";
		pathAsSVG += "\t<g>\n";
		pathAsSVG += "\t\t<path \n";
		pathAsSVG += "\t\t\tstyle='"
		if (this.colorBorder != null)
		{
			pathAsSVG += "stroke:" + this.colorBorder + ";";
		}
		if (this.colorFill != null)
		{
			pathAsSVG += "fill:" + this.colorFill + ";";
		}
		pathAsSVG += "' \n";
		pathAsSVG += "\t\t\td='M "
		for (var v = 0; v < this.vertices.length; v++)
		{
			var vertex = this.vertices[v];
			var vertexPos = vertex.pos;
			var curveControlPoints = vertex.curveControlPoints;
			var vertexAsString = 
				vertexPos.x + "," + vertexPos.y + " ";

			if (curveControlPoints != null)
			{
				vertexAsString += "C ";

				for (var ccp = 0; ccp < 2; ccp++)
				{
					var curveControlPoint = curveControlPoints[ccp];
					vertexAsString += 
						curveControlPoint.x + "," 
						+ curveControlPoint.y + " ";
				}
			}

			pathAsSVG += vertexAsString;
 		}
		if (this.isClosed == true)
		{
			pathAsSVG += "Z";
		}
		pathAsSVG += "' \n";
		pathAsSVG += "\t\t/>\n";
		pathAsSVG += "\t</g>\n";
		pathAsSVG += "</svg>";
 
		return pathAsSVG;
	} 
}

function PathVertex(pos, curveControlPoints)
{
	this.pos = pos;
	this.curveControlPoints = curveControlPoints;
}
 
</script>
 
</body>
</html>

Posted in Uncategorized | Tagged , , , , , , | Leave a comment

Drawing Ellipses (Ovals) to an HTML5 Canvas with JavaScript

The JavaScript code below, when run, prompts the user to input information about an ellipse (oval), and then draws that ellipse to the screen. To see it in action, copy it into an .html file and open that file in a web browser that runs JavaScript.

UPDATE 2017/08/21 (LATER) – I have modified the code significantly, to correct the inaccurate usage of “major axis”, when what it really meant was “semimajor” axis, and the same for the minor axis. In fact, I just replaced any references to “major” and “minor” with “width” and “height”, since before the user could easily specify a minor axis longer than the major one. The program also now accepts rotation in radians rather than in turns, since that is more standard. Finally, the same ellipse-drawing code is now called from both the original “fillEllipse” function and the new “strokeEllipse” function.

DrawOval.png


<html>
<body>
 
<div id="divUI">

	<label><b>Ellipse Renderer</b></label><br />
 
	<label>Canvas Size:</label> 
	<input id="inputCanvasSizeX" type="number" value="100"></input>
	<label>x</label>
	<input id="inputCanvasSizeY" type="number" value="100"></input>
	<br />
 
	<label>Center Position:</label> 
	<input id="inputCenterX" type="number" value="50"></input>
	<label>x</label>
	<input id="inputCenterY" type="number" value="50"></input>
	<br />
 
	<label>Ellipse Size:</label>
	<input id="inputEllipseSizeX" type="number" value="60"></input>
	<label>x</label>
	<input id="inputEllipseSizeY" type="number" value="40"></input>
	<br />

	<label>Rotation in Radians:</label>
	<input id="inputRotationInRadians" type="number" value=".3"></input>
	<br />

	<button onclick="buttonDrawEllipse_Clicked();">Draw Ellipse</button>

	<div id="divOutput"></div>

</div>

<script type="text/javascript">

function buttonDrawEllipse_Clicked()
{ 
	var inputNames = 
	[
		"CanvasSizeX",
		"CanvasSizeY",
		"CenterX",
		"CenterY",
		"EllipseSizeX",
		"EllipseSizeY",
		"RotationInRadians"
	];
	
	var inputLookup = [];
	
	for (var i = 0; i < inputNames.length; i++)
	{
		var inputName = inputNames[i];
		var inputID = "input" + inputName;
		var input = document.getElementById(inputID);
		var inputValueAsFloat = parseFloat(input.value);
		inputLookup[inputName] = inputValueAsFloat;
	}

	var canvas = document.createElement("canvas");
	canvas.width = inputLookup["CanvasSizeX"];
	canvas.height = inputLookup["CanvasSizeY"];
	var divOutput = document.getElementById("divOutput");
	divOutput.innerHTML = "";
	divOutput.appendChild(canvas);
 
	var graphics = canvas.getContext("2d");	

	graphics.strokeRect(0, 0, canvas.width, canvas.height);

	var centerX = inputLookup["CenterX"];
	var centerY = inputLookup["CenterY"];
	var width = inputLookup["EllipseSizeX"];
	var height = inputLookup["EllipseSizeY"];
	var rotationInRadians = inputLookup["RotationInRadians"];

	graphics.fillStyle = "Cyan"; 
	graphics.fillEllipse
	(
		centerX,
		centerY,
		width,
		height,
		rotationInRadians
	);

	graphics.strokeStyle = "Black"; 
	graphics.strokeEllipse
	(
		centerX,
		centerY,
		width,
		height,
		rotationInRadians
	);
 
}
 
function CanvasRenderingContext2DExtensions()
{
	// extension class
}
{
	// helpers

	CanvasRenderingContext2D.prototype.pathEllipse = function
	(
		centerX,
		centerY,
		width,
		height,
		rotationInRadians
	)
	{
		this.save();
	
		this.translate(centerX, centerY);

		this.rotate(rotationInRadians); 

		var ratioOfHeightToWidth = height / width;
		this.scale(1, ratioOfHeightToWidth);
			
		this.beginPath();
		this.arc
		(
			0, 0, // center
			width / 2, // "radius"
			0, Math.PI * 2.0 // start, stop angle
		);
		this.fill();
	
		this.restore();
	}

	// extensions

	CanvasRenderingContext2D.prototype.fillEllipse = function
	(
		centerX, centerY, width, height, rotationInRadians
	)
	{
		this.pathEllipse
		(
			centerX, centerY, width, height, rotationInRadians
		);
		
		this.fill();		
	}
	
	CanvasRenderingContext2D.prototype.strokeEllipse = function
	(
		centerX, centerY, width, height, rotationInRadians
	)
	{
		this.pathEllipse
		(
			centerX, centerY, width, height, rotationInRadians
		);
		
		this.stroke();		
	}
 
}
 
</script>
 
</body>
</html>

Posted in Uncategorized | Tagged , , , , | Leave a comment

A Binary To Image File Transcoder In JavaScript

The JavaScript program below, when run, allows the user to load an arbitrary file as bytes, convert it to a PNG image, save the image as a file, reload that image file, convert it back to bytes, and re-save the bytes. To see it in action, copy it into an .html file and open that file in a web browser that runs JavaScript.

UPDATE 2017/08/17 – I have updated the program to encode the number of bytes (up to 16MiB) in the upper-left pixel, leaving the rest of the first line blank.


<html>
<body>

<div id="divUI">

	<p><b>Binary File to Image Transcoder:</b></p>


	<div>
		<label>Filename:</label>
		<input id="inputFileName" value="Data"></input>
	</div>

	<div>
		<label>Bytes:</label><br />
		<input type="file" onchange="inputFileAsBytes_Changed(this);"></input><br />
		<textarea id="textareaBytesAsHexadecimal" cols="32" rows="16"></textarea><br />
		<button onclick="buttonBytesSaveToFile_Clicked();">Save Bytes</button><br />
	</div>

	<button onclick="buttonBytesToImage_Clicked();">Bytes to Image</button>
	<button onclick="buttonImageToBytes_Clicked();">Image to Bytes</button>

	<div>
		<label>Image:</label><br />
		<input type="file" onchange="inputFileAsImage_Changed(this);"></input><br />
		<div id="divImageDisplay"></div><br />
		<button onclick="buttonImageSaveToFile_Clicked();">Save Image</button><br />
	</div>

</div>

<script type="text/javascript">

// ui event handlers

function bytesFromTextarea()
{
	var textareaBytesAsHexadecimal = 
		document.getElementById("textareaBytesAsHexadecimal");
	var bytesAsStringHexadecimal = textareaBytesAsHexadecimal.value;

	var bytes = [];

	var numberOfNibbles = bytesAsStringHexadecimal.length

	for (var b = 0; b < numberOfNibbles; b += 2)
	{
		var byteAsStringHexadecimal = bytesAsStringHexadecimal.substr(b, 2);
		var byte = parseInt(byteAsStringHexadecimal, 16);
		bytes.push(byte);
	}

	return bytes;
}

function buttonBytesSaveToFile_Clicked()
{
	var inputFileName = document.getElementById("inputFileName");
	var fileName = inputFileName.value + ".bin";

	var bytes = bytesFromTextarea();

	new FileHelper().saveBytesToFileWithName(bytes, fileName);
}

function buttonBytesToImage_Clicked()
{
	var bytes = bytesFromTextarea();

	var numberOfBytes = bytes.length;
	
	var bytesPerPixel = 3;

	var numberOfPixels = Math.ceil(numberOfBytes / bytesPerPixel);
	
	var imageWidthInPixels 
		= Math.ceil(Math.sqrt(numberOfPixels));
	var imageHeightInPixels 
		= Math.ceil(numberOfPixels / imageWidthInPixels);
	var imageSizeInPixels 
		= new Coords(imageWidthInPixels, imageHeightInPixels + 1);

	var canvas = document.createElement("canvas");
	canvas.width = imageSizeInPixels.x;
	canvas.height = imageSizeInPixels.y;

	var graphics = canvas.getContext("2d");

	var numberOfBytes = bytes.length;

	// Max file size is 2^24 B = 16 MiB.
	var numberOfBytesMax = 2 << 23.

	if (numberOfBytes > numberOfBytesMax)
	{
		alert("File must not be larger than " + numberOfBytesMax + " bytes.");
		return;
	}

	// Store the file's length in bytes in the upper-left pixel.

	var numberOfBytesAsRGB = 
	[
		(numberOfBytes >> 16) & 0xff,
		(numberOfBytes >> 8) & 0xff,
		(numberOfBytes) & 0xff
	];
	var pixelColorAsString = 
		"rgb(" + numberOfBytesAsRGB.join(",") + ")";

	graphics.fillStyle = pixelColorAsString;
	graphics.fillRect(0, 0, 1, 1);


	// Store 3 bytes per pixel as RGB components.

	var byteIndex = 0;

	for (var y = 1; y < imageSizeInPixels.y; y++)
	{
		for (var x = 0; x < imageSizeInPixels.x; x++)
		{
			var pixelColorRGB = [0, 0, 0];

			for (var b = 0; b < bytesPerPixel; b++)
			{
				if (byteIndex < numberOfBytes)
				{
					var byte = bytes[byteIndex];
					pixelColorRGB[b] = byte;
				}

				byteIndex++;
			}

			var pixelColorAsString = 
				"rgb(" + pixelColorRGB.join(",") + ")";

			graphics.fillStyle = pixelColorAsString;
			graphics.fillRect(x, y, 1, 1);
		}
	}

	var imageAsDataURL = canvas.toDataURL("image/png");
	var imgElement = document.createElement("img");
	imgElement.onload = function(event2)
	{
		var divImageDisplay = document.getElementById
		(
			"divImageDisplay"
		);
		divImageDisplay.innerHTML = "";
		divImageDisplay.appendChild(imgElement);
	}
	imgElement.src = imageAsDataURL;
}

function buttonImageSaveToFile_Clicked()
{
	var divImageDisplay = document.getElementById
	(
		"divImageDisplay"
	);
	var imgElement = divImageDisplay.getElementsByTagName("img")[0];

	if (imgElement == null)
	{
		alert("No image specified!");
	}

	var canvas = document.createElement("canvas");
	canvas.width = imgElement.width;
	canvas.height = imgElement.height;
	var graphics = canvas.getContext("2d");

	graphics.drawImage(imgElement, 0, 0);

	var inputFileName = document.getElementById("inputFileName");
	var fileName = inputFileName.value;

	new FileHelper().saveCanvasToFileWithName
	(
		canvas, 
		fileName + ".png"
	);
}

function buttonImageToBytes_Clicked()
{
	var divImageDisplay = document.getElementById("divImageDisplay");
	var imgElement = divImageDisplay.getElementsByTagName("img")[0];

	if (imgElement == null)
	{
		alert("No image specified!");
		return;
	}

	var imageSizeInPixels = new Coords
	(
		imgElement.width,
		imgElement.height
	);

	var canvas = document.createElement("canvas");
	canvas.width = imageSizeInPixels.x;
	canvas.height = imageSizeInPixels.y;

	var graphics = canvas.getContext("2d");

	graphics.drawImage(imgElement, 0, 0);

	var bytesPerPixel = 3;

	var bytes = [];

	var pixelColorsRGBA = graphics.getImageData
	(
		0, 0, imageSizeInPixels.x, imageSizeInPixels.y
	).data;

	// Decode the file's length in bytes from the upper-left pixel.

	var numberOfBytesAsRGB = pixelColorsRGBA.slice(0, 3);

	var numberOfBytes = 
		(numberOfBytesAsRGB[0] << 16)
		+ (numberOfBytesAsRGB[1] << 8)
		+ (numberOfBytesAsRGB[2]);

	var byteIndex = 0;

	for (var y = 1; y < imageSizeInPixels.y; y++)
	{
		for (var x = 0; x < imageSizeInPixels.x; x++)
		{
			var pixelIndex = 
				y * imageSizeInPixels.x 
				+ x;

			var pixelColorRGBA = pixelColorsRGBA.slice
			(
				pixelIndex * 4, 
				pixelIndex * 4 + 4
			);

			for (var b = 0; b < bytesPerPixel; b++)
			{
				var byte = pixelColorRGBA[b];

				bytes[byteIndex] = byte;

				byteIndex++;
			}
		}
	}

	bytes.length = numberOfBytes;

	var bytesAsHexadecimal = "";

	for (var b = 0; b < numberOfBytes; b++)
	{
		var byte = bytes[b];
		var byteAsHexadecimal = byte.toString(16);
		while (byteAsHexadecimal.length < 2)
		{
			byteAsHexadecimal = "0" + byteAsHexadecimal;
		}
		bytesAsHexadecimal += byteAsHexadecimal;
	}

	var textareaBytesAsHexadecimal = document.getElementById
	(
		"textareaBytesAsHexadecimal"
	);

	textareaBytesAsHexadecimal.value = bytesAsHexadecimal;

}

function inputFileAsBytes_Changed(inputFileAsBytes)
{
	var file = inputFileAsBytes.files[0];

	var fileReader = new FileReader();
	fileReader.onload = function(event)
	{
		var fileAsBinaryString = event.target.result;

		var numberOfBytes = fileAsBinaryString.length;

		var bytesAsHexadecimal = "";

		for (var b = 0; b < numberOfBytes; b++)
		{
			var byte = fileAsBinaryString.charCodeAt(b);
			var byteAsHexadecimal = byte.toString(16);
			while (byteAsHexadecimal.length < 2)
			{
				byteAsHexadecimal = "0" + byteAsHexadecimal;
			}
			bytesAsHexadecimal += byteAsHexadecimal;
		}

		var textareaBytesAsHexadecimal = document.getElementById
		(
			"textareaBytesAsHexadecimal"
		);

		textareaBytesAsHexadecimal.value = 
			bytesAsHexadecimal;
		
	}
	fileReader.readAsBinaryString(file);
}

function inputFileAsImage_Changed(inputFileAsImage)
{
	var file = inputFileAsImage.files[0];

	var fileReader = new FileReader();
	fileReader.onload = function(event)
	{
		var imageAsDataURL = event.target.result;
		var imgElement = document.createElement("img");
		imgElement.onload = function(event2)
		{
			var divImageDisplay = document.getElementById
			(
				"divImageDisplay"
			);
			divImageDisplay.innerHTML = "";
			divImageDisplay.appendChild(imgElement);
		}
		imgElement.src = imageAsDataURL;
	}
	fileReader.readAsDataURL(file);
}

// classes

function Coords(x, y)
{
	this.x = x;
	this.y = y;
}

function FileHelper()
{
	// do nothing
}
{
	FileHelper.prototype.saveBytesToFileWithName = function(bytesToWrite, fileNameToSaveAs)
	{
		var bytesToWriteAsArrayBuffer = new ArrayBuffer(bytesToWrite.length);
		var bytesToWriteAsUIntArray = new Uint8Array(bytesToWriteAsArrayBuffer);
		for (var i = 0; i < bytesToWrite.length; i++) 
		{
			bytesToWriteAsUIntArray[i] = bytesToWrite[i];
		}
  
		var bytesToWriteAsBlob = new Blob
		(
			[ bytesToWriteAsArrayBuffer ], 
			{ type:"application/type" }
		);
  
		var link = document.createElement("a");
		link.href = window.URL.createObjectURL(bytesToWriteAsBlob);
		link.download = fileNameToSaveAs;
		link.click();
	}

	FileHelper.prototype.saveCanvasToFileWithName = function(canvas, fileNameToSaveAs)
	{
 		var imageFromCanvasURL = canvas.toDataURL("image/png");
 
		var imageAsByteString = atob(imageFromCanvasURL.split(',')[1]);
		var imageAsArrayBuffer = new ArrayBuffer(imageAsByteString.length);
		var imageAsArrayUnsigned = new Uint8Array(imageAsArrayBuffer);
		for (var i = 0; i < imageAsByteString.length; i++) 
		{
			imageAsArrayUnsigned[i] = imageAsByteString.charCodeAt(i);
		}
 
		var imageAsBlob = new Blob([imageAsArrayBuffer], {type:"image/png"});
 
		if (fileNameToSaveAs.toLowerCase().endsWith(".png") == false)
		{
			fileNameToSaveAs += ".png";
		}
 
		var link = document.createElement("a");
		link.href = window.URL.createObjectURL(imageAsBlob);
		link.download = fileNameToSaveAs;
		link.click();
	}

}

</script>

</body>
</html>

Posted in Uncategorized | Tagged , , , | Leave a comment

A Rudimentary JavaScript Minifier

The code below implements a rudimentary JavaScript minifier. A “minifier” takes a code listing and removes unnecessary space in it, primarily by renaming functions and variables using shorter names. Minifying JavaScript allows it to be transported and compiled more efficiently.

This implementation is fairly primitive. It’s unlikely to work for code that references more built-in functions and objects, or that declares more that about 52 variables or functions. Like many of the programs on this site, it is intended mostly as a basis for further work.

To see the code in action, copy it into an .html file and open that file in a web browser that runs JavaScript.


<html>
<body>

<div id="divUI">

	<label><b>JavaScript Minifier</b></label>
	
	<p>Enter JavaScript code in the textbox and click the button to minify it.</p>

	<label>Keywords to Ignore:</label><br />
	<textarea id="textareaKeywordsToIgnore" cols="80" rows="8">
alert
function
var
return
	</textarea><br />

	<label>Code to Minify:</label><br />
	<textarea id="textareaCodeToMinify" cols="80" rows="16">
// This is a test!

function add(addend0, addend1)
{
	var result = addend0 + addend1;
	return result;
}

var addend0 = 1; // test comment
var addend1 = 2;

var sum = add(addend0, addend1);

alert("The sum of " + addend0 + " and " + addend1 + " is " + sum);

	</textarea><br />

	<button onclick="buttonMinify_Clicked();">Minify</button><br />

	<label>Code after Minification:</label><br />
	<textarea id="textareaCodeMinified" cols="80" rows="8"></textarea><br />

</div>

<script type="text/javascript">

// ui event handlers

function buttonMinify_Clicked()
{
	var textareaKeywordsToIgnore = 
		document.getElementById("textareaKeywordsToIgnore");

	var keywordsToIgnoreAsString = 
		textareaKeywordsToIgnore.value;
	var keywordsToIgnore = 
		keywordsToIgnoreAsString.split("\n"); 

	var textareaCodeToMinify = 
		document.getElementById("textareaCodeToMinify");

	var codeToMinify = textareaCodeToMinify.value;

	var minifier = new Minifier(keywordsToIgnore);

	var codeMinified = minifier.minifyCode(codeToMinify);

	var textareaCodeMinified = 
		document.getElementById("textareaCodeMinified");

	textareaCodeMinified.value = codeMinified;
}

// classes

function Minifier(keywords)
{
	this.keywords = keywords;

	var identifiersMinifiedAsString = 
		"a;b;c;d;e;f;g;h;i;j;k;l;m;n;o;p;q;r;s;t;u;v;w;x;y;z;"
		+ "A;B;C;D;E;F;G;H;I;J;K;L;M;N;O;P;Q;R;S;T;U;V;W;X;Y;Z";

	this.identifiersMinified = 
		identifiersMinifiedAsString.split(";");
}
{
	Minifier.prototype.minifyCode = function(codeToMinify)
	{
		var codeAsLines = codeToMinify.split("\n");
		var codeAsLinesMinusComments = [];

		for (var i = 0; i < codeAsLines.length; i++)
		{
			var codeLine = codeAsLines[i];
			var indexOfDoubleSlash = codeLine.indexOf("//");

			var codeLineMinusComments;

			if (indexOfDoubleSlash == -1)
			{
				codeLineMinusComments = codeLine;
			}
			else
			{
				codeLineMinusComments = 
					codeLine.substr(0, indexOfDoubleSlash);
			}

			codeAsLinesMinusComments.push
			(
				codeLineMinusComments
			);

		}

		var codeMinusComments = codeAsLinesMinusComments.join("\n");

		var tokenizer = new Tokenizer();

		var codeAsTokens = 
			tokenizer.tokenizeString(codeMinusComments);

		var identifierLookup = [];

		for (var k = 0; k < this.keywords.length; k++)
		{
			var keyword = this.keywords[k];
			identifierLookup[keyword] = keyword + " ";	
		}

		var codeMinifiedAsTokens = [];

		for (var t = 0; t < codeAsTokens.length; t++)
		{
			var tokenToMinify = codeAsTokens[t];

			var tokenMinified;
		
			if (tokenToMinify.isIdentifier() == false)
			{
				tokenMinified = tokenToMinify;
			}
			else
			{
				tokenMinified = identifierLookup[tokenToMinify];

				if (tokenMinified == null)
				{
					var numberOfIdentifiersSoFar = 
						identifierLookup.length;

					tokenMinified = 
						this.identifiersMinified[numberOfIdentifiersSoFar];
	
					identifierLookup[tokenToMinify] = tokenMinified;
	 				identifierLookup.push(tokenToMinify);
				}
			}

			codeMinifiedAsTokens.push(tokenMinified);
		}

		var codeMinified = codeMinifiedAsTokens.join(""); // todo

		return codeMinified;
	}
}

function StringHelper()
{
	// do nothing
}
{
	String.prototype.isLetter = function()
	{
		var charCode = this.charCodeAt(0);
		var returnValue = 
		(
			(
				charCode >= "A".charCodeAt(0) 
				&& charCode <= "Z".charCodeAt(0) 
			)
			||
			(
				charCode >= "a".charCodeAt(0) 
				&& charCode <= "z".charCodeAt(0) 
			)

		);

		return returnValue;
	}

	String.prototype.isIdentifier = function()
	{
		var returnValue = 
		( 
			this.length >= 1
			&& isNaN( parseFloat(this) ) 
			&& this[0].isLetter()
		);

		return returnValue;
	}

	String.prototype.isNumber = function()
	{
		var returnValue = 
		(
			isNaN(parseFloat(this)) == false
			|| this == "."
		)

		return returnValue;
	}

	String.prototype.isWhitespace = function()
	{
		var returnValue = true;

		for (var i = 0; i < this.length; i++)
		{
			var char = this[i];
			if 
			(
				char != " "
				&& char != "\t"
				&& char != "\r"
				&& char != "\n"
			)
			{
				returnValue = false;
				break;
			}
		}

		return returnValue;
	}
}

function Tokenizer()
{
	// do nothing
}
{
	Tokenizer.prototype.tokenizeString = function(stringToTokenize)
	{
		var tokensSoFar = [];

		var tokenInProgress = "";

		var isWithinQuotes;

		for (var i = 0; i < stringToTokenize.length; i++)
		{
			var char = stringToTokenize[i];
			
			if (isWithinQuotes == true)
			{
				tokenInProgress += char;

				if (char == "\"")
				{
					isWithinQuotes = false;
					tokensSoFar.push(tokenInProgress);
					tokenInProgress = "";
				}
				else if (char == "\\")
				{
					i++;
					var charNext = stringToTokenize[i];
					tokenInProgress += charNext;
				}
			}
			else if (char.isWhitespace())
			{
				if (tokenInProgress.length > 1)
				{
					tokensSoFar.push(tokenInProgress);
					tokenInProgress = "";
				}
			}
			else if (char.isLetter() || char.isNumber())
			{
				tokenInProgress += char;	
			}
			else if (char == "\"")
			{
				isWithinQuotes = true;
				tokenInProgress += char;
			}
			else
			{
				tokensSoFar.push(tokenInProgress);
				tokensSoFar.push(char);
				tokenInProgress = "";
			}
		}		

		return tokensSoFar;
	}
}

</script>

</body>
</html>

Posted in Uncategorized | Tagged , , , | Leave a comment

Hello World with the Bootstrap Web UI Framework

The instructions below can be followed to build and run a minimal web page using Bootstrap. Bootstrap is a free and open source web UI development framework originally created by developers working at Twitter.

(To be honest, there’s not much to this post at the moment. When I tried to do a more complicated example than Hello World, the layout stopped working as expected. Nonetheless, I’ll post what I have for future reference.)

1. In any convenient location, create a new directory named “BootstrapTest”.

2. Download the package for Bootstrap into the newly created BootstrapTest directory. As of this writing, the latest version is available for download as a .zip file at the URL “https://getbootstrap.com/”.

3. Extract the contents of the downloaded .zip file using any convenient .zip extraction utility, and rename the resulting directory to “bootstrap”.

4. In the BootstrapTest directory, create a new text file named “BootstrapTest.html”, containing the following text. This code is minimally adapted from a sample found on the official Bootstrap site at the URL “https://getbootstrap.com/”.


<!-- This code is minimally adapted from a sample -->
<!-- found on the official Bootstrap site -->
<!-- at the URL https://getbootstrap.com. -->

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
    <title>Bootstrap 101 Template</title>

    <!-- Bootstrap -->
    <link href="bootstrap/css/bootstrap.min.css" rel="stylesheet">

    <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
    <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
    <!--[if lt IE 9]>
      <script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script>
      <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
    <![endif]-->
  </head>
  <body>
    <h1>Hello, world!</h1>

    <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
    <!-- Include all compiled plugins (below), or include individual files as needed -->
    <script src="bootstrap/js/bootstrap.min.js"></script>
  </body>
</html>


5. Open the BootstrapTest.html file created in the previous step in a web browser that runs JavaScript. Verify that the message “Hello, World” appears.

Posted in Uncategorized | Tagged , , , | Leave a comment

Installing and Running Magento

The instructions below can be followed to install and run a sample e-commerce site using Magento. Magento is an open-source e-commerce platform implemented in PHP and originally developed by Varien, Inc.

1. Download and install XAMPP, a software package which incorporates the Apache web server, the MariaDB database, and the PHP server-side web language, all of which will be necessary to install Magento. As of this writing, the latest version of XAMPP is available for download from the URL “https://www.apachefriends.org/download.html”.

2. Install XAMPP. When prompted, specify and make a note of the password for the “root” user of the MariaDB database.

3. Initiate a MySQL client session somehow, perhaps by opening a command prompt and entering the command “mysql -u root –password”. Enter the password specified in the previous step when prompted.

4. At the MariaDB session prompt, enter the command “create database magento;”.

5. Download Magento. As of this writing, the latest version is available by visiting the URL “https://github.com/OpenMage/magento-mirror”, clicking on the “Clone or Download” button, and clicking the “Download ZIP” button.

6. Extract Magento from the downloaded .zip file using any convenient ZIP utility, perhaps 7-Zip.

7. Rename the extracted Magento directory to “magento” and copy it into Apache’s “htdocs” directory (perhaps at “C:\xampp\htdocs”).

8. Enable PHP’s “soap” extension by locating the file “php.ini” (perhaps at “C:\xampp\php\php.ini”) and editing it to remove the initial semicolon from the line “;extension=php_soap.dll”. This will uncomment the line, which should enable the extension.

9. Still within php,ini, locate the max_execution_time variable and increase its value significantly, perhaps to 120 (seconds). Save the file. This change may prevent a premature timeout error during the installation process.

10. Start or restart the Apache server, perhaps by starting the XAMPP control panel and clicking the “Start” button on the line for Apache. This will apply the changes from the previous steps to the PHP and web server setup.

11. Open a web browser and navigate to the Magento Installation Wizard at the URL “http://127.0.0.1/magento”. (Do not use “localhost”, as the Magento installer does not seem to handle this alias correctly.)

12. Follow the prompts to install Magento, and make a note of the encryption key generated by Magento in the final step. (The installation process is by no means robust, and following any errors it may be necessary to discard and replace te magento directory under htdocs, drop and re-create the “magento” database in MySQL, restart Apache, and re-start the installation. If errors do occur, Magento will likely suppress any details from appearing in the browser, so it may be helpful to look in the “var/reports” directory under the “magento” directory to see the actual error text.)

13. On the final page of the Magento Installation Wizard, click the button labeled “Go to Backend”. On the page that appears in a new browser tab, log in using the admin account created in the previous step. Dismiss any popup notifications, and verify that the Magento Admin Panel user interface appears as expected.

14 .Back on the final page of the Magento Installation Wizard, click the button labeled “Go to Frontend”. Verify that the default Magento frontend user interface appears.

Posted in Uncategorized | Tagged , | Leave a comment