Draw.java


Below is the syntax highlighted version of Draw.java from §2.4 Input and Output.

/*
 *  Requested features 
 *  ------------------
 *     -  Add support for CubicCurve2D or QudarticCurve2D or Arc2D
 *     -  Add support for gradient fill, etc.
 *
 *     lines are much faster than spots?
 *     can't use AffineTransform since it inverts images and strings
 *
 *     setColorRandom() ??
 *     XOR mode?
 * 
 *     Careful using setFont in inner loop within an animation - it can cause flicker.
 */

import javax.swing.*;
import java.awt.*;
import java.awt.geom.*;
import java.net.*;
import java.applet.*;
import java.awt.image.*;
import java.io.*;
import javax.imageio.ImageIO;

public class Draw {

    private BufferedImage offscreenImage;    // double buffered image
    private BufferedImage onscreenImage;     // double buffered image
    private Graphics2D offscreen;
    private Graphics2D onscreen;
    protected JFrame f;
    private Canvas canvas = new Canvas();    // for MediaTracker
    private double x = 0.0, y = 0.0;         // turtle is at coordinate (x, y)
    private double orientation = 0.0;        // facing this many degrees counterclockwise
    private int width, height;               // size of drawing area in pixels
    private Color background = Color.white;  // background color
    private Color foreground = Color.black;  // foreground color
    private boolean penDown = false;         // is the pen up or down?
    private boolean fill = true;             // fill in circles and rectangles?
    private boolean center = true;           // center spot at (x, y) or make (x, y) lower left
    private double xmin, xmax, ymin, ymax;   // boundary of (x, y) coordinates
    private Insets insets;
    private String title = "Draw Frame";     // title of the frame in the menubar

    private Font font = new Font("Serif", Font.PLAIN, 16);


    // create a new drawing region of given dimensions
    public Draw(int width, int height) {
        this.width  = width;
        this.height = height;
        if (width <= 0 || height <= 0) throw new RuntimeException("Illegal dimension");
        offscreenImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        onscreenImage  = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        offscreen = (Graphics2D) offscreenImage.getGraphics();
        onscreen  = (Graphics2D) onscreenImage.getGraphics();
        setScale(0.0, 0.0, width, height);
        clear();
    }


    // accessor methods
    public double x()           { return x;           }
    public double y()           { return y;           }
    public double orientation() { return orientation; }
    public int width()          { return width;       }
    public int height()         { return height;      }

    // simple state changing methods
    public void setTitle(String s) { title = s; }

    public void penDown()       { penDown = true;     }
    public void penUp()         { penDown = false;    }
    public void fillOn()        { fill    = true;     }
    public void fillOff()       { fill    = false;    }
    public void centerOn()      { center  = true;     }
    public void centerOff()     { center  = false;    }

    public void XOROn()         { offscreen.setXORMode(background); }
    public void XOROff()        { offscreen.setPaintMode();         }

    // rotate counterclockwise in degrees
    public void rotate(double angle) { orientation += angle; }


   /***********************************************************************************
    *  Affine transform
    ***********************************************************************************/

    // change the user coordinate system
    public void setScale(double xmin, double ymin, double xmax, double ymax) {

        // update (x, y) so that they stay at same screen position ???
        // update orientation so that it stays the same relative to screen coorindates ???

        // there may be some bugs when using scaling with images ???


        this.xmin = xmin;
        this.ymin = ymin;
        this.xmax = xmax;
        this.ymax = ymax;
    }

    // scale from user coordinates to screen coordinates
    public double scaleX(double x)  { return width  * (x - xmin) / (xmax - xmin); }
    public double scaleY(double y)  { return height * (ymax - y) / (ymax - ymin); }
    public double factorX(double w) { return w * width  / Math.abs(xmax - xmin);  }
    public double factorY(double h) { return h * height / Math.abs(ymax - ymin);  }


    // scale from screen coordinates to user coordinates
    public double toUserX(double x)  { return (xmax - xmin) * (x - insets.left) / width  + xmin; }
    public double toUserY(double y)  { return (ymax - ymin) * (height - y + insets.top) / height + ymin; }


   /***********************************************************************************
    *  Background and foreground colors
    ***********************************************************************************/

    // clear the background
    public void clear() {
        offscreen.setColor(background);
        offscreen.fillRect(0, 0, width, height);
        offscreen.setColor(foreground);
    }

    // clear the background with a new color
    public void clear(Color color) {
        background = color;
        clear();
    }
   
    // set the foreground color
    public void setColor(Color color) {
        foreground = color;
        offscreen.setColor(foreground);
    }

    // set the foreground color using red-green-blue (inputs between 0 and 255)
    public void setColorRGB(int r, int g, int b) {
        setColor(new Color(r, g, b));
    }

    // set the foreground color using hue-saturation-brightness (inputs between 0 and 255)
    public void setColorHSB(int h, int s, int b) {
        setColor(Color.getHSBColor(1.0f * h / 255, 1.0f * s / 255, 1.0f * b / 255));
    }

    // set the foreground color using hue-saturation-brightness (inputs between 0.0 and 1.0)
    public void setColorHSB(double h, double s, double b) {
        setColor(Color.getHSBColor((float) h, (float) s, (float) b));
    }

    // set the foreground color to a random color
    public void setColorRandom() {
        setColorHSB((int) (Math.random() * 256), 255, 255);
    }


   /***********************************************************************************
    *  Move the turtle
    ***********************************************************************************/

    // go to (x, y), drawing if the pen is down
    public void go(double x, double y) {
        if (penDown) offscreen.draw(new Line2D.Double(scaleX(this.x), scaleY(this.y), scaleX(x), scaleY(y)));
        this.x = x;
        this.y = y;
    }

    // walk forward, drawing if the pen is down
    public void goForward(double d) {
        double oldx = x;
        double oldy = y;
        x += d * Math.cos(Math.toRadians(orientation));
        y += d * Math.sin(Math.toRadians(orientation));
        if (penDown) offscreen.draw(new Line2D.Double(scaleX(x), scaleY(y), scaleX(oldx), scaleY(oldy)));
    }



   /***********************************************************************************
    *  Draw spots
    ***********************************************************************************/
    // draw pixel at current location
    public void spot() { spot(0.0); }

    // draw circle of diameter d, centered at current location; degenerate to pixel if small
    public void spot(double d) {
        double xs = scaleX(x);
        double ys = scaleY(y);
        double ws = factorX(d);
        double hs = factorY(d);
        if (ws <= 1 && hs <= 1)    offscreen.fillRect((int) xs, (int) ys, 1, 1);
        else if ( fill && !center) offscreen.fill(new Ellipse2D.Double(xs, ys - hs, ws, hs));
        else if (!fill && !center) offscreen.draw(new Ellipse2D.Double(xs, ys - hs, ws, hs));
        else if ( fill &&  center) offscreen.fill(new Ellipse2D.Double(xs - ws/2, ys - hs/2, ws, hs));
        else if (!fill &&  center) offscreen.draw(new Ellipse2D.Double(xs - ws/2, ys - hs/2, ws, hs));
    }


    // draw w-by-h rectangle, centered at current location; degenerate to single pixel if too small
    public void spot(double w, double h) {
        // screen coordinates
        double xs = scaleX(x);
        double ys = scaleY(y);
        double ws = factorX(w);
        double hs = factorY(h);
        if (ws <= 1 && hs <= 1)    offscreen.fillRect((int) xs, (int) ys, 1, 1);
        else if ( fill && !center) offscreen.fill(new Rectangle2D.Double(xs, ys - hs, ws, hs));
        else if (!fill && !center) offscreen.draw(new Rectangle2D.Double(xs, ys - hs, ws, hs));
        else if ( fill &&  center) offscreen.fill(new Rectangle2D.Double(xs - ws/2, ys - hs/2, ws, hs));
        else if (!fill &&  center) offscreen.draw(new Rectangle2D.Double(xs - ws/2, ys - hs/2, ws, hs));
    }

    // obsolete
    private Image getImage2(String s) {
        URL url = Draw.class.getResource(s);
        if (url == null) throw new RuntimeException("image " + s + " not found");

        Image image = Toolkit.getDefaultToolkit().getImage(url);
        MediaTracker tracker = new MediaTracker(canvas); 
        tracker.addImage(image, 1);
        try { tracker.waitForAll(); }
        catch (InterruptedException e) { }
        return image;
    }


    // get an image from the given filename
    private Image getImage(String filename) {
        URL url = Draw.class.getResource(filename);
        if (url == null) throw new RuntimeException("image " + filename + " not found");
        ImageIcon icon = new ImageIcon(url);
        return icon.getImage();
    }


    // draw spot using gif - fix to be centered at (x, y)
    public void spot(String s) {
        Image image = getImage(s);
        double xs = scaleX(x);
        double ys = scaleY(y);
        int ws = image.getWidth(null);
        int hs = image.getHeight(null);

        // center of rotation is (xs, ys)
        if (center) {
            offscreen.rotate(Math.toRadians(orientation), xs, ys);
            offscreen.drawImage(image, (int) (xs - ws/2.0), (int) (ys - hs/2.0), null);
            offscreen.rotate(Math.toRadians(-orientation), xs, ys);
        }
        else { /// ?????
            offscreen.rotate(Math.toRadians(orientation), xs + ws/2.0, ys - hs/2.0);
            offscreen.drawImage(image, (int) (xs), (int) (ys - hs), null);
            offscreen.rotate(Math.toRadians(-orientation), xs + ws/2.0, ys - hs/2.0);
        }
    }

    // draw spot using gif, centered on (x, y), scaled of size w-by-h
    // center vs. !center
    public void spot(String s, double w, double h) {   
        Image image = getImage(s);
        double xs = scaleX(x);
        double ys = scaleY(y);
        double ws = factorX(w);
        double hs = factorY(h);
        // center of rotation is (xs, ys)
        offscreen.rotate(Math.toRadians(orientation), xs, ys);
        if (center) offscreen.drawImage(image, (int) (xs - ws/2.0), (int) (ys - hs/2.0), (int) ws, (int) hs, null);
        else        offscreen.drawImage(image, (int) (xs - ws)    , (int) (ys - hs)    , (int) ws, (int) hs, null);
        offscreen.rotate(Math.toRadians(-orientation), xs, ys);
    }


   /***********************************************************************************
    *  Writing text
    ***********************************************************************************/

    // write the given string in the current font
    public void setFont(Font font) {
        this.font = font;
    }

    // write the given string in the current font, center on the current location
    public void write(String s) {
        offscreen.setFont(font);
        FontMetrics metrics = offscreen.getFontMetrics();
        double xs = scaleX(x);
        double ys = scaleY(y);
        int ws = metrics.stringWidth(s);
        int hs = metrics.getDescent();
        offscreen.rotate(Math.toRadians(orientation), xs, ys);
        if (center) offscreen.drawString(s, (float) (xs - ws/2.0), (float) (ys + hs));
        else        offscreen.drawString(s, (float) (xs)         , (float) (ys));
        offscreen.rotate(Math.toRadians(-orientation), xs, ys);
    }


   /***********************************************************************************
    *  Display the image on screen or save to file
    ***********************************************************************************/
    // wait for a short while
    public void pause(int delay) {
        show();
        try { Thread.currentThread().sleep(delay); }
        catch (InterruptedException e) { }
    }

    // view on-screen, creating new frame if necessary
    public void show() {

        // create the GUI for viewing the image if needed
        if (f == null) {
            f = new JFrame();
            ImageIcon icon = new ImageIcon(onscreenImage);
            f.setContentPane(new JLabel(icon));
            f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);            // closes all windows
            // f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);      // closes only current window
            f.setTitle(title);
            f.setResizable(false);
            f.pack();
            f.setVisible(true);
            insets = f.getInsets();    // must be after frame is rendered
        }

        // draw
        onscreen.drawImage(offscreenImage, 0, 0, null);
        f.setTitle(title);
        f.repaint();

/*
        // schedule for the event-dispatching thread
        javax.swing.SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                onscreen.drawImage(offscreenImage, 0, 0, null);
                f.repaint();
            }
        });
*/

    }


    // save to file - suffix can be png, jpg, or gif
    public void save(String filename) {
        File file = new File(filename);
        String suffix = filename.substring(filename.lastIndexOf('.') + 1);
        try { ImageIO.write(offscreenImage, suffix, file); }
        catch (IOException e) { e.printStackTrace(); }
    }

    // for emebedding into a JPanel
    public JLabel getJLabel() {
        if (offscreenImage == null) return null;         // no image available
        ImageIcon icon = new ImageIcon(offscreenImage);
        JLabel jlabel = new JLabel(icon);
        jlabel.setAlignmentX(0.5f);
        return jlabel;
    }


   /***********************************************************************************
    *  Sound
    ***********************************************************************************/

    // play a wav or midi sound
    public void play(String s) {
        URL url = Draw.class.getResource(s); 
        if (url == null) throw new RuntimeException("audio " + s + " not found");
        AudioClip clip = Applet.newAudioClip(url);
        clip.play();
    }







    // test client
    public static void main(String args[]) {
        Draw t = new Draw(600, 600);
//        t.setScale(0, 600, 600, 0);
        t.clear(Color.gray);
        t.penUp();
        t.go(100, 300);
        t.penDown();
        t.setColor(Color.blue);
        t.spot(30);
        t.rotate(30);
        t.setColor(Color.green);
        t.goForward(200);
        t.rotate(-30);
        t.setColor(Color.red);
        t.spot(30, 30);
        t.pause(1000);

        t.penUp();
        t.go(200, 100);
        t.spot("joker.gif");
        t.spot(10);
        t.setColor(Color.magenta);
        t.go(200, 175);
        t.spot(10);
        Font font = new Font("Arial", Font.BOLD, 30);
        t.setFont(font);
        t.write("Joker");
        t.pause(1000);

        t.go(400, 100);
        t.rotate(90);
        t.spot("joker.gif");
        t.spot(10);
        t.go(475, 100);
        t.spot(10);
        t.write("Kingpin");
        t.show();   // don't forget to repaint at end
    }

}


Last updated: Fri Feb 25 14:32:19 EST 2005 .
Copyright © 2004, Robert Sedgewick and Kevin Wayne.