Extending Montage with Painters

Montage has a few built in tools that let you use lines, rectangles, ovals, polygons, text and images to create pictures. You can use Java to build new tools for Montage.

To create a new tool you must write a small Java program called a Painter. After you compile the Painter with a Java compiler you can add it to the Montage toolbox by using the File->Get Painter... menu option from the Main Window. The graphic items drawn with Painters can be cut, copied, pasted, grouped, ungrouped, moved and resized just like the items drawn with the built in tools. If you used Painters to create a picture, MView or any other applet using Montage's API will automatically fetch them over the network.

Writing a Painter is easy. Any Java class only needs to meet two requirements to qualify as a Painter:
1. It must implement the Painter interface.
2. If it has a constructor then the constructor must have zero arguments.

This is the definition of the Painter interface:

// Painter.java
// Copyright (c) 1996 Ludens, SA de CV

package com.ludens.montage;

import java.awt.*;

public interface Painter {

    public static final int TOOLBOX = 0;

    public static final int REPAINT = 1;

    public static final int RESIZE = 2;

    public void paint(Graphics g,
                          int x, int y, int width, int height,
                          Color interiorColor,
                          Color outlineColor,
                          boolean hasInterior,
                          boolean hasOutline,
                          Component parent,
                          int why);

}

As you can see the Painter interface specifies just one method, called paint, that your class must implement. Montage calls this method whenever a graphic item created with your Painter needs to be redrawn. This means that Montage handles everything else and you do not need to worry about cutting, copying, pasting, saving, loading, handling mouse clicks, or keeping track of each graphic item created with your Painter.


A trivial Painter

Here is an example of a trivial Painter that draws black rectangles:
// BlackRectP.java
// Copyright (c) 1996 Ludens, SA de CV

import com.ludens.montage.Painter;
import java.awt.*;

public class BlackRectP implements Painter {

    public void
    paint(Graphics g, int x, int y, int width, int height,
                      Color interiorColor, Color outlineColor,
                      boolean hasInterior, boolean hasOutline,
                      Component parent,
                      int why)
    {
        g.setColor(Color.black);
        g.fillRect(x, y, width, height);
    }

}

Lets go through it line by line.

import com.ludens.montage.Painter;
import java.awt.*;
These two lines are used to inform the Java compiler about classes from other packages that will be used in this program. com.ludens.montage.Painter is the interface definition for Montage Painters and java.awt.* means all the classes in Java's graphics API.

public class BlackRectP implements Painter {
Here we specify that BlackRectP is the class name and that it implements Montage's Painter interface. By convention all Painter class names end with P (upper case P), Montage uses this convention to know what .class files to list in the Get Painter dialog. You can create a Painter that does not follow this convention but you will have to type in its complete file name when you want to place in Montage's toolbox.

    public void
    paint(Graphics g, int x, int y, int width, int height,
                      Color interiorColor, Color outlineColor,
                      boolean hasInterior, boolean hasOutline,
                      Component parent,
                      int why)
This is the header of the paint method required by the Painter interface. It must be a public method so that Montage can call it and void indicates it does not return any result.

The first parameter, Graphics g, is the graphics context used to draw the picture.

int x and int y specify the upper left corner of the rectangle where the graphic item must be drawn.

int width and int height specify the size of the rectangle where the graphic item must be drawn.

Color interiorColor and Color outlineColor specify the colors that should be used to draw the graphic item. Although BlackRectP ignores these parameters, this is not the recommended behavior for a Painter.

boolean hasInterior and boolean hasOutline are two flags that indicate whether the interior and outline of this graphic item are enabled or not. BlackRectP also ignores these parameters, this is not the recommended behavior for a Painter.

The next two parameters are for advanced Painter programming and can be ignored most of the time. Component parent is a handle to the AWT component that contains the picture. This parameter is only used in some special cases like animated Painters; other Painters should ignore it. BlackRectP ignores it.

int why tells the painter why its paint method was called, it can have three possible values: TOOLBOX, REPAINT, and RESIZE. Most of the time the value will be REPAINT indicating that the item is in a picture, and that part of the picture is being repainted. RESIZE is for the special case when the item is being resized, if rendering the item takes a long time this should be taken as a hint to just draw its outline to guarantee a reasonable response time. The value is TOOLBOX when painting the tool icon in the toolbox. Well behaved Painters shouldn't distinguish between REPAINT and TOOLBOX; they should paint the tool the same way they paint a graphic item in a picture. But for some Painters it does make sense to distinguish between them, an example would be MeasureP.java (in $MONTAGE/src/Painters if you have Unix or %MONTAGE%\src\Painters if you are using Windows95 or WindowsNT). BlackRectP ignores this parameter.

    {
        g.setColor(Color.black);
        g.fillRect(x, y, width, height);
    }
This is where the work is done. g.setColor(Color.black) uses the handle to the graphics context to set the color for subsequent drawing operations. The selected color is black.

g.fillRect(x, y, width, height) fills the rectangle indicated by Montage with the current color (black).
These two lines are all the executable code in BlackRectP. The source code should be in a file named BlackRectP.java. If you are using Sun's JDK you can compile it by typing at the command prompt:

javac BlackRectP.java
Before you can compile you must ensure that the Java compiler will find the Painter interface inside montage.zip.
In Windows 95 or Windows NT you can do this by typing:

SET CLASSPATH=%MONTAGE%\CLASSES\MONTAGE.ZIP;.

If you are using Unix with sh or ksh then you should type:

CLASSPATH=$MONTAGE/classes/montage.zip:.; export CLASSPATH

If you are using Unix with csh then you should type:

setenv CLASSPATH $MONTAGE/classes/montage.zip:.

This should produce, if you didn't make any typing mistakes, a file named BlackRectP.class that contains the Java bytecodes Montage will use to draw with this Painter. You can now use the File->Get Painter... menu option from Montage's Main Window to put BlackRectP in your toolbox and you will see a new tool button with a black square on top.

With this tool you can draw and manipulate black rectangles the same way you would with the built in rectangle tool. Now stop and think about all the code to handle black rectangles that you did not have to write: how to save them, how to load them, how to copy them, how to move them, how to resize them and how to respond to mouse events. Furthermore, did you notice you didn't need to use any linker?

On the other hand, from a non programmer's point of view this tool is not very interesting, it doesn't do anything that couldn't already be done with the built in rectangle tool. Actually, it does less because you can't change the color of these rectangles or turn their interior on and off. So let's write a new Painter that draws something that would impossible to draw with the built in tools, a Painter that draws the top half of a circle.


A more useful Painter

Here is the source code for TopHalfCircleP, a Painter that draws the top half of a circle:
// TopHalfCircleP.java
// Copyright (c) 1996 Ludens, SA de CV

import com.ludens.montage.Painter;
import java.awt.*;

public class TopHalfCircleP implements Painter {

    public void
    paint(Graphics g, int x, int y, int width, int height,
                      Color interiorColor, Color outlineColor,
                      boolean hasInterior, boolean hasOutline,
                      Component parent,
                      int why)
    {
        if (hasInterior)
        {
            g.setColor(interiorColor);
            g.fillArc(x, y, width, height * 2, 0, 180);
        }
        if (hasOutline)
        {
            g.setColor(outlineColor);
            g.drawArc(x, y, width, height * 2, 0, 180);
        }
    }

}
You probably noticed that it is very similar to the code for the black rectangles Painter. The only difference, besides the name of the class, lies in the body of the paint method. It contains two if statements. The body of the first one is executed only when the half circle has a filled interior. The body of the second one is executed only when the outline of the half circle should be drawn. Placing the drawing code inside this two if statements is what makes the graphic items drawn with this Painter sensitive to the settings of the Interior and Outline switches in the toolbox.

Furthermore, instead of hardcoding the interior and outline colors it uses the interiorColor and outlineColor parameters. This way you can use the Color Selector to interactively choose the colors you want for your half circles.

g.fillArc(x, y, width, height * 2, 0, 180) paints an half an oval, from 0 to 180 degrees, with the current color. The height parameter is multiplied by 2 to make the half oval fit the rectangle specified by Montage. g.drawArc(x, y, width, height * 2, 0, 180) is similar to fillArc but instead of filling half an oval it just draws the outline with current color.

You can use this Painter as a template for you own Painters, you just need to change the name of the class and replace the fillArc and drawArc by the code that draws the sphape you need. Painters written this way will work the way any user would expect them to work: exactly like the built in tools.


A quick look inside Montage

When you draw a picture with Montage you can grab any graphic item and move it or resize it by dragging the mouse. While you do so, it might move in front of other graphic items lying beyond it or behind other items in front of it, and none of them seem to be affected in any way. It almost looks like you have special hardware that lets you handle graphics in separate layers.

The thruth is that Montage doesn't use any special hardware, all graphic items are drawn on the same framebuffer and they overwrite the pixels of the items below them. If these items appear untouched it is because Montage makes sure that the area dammaged by the moving item is repainted before updating the screen. This means that simply by dragging a graphic item in front of another you might cause their respective paint methods to be called hundreds of times: the one below must be repainted to repair the dammage caused by the one moving on top, which in turn has to repainted at each new position. Therefore, if one (or both) of the paint routines is slow then the movement will appear sluggish. To avoid this you should try to make the paint method in your Painters as fast as possible.

If all you are drawing are simple graphic shapes then you probably don't need to worry about this. On the other hand, if you are using Painters to generate complex backgrounds for your pictures then you should be careful about how you program them. A good idea is to cache the pixels generated by the last call to paint, and be ready to reuse them if the next call has compatible parameters. The next section shows an example of how to do this.


Using image caches to improve paint performance


Moiré patterns are created by the interference between two similar lines, with different colors, drawn on a raster display. They are very simple to program and produce interesting patterns on the screen. Unfortunately, drawing Moiré patterns is slow. The following Painter uses an offscreen image to cache the Moiré pattern, it can be used as the background for a Montage picture without any noticeable impact on performance.

Here is the complete source code for MoireP:

// MoireP.java
// Copyright (c) 1996 Ludens, SA de CV

import com.ludens.montage.Painter;
import java.awt.*;

public class MoireP implements Painter {

    Image img;

    Graphics gr;

    int cachedWidth;

    int cachedHeight;

    Color cachedInteriorColor;

    Color cachedOutlineColor;

    public void
    paint(Graphics g, int x, int y, int width, int height,
                      Color interiorColor, Color outlineColor,
                      boolean hasInterior, boolean hasOutline,
                      Component parent,
                      int why)
    {
        boolean newImage = false;
        if (width < 2 || height < 2)
            return;
        if (why == Painter.RESIZE)
        {
            g.setColor(outlineColor);
            g.drawRect(x, y, width, height);
            return;
        }
        if (img == null ||
            width != cachedWidth || height != cachedHeight)
        {
            img = parent.createImage(width, height);
            gr = img.getGraphics();
            cachedWidth = width;
            cachedHeight = height;
            newImage = true;
        }
        if (newImage ||
            cachedInteriorColor == null ||
            cachedInteriorColor.getRGB() !=
                      interiorColor.getRGB() ||
            cachedOutlineColor == null ||
            cachedOutlineColor.getRGB() !=
                      outlineColor.getRGB())
        {
            int cx = width / 2;
            int cy = height / 2;
            Color c = interiorColor;
            for (int i = 0; i < width; i++)
            {
                gr.setColor(c);
                gr.drawLine(cx, cy, i, 0);
                c = (c == interiorColor)
                    ? outlineColor : interiorColor;
            }
            for (int i = 0; i < height; i++)
            {
                gr.setColor(c);
                gr.drawLine(cx, cy, width, i);
                c = (c == interiorColor)
                    ? outlineColor : interiorColor;
            }
            for (int i = width - 1; i > 0; i--)
            {
                gr.setColor(c);
                gr.drawLine(cx, cy, i, height);
                c = (c == interiorColor)
                    ? outlineColor : interiorColor;
            }
            for (int i = height - 1; i > 0; i--)
            {
                gr.setColor(c);
                gr.drawLine(cx, cy, 0, i);
                c = (c == interiorColor)
                    ? outlineColor : interiorColor;
            }
            cachedInteriorColor = interiorColor;
            cachedOutlineColor = outlineColor;
        }
        g.drawImage(img, x, y, parent);
    }

}
Lets go through the code.
import com.ludens.montage.Painter;
import java.awt.*;

public class MoireP implements Painter {
This is identical to any other Painter.

    Image img;

    Graphics gr;

    int cachedWidth;

    int cachedHeight;

    Color cachedInteriorColor;

    Color cachedOutlineColor;
    
img is the offscreen image used as a cache.

gr is the graphics context used to draw on the offscreen image.

cachedWidth and cachedHeight are the width and height of the offscreen image.

cachedInteriorColor and cachedOutlineColor are the colors used to render the offscreen image.

Please note that when a new instance of MoireP is created, its img, gr, cachedInteriorColor and cachedOutlineColor instance variables are initialized to null.

    
    public void
    paint(Graphics g, int x, int y, int width, int height,
                      Color interiorColor, Color outlineColor,
                      boolean hasInterior, boolean hasOutline,
                      Component parent,
                      int why)
    {
This is a standard header for a Painter's paint method.
        if (why == Painter.RESIZE)
        {
            g.setColor(outlineColor);
            g.drawRect(x, y, width, height);
            return;
        }
Skip rendering when resizing. Just draw the outline.
        boolean newImage = false;
        if (width < 2 || height < 2)
            return;
When newImage is set to true it indicates that a new offscreen image was created and needs to be rendered. If width or height are too small there is no point in trying to render a Moiré pattern. Also, values less than 2 might make a later call to createImage to fail.
         if (img == null ||
            width != cachedWidth || height != cachedHeight)
        {
            img = parent.createImage(width, height);
            gr = img.getGraphics();
            cachedWidth = width;
            cachedHeight = height;
            newImage = true;
        }
If we don't have an offscreen image or we already have one but the width or height differ from the one we need, then create a new offscreen image, remember its size, and indicate that the Moiré pattern needs to rendered.
         if (newImage ||
            cachedInteriorColor == null ||
            cachedInteriorColor.getRGB() !=
                      interiorColor.getRGB() ||
            cachedOutlineColor == null ||
            cachedOutlineColor.getRGB() !=
                      outlineColor.getRGB())
        {
If a new offscreen image was created or the colors are different from the colors in the rendered image then the pattern should be rendered again.
            int cx = width / 2;
            int cy = height / 2;
            Color c = interiorColor;
            for (int i = 0; i < width; i++)
            {
                gr.setColor(c);
                gr.drawLine(cx, cy, i, 0);
                c = (c == interiorColor)
                    ? outlineColor : interiorColor;
            }
            for (int i = 0; i < height; i++)
            {
                gr.setColor(c);
                gr.drawLine(cx, cy, width, i);
                c = (c == interiorColor)
                    ? outlineColor : interiorColor;
            }
            for (int i = width - 1; i > 0; i--)
            {
                gr.setColor(c);
                gr.drawLine(cx, cy, i, height);
                c = (c == interiorColor)
                    ? outlineColor : interiorColor;
            }
            for (int i = height - 1; i > 0; i--)
            {
                gr.setColor(c);
                gr.drawLine(cx, cy, 0, i);
                c = (c == interiorColor)
                    ? outlineColor : interiorColor;
            }
This is the actual rendering code for the Moiré pattern. It just draws lines from the center of the rectangle to each pixel on its boundary, while alternating between the two colors. There is a for loop for each of the sides. Notice how all the calls to setColor and drawLine are made using gr so that all the rendering is done on the offscreen image.
            cachedInteriorColor = interiorColor;
            cachedOutlineColor = outlineColor;
        }
Remember the colors used to render the current offscreen image.
        g.drawImage(img, x, y, parent);
    }

}
Copy the offscreen image to the screen. Most of time this will be the only line of code from the paint method that will need to be executed.

With this technique you do not need to rerender the Moiré pattern when moving it or dragging a graphics item in front of it, which are by far the most common operations.


Writing animated Painters


Writing animated Painters is easier than it might seem. You just need to understand the AWT repaint model. Here is an example of an animated Painter, CicleP, that draws a rotating pie slice:

// CicleP 
// Copyright (c) 1996 Ludens, SA de CV 
  
import com.ludens.montage.Painter;
import java.awt.*;

public class CicleP implements Painter {

    int angle = 0;

    int angleDelta = 5;

    public void
    paint(Graphics g, int x, int y, int width, int height,
                      Color interiorColor, Color outlineColor,
                      boolean hasInterior, boolean hasOutline,
                      Component parent,
                      int why)
    {
        if (hasInterior)
        {
            g.setColor(interiorColor);
            g.fillArc(x + 1, y + 1,
                      width - 2, height - 2, angle, 45);
        }
        if (hasOutline)
        {
            g.setColor(outlineColor);
            g.drawArc(x + 1, y + 1,
                      width - 2, height - 2, angle, 45);
        }
        angle += angleDelta;
        if (angle == 360)
            angle = 0;
        parent.repaint(x, y, width, height);
    }

}
This code is very similar to TopHalfCircleP and we will just point out the differences.
    int angle = 0;

    int angleDelta = 5;
We have two instance variables, angle indicates the current rotation of the pie slice and angleDelta is the amount of rotation between animation frames. Both quantities are in degrees.
        angle += angleDelta;
        if (angle == 360)
            angle = 0;
After drawing the pie slice at the desired angle, we update the angle for the next frame and make sure it stays within reasonable bounds. The next time the paint method is called it will draw the pie slice in its new position.
    parent.repaint(x, y, width, height);
This call to repaint creates an endless loop. Once we finish painting the graphic item we tell its parent, the window where it is drawn, that this area of the screen needs to be repainted. To repaint it, it will need to call again the paint method for this item, the pie slice will be drawn in its new position, and the parent will be notified again of the need to repaint this area of the screen.

If you are not used to Java this way of creating an endless loop might seem a very bad idea. At first it looks like the application will stay in an endless loop and will not respond to any external events. On second thought it looks worse; it seems that paint is making endless recursive calls to itself that will use up all stack space and crash the application.

Yet, if your try it, you will notice that you can have a picture with many animated graphic items (even many animated pictures at the same time) and it doesn't affect at all Montage's response to mouse events. Furthermore, you can wait as long as you like and Montage won't crash because it ran out of stack space.

The secret to this behavior lies in Java's multithreaded nature. A call to parent.repaint() just signals an updater thread that this window needs to be repainted, paint is never called from inside repaint(), so there aren't any recursive calls and there isn't any danger of running out of stack space. Also, all mouse and keyboard events are handled by a separate thread which doesn't have to wait for any updater thread.


Handling of errors inside Painters

There is always the possibility that a fatal error occurs while executing the paint method in a Painter. If this happens when Montage calls the method to draw the tool inside the toolbox, it will indicate the problem by drawing a tool button with a red exclamation point. If the error occurs while drawing in a picture window, Montage will draw a large red rectangle with a complete description of the error and the values of the parameters when the error ocurred.
Copyright © Ludens, SA de CV 1996, 1997 All Rights Reserved.
Sun, Java, and Solaris are Trademarks of Sun Microsystems.
Windows 95 and Windows NT are Trademarks of Microsoft.