/****************************************************************************** * Compilation: javac StdDraw.java * Execution: java StdDraw * Dependencies: none * * 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. * - Fix setCanvasSize() so that it can only be called once. * - On some systems, drawing a line (or other shape) that extends way * beyond canvas (e.g., to infinity) dimensions does not get drawn. * * Remarks * ------- * - don't use AffineTransform for rescaling since it inverts * images and strings * ******************************************************************************/ import java.awt.BasicStroke; import java.awt.Color; import java.awt.Component; import java.awt.FileDialog; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Image; import java.awt.MediaTracker; import java.awt.RenderingHints; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.awt.geom.Arc2D; import java.awt.geom.Ellipse2D; import java.awt.geom.GeneralPath; import java.awt.geom.Line2D; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.awt.image.DirectColorModel; import java.awt.image.WritableRaster; import java.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.util.LinkedList; import java.util.TreeSet; import java.util.NoSuchElementException; import javax.imageio.ImageIO; import javax.swing.ImageIcon; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.KeyStroke; /** * The {@code StdDraw} 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, squares, * circles, and other geometric shapes in a window on your computer and * to save the drawings to a file. Standard drawing also includes * facilities for text, color, pictures, and animation, along with * user interaction via the keyboard and mouse. *
* Getting started. * To use this class, you must have {@code StdDraw.class} in your * Java classpath. If you used our autoinstaller, you should be all set. * Otherwise, either download * stdlib.jar * and add to your Java classpath or download * StdDraw.java * and put a copy in your working directory. *
* Now, type the following short program into your editor: *
 *   public class TestStdDraw {
 *       public static void main(String[] args) {
 *           StdDraw.setPenRadius(0.05);
 *           StdDraw.setPenColor(StdDraw.BLUE);
 *           StdDraw.point(0.5, 0.5);
 *           StdDraw.setPenColor(StdDraw.MAGENTA);
 *           StdDraw.line(0.2, 0.2, 0.8, 0.2);
 *       }
 *   }
 *  
 *  If you compile and execute the program, you should see a window
 *  appear with a thick magenta line and a blue point.
 *  This program illustrates the two main types of methods in standard
 *  drawing—methods that draw geometric shapes and methods that
 *  control drawing parameters.
 *  The methods {@code StdDraw.line()} and {@code StdDraw.point()}
 *  draw lines and points; the methods {@code StdDraw.setPenRadius()}
 *  and {@code StdDraw.setPenColor()} control the line thickness and color.
 *  * Points and lines. * You can draw points and line segments with the following methods: *
* The x- and y-coordinates must be in the drawing area * (between 0 and 1 and by default) or the points and lines will not be visible. *
* Squares, circles, rectangles, and ellipses. * You can draw squares, circles, rectangles, and ellipses using * the following methods: *
* All of these methods take as arguments the location and size of the shape. * The location is always specified by the x- and y-coordinates * of its center. * The size of a circle is specified by its radius and the size of an ellipse is * specified by the lengths of its semi-major and semi-minor axes. * The size of a square or rectangle is specified by its half-width or half-height. * The convention for drawing squares and rectangles is parallel to those for * drawing circles and ellipses, but may be unexpected to the uninitiated. *
* The methods above trace outlines of the given shapes. The following methods * draw filled versions: *
* Circular arcs. * You can draw circular arcs with the following method: *
* The arc is from the circle centered at (x, y) of the specified radius. * The arc extends from angle1 to angle2. By convention, the angles are * polar (counterclockwise angle from the x-axis) * and represented in degrees. For example, {@code StdDraw.arc(0.0, 0.0, 1.0, 0, 90)} * draws the arc of the unit circle from 3 o'clock (0 degrees) to 12 o'clock (90 degrees). *
* Polygons. * You can draw polygons with the following methods: *
* The points in the polygon are ({@code x[i]}, {@code y[i]}). * For example, the following code fragment draws a filled diamond * with vertices (0.1, 0.2), (0.2, 0.3), (0.3, 0.2), and (0.2, 0.1): *
 *   double[] x = { 0.1, 0.2, 0.3, 0.2 };
 *   double[] y = { 0.2, 0.3, 0.2, 0.1 };
 *   StdDraw.filledPolygon(x, y);
 *  
 *  * Pen size. * The pen is circular, so that when you set the pen radius to r * and draw a point, you get a circle of radius r. Also, lines are * of thickness 2r and have rounded ends. The default pen radius * is 0.005 and is not affected by coordinate scaling. This default pen * radius is about 1/200 the width of the default canvas, so that if * you draw 100 points equally spaced along a horizontal or vertical line, * you will be able to see individual circles, but if you draw 200 such * points, the result will look like a line. *
* For example, {@code StdDraw.setPenRadius(0.025)} makes * the thickness of the lines and the size of the points to be five times * the 0.005 default. * To draw points with the minimum possible radius (one pixel on typical * displays), set the pen radius to 0.0. *
* Pen color. * All geometric shapes (such as points, lines, and circles) are drawn using * the current pen color. By default, it is black. * You can change the pen color with the following methods: *
* The first method allows you to specify colors using the RGB color system. * This color picker * is a convenient way to find a desired color. * The second method allows you to specify colors using the * {@link Color} data type that is discussed in Chapter 3. Until then, * you can use this method with one of these predefined colors in standard drawing: * {@link #BLACK}, {@link #BLUE}, {@link #CYAN}, {@link #DARK_GRAY}, {@link #GRAY}, * {@link #GREEN}, {@link #LIGHT_GRAY}, {@link #MAGENTA}, {@link #ORANGE}, * {@link #PINK}, {@link #RED}, {@link #WHITE}, {@link #YELLOW}, * {@link #BOOK_BLUE}, {@link #BOOK_LIGHT_BLUE}, {@link #BOOK_RED}, and * {@link #PRINCETON_ORANGE}. * For example, {@code StdDraw.setPenColor(StdDraw.MAGENTA)} sets the * pen color to magenta. *
* Canvas size. * By default, all drawing takes places in a 512-by-512 canvas. * The canvas does not include the window title or window border. * You can change the size of the canvas with the following method: *
* This sets the canvas size to be width-by-height pixels. * It also erases the current drawing and resets the coordinate system, * pen radius, pen color, and font back to their default values. * Ordinarly, this method is called once, at the very beginning of a program. * For example, {@code StdDraw.setCanvasSize(800, 800)} * sets the canvas size to be 800-by-800 pixels. *
* Canvas scale and coordinate system. * By default, all drawing takes places in the unit square, with (0, 0) at * lower left and (1, 1) at upper right. You can change the default * coordinate system with the following methods: *
* The arguments are the coordinates of the minimum and maximum * x- or y-coordinates that will appear in the canvas. * For example, if you wish to use the default coordinate system but * leave a small margin, you can call {@code StdDraw.setScale(-.05, 1.05)}. *
* These methods change the coordinate system for subsequent drawing * commands; they do not affect previous drawings. * These methods do not change the canvas size; so, if the x- * and y-scales are different, squares will become rectangles * and circles will become ellipses. *
* Text. * You can use the following methods to annotate your drawings with text: *
* The first two methods write the specified text in the current font, * centered at (x, y). * The second method allows you to rotate the text. * The last two methods either left- or right-align the text at (x, y). *
* The default font is a Sans Serif font with point size 16. * You can use the following method to change the font: *
* You use the {@link Font} data type to specify the font. This allows you to * choose the face, size, and style of the font. For example, the following * code fragment sets the font to Arial Bold, 60 point. *
 *   Font font = new Font("Arial", Font.BOLD, 60);
 *   StdDraw.setFont(font);
 *   StdDraw.text(0.5, 0.5, "Hello, World");
 *  
 *  * Images. * You can use the following methods to add images to your drawings: *
* These methods draw the specified image, centered at (x, y). * The supported image formats are JPEG, PNG, and GIF. * The image will display at its native size, independent of the coordinate system. * Optionally, you can rotate the image a specified number of degrees counterclockwise * or rescale it to fit snugly inside a width-by-height bounding box. *
* Saving to a file. * You save your image to a file using the File → Save menu option. * You can also save a file programatically using the following method: *
* The supported image formats are JPEG and PNG. The filename must have either the * extension .jpg or .png. * We recommend using PNG for drawing that consist solely of geometric shapes and JPEG * for drawings that contains pictures. *
* Clearing the canvas. * To clear the entire drawing canvas, you can use the following methods: *
* The first method clears the canvas to white; the second method * allows you to specify a color of your choice. For example, * {@code StdDraw.clear(StdDraw.LIGHT_GRAY)} clears the canvas to a shade * of gray. *
* Computer animations and double buffering. * Double buffering is one of the most powerful features of standard drawing, * enabling computer animations. * The following methods control the way in which objects are drawn: *
* By default, double buffering is disabled, which means that as soon as you * call a drawing * method—such as {@code point()} or {@code line()}—the * results appear on the screen. *
* When double buffering is enabled by calling {@link #enableDoubleBuffering()}, * all drawing takes place on the offscreen canvas. The offscreen canvas * is not displayed. Only when you call * {@link #show()} does your drawing get copied from the offscreen canvas to * the onscreen canvas, where it is displayed in the standard drawing window. You * can think of double buffering as collecting all of the lines, points, shapes, * and text that you tell it to draw, and then drawing them all * simultaneously, upon request. *
* The most important use of double buffering is to produce computer * animations, creating the illusion of motion by rapidly * displaying static drawings. To produce an animation, repeat * the following four steps: *
* The {@link #clear()}, {@link #show()}, and {@link #pause(int t)} methods * support the first, third, and fourth of these steps, respectively. *
* For example, this code fragment animates two balls moving in a circle. *
 *   StdDraw.setScale(-2, +2);
 *   StdDraw.enableDoubleBuffering();
 *
 *   for (double t = 0.0; true; t += 0.02) {
 *       double x = Math.sin(t);
 *       double y = Math.cos(t);
 *       StdDraw.clear();
 *       StdDraw.filledCircle(x, y, 0.05);
 *       StdDraw.filledCircle(-x, -y, 0.05);
 *       StdDraw.show();
 *       StdDraw.pause(20);
 *   }
 *  
 *  * Keyboard and mouse inputs. * Standard drawing has very basic support for keyboard and mouse input. * It is much less powerful than most user interface libraries provide, but also much simpler. * You can use the following methods to intercept mouse events: *
* The first method tells you whether a mouse button is currently being pressed. * The last two methods tells you the x- and y-coordinates of the mouse's * current position, using the same coordinate system as the canvas (the unit square, by default). * You should use these methods in an animation loop that waits a short while before trying * to poll the mouse for its current state. * You can use the following methods to intercept keyboard events: *
* If the user types lots of keys, they will be saved in a list until you process them. * The first method tells you whether the user has typed a key (that your program has * not yet processed). * The second method returns the next key that the user typed (that your program has * not yet processed) and removes it from the list of saved keystrokes. * The third method tells you whether a key is currently being pressed. *
* Accessing control parameters. * You can use the following methods to access the current pen color, pen radius, * and font: *
* These methods are useful when you want to temporarily change a * control parameter and reset it back to its original value. *
* Corner cases. * Here are some corner cases. *
* Performance tricks. * Standard drawing is capable of drawing large amounts of data. * Here are a few tricks and tips: *
* Known bugs and issues. *
 *  Reference.
 *  For additional documentation,
 *  see Section 1.5 of
 *  Computer Science: An Interdisciplinary Approach
 *  by Robert Sedgewick and Kevin Wayne.
 *
 *  @author Robert Sedgewick
 *  @author Kevin Wayne
 */
public final class StdDraw implements ActionListener, MouseListener, MouseMotionListener, KeyListener {
    /**
     *  The color black.
     */
    public static final Color BLACK = Color.BLACK;
    /**
     *  The color blue.
     */
    public static final Color BLUE = Color.BLUE;
    /**
     *  The color cyan.
     */
    public static final Color CYAN = Color.CYAN;
    /**
     *  The color dark gray.
     */
    public static final Color DARK_GRAY = Color.DARK_GRAY;
    /**
     *  The color gray.
     */
    public static final Color GRAY = Color.GRAY;
    /**
     *  The color green.
     */
    public static final Color GREEN  = Color.GREEN;
    /**
     *  The color light gray.
     */
    public static final Color LIGHT_GRAY = Color.LIGHT_GRAY;
    /**
     *  The color magenta.
     */
    public static final Color MAGENTA = Color.MAGENTA;
    /**
     *  The color orange.
     */
    public static final Color ORANGE = Color.ORANGE;
    /**
     *  The color pink.
     */
    public static final Color PINK = Color.PINK;
    /**
     *  The color red.
     */
    public static final Color RED = Color.RED;
    /**
     *  The color white.
     */
    public static final Color WHITE = Color.WHITE;
    /**
     *  The color yellow.
     */
    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, 166).
     */
    public static final Color BOOK_BLUE = new Color(9, 90, 166);
    /**
     * Shade of light blue used in Introduction to Programming in Java.
     * The RGB values are approximately (103, 198, 243).
     */
    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);
    /**
     * Shade of orange used in Princeton University's identity.
     * It is PMS 158. The RGB values are approximately (245, 128, 37).
     */
    public static final Color PRINCETON_ORANGE = new Color(245, 128, 37);
    // 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, 0% border
    // private static final double BORDER = 0.05;
    private static final double BORDER = 0.00;
    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 isMousePressed = false;
    private static double mouseX = 0;
    private static double mouseY = 0;
    // queue of typed key characters
    private static LinkedList 
     * The predefined pen colors are
     * {@code StdDraw.BLACK}, {@code StdDraw.BLUE}, {@code StdDraw.CYAN},
     * {@code StdDraw.DARK_GRAY}, {@code StdDraw.GRAY}, {@code StdDraw.GREEN},
     * {@code StdDraw.LIGHT_GRAY}, {@code StdDraw.MAGENTA}, {@code StdDraw.ORANGE},
     * {@code StdDraw.PINK}, {@code StdDraw.RED}, {@code StdDraw.WHITE}, and
     * {@code StdDraw.YELLOW}.
     *
     * @param color the color to make the pen
     * @throws IllegalArgumentException if {@code color} is {@code null}
     */
    public static void setPenColor(Color color) {
        validateNotNull(color, "color");
        penColor = color;
        offscreen.setColor(penColor);
    }
    /**
     * Sets the pen color to the specified RGB color.
     *
     * @param  red the amount of red (between 0 and 255)
     * @param  green the amount of green (between 0 and 255)
     * @param  blue the amount of blue (between 0 and 255)
     * @throws IllegalArgumentException if {@code red}, {@code green},
     *         or {@code blue} is outside its prescribed range
     */
    public static void setPenColor(int red, int green, int blue) {
        if (red   < 0 || red   >= 256) throw new IllegalArgumentException("red must be between 0 and 255");
        if (green < 0 || green >= 256) throw new IllegalArgumentException("green must be between 0 and 255");
        if (blue  < 0 || blue  >= 256) throw new IllegalArgumentException("blue must be between 0 and 255");
        setPenColor(new Color(red, green, blue));
    }
    /**
     * Returns the current font.
     *
     * @return the current font
     */
    public static Font getFont() {
        return font;
    }
    /**
     * Sets the font to the default font (sans serif, 16 point).
     */
    public static void setFont() {
        setFont(DEFAULT_FONT);
    }
    /**
     * Sets the font to the specified value.
     *
     * @param font the font
     * @throws IllegalArgumentException if {@code font} is {@code null}
     */
    public static void setFont(Font font) {
        validateNotNull(font, "font");
        StdDraw.font = font;
    }
   /***************************************************************************
    *  Drawing geometric shapes.
    ***************************************************************************/
    /**
     * Draws a line segment between (x0, y0) and
     * (x1, y1).
     *
     * @param  x0 the x-coordinate of one endpoint
     * @param  y0 the y-coordinate of one endpoint
     * @param  x1 the x-coordinate of the other endpoint
     * @param  y1 the y-coordinate of the other endpoint
     * @throws IllegalArgumentException if any coordinate is either NaN or infinite
     */
    public static void line(double x0, double y0, double x1, double y1) {
        validate(x0, "x0");
        validate(y0, "y0");
        validate(x1, "x1");
        validate(y1, "y1");
        offscreen.draw(new Line2D.Double(scaleX(x0), scaleY(y0), scaleX(x1), scaleY(y1)));
        draw();
    }
    /**
     * Draws one pixel at (x, y).
     * This method is private because pixels depend on the display.
     * To achieve the same effect, set the pen radius to 0 and call {@code point()}.
     *
     * @param  x the x-coordinate of the pixel
     * @param  y the y-coordinate of the pixel
     * @throws IllegalArgumentException if {@code x} or {@code y} is either NaN or infinite
     */
    private static void pixel(double x, double y) {
        validate(x, "x");
        validate(y, "y");
        offscreen.fillRect((int) Math.round(scaleX(x)), (int) Math.round(scaleY(y)), 1, 1);
    }
    /**
     * Draws a point centered at (x, y).
     * The point is a filled circle whose radius is equal to the pen radius.
     * To draw a single-pixel point, first set the pen radius to 0.
     *
     * @param x the x-coordinate of the point
     * @param y the y-coordinate of the point
     * @throws IllegalArgumentException if either {@code x} or {@code y} is either NaN or infinite
     */
    public static void point(double x, double y) {
        validate(x, "x");
        validate(y, "y");
        double xs = scaleX(x);
        double ys = scaleY(y);
        double r = penRadius;
        float scaledPenRadius = (float) (r * DEFAULT_SIZE);
        // double ws = factorX(2*r);
        // double hs = factorY(2*r);
        // if (ws <= 1 && hs <= 1) pixel(x, y);
        if (scaledPenRadius <= 1) pixel(x, y);
        else offscreen.fill(new Ellipse2D.Double(xs - scaledPenRadius/2, ys - scaledPenRadius/2,
                                                 scaledPenRadius, scaledPenRadius));
        draw();
    }
    /**
     * Draws a circle of the specified radius, centered at (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  radius the radius of the circle
     * @throws IllegalArgumentException if {@code radius} is negative
     * @throws IllegalArgumentException if any argument is either NaN or infinite
     */
    public static void circle(double x, double y, double radius) {
        validate(x, "x");
        validate(y, "y");
        validate(radius, "radius");
        validateNonnegative(radius, "radius");
        double xs = scaleX(x);
        double ys = scaleY(y);
        double ws = factorX(2*radius);
        double hs = factorY(2*radius);
        if (ws <= 1 && hs <= 1) pixel(x, y);
        else offscreen.draw(new Ellipse2D.Double(xs - ws/2, ys - hs/2, ws, hs));
        draw();
    }
    /**
     * Draws a filled circle of the specified radius, centered at (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  radius the radius of the circle
     * @throws IllegalArgumentException if {@code radius} is negative
     * @throws IllegalArgumentException if any argument is either NaN or infinite
     */
    public static void filledCircle(double x, double y, double radius) {
        validate(x, "x");
        validate(y, "y");
        validate(radius, "radius");
        validateNonnegative(radius, "radius");
        double xs = scaleX(x);
        double ys = scaleY(y);
        double ws = factorX(2*radius);
        double hs = factorY(2*radius);
        if (ws <= 1 && hs <= 1) pixel(x, y);
        else offscreen.fill(new Ellipse2D.Double(xs - ws/2, ys - hs/2, ws, hs));
        draw();
    }
    /**
     * Draws an ellipse with the specified semimajor and semiminor axes,
     * centered at (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 IllegalArgumentException if either {@code semiMajorAxis}
     *         or {@code semiMinorAxis} is negative
     * @throws IllegalArgumentException if any argument is either NaN or infinite
     */
    public static void ellipse(double x, double y, double semiMajorAxis, double semiMinorAxis) {
        validate(x, "x");
        validate(y, "y");
        validate(semiMajorAxis, "semimajor axis");
        validate(semiMinorAxis, "semiminor axis");
        validateNonnegative(semiMajorAxis, "semimajor axis");
        validateNonnegative(semiMinorAxis, "semiminor axis");
        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();
    }
    /**
     * Draws a filled ellipse with the specified semimajor and semiminor axes,
     * centered at (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 IllegalArgumentException if either {@code semiMajorAxis}
     *         or {@code semiMinorAxis} is negative
     * @throws IllegalArgumentException if any argument is either NaN or infinite
     */
    public static void filledEllipse(double x, double y, double semiMajorAxis, double semiMinorAxis) {
        validate(x, "x");
        validate(y, "y");
        validate(semiMajorAxis, "semimajor axis");
        validate(semiMinorAxis, "semiminor axis");
        validateNonnegative(semiMajorAxis, "semimajor axis");
        validateNonnegative(semiMinorAxis, "semiminor axis");
        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();
    }
    /**
     * Draws a circular arc of the specified radius,
     * centered at (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  radius 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 IllegalArgumentException if {@code radius} is negative
     * @throws IllegalArgumentException if any argument is either NaN or infinite
     */
    public static void arc(double x, double y, double radius, double angle1, double angle2) {
        validate(x, "x");
        validate(y, "y");
        validate(radius, "arc radius");
        validate(angle1, "angle1");
        validate(angle2, "angle2");
        validateNonnegative(radius, "arc radius");
        while (angle2 < angle1) angle2 += 360;
        double xs = scaleX(x);
        double ys = scaleY(y);
        double ws = factorX(2*radius);
        double hs = factorY(2*radius);
        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();
    }
    /**
     * Draws a square of the specified size, centered at (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  halfLength one half the length of any side of the square
     * @throws IllegalArgumentException if {@code halfLength} is negative
     * @throws IllegalArgumentException if any argument is either NaN or infinite
     */
    public static void square(double x, double y, double halfLength) {
        validate(x, "x");
        validate(y, "y");
        validate(halfLength, "halfLength");
        validateNonnegative(halfLength, "half length");
        double xs = scaleX(x);
        double ys = scaleY(y);
        double ws = factorX(2*halfLength);
        double hs = factorY(2*halfLength);
        if (ws <= 1 && hs <= 1) pixel(x, y);
        else offscreen.draw(new Rectangle2D.Double(xs - ws/2, ys - hs/2, ws, hs));
        draw();
    }
    /**
     * Draws a filled square of the specified size, centered at (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  halfLength one half the length of any side of the square
     * @throws IllegalArgumentException if {@code halfLength} is negative
     * @throws IllegalArgumentException if any argument is either NaN or infinite
     */
    public static void filledSquare(double x, double y, double halfLength) {
        validate(x, "x");
        validate(y, "y");
        validate(halfLength, "halfLength");
        validateNonnegative(halfLength, "half length");
        double xs = scaleX(x);
        double ys = scaleY(y);
        double ws = factorX(2*halfLength);
        double hs = factorY(2*halfLength);
        if (ws <= 1 && hs <= 1) pixel(x, y);
        else offscreen.fill(new Rectangle2D.Double(xs - ws/2, ys - hs/2, ws, hs));
        draw();
    }
    /**
     * Draws a rectangle of the specified size, centered at (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 one half the width of the rectangle
     * @param  halfHeight one half the height of the rectangle
     * @throws IllegalArgumentException if either {@code halfWidth} or {@code halfHeight} is negative
     * @throws IllegalArgumentException if any argument is either NaN or infinite
     */
    public static void rectangle(double x, double y, double halfWidth, double halfHeight) {
        validate(x, "x");
        validate(y, "y");
        validate(halfWidth, "halfWidth");
        validate(halfHeight, "halfHeight");
        validateNonnegative(halfWidth, "half width");
        validateNonnegative(halfHeight, "half height");
        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();
    }
    /**
     * Draws a filled rectangle of the specified size, centered at (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 one half the width of the rectangle
     * @param  halfHeight one half the height of the rectangle
     * @throws IllegalArgumentException if either {@code halfWidth} or {@code halfHeight} is negative
     * @throws IllegalArgumentException if any argument is either NaN or infinite
     */
    public static void filledRectangle(double x, double y, double halfWidth, double halfHeight) {
        validate(x, "x");
        validate(y, "y");
        validate(halfWidth, "halfWidth");
        validate(halfHeight, "halfHeight");
        validateNonnegative(halfWidth, "half width");
        validateNonnegative(halfHeight, "half height");
        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();
    }
    /**
     * Draws a polygon with the vertices 
     * (x0, y0),
     * (x1, y1), ...,
     * (xn–1, yn–1).
     *
     * @param  x an array of all the x-coordinates of the polygon
     * @param  y an array of all the y-coordinates of the polygon
     * @throws IllegalArgumentException unless {@code x[]} and {@code y[]}
     *         are of the same length
     * @throws IllegalArgumentException if any coordinate is either NaN or infinite
     * @throws IllegalArgumentException if either {@code x[]} or {@code y[]} is {@code null}
     */
    public static void polygon(double[] x, double[] y) {
        validateNotNull(x, "x-coordinate array");
        validateNotNull(y, "y-coordinate array");
        for (int i = 0; i < x.length; i++) validate(x[i], "x[" + i + "]");
        for (int i = 0; i < y.length; i++) validate(y[i], "y[" + i + "]");
        int n1 = x.length;
        int n2 = y.length;
        if (n1 != n2) throw new IllegalArgumentException("arrays must be of the same length");
        int n = n1;
        if (n == 0) return;
        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();
    }
    /**
     * Draws a filled polygon with the vertices 
     * (x0, y0),
     * (x1, y1), ...,
     * (xn–1, yn–1).
     *
     * @param  x an array of all the x-coordinates of the polygon
     * @param  y an array of all the y-coordinates of the polygon
     * @throws IllegalArgumentException unless {@code x[]} and {@code y[]}
     *         are of the same length
     * @throws IllegalArgumentException if any coordinate is either NaN or infinite
     * @throws IllegalArgumentException if either {@code x[]} or {@code y[]} is {@code null}
     */
    public static void filledPolygon(double[] x, double[] y) {
        validateNotNull(x, "x-coordinate array");
        validateNotNull(y, "y-coordinate array");
        for (int i = 0; i < x.length; i++) validate(x[i], "x[" + i + "]");
        for (int i = 0; i < y.length; i++) validate(y[i], "y[" + i + "]");
        int n1 = x.length;
        int n2 = y.length;
        if (n1 != n2) throw new IllegalArgumentException("arrays must be of the same length");
        int n = n1;
        if (n == 0) return;
        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) {
        if (filename == null) throw new IllegalArgumentException();
        // 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 (MalformedURLException e) {
                /* not a url */
            }
        }
        // in case file is inside a .jar (classpath relative to StdDraw)
        if ((icon == null) || (icon.getImageLoadStatus() != MediaTracker.COMPLETE)) {
            URL url = StdDraw.class.getResource(filename);
            if (url != null)
                icon = new ImageIcon(url);
        }
        // in case file is inside a .jar (classpath relative to root of jar)
        if ((icon == null) || (icon.getImageLoadStatus() != MediaTracker.COMPLETE)) {
            URL url = StdDraw.class.getResource("/" + filename);
            if (url == null) throw new IllegalArgumentException("image " + filename + " not found");
            icon = new ImageIcon(url);
        }
        return icon.getImage();
    }
   /***************************************************************************
    * [Summer 2016] Should we update to use ImageIO instead of ImageIcon()?
    *               Seems to have some issues loading images on some systems
    *               and slows things down on other systems.
    *               especially if you don't call ImageIO.setUseCache(false)
    *               One advantage is that it returns a BufferedImage.
    ***************************************************************************/
/*
    private static BufferedImage getImage(String filename) {
        if (filename == null) throw new IllegalArgumentException();
        // from a file or URL
        try {
            URL url = new URL(filename);
            BufferedImage image = ImageIO.read(url);
            return image;
        } 
        catch (IOException e) {
            // ignore
        }
        // in case file is inside a .jar (classpath relative to StdDraw)
        try {
            URL url = StdDraw.class.getResource(filename);
            BufferedImage image = ImageIO.read(url);
            return image;
        } 
        catch (IOException e) {
            // ignore
        }
        // in case file is inside a .jar (classpath relative to root of jar)
        try {
            URL url = StdDraw.class.getResource("/" + filename);
            BufferedImage image = ImageIO.read(url);
            return image;
        } 
        catch (IOException e) {
            // ignore
        }
        throw new IllegalArgumentException("image " + filename + " not found");
    }
*/
    /**
     * Draws the specified image centered at (x, y).
     * The supported image formats are JPEG, PNG, and GIF.
     * As an optimization, the picture is cached, so there is no performance
     * penalty for redrawing the same image multiple times (e.g., in an animation).
     * However, if you change the picture file after drawing it, subsequent
     * calls will draw the original picture.
     *
     * @param  x the center x-coordinate of the image
     * @param  y the center y-coordinate of the image
     * @param  filename the name of the image/picture, e.g., "ball.gif"
     * @throws IllegalArgumentException if the image filename is invalid
     * @throws IllegalArgumentException if either {@code x} or {@code y} is either NaN or infinite
     */
    public static void picture(double x, double y, String filename) {
        validate(x, "x");
        validate(y, "y");
        validateNotNull(filename, "filename");
        // BufferedImage image = getImage(filename);
        Image image = getImage(filename);
        double xs = scaleX(x);
        double ys = scaleY(y);
        // int ws = image.getWidth();    // can call only if image is a BufferedImage
        // int hs = image.getHeight();
        int ws = image.getWidth(null);
        int hs = image.getHeight(null);
        if (ws < 0 || hs < 0) throw new IllegalArgumentException("image " + filename + " is corrupt");
        offscreen.drawImage(image, (int) Math.round(xs - ws/2.0), (int) Math.round(ys - hs/2.0), null);
        draw();
    }
    /**
     * Draws the specified image centered at (x, y),
     * rotated given number of degrees.
     * The supported image formats are JPEG, PNG, and GIF.
     *
     * @param  x the center x-coordinate of the image
     * @param  y the center y-coordinate of the image
     * @param  filename the name of the image/picture, e.g., "ball.gif"
     * @param  degrees is the number of degrees to rotate counterclockwise
     * @throws IllegalArgumentException if the image filename is invalid
     * @throws IllegalArgumentException if {@code x}, {@code y}, {@code degrees} is NaN or infinite
     * @throws IllegalArgumentException if {@code filename} is {@code null}
     */
    public static void picture(double x, double y, String filename, double degrees) {
        validate(x, "x");
        validate(y, "y");
        validate(degrees, "degrees");
        validateNotNull(filename, "filename");
        // BufferedImage image = getImage(filename);
        Image image = getImage(filename);
        double xs = scaleX(x);
        double ys = scaleY(y);
        // int ws = image.getWidth();    // can call only if image is a BufferedImage
        // int hs = image.getHeight();
        int ws = image.getWidth(null);
        int hs = image.getHeight(null);
        if (ws < 0 || hs < 0) throw new IllegalArgumentException("image " + filename + " 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();
    }
    /**
     * Draws the specified image centered at (x, y),
     * rescaled to the specified bounding box.
     * The supported image formats are JPEG, PNG, and GIF.
     *
     * @param  x the center x-coordinate of the image
     * @param  y the center y-coordinate of the image
     * @param  filename the name of the image/picture, e.g., "ball.gif"
     * @param  scaledWidth the width of the scaled image (in screen coordinates)
     * @param  scaledHeight the height of the scaled image (in screen coordinates)
     * @throws IllegalArgumentException if either {@code scaledWidth}
     *         or {@code scaledHeight} is negative
     * @throws IllegalArgumentException if the image filename is invalid
     * @throws IllegalArgumentException if {@code x} or {@code y} is either NaN or infinite
     * @throws IllegalArgumentException if {@code filename} is {@code null}
     */
    public static void picture(double x, double y, String filename, double scaledWidth, double scaledHeight) {
        validate(x, "x");
        validate(y, "y");
        validate(scaledWidth, "scaled width");
        validate(scaledHeight, "scaled height");
        validateNotNull(filename, "filename");
        validateNonnegative(scaledWidth, "scaled width");
        validateNonnegative(scaledHeight, "scaled height");
        Image image = getImage(filename);
        double xs = scaleX(x);
        double ys = scaleY(y);
        double ws = factorX(scaledWidth);
        double hs = factorY(scaledHeight);
        if (ws < 0 || hs < 0) throw new IllegalArgumentException("image " + filename + " 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();
    }
    /**
     * Draws the specified image centered at (x, y), rotated
     * given number of degrees, and rescaled to the specified bounding box.
     * The supported image formats are JPEG, PNG, and GIF.
     *
     * @param  x the center x-coordinate of the image
     * @param  y the center y-coordinate of the image
     * @param  filename the name of the image/picture, e.g., "ball.gif"
     * @param  scaledWidth the width of the scaled image (in screen coordinates)
     * @param  scaledHeight the height of the scaled image (in screen coordinates)
     * @param  degrees is the number of degrees to rotate counterclockwise
     * @throws IllegalArgumentException if either {@code scaledWidth}
     *         or {@code scaledHeight} is negative
     * @throws IllegalArgumentException if the image filename is invalid
     */
    public static void picture(double x, double y, String filename, double scaledWidth, double scaledHeight, double degrees) {
        validate(x, "x");
        validate(y, "y");
        validate(scaledWidth, "scaled width");
        validate(scaledHeight, "scaled height");
        validate(degrees, "degrees");
        validateNotNull(filename, "filename");
        validateNonnegative(scaledWidth, "scaled width");
        validateNonnegative(scaledHeight, "scaled height");
        Image image = getImage(filename);
        double xs = scaleX(x);
        double ys = scaleY(y);
        double ws = factorX(scaledWidth);
        double hs = factorY(scaledHeight);
        if (ws < 0 || hs < 0) throw new IllegalArgumentException("image " + filename + " 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.
    ***************************************************************************/
    /**
     * Writes the given text string in the current font, centered at (x, y).
     *
     * @param  x the center x-coordinate of the text
     * @param  y the center y-coordinate of the text
     * @param  text the text to write
     * @throws IllegalArgumentException if {@code text} is {@code null}
     * @throws IllegalArgumentException if {@code x} or {@code y} is either NaN or infinite
     */
    public static void text(double x, double y, String text) {
        validate(x, "x");
        validate(y, "y");
        validateNotNull(text, "text");
        offscreen.setFont(font);
        FontMetrics metrics = offscreen.getFontMetrics();
        double xs = scaleX(x);
        double ys = scaleY(y);
        int ws = metrics.stringWidth(text);
        int hs = metrics.getDescent();
        offscreen.drawString(text, (float) (xs - ws/2.0), (float) (ys + hs));
        draw();
    }
    /**
     * Writes the given text string in the current font, centered at (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  text the text to write
     * @param  degrees is the number of degrees to rotate counterclockwise
     * @throws IllegalArgumentException if {@code text} is {@code null}
     * @throws IllegalArgumentException if {@code x}, {@code y}, or {@code degrees} is either NaN or infinite
     */
    public static void text(double x, double y, String text, double degrees) {
        validate(x, "x");
        validate(y, "y");
        validate(degrees, "degrees");
        validateNotNull(text, "text");
        double xs = scaleX(x);
        double ys = scaleY(y);
        offscreen.rotate(Math.toRadians(-degrees), xs, ys);
        text(x, y, text);
        offscreen.rotate(Math.toRadians(+degrees), xs, ys);
    }
    /**
     * Writes 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  text the text
     * @throws IllegalArgumentException if {@code text} is {@code null}
     * @throws IllegalArgumentException if {@code x} or {@code y} is either NaN or infinite
     */
    public static void textLeft(double x, double y, String text) {
        validate(x, "x");
        validate(y, "y");
        validateNotNull(text, "text");
        offscreen.setFont(font);
        FontMetrics metrics = offscreen.getFontMetrics();
        double xs = scaleX(x);
        double ys = scaleY(y);
        int hs = metrics.getDescent();
        offscreen.drawString(text, (float) xs, (float) (ys + hs));
        draw();
    }
    /**
     * Writes 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  text the text to write
     * @throws IllegalArgumentException if {@code text} is {@code null}
     * @throws IllegalArgumentException if {@code x} or {@code y} is either NaN or infinite
     */
    public static void textRight(double x, double y, String text) {
        validate(x, "x");
        validate(y, "y");
        validateNotNull(text, "text");
        offscreen.setFont(font);
        FontMetrics metrics = offscreen.getFontMetrics();
        double xs = scaleX(x);
        double ys = scaleY(y);
        int ws = metrics.stringWidth(text);
        int hs = metrics.getDescent();
        offscreen.drawString(text, (float) (xs - ws), (float) (ys + hs));
        draw();
    }
    /**
     * Copies the offscreen buffer to the onscreen buffer, pauses for t milliseconds
     * and enables double buffering.
     * @param t number of milliseconds
     * @deprecated replaced by {@link #enableDoubleBuffering()}, {@link #show()}, and {@link #pause(int t)}
     */
    @Deprecated
    public static void show(int t) {
        validateNonnegative(t, "t");
        show();
        pause(t);
        enableDoubleBuffering();
    }
    /**
     * Pauses for t milliseconds. This method is intended to support computer animations.
     * @param t number of milliseconds
     */
    public static void pause(int t) {
        validateNonnegative(t, "t");
        try {
            Thread.sleep(t);
        }
        catch (InterruptedException e) {
            System.out.println("Error sleeping");
        }
    }
    /**
     * Copies offscreen buffer to onscreen buffer. There is no reason to call
     * this method unless double buffering is enabled.
     */
    public static void show() {
        onscreen.drawImage(offscreenImage, 0, 0, null);
        frame.repaint();
    }
    // draw onscreen if defer is false
    private static void draw() {
        if (!defer) show();
    }
    /**
     * Enables double buffering. All subsequent calls to 
     * drawing methods such as {@code line()}, {@code circle()},
     * and {@code square()} will be deferred until the next call
     * to show(). Useful for animations.
     */
    public static void enableDoubleBuffering() {
        defer = true;
    }
    /**
     * Disables double buffering. All subsequent calls to 
     * drawing methods such as {@code line()}, {@code circle()},
     * and {@code square()} will be displayed on screen when called.
     * This is the default.
     */
    public static void disableDoubleBuffering() {
        defer = false;
    }
   /***************************************************************************
    *  Save drawing to a file.
    ***************************************************************************/
    /**
     * Saves the drawing to using the specified filename.
     * The supported image formats are JPEG and PNG;
     * the filename suffix must be {@code .jpg} or {@code .png}.
     *
     * @param  filename the name of the file with one of the required suffixes
     * @throws IllegalArgumentException if {@code filename} is {@code null}
     */
    public static void save(String filename) {
        validateNotNull(filename, "filename");
        File file = new File(filename);
        String suffix = filename.substring(filename.lastIndexOf('.') + 1);
        // png files
        if ("png".equalsIgnoreCase(suffix)) {
            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 ("jpg".equalsIgnoreCase(suffix)) {
            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.
     */
    @Override
    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.
    ***************************************************************************/
    /**
     * Returns true if the mouse is being pressed.
     *
     * @return {@code true} if the mouse is being pressed; {@code false} otherwise
     */
    public static boolean isMousePressed() {
        synchronized (mouseLock) {
            return isMousePressed;
        }
    }
    /**
     * Returns true if the mouse is being pressed.
     *
     * @return {@code true} if the mouse is being pressed; {@code false} otherwise
     * @deprecated replaced by {@link #isMousePressed()}
     */
    @Deprecated
    public static boolean mousePressed() {
        synchronized (mouseLock) {
            return isMousePressed;
        }
    }
    /**
     * Returns the x-coordinate of the mouse.
     *
     * @return the x-coordinate of the mouse
     */
    public static double mouseX() {
        synchronized (mouseLock) {
            return mouseX;
        }
    }
    /**
     * Returns the y-coordinate of the mouse.
     *
     * @return y-coordinate of the mouse
     */
    public static double mouseY() {
        synchronized (mouseLock) {
            return mouseY;
        }
    }
    /**
     * This method cannot be called directly.
     */
    @Override
    public void mouseClicked(MouseEvent e) {
        // this body is intentionally left empty
    }
    /**
     * This method cannot be called directly.
     */
    @Override
    public void mouseEntered(MouseEvent e) {
        // this body is intentionally left empty
    }
    /**
     * This method cannot be called directly.
     */
    @Override
    public void mouseExited(MouseEvent e) {
        // this body is intentionally left empty
    }
    /**
     * This method cannot be called directly.
     */
    @Override
    public void mousePressed(MouseEvent e) {
        synchronized (mouseLock) {
            mouseX = StdDraw.userX(e.getX());
            mouseY = StdDraw.userY(e.getY());
            isMousePressed = true;
        }
    }
    /**
     * This method cannot be called directly.
     */
    @Override
    public void mouseReleased(MouseEvent e) {
        synchronized (mouseLock) {
            isMousePressed = false;
        }
    }
    /**
     * This method cannot be called directly.
     */
    @Override
    public void mouseDragged(MouseEvent e)  {
        synchronized (mouseLock) {
            mouseX = StdDraw.userX(e.getX());
            mouseY = StdDraw.userY(e.getY());
        }
    }
    /**
     * This method cannot be called directly.
     */
    @Override
    public void mouseMoved(MouseEvent e) {
        synchronized (mouseLock) {
            mouseX = StdDraw.userX(e.getX());
            mouseY = StdDraw.userY(e.getY());
        }
    }
   /***************************************************************************
    *  Keyboard interactions.
    ***************************************************************************/
    /**
     * Returns true if the user has typed a key (that has not yet been processed).
     *
     * @return {@code true} if the user has typed a key (that has not yet been processed
     *         by {@link #nextKeyTyped()}; {@code false} otherwise
     */
    public static boolean hasNextKeyTyped() {
        synchronized (keyLock) {
            return !keysTyped.isEmpty();
        }
    }
    /**
     * Returns the next key that was typed by the user (that your program has not already processed).
     * This method should be preceded by a call to {@link #hasNextKeyTyped()} to ensure
     * that there is a next key to process.
     * This method returns a Unicode character corresponding to the key
     * typed (such as {@code 'a'} or {@code 'A'}).
     * It cannot identify action keys (such as F1 and arrow keys)
     * or modifier keys (such as control).
     *
     * @return the next key typed by the user (that your program has not already processed).
     * @throws NoSuchElementException if there is no remaining key
     */
    public static char nextKeyTyped() {
        synchronized (keyLock) {
            if (keysTyped.isEmpty()) {
                throw new NoSuchElementException("your program has already processed all keystrokes");
            }
            return keysTyped.remove(keysTyped.size() - 1);
            // return keysTyped.removeLast();
        }
    }
    /**
     * Returns true if the given key is being pressed.
     *  
     * This method takes the keycode (corresponding to a physical key)
    *  as an argument. It can handle action keys
     * (such as F1 and arrow keys) and modifier keys (such as shift and control).
     * See {@link KeyEvent} for a description of key codes.
     *
     * @param  keycode the key to check if it is being pressed
     * @return {@code true} if {@code keycode} is currently being pressed;
     *         {@code false} otherwise
     */
    public static boolean isKeyPressed(int keycode) {
        synchronized (keyLock) {
            return keysDown.contains(keycode);
        }
    }
    /**
     * This method cannot be called directly.
     */
    @Override
    public void keyTyped(KeyEvent e) {
        synchronized (keyLock) {
            keysTyped.addFirst(e.getKeyChar());
        }
    }
    /**
     * This method cannot be called directly.
     */
    @Override
    public void keyPressed(KeyEvent e) {
        synchronized (keyLock) {
            keysDown.add(e.getKeyCode());
        }
    }
    /**
     * This method cannot be called directly.
     */
    @Override
    public void keyReleased(KeyEvent e) {
        synchronized (keyLock) {
            keysDown.remove(e.getKeyCode());
        }
    }
   /***************************************************************************
    *  For improved resolution on Mac Retina displays.
    ***************************************************************************/
    private static class RetinaImageIcon extends ImageIcon {
    
        public RetinaImageIcon(Image image) {
            super(image);
        }
        public int getIconWidth() {
            return super.getIconWidth() / 2;
        }
        /**
         * Gets the height of the icon.
         *
         * @return the height in pixels of this icon
         */
        public int getIconHeight() {
            return super.getIconHeight() / 2;
        }
        public synchronized void paintIcon(Component c, Graphics g, int x, int y) {
            Graphics2D g2 = (Graphics2D) g.create();
            g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,RenderingHints.VALUE_INTERPOLATION_BICUBIC);
            g2.setRenderingHint(RenderingHints.KEY_RENDERING,RenderingHints.VALUE_RENDER_QUALITY);
            g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
            g2.scale(0.5, 0.5);
            super.paintIcon(c, g2, x * 2, y * 2);
            g2.dispose();
        }
    }
    /**
     * Test client.
     *
     * @param args the command-line arguments
     */
    public static void main(String[] args) {
        StdDraw.square(0.2, 0.8, 0.1);
        StdDraw.filledSquare(0.8, 0.8, 0.2);
        StdDraw.circle(0.8, 0.2, 0.2);
        StdDraw.setPenColor(StdDraw.BOOK_RED);
        StdDraw.setPenRadius(0.02);
        StdDraw.arc(0.8, 0.2, 0.1, 200, 45);
        // draw a blue diamond
        StdDraw.setPenRadius();
        StdDraw.setPenColor(StdDraw.BOOK_BLUE);
        double[] x = { 0.1, 0.2, 0.3, 0.2 };
        double[] y = { 0.2, 0.3, 0.2, 0.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");
    }
}