A Particle-Based Texture Generator in JavaScript

The code included below generates and displays several swatches of procedurally-generated textures. To see it in action, copy it into a .html file and open that file in a web browser that runs JavaScript.

It’s still very much a work in progress. Only the “Rectangles” particle type is working right now, and there’s yet-unused code in there to build images from strings that came from a previous post. The “Ellipses” particle type might allow for some interesting effects, and it would be nice to have some way to control particle distribution, but I wasn’t able to figure out how all the transformations should work.

TexturesGenerated

<html>
<body>
<script type='text/javascript'>

// main

function TextureGeneratorTest()
{
	this.main = function()
	{
		var textureGenerator = new TextureGenerator();

		var textureSizeInPixels = new Coords(100, 100);

		var particleMethod = TextureGenerator.ParticleMethods.Rectangles;

		var textureImages = 
		[
			textureGenerator.buildTextureImage
			(
				"Sand",
				textureSizeInPixels, 
				1500, // numberOfParticles
				new Coords(3, 3), // particleSizeMin
				new Coords(3, 3), // particleSizeMax
				particleMethod
			),

			textureGenerator.buildTextureImage
			(
				"Cells",
				textureSizeInPixels, 
				800, // numberOfParticles
				new Coords(5, 5), // particleSizeMin
				new Coords(5, 5), // particleSizeMax
				particleMethod
			),

			textureGenerator.buildTextureImage
			(
				"Blotches",
				textureSizeInPixels, 
				500, // numberOfParticles
				new Coords(5, 5), // particleSizeMin
				new Coords(15, 15), // particleSizeMax
				particleMethod
			),

			textureGenerator.buildTextureImage
			(
				"Streaks",
				textureSizeInPixels, 
				100, // numberOfParticles
				new Coords(100, 5), // particleSizeMin
				new Coords(100, 5), // particleSizeMax
				particleMethod
			),

			textureGenerator.buildTextureImage
			(
				"Grain",
				textureSizeInPixels, 
				700, // numberOfParticles
				new Coords(50, 1), // particleSizeMin
				new Coords(30, 1), // particleSizeMax
				particleMethod
			)
		];

		for (var i = 0; i < textureImages.length; i++)
		{
			var textureImage = textureImages[i];

			document.write
			(
				textureImage.name + ":"
			);

			document.body.appendChild
			(
				textureImage.systemImage
			);
		}
	}
}

// classes

// classes

function Color(name, symbol, systemColor)
{
	this.name = name;
	this.symbol = symbol;
	this.systemColor = systemColor;
}
{
	// static methods

	Color.getBySymbol = function(symbolToGet)
	{
		var returnValue = Color.Instances._SymbolToColorLookup.getByKey(symbolToGet);
		return returnValue;
	}

	// instances

	function Color_Instances()
	{
		// Not sure why rgba is necessary, but it is.
		this.Transparent = new Color("Transparent", ".", "rgba(0, 0, 0, 0)");

		this.Black 	= new Color("Black", 	"k", "#000000");
		this.Blue 	= new Color("Blue", 	"b", "#0000ff");
		this.Gray 	= new Color("Gray", 	"a", "#808080");
		this.Green 	= new Color("Green", 	"g", "#00ff00");
		this.Orange 	= new Color("Orange", 	"o", "#ff8800");
		this.Purple 	= new Color("Purple", 	"p", "#ff00ff");
		this.Red 	= new Color("Red", 	"r", "#ff0000");
		this.White 	= new Color("White", 	"w", "#ffffff");
		this.Yellow 	= new Color("Yellow", 	"y", "#ffff00");

		this._All = new Array
		(
			this.Transparent,

			this.Black,
			this.Blue,
			this.Gray,
			this.Green,
			this.Orange,
			this.Purple,
			this.Red,
			this.White,
			this.Yellow	
		);

		for (var i = 0; i < this._All.length; i++)
		{
			var color = this._All[i];
			this._All[color.symbol] = color;
		}
	}	

	Color.Instances = new Color_Instances();
}

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

	Coords.NumberOfDimensions = 2;

	// instance methods

	Coords.prototype.absolute = function()
	{
		this.x = Math.abs(this.x);
		this.y = Math.abs(this.y);

		return this;
	}

	Coords.prototype.add = function(other)
	{
		this.x += other.x;
		this.y += other.y;

		return this;
	}

	Coords.prototype.clear = function()
	{
		this.x = 0;
		this.y = 0;

		return this;
	}

	Coords.prototype.clone = function()
	{
		var returnValue = new Coords(this.x, this.y);

		return returnValue;
	}

	Coords.prototype.dimension = function(dimensionIndex)
	{
		var returnValue;

		if (dimensionIndex == 0)
		{
			returnValue = this.x;
		}
		else
		{
			returnValue = this.y;
		}

		return returnValue;
	}

	Coords.prototype.directions = function()
	{
		if (this.x > 0)
		{
			this.x = 1;
		}
		else if (this.x < 0)
		{
			this.x = -1;
		}

		if (this.y > 0)
		{
			this.y = 1;
		}
		else if (this.y < 0)
		{
			this.y = -1;
		}

		return this;
	}

	Coords.prototype.divide = function(other)
	{
		this.x /= other.x;
		this.y /= other.y;

		return this;
	}

	Coords.prototype.divideScalar = function(scalar)
	{
		this.x /= scalar;
		this.y /= scalar;

		return this;
	}

	Coords.prototype.equals = function(other)
	{
		return (this.x == other.x && this.y == other.y);
	}

	Coords.prototype.floor = function()
	{
		this.x = Math.floor(this.x);
		this.y = Math.floor(this.y);

		return this;
	}

	Coords.prototype.magnitude = function()
	{
		var returnValue = Math.sqrt(this.x * this.x + this.y * this.y);

		return returnValue;
	}

	Coords.prototype.multiply = function(other)
	{
		this.x *= other.x;
		this.y *= other.y;

		return this;
	}

	Coords.prototype.multiplyScalar = function(scalar)
	{
		this.x *= scalar;
		this.y *= scalar;

		return this;
	}

	Coords.prototype.normalize = function()
	{
		return this.divideScalar(this.magnitude());
	}

	Coords.prototype.overwriteWith = function(other)
	{
		this.x = other.x;
		this.y = other.y;

		return this;
	}

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

		return this;
	}

	Coords.prototype.round = function()
	{
		this.x = Math.round(this.x);
		this.y = Math.round(this.y);

		return this;
	}

	Coords.prototype.subtract = function(other)
	{
		this.x -= other.x;
		this.y -= other.y;

		return this;
	}

	Coords.prototype.sumOfXAndY = function()
	{
		return this.x + this.y;
	}

	Coords.prototype.toString = function()
	{
		return "<Coords x='" + this.x + "' y='" + this.y + "' />"
	}

	Coords.prototype.trimToRange = function(rangeToTrimTo)
	{
		if (this.x < 0)
		{
			this.x = 0;
		}
		else if (this.x > rangeToTrimTo.x)
		{
			this.x = rangeToTrimTo.x;
		}

		if (this.y < 0)
		{
			this.y = 0;
		}
		else if (this.y > rangeToTrimTo.y)
		{
			this.y = rangeToTrimTo.y;
		}

		return this;
	}
}

function Image(name, systemImage)
{
	this.name = name;
	this.systemImage = systemImage;
}
{
	// instance methods

	Image.prototype.clone = function()
	{
		var returnValue = new Image();

		returnValue.filePath = this.filePath;
		returnValue.htmlElement = document.createElement("img");
		returnValue.htmlElement.src = this.htmlElement.src;

		return returnValue;
	}
}

function ImageHelper()
{}
{
	// static methods

	ImageHelper.buildImageFromStrings = function(scaleMultiplier, stringsForPixels)
	{
		var sizeInPixels = new Coords
		(
			stringsForPixels[0].length, stringsForPixels.length
		);

		var canvas = document.createElement("canvas");
		canvas.width = sizeInPixels.x * scaleMultiplier;
		canvas.height = sizeInPixels.y * scaleMultiplier;

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

		var pixelPos = new Coords(0, 0);
		var colorForPixel = Color.Instances.Transparent;

		for (var y = 0; y < sizeInPixels.y; y ++)
		{
			var stringForPixelRow = stringsForPixels[y];
			pixelPos.y = y * scaleMultiplier;

			for (var x = 0; x < sizeInPixels.x; x ++)
			{
				var charForPixel = stringForPixelRow[x];
				pixelPos.x = x * scaleMultiplier;

				colorForPixel = Color.getBySymbol(charForPixel);

				graphics.fillStyle = colorForPixel.systemColor;
				graphics.fillRect
				(
					pixelPos.x, pixelPos.y, 
					scaleMultiplier, scaleMultiplier
				);				
			}
		}

		var imageFromCanvasURL = canvas.toDataURL("image/png");
		var htmlImageFromCanvas = document.createElement("img");
		htmlImageFromCanvas.width = canvas.width;
		htmlImageFromCanvas.height = canvas.height;
		htmlImageFromCanvas.src = imageFromCanvasURL;

		var returnValue = new Image(htmlImageFromCanvas);

		return returnValue;
	}
}

function TextureGenerator()
{}
{
	TextureGenerator.prototype.buildTextureImage = function
	(
		name,
		sizeInPixels,
		numberOfParticles,
		particleSizeMin,
		particleSizeMax,
		particleMethod
	)
	{
		var canvas = document.createElement("canvas");
		canvas.width = sizeInPixels.x;
		canvas.height = sizeInPixels.y;

		var graphics = canvas.getContext("2d");
		graphics.fillStyle = "rgba(0, 0, 0, .1)";
		//graphics.strokeStyle = "rgba(0, 0, 0, .1)";

		var particleSizeRange = particleSizeMax.clone().subtract
		(
			particleSizeMin
		);

		for (var n = 0; n < numberOfParticles; n++)
		{
			var particleSize = new Coords
			(
				particleSizeMin.x + Math.random() * particleSizeRange.x, 
				particleSizeMin.y + Math.random() * particleSizeRange.y
			);

			var particlePos = new Coords
			(
				Math.floor(Math.random() * sizeInPixels.x),
				Math.floor(Math.random() * sizeInPixels.y)
			)

			particleMethod(graphics, particlePos, particleSize);
		}

		var imageFromCanvasURL = canvas.toDataURL("image/png");
		var htmlImageFromCanvas = document.createElement("img");
		htmlImageFromCanvas.width = canvas.width;
		htmlImageFromCanvas.height = canvas.height;
		htmlImageFromCanvas.src = imageFromCanvasURL;

		var returnValue = new Image(name, htmlImageFromCanvas);

		return returnValue;

	}

	TextureGenerator.ParticleMethods = new TextureGenerator_ParticleMethods()
	function TextureGenerator_ParticleMethods()
	{
		this.Ellipses = function(graphics, pos, size)
		{
			var scaleFactor = size.y / size.x;
			graphics.scale(1, scaleFactor);
			pos.divide(new Coords(scaleFactor, 1));
			graphics.beginPath();
			graphics.arc
			(
				pos.x, pos.y,
				size.x / 2,
				0, 2 * Math.PI
			);
			graphics.fill();
			graphics.closePath();
			graphics.restore();
		}

		this.Rectangles = function(graphics, pos, size)
		{
			pos.subtract
			(
				size.clone().divideScalar(2)
			);

			graphics.fillRect
			(
				pos.x, pos.y,
				size.x, size.y
			);
		}
	}
}

// run

new TextureGeneratorTest().main();

</script>
</body>
</html>
This entry was posted in Uncategorized and tagged , , , , . Bookmark the permalink.

4 Responses to A Particle-Based Texture Generator in JavaScript

  1. super says:

    i have a lot of jealous on you man

  2. super says:

    are you using json over here man?????????????

    • Er, not that I know of. Do you mean that syntax where you declare objects like:

      var myObject = { myPropertyOne : 1, myPropertyTwo: ‘Two’ };

      If so, I personally just don’t like that syntax. I guess it just doesn’t look enough like Java for me. I understand that you have to use it if you’re going to be a Real JavaScript Guy. I have no real interest in being one of those, though.

  3. super says:

    better you can write a own library man like query.Because you are much familiar with unobtrusive JavaScript right??? now iam learning unobtrusive JavaScript step by step

Leave a reply to thiscouldbebetter Cancel reply