JavaScript In Quartz Composer

Javascript in Quartz Composer can be a somewhat tricky subject. On the one hand, it's a powerful language that allowed for complex controls. On the other hand, Quartz Composer users typically aren't programmers, so the power is lost.

Before we begin our sojourn into Javascript in QC, let's briefly go over what Javascript Isn't, and what it Can't do.

  • Javascript (sometimes called ECMAScript) is NOT Java. It's not related at all to Java, and learning Java will not make you a good Javascript programmer.
  • Javascript cannot process images. You may see JS in other applications handle per-pixel operations, but this isn't the case in QC -- it's not possible, so don't bother.
  • Javascript is slow -- complex scripts will slow down your composition significantly.

Now, on with the show.

In Quartz Composer, data comes in 10 flavors:

  • Booleans
  • Indexes (positive integers)
  • Numbers (positive or negative non-integers)
  • Strings
  • Images
  • Colors
  • Structures
  • Interaction (creation of Interaction ports isn't supported by the javascript patch)
  • QCMesh (creation of Mesh ports isn't supported by the javascript patch)
  • Virtual (any of the above, or a custom type created by use of the private API. Creation of Virtual ports is supported by the javascript patch)

In Javascript, data is represented by "objects." An object can be as simple as a single letter, or as complex as a nested structure of structures.

Javascript in Quartz Composer can do interesting things with Booleans, Indexes, Numbers, Strings, Images, Structures, and Virtual Data. We'll go over some brief examples to show how this works.

Booleans

Booleans are the easiest type to work with -- they have only two states: 0, or 1. (true and false, on and off, you get the idea).

Here is a simple javascript that compares 2 numbers, a and b, and returns True if a is larger than b.

function (__boolean boolOutput) main (__number a, __number b)
{
   var result = new Object();
   if(a > b)
      result.boolOutput = true;
   else
      result.boolOutput = false;
   return result;
}

BoolJavascript.qtz

Indexes

Indexes are also very simple; they can represent any integer (whole number) value between 0 and 2,147,483,647 inclusive (that's 2^32-1, for those keeping score).

Here's a javascript that counts each time it executes

var count = 0;
function (__index indexOutput) main (__number ignored)
{
   var result = new Object();
   result.indexOutput = count;
   count = count + 1;
   return result;
}

Numbers

Numbers are very similar to indexes, except they can represent fractions, and can also be negative.

Strings

Strings are collections of letters and numbers. I'll post an example for this later (or someone else can).

Images

QC's javascript patch can be used to build structures of images, structures of images with additional metadata, etc.

An example that creates three image inputs and builds a custom queue with javascript would look like this:

function (__structure Queue) main (__image Value[3], __index Size)
{
   var result = new Object();
   _Queue.push([Value[0], Value[1], Value[2]]);
        if (_Queue.length > Size) _Queue.splice(0,_Queue.length-Size);   
   result.Queue = _Queue;
   return result;
}

A more complex example, used in the Developer Example "Image TV" helps to create a composition that scans a directory for photos, importing cycling through those images, while downsizing and creating two output structures for rendering:

var _lastSignal = false;
 
var _sharpImages = null;
var _blurredImages = null;
var _index = -1;
 
function (__structure sharpImages, __structure blurredImages, __string downloadURL, __number progress) main(__structure inList, __image sharpImage, __image blurredImage, __boolean downloadedSignal, __boolean resetSignal)
{
   var result = new Object();
 
   if(resetSignal) {
      _sharpImages = null;
      _blurredImages = null;
      _index = -1;
   }
 
   if(inList != null) {
      if((_index < 0) || (downloadedSignal && !_lastSignal)) {
         if(_index < 0) {
            _sharpImages = new Array(inList.length);
            _blurredImages = new Array(inList.length);
            _index = 0;
            result.progress = 0.0;
         }
         else {
            _sharpImages[_index] = sharpImage;
            _blurredImages[_index] = blurredImage;
            _index += 1;
         }
 
         if(_index < inList.length)
         result.downloadURL = inList[_index].url;
         else
         result.downloadURL = null;
 
         result.progress = _index / inList.length;
      }
      _lastSignal = downloadedSignal;
   }
 
   result.sharpImages = _sharpImages;
   result.blurredImages = _blurredImages;
 
   return result;
}

Structures

Generating/handling structures in Javascript requires some basic programming know-how.

The two new ideas are Objects and Arrays.

An Array is simply an indexed set of items. The indexes range from 0 to however many items there are minus 1.

For example, and Array with 4 items would have indexes 0, 1, 2, and 3.

Here's a simple example of an Array.

function (__number outputNumber) main (__number inputNumber[2])
{
   var result = new Object();
   result.outputNumber = inputNumber[0] + inputNumber[1];
   return result;
}

inputNumber is an array with 2 items, 0 and 1.

Objects are similar to arrays, except that instead of indexes, you use "keys". A key is a string or a number. Instead of using myArray[0], you could use myArray["something"], where "something" is the "key".

In Quartz Composer, structures come in 2 flavors: indexed, and keyed. Start to see a parallel? in JS, Arrays use Indexes, and Objects use Keys... So in Javascript, just think "Object = Keyed Structure" and "Array = Indexed Structure".

So in Javascript, there are 2 ways to make structures:

   var aStructure = new Array();
   var anotherStructure = new Object();

You can probably guess, but in the above snippet, aStructure will be an indexed array (0, 1, 2, ..., n), while anotherStructure will be a keyed structure (key0 : value0, key1: value1, ..., keyN: valueN).

Naming keys is extraordinarily simple:

   var keyStructure = new Object();
   keyStructure.cool = "42";
   keyStructure["rad"] = "4242";

Here we have 2 ways to set key/value pairs; we can do myStructure.TheKeyName = something, or we can do myStructure[TheKeyName]; They're different ways of accomplishing the same thing. The top version is nice if you have a specific name you want to use, while the bottom one is handy for loops.

Now we're going to get a bit crazy. So far, we've only put numbers and strings into Objects. However, you can also put Objects inside Objects.

   var myStructure = new Object();
   myStructure.something = new Object();
   myStructure.something.amazing = new Object();
   myStructure.something.amazing.here = "yes";

Any guesses what the above would produce? That's right, it's a structure with a key called "something", which contains a structure that has a key called "amazing", which contains a structure that has a key called "here" that holds the value "yes".

In Quartz Composer, that would look like this:

Virtual

Not many people are aware that QC's javascript patch has support for Virtual data, and one can create Virtual type ports to manipulate Virtual data in this way.

For example, if one has created a "custom data" type with the private API or "SkankySDK", one could attempt to input it into a QC Javascript patch by creating a Structure port, and passing the data that way. This will often work, but it will result in exceptions in the QuartzComposer.app Editor when one "hovers" over the tooltips.

Instead, one should create a Virtual port for input. Since Virtual data encompasses all types able to be sent in a "noodle" in QC, it would be extremely rare (and perhaps impossible) to have this result in an exception when hovering at a tooltip.

This is an example of a script that receives data from custom "K2DBezierPathPort", and inputs it into a Javascript patch in a proper way, producing a structure as output (found in Kineme2D's Sample Compositions download):

function (__structure array) main (__virtual element[4])
{
   var result = new Object();
   result.array = new Array();
   for (i = 0; i < 4; ++i)
      result.array[i] = element[i];
   return result;
}
PreviewAttachmentSize
NestedStructures.png
NestedStructures.png18.12 KB
BoolJavascript.qtz5.82 KB