the other dell

Introduction - Normal Distribution of Random Numbers pt 2 - Gaussian paint spray

The paint splatter exercises I did yesterday seemed a bit dull and overnight I realised it should be fairly easy to make them more interactive and fun. This post is a slight diversion from the book, or another interpretation of the previous exercise.

My aim is to create a Gaussian spray paint simulator. Starting from a blank canvas, the painter can spray where they like by holding down a mouse button when the pointer is over the cavas. Spray fans out from the clicked point in Gaussian distribution. This is roughly the same as the spray can tools in most art packages.

Colour will be picked at random with each successive spray.

In Processing

Processing provides built-in support for handling mouse events. I’ve already used the “live” mouseX and mouseY values, which Processing keeps constantly up to date for me.

To respond to the painter pressing a mouse button (which is the only other interaction I’m catering for in this exercise), I need to implement a mousePressed() function. Processing will use the function to handle mouse press events, if it’s present in the sketch. I want this function to pick a colour, and for that colour to last the duration of the mouse press. I moved the red, green and blue declarations out of the draw() function and into the “global” space, so they persist between successive calls to draw(), and set them to random values when the mouse is pressed. I also set the fill colour in the mousePressed() handler. I decided that the spray would be better in bolder colours so I set the opacity to 80.

I only want the canvas painted on when the mouse is pressed. The mousePressed flag will suffice for this. The mouse events example code I found uses a couple of semaphor flags to indicate various states, but my example is so simple it doesn’t need this. I updated the draw() function so that it would only draw ellipses when the mouse button was pressed. The x and y values of the paint splatters are picked at random using the mouseX and mouseY values as the mean for the Gaussian function.

import java.util.Random;

Random generator;
int red, green, blue;

void setup() {
  size(640,360);
  noStroke();
  generator = new Random();
}

float gaussian( float mu, float sigma ) {
  float num = (float) generator.nextGaussian();
  float ret = sigma * num + mu;

  return ret;
}

void draw() {
  if( !mousePressed ) return;

  float x = gaussian( mouseX, 10 );
  float y = gaussian( mouseY, 10 );
  ellipse( x, y, 8, 8 );
}

void mousePressed() {
  red = (int) gaussian( 125, 60 );
  green = (int) gaussian( 125, 60 );
  blue = (int) gaussian( 125, 60 );
  fill( red, green, blue, 80 );
}

In JavaScript

My “framework”, Frames, doesn’t have hooks for mouse events yet. It does expose some helpful mouse event properties, but as discussed before, the interface wasn’t comfortable.

One change I have made is to make the interface to Frames’ mouse properties nicer. Instead of calling an accessor, live properties can now be read like this:

Frames.mouse.x // will hold the mouse x co-ordinate over the canvas
Frames.mouse.y // will hold the mouse y co-ordinate over the canvas
Frames.mouse.pressed // will hold the boolean value reflecting the button pressed state

How does that work?

In the first version of this interface, the variables Frames used to track the values were simple numeric and boolean types. Javascript provides access to built-in, simple types, by copy. This means that if they were exported directly from the closure, the exposed property would only ever have the initial value of mouseX. In the case shown below, that would be undefined.

var Frames = (function () {
	var mouseX;
	// ...
	function handleMouseMove ( event ) {
		mouseX = event.pageX;
	}
	// ...

	return {
		mouseX: mouseX
	}
})();

However, JavaScript provides access to complex _objects_ by reference. This means that if an _object with properties_ is exported, the consumer will get a reference to the object, not a copy of it. The reference will allow the consumer to get at any child properties, even if they are updated from code within the closure.

```javascript
var Frames = ( function () {
	// ...

	var mouse = {
		x: null,
		y: null,
		pressed: false
	}


	//
	// event handlers
	//
	function setMouseXY ( event ) {
		mouse.x = event.pageX;
		mouse.y = event.pageY;
	}

	function setMousePressedTrue ( event ) {
		mouse.pressed = true;
	}

	function setMousePressedFalse ( event ) {
		mouse.pressed = false;
	}

	// ...

	return {
		mouse: mouse
		// ...
	};
})();

Back to the spray paint

I decided that, for now, I wouldn’t add any more hooks to Frames. In order to handle mouse events on the canvas, the application will add them itself.

The logic for picking new colours and turning spraying on and off is just the same as in the Processing vesrion.

(function( Frames, Random ){
	var canvasWidth = 640;
	var canvasHeight = 360;
	var context = null;
	var canvas = null;
	var mousedown = false;
	var red, green, blue;

	function setup () {
		context = Frames.setupCanvas( canvasWidth, canvasHeight );
		canvas = context.canvas;

		canvas.addEventListener( 'mousedown', handleMouseDown );
	}

	function draw () {
		var x, y;

		if( !Frames.mouse.pressed ) return;

		x = parseInt( Random.normal( Frames.mouse.x, 12 ), 10 );
		y = parseInt( Random.normal( Frames.mouse.y, 12 ), 10 );

		context.beginPath();
		context.arc( x, y, 8, 0, 2 * Math.PI );
		context.fill();
	}

	function handleMouseDown () {
		red = parseInt( Random.normal( 125, 40 ), 10 );
		green = parseInt( Random.normal( 125, 40 ), 10 );
		blue = parseInt( Random.normal( 125, 40 ), 10 );
		context.fillStyle = 'rgba('+ red +','+ green +','+ blue +',0.7 )';
	}

	Frames.go( setup, draw );

})( Frames, new Random() );

The paint splatters look better with 0.7 opacity than 0.8 (or 80, as it is specified in the equivalent Processing instruction).

Thoughts for further work

If I do add event hooks to Frames, I think an interface like this could be comfortable to work with:

Frames.addMouseUpListener( handlerCallback );
Frames.addMouseDownListener( handlerCallback );