Kaleidoscopes

how-to
Sep 19, 20059 mins

Enchant yourself with symmetry and color

The tube-based kaleidoscope was invented in 1816 by Scottish born Sir David Brewster (a scientist and inventor, among other professions). He named his invention kaleidoscope from the Greek words, kalos (beautiful), eidos (form), and scopes (watcher). Simply put, the kaleidoscope is a “beautiful form watcher.”

Kaleidoscopes are enchanting devices. This enchantment stems from the symmetric and colorful designs that occur as tubes containing bits of colored glass and mirrors rotate. It’s no wonder that many computer programs, including Java applets, have been created to reveal this symmetry. For example, I recently encountered a really nice kaleidoscope applet called Kaleidoscope Painter. This applet paints an endless variety of symmetric and colorful designs.

This Java Fun and Games installment introduces two more kaleidoscope applets. The first applet, Kal1, divides its drawing area into four quadrants. Randomly located and colored lines are drawn in the first quadrant. Symmetry occurs by reflecting that quadrant’s lines into the other three quadrants. Listing 1 presents this applet’s source code.

Listing 1. Kal1.java

 

// Kal1.java

import java.awt.*; import java.applet.Applet;

public class Kal1 extends Applet implements Runnable { // Maximum iterations before background is cleared.

final static int MAXITER = 200;

// Animation thread.

Thread runner;

// Current number of iterations counter.

int counter;

// Width of applet drawing area.

int width;

// Height of applet drawing area.

int height;

// Image buffer.

Image imBuffer;

// Graphics context associated with image buffer.

Graphics imG;

// Initialize the applet. Invoked by Web browser.

public void init () { // Acquire the applet's width and height.

width = getSize ().width; height = getSize ().height;

// Create an image buffer.

imBuffer = createImage (width, height);

// Get a graphics context associated with the buffer, so we can draw // onto the buffer.

imG = imBuffer.getGraphics ();

// Initialize image buffer by setting all pixels to black.

imG.setColor (Color.black); imG.fillRect (0, 0, width, height); }

// Start the applet. Invoked by Web browser.

public void start () { // If no thread exists, create a Thread object that associates with the // current Kal object, and start a new thread that invokes the current // Kal object's run() method. Before doing that, clear the counter so // we don't draw only a few lines before clearing the applet's drawing // area.

if (runner == null) { counter = 0;

runner = new Thread (this); runner.start (); } }

// Paint the applet's drawing area. Invoked by Web browser.

public void paint (Graphics g) { // Clear image buffer before it gets too crowded.

if (++counter == MAXITER) { imG.setColor (Color.black); imG.fillRect (0, 0, width, height); counter = 0; }

// Generate a random color and establish that color as the image buffer // Graphics context's drawing color.

Color c = new Color (rnd (256), rnd (256), rnd (256)); imG.setColor (c);

// Obtain starting coordinates for line in upper-left quadrant.

int x1 = rnd (width / 2); int y1 = rnd (height / 2);

// Obtain ending coordinates for line in upper-left quadrant.

int x2 = rnd (width / 2); int y2 = rnd (height / 2);

// Draw the line in the upper-left quadrant.

imG.drawLine (x1, y1, x2, y2);

// Reflect the line into the upper-right quadrant.

imG.drawLine (width - x2, y2, width - x1, y1);

// Reflect the line into the lower-left quadrant.

imG.drawLine (x1, height - y1, x2, height - y2);

// Reflect the line into the lower-right quadrant.

imG.drawLine (width - x2, height - y2, width - x1, height - y1);

// Blit the image buffer to the applet's drawing area.

g.drawImage (imBuffer, 0, 0, this); }

// Obtain a random integer.

static int rnd (int limit) { // Return a random integer that ranges from 0 through limit-1.

return (int) (Math.random () * limit);

}

// Run the animation.

public void run () { // Obtain a reference to the thread that was started in the applet's // start() method.

Thread current = Thread.currentThread ();

// As long as runner contains the same reference, keep looping. The // reference in runner becomes null when the applet's stop() method // executes.

while (current == runner) { // Invoke the paint(Graphics g) method for another drawing operation.

repaint ();

// Pause for 50 milliseconds, to achieve an eye-pleasing display.

try { Thread.sleep (50); } catch (InterruptedException e) { } } }

// Stop the applet. Invoked by Web browser.

public void stop () { // Tell the drawing thread to terminate.

runner = null; }

// The AWT invokes the update() method in response to the repaint() method // calls that are made in the run() method. The default implementation of // this method, which is inherited from the Container class, clears the // applet's drawing area to the background color prior to calling paint(). // This clearing followed by drawing causes flicker. Kal1 overrides // update() to prevent the background from being cleared, which eliminates // the flicker.

public void update (Graphics g) { paint (g); } }

Listing 1’s run() method contains the applet’s animation loop. That loop alternately calls repaint(), which causes the AWT (Abstract Window Toolkit) to invoke update(), or sleeps for 50 milliseconds to achieve an animation rate consistent across different computer speeds.

The update() method invokes paint() to yield the next iteration of the kaleidoscope’s animation: one line is drawn, in a random location and color, in the upper-left quadrant and reflected into the other quadrants. To prevent flicker, update() is overridden and paint() draws into an image buffer.

After compiling Kal1.java, you’ll want to run this applet. Before you can do that, however, you must describe the applet to appletviewer via HTML. Listing 2 provides the needed HTML.

Listing 2. Kal1.html

 <applet code=Kal1.class width=300 height=300>
</applet>

To run the applet, invoke appletviewer Kal1.html (case is not important). Figure 1 reveals a single frame of the line-based kaleidoscope animation.

I have an exercise for you: Modify Kal1.java to display circles, lines, points, rectangles, or triangles—depending on the value of an applet parameter (expressed via the <param> tag). If you feel really ambitious, add some radio buttons to dynamically choose a specific geometrical figure or even a mixture of geometrical figures. For example, Kal1 could display a mix of circles, lines, and triangles.

I don’t know about you, but I want more impressive kaleidoscope animation. How about replacing the line-based animation with animation based on filled polygons? And, instead of confining polygons to the upper-left quadrant and reflecting them into the other three quadrants, let’s display each polygon at any place within the drawing area and reflect it horizontally and vertically from the applet’s center. My second applet, Kal2, does just that. Its source code appears in Listing 3.

Listing 3. Kal2.java

 

// Kal2.java

import java.awt.*; import java.applet.Applet;

public class Kal2 extends Applet implements Runnable { // Maximum iterations before background is cleared.

final static int MAXITER = 200;

// Maximum number of points that comprise polygon.

final static int MAXPOINTS = 6;

// Animation thread.

Thread runner;

// Current number of iterations counter.

int counter;

// Width of applet drawing area.

int width;

// Height of applet drawing area.

int height;

// Image buffer.

Image imBuffer;

// Graphics context associated with image buffer.

Graphics imG;

// Array of polygon points x-coordinates.

int [] xpoints = new int [MAXPOINTS];

// Array of polygon points y-coordinates.

int [] ypoints = new int [MAXPOINTS];

// Initialize the applet. Invoked by Web browser.

public void init () { // Acquire the applet's width and height.

width = getSize ().width; height = getSize ().height;

// Create an image buffer.

imBuffer = createImage (width, height);

// Get a graphics context associated with the buffer, so we can draw // onto the buffer.

imG = imBuffer.getGraphics ();

// Initialize image buffer by setting all pixels to black.

imG.setColor (Color.black); imG.fillRect (0, 0, width, height); }

// Start the applet. Invoked by Web browser.

public void start () { // If no thread exists, create a Thread object that associates with the // current Kal object, and start a new thread that invokes the current // Kal object's run() method. Before doing that, clear the counter so // we don't draw only a few polygons before clearing the applet's // drawing area.

if (runner == null) { counter = 0;

runner = new Thread (this); runner.start (); } }

// Paint the applet's drawing area. Invoked by Web browser.

public void paint (Graphics g) { // Clear image buffer before it gets too crowded.

if (++counter == MAXITER) { imG.setColor (Color.black); imG.fillRect (0, 0, width, height); counter = 0; }

// Generate a random color and establish that color as the image buffer // Graphics context's drawing color.

Color c = new Color (rnd (256), rnd (256), rnd (256)); imG.setColor (c);

// Obtain number of points that compose the polygon: 1 through MAXPOINTS.

int npoints = rnd (MAXPOINTS) + 1;

// Obtain coordinates for each point composing the polygon.

for (int i = 0; i < npoints; i++) { xpoints [i] = rnd (width); ypoints [i] = rnd (height); }

// Display initial polygon.

imG.fillPolygon (xpoints, ypoints, npoints);

// Reflect polygon horizontally.

for (int i = 0; i < npoints; i++) xpoints [i] = width - xpoints [i];

imG.fillPolygon (xpoints, ypoints, npoints);

// Reflect polygon vertically.

for (int i = 0; i < npoints; i++) ypoints [i] = height - ypoints [i];

imG.fillPolygon (xpoints, ypoints, npoints);

// Restore original polygon x coordinates and reflect vertically.

for (int i = 0; i < npoints; i++) xpoints [i] = width - xpoints [i];

imG.fillPolygon (xpoints, ypoints, npoints);

// Blit the image buffer to the applet's drawing area.

g.drawImage (imBuffer, 0, 0, this); }

// Obtain a random integer.

static int rnd (int limit) { // Return a random integer that ranges from 0 through limit-1.

return (int) (Math.random () * limit); }

// Run the animation.

public void run () { // Obtain a reference to the thread that was started in the applet's // start() method.

Thread current = Thread.currentThread ();

// As long as runner contains the same reference, keep looping. The // reference in runner becomes null when the applet's stop() method // executes.

while (current == runner) { // Invoke the paint(Graphics g) method for another drawing operation.

repaint ();

// Pause for 50 milliseconds to achieve an eye-pleasing display.

try { Thread.sleep (50); } catch (InterruptedException e) { } } }

// Stop the applet. Invoked by Web browser.

public void stop () { // Tell the drawing thread to terminate.

runner = null; }

// The AWT invokes the update() method in response to the repaint() method // calls that are made in the run() method. The default implementation of // this method, which is inherited from the Container class, clears the // applet's drawing area to the background color prior to calling paint(). // This clearing, followed by drawing, causes flicker. Kal1 overrides // update() to prevent the background from being cleared, which eliminates // the flicker.

public void update (Graphics g) { paint (g); } }

For the most part, Kal2.java resembles Kal1.java. The major difference is found in paint(), where Graphics‘s fillPolygon() method draws filled polygons in the image buffer.

As with Kal1, Kal2 requires appropriate HTML before you can run it via the appletviewer program. Listing 4 provides the needed HTML.

Listing 4. Kal2.html

 <applet code=Kal2.class width=300 height=300>
</applet>

To run the applet, invoke appletviewer Kal2.html (case is not important). Figure 2 reveals a single frame of the filled polygon-based kaleidoscope animation.

Review

Thanks to their symmetric and colorful designs, kaleidoscopes are enchanting devices. Small wonder that lots of computer programs, including Java applets, have been created to showcase different kinds of kaleidoscope-based symmetry.

Jeff Friesen is a freelance software developer and educator specializing in C, C++, and Java technology.