001package stdlib; 002/* *********************************************************************** 003 * Compilation: javac StdDraw.java 004 * Execution: java StdDraw 005 * 006 * Standard drawing library. This class provides a basic capability for 007 * creating drawings with your programs. It uses a simple graphics model that 008 * allows you to create drawings consisting of points, lines, and curves 009 * in a window on your computer and to save the drawings to a file. 010 * 011 * Todo 012 * ---- 013 * - Add support for gradient fill, etc. 014 * 015 * Remarks 016 * ------- 017 * - don't use AffineTransform for rescaling since it inverts 018 * images and strings 019 * - careful using setFont in inner loop within an animation - 020 * it can cause flicker 021 * 022 *************************************************************************/ 023 024import java.awt.*; 025import java.awt.event.*; 026import java.awt.geom.*; 027import java.awt.image.*; 028import java.io.*; 029import java.net.*; 030import java.util.LinkedList; 031import java.util.TreeSet; 032import javax.imageio.ImageIO; 033import javax.swing.*; 034 035/** 036 * <i>Standard draw</i>. This class provides a basic capability for 037 * creating drawings with your programs. It uses a simple graphics model that 038 * allows you to create drawings consisting of points, lines, and curves 039 * in a window on your computer and to save the drawings to a file. 040 * <p> 041 * For additional documentation, see <a href="http://introcs.cs.princeton.edu/15inout">Section 1.5</a> of 042 * <i>Introduction to Programming in Java: An Interdisciplinary Approach</i> by Robert Sedgewick and Kevin Wayne. 043 */ 044public final class StdDraw implements ActionListener, MouseListener, MouseMotionListener, KeyListener { 045 046 // pre-defined colors 047 public static final Color BLACK = Color.BLACK; 048 public static final Color BLUE = Color.BLUE; 049 public static final Color CYAN = Color.CYAN; 050 public static final Color DARK_GRAY = Color.DARK_GRAY; 051 public static final Color GRAY = Color.GRAY; 052 public static final Color GREEN = Color.GREEN; 053 public static final Color LIGHT_GRAY = Color.LIGHT_GRAY; 054 public static final Color MAGENTA = Color.MAGENTA; 055 public static final Color ORANGE = Color.ORANGE; 056 public static final Color PINK = Color.PINK; 057 public static final Color RED = Color.RED; 058 public static final Color WHITE = Color.WHITE; 059 public static final Color YELLOW = Color.YELLOW; 060 061 /** 062 * Shade of blue used in Introduction to Programming in Java. 063 * It is Pantone 300U. The RGB values are approximately (9, 90, 166). 064 */ 065 public static final Color BOOK_BLUE = new Color( 9, 90, 166); 066 public static final Color BOOK_LIGHT_BLUE = new Color(103, 198, 243); 067 068 /** 069 * Shade of red used in Algorithms 4th edition. 070 * It is Pantone 1805U. The RGB values are approximately (150, 35, 31). 071 */ 072 public static final Color BOOK_RED = new Color(150, 35, 31); 073 074 // default colors 075 private static final Color DEFAULT_PEN_COLOR = BLACK; 076 private static final Color DEFAULT_CLEAR_COLOR = WHITE; 077 078 // current pen color 079 private static Color penColor; 080 081 // default canvas size is DEFAULT_SIZE-by-DEFAULT_SIZE 082 private static final int DEFAULT_SIZE = 512; 083 private static int width = DEFAULT_SIZE; 084 private static int height = DEFAULT_SIZE; 085 086 // default pen radius 087 private static final double DEFAULT_PEN_RADIUS = 0.002; 088 089 // current pen radius 090 private static double penRadius; 091 092 // show we draw immediately or wait until next show? 093 private static boolean defer = false; 094 095 // boundary of drawing canvas, 5% border 096 private static final double BORDER = 0.05; 097 private static final double DEFAULT_XMIN = 0.0; 098 private static final double DEFAULT_XMAX = 1.0; 099 private static final double DEFAULT_YMIN = 0.0; 100 private static final double DEFAULT_YMAX = 1.0; 101 private static double xmin, ymin, xmax, ymax; 102 103 // for synchronization 104 private static Object mouseLock = new Object(); 105 private static Object keyLock = new Object(); 106 107 // default font 108 private static final Font DEFAULT_FONT = new Font("SansSerif", Font.PLAIN, 16); 109 110 // current font 111 private static Font font; 112 113 // double buffered graphics 114 private static BufferedImage offscreenImage, onscreenImage; 115 private static Graphics2D offscreen, onscreen; 116 117 // singleton for callbacks: avoids generation of extra .class files 118 private static StdDraw std = new StdDraw(); 119 120 // the frame for drawing to the screen 121 private static JFrame frame; 122 123 // mouse state 124 private static boolean mousePressed = false; 125 private static double mouseX = 0; 126 private static double mouseY = 0; 127 128 // queue of typed key characters 129 private static LinkedList<Character> keysTyped = new LinkedList<>(); 130 131 // set of key codes currently pressed down 132 private static TreeSet<Integer> keysDown = new TreeSet<>(); 133 134 135 // singleton pattern: client can't instantiate 136 private StdDraw() { } 137 138 139 // static initializer 140 static { init(); } 141 142 /** 143 * Set the window size to the default size 512-by-512 pixels. 144 */ 145 public static void setCanvasSize() { 146 setCanvasSize(DEFAULT_SIZE, DEFAULT_SIZE); 147 } 148 149 /** 150 * Set the window size to w-by-h pixels. 151 * 152 * @param w the width as a number of pixels 153 * @param h the height as a number of pixels 154 * @throws java.lang.RuntimeException if the width or height is 0 or negative 155 */ 156 public static void setCanvasSize(int w, int h) { 157 if (w < 1 || h < 1) throw new RuntimeException("width and height must be positive"); 158 width = w; 159 height = h; 160 init(); 161 } 162 163 // init 164 private static void init() { 165 if (frame != null) frame.setVisible(false); 166 frame = new JFrame(); 167 offscreenImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); 168 onscreenImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); 169 offscreen = offscreenImage.createGraphics(); 170 onscreen = onscreenImage.createGraphics(); 171 setXscale(); 172 setYscale(); 173 offscreen.setColor(DEFAULT_CLEAR_COLOR); 174 offscreen.fillRect(0, 0, width, height); 175 setPenColor(); 176 setPenRadius(); 177 setFont(); 178 clear(); 179 180 // add antialiasing 181 RenderingHints hints = new RenderingHints(RenderingHints.KEY_ANTIALIASING, 182 RenderingHints.VALUE_ANTIALIAS_ON); 183 hints.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); 184 offscreen.addRenderingHints(hints); 185 186 // frame stuff 187 ImageIcon icon = new ImageIcon(onscreenImage); 188 JLabel draw = new JLabel(icon); 189 190 draw.addMouseListener(std); 191 draw.addMouseMotionListener(std); 192 193 frame.setContentPane(draw); 194 frame.addKeyListener(std); // JLabel cannot get keyboard focus 195 frame.setResizable(false); 196 frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); // closes all windows 197 //frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); // closes only current window 198 frame.setTitle("Standard Draw"); 199 frame.setJMenuBar(createMenuBar()); 200 frame.pack(); 201 frame.requestFocusInWindow(); 202 frame.setVisible(true); 203 } 204 205 // create the menu bar (changed to private) 206 @SuppressWarnings("deprecation") 207 private static JMenuBar createMenuBar() { 208 JMenuBar menuBar = new JMenuBar(); 209 JMenu menu = new JMenu("File"); 210 menuBar.add(menu); 211 JMenuItem menuItem1 = new JMenuItem(" Save... "); 212 menuItem1.addActionListener(std); 213 menuItem1.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, 214 Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); 215 menu.add(menuItem1); 216 return menuBar; 217 } 218 219 220 /* *********************************************************************** 221 * User and screen coordinate systems 222 *************************************************************************/ 223 224 /** 225 * Set the x-scale to be the default (between 0.0 and 1.0). 226 */ 227 public static void setXscale() { setXscale(DEFAULT_XMIN, DEFAULT_XMAX); } 228 229 /** 230 * Set the y-scale to be the default (between 0.0 and 1.0). 231 */ 232 public static void setYscale() { setYscale(DEFAULT_YMIN, DEFAULT_YMAX); } 233 234 /** 235 * Set the x-scale (a 10% border is added to the values) 236 * @param min the minimum value of the x-scale 237 * @param max the maximum value of the x-scale 238 */ 239 public static void setXscale(double min, double max) { 240 double size = max - min; 241 synchronized (mouseLock) { 242 xmin = min - BORDER * size; 243 xmax = max + BORDER * size; 244 } 245 } 246 247 /** 248 * Set the y-scale (a 10% border is added to the values). 249 * @param min the minimum value of the y-scale 250 * @param max the maximum value of the y-scale 251 */ 252 public static void setYscale(double min, double max) { 253 double size = max - min; 254 synchronized (mouseLock) { 255 ymin = min - BORDER * size; 256 ymax = max + BORDER * size; 257 } 258 } 259 260 /** 261 * Set the x-scale and y-scale (a 10% border is added to the values) 262 * @param min the minimum value of the x- and y-scales 263 * @param max the maximum value of the x- and y-scales 264 */ 265 public static void setScale(double min, double max) { 266 double size = max - min; 267 synchronized (mouseLock) { 268 xmin = min - BORDER * size; 269 xmax = max + BORDER * size; 270 ymin = min - BORDER * size; 271 ymax = max + BORDER * size; 272 } 273 } 274 275 // helper functions that scale from user coordinates to screen coordinates and back 276 private static double scaleX(double x) { return width * (x - xmin) / (xmax - xmin); } 277 private static double scaleY(double y) { return height * (ymax - y) / (ymax - ymin); } 278 private static double factorX(double w) { return w * width / Math.abs(xmax - xmin); } 279 private static double factorY(double h) { return h * height / Math.abs(ymax - ymin); } 280 private static double userX(double x) { return xmin + x * (xmax - xmin) / width; } 281 private static double userY(double y) { return ymax - y * (ymax - ymin) / height; } 282 283 284 /** 285 * Clear the screen to the default color (white). 286 */ 287 public static void clear() { clear(DEFAULT_CLEAR_COLOR); } 288 /** 289 * Clear the screen to the given color. 290 * @param color the Color to make the background 291 */ 292 public static void clear(Color color) { 293 offscreen.setColor(color); 294 offscreen.fillRect(0, 0, width, height); 295 offscreen.setColor(penColor); 296 draw(); 297 } 298 299 /** 300 * Get the current pen radius. 301 */ 302 public static double getPenRadius() { return penRadius; } 303 304 /** 305 * Set the pen size to the default (.002). 306 */ 307 public static void setPenRadius() { setPenRadius(DEFAULT_PEN_RADIUS); } 308 /** 309 * Set the radius of the pen to the given size. 310 * @param r the radius of the pen 311 * @throws java.lang.RuntimeException if r is negative 312 */ 313 public static void setPenRadius(double r) { 314 if (r < 0) throw new RuntimeException("pen radius must be positive"); 315 penRadius = r; 316 float scaledPenRadius = (float) (r * DEFAULT_SIZE); 317 BasicStroke stroke = new BasicStroke(scaledPenRadius, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND); 318 // BasicStroke stroke = new BasicStroke(scaledPenRadius); 319 offscreen.setStroke(stroke); 320 } 321 322 /** 323 * Get the current pen color. 324 */ 325 public static Color getPenColor() { return penColor; } 326 327 /** 328 * Set the pen color to the default color (black). 329 */ 330 public static void setPenColor() { setPenColor(DEFAULT_PEN_COLOR); } 331 /** 332 * Set the pen color to the given color. The available pen colors are 333 * BLACK, BLUE, CYAN, DARK_GRAY, GRAY, GREEN, LIGHT_GRAY, MAGENTA, 334 * ORANGE, PINK, RED, WHITE, and YELLOW. 335 * @param color the Color to make the pen 336 */ 337 public static void setPenColor(Color color) { 338 penColor = color; 339 offscreen.setColor(penColor); 340 } 341 342 /** 343 * Get the current font. 344 */ 345 public static Font getFont() { return font; } 346 347 /** 348 * Set the font to the default font (sans serif, 16 point). 349 */ 350 public static void setFont() { setFont(DEFAULT_FONT); } 351 352 /** 353 * Set the font to the given value. 354 * @param f the font to make text 355 */ 356 public static void setFont(Font f) { font = f; } 357 358 359 /* *********************************************************************** 360 * Drawing geometric shapes. 361 *************************************************************************/ 362 363 /** 364 * Draw a line from (x0, y0) to (x1, y1). 365 * @param x0 the x-coordinate of the starting point 366 * @param y0 the y-coordinate of the starting point 367 * @param x1 the x-coordinate of the destination point 368 * @param y1 the y-coordinate of the destination point 369 */ 370 public static void line(double x0, double y0, double x1, double y1) { 371 offscreen.draw(new Line2D.Double(scaleX(x0), scaleY(y0), scaleX(x1), scaleY(y1))); 372 draw(); 373 } 374 375 /** 376 * Draw one pixel at (x, y). 377 * @param x the x-coordinate of the pixel 378 * @param y the y-coordinate of the pixel 379 */ 380 private static void pixel(double x, double y) { 381 offscreen.fillRect((int) Math.round(scaleX(x)), (int) Math.round(scaleY(y)), 1, 1); 382 } 383 384 /** 385 * Draw a point at (x, y). 386 * @param x the x-coordinate of the point 387 * @param y the y-coordinate of the point 388 */ 389 public static void point(double x, double y) { 390 double xs = scaleX(x); 391 double ys = scaleY(y); 392 double r = penRadius; 393 float scaledPenRadius = (float) (r * DEFAULT_SIZE); 394 395 // double ws = factorX(2*r); 396 // double hs = factorY(2*r); 397 // if (ws <= 1 && hs <= 1) pixel(x, y); 398 if (scaledPenRadius <= 1) pixel(x, y); 399 else offscreen.fill(new Ellipse2D.Double(xs - scaledPenRadius/2, ys - scaledPenRadius/2, 400 scaledPenRadius, scaledPenRadius)); 401 draw(); 402 } 403 404 /** 405 * Draw a circle of radius r, centered on (x, y). 406 * @param x the x-coordinate of the center of the circle 407 * @param y the y-coordinate of the center of the circle 408 * @param r the radius of the circle 409 * @throws java.lang.RuntimeException if the radius of the circle is negative 410 */ 411 public static void circle(double x, double y, double r) { 412 if (r < 0) throw new RuntimeException("circle radius can't be negative"); 413 double xs = scaleX(x); 414 double ys = scaleY(y); 415 double ws = factorX(2*r); 416 double hs = factorY(2*r); 417 if (ws <= 1 && hs <= 1) pixel(x, y); 418 else offscreen.draw(new Ellipse2D.Double(xs - ws/2, ys - hs/2, ws, hs)); 419 draw(); 420 } 421 422 /** 423 * Draw filled circle of radius r, centered on (x, y). 424 * @param x the x-coordinate of the center of the circle 425 * @param y the y-coordinate of the center of the circle 426 * @param r the radius of the circle 427 * @throws java.lang.RuntimeException if the radius of the circle is negative 428 */ 429 public static void filledCircle(double x, double y, double r) { 430 if (r < 0) throw new RuntimeException("circle radius can't be negative"); 431 double xs = scaleX(x); 432 double ys = scaleY(y); 433 double ws = factorX(2*r); 434 double hs = factorY(2*r); 435 if (ws <= 1 && hs <= 1) pixel(x, y); 436 else offscreen.fill(new Ellipse2D.Double(xs - ws/2, ys - hs/2, ws, hs)); 437 draw(); 438 } 439 440 441 /** 442 * Draw an ellipse with given semimajor and semiminor axes, centered on (x, y). 443 * @param x the x-coordinate of the center of the ellipse 444 * @param y the y-coordinate of the center of the ellipse 445 * @param semiMajorAxis is the semimajor axis of the ellipse 446 * @param semiMinorAxis is the semiminor axis of the ellipse 447 * @throws java.lang.RuntimeException if either of the axes are negative 448 */ 449 public static void ellipse(double x, double y, double semiMajorAxis, double semiMinorAxis) { 450 if (semiMajorAxis < 0) throw new RuntimeException("ellipse semimajor axis can't be negative"); 451 if (semiMinorAxis < 0) throw new RuntimeException("ellipse semiminor axis can't be negative"); 452 double xs = scaleX(x); 453 double ys = scaleY(y); 454 double ws = factorX(2*semiMajorAxis); 455 double hs = factorY(2*semiMinorAxis); 456 if (ws <= 1 && hs <= 1) pixel(x, y); 457 else offscreen.draw(new Ellipse2D.Double(xs - ws/2, ys - hs/2, ws, hs)); 458 draw(); 459 } 460 461 /** 462 * Draw an ellipse with given semimajor and semiminor axes, centered on (x, y). 463 * @param x the x-coordinate of the center of the ellipse 464 * @param y the y-coordinate of the center of the ellipse 465 * @param semiMajorAxis is the semimajor axis of the ellipse 466 * @param semiMinorAxis is the semiminor axis of the ellipse 467 * @throws java.lang.RuntimeException if either of the axes are negative 468 */ 469 public static void filledEllipse(double x, double y, double semiMajorAxis, double semiMinorAxis) { 470 if (semiMajorAxis < 0) throw new RuntimeException("ellipse semimajor axis can't be negative"); 471 if (semiMinorAxis < 0) throw new RuntimeException("ellipse semiminor axis can't be negative"); 472 double xs = scaleX(x); 473 double ys = scaleY(y); 474 double ws = factorX(2*semiMajorAxis); 475 double hs = factorY(2*semiMinorAxis); 476 if (ws <= 1 && hs <= 1) pixel(x, y); 477 else offscreen.fill(new Ellipse2D.Double(xs - ws/2, ys - hs/2, ws, hs)); 478 draw(); 479 } 480 481 482 /** 483 * Draw an arc of radius r, centered on (x, y), from angle1 to angle2 (in degrees). 484 * @param x the x-coordinate of the center of the circle 485 * @param y the y-coordinate of the center of the circle 486 * @param r the radius of the circle 487 * @param angle1 the starting angle. 0 would mean an arc beginning at 3 o'clock. 488 * @param angle2 the angle at the end of the arc. For example, if 489 * you want a 90 degree arc, then angle2 should be angle1 + 90. 490 * @throws java.lang.RuntimeException if the radius of the circle is negative 491 */ 492 public static void arc(double x, double y, double r, double angle1, double angle2) { 493 if (r < 0) throw new RuntimeException("arc radius can't be negative"); 494 while (angle2 < angle1) angle2 += 360; 495 double xs = scaleX(x); 496 double ys = scaleY(y); 497 double ws = factorX(2*r); 498 double hs = factorY(2*r); 499 if (ws <= 1 && hs <= 1) pixel(x, y); 500 else offscreen.draw(new Arc2D.Double(xs - ws/2, ys - hs/2, ws, hs, angle1, angle2 - angle1, Arc2D.OPEN)); 501 draw(); 502 } 503 504 /** 505 * Draw a square of side length 2r, centered on (x, y). 506 * @param x the x-coordinate of the center of the square 507 * @param y the y-coordinate of the center of the square 508 * @param r radius is half the length of any side of the square 509 * @throws java.lang.RuntimeException if r is negative 510 */ 511 public static void square(double x, double y, double r) { 512 if (r < 0) throw new RuntimeException("square side length can't be negative"); 513 double xs = scaleX(x); 514 double ys = scaleY(y); 515 double ws = factorX(2*r); 516 double hs = factorY(2*r); 517 if (ws <= 1 && hs <= 1) pixel(x, y); 518 else offscreen.draw(new Rectangle2D.Double(xs - ws/2, ys - hs/2, ws, hs)); 519 draw(); 520 } 521 522 /** 523 * Draw a filled square of side length 2r, centered on (x, y). 524 * @param x the x-coordinate of the center of the square 525 * @param y the y-coordinate of the center of the square 526 * @param r radius is half the length of any side of the square 527 * @throws java.lang.RuntimeException if r is negative 528 */ 529 public static void filledSquare(double x, double y, double r) { 530 if (r < 0) throw new RuntimeException("square side length can't be negative"); 531 double xs = scaleX(x); 532 double ys = scaleY(y); 533 double ws = factorX(2*r); 534 double hs = factorY(2*r); 535 if (ws <= 1 && hs <= 1) pixel(x, y); 536 else offscreen.fill(new Rectangle2D.Double(xs - ws/2, ys - hs/2, ws, hs)); 537 draw(); 538 } 539 540 541 /** 542 * Draw a rectangle of given half width and half height, centered on (x, y). 543 * @param x the x-coordinate of the center of the rectangle 544 * @param y the y-coordinate of the center of the rectangle 545 * @param halfWidth is half the width of the rectangle 546 * @param halfHeight is half the height of the rectangle 547 * @throws java.lang.RuntimeException if halfWidth or halfHeight is negative 548 */ 549 public static void rectangle(double x, double y, double halfWidth, double halfHeight) { 550 if (halfWidth < 0) throw new RuntimeException("half width can't be negative"); 551 if (halfHeight < 0) throw new RuntimeException("half height can't be negative"); 552 double xs = scaleX(x); 553 double ys = scaleY(y); 554 double ws = factorX(2*halfWidth); 555 double hs = factorY(2*halfHeight); 556 if (ws <= 1 && hs <= 1) pixel(x, y); 557 else offscreen.draw(new Rectangle2D.Double(xs - ws/2, ys - hs/2, ws, hs)); 558 draw(); 559 } 560 561 /** 562 * Draw a filled rectangle of given half width and half height, centered on (x, y). 563 * @param x the x-coordinate of the center of the rectangle 564 * @param y the y-coordinate of the center of the rectangle 565 * @param halfWidth is half the width of the rectangle 566 * @param halfHeight is half the height of the rectangle 567 * @throws java.lang.RuntimeException if halfWidth or halfHeight is negative 568 */ 569 public static void filledRectangle(double x, double y, double halfWidth, double halfHeight) { 570 if (halfWidth < 0) throw new RuntimeException("half width can't be negative"); 571 if (halfHeight < 0) throw new RuntimeException("half height can't be negative"); 572 double xs = scaleX(x); 573 double ys = scaleY(y); 574 double ws = factorX(2*halfWidth); 575 double hs = factorY(2*halfHeight); 576 if (ws <= 1 && hs <= 1) pixel(x, y); 577 else offscreen.fill(new Rectangle2D.Double(xs - ws/2, ys - hs/2, ws, hs)); 578 draw(); 579 } 580 581 582 /** 583 * Draw a polygon with the given (x[i], y[i]) coordinates. 584 * @param x an array of all the x-coordindates of the polygon 585 * @param y an array of all the y-coordindates of the polygon 586 */ 587 public static void polygon(double[] x, double[] y) { 588 int N = x.length; 589 GeneralPath path = new GeneralPath(); 590 path.moveTo((float) scaleX(x[0]), (float) scaleY(y[0])); 591 for (int i = 0; i < N; i++) 592 path.lineTo((float) scaleX(x[i]), (float) scaleY(y[i])); 593 path.closePath(); 594 offscreen.draw(path); 595 draw(); 596 } 597 598 /** 599 * Draw a filled polygon with the given (x[i], y[i]) coordinates. 600 * @param x an array of all the x-coordindates of the polygon 601 * @param y an array of all the y-coordindates of the polygon 602 */ 603 public static void filledPolygon(double[] x, double[] y) { 604 int N = x.length; 605 GeneralPath path = new GeneralPath(); 606 path.moveTo((float) scaleX(x[0]), (float) scaleY(y[0])); 607 for (int i = 0; i < N; i++) 608 path.lineTo((float) scaleX(x[i]), (float) scaleY(y[i])); 609 path.closePath(); 610 offscreen.fill(path); 611 draw(); 612 } 613 614 615 616 /* *********************************************************************** 617 * Drawing images. 618 *************************************************************************/ 619 620 // get an image from the given filename 621 private static Image getImage(String filename) { 622 623 // to read from file 624 ImageIcon icon = new ImageIcon(filename); 625 626 // try to read from URL 627 if ((icon == null) || (icon.getImageLoadStatus() != MediaTracker.COMPLETE)) { 628 try { 629 URL url = new URL(filename); 630 icon = new ImageIcon(url); 631 } catch (Exception e) { /* not a url */ } 632 } 633 634 // in case file is inside a .jar 635 if ((icon == null) || (icon.getImageLoadStatus() != MediaTracker.COMPLETE)) { 636 URL url = StdDraw.class.getResource(filename); 637 if (url == null) throw new RuntimeException("image " + filename + " not found"); 638 icon = new ImageIcon(url); 639 } 640 641 return icon.getImage(); 642 } 643 644 /** 645 * Draw picture (gif, jpg, or png) centered on (x, y). 646 * @param x the center x-coordinate of the image 647 * @param y the center y-coordinate of the image 648 * @param s the name of the image/picture, e.g., "ball.gif" 649 * @throws java.lang.RuntimeException if the image is corrupt 650 */ 651 public static void picture(double x, double y, String s) { 652 Image image = getImage(s); 653 double xs = scaleX(x); 654 double ys = scaleY(y); 655 int ws = image.getWidth(null); 656 int hs = image.getHeight(null); 657 if (ws < 0 || hs < 0) throw new RuntimeException("image " + s + " is corrupt"); 658 659 offscreen.drawImage(image, (int) Math.round(xs - ws/2.0), (int) Math.round(ys - hs/2.0), null); 660 draw(); 661 } 662 663 /** 664 * Draw picture (gif, jpg, or png) centered on (x, y), 665 * rotated given number of degrees 666 * @param x the center x-coordinate of the image 667 * @param y the center y-coordinate of the image 668 * @param s the name of the image/picture, e.g., "ball.gif" 669 * @param degrees is the number of degrees to rotate counterclockwise 670 * @throws java.lang.RuntimeException if the image is corrupt 671 */ 672 public static void picture(double x, double y, String s, double degrees) { 673 Image image = getImage(s); 674 double xs = scaleX(x); 675 double ys = scaleY(y); 676 int ws = image.getWidth(null); 677 int hs = image.getHeight(null); 678 if (ws < 0 || hs < 0) throw new RuntimeException("image " + s + " is corrupt"); 679 680 offscreen.rotate(Math.toRadians(-degrees), xs, ys); 681 offscreen.drawImage(image, (int) Math.round(xs - ws/2.0), (int) Math.round(ys - hs/2.0), null); 682 offscreen.rotate(Math.toRadians(+degrees), xs, ys); 683 684 draw(); 685 } 686 687 /** 688 * Draw picture (gif, jpg, or png) centered on (x, y), rescaled to w-by-h. 689 * @param x the center x coordinate of the image 690 * @param y the center y coordinate of the image 691 * @param s the name of the image/picture, e.g., "ball.gif" 692 * @param w the width of the image 693 * @param h the height of the image 694 * @throws java.lang.RuntimeException if the width height are negative 695 * @throws java.lang.RuntimeException if the image is corrupt 696 */ 697 public static void picture(double x, double y, String s, double w, double h) { 698 Image image = getImage(s); 699 double xs = scaleX(x); 700 double ys = scaleY(y); 701 if (w < 0) throw new RuntimeException("width is negative: " + w); 702 if (h < 0) throw new RuntimeException("height is negative: " + h); 703 double ws = factorX(w); 704 double hs = factorY(h); 705 if (ws < 0 || hs < 0) throw new RuntimeException("image " + s + " is corrupt"); 706 if (ws <= 1 && hs <= 1) pixel(x, y); 707 else { 708 offscreen.drawImage(image, (int) Math.round(xs - ws/2.0), 709 (int) Math.round(ys - hs/2.0), 710 (int) Math.round(ws), 711 (int) Math.round(hs), null); 712 } 713 draw(); 714 } 715 716 717 /** 718 * Draw picture (gif, jpg, or png) centered on (x, y), rotated 719 * given number of degrees, rescaled to w-by-h. 720 * @param x the center x-coordinate of the image 721 * @param y the center y-coordinate of the image 722 * @param s the name of the image/picture, e.g., "ball.gif" 723 * @param w the width of the image 724 * @param h the height of the image 725 * @param degrees is the number of degrees to rotate counterclockwise 726 * @throws java.lang.RuntimeException if the image is corrupt 727 */ 728 public static void picture(double x, double y, String s, double w, double h, double degrees) { 729 Image image = getImage(s); 730 double xs = scaleX(x); 731 double ys = scaleY(y); 732 double ws = factorX(w); 733 double hs = factorY(h); 734 if (ws < 0 || hs < 0) throw new RuntimeException("image " + s + " is corrupt"); 735 if (ws <= 1 && hs <= 1) pixel(x, y); 736 737 offscreen.rotate(Math.toRadians(-degrees), xs, ys); 738 offscreen.drawImage(image, (int) Math.round(xs - ws/2.0), 739 (int) Math.round(ys - hs/2.0), 740 (int) Math.round(ws), 741 (int) Math.round(hs), null); 742 offscreen.rotate(Math.toRadians(+degrees), xs, ys); 743 744 draw(); 745 } 746 747 748 /* *********************************************************************** 749 * Drawing text. 750 *************************************************************************/ 751 752 /** 753 * Write the given text string in the current font, centered on (x, y). 754 * @param x the center x-coordinate of the text 755 * @param y the center y-coordinate of the text 756 * @param s the text 757 */ 758 public static void text(double x, double y, String s) { 759 offscreen.setFont(font); 760 FontMetrics metrics = offscreen.getFontMetrics(); 761 double xs = scaleX(x); 762 double ys = scaleY(y); 763 int ws = metrics.stringWidth(s); 764 int hs = metrics.getDescent(); 765 offscreen.drawString(s, (float) (xs - ws/2.0), (float) (ys + hs)); 766 draw(); 767 } 768 769 /** 770 * Write the given text string in the current font, centered on (x, y) and 771 * rotated by the specified number of degrees 772 * @param x the center x-coordinate of the text 773 * @param y the center y-coordinate of the text 774 * @param s the text 775 * @param degrees is the number of degrees to rotate counterclockwise 776 */ 777 public static void text(double x, double y, String s, double degrees) { 778 double xs = scaleX(x); 779 double ys = scaleY(y); 780 offscreen.rotate(Math.toRadians(-degrees), xs, ys); 781 text(x, y, s); 782 offscreen.rotate(Math.toRadians(+degrees), xs, ys); 783 } 784 785 786 /** 787 * Write the given text string in the current font, left-aligned at (x, y). 788 * @param x the x-coordinate of the text 789 * @param y the y-coordinate of the text 790 * @param s the text 791 */ 792 public static void textLeft(double x, double y, String s) { 793 offscreen.setFont(font); 794 FontMetrics metrics = offscreen.getFontMetrics(); 795 double xs = scaleX(x); 796 double ys = scaleY(y); 797 int hs = metrics.getDescent(); 798 offscreen.drawString(s, (float) (xs), (float) (ys + hs)); 799 draw(); 800 } 801 802 /** 803 * Write the given text string in the current font, right-aligned at (x, y). 804 * @param x the x-coordinate of the text 805 * @param y the y-coordinate of the text 806 * @param s the text 807 */ 808 public static void textRight(double x, double y, String s) { 809 offscreen.setFont(font); 810 FontMetrics metrics = offscreen.getFontMetrics(); 811 double xs = scaleX(x); 812 double ys = scaleY(y); 813 int ws = metrics.stringWidth(s); 814 int hs = metrics.getDescent(); 815 offscreen.drawString(s, (float) (xs - ws), (float) (ys + hs)); 816 draw(); 817 } 818 819 820 821 /** 822 * Display on screen, pause for t milliseconds, and turn on 823 * <em>animation mode</em>: subsequent calls to 824 * drawing methods such as {@code line()}, {@code circle()}, and {@code square()} 825 * will not be displayed on screen until the next call to {@code show()}. 826 * This is useful for producing animations (clear the screen, draw a bunch of shapes, 827 * display on screen for a fixed amount of time, and repeat). It also speeds up 828 * drawing a huge number of shapes (call {@code show(0)} to defer drawing 829 * on screen, draw the shapes, and call {@code show(0)} to display them all 830 * on screen at once). 831 * @param t number of milliseconds 832 */ 833 public static void show(int t) { 834 defer = false; 835 draw(); 836 try { Thread.sleep(t); } 837 catch (InterruptedException e) { System.out.println("Error sleeping"); } 838 defer = true; 839 } 840 841 /** 842 * Display on-screen and turn off animation mode: 843 * subsequent calls to 844 * drawing methods such as {@code line()}, {@code circle()}, and {@code square()} 845 * will be displayed on screen when called. This is the default. 846 */ 847 public static void show() { 848 defer = false; 849 draw(); 850 } 851 852 // draw onscreen if defer is false 853 private static void draw() { 854 if (defer) return; 855 onscreen.drawImage(offscreenImage, 0, 0, null); 856 frame.repaint(); 857 } 858 859 860 /* *********************************************************************** 861 * Save drawing to a file. 862 *************************************************************************/ 863 864 /** 865 * Save onscreen image to file - suffix must be png, jpg, or gif. 866 * @param filename the name of the file with one of the required suffixes 867 */ 868 public static void save(String filename) { 869 File file = new File(filename); 870 String suffix = filename.substring(filename.lastIndexOf('.') + 1); 871 872 // png files 873 if (suffix.toLowerCase().equals("png")) { 874 try { ImageIO.write(onscreenImage, suffix, file); } 875 catch (IOException e) { e.printStackTrace(); } 876 } 877 878 // need to change from ARGB to RGB for jpeg 879 // reference: http://archives.java.sun.com/cgi-bin/wa?A2=ind0404&L=java2d-interest&D=0&P=2727 880 else if (suffix.toLowerCase().equals("jpg")) { 881 WritableRaster raster = onscreenImage.getRaster(); 882 WritableRaster newRaster; 883 newRaster = raster.createWritableChild(0, 0, width, height, 0, 0, new int[] {0, 1, 2}); 884 DirectColorModel cm = (DirectColorModel) onscreenImage.getColorModel(); 885 DirectColorModel newCM = new DirectColorModel(cm.getPixelSize(), 886 cm.getRedMask(), 887 cm.getGreenMask(), 888 cm.getBlueMask()); 889 BufferedImage rgbBuffer = new BufferedImage(newCM, newRaster, false, null); 890 try { ImageIO.write(rgbBuffer, suffix, file); } 891 catch (IOException e) { e.printStackTrace(); } 892 } 893 894 else { 895 System.out.println("Invalid image file type: " + suffix); 896 } 897 } 898 899 900 /** 901 * This method cannot be called directly. 902 */ 903 public void actionPerformed(ActionEvent e) { 904 FileDialog chooser = new FileDialog(StdDraw.frame, "Use a .png or .jpg extension", FileDialog.SAVE); 905 chooser.setVisible(true); 906 String filename = chooser.getFile(); 907 if (filename != null) { 908 StdDraw.save(chooser.getDirectory() + File.separator + chooser.getFile()); 909 } 910 } 911 912 913 /* *********************************************************************** 914 * Mouse interactions. 915 *************************************************************************/ 916 917 /** 918 * Is the mouse being pressed? 919 * @return true or false 920 */ 921 public static boolean mousePressed() { 922 synchronized (mouseLock) { 923 return mousePressed; 924 } 925 } 926 927 /** 928 * What is the x-coordinate of the mouse? 929 * @return the value of the x-coordinate of the mouse 930 */ 931 public static double mouseX() { 932 synchronized (mouseLock) { 933 return mouseX; 934 } 935 } 936 937 /** 938 * What is the y-coordinate of the mouse? 939 * @return the value of the y-coordinate of the mouse 940 */ 941 public static double mouseY() { 942 synchronized (mouseLock) { 943 return mouseY; 944 } 945 } 946 947 948 /** 949 * This method cannot be called directly. 950 */ 951 public void mouseClicked(MouseEvent e) { } 952 953 /** 954 * This method cannot be called directly. 955 */ 956 public void mouseEntered(MouseEvent e) { } 957 958 /** 959 * This method cannot be called directly. 960 */ 961 public void mouseExited(MouseEvent e) { } 962 963 /** 964 * This method cannot be called directly. 965 */ 966 public void mousePressed(MouseEvent e) { 967 synchronized (mouseLock) { 968 mouseX = StdDraw.userX(e.getX()); 969 mouseY = StdDraw.userY(e.getY()); 970 mousePressed = true; 971 } 972 } 973 974 /** 975 * This method cannot be called directly. 976 */ 977 public void mouseReleased(MouseEvent e) { 978 synchronized (mouseLock) { 979 mousePressed = false; 980 } 981 } 982 983 /** 984 * This method cannot be called directly. 985 */ 986 public void mouseDragged(MouseEvent e) { 987 synchronized (mouseLock) { 988 mouseX = StdDraw.userX(e.getX()); 989 mouseY = StdDraw.userY(e.getY()); 990 } 991 } 992 993 /** 994 * This method cannot be called directly. 995 */ 996 public void mouseMoved(MouseEvent e) { 997 synchronized (mouseLock) { 998 mouseX = StdDraw.userX(e.getX()); 999 mouseY = StdDraw.userY(e.getY()); 1000 } 1001 } 1002 1003 1004 /* *********************************************************************** 1005 * Keyboard interactions. 1006 *************************************************************************/ 1007 1008 /** 1009 * Has the user typed a key? 1010 * @return true if the user has typed a key, false otherwise 1011 */ 1012 public static boolean hasNextKeyTyped() { 1013 synchronized (keyLock) { 1014 return !keysTyped.isEmpty(); 1015 } 1016 } 1017 1018 /** 1019 * What is the next key that was typed by the user? This method returns 1020 * a Unicode character corresponding to the key typed (such as 'a' or 'A'). 1021 * It cannot identify action keys (such as F1 1022 * and arrow keys) or modifier keys (such as control). 1023 * @return the next Unicode key typed 1024 */ 1025 public static char nextKeyTyped() { 1026 synchronized (keyLock) { 1027 return keysTyped.removeLast(); 1028 } 1029 } 1030 1031 /** 1032 * Is the keycode currently being pressed? This method takes as an argument 1033 * the keycode (corresponding to a physical key). It can handle action keys 1034 * (such as F1 and arrow keys) and modifier keys (such as shift and control). 1035 * See <a href = "http://download.oracle.com/javase/6/docs/api/java/awt/event/KeyEvent.html">KeyEvent.java</a> 1036 * for a description of key codes. 1037 * @return true if keycode is currently being pressed, false otherwise 1038 */ 1039 public static boolean isKeyPressed(int keycode) { 1040 synchronized (keyLock) { 1041 return keysDown.contains(keycode); 1042 } 1043 } 1044 1045 1046 /** 1047 * This method cannot be called directly. 1048 */ 1049 public void keyTyped(KeyEvent e) { 1050 synchronized (keyLock) { 1051 keysTyped.addFirst(e.getKeyChar()); 1052 } 1053 } 1054 1055 /** 1056 * This method cannot be called directly. 1057 */ 1058 public void keyPressed(KeyEvent e) { 1059 synchronized (keyLock) { 1060 keysDown.add(e.getKeyCode()); 1061 } 1062 } 1063 1064 /** 1065 * This method cannot be called directly. 1066 */ 1067 public void keyReleased(KeyEvent e) { 1068 synchronized (keyLock) { 1069 keysDown.remove(e.getKeyCode()); 1070 } 1071 } 1072 1073 1074 1075 1076 /** 1077 * Test client. 1078 */ 1079 public static void main(String[] args) { 1080 StdDraw.square(.2, .8, .1); 1081 StdDraw.filledSquare(.8, .8, .2); 1082 StdDraw.circle(.8, .2, .2); 1083 1084 StdDraw.setPenColor(StdDraw.BOOK_RED); 1085 StdDraw.setPenRadius(.02); 1086 StdDraw.arc(.8, .2, .1, 200, 45); 1087 1088 // draw a blue diamond 1089 StdDraw.setPenRadius(); 1090 StdDraw.setPenColor(StdDraw.BOOK_BLUE); 1091 double[] x = { .1, .2, .3, .2 }; 1092 double[] y = { .2, .3, .2, .1 }; 1093 StdDraw.filledPolygon(x, y); 1094 1095 // text 1096 StdDraw.setPenColor(StdDraw.BLACK); 1097 StdDraw.text(0.2, 0.5, "black text"); 1098 StdDraw.setPenColor(StdDraw.WHITE); 1099 StdDraw.text(0.8, 0.8, "white text"); 1100 } 1101 1102}