2008-10-15

Top Drawing

I wrote the last time about simple ways to "just get something onto the screen", and I ended by exploring a simple Top Draw program. I hinted at the ease with which Top Draw allows compositing a complex "painting", taking more or less by fiat the examples included with the program. I since took advantage of the basic rosette drawing subroutine to make a wallpaper that I could actually install:

A desktop wallpaper generated with Top Draw

In my opinion there are two advantages to using Top Draw:

  1. It abstracts the compositing paradigm with a Photoshop-like Layer object,
  2. It allows to interleave procedural drawing with arbitrary Core Image filters,

thus leveraging the built-in capabilities of Mac OS X Quartz subsystem.

I first refactored my script so that the drawing code is independent of the global transform being applied:


function wheel(m, n) {
  if (m < 1 || n < 5) return;
  var color = new Color();
  color.alpha = 0.3;
  var a = Math.PI / n;
  var k = Math.cos(a), s = Math.sin(a);
  var t = (k + s) / (k - s);
  var d = k + s * t;
  var r = Math.pow(k - s, m);
  for (var e = 0; e != m; e++) {
    color.saturation = 0.5*(1 + e/m); // 0.5 -- 1.0
    color.brightness = 0.5*(2 - e/m); // 1.0 -- 0.5
    for (var i = 0; i != n; i++) {
      var b = (2*i + e) * a;
      var x = r * Math.cos(b), y = r * Math.sin(b);
      color.hue = i / n;
      desktop.fillStyle = color;
      desktop.beginPath();
      desktop.moveTo(x, y);
      desktop.lineTo(d * (k * x - s * y), d * (k * y + s * x));
      desktop.lineTo(t * x, t * y);
      desktop.lineTo(d * (k * x + s * y), d * (k * y - s * x));
      desktop.lineTo(x, y);
      desktop.fill();
    }
    r /= k - s;
  }
}

Gone are centers and offsets in each parameter to the drawing commands, as gone is the global scaling of the drawing. Then, I use random number generators to place the rosettes all over the screen:

desktop.fillLayer(new Color(0));
var db = desktop.bounds;

var xrnd = new Randomizer(0, db.width );
var yrnd = new Randomizer(0, db.height);

In order to give some depth to the final result, I'll blur each drawing a bit so that newer rosettes are sharper than older ones:

var f = new Filter("CIGaussianBlur");
f.setKeyValue("inputRadius", 2);

I'll draw a number of rosettes, four in this case, in random places:

for (var i = 1; i <= 4; i++) {
  var cx = xrnd.intValue;
  var cy = yrnd.intValue;

I make sure that the rosette fits the screen with the given center:

  var sz = Math.max(cx, cy, db.width - cx, db.height - cy);

In order to isolate each drawing from the modifications to the global transform, I save the drawing context prior to changing scale and center:

  desktop.save();
  desktop.translate(db.x + cx, db.y + cy);
  desktop.scale(sz, sz);

I then draw the rosette and restore the context. I could easily give random parameters to the drawing routine, but I find that by having uniform shapes the rosettes nicely stack one over another:

  wheel(8, 10);
  desktop.restore();

Finally, I apply the prepared filter to the resulting image:

  desktop.applyFilter(f);
}

As you can see, it's really easy to get good results with remarkably little code. Keep hitting Command-R until you find a result you like, and install it on your desktop.

1 comment:

Anonymous said...

Nice wallpaper!

Yet another nifty (command line) utility to apply Core Image filters is CoreImageTool!

http://codesnippets.joyent.com/posts/show/1731