From c0033f60be4fdaa3201952c226ef5042c7874646 Mon Sep 17 00:00:00 2001 From: Daniel Weschke Date: Sat, 15 Mar 2014 10:34:20 +0100 Subject: [PATCH] Add some gemometry point classes in math\geometry package and standard draw libaray --- src/math/geometry/Point.java | 194 +++++ src/math/geometry/Point2D.java | 84 ++ src/math/geometry/PointPolar.java | 11 + src/math/geometry/PointPolar2D.java | 57 ++ src/stdlib/Color.java | 105 +++ src/stdlib/StdDraw.java | 1147 +++++++++++++++++++++++++++ 6 files changed, 1598 insertions(+) create mode 100644 src/math/geometry/Point.java create mode 100644 src/math/geometry/Point2D.java create mode 100644 src/math/geometry/PointPolar.java create mode 100644 src/math/geometry/PointPolar2D.java create mode 100644 src/stdlib/Color.java create mode 100644 src/stdlib/StdDraw.java diff --git a/src/math/geometry/Point.java b/src/math/geometry/Point.java new file mode 100644 index 0000000..6dc2094 --- /dev/null +++ b/src/math/geometry/Point.java @@ -0,0 +1,194 @@ +package math.geometry; + +import java.text.DecimalFormat; + +import math.matrix.Vector; + +public class Point { + protected double[] point; + protected int n; + + /** + * empty point. + */ + public Point(int n){ + this.n = n; + point = new double[n]; + } + + /** + * new point. + * @param x x-coordinate + * @param y y-coordinate + * @param z z-coordinate + */ + public Point(double x, double y, double z){ + n = 3; + point = new double[]{x, y, z}; + } + + /** + * new point. + * @param p n-dimensional point + */ + public Point(double... p){ + n = p.length; + point = p; + } + + /** + * Copy constructor. + * @param a the point to copy + */ + public Point(Point a){ + this(a.get()); + } + + public Point create(int n){ + if(this instanceof Point2D) return new Point2D(); + else return new Point(n); + } + + /** + * Create random point. + */ + public static Point random(int n){ + double[] point = new double[n]; + int i; + for(i=0; iSection 1.5 of + * Introduction to Programming in Java: An Interdisciplinary Approach by Robert Sedgewick and Kevin Wayne. + */ +public class StdDraw implements ActionListener, MouseListener, MouseMotionListener, KeyListener { + + // default colors + private static final Color DEFAULT_PEN_COLOR = Color.LIGHT_BLUE; // GRAY, BLUE, BLACK + private static final Color DEFAULT_GRAPH_COLOR = Color.LIGHT_BLUE; // GRAY, BLUE, BLACK + private static final Color DEFAULT_CLEAR_COLOR = Color.JET; // LIGHT_GRAY, WHITE + + // current pen color + private static Color penColor = DEFAULT_PEN_COLOR; + private static Color graphColor = DEFAULT_GRAPH_COLOR; + + // default canvas size is DEFAULT_SIZE-by-DEFAULT_SIZE + public static final int DEFAULT_SIZE = 512; // dw: changed from private to public + private static int width = DEFAULT_SIZE; + private static int height = DEFAULT_SIZE; + + // default pen radius + private static final double DEFAULT_PEN_RADIUS = 0.002; + + // current pen radius + private static double penRadius; + + // show we draw immediately or wait until next show? + private static boolean defer = false; + + // boundary of drawing canvas, 5% border + private static final double BORDER = 0.05; + private static final double DEFAULT_XMIN = 0.0; + private static final double DEFAULT_XMAX = 1.0; + private static final double DEFAULT_YMIN = 0.0; + private static final double DEFAULT_YMAX = 1.0; + private static double xmin, ymin, xmax, ymax; + + // for synchronization + private static Object mouseLock = new Object(); + private static Object keyLock = new Object(); + + // default font + private static final Font DEFAULT_FONT = new Font("SansSerif", Font.PLAIN, 16); + + // current font + private static Font font; + + // double buffered graphics + private static BufferedImage offscreenImage, onscreenImage; + private static Graphics2D offscreen, onscreen; + + // singleton for callbacks: avoids generation of extra .class files +// private static StdDraw std = new StdDraw(); + + // the frame for drawing to the screen + public static JFrame frame; // dw: changed from private to public + + // mouse state + private static boolean mousePressed = false; + private static double mouseX = 0; + private static double mouseY = 0; + + // queue of typed key characters + private static LinkedList keysTyped = new LinkedList(); + + // set of key codes currently pressed down + private static TreeSet keysDown = new TreeSet(); + + /** + * top offset of the canvas + */ + public static int top = 0; + /** + * left offset of the canvas + */ + public static int left = 0; + + // not instantiable + public StdDraw() { init(); } // dw: changed from private to public + + + // static initializer +// { init(); } + + /** + * Set the window size to the default size 512-by-512 pixels. + */ + public void setCanvasSize() { + setCanvasSize(DEFAULT_SIZE, DEFAULT_SIZE); + } + + /** + * Set the window size to w-by-h pixels. + * + * @param w the width as a number of pixels + * @param h the height as a number of pixels + * @throws a RunTimeException if the width or height is 0 or negative + */ + public void setCanvasSize(int w, int h) { + if (w < 1 || h < 1) throw new RuntimeException("width and height must be positive"); + width = w; + height = h; + init(); + } + + // init + private void init() { + if (frame != null) frame.setVisible(false); + frame = new JFrame(); + offscreenImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + onscreenImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + offscreen = offscreenImage.createGraphics(); + onscreen = onscreenImage.createGraphics(); + setXscale(); + setYscale(); + offscreen.setColor(DEFAULT_CLEAR_COLOR); + offscreen.fillRect(0, 0, width, height); + setPenColor(); + setPenRadius(); + setFont(); + clear(); + + // add antialiasing + RenderingHints hints = new RenderingHints(RenderingHints.KEY_ANTIALIASING, + RenderingHints.VALUE_ANTIALIAS_ON); + hints.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); + offscreen.addRenderingHints(hints); + + // frame stuff + ImageIcon icon = new ImageIcon(onscreenImage); + JLabel draw = new JLabel(icon); + + draw.addMouseListener(this); + draw.addMouseMotionListener(this); + + frame.setContentPane(draw); + frame.addKeyListener(this); // JLabel cannot get keyboard focus + frame.setResizable(false); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // closes all windows + // frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); // closes only current window + frame.setTitle("Standard Draw"); + frame.setJMenuBar(createMenuBar()); + frame.pack(); + frame.requestFocusInWindow(); + frame.setVisible(true); + } + + // create the menu bar (changed to private) + public JMenuBar createMenuBar() { + JMenuBar menuBar = new JMenuBar(); + JMenu menu = new JMenu("File"); + menuBar.add(menu); + JMenuItem menuItem1 = new JMenuItem(" Save... "); + menuItem1.addActionListener(this); + menuItem1.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, + Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); + menu.add(menuItem1); + return menuBar; + } + + + /************************************************************************* + * User and screen coordinate systems + *************************************************************************/ + + /** + * Set the x-scale to be the default (between 0.0 and 1.0). + */ + public void setXscale() { setXscale(DEFAULT_XMIN, DEFAULT_XMAX); } + + /** + * Set the y-scale to be the default (between 0.0 and 1.0). + */ + public void setYscale() { setYscale(DEFAULT_YMIN, DEFAULT_YMAX); } + + /** + * Set the x-scale (a 10% border is added to the values) + * @param min the minimum value of the x-scale + * @param max the maximum value of the x-scale + */ + public void setXscale(double min, double max) { + double size = max - min; + xmin = min - BORDER * size; + xmax = max + BORDER * size; + } + + /** + * Set the y-scale (a 10% border is added to the values). + * @param min the minimum value of the y-scale + * @param max the maximum value of the y-scale + */ + public void setYscale(double min, double max) { + double size = max - min; + ymin = min - BORDER * size; + ymax = max + BORDER * size; + } + + /** + * Set the x-scale and y-scale (a 10% border is added to the values) + * @param min the minimum value of the x- and y-scales + * @param max the maximum value of the x- and y-scales + */ + public void setScale(double min, double max) { + setXscale(min, max); + setYscale(min, max); + } + + // helper functions that scale from user coordinates to screen coordinates and back + private static double scaleX(double x) { return width * (x - xmin) / (xmax - xmin); } + private static double scaleY(double y) { return height * (ymax - y) / (ymax - ymin); } + private static double factorX(double w) { return w * width / Math.abs(xmax - xmin); } + private static double factorY(double h) { return h * height / Math.abs(ymax - ymin); } + private static double userX(double x) { return xmin + x * (xmax - xmin) / width; } + private static double userY(double y) { return ymax - y * (ymax - ymin) / height; } + + + /** + * Clear the screen to the default color (white). + */ + public static void clear() { clear(DEFAULT_CLEAR_COLOR); } + /** + * Clear the screen to the given color. + * @param color the Color to make the background + */ + public static void clear(Color color) { + offscreen.setColor(color); + offscreen.fillRect(0, 0, width, height); + offscreen.setColor(penColor); + draw(); + } + + /** + * Get the current pen radius. + */ + public static double getPenRadius() { return penRadius; } + + /** + * Set the pen size to the default (.002). + */ + public void setPenRadius() { setPenRadius(DEFAULT_PEN_RADIUS); } + /** + * Set the radius of the pen to the given size. + * @param r the radius of the pen + * @throws RuntimeException if r is negative + */ + public void setPenRadius(double r) { + if (r < 0) throw new RuntimeException("pen radius must be positive"); + penRadius = r * DEFAULT_SIZE; + BasicStroke stroke = new BasicStroke((float) penRadius, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND); + // BasicStroke stroke = new BasicStroke((float) penRadius); + offscreen.setStroke(stroke); + } + + /** + * Get the current pen color. + */ + public static Color getPenColor() { return penColor; } + + /** + * Set the pen color to the default color (black). + */ + public void setPenColor() { setPenColor(DEFAULT_PEN_COLOR); } + + /** + * Set the pen color to the given color. + * See available pen colors in class {@link stdlib#Color Color}. + * @param color the Color to make the pen + */ + public void setPenColor(Color color) { + penColor = color; + offscreen.setColor(penColor); + } + + /** + * Get the graph color. + */ + public static Color getGraphColor() { return graphColor; } + + /** + * Set the pen color to the default color (black). + */ + public static void setGraphColor() { setGraphColor(DEFAULT_GRAPH_COLOR); } + + /** + * Set the pen color to the given color. + * See available pen colors in class {@link stdlib#Color Color}. + * @param color the Color to make the graph + */ + public static void setGraphColor(Color color) { + graphColor = color; + offscreen.setColor(graphColor); + } + + /** + * Get the clear color. + */ + public static Color getClearColor() { return DEFAULT_CLEAR_COLOR; } + + /** + * Get the current font. + */ + public static Font getFont() { return font; } + + /** + * Set the font to the default font (sans serif, 16 point). + */ + public static void setFont() { setFont(DEFAULT_FONT); } + + /** + * Set the font to the given value. + * @param f the font to make text + */ + public static void setFont(Font f) { font = f; } + + + /************************************************************************* + * Drawing axes. + *************************************************************************/ + + /** + * Draw an axes from (left, 0) to (right, 0) and (0, down) to (0, up. + * @param left the minimum x-coordinate of the x axes + * @param right the maximum x-coordinate of the x axes + * @param down the minimum y-coordinate of the y axes + * @param up the maximum y-coordinate of the y axes + */ + public static void setAxes(double left, double right, double down, double up){ + offscreen.draw(new Line2D.Double(scaleX(left), scaleY(0), scaleX(right), scaleY(0))); + offscreen.draw(new Line2D.Double(scaleX(0), scaleY(down), scaleX(0), scaleY(up))); + draw(); + } + + + /************************************************************************* + * Drawing geometric shapes. + *************************************************************************/ + + /** + * Draw a line from (x0, y0) to (x1, y1). + * @param x0 the x-coordinate of the starting point + * @param y0 the y-coordinate of the starting point + * @param x1 the x-coordinate of the destination point + * @param y1 the y-coordinate of the destination point + */ + public void line(double x0, double y0, double x1, double y1) { + offscreen.draw(new Line2D.Double(scaleX(x0), scaleY(y0), scaleX(x1), scaleY(y1))); + draw(); + } + + /** + * Draw one pixel at (x, y). + * @param x the x-coordinate of the pixel + * @param y the y-coordinate of the pixel + */ + private static void pixel(double x, double y) { + offscreen.fillRect((int) Math.round(scaleX(x)), (int) Math.round(scaleY(y)), 1, 1); + } + + /** + * Draw a point at (x, y). + * @param x the x-coordinate of the point + * @param y the y-coordinate of the point + */ + public void point(double x, double y) { + double xs = scaleX(x); + double ys = scaleY(y); + double r = penRadius; + // double ws = factorX(2*r); + // double hs = factorY(2*r); + // if (ws <= 1 && hs <= 1) pixel(x, y); + if (r <= 1) pixel(x, y); + else offscreen.fill(new Ellipse2D.Double(xs - r/2, ys - r/2, r, r)); + draw(); + } + + /** + * Draw a circle of radius r, centered on (x, y). + * @param x the x-coordinate of the center of the circle + * @param y the y-coordinate of the center of the circle + * @param r the radius of the circle + * @throws RuntimeException if the radius of the circle is negative + */ + public void circle(double x, double y, double r) { // dw: removed static + if (r < 0) throw new RuntimeException("circle radius can't be negative"); + double xs = scaleX(x); + double ys = scaleY(y); + double ws = factorX(2*r); + double hs = factorY(2*r); + if (ws <= 1 && hs <= 1) pixel(x, y); + else offscreen.draw(new Ellipse2D.Double(xs - ws/2, ys - hs/2, ws, hs)); + draw(); + } + + /** + * Draw filled circle of radius r, centered on (x, y). + * @param x the x-coordinate of the center of the circle + * @param y the y-coordinate of the center of the circle + * @param r the radius of the circle + * @throws RuntimeException if the radius of the circle is negative + */ + public static void filledCircle(double x, double y, double r) { + if (r < 0) throw new RuntimeException("circle radius can't be negative"); + double xs = scaleX(x); + double ys = scaleY(y); + double ws = factorX(2*r); + double hs = factorY(2*r); + if (ws <= 1 && hs <= 1) pixel(x, y); + else offscreen.fill(new Ellipse2D.Double(xs - ws/2, ys - hs/2, ws, hs)); + draw(); + } + + + /** + * Draw an ellipse with given semimajor and semiminor axes, centered on (x, y). + * @param x the x-coordinate of the center of the ellipse + * @param y the y-coordinate of the center of the ellipse + * @param semiMajorAxis is the semimajor axis of the ellipse + * @param semiMinorAxis is the semiminor axis of the ellipse + * @throws RuntimeException if either of the axes are negative + */ + public static void ellipse(double x, double y, double semiMajorAxis, double semiMinorAxis) { + if (semiMajorAxis < 0) throw new RuntimeException("ellipse semimajor axis can't be negative"); + if (semiMinorAxis < 0) throw new RuntimeException("ellipse semiminor axis can't be negative"); + double xs = scaleX(x); + double ys = scaleY(y); + double ws = factorX(2*semiMajorAxis); + double hs = factorY(2*semiMinorAxis); + if (ws <= 1 && hs <= 1) pixel(x, y); + else offscreen.draw(new Ellipse2D.Double(xs - ws/2, ys - hs/2, ws, hs)); + draw(); + } + + /** + * Draw an ellipse with given semimajor and semiminor axes, centered on (x, y). + * @param x the x-coordinate of the center of the ellipse + * @param y the y-coordinate of the center of the ellipse + * @param semiMajorAxis is the semimajor axis of the ellipse + * @param semiMinorAxis is the semiminor axis of the ellipse + * @throws RuntimeException if either of the axes are negative + */ + public static void filledEllipse(double x, double y, double semiMajorAxis, double semiMinorAxis) { + if (semiMajorAxis < 0) throw new RuntimeException("ellipse semimajor axis can't be negative"); + if (semiMinorAxis < 0) throw new RuntimeException("ellipse semiminor axis can't be negative"); + double xs = scaleX(x); + double ys = scaleY(y); + double ws = factorX(2*semiMajorAxis); + double hs = factorY(2*semiMinorAxis); + if (ws <= 1 && hs <= 1) pixel(x, y); + else offscreen.fill(new Ellipse2D.Double(xs - ws/2, ys - hs/2, ws, hs)); + draw(); + } + + + /** + * Draw an arc of radius r, centered on (x, y), from angle1 to angle2 (in degrees). + * @param x the x-coordinate of the center of the circle + * @param y the y-coordinate of the center of the circle + * @param r the radius of the circle + * @param angle1 the starting angle. 0 would mean an arc beginning at 3 o'clock. + * @param angle2 the angle at the end of the arc. For example, if + * you want a 90 degree arc, then angle2 should be angle1 + 90. + * @throws RuntimeException if the radius of the circle is negative + */ + public void arc(double x, double y, double r, double angle1, double angle2) { + if (r < 0) throw new RuntimeException("arc radius can't be negative"); + while (angle2 < angle1) angle2 += 360; + double xs = scaleX(x); + double ys = scaleY(y); + double ws = factorX(2*r); + double hs = factorY(2*r); + if (ws <= 1 && hs <= 1) pixel(x, y); + else offscreen.draw(new Arc2D.Double(xs - ws/2, ys - hs/2, ws, hs, angle1, angle2 - angle1, Arc2D.OPEN)); + draw(); + } + + /** + * Draw a square of side length 2r, centered on (x, y). + * @param x the x-coordinate of the center of the square + * @param y the y-coordinate of the center of the square + * @param r radius is half the length of any side of the square + * @throws RuntimeException if r is negative + */ + public void square(double x, double y, double r) { + if (r < 0) throw new RuntimeException("square side length can't be negative"); + double xs = scaleX(x); + double ys = scaleY(y); + double ws = factorX(2*r); + double hs = factorY(2*r); + if (ws <= 1 && hs <= 1) pixel(x, y); + else offscreen.draw(new Rectangle2D.Double(xs - ws/2, ys - hs/2, ws, hs)); + draw(); + } + + /** + * Draw a filled square of side length 2r, centered on (x, y). + * @param x the x-coordinate of the center of the square + * @param y the y-coordinate of the center of the square + * @param r radius is half the length of any side of the square + * @throws RuntimeException if r is negative + */ + public void filledSquare(double x, double y, double r) { + if (r < 0) throw new RuntimeException("square side length can't be negative"); + double xs = scaleX(x); + double ys = scaleY(y); + double ws = factorX(2*r); + double hs = factorY(2*r); + if (ws <= 1 && hs <= 1) pixel(x, y); + else offscreen.fill(new Rectangle2D.Double(xs - ws/2, ys - hs/2, ws, hs)); + draw(); + } + + + /** + * Draw a rectangle of given half width and half height, centered on (x, y). + * @param x the x-coordinate of the center of the rectangle + * @param y the y-coordinate of the center of the rectangle + * @param halfWidth is half the width of the rectangle + * @param halfHeight is half the height of the rectangle + * @throws RuntimeException if halfWidth or halfHeight is negative + */ + public static void rectangle(double x, double y, double halfWidth, double halfHeight) { + if (halfWidth < 0) throw new RuntimeException("half width can't be negative"); + if (halfHeight < 0) throw new RuntimeException("half height can't be negative"); + double xs = scaleX(x); + double ys = scaleY(y); + double ws = factorX(2*halfWidth); + double hs = factorY(2*halfHeight); + if (ws <= 1 && hs <= 1) pixel(x, y); + else offscreen.draw(new Rectangle2D.Double(xs - ws/2, ys - hs/2, ws, hs)); + draw(); + } + + /** + * Draw a filled rectangle of given half width and half height, centered on (x, y). + * @param x the x-coordinate of the center of the rectangle + * @param y the y-coordinate of the center of the rectangle + * @param halfWidth is half the width of the rectangle + * @param halfHeight is half the height of the rectangle + * @throws RuntimeException if halfWidth or halfHeight is negative + */ + public static void filledRectangle(double x, double y, double halfWidth, double halfHeight) { + if (halfWidth < 0) throw new RuntimeException("half width can't be negative"); + if (halfHeight < 0) throw new RuntimeException("half height can't be negative"); + double xs = scaleX(x); + double ys = scaleY(y); + double ws = factorX(2*halfWidth); + double hs = factorY(2*halfHeight); + if (ws <= 1 && hs <= 1) pixel(x, y); + else offscreen.fill(new Rectangle2D.Double(xs - ws/2, ys - hs/2, ws, hs)); + draw(); + } + + + /** + * Draw a polygon with the given (x[i], y[i]) coordinates. + * @param x an array of all the x-coordindates of the polygon + * @param y an array of all the y-coordindates of the polygon + */ + public static void polygon(double[] x, double[] y) { + int N = x.length; + GeneralPath path = new GeneralPath(); + path.moveTo((float) scaleX(x[0]), (float) scaleY(y[0])); + for (int i = 0; i < N; i++) + path.lineTo((float) scaleX(x[i]), (float) scaleY(y[i])); + path.closePath(); + offscreen.draw(path); + draw(); + } + + /** + * Draw a filled polygon with the given (x[i], y[i]) coordinates. + * @param x an array of all the x-coordindates of the polygon + * @param y an array of all the y-coordindates of the polygon + */ + public static void filledPolygon(double[] x, double[] y) { + int N = x.length; + GeneralPath path = new GeneralPath(); + path.moveTo((float) scaleX(x[0]), (float) scaleY(y[0])); + for (int i = 0; i < N; i++) + path.lineTo((float) scaleX(x[i]), (float) scaleY(y[i])); + path.closePath(); + offscreen.fill(path); + draw(); + } + + + /************************************************************************* + * Drawing images. + *************************************************************************/ + + // get an image from the given filename + private static Image getImage(String filename) { + + // to read from file + ImageIcon icon = new ImageIcon(filename); + + // try to read from URL + if ((icon == null) || (icon.getImageLoadStatus() != MediaTracker.COMPLETE)) { + try { + URL url = new URL(filename); + icon = new ImageIcon(url); + } catch (Exception e) { /* not a url */ } + } + + // in case file is inside a .jar + if ((icon == null) || (icon.getImageLoadStatus() != MediaTracker.COMPLETE)) { + URL url = StdDraw.class.getResource(filename); + if (url == null) throw new RuntimeException("image " + filename + " not found"); + icon = new ImageIcon(url); + } + + return icon.getImage(); + } + + /** + * Draw picture (gif, jpg, or png) centered on (x, y). + * @param x the center x-coordinate of the image + * @param y the center y-coordinate of the image + * @param s the name of the image/picture, e.g., "ball.gif" + * @throws RuntimeException if the image is corrupt + */ + public static void picture(double x, double y, String s) { + Image image = getImage(s); + double xs = scaleX(x); + double ys = scaleY(y); + int ws = image.getWidth(null); + int hs = image.getHeight(null); + if (ws < 0 || hs < 0) throw new RuntimeException("image " + s + " is corrupt"); + + offscreen.drawImage(image, (int) Math.round(xs - ws/2.0), (int) Math.round(ys - hs/2.0), null); + draw(); + } + + /** + * Draw picture (gif, jpg, or png) centered on (x, y), + * rotated given number of degrees + * @param x the center x-coordinate of the image + * @param y the center y-coordinate of the image + * @param s the name of the image/picture, e.g., "ball.gif" + * @param degrees is the number of degrees to rotate counterclockwise + * @throws RuntimeException if the image is corrupt + */ + public static void picture(double x, double y, String s, double degrees) { + Image image = getImage(s); + double xs = scaleX(x); + double ys = scaleY(y); + int ws = image.getWidth(null); + int hs = image.getHeight(null); + if (ws < 0 || hs < 0) throw new RuntimeException("image " + s + " is corrupt"); + + offscreen.rotate(Math.toRadians(-degrees), xs, ys); + offscreen.drawImage(image, (int) Math.round(xs - ws/2.0), (int) Math.round(ys - hs/2.0), null); + offscreen.rotate(Math.toRadians(+degrees), xs, ys); + + draw(); + } + + /** + * Draw picture (gif, jpg, or png) centered on (x, y), rescaled to w-by-h. + * @param x the center x coordinate of the image + * @param y the center y coordinate of the image + * @param s the name of the image/picture, e.g., "ball.gif" + * @param w the width of the image + * @param h the height of the image + * @throws RuntimeException if the width height are negative + * @throws RuntimeException if the image is corrupt + */ + public static void picture(double x, double y, String s, double w, double h) { + Image image = getImage(s); + double xs = scaleX(x); + double ys = scaleY(y); + if (w < 0) throw new RuntimeException("width is negative: " + w); + if (h < 0) throw new RuntimeException("height is negative: " + h); + double ws = factorX(w); + double hs = factorY(h); + if (ws < 0 || hs < 0) throw new RuntimeException("image " + s + " is corrupt"); + if (ws <= 1 && hs <= 1) pixel(x, y); + else + offscreen.drawImage(image, (int) Math.round(xs - ws/2.0), + (int) Math.round(ys - hs/2.0), + (int) Math.round(ws), + (int) Math.round(hs), null); + draw(); + } + + + /** + * Draw picture (gif, jpg, or png) centered on (x, y), rotated + * given number of degrees, rescaled to w-by-h. + * @param x the center x-coordinate of the image + * @param y the center y-coordinate of the image + * @param s the name of the image/picture, e.g., "ball.gif" + * @param w the width of the image + * @param h the height of the image + * @param degrees is the number of degrees to rotate counterclockwise + * @throws RuntimeException if the image is corrupt + */ + public static void picture(double x, double y, String s, double w, double h, double degrees) { + Image image = getImage(s); + double xs = scaleX(x); + double ys = scaleY(y); + double ws = factorX(w); + double hs = factorY(h); + if (ws < 0 || hs < 0) throw new RuntimeException("image " + s + " is corrupt"); + if (ws <= 1 && hs <= 1) pixel(x, y); + + offscreen.rotate(Math.toRadians(-degrees), xs, ys); + offscreen.drawImage(image, (int) Math.round(xs - ws/2.0), + (int) Math.round(ys - hs/2.0), + (int) Math.round(ws), + (int) Math.round(hs), null); + offscreen.rotate(Math.toRadians(+degrees), xs, ys); + + draw(); + } + + + /************************************************************************* + * Drawing text. + *************************************************************************/ + + /** + * Write the given text string in the current font, centered on (x, y). + * @param x the center x-coordinate of the text + * @param y the center y-coordinate of the text + * @param s the text + */ + public static void text(double x, double y, String s) { + offscreen.setFont(font); + FontMetrics metrics = offscreen.getFontMetrics(); + double xs = scaleX(x); + double ys = scaleY(y); + int ws = metrics.stringWidth(s); + int hs = metrics.getDescent(); + offscreen.drawString(s, (float) (xs - ws/2.0), (float) (ys + hs)); + draw(); + } + + /** + * Write the given text string in the current font, centered on (x, y) and + * rotated by the specified number of degrees + * @param x the center x-coordinate of the text + * @param y the center y-coordinate of the text + * @param s the text + * @param degrees is the number of degrees to rotate counterclockwise + */ + public static void text(double x, double y, String s, double degrees) { + double xs = scaleX(x); + double ys = scaleY(y); + offscreen.rotate(Math.toRadians(-degrees), xs, ys); + text(x, y, s); + offscreen.rotate(Math.toRadians(+degrees), xs, ys); + } + + + /** + * Write the given text string in the current font, left-aligned at (x, y). + * @param x the x-coordinate of the text + * @param y the y-coordinate of the text + * @param s the text + */ + public static void textLeft(double x, double y, String s) { + offscreen.setFont(font); + FontMetrics metrics = offscreen.getFontMetrics(); + double xs = scaleX(x); + double ys = scaleY(y); + int hs = metrics.getDescent(); + offscreen.drawString(s, (float) (xs), (float) (ys + hs)); + draw(); + } + + /** + * Write the given text string in the current font, right-aligned at (x, y). + * @param x the x-coordinate of the text + * @param y the y-coordinate of the text + * @param s the text + */ + public static void textRight(double x, double y, String s) { + offscreen.setFont(font); + FontMetrics metrics = offscreen.getFontMetrics(); + double xs = scaleX(x); + double ys = scaleY(y); + int ws = metrics.stringWidth(s); + int hs = metrics.getDescent(); + offscreen.drawString(s, (float) (xs - ws), (float) (ys + hs)); + draw(); + } + + + + /** + * Display on screen, pause for t milliseconds, and turn on + * animation mode: subsequent calls to + * drawing methods such as line(), circle(), and square() + * will not be displayed on screen until the next call to show(). + * This is useful for producing animations (clear the screen, draw a bunch of shapes, + * display on screen for a fixed amount of time, and repeat). It also speeds up + * drawing a huge number of shapes (call show(0) to defer drawing + * on screen, draw the shapes, and call show(0) to display them all + * on screen at once). + * @param t number of milliseconds + */ + public void show(int t) { + defer = false; + draw(); + try { Thread.currentThread(); + Thread.sleep(t); } + catch (InterruptedException e) { System.out.println("Error sleeping"); } + defer = true; + } + + /** + * Display on-screen and turn off animation mode: + * subsequent calls to + * drawing methods such as line(), circle(), and square() + * will be displayed on screen when called. This is the default. + */ + public static void show() { + defer = false; + draw(); + } + + // draw onscreen if defer is false + public static void draw() { + if(defer) return; + onscreen.drawImage(offscreenImage, left, top, null); + frame.repaint(); + } + + + /************************************************************************* + * Save drawing to a file. + *************************************************************************/ + + /** + * Save onscreen image to file - suffix must be png, jpg, or gif. + * @param filename the name of the file with one of the required suffixes + */ + public static void save(String filename) { + File file = new File(filename); + String suffix = filename.substring(filename.lastIndexOf('.') + 1); + + // png files + if (suffix.toLowerCase().equals("png")) { + try { ImageIO.write(onscreenImage, suffix, file); } + catch (IOException e) { e.printStackTrace(); } + } + + // need to change from ARGB to RGB for jpeg + // reference: http://archives.java.sun.com/cgi-bin/wa?A2=ind0404&L=java2d-interest&D=0&P=2727 + else if (suffix.toLowerCase().equals("jpg")) { + WritableRaster raster = onscreenImage.getRaster(); + WritableRaster newRaster; + newRaster = raster.createWritableChild(0, 0, width, height, 0, 0, new int[] {0, 1, 2}); + DirectColorModel cm = (DirectColorModel) onscreenImage.getColorModel(); + DirectColorModel newCM = new DirectColorModel(cm.getPixelSize(), + cm.getRedMask(), + cm.getGreenMask(), + cm.getBlueMask()); + BufferedImage rgbBuffer = new BufferedImage(newCM, newRaster, false, null); + try { ImageIO.write(rgbBuffer, suffix, file); } + catch (IOException e) { e.printStackTrace(); } + } + + else { + System.out.println("Invalid image file type: " + suffix); + } + } + + + /** + * This method cannot be called directly. + */ + @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. + *************************************************************************/ + + /** + * Is the mouse being pressed? + * @return true or false + */ + public static boolean mousePressed() { + synchronized (mouseLock) { + return mousePressed; + } + } + + /** + * What is the x-coordinate of the mouse? + * @return the value of the x-coordinate of the mouse + */ + public static double mouseX() { + synchronized (mouseLock) { + return mouseX; + } + } + + /** + * What is the y-coordinate of the mouse? + * @return the value of the y-coordinate of the mouse + */ + public static double mouseY() { + synchronized (mouseLock) { + return mouseY; + } + } + + + /** + * This method cannot be called directly. + */ + @Override + public void mouseClicked(MouseEvent e) { } + + /** + * This method cannot be called directly. + */ + @Override + public void mouseEntered(MouseEvent e) { } + + /** + * This method cannot be called directly. + */ + @Override + public void mouseExited(MouseEvent e) { } + + /** + * This method cannot be called directly. + */ + @Override + public void mousePressed(MouseEvent e) { + synchronized (mouseLock) { + mouseX = StdDraw.userX(e.getX()); + mouseY = StdDraw.userY(e.getY()); + mousePressed = true; + } + } + + /** + * This method cannot be called directly. + */ + @Override + public void mouseReleased(MouseEvent e) { + synchronized (mouseLock) { + mousePressed = 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. + *************************************************************************/ + + /** + * Has the user typed a key? + * @return true if the user has typed a key, false otherwise + */ + public static boolean hasNextKeyTyped() { + synchronized (keyLock) { + return !keysTyped.isEmpty(); + } + } + + /** + * What is the next key that was typed by the user? This method returns + * a Unicode character corresponding to the key typed (such as 'a' or 'A'). + * It cannot identify action keys (such as F1 + * and arrow keys) or modifier keys (such as control). + * @return the next Unicode key typed + */ + public static char nextKeyTyped() { + synchronized (keyLock) { + return keysTyped.removeLast(); + } + } + + /** + * Is the keycode currently being pressed? This method takes as an argument + * the keycode (corresponding to a physical key). It can handle action keys + * (such as F1 and arrow keys) and modifier keys (such as shift and control). + * See KeyEvent.java + * for a description of key codes. + * @return true if keycode is currently being pressed, false otherwise + */ + public static boolean isKeyPressed(int keycode) { + return keysDown.contains(keycode); + } + + + /** + * This method cannot be called directly. + */ + @Override + public void keyTyped(KeyEvent e) { + synchronized (keyLock) { + keysTyped.addFirst(e.getKeyChar()); + } + } + + /** + * This method cannot be called directly. + */ + @Override + public void keyPressed(KeyEvent e) { + keysDown.add(e.getKeyCode()); + } + + /** + * This method cannot be called directly. + */ + @Override + public void keyReleased(KeyEvent e) { + keysDown.remove(e.getKeyCode()); + } + + + + /** + * Test client. + */ + public static void main(String[] args) { + StdDraw sd = new StdDraw(); + sd.square(.2, .8, .1); + sd.filledSquare(.8, .8, .2); + sd.circle(.8, .2, .2); + + sd.setPenColor(Color.LIGHT_RED); + sd.setPenRadius(.02); + sd.arc(.8, .2, .1, 200, 45); + + // draw a blue diamond + sd.setPenRadius(); + sd.setPenColor(Color.BLUE_GREEN); + double[] x = { .1, .2, .3, .2 }; + double[] y = { .2, .3, .2, .1 }; + StdDraw.filledPolygon(x, y); + + // text + sd.setPenColor(Color.BLACK); + StdDraw.text(0.2, 0.5, "black text"); + sd.setPenColor(Color.WHITE); + StdDraw.text(0.8, 0.8, "white text"); + } + +}