diff --git a/src/TheArtist.java b/src/TheArtist.java new file mode 100644 index 0000000..69c8fd2 --- /dev/null +++ b/src/TheArtist.java @@ -0,0 +1,69 @@ +/** +* +* D:\Git\Golden-Ratio-Image\src\TheArtist.java +* +* @author Avinal Kumar +* @since April 26, 2020 +*/ + +package src; + +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import javax.imageio.ImageIO; +import src.depends.StdDraw; + +public class TheArtist { + private double px, py, r, degree; + private double spacing = 15; + private int width, height; + private double goldenRatio = (Math.sqrt(5.0) + 1) / 2; + + int iter = 0; + boolean smallChaos = false; + BufferedImage img = null; + + TheArtist(File f) { + try { + img = ImageIO.read(f); + width = img.getWidth(); + height = img.getHeight(); + } catch (IOException e) { + System.out.println(e); + } + } + + private void calculatePointPosition(double x, double y, double grade) { + px = x + Math.cos(Math.toRadians(grade)) * (r / 2); + py = y + Math.sin(Math.toRadians(grade)) * (r / 2); + } + + public void goldenDraw() { + StdDraw.clear(StdDraw.WHITE); + StdDraw.setXscale(-10, width + 10); + StdDraw.setYscale(-1 * (height + 10), 10); + StdDraw.enableDoubleBuffering(); + + for (int i = 5000; i > 0; i--) { + degree = (iter * goldenRatio) * 360; + r = Math.sqrt(iter++) * spacing; + calculatePointPosition(width / 2, height / 2, (degree % 360)); + if (px - 10 <= 0 || px + 10 >= width || py - 10 <= 0 || py + 10 >= height) { + break; + } + int p = img.getRGB((int) px, (int) py); + // int a = (p >> 24) & 0xff; + int r = (p >> 16) & 0xff; + int g = (p >> 8) & 0xff; + int b = p & 0xff; + int avg = (r + g + b) / 3; + float luminance = (r * 0.2126f + g * 0.7152f + b * 0.0722f) / 255; + + StdDraw.setPenColor(avg, avg, avg); + StdDraw.setPenRadius((1 - luminance) / 70); + StdDraw.point(px, -1 * py); + StdDraw.show(); + } + } +} \ No newline at end of file diff --git a/src/TheCanvas.java b/src/TheCanvas.java new file mode 100644 index 0000000..1af4cb1 --- /dev/null +++ b/src/TheCanvas.java @@ -0,0 +1,42 @@ +/** +* +* D:\Git\Golden-Ratio-Image\src\TheCanvas.java +* +* @author Avinal Kumar +* @since April 26, 2020 +*/ + +package src; + +import java.io.*; +import javax.swing.JFileChooser; +import javax.swing.filechooser.FileNameExtensionFilter; + +/** + * TheCanvas + * + * + */ +public class TheCanvas { + + public static void main(String[] args) { + + + File f = openFile(); + TheArtist art = new TheArtist(f); + art.goldenDraw(); + + } + + public static File openFile() { + JFileChooser open = new JFileChooser(); + FileNameExtensionFilter filter = new FileNameExtensionFilter("Image Files", "jpg", "jpeg"); + open.setFileFilter(filter); + int status = open.showOpenDialog(null); + if (status == JFileChooser.APPROVE_OPTION) { + return open.getSelectedFile(); + } else { + return new File("tree.txt"); + } + } +} \ No newline at end of file diff --git a/src/depends/StdDraw.java b/src/depends/StdDraw.java new file mode 100644 index 0000000..6be2c11 --- /dev/null +++ b/src/depends/StdDraw.java @@ -0,0 +1,2098 @@ +package src.depends; + +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 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 { + init(); + } + + /** + * Sets the canvas (drawing area) to be 512-by-512 pixels. This 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. + */ + public static void setCanvasSize() { + setCanvasSize(DEFAULT_SIZE, DEFAULT_SIZE); + } + + /** + * Sets the canvas (drawing area) to be width-by-height + * pixels. This 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. + * + * @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; + init(); + } + + // init + private static void init() { + if (frame != null) + frame.setVisible(false); + frame = new JFrame(); + 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 + + setXscale(); + setYscale(); + offscreen.setColor(DEFAULT_CLEAR_COLOR); + offscreen.fillRect(0, 0, width, height); + setPenColor(); + setPenRadius(); + setFont(); + clear(); + + // add antialiasing + RenderingHints hints = new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + hints.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); + offscreen.addRenderingHints(hints); + + // frame stuff + RetinaImageIcon icon = new RetinaImageIcon(onscreenImage); + JLabel draw = new JLabel(icon); + + draw.addMouseListener(std); + draw.addMouseMotionListener(std); + + frame.setContentPane(draw); + 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("Standard Draw"); + frame.setJMenuBar(createMenuBar()); + frame.pack(); + frame.requestFocusInWindow(); + frame.setVisible(true); + } + + // create the menu bar (changed to private) + private static JMenuBar createMenuBar() { + JMenuBar menuBar = new JMenuBar(); + JMenu menu = new JMenu("File"); + menuBar.add(menu); + JMenuItem menuItem1 = new JMenuItem(" Save... "); + menuItem1.addActionListener(std); + // Java 10+: replace getMenuShortcutKeyMask() with getMenuShortcutKeyMaskEx() + menuItem1.setAccelerator( + KeyStroke.getKeyStroke(KeyEvent.VK_S, Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx())); + menu.add(menuItem1); + return menuBar; + } + + /*************************************************************************** + * User and screen coordinate systems. + ***************************************************************************/ + + // 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"); + } + + /** + * Sets the x-scale to be the default (between 0.0 and 1.0). + */ + public static void setXscale() { + setXscale(DEFAULT_XMIN, DEFAULT_XMAX); + } + + /** + * Sets the y-scale to be the default (between 0.0 and 1.0). + */ + public static void setYscale() { + setYscale(DEFAULT_YMIN, DEFAULT_YMAX); + } + + /** + * Sets the x-scale and y-scale to be the default (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 (mouseLock) { + 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 (mouseLock) { + 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 (mouseLock) { + 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 to the default color (white). + */ + public static void clear() { + clear(DEFAULT_CLEAR_COLOR); + } + + /** + * Clears the screen to the specified color. + * + * @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"); + offscreen.setColor(color); + offscreen.fillRect(0, 0, width, height); + offscreen.setColor(penColor); + 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; + } + + /** + * 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. + *

+ * 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"); + } + +} \ No newline at end of file