Graphical haiku

16 April 1997

Various and sundry little display hacks are floating around in the collective knowledge of computer scientists everywhere. Some are found in signatures, others are tucked deeply into the twisted folds of entries in the IOCCC, countless others are undoubtedly languishing in anonymity on some lonely hard drive. This week I've put together a collection of interesting graphics hacks I've encountered in various situations. Some I've only seen and had to reverse-engineer; some are from screen savers; others have just managed to ooze into my memory by osmosis.

I've tried to boil them down to their very essence for presentation in this article. To that extent, I present the core of the algorithm for your perusal in the text, and I've tried to encapsulate all the system-specific stuff into simple calls from that algorithm. The real source is only a click away, so feel free to peruse that if you feel something is not quite clear from the code excerpt. Without further ado, I present hacks:

Bent over backward
First, we'll warm up with something easy. If you haven't seen this one, you shouldn't have much trouble figuring out what it's going to do from a glance at the code.

for (int x = 0, y = 0; x < width;
  x += width/LINES, y += height/LINES) {
  drawLine(0, y, x, height);
  drawLine(width, height-y, width-x, 0);
}

Now that you've visualized it, take a look at the real thing1. Pretty cute, isn't it.

Hey, I've got curtains like that!
Now we'll break out the tried-and-true tool of recursion2. This one is no head-scratcher, but it sure looks cool.

void deco (int x, int y, int w, int h, int depth)
{
  if ((random(MAXDEPTH) > depth) || (w < 8) || (h < 8)) {
    drawRectangle(x, y, w, h, _colors[random(COLORS)]);

  } else {
    if (random(100) > 50) {
      deco(x, y, w/2, h, depth-1);
      deco(x+w/2, y, w/2, h, depth-1);
    } else {
      deco(x, y, w, h/2, depth-1);
      deco(x, y+h/2, w, h/2, depth-1);
    }
  }
}

It all starts with one call like so:

deco(0, 0, width, height, MAXDEPTH);

Got it figured out yet? If you don't associate the `70s with some sort of bad experience, you should be able to fully appreciate this hack. Go ahead and have a look at the output3. Clicking on the applet will cause it to generate different output4.

The Golden Spiral
Ok, enough with the easy stuff. Here's something that's just plain non-intuitive. Definitely evaluate the loop by hand a few times and figure out what the code is doing, because it will be all the more pleasing when you actually see what is displayed. To clarify, the drawCircle() function call takes an x coordinate, a y coordinate, a diameter and a color. Don't worry about the fact that the size of the circle is increasing as the loop iterates, though; this is just for effect and isn't crucial to the algorithm.

double GOLDEN_NUMBER = (Math.sqrt(5.0)-1.0)/2.0;
double GOLDEN_ANGLE  = 2*Math.PI * (1.0-GOLDEN_NUMBER);

double side = (double)Math.min(width, height), th = 0.0;
for (double r = 0.05; r < 0.5; r += 0.001) {
  drawCircle((int)(r * Math.sin(th) * side) + width/2,
             (int)(r * Math.cos(th) * side) + height/2,
             Math.max(side / 25.0 * Math.sqrt(r), 1.0),
             colors[(int)(r * 2 * (double)COLORS)]);
  th = th + GOLDEN_ANGLE;
}

If you think you know what's going to happen, find out5 if you're right. This unexpected multiple-spiral effect is the result of the carefully chosen angle of increment6. If this pattern looks familiar to you, it should. It is the same pattern made by sunflower seeds as they grow on a sunflower. (Consequently, that is the motivation for the slowly increasing radii of the circles, to simulate the growth of the sunflower seed.) The golden mean is a fascinating topic, and much better introductions to the subject have been put together than I could present here.

Circular interference
To make up for the density of that last hack, this next one is nice and sparse. Unfortunately (or perhaps fortunately), that doesn't mean its results are any less interesting. This one, too, benefits from a close scrutiny of the code. Since the colors are important this time, I'll let you in on what is not explicitly shown in the code excerpt: The variable colors is an array of Color objects that are spread through the color spectrum to achieve a smooth transition from red to green to blue, back to red. The constant COLORS is the number of colors in the array (in this case, 64). Finally, the drawPixel() function takes a Color object as its first argument and draws the pixel in that color. So now that you've got the details, try to figure out what's going to happen.

int factor = random(10) + 1;
for (int y = 0; y < height; y++) {
  for (int x = 0; x < width; x++) {
    drawPixel(colors[((x*x+y*y)/factor) % COLORS], x, y);
  }
}

Not sure what in the heck is going on? Find out.7 If you click the mouse in the applet, it will choose a different random divisor and regenerate the display. Try it a couple of times. Knowing the divisor is chosen to be a random integer from 1 to 10 should clue you in to what's going on at the primary level. That still doesn't explain the crazy interference patterns.

Pixel-elation
This last one is a little longer than the others, but I think it still fares well enough by the display hack metric8. If you're worn out from deciphering the previous hacks, then it's all right if you skip the analysis this one time.

final static int[] _scale = { 1, 2, 4, 6, 10, 14, 18 };

int mutate (int value, int depth)
{
  return (value + random(_scale[depth]) -
          _scale[depth]/2 + COLORS) % COLORS;
}

int avg2 (int x0, int y0, int x1, int y1)
{
  return (getPixel(x0, y0) + getPixel(x1, y1))/2;
}

int avg4 (int x0, int y0, int x1, int y1)
{
  return (getPixel(x0, y0) + getPixel(x0, y1) +
          getPixel(x1, y0) + getPixel(x1, y1))/4;
}

for (int depth = 6; depth >= 0; --depth) {
  for (int y = 0; y < SIZE; y += (1 << depth)) {
    int ny = y + (1 << depth), hy = y + (1 << depth)/2;
    for (int x = 0; x < SIZE; x += (1 << depth)) {
      int nx = x + (1 << depth), hx = x + (1 << depth)/2;
      setPixel(mutate(avg2(x, y, nx, y), depth), hx, y);
      setPixel(mutate(avg2(x, y, x, ny), depth), x, hy);
      setPixel(mutate(avg4(x, y, nx, ny), depth), hx, y);
    }
  }
}

Have a look at this final example's output9. Pretty keen eh? In case you didn't figure it out for yourself, what's going on here is that the four corners of the square are filled with random colors. Then the color of each point halfway between those four corners (and the center point) is set to the average of the colors of the points it's between.

The catch is that it's not exactly the average. The color is mutated a little bit away from the exact average. In the first iteration, it has the possibility of being mutated a lot, however as we splice the squares further and further, the range of mutation becomes smaller and smaller. By the time we're mutating pixels that are right next to each other, the mutation range is down to one. So no two neighboring pixels have a sharp color difference.

E pluribus unix
I've put together a few interesting graphics hacks for your scrutiny and appreciation; hopefully, you've found them at least mildly enjoyable. So now I want you to pitch in and send me any of those clever little hacks you once saw and tucked away in a file for safekeeping. I'll put them together in subsequent articles like this one and everyone can read them and be amused. Don't worry if they're not written in Java, or even if they're not graphics hacks. I can take care of translating them into a Web-presentable format. Also, if you can point me toward a definitive author for anything you send in, that would be great; but I'd much rather have things unattributed than incorrectly attributed.

Happy hacking. *

-- Michael <mdb@go2net.com> just fell off the turnip truck (again).

Source code to the hacks as a gzipped tar file or a zip file.