/****************************************************************************** * 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 geometric shapes (e.g., * points, lines, circles, rectangles) in a window on your computer * and to save the drawings to a file. * * Todo * ---- * - Don't show window until first unbuffered drawing command or call to show() * (with setVisible not set to false). * - Add support for gradient fill, etc. * - Fix setCanvasSize() so that it can be called only 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.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.net.URI; import java.net.URISyntaxException; 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 static methods 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, cut-and-paste 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.002 and is not affected by coordinate scaling. This default pen * radius is about 1/500 the width of the default canvas, so that if * you draw 200 points equally spaced along a horizontal or vertical line, * you will be able to see individual circles, but if you draw 250 such * points, the result will look like a line. *

*

* For example, {@code StdDraw.setPenRadius(0.01)} makes * the thickness of the lines and the size of the points to be five times * the 0.002 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, which is defined in Java's {@link java.awt} package. * Standard drawing defines a number of predefined colors including * {@link #BLACK}, {@link #WHITE}, {@link #RED}, {@link #GREEN}, * and {@link #BLUE}. * For example, {@code StdDraw.setPenColor(StdDraw.RED)} sets the * pen color to red. *

* Window title. * By default, the standard drawing window title is "Standard Draw". * You can change the title with the following method: *

*

* This sets the standard drawing window title to the specified string. *

* 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 clears the current drawing using the default background color (white). * Ordinarily, this method is called only 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: *

*

* To specify the font, you use the {@link Font} data type, * which is defined in Java's {@link java.awt} package. * 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. * The import statement allows you to refer to Font * directly, without needing the fully qualified name java.awt.Font. *

 *   import java.awt.Font;
 *   ...
 *   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 image must be in a supported file format (typically JPEG, PNG, GIF, TIFF, and BMP). * 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 bounding box. *

* Saving to a file. * You can save your image to a file using the File → Save menu option. * You can also save a file programmatically using the following method: *

*

* You can save the drawing to a file in a supported file format * (typically JPEG, PNG, GIF, TIFF, and BMP). * *

File formats. * The {@code StdDraw} class supports reading and writing images to any of the * file formats supported by {@link javax.imageio} (typically JPEG, PNG, * GIF, TIFF, and BMP). * The file extensions corresponding to JPEG, PNG, GIF, TIFF, and BMP, * are {@code .jpg}, {@code .png}, {@code .gif}, {@code .tif}, * and {@code .bmp}, respectively. *

* We recommend using PNG for drawing that consist solely of geometric shapes * and JPEG for drawings that contains pictures. * The JPEG file format does not support transparent backgrounds. * *

* Clearing the canvas. * To clear the entire drawing canvas, you can use the following methods: *

*

* The first method clears the canvas to the default background color (white); * the second method allows you to specify the background color. For example, * {@code StdDraw.clear(StdDraw.LIGHT_GRAY)} clears the canvas to a shade * of gray. To make the background transparent, * call {@code StdDraw.clear(StdDraw.TRANSPARENT)}. * *

* 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.0, +2.0);
 *   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.1);
 *       StdDraw.filledCircle(-x, -y, 0.1);
 *       StdDraw.show();
 *       StdDraw.pause(20);
 *   }
 *  
* Without double buffering, the balls would flicker as they move. *

* 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, later, 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 aqua (0, 255, 255). */ public static final Color AQUA = new Color(0, 255, 255); /** * The color black (0, 0, 0). */ public static final Color BLACK = Color.BLACK; /** * The color blue (0, 0, 255). */ public static final Color BLUE = Color.BLUE; /** * The color cyan (0, 255, 255). */ public static final Color CYAN = Color.CYAN; /** * The color fuscia (255, 0, 255). */ public static final Color FUSCIA = new Color(255, 0, 255); /** * The color dark gray (64, 64, 64). */ public static final Color DARK_GRAY = Color.DARK_GRAY; /** * The color gray (128, 128, 128). */ public static final Color GRAY = Color.GRAY; /** * The color green (0, 128, 0). */ public static final Color GREEN = new Color(0, 128, 0); /** * The color light gray (192, 192, 192). */ public static final Color LIGHT_GRAY = Color.LIGHT_GRAY; /** * The color lime (0, 255, 0). */ public static final Color LIME = new Color(0, 255, 0); /** * The color magenta (255, 0, 255). */ public static final Color MAGENTA = Color.MAGENTA; /** * The color maroon (128, 0, 0). */ public static final Color MAROON = new Color(128, 0, 0); /** * The color navy (0, 0, 128). */ public static final Color NAVY = new Color(0, 0, 128); /** * The color olive (128, 128, 0). */ public static final Color OLIVE = new Color(128, 128, 0); /** * The color orange (255, 200, 0). */ public static final Color ORANGE = Color.ORANGE; /** * The color pink (255, 175, 175). */ public static final Color PINK = Color.PINK; /** * The color purple (128, 0, 128). */ public static final Color PURPLE = new Color(128, 0, 128); /** * The color red (255, 0, 0). */ public static final Color RED = Color.RED; /** * The color silver (192, 192, 192). */ public static final Color SILVER = new Color(192, 192, 192); /** * The color teal (0, 128, 128). */ public static final Color TEAL = new Color(0, 128, 128); /** * The color white (255, 255, 255). */ public static final Color WHITE = Color.WHITE; /** * The color yellow (255, 255, 0). */ public static final Color YELLOW = Color.YELLOW; /** * A 100% transparent color, for a transparent background. */ public static final Color TRANSPARENT = new Color(0, 0, 0, 0); /** * The 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); /** * The 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); /** * The 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); /** * The 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_BACKGROUND_COLOR = WHITE; // current pen color private static Color penColor = DEFAULT_PEN_COLOR; // current background color private static Color backgroundColor = DEFAULT_BACKGROUND_COLOR; // default title of standard drawing window private static final String DEFAULT_WINDOW_TITLE = "Standard Draw"; // current title of standard drawing window private static String windowTitle = DEFAULT_WINDOW_TITLE; // 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 = DEFAULT_PEN_RADIUS; // 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 = DEFAULT_XMIN; private static double xmax = DEFAULT_XMAX; private static double ymin = DEFAULT_YMIN; private static double ymax = DEFAULT_YMAX; // for synchronization private static final Object MOUSE_LOCK = new Object(); private static final Object KEY_LOCK = new Object(); // default font private static final Font DEFAULT_FONT = new Font("SansSerif", Font.PLAIN, 16); // current font private static Font font = DEFAULT_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; // is the JFrame visible (upon calling draw())? private static boolean isJFrameVisible = true; // 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 keysTyped = new LinkedList(); // set of key codes currently pressed down private static TreeSet keysDown = new TreeSet(); // singleton pattern: client can't instantiate private StdDraw() { } // static initializer static { initCanvas(); initGUI(); } /** * Makes the drawing window visible or invisible. * * @param isVisible if {@code true}, makes the drawing window visible, * otherwise hides the drawing window. */ public static void setVisible(boolean isVisible) { isJFrameVisible = isVisible; frame.setVisible(isVisible); } /** * Sets the canvas (drawing area) to be 512-by-512 pixels. * This also clears the current drawing using the default background * color (white). * Ordinarily, this method is called once, at the very beginning * of a program. */ public static void setCanvasSize() { setCanvasSize(DEFAULT_SIZE, DEFAULT_SIZE); } /** * Sets the canvas (drawing area) to be width-by-height pixels. * This also clears the current drawing using the default background * color (white). * Ordinarily, this method is called once, at the very beginning * of a program. * * @param canvasWidth the width as a number of pixels * @param canvasHeight the height as a number of pixels * @throws IllegalArgumentException unless both {@code canvasWidth} and * {@code canvasHeight} are positive */ public static void setCanvasSize(int canvasWidth, int canvasHeight) { if (canvasWidth <= 0) throw new IllegalArgumentException("width must be positive"); if (canvasHeight <= 0) throw new IllegalArgumentException("height must be positive"); width = canvasWidth; height = canvasHeight; initCanvas(); initGUI(); } // initialize the drawing canvas private static void initCanvas() { // BufferedImage stuff offscreenImage = new BufferedImage(2*width, 2*height, BufferedImage.TYPE_INT_ARGB); onscreenImage = new BufferedImage(2*width, 2*height, BufferedImage.TYPE_INT_ARGB); offscreen = offscreenImage.createGraphics(); onscreen = onscreenImage.createGraphics(); offscreen.scale(2.0, 2.0); // since we made it 2x as big // initialize drawing window offscreen.setBackground(DEFAULT_BACKGROUND_COLOR); offscreen.clearRect(0, 0, width, height); onscreen.setBackground(DEFAULT_BACKGROUND_COLOR); onscreen.clearRect(0, 0, 2*width, 2*height); // set the pen color offscreen.setColor(DEFAULT_PEN_COLOR); // add antialiasing RenderingHints hints = new RenderingHints(null); hints.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); hints.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); offscreen.addRenderingHints(hints); } // initialize the GUI private static void initGUI() { // create the JFrame (if necessary) if (frame == null) { frame = new JFrame(); frame.addKeyListener(std); // JLabel cannot get keyboard focus frame.setFocusTraversalKeysEnabled(false); // allow VK_TAB with isKeyPressed() frame.setResizable(false); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // closes all windows // frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); // closes only current window frame.setTitle(windowTitle); frame.setJMenuBar(createMenuBar()); } // create the ImageIcon RetinaImageIcon icon = new RetinaImageIcon(onscreenImage); JLabel draw = new JLabel(icon); draw.addMouseListener(std); draw.addMouseMotionListener(std); // finsh up the JFrame frame.setContentPane(draw); frame.pack(); frame.requestFocusInWindow(); frame.setVisible(false); } // create the menu bar private static JMenuBar createMenuBar() { JMenuBar menuBar = new JMenuBar(); JMenu menu = new JMenu("File"); menuBar.add(menu); JMenuItem menuItem1 = new JMenuItem(" Save... "); menuItem1.addActionListener(std); // Java 11: use getMenuShortcutKeyMaskEx() // Java 8: use getMenuShortcutKeyMask() menuItem1.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx())); menu.add(menuItem1); return menuBar; } /** * Closes the standard drawing window. * This allows the client program to terminate instead of requiring * the user to close the standard drawing window manually. * Drawing after calling this method will restore the previous window state. */ public static void close() { frame.dispose(); } /*************************************************************************** * Input validation helper methods. ***************************************************************************/ // throw an IllegalArgumentException if x is NaN or infinite private static void validate(double x, String name) { if (Double.isNaN(x)) throw new IllegalArgumentException(name + " is NaN"); if (Double.isInfinite(x)) throw new IllegalArgumentException(name + " is infinite"); } // throw an IllegalArgumentException if s is null private static void validateNonnegative(double x, String name) { if (x < 0) throw new IllegalArgumentException(name + " negative"); } // throw an IllegalArgumentException if s is null private static void validateNotNull(Object x, String name) { if (x == null) throw new IllegalArgumentException(name + " is null"); } /*************************************************************************** * Set the title of standard drawing window. ***************************************************************************/ /** * Sets the title of the standard drawing window to the specified string. * * @param title the title * @throws IllegalArgumentException if {@code title} is {@code null} */ public static void setTitle(String title) { validateNotNull(title, "title"); frame.setTitle(title); windowTitle = title; } /*************************************************************************** * User and screen coordinate systems. ***************************************************************************/ /** * Sets the x-scale to the default range (between 0.0 and 1.0). */ public static void setXscale() { setXscale(DEFAULT_XMIN, DEFAULT_XMAX); } /** * Sets the y-scale to the default range (between 0.0 and 1.0). */ public static void setYscale() { setYscale(DEFAULT_YMIN, DEFAULT_YMAX); } /** * Sets both the x-scale and y-scale to the default range * (between 0.0 and 1.0). */ public static void setScale() { setXscale(); setYscale(); } /** * Sets the x-scale to the specified range. * * @param min the minimum value of the x-scale * @param max the maximum value of the x-scale * @throws IllegalArgumentException if {@code (max == min)} * @throws IllegalArgumentException if either {@code min} or {@code max} is either NaN or infinite */ public static void setXscale(double min, double max) { validate(min, "min"); validate(max, "max"); double size = max - min; if (size == 0.0) throw new IllegalArgumentException("the min and max are the same"); synchronized (MOUSE_LOCK) { xmin = min - BORDER * size; xmax = max + BORDER * size; } } /** * Sets the y-scale to the specified range. * * @param min the minimum value of the y-scale * @param max the maximum value of the y-scale * @throws IllegalArgumentException if {@code (max == min)} * @throws IllegalArgumentException if either {@code min} or {@code max} is either NaN or infinite */ public static void setYscale(double min, double max) { validate(min, "min"); validate(max, "max"); double size = max - min; if (size == 0.0) throw new IllegalArgumentException("the min and max are the same"); synchronized (MOUSE_LOCK) { ymin = min - BORDER * size; ymax = max + BORDER * size; } } /** * Sets both the x-scale and y-scale to the (same) specified range. * * @param min the minimum value of the x- and y-scales * @param max the maximum value of the x- and y-scales * @throws IllegalArgumentException if {@code (max == min)} * @throws IllegalArgumentException if either {@code min} or {@code max} is either NaN or infinite */ public static void setScale(double min, double max) { validate(min, "min"); validate(max, "max"); double size = max - min; if (size == 0.0) throw new IllegalArgumentException("the min and max are the same"); synchronized (MOUSE_LOCK) { xmin = min - BORDER * size; xmax = max + BORDER * size; ymin = min - BORDER * size; ymax = max + BORDER * size; } } // 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; } /** * Clears the screen using the default background color (white). */ public static void clear() { clear(DEFAULT_BACKGROUND_COLOR); } /** * Clears the screen using the specified background color. * To make the background transparent, use {@code StdDraw.TRANSPARENT}. * * @param color the color to make the background * @throws IllegalArgumentException if {@code color} is {@code null} */ public static void clear(Color color) { validateNotNull(color, "color"); backgroundColor = color; offscreen.setBackground(backgroundColor); offscreen.clearRect(0, 0, width, height); onscreen.setBackground(backgroundColor); onscreen.clearRect(0, 0, 2*width, 2*height); draw(); } /** * Returns the current pen radius. * * @return the current value of the pen radius */ public static double getPenRadius() { return penRadius; } /** * Sets the pen size to the default size (0.002). * The pen is circular, so that lines have rounded ends, and when you set the * pen radius and draw a point, you get a circle of the specified radius. * The pen radius is not affected by coordinate scaling. */ public static void setPenRadius() { setPenRadius(DEFAULT_PEN_RADIUS); } /** * Sets the radius of the pen to the specified size. * The pen is circular, so that lines have rounded ends, and when you set the * pen radius and draw a point, you get a circle of the specified radius. * The pen radius is not affected by coordinate scaling. * * @param radius the radius of the pen * @throws IllegalArgumentException if {@code radius} is negative, NaN, or infinite */ public static void setPenRadius(double radius) { validate(radius, "pen radius"); validateNonnegative(radius, "pen radius"); penRadius = radius; float scaledPenRadius = (float) (radius * DEFAULT_SIZE); BasicStroke stroke = new BasicStroke(scaledPenRadius, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND); // BasicStroke stroke = new BasicStroke(scaledPenRadius); offscreen.setStroke(stroke); } /** * Returns the current pen color. * * @return the current pen color */ public static Color getPenColor() { return penColor; } /** * Returns the current background color. * * @return the current background color */ public static Color getBackgroundColor() { return backgroundColor; } /** * Sets the pen color to the default color (black). */ public static void setPenColor() { setPenColor(DEFAULT_PEN_COLOR); } /** * Sets the pen color to the specified color. *

* There are a number predefined pen colors, such as * {@code StdDraw.BLACK}, {@code StdDraw.WHITE}, {@code StdDraw.RED}, * {@code StdDraw.GREEN}, and {@code StdDraw.BLUE}. * * @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.getImageLoadStatus() != MediaTracker.COMPLETE) { try { URI uri = new URI(filename); if (uri.isAbsolute()) { URL url = uri.toURL(); icon = new ImageIcon(url); } } catch (MalformedURLException | URISyntaxException e) { /* not a url */ } } // in case file is inside a .jar (classpath relative to StdDraw) if (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.getImageLoadStatus() != MediaTracker.COMPLETE) { URL url = StdDraw.class.getResource("/" + filename); if (url == null) throw new IllegalArgumentException("could not read image: '" + filename + "'"); 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 typically JPEG, PNG, GIF, TIFF, and BMP. * 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 typically JPEG, PNG, GIF, TIFF, and BMP. * * @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 typically JPEG, PNG, GIF, TIFF, and BMP. * * @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 typically JPEG, PNG, GIF, TIFF, and BMP. * * @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 * @throws IllegalArgumentException if {@code t} is negative * @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 * @throws IllegalArgumentException if {@code t} is negative */ 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); // make frame visible upon first call to show() if (frame.isVisible() != isJFrameVisible) { frame.setVisible(isJFrameVisible); } 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 a file in a supported file format * (typically JPEG, PNG, GIF, TIFF, and BMP). * The filetype extension must be {@code .jpg}, {@code .png}, {@code .gif}, * {@code .bmp}, or {@code .tif}. * * @param filename the name of the file * @throws IllegalArgumentException if {@code filename} is {@code null} * @throws IllegalArgumentException if {@code filename} is the empty string * @throws IllegalArgumentException if {@code filename} has invalid filetype extension * @throws IllegalArgumentException if cannot write the file {@code filename} */ public static void save(String filename) { validateNotNull(filename, "filename"); if (filename.length() == 0) { throw new IllegalArgumentException("argument to save() is the empty string"); } File file = new File(filename); String suffix = filename.substring(filename.lastIndexOf('.') + 1); if (!filename.contains(".") || suffix.length() == 0) { throw new IllegalArgumentException("The filename '" + filename + "' has no filetype extension, such as .jpg or .png"); } try { // if the file format supports transparency (such as PNG or GIF) if (ImageIO.write(onscreenImage, suffix, file)) return; // if the file format does not support transparency (such as JPEG or BMP) BufferedImage saveImage = new BufferedImage(2*width, 2*height, BufferedImage.TYPE_INT_RGB); saveImage.createGraphics().drawImage(onscreenImage, 0, 0, Color.WHITE, null); if (ImageIO.write(saveImage, suffix, file)) return; // failed to save the file; probably wrong format throw new IllegalArgumentException("The filetype '" + suffix + "' is not supported"); } catch (IOException e) { throw new IllegalArgumentException("could not write file '" + filename + "'", e); } } /** * This method cannot be called directly. */ @Override public void actionPerformed(ActionEvent event) { FileDialog chooser = new FileDialog(StdDraw.frame, "Use a .png or .jpg extension", FileDialog.SAVE); chooser.setVisible(true); String selectedDirectory = chooser.getDirectory(); String selectedFilename = chooser.getFile(); if (selectedDirectory != null && selectedFilename != null) { try { StdDraw.save(selectedDirectory + selectedFilename); } catch (IllegalArgumentException e) { System.err.println(e.getMessage()); } } } /*************************************************************************** * 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 (MOUSE_LOCK) { 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 (MOUSE_LOCK) { return isMousePressed; } } /** * Returns the x-coordinate of the mouse. * * @return the x-coordinate of the mouse */ public static double mouseX() { synchronized (MOUSE_LOCK) { return mouseX; } } /** * Returns the y-coordinate of the mouse. * * @return y-coordinate of the mouse */ public static double mouseY() { synchronized (MOUSE_LOCK) { return mouseY; } } /** * This method cannot be called directly. */ @Override public void mouseClicked(MouseEvent event) { // this body is intentionally left empty } /** * This method cannot be called directly. */ @Override public void mouseEntered(MouseEvent event) { // this body is intentionally left empty } /** * This method cannot be called directly. */ @Override public void mouseExited(MouseEvent event) { // this body is intentionally left empty } /** * This method cannot be called directly. */ @Override public void mousePressed(MouseEvent event) { synchronized (MOUSE_LOCK) { mouseX = StdDraw.userX(event.getX()); mouseY = StdDraw.userY(event.getY()); isMousePressed = true; } } /** * This method cannot be called directly. */ @Override public void mouseReleased(MouseEvent event) { synchronized (MOUSE_LOCK) { isMousePressed = false; } } /** * This method cannot be called directly. */ @Override public void mouseDragged(MouseEvent event) { synchronized (MOUSE_LOCK) { mouseX = StdDraw.userX(event.getX()); mouseY = StdDraw.userY(event.getY()); } } /** * This method cannot be called directly. */ @Override public void mouseMoved(MouseEvent event) { synchronized (MOUSE_LOCK) { mouseX = StdDraw.userX(event.getX()); mouseY = StdDraw.userY(event.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 (KEY_LOCK) { 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 (KEY_LOCK) { 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 (KEY_LOCK) { return keysDown.contains(keycode); } } /** * This method cannot be called directly. */ @Override public void keyTyped(KeyEvent event) { synchronized (KEY_LOCK) { keysTyped.addFirst(event.getKeyChar()); } } /** * This method cannot be called directly. */ @Override public void keyPressed(KeyEvent event) { synchronized (KEY_LOCK) { keysDown.add(event.getKeyCode()); } } /** * This method cannot be called directly. */ @Override public void keyReleased(KeyEvent event) { synchronized (KEY_LOCK) { keysDown.remove(event.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; } /** * Returns 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"); } }