content: /*************************************************************************
   *  Compilation:  javac StdDraw.java
   *  Execution:    java StdDraw
   *
   *  Standard drawing library. This class provides a basic capability for
   *  creating drawings with your programs. It uses a simple graphics model that
   *  allows you to create drawings consisting of points, lines, and curves
   *  in a window on your computer and to save the drawings to a file.
   *
   *  Todo
   *  ----
   *    -  Add support for gradient fill, etc.
   *
   *  Remarks
   *  -------
   *    -  don't use AffineTransform for rescaling since it inverts
   *       images and strings
   *    -  careful using setFont in inner loop within an animation -
   *       it can cause flicker
   *
   *************************************************************************/
  
  import java.awt.*;
  import java.awt.event.*;
  import java.awt.geom.*;
  import java.awt.image.*;
  import java.io.*;
  import java.net.*;
  import java.util.LinkedList;
  import java.util.TreeSet;
  import javax.imageio.ImageIO;
  import javax.swing.*;
  
  /**
   *  <i>Standard draw</i>. This class provides a basic capability for
   *  creating drawings with your programs. It uses a simple graphics model that
   *  allows you to create drawings consisting of points, lines, and curves
   *  in a window on your computer and to save the drawings to a file.
   *  <p>
   *  For additional documentation, see <a href="http://introcs.cs.princeton.edu/15inout">Section 1.5</a> of
   *  <i>Introduction to Programming in Java: An Interdisciplinary Approach</i> by Robert Sedgewick and Kevin Wayne.
   */
  public final class StdDraw implements ActionListener, MouseListener, MouseMotionListener, KeyListener {
  
      // pre-defined colors
      public static final Color BLACK      = Color.BLACK;
      public static final Color BLUE       = Color.BLUE;
      public static final Color CYAN       = Color.CYAN;
      public static final Color DARK_GRAY  = Color.DARK_GRAY;
      public static final Color GRAY       = Color.GRAY;
      public static final Color GREEN      = Color.GREEN;
      public static final Color LIGHT_GRAY = Color.LIGHT_GRAY;
      public static final Color MAGENTA    = Color.MAGENTA;
      public static final Color ORANGE     = Color.ORANGE;
      public static final Color PINK       = Color.PINK;
      public static final Color RED        = Color.RED;
      public static final Color WHITE      = Color.WHITE;
      public static final Color YELLOW     = Color.YELLOW;
  
      /**
       * Shade of blue used in Introduction to Programming in Java.
       * It is Pantone 300U. The RGB values are approximately (9, 90, 266).
       */
      public static final Color BOOK_BLUE       = new Color(  9,  90, 166);
      public static final Color BOOK_LIGHT_BLUE = new Color(103, 198, 243);
  
      /**
       * Shade of red used in Algorithms 4th edition.
       * It is Pantone 1805U. The RGB values are approximately (150, 35, 31).
       */
      public static final Color BOOK_RED = new Color(150, 35, 31);
  
      // default colors
      private static final Color DEFAULT_PEN_COLOR   = BLACK;
      private static final Color DEFAULT_CLEAR_COLOR = WHITE;
  
      // current pen color
      private static Color penColor;
  
      // default canvas size is DEFAULT_SIZE-by-DEFAULT_SIZE
      private static final int DEFAULT_SIZE = 512;
      private static int width  = DEFAULT_SIZE;
      private static int height = DEFAULT_SIZE;
  
      // default pen radius
      private static final double DEFAULT_PEN_RADIUS = 0.002;
  
      // current pen radius
      private static double penRadius;
  
      // show we draw immediately or wait until next show?
      private static boolean defer = false;
  
      // boundary of drawing canvas, 5% border
      private static final double BORDER = 0.05;
      private static final double DEFAULT_XMIN = 0.0;
      private static final double DEFAULT_XMAX = 1.0;
      private static final double DEFAULT_YMIN = 0.0;
      private static final double DEFAULT_YMAX = 1.0;
      private static double xmin, ymin, xmax, ymax;
  
      // for synchronization
      private static Object mouseLock = new Object();
      private static Object keyLock = new Object();
  
      // default font
      private static final Font DEFAULT_FONT = new Font("SansSerif", Font.PLAIN, 16);
  
      // current font
      private static Font font;
  
      // double buffered graphics
      private static BufferedImage offscreenImage, onscreenImage;
      private static Graphics2D offscreen, onscreen;
  
      // singleton for callbacks: avoids generation of extra .class files
      private static StdDraw std = new StdDraw();
  
      // the frame for drawing to the screen
      private static JFrame frame;
  
      // mouse state
      private static boolean mousePressed = false;
      private static double mouseX = 0;
      private static double mouseY = 0;
  
      // queue of typed key characters
      private static LinkedList<Character> keysTyped = new LinkedList<Character>();
  
      // set of key codes currently pressed down
      private static TreeSet<Integer> keysDown = new TreeSet<Integer>();
    
  
      // not instantiable
      private StdDraw() { }
  
  
      // static initializer
      static { init(); }
  
      /**
       * Set the window size to the default size 512-by-512 pixels.
       */
      public static void setCanvasSize() {
          setCanvasSize(DEFAULT_SIZE, DEFAULT_SIZE);
      }
  
      /**
       * Set the window size to w-by-h pixels.
       *
       * @param w the width as a number of pixels
       * @param h the height as a number of pixels
       * @throws a RunTimeException if the width or height is 0 or negative
       */
      public static void setCanvasSize(int w, int h) {
          if (w < 1 || h < 1) throw new RuntimeException("width and height must be positive");
          width = w;
          height = h;
          init();
      }
  
      // init
      private static void init() {
          if (frame != null) frame.setVisible(false);
          frame = new JFrame();
          offscreenImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
          onscreenImage  = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
          offscreen = offscreenImage.createGraphics();
          onscreen  = onscreenImage.createGraphics();
          setXscale();
          setYscale();
          offscreen.setColor(DEFAULT_CLEAR_COLOR);
          offscreen.fillRect(0, 0, width, height);
          setPenColor();
          setPenRadius();
          setFont();
          clear();
  
          // add antialiasing
          RenderingHints hints = new RenderingHints(RenderingHints.KEY_ANTIALIASING,
                                                    RenderingHints.VALUE_ANTIALIAS_ON);
          hints.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
          offscreen.addRenderingHints(hints);
  
          // frame stuff
          ImageIcon icon = new ImageIcon(onscreenImage);
          JLabel draw = new JLabel(icon);
  
          draw.addMouseListener(std);
          draw.addMouseMotionListener(std);
  
          frame.setContentPane(draw);
          frame.addKeyListener(std);    // JLabel cannot get keyboard focus
          frame.setResizable(false);
          frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);            // closes all windows
          // frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);      // closes only current window
          frame.setTitle("Standard Draw");
          frame.setJMenuBar(createMenuBar());
          frame.pack();
          frame.requestFocusInWindow();
          frame.setVisible(true);
      }
  
      // create the menu bar (changed to private)
      private static JMenuBar createMenuBar() {
          JMenuBar menuBar = new JMenuBar();
          JMenu menu = new JMenu("File");
          menuBar.add(menu);
          JMenuItem menuItem1 = new JMenuItem(" Save...   ");
          menuItem1.addActionListener(std);
          menuItem1.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S,
                                  Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
          menu.add(menuItem1);
          return menuBar;
      }
  
  
     /*************************************************************************
      *  User and screen coordinate systems
      *************************************************************************/
  
      /**
       * Set the x-scale to be the default (between 0.0 and 1.0).
       */
      public static void setXscale() { setXscale(DEFAULT_XMIN, DEFAULT_XMAX); }
  
      /**
       * Set the y-scale to be the default (between 0.0 and 1.0).
       */
      public static void setYscale() { setYscale(DEFAULT_YMIN, DEFAULT_YMAX); }
  
      /**
       * Set the x-scale (a 10% border is added to the values)
       * @param min the minimum value of the x-scale
       * @param max the maximum value of the x-scale
       */
      public static void setXscale(double min, double max) {
          double size = max - min;
          xmin = min - BORDER * size;
          xmax = max + BORDER * size;
      }
  
      /**
       * Set the y-scale (a 10% border is added to the values).
       * @param min the minimum value of the y-scale
       * @param max the maximum value of the y-scale
       */
      public static void setYscale(double min, double max) {
          double size = max - min;
          ymin = min - BORDER * size;
          ymax = max + BORDER * size;
      }
  
      /**
       * Set the x-scale and y-scale (a 10% border is added to the values)
       * @param min the minimum value of the x- and y-scales
       * @param max the maximum value of the x- and y-scales
       */
      public static void setScale(double min, double max) {
          setXscale(min, max);
          setYscale(min, max);
      }
  
      // helper functions that scale from user coordinates to screen coordinates and back
      private static double  scaleX(double x) { return width  * (x - xmin) / (xmax - xmin); }
      private static double  scaleY(double y) { return height * (ymax - y) / (ymax - ymin); }
      private static double factorX(double w) { return w * width  / Math.abs(xmax - xmin);  }
      private static double factorY(double h) { return h * height / Math.abs(ymax - ymin);  }
      private static double   userX(double x) { return xmin + x * (xmax - xmin) / width;    }
      private static double   userY(double y) { return ymax - y * (ymax - ymin) / height;   }
  
  
      /**
       * Clear the screen to the default color (white).
       */
      public static void clear() { clear(DEFAULT_CLEAR_COLOR); }
      /**
       * Clear the screen to the given color.
       * @param color the Color to make the background
       */
      public static void clear(Color color) {
          offscreen.setColor(color);
          offscreen.fillRect(0, 0, width, height);
          offscreen.setColor(penColor);
          draw();
      }
  
      /**
       * Get the current pen radius.
       */
      public static double getPenRadius() { return penRadius; }
  
      /**
       * Set the pen size to the default (.002).
       */
      public static void setPenRadius() { setPenRadius(DEFAULT_PEN_RADIUS); }
      /**
       * Set the radius of the pen to the given size.
       * @param r the radius of the pen
       * @throws RuntimeException if r is negative
       */
      public static void setPenRadius(double r) {
          if (r < 0) throw new RuntimeException("pen radius must be positive");
          penRadius = r * DEFAULT_SIZE;
          BasicStroke stroke = new BasicStroke((float) penRadius, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);
          // BasicStroke stroke = new BasicStroke((float) penRadius);
          offscreen.setStroke(stroke);
      }
  
      /**
       * Get the current pen color.
       */
      public static Color getPenColor() { return penColor; }
  
      /**
       * Set the pen color to the default color (black).
       */
      public static void setPenColor() { setPenColor(DEFAULT_PEN_COLOR); }
      /**
       * Set the pen color to the given color. The available pen colors are
       * BLACK, BLUE, CYAN, DARK_GRAY, GRAY, GREEN, LIGHT_GRAY, MAGENTA,
       * ORANGE, PINK, RED, WHITE, and YELLOW.
       * @param color the Color to make the pen
       */
      public static void setPenColor(Color color) {
          penColor = color;
          offscreen.setColor(penColor);
      }
  
      /**
       * Get the current font.
       */
      public static Font getFont() { return font; }
  
      /**
       * Set the font to the default font (sans serif, 16 point).
       */
      public static void setFont() { setFont(DEFAULT_FONT); }
  
      /**
       * Set the font to the given value.
       * @param f the font to make text
       */
      public static void setFont(Font f) { font = f; }
  
  
     /*************************************************************************
      *  Drawing geometric shapes.
      *************************************************************************/
  
      /**
       * Draw a line from (x0, y0) to (x1, y1).
       * @param x0 the x-coordinate of the starting point
       * @param y0 the y-coordinate of the starting point
       * @param x1 the x-coordinate of the destination point
       * @param y1 the y-coordinate of the destination point
       */
      public static void line(double x0, double y0, double x1, double y1) {
          offscreen.draw(new Line2D.Double(scaleX(x0), scaleY(y0), scaleX(x1), scaleY(y1)));
          draw();
      }
  
      /**
       * Draw one pixel at (x, y).
       * @param x the x-coordinate of the pixel
       * @param y the y-coordinate of the pixel
       */
      private static void pixel(double x, double y) {
          offscreen.fillRect((int) Math.round(scaleX(x)), (int) Math.round(scaleY(y)), 1, 1);
      }
  
      /**
       * Draw a point at (x, y).
       * @param x the x-coordinate of the point
       * @param y the y-coordinate of the point
       */
      public static void point(double x, double y) {
          double xs = scaleX(x);
          double ys = scaleY(y);
          double r = penRadius;
          // double ws = factorX(2*r);
          // double hs = factorY(2*r);
          // if (ws <= 1 && hs <= 1) pixel(x, y);
          if (r <= 1) pixel(x, y);
          else offscreen.fill(new Ellipse2D.Double(xs - r/2, ys - r/2, r, r));
          draw();
      }
  
      /**
       * Draw a circle of radius r, centered on (x, y).
       * @param x the x-coordinate of the center of the circle
       * @param y the y-coordinate of the center of the circle
       * @param r the radius of the circle
       * @throws RuntimeException if the radius of the circle is negative
       */
      public static void circle(double x, double y, double r) {
          if (r < 0) throw new RuntimeException("circle radius can't be negative");
          double xs = scaleX(x);
          double ys = scaleY(y);
          double ws = factorX(2*r);
          double hs = factorY(2*r);
          if (ws <= 1 && hs <= 1) pixel(x, y);
          else offscreen.draw(new Ellipse2D.Double(xs - ws/2, ys - hs/2, ws, hs));
          draw();
      }
  
      /**
       * Draw filled circle of radius r, centered on (x, y).
       * @param x the x-coordinate of the center of the circle
       * @param y the y-coordinate of the center of the circle
       * @param r the radius of the circle
       * @throws RuntimeException if the radius of the circle is negative
       */
      public static void filledCircle(double x, double y, double r) {
          if (r < 0) throw new RuntimeException("circle radius can't be negative");
          double xs = scaleX(x);
          double ys = scaleY(y);
          double ws = factorX(2*r);
          double hs = factorY(2*r);
          if (ws <= 1 && hs <= 1) pixel(x, y);
          else offscreen.fill(new Ellipse2D.Double(xs - ws/2, ys - hs/2, ws, hs));
          draw();
      }
  
  
      /**
       * Draw an ellipse with given semimajor and semiminor axes, centered on (x, y).
       * @param x the x-coordinate of the center of the ellipse
       * @param y the y-coordinate of the center of the ellipse
       * @param semiMajorAxis is the semimajor axis of the ellipse
       * @param semiMinorAxis is the semiminor axis of the ellipse
       * @throws RuntimeException if either of the axes are negative
       */
      public static void ellipse(double x, double y, double semiMajorAxis, double semiMinorAxis) {
          if (semiMajorAxis < 0) throw new RuntimeException("ellipse semimajor axis can't be negative");
          if (semiMinorAxis < 0) throw new RuntimeException("ellipse semiminor axis can't be negative");
          double xs = scaleX(x);
          double ys = scaleY(y);
          double ws = factorX(2*semiMajorAxis);
          double hs = factorY(2*semiMinorAxis);
          if (ws <= 1 && hs <= 1) pixel(x, y);
          else offscreen.draw(new Ellipse2D.Double(xs - ws/2, ys - hs/2, ws, hs));
          draw();
      }
  
      /**
       * Draw an ellipse with given semimajor and semiminor axes, centered on (x, y).
       * @param x the x-coordinate of the center of the ellipse
       * @param y the y-coordinate of the center of the ellipse
       * @param semiMajorAxis is the semimajor axis of the ellipse
       * @param semiMinorAxis is the semiminor axis of the ellipse
       * @throws RuntimeException if either of the axes are negative
       */
      public static void filledEllipse(double x, double y, double semiMajorAxis, double semiMinorAxis) {
          if (semiMajorAxis < 0) throw new RuntimeException("ellipse semimajor axis can't be negative");
          if (semiMinorAxis < 0) throw new RuntimeException("ellipse semiminor axis can't be negative");
          double xs = scaleX(x);
          double ys = scaleY(y);
          double ws = factorX(2*semiMajorAxis);
          double hs = factorY(2*semiMinorAxis);
          if (ws <= 1 && hs <= 1) pixel(x, y);
          else offscreen.fill(new Ellipse2D.Double(xs - ws/2, ys - hs/2, ws, hs));
          draw();
      }
  
  
      /**
       * Draw an arc of radius r, centered on (x, y), from angle1 to angle2 (in degrees).
       * @param x the x-coordinate of the center of the circle
       * @param y the y-coordinate of the center of the circle
       * @param r the radius of the circle
       * @param angle1 the starting angle. 0 would mean an arc beginning at 3 o'clock.
       * @param angle2 the angle at the end of the arc. For example, if
       *        you want a 90 degree arc, then angle2 should be angle1 + 90.
       * @throws RuntimeException if the radius of the circle is negative
       */
      public static void arc(double x, double y, double r, double angle1, double angle2) {
          if (r < 0) throw new RuntimeException("arc radius can't be negative");
          while (angle2 < angle1) angle2 += 360;
          double xs = scaleX(x);
          double ys = scaleY(y);
          double ws = factorX(2*r);
          double hs = factorY(2*r);
          if (ws <= 1 && hs <= 1) pixel(x, y);
          else offscreen.draw(new Arc2D.Double(xs - ws/2, ys - hs/2, ws, hs, angle1, angle2 - angle1, Arc2D.OPEN));
          draw();
      }
  
      /**
       * Draw a square of side length 2r, centered on (x, y).
       * @param x the x-coordinate of the center of the square
       * @param y the y-coordinate of the center of the square
       * @param r radius is half the length of any side of the square
       * @throws RuntimeException if r is negative
       */
      public static void square(double x, double y, double r) {
          if (r < 0) throw new RuntimeException("square side length can't be negative");
          double xs = scaleX(x);
          double ys = scaleY(y);
          double ws = factorX(2*r);
          double hs = factorY(2*r);
          if (ws <= 1 && hs <= 1) pixel(x, y);
          else offscreen.draw(new Rectangle2D.Double(xs - ws/2, ys - hs/2, ws, hs));
          draw();
      }
  
      /**
       * Draw a filled square of side length 2r, centered on (x, y).
       * @param x the x-coordinate of the center of the square
       * @param y the y-coordinate of the center of the square
       * @param r radius is half the length of any side of the square
       * @throws RuntimeException if r is negative
       */
      public static void filledSquare(double x, double y, double r) {
          if (r < 0) throw new RuntimeException("square side length can't be negative");
          double xs = scaleX(x);
          double ys = scaleY(y);
          double ws = factorX(2*r);
          double hs = factorY(2*r);
          if (ws <= 1 && hs <= 1) pixel(x, y);
          else offscreen.fill(new Rectangle2D.Double(xs - ws/2, ys - hs/2, ws, hs));
          draw();
      }
  
  
      /**
       * Draw a rectangle of given half width and half height, centered on (x, y).
       * @param x the x-coordinate of the center of the rectangle
       * @param y the y-coordinate of the center of the rectangle
       * @param halfWidth is half the width of the rectangle
       * @param halfHeight is half the height of the rectangle
       * @throws RuntimeException if halfWidth or halfHeight is negative
       */
      public static void rectangle(double x, double y, double halfWidth, double halfHeight) {
          if (halfWidth  < 0) throw new RuntimeException("half width can't be negative");
          if (halfHeight < 0) throw new RuntimeException("half height can't be negative");
          double xs = scaleX(x);
          double ys = scaleY(y);
          double ws = factorX(2*halfWidth);
          double hs = factorY(2*halfHeight);
          if (ws <= 1 && hs <= 1) pixel(x, y);
          else offscreen.draw(new Rectangle2D.Double(xs - ws/2, ys - hs/2, ws, hs));
          draw();
      }
  
      /**
       * Draw a filled rectangle of given half width and half height, centered on (x, y).
       * @param x the x-coordinate of the center of the rectangle
       * @param y the y-coordinate of the center of the rectangle
       * @param halfWidth is half the width of the rectangle
       * @param halfHeight is half the height of the rectangle
       * @throws RuntimeException if halfWidth or halfHeight is negative
       */
      public static void filledRectangle(double x, double y, double halfWidth, double halfHeight) {
          if (halfWidth  < 0) throw new RuntimeException("half width can't be negative");
          if (halfHeight < 0) throw new RuntimeException("half height can't be negative");
          double xs = scaleX(x);
          double ys = scaleY(y);
          double ws = factorX(2*halfWidth);
          double hs = factorY(2*halfHeight);
          if (ws <= 1 && hs <= 1) pixel(x, y);
          else offscreen.fill(new Rectangle2D.Double(xs - ws/2, ys - hs/2, ws, hs));
          draw();
      }
  
  
      /**
       * Draw a polygon with the given (x[i], y[i]) coordinates.
       * @param x an array of all the x-coordindates of the polygon
       * @param y an array of all the y-coordindates of the polygon
       */
      public static void polygon(double[] x, double[] y) {
          int N = x.length;
          GeneralPath path = new GeneralPath();
          path.moveTo((float) scaleX(x[0]), (float) scaleY(y[0]));
          for (int i = 0; i < N; i++)
              path.lineTo((float) scaleX(x[i]), (float) scaleY(y[i]));
          path.closePath();
          offscreen.draw(path);
          draw();
      }
  
      /**
       * Draw a filled polygon with the given (x[i], y[i]) coordinates.
       * @param x an array of all the x-coordindates of the polygon
       * @param y an array of all the y-coordindates of the polygon
       */
      public static void filledPolygon(double[] x, double[] y) {
          int N = x.length;
          GeneralPath path = new GeneralPath();
          path.moveTo((float) scaleX(x[0]), (float) scaleY(y[0]));
          for (int i = 0; i < N; i++)
              path.lineTo((float) scaleX(x[i]), (float) scaleY(y[i]));
          path.closePath();
          offscreen.fill(path);
          draw();
      }
  
  
  
     /*************************************************************************
      *  Drawing images.
      *************************************************************************/
  
      // get an image from the given filename
      private static Image getImage(String filename) {
  
          // to read from file
          ImageIcon icon = new ImageIcon(filename);
  
          // try to read from URL
          if ((icon == null) || (icon.getImageLoadStatus() != MediaTracker.COMPLETE)) {
              try {
                  URL url = new URL(filename);
                  icon = new ImageIcon(url);
              } catch (Exception e) { /* not a url */ }
          }
  
          // in case file is inside a .jar
          if ((icon == null) || (icon.getImageLoadStatus() != MediaTracker.COMPLETE)) {
              URL url = StdDraw.class.getResource(filename);
              if (url == null) throw new RuntimeException("image " + filename + " not found");
              icon = new ImageIcon(url);
          }
  
          return icon.getImage();
      }
  
      /**
       * Draw picture (gif, jpg, or png) centered on (x, y).
       * @param x the center x-coordinate of the image
       * @param y the center y-coordinate of the image
       * @param s the name of the image/picture, e.g., "ball.gif"
       * @throws RuntimeException if the image is corrupt
       */
      public static void picture(double x, double y, String s) {
          Image image = getImage(s);
          double xs = scaleX(x);
          double ys = scaleY(y);
          int ws = image.getWidth(null);
          int hs = image.getHeight(null);
          if (ws < 0 || hs < 0) throw new RuntimeException("image " + s + " is corrupt");
  
          offscreen.drawImage(image, (int) Math.round(xs - ws/2.0), (int) Math.round(ys - hs/2.0), null);
          draw();
      }
  
      /**
       * Draw picture (gif, jpg, or png) centered on (x, y),
       * rotated given number of degrees
       * @param x the center x-coordinate of the image
       * @param y the center y-coordinate of the image
       * @param s the name of the image/picture, e.g., "ball.gif"
       * @param degrees is the number of degrees to rotate counterclockwise
       * @throws RuntimeException if the image is corrupt
       */
      public static void picture(double x, double y, String s, double degrees) {
          Image image = getImage(s);
          double xs = scaleX(x);
          double ys = scaleY(y);
          int ws = image.getWidth(null);
          int hs = image.getHeight(null);
          if (ws < 0 || hs < 0) throw new RuntimeException("image " + s + " is corrupt");
  
          offscreen.rotate(Math.toRadians(-degrees), xs, ys);
          offscreen.drawImage(image, (int) Math.round(xs - ws/2.0), (int) Math.round(ys - hs/2.0), null);
          offscreen.rotate(Math.toRadians(+degrees), xs, ys);
  
          draw();
      }
  
      /**
       * Draw picture (gif, jpg, or png) centered on (x, y), rescaled to w-by-h.
       * @param x the center x coordinate of the image
       * @param y the center y coordinate of the image
       * @param s the name of the image/picture, e.g., "ball.gif"
       * @param w the width of the image
       * @param h the height of the image
       * @throws RuntimeException if the width height are negative
       * @throws RuntimeException if the image is corrupt
       */
      public static void picture(double x, double y, String s, double w, double h) {
          Image image = getImage(s);
          double xs = scaleX(x);
          double ys = scaleY(y);
          if (w < 0) throw new RuntimeException("width is negative: " + w);
          if (h < 0) throw new RuntimeException("height is negative: " + h);
          double ws = factorX(w);
          double hs = factorY(h);
          if (ws < 0 || hs < 0) throw new RuntimeException("image " + s + " is corrupt");
          if (ws <= 1 && hs <= 1) pixel(x, y);
          else {
              offscreen.drawImage(image, (int) Math.round(xs - ws/2.0),
                                         (int) Math.round(ys - hs/2.0),
                                         (int) Math.round(ws),
                                         (int) Math.round(hs), null);
          }
          draw();
      }
  
  
      /**
       * Draw picture (gif, jpg, or png) centered on (x, y), rotated
       * given number of degrees, rescaled to w-by-h.
       * @param x the center x-coordinate of the image
       * @param y the center y-coordinate of the image
       * @param s the name of the image/picture, e.g., "ball.gif"
       * @param w the width of the image
       * @param h the height of the image
       * @param degrees is the number of degrees to rotate counterclockwise
       * @throws RuntimeException if the image is corrupt
       */
      public static void picture(double x, double y, String s, double w, double h, double degrees) {
          Image image = getImage(s);
          double xs = scaleX(x);
          double ys = scaleY(y);
          double ws = factorX(w);
          double hs = factorY(h);
          if (ws < 0 || hs < 0) throw new RuntimeException("image " + s + " is corrupt");
          if (ws <= 1 && hs <= 1) pixel(x, y);
  
          offscreen.rotate(Math.toRadians(-degrees), xs, ys);
          offscreen.drawImage(image, (int) Math.round(xs - ws/2.0),
                                     (int) Math.round(ys - hs/2.0),
                                     (int) Math.round(ws),
                                     (int) Math.round(hs), null);
          offscreen.rotate(Math.toRadians(+degrees), xs, ys);
  
          draw();
      }
  
  
     /*************************************************************************
      *  Drawing text.
      *************************************************************************/
  
      /**
       * Write the given text string in the current font, centered on (x, y).
       * @param x the center x-coordinate of the text
       * @param y the center y-coordinate of the text
       * @param s the text
       */
      public static void text(double x, double y, 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.drawString(s, (float) (xs - ws/2.0), (float) (ys + hs));
          draw();
      }
  
      /**
       * Write the given text string in the current font, centered on (x, y) and
       * rotated by the specified number of degrees  
       * @param x the center x-coordinate of the text
       * @param y the center y-coordinate of the text
       * @param s the text
       * @param degrees is the number of degrees to rotate counterclockwise
       */
      public static void text(double x, double y, String s, double degrees) {
          double xs = scaleX(x);
          double ys = scaleY(y);
          offscreen.rotate(Math.toRadians(-degrees), xs, ys);
          text(x, y, s);
          offscreen.rotate(Math.toRadians(+degrees), xs, ys);
      }
  
  
      /**
       * Write the given text string in the current font, left-aligned at (x, y).
       * @param x the x-coordinate of the text
       * @param y the y-coordinate of the text
       * @param s the text
       */
      public static void textLeft(double x, double y, String s) {
          offscreen.setFont(font);
          FontMetrics metrics = offscreen.getFontMetrics();
          double xs = scaleX(x);
          double ys = scaleY(y);
          int hs = metrics.getDescent();
          offscreen.drawString(s, (float) (xs), (float) (ys + hs));
          draw();
      }
  
      /**
       * Write the given text string in the current font, right-aligned at (x, y).
       * @param x the x-coordinate of the text
       * @param y the y-coordinate of the text
       * @param s the text
       */
      public static void textRight(double x, double y, 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.drawString(s, (float) (xs - ws), (float) (ys + hs));
          draw();
      }
  
  
  
      /**
       * Display on screen, pause for t milliseconds, and turn on
       * <em>animation mode</em>: subsequent calls to
       * drawing methods such as <tt>line()</tt>, <tt>circle()</tt>, and <tt>square()</tt>
       * will not be displayed on screen until the next call to <tt>show()</tt>.
       * This is useful for producing animations (clear the screen, draw a bunch of shapes,
       * display on screen for a fixed amount of time, and repeat). It also speeds up
       * drawing a huge number of shapes (call <tt>show(0)</tt> to defer drawing
       * on screen, draw the shapes, and call <tt>show(0)</tt> to display them all
       * on screen at once).
       * @param t number of milliseconds
       */
      public static void show(int t) {
          defer = false;
          draw();
          try { Thread.currentThread().sleep(t); }
          catch (InterruptedException e) { System.out.println("Error sleeping"); }
          defer = true;
      }
  
      /**
       * Display on-screen and turn off animation mode:
       * subsequent calls to
       * drawing methods such as <tt>line()</tt>, <tt>circle()</tt>, and <tt>square()</tt>
       * will be displayed on screen when called. This is the default.
       */
      public static void show() {
          defer = false;
          draw();
      }
  
      // draw onscreen if defer is false
      private static void draw() {
          if (defer) return;
          onscreen.drawImage(offscreenImage, 0, 0, null);
          frame.repaint();
      }
  
  
     /*************************************************************************
      *  Save drawing to a file.
      *************************************************************************/
  
      /**
       * Save onscreen image to file - suffix must be png, jpg, or gif.
       * @param filename the name of the file with one of the required suffixes
       */
      public static void save(String filename) {
          File file = new File(filename);
          String suffix = filename.substring(filename.lastIndexOf('.') + 1);
  
          // png files
          if (suffix.toLowerCase().equals("png")) {
              try { ImageIO.write(onscreenImage, suffix, file); }
              catch (IOException e) { e.printStackTrace(); }
          }
  
          // need to change from ARGB to RGB for jpeg
          // reference: http://archives.java.sun.com/cgi-bin/wa?A2=ind0404&L=java2d-interest&D=0&P=2727
          else if (suffix.toLowerCase().equals("jpg")) {
              WritableRaster raster = onscreenImage.getRaster();
              WritableRaster newRaster;
              newRaster = raster.createWritableChild(0, 0, width, height, 0, 0, new int[] {0, 1, 2});
              DirectColorModel cm = (DirectColorModel) onscreenImage.getColorModel();
              DirectColorModel newCM = new DirectColorModel(cm.getPixelSize(),
                                                            cm.getRedMask(),
                                                            cm.getGreenMask(),
                                                            cm.getBlueMask());
              BufferedImage rgbBuffer = new BufferedImage(newCM, newRaster, false,  null);
              try { ImageIO.write(rgbBuffer, suffix, file); }
              catch (IOException e) { e.printStackTrace(); }
          }
  
          else {
              System.out.println("Invalid image file type: " + suffix);
          }
      }
  
  
      /**
       * This method cannot be called directly.
       */
      public void actionPerformed(ActionEvent e) {
          FileDialog chooser = new FileDialog(StdDraw.frame, "Use a .png or .jpg extension", FileDialog.SAVE);
          chooser.setVisible(true);
          String filename = chooser.getFile();
          if (filename != null) {
              StdDraw.save(chooser.getDirectory() + File.separator + chooser.getFile());
          }
      }
  
  
     /*************************************************************************
      *  Mouse interactions.
      *************************************************************************/
  
      /**
       * Is the mouse being pressed?
       * @return true or false
       */
      public static boolean mousePressed() {
          synchronized (mouseLock) {
              return mousePressed;
          }
      }
  
      /**
       * What is the x-coordinate of the mouse?
       * @return the value of the x-coordinate of the mouse
       */
      public static double mouseX() {
          synchronized (mouseLock) {
              return mouseX;
          }
      }
  
      /**
       * What is the y-coordinate of the mouse?
       * @return the value of the y-coordinate of the mouse
       */
      public static double mouseY() {
          synchronized (mouseLock) {
              return mouseY;
          }
      }
  
  
      /**
       * This method cannot be called directly.
       */
      public void mouseClicked(MouseEvent e) { }
  
      /**
       * This method cannot be called directly.
       */
      public void mouseEntered(MouseEvent e) { }
  
      /**
       * This method cannot be called directly.
       */
      public void mouseExited(MouseEvent e) { }
  
      /**
       * This method cannot be called directly.
       */
      public void mousePressed(MouseEvent e) {
          synchronized (mouseLock) {
              mouseX = StdDraw.userX(e.getX());
              mouseY = StdDraw.userY(e.getY());
              mousePressed = true;
          }
      }
  
      /**
       * This method cannot be called directly.
       */
      public void mouseReleased(MouseEvent e) {
          synchronized (mouseLock) {
              mousePressed = false;
          }
      }
  
      /**
       * This method cannot be called directly.
       */
      public void mouseDragged(MouseEvent e)  {
          synchronized (mouseLock) {
              mouseX = StdDraw.userX(e.getX());
              mouseY = StdDraw.userY(e.getY());
          }
      }
  
      /**
       * This method cannot be called directly.
       */
      public void mouseMoved(MouseEvent e) {
          synchronized (mouseLock) {
              mouseX = StdDraw.userX(e.getX());
              mouseY = StdDraw.userY(e.getY());
          }
      }
  
  
     /*************************************************************************
      *  Keyboard interactions.
      *************************************************************************/
  
      /**
       * Has the user typed a key?
       * @return true if the user has typed a key, false otherwise
       */
      public static boolean hasNextKeyTyped() {
          synchronized (keyLock) {
              return !keysTyped.isEmpty();
          }
      }
  
      /**
       * What is the next key that was typed by the user? This method returns
       * a Unicode character corresponding to the key typed (such as 'a' or 'A').
       * It cannot identify action keys (such as F1
       * and arrow keys) or modifier keys (such as control).
       * @return the next Unicode key typed
       */
      public static char nextKeyTyped() {
          synchronized (keyLock) {
              return keysTyped.removeLast();
          }
      }
  
      /**
       * Is the keycode currently being pressed? This method takes as an argument
       * the keycode (corresponding to a physical key). It can handle action keys
       * (such as F1 and arrow keys) and modifier keys (such as shift and control).
       * See <a href = "http://download.oracle.com/javase/6/docs/api/java/awt/event/KeyEvent.html">KeyEvent.java</a>
       * for a description of key codes.
       * @return true if keycode is currently being pressed, false otherwise
       */
      public static boolean isKeyPressed(int keycode) {
          return keysDown.contains(keycode);
      }
  
  
      /**
       * This method cannot be called directly.
       */
      public void keyTyped(KeyEvent e) {
          synchronized (keyLock) {
              keysTyped.addFirst(e.getKeyChar());
          }
      }
  
      /**
       * This method cannot be called directly.
       */
      public void keyPressed(KeyEvent e) {
          keysDown.add(e.getKeyCode());
      }
  
      /**
       * This method cannot be called directly.
       */
      public void keyReleased(KeyEvent e) {
          keysDown.remove(e.getKeyCode());
      }
  
  
  
  
      /**
       * Test client.
       */
      public static void main(String[] args) {
          StdDraw.square(.2, .8, .1);
          StdDraw.filledSquare(.8, .8, .2);
          StdDraw.circle(.8, .2, .2);
  
          StdDraw.setPenColor(StdDraw.BOOK_RED);
          StdDraw.setPenRadius(.02);
          StdDraw.arc(.8, .2, .1, 200, 45);
  
          // draw a blue diamond
          StdDraw.setPenRadius();
          StdDraw.setPenColor(StdDraw.BOOK_BLUE);
          double[] x = { .1, .2, .3, .2 };
          double[] y = { .2, .3, .2, .1 };
          StdDraw.filledPolygon(x, y);
  
          // text
          StdDraw.setPenColor(StdDraw.BLACK);
          StdDraw.text(0.2, 0.5, "black text");
          StdDraw.setPenColor(StdDraw.WHITE);
          StdDraw.text(0.8, 0.8, "white text");
      }
  
  }
  