package org.teachingextensions.logo.utils.MazeUtils; import java.awt.BasicStroke; import java.awt.Color; import java.awt.FileDialog; import java.awt.Font; import java.awt.FontMetrics; 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.KeyEvent; import java.awt.event.KeyListener; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; 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.URL; import java.util.LinkedList; import java.util.NoSuchElementException; import java.util.TreeSet; 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; public final class StdDraw implements ActionListener, KeyListener { private static final int DEFAULT_SIZE = 350; private static int width = DEFAULT_SIZE; private static int height = DEFAULT_SIZE; private static final double DEFAULT_PEN_RADIUS = 0.0175; private static double currentPenRadius; private static boolean defer = false; 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; private static final Font DEFAULT_FONT = new Font("SansSerif", Font.PLAIN, 16); private static Font font; private static BufferedImage offscreenImage, onscreenImage; private static Graphics2D offscreen, onscreen; private static StdDraw stdDraw = new StdDraw(); public static JFrame frame; private static Object keyLock = new Object(); private static LinkedList<Character> keysTyped = new LinkedList<Character>(); private static TreeSet<Integer> keysDown = new TreeSet<Integer>(); private static long nextDrawInMS = -1; private StdDraw() { } static { init(); } public static void main(String[] args) { StdDraw.square(.2, .8, .1); StdDraw.filledSquare(.8, .8, .2); StdDraw.circle(.8, .2, .2); StdDraw.setPenColor(StdDrawColors.BOOK_RED); StdDraw.setPenRadius(.02); StdDraw.arc(.8, .2, .1, 200, 45); StdDraw.setPenRadius(); StdDraw.setPenColor(StdDrawColors.BOOK_BLUE); double[] x = {.1, .2, .3, .2}; double[] y = {.2, .3, .2, .1}; StdDraw.filledPolygon(x, y); StdDraw.setPenColor(StdDrawColors.BLACK); StdDraw.text(0.2, 0.5, "black text"); StdDraw.setPenColor(StdDrawColors.WHITE); StdDraw.text(0.8, 0.8, "white text"); } public static void setCanvasSize() { setCanvasSize(DEFAULT_SIZE, DEFAULT_SIZE); } public static void setCanvasSize(int canvasWidth, int canvasHeight) { if (width <= 0 || height <= 0) throw new IllegalArgumentException("width and height must be positive"); width = canvasWidth; height = canvasHeight; init(); } private static void init() { if (frame != null) frame.setVisible(false); frame = new JFrame(); offscreenImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); onscreenImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); offscreen = offscreenImage.createGraphics(); onscreen = onscreenImage.createGraphics(); setXscale(); setYscale(); offscreen.setColor(StdDrawColors.DEFAULT_CLEAR_COLOR); offscreen.fillRect(0, 0, width, height); setPenColor(); setPenRadius(); setFont(); clearScreenToWhite(); RenderingHints hints = new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); hints.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); offscreen.addRenderingHints(hints); ImageIcon icon = new ImageIcon(onscreenImage); JLabel draw = new JLabel(icon); frame.setContentPane(draw); frame.addKeyListener(stdDraw); frame.setResizable(false); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // close all windows // frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); // closes only current window frame.setTitle("Amazing Maze!"); frame.setJMenuBar(createMenuBar()); frame.pack(); frame.requestFocusInWindow(); frame.setVisible(true); // TODO: This is not working, so mouse events won't work yet... //draw.addMouseListener(stdDraw); //draw.addMouseMotionListener(stdDraw); MouseListener l = null; draw.addMouseListener(l); MouseMotionListener m = null; draw.addMouseMotionListener(m); } private static JMenuBar createMenuBar() { JMenuBar menuBar = new JMenuBar(); JMenu menu = new JMenu("File"); menuBar.add(menu); JMenuItem menuItem1 = new JMenuItem(" Save... "); menuItem1.addActionListener(stdDraw); menuItem1.setAccelerator( KeyStroke.getKeyStroke(KeyEvent.VK_S, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); menu.add(menuItem1); return menuBar; } public static void setXscale() { setXscale(DEFAULT_XMIN, DEFAULT_XMAX); } public static void setYscale() { setYscale(DEFAULT_YMIN, DEFAULT_YMAX); } public static void setScale() { setXscale(); setYscale(); } public static void setXscale(double min, double max) { double size = max - min; if (size == 0.0) throw new IllegalArgumentException("the min and max are the same"); synchronized (StdDrawMouseEvents.mouseLock) { xmin = min - BORDER * size; xmax = max + BORDER * size; } } public static void setYscale(double min, double max) { double size = max - min; if (size == 0.0) throw new IllegalArgumentException("the min and max are the same"); synchronized (StdDrawMouseEvents.mouseLock) { ymin = min - BORDER * size; ymax = max + BORDER * size; } } public static void setScale(double min, double max) { double size = max - min; if (size == 0.0) throw new IllegalArgumentException("the min and max are the same"); synchronized (StdDrawMouseEvents.mouseLock) { xmin = min - BORDER * size; xmax = max + BORDER * size; ymin = min - BORDER * size; ymax = max + BORDER * size; } } 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); } static double userX(double x) { return xmin + x * (xmax - xmin) / width; } static double userY(double y) { return ymax - y * (ymax - ymin) / height; } public static void clearScreenToWhite() { clearScreenToColor(StdDrawColors.DEFAULT_CLEAR_COLOR); } public static void clearScreenToColor(Color color) { offscreen.setColor(color); offscreen.fillRect(0, 0, width, height); offscreen.setColor(StdDrawColors.currentPenColor); draw(); } public static double getPenRadius() { return currentPenRadius; } public static void setPenRadius() { setPenRadius(DEFAULT_PEN_RADIUS); } public static void setPenRadius(double radius) { if (!(radius >= 0)) throw new IllegalArgumentException("pen radius must be nonnegative"); currentPenRadius = radius; float scaledPenRadius = (float) (radius * DEFAULT_SIZE); BasicStroke stroke = new BasicStroke(scaledPenRadius, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND); offscreen.setStroke(stroke); } public static Color getPenColor() { return StdDrawColors.currentPenColor; } public static void setPenColor() { setPenColor(StdDrawColors.DEFAULT_PEN_COLOR); } public static void setPenColor(Color color) { if (color == null) throw new NullPointerException(); StdDrawColors.currentPenColor = color; offscreen.setColor(StdDrawColors.currentPenColor); } public static void setPenColor(int red, int green, int blue) { if (red < 0 || red >= 256) throw new IllegalArgumentException("amount of red must be between 0 and 255"); if (green < 0 || green >= 256) throw new IllegalArgumentException("amount of green must be between 0 and 255"); if (blue < 0 || blue >= 256) throw new IllegalArgumentException("amount of blue must be between 0 and 255"); setPenColor(new Color(red, green, blue)); } public static Font getFont() { return font; } public static void setFont() { setFont(DEFAULT_FONT); } public static void setFont(Font font) { if (font == null) throw new NullPointerException(); StdDraw.font = font; } /** * Draws a line segment between (<em>x</em><sub>0</sub>, <em>y</em><sub>0</sub>) and * (<em>x</em><sub>1</sub>, <em>y</em><sub>1</sub>). * * @param x0 the <em>x</em>-coordinate of one endpoint * @param y0 the <em>y</em>-coordinate of one endpoint * @param x1 the <em>x</em>-coordinate of the other endpoint * @param y1 the <em>y</em>-coordinate of the other endpoint */ public static void line(double x0, double y0, double x1, double y1) { offscreen.draw(new Line2D.Double(scaleX(x0), scaleY(y0), scaleX(x1), scaleY(y1))); draw(); } /** * Draws one pixel at (<em>x</em>, <em>y</em>). * 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 <em>x</em>-coordinate of the pixel * @param y the <em>y</em>-coordinate of the pixel */ private static void pixel(double x, double y) { offscreen.fillRect((int) Math.round(scaleX(x)), (int) Math.round(scaleY(y)), 1, 1); } /** * Draws a point centered at (<em>x</em>, <em>y</em>). * 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 <em>x</em>-coordinate of the point * @param y the <em>y</em>-coordinate of the point */ public static void point(double x, double y) { double xs = scaleX(x); double ys = scaleY(y); double r = currentPenRadius; float scaledPenRadius = (float) (r * DEFAULT_SIZE); 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 (<em>x</em>, <em>y</em>). * * @param x the <em>x</em>-coordinate of the center of the circle * @param y the <em>y</em>-coordinate of the center of the circle * @param radius the radius of the circle * @throws IllegalArgumentException if {@code radius} is negative */ public static void circle(double x, double y, double radius) { if (!(radius >= 0)) throw new IllegalArgumentException("radius must be nonnegative"); 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 (<em>x</em>, <em>y</em>). * * @param x the <em>x</em>-coordinate of the center of the circle * @param y the <em>y</em>-coordinate of the center of the circle * @param radius the radius of the circle * @throws IllegalArgumentException if {@code radius} is negative */ public static void filledCircle(double x, double y, double radius) { if (!(radius >= 0)) throw new IllegalArgumentException("radius must be nonnegative"); 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 (<em>x</em>, <em>y</em>). * * @param x the <em>x</em>-coordinate of the center of the ellipse * @param y the <em>y</em>-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 */ public static void ellipse(double x, double y, double semiMajorAxis, double semiMinorAxis) { if (!(semiMajorAxis >= 0)) throw new IllegalArgumentException("ellipse semimajor axis must be nonnegative"); if (!(semiMinorAxis >= 0)) throw new IllegalArgumentException("ellipse semiminor axis must be nonnegative"); 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 an ellipse with the specified semimajor and semiminor axes, * centered at (<em>x</em>, <em>y</em>). * * @param x the <em>x</em>-coordinate of the center of the ellipse * @param y the <em>y</em>-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 */ public static void filledEllipse(double x, double y, double semiMajorAxis, double semiMinorAxis) { if (!(semiMajorAxis >= 0)) throw new IllegalArgumentException("ellipse semimajor axis must be nonnegative"); if (!(semiMinorAxis >= 0)) throw new IllegalArgumentException("ellipse semiminor axis must be nonnegative"); 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 (<em>x</em>, <em>y</em>), from angle1 to angle2 (in degrees). * * @param x the <em>x</em>-coordinate of the center of the circle * @param y the <em>y</em>-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 */ public static void arc(double x, double y, double radius, double angle1, double angle2) { if (radius < 0) throw new IllegalArgumentException("arc radius must be nonnegative"); 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 side length 2r, centered at (<em>x</em>, <em>y</em>). * * @param x the <em>x</em>-coordinate of the center of the square * @param y the <em>y</em>-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 */ public static void square(double x, double y, double halfLength) { if (!(halfLength >= 0)) throw new IllegalArgumentException("half length must be nonnegative"); 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 (<em>x</em>, <em>y</em>). * * @param x the <em>x</em>-coordinate of the center of the square * @param y the <em>y</em>-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 */ public static void filledSquare(double x, double y, double halfLength) { if (!(halfLength >= 0)) throw new IllegalArgumentException("half length must be nonnegative"); 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 (<em>x</em>, <em>y</em>). * * @param x the <em>x</em>-coordinate of the center of the rectangle * @param y the <em>y</em>-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 */ public static void rectangle(double x, double y, double halfWidth, double halfHeight) { if (!(halfWidth >= 0)) throw new IllegalArgumentException("half width must be nonnegative"); if (!(halfHeight >= 0)) throw new IllegalArgumentException("half height must be nonnegative"); 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 (<em>x</em>, <em>y</em>). * * @param x the <em>x</em>-coordinate of the center of the rectangle * @param y the <em>y</em>-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 */ public static void filledRectangle(double x, double y, double halfWidth, double halfHeight) { if (!(halfWidth >= 0)) throw new IllegalArgumentException("half width must be nonnegative"); if (!(halfHeight >= 0)) throw new IllegalArgumentException("half height must be nonnegative"); 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 * (<em>x</em><sub>0</sub>, <em>y</em><sub>0</sub>), * (<em>x</em><sub>1</sub>, <em>y</em><sub>1</sub>), ..., * (<em>x</em><sub><em>n</em>−1</sub>, <em>y</em><sub><em>n</em>−1</sub>). * * @param x an array of all the <em>x</em>-coordinates of the polygon * @param y an array of all the <em>y</em>-coordinates of the polygon * @throws IllegalArgumentException unless {@code x[]} and {@code y[]} * are of the same length */ public static void polygon(double[] x, double[] y) { if (x == null) throw new NullPointerException(); if (y == null) throw new NullPointerException(); int n1 = x.length; int n2 = y.length; if (n1 != n2) throw new IllegalArgumentException("arrays must be of the same length"); int n = n1; 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 polygon with the vertices * (<em>x</em><sub>0</sub>, <em>y</em><sub>0</sub>), * (<em>x</em><sub>1</sub>, <em>y</em><sub>1</sub>), ..., * (<em>x</em><sub><em>n</em>−1</sub>, <em>y</em><sub><em>n</em>−1</sub>). * * @param x an array of all the <em>x</em>-coordinates of the polygon * @param y an array of all the <em>y</em>-coordinates of the polygon * @throws IllegalArgumentException unless {@code x[]} and {@code y[]} * are of the same length */ public static void filledPolygon(double[] x, double[] y) { if (x == null) throw new NullPointerException(); if (y == null) throw new NullPointerException(); int n1 = x.length; int n2 = y.length; if (n1 != n2) throw new IllegalArgumentException("arrays must be of the same length"); int n = n1; 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(); } private static Image getImageFromFile(String filename) { if (filename == null) throw new NullPointerException(); ImageIcon icon = new ImageIcon(filename); if ((icon == null) || (icon.getImageLoadStatus() != MediaTracker.COMPLETE)) { try { URL url = new URL(filename); icon = new ImageIcon(url); } catch (Exception e) { } } if ((icon == null) || (icon.getImageLoadStatus() != MediaTracker.COMPLETE)) { // in case file is inside a .jar (classpath relative to StdDraw) URL url = StdDraw.class.getResource(filename); if (url != null) icon = new ImageIcon(url); } if ((icon == null) || (icon.getImageLoadStatus() != MediaTracker.COMPLETE)) { // in case file is inside a .jar (classpath relative to root of jar) URL url = StdDraw.class.getResource("/" + filename); if (url == null) throw new IllegalArgumentException("image " + filename + " not found"); icon = new ImageIcon(url); } return icon.getImage(); } /** * Draws the specified image centered at (<em>x</em>, <em>y</em>). * 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 <em>x</em>-coordinate of the image * @param y the center <em>y</em>-coordinate of the image * @param filename the name of the image/picture, e.g., "ball.gif" * @throws IllegalArgumentException if the image filename is invalid */ public static void picture(double x, double y, String filename) { Image image = getImageFromFile(filename); double xs = scaleX(x); double ys = scaleY(y); int ws = image.getWidth(null); int hs = image.getHeight(null); if (ws < 0 || hs < 0) throw new 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 (<em>x</em>, <em>y</em>), * rotated given number of degrees. * The supported image formats are JPEG, PNG, and GIF. * * @param x the center <em>x</em>-coordinate of the image * @param y the center <em>y</em>-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 */ public static void picture(double x, double y, String filename, double degrees) { Image image = getImageFromFile(filename); double xs = scaleX(x); double ys = scaleY(y); int ws = image.getWidth(null); int hs = image.getHeight(null); if (ws < 0 || hs < 0) throw new 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 (<em>x</em>, <em>y</em>), * rescaled to the specified bounding box. * The supported image formats are JPEG, PNG, and GIF. * * @param x the center <em>x</em>-coordinate of the image * @param y the center <em>y</em>-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 pixels * @param scaledHeight the height of the scaled image in pixels * @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) { Image image = getImageFromFile(filename); if (scaledWidth < 0) throw new IllegalArgumentException("width is negative: " + scaledWidth); if (scaledHeight < 0) throw new IllegalArgumentException("height is negative: " + scaledHeight); 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 (<em>x</em>, <em>y</em>), 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 <em>x</em>-coordinate of the image * @param y the center <em>y</em>-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 pixels * @param scaledHeight the height of the scaled image in pixels * @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) { if (scaledWidth < 0) throw new IllegalArgumentException("width is negative: " + scaledWidth); if (scaledHeight < 0) throw new IllegalArgumentException("height is negative: " + scaledHeight); Image image = getImageFromFile(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(); } /** * Write the given text string in the current font, centered at (<em>x</em>, <em>y</em>). * * @param x the center <em>x</em>-coordinate of the text * @param y the center <em>y</em>-coordinate of the text * @param text the text to write */ public static void text(double x, double y, String text) { if (text == null) throw new NullPointerException(); 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(); } /** * Write the given text string in the current font, centered at (<em>x</em>, <em>y</em>) and * rotated by the specified number of degrees. * @param x the center <em>x</em>-coordinate of the text * @param y the center <em>y</em>-coordinate of the text * @param text the text to write * @param degrees is the number of degrees to rotate counterclockwise */ public static void text(double x, double y, String text, double degrees) { if (text == null) throw new NullPointerException(); 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); } /** * Write the given text string in the current font, left-aligned at (<em>x</em>, <em>y</em>). * @param x the <em>x</em>-coordinate of the text * @param y the <em>y</em>-coordinate of the text * @param text the text */ public static void textLeft(double x, double y, String text) { if (text == null) throw new NullPointerException(); 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(); } /** * Write the given text string in the current font, right-aligned at (<em>x</em>, <em>y</em>). * * @param x the <em>x</em>-coordinate of the text * @param y the <em>y</em>-coordinate of the text * @param text the text to write */ public static void textRight(double x, double y, String text) { if (text == null) throw new NullPointerException(); 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(); } public static void show(int t) { long millis = System.currentTimeMillis(); if (millis < nextDrawInMS) { try { Thread.sleep(nextDrawInMS - millis); } catch (InterruptedException e) { System.out.println("Error sleeping"); } millis = nextDrawInMS; } defer = false; draw(); defer = true; nextDrawInMS = millis + t; } /** * Display on-screen and turn off animation mode: * 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 show() { defer = false; nextDrawInMS = -1; draw(); } private static void draw() { if (defer) return; onscreen.drawImage(offscreenImage, 0, 0, null); frame.repaint(); } /** * Saves the drawing to using the specified filename. * The supported image formats are JPEG and PNG; * the filename suffix must be <tt>.jpg</tt> or <tt>.png</tt>. * * @param filename the name of the file with one of the required suffixes */ public static void save(String filename) { if (filename == null) throw new NullPointerException(); File file = new File(filename); String suffix = filename.substring(filename.lastIndexOf('.') + 1); if (suffix.toLowerCase().equals("png")) { try { ImageIO.write(onscreenImage, suffix, file); } catch (IOException e) { e.printStackTrace(); } } else if (suffix.toLowerCase().equals("jpg")) { WritableRaster raster = onscreenImage.getRaster(); WritableRaster newRaster; newRaster = raster.createWritableChild(0, 0, width, height, 0, 0, new int[]{0, 1, 2}); DirectColorModel cm = (DirectColorModel) onscreenImage.getColorModel(); DirectColorModel newCM = new DirectColorModel(cm.getPixelSize(), cm.getRedMask(), cm.getGreenMask(), cm.getBlueMask()); BufferedImage rgbBuffer = new BufferedImage(newCM, newRaster, false, null); try { ImageIO.write(rgbBuffer, suffix, file); } catch (IOException e) { e.printStackTrace(); } } else { System.out.println("Invalid image file type: " + suffix); } } @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()); } } public static boolean isNextKeyTyped() { 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 #isNextKeyTyped()} 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.removeLast(); } } /** * Returns true if the given key is being pressed. * <p> * 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 <tt>true</tt> if {@code keycode} is currently being pressed; * <tt>false</tt> otherwise */ public static boolean isKeyPressed(int keycode) { synchronized (keyLock) { return keysDown.contains(keycode); } } @Override public void keyTyped(KeyEvent e) { synchronized (keyLock) { keysTyped.addFirst(e.getKeyChar()); } } @Override public void keyPressed(KeyEvent e) { synchronized (keyLock) { keysDown.add(e.getKeyCode()); } } @Override public void keyReleased(KeyEvent e) { synchronized (keyLock) { keysDown.remove(e.getKeyCode()); } } }