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 private static JMenuBar createMenuBar() { 207 JMenuBar menuBar = new JMenuBar(); 208 JMenu menu = new JMenu("File"); 209 menuBar.add(menu); 210 JMenuItem menuItem1 = new JMenuItem(" Save... "); 211 menuItem1.addActionListener(std); 212 menuItem1.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, 213 Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); 214 menu.add(menuItem1); 215 return menuBar; 216 } 217 218 219 /* *********************************************************************** 220 * User and screen coordinate systems 221 *************************************************************************/ 222 223 /** 224 * Set the x-scale to be the default (between 0.0 and 1.0). 225 */ 226 public static void setXscale() { setXscale(DEFAULT_XMIN, DEFAULT_XMAX); } 227 228 /** 229 * Set the y-scale to be the default (between 0.0 and 1.0). 230 */ 231 public static void setYscale() { setYscale(DEFAULT_YMIN, DEFAULT_YMAX); } 232 233 /** 234 * Set the x-scale (a 10% border is added to the values) 235 * @param min the minimum value of the x-scale 236 * @param max the maximum value of the x-scale 237 */ 238 public static void setXscale(double min, double max) { 239 double size = max - min; 240 synchronized (mouseLock) { 241 xmin = min - BORDER * size; 242 xmax = max + BORDER * size; 243 } 244 } 245 246 /** 247 * Set the y-scale (a 10% border is added to the values). 248 * @param min the minimum value of the y-scale 249 * @param max the maximum value of the y-scale 250 */ 251 public static void setYscale(double min, double max) { 252 double size = max - min; 253 synchronized (mouseLock) { 254 ymin = min - BORDER * size; 255 ymax = max + BORDER * size; 256 } 257 } 258 259 /** 260 * Set the x-scale and y-scale (a 10% border is added to the values) 261 * @param min the minimum value of the x- and y-scales 262 * @param max the maximum value of the x- and y-scales 263 */ 264 public static void setScale(double min, double max) { 265 double size = max - min; 266 synchronized (mouseLock) { 267 xmin = min - BORDER * size; 268 xmax = max + BORDER * size; 269 ymin = min - BORDER * size; 270 ymax = max + BORDER * size; 271 } 272 } 273 274 // helper functions that scale from user coordinates to screen coordinates and back 275 private static double scaleX(double x) { return width * (x - xmin) / (xmax - xmin); } 276 private static double scaleY(double y) { return height * (ymax - y) / (ymax - ymin); } 277 private static double factorX(double w) { return w * width / Math.abs(xmax - xmin); } 278 private static double factorY(double h) { return h * height / Math.abs(ymax - ymin); } 279 private static double userX(double x) { return xmin + x * (xmax - xmin) / width; } 280 private static double userY(double y) { return ymax - y * (ymax - ymin) / height; } 281 282 283 /** 284 * Clear the screen to the default color (white). 285 */ 286 public static void clear() { clear(DEFAULT_CLEAR_COLOR); } 287 /** 288 * Clear the screen to the given color. 289 * @param color the Color to make the background 290 */ 291 public static void clear(Color color) { 292 offscreen.setColor(color); 293 offscreen.fillRect(0, 0, width, height); 294 offscreen.setColor(penColor); 295 draw(); 296 } 297 298 /** 299 * Get the current pen radius. 300 */ 301 public static double getPenRadius() { return penRadius; } 302 303 /** 304 * Set the pen size to the default (.002). 305 */ 306 public static void setPenRadius() { setPenRadius(DEFAULT_PEN_RADIUS); } 307 /** 308 * Set the radius of the pen to the given size. 309 * @param r the radius of the pen 310 * @throws java.lang.RuntimeException if r is negative 311 */ 312 public static void setPenRadius(double r) { 313 if (r < 0) throw new RuntimeException("pen radius must be positive"); 314 penRadius = r; 315 float scaledPenRadius = (float) (r * DEFAULT_SIZE); 316 BasicStroke stroke = new BasicStroke(scaledPenRadius, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND); 317 // BasicStroke stroke = new BasicStroke(scaledPenRadius); 318 offscreen.setStroke(stroke); 319 } 320 321 /** 322 * Get the current pen color. 323 */ 324 public static Color getPenColor() { return penColor; } 325 326 /** 327 * Set the pen color to the default color (black). 328 */ 329 public static void setPenColor() { setPenColor(DEFAULT_PEN_COLOR); } 330 /** 331 * Set the pen color to the given color. The available pen colors are 332 * BLACK, BLUE, CYAN, DARK_GRAY, GRAY, GREEN, LIGHT_GRAY, MAGENTA, 333 * ORANGE, PINK, RED, WHITE, and YELLOW. 334 * @param color the Color to make the pen 335 */ 336 public static void setPenColor(Color color) { 337 penColor = color; 338 offscreen.setColor(penColor); 339 } 340 341 /** 342 * Get the current font. 343 */ 344 public static Font getFont() { return font; } 345 346 /** 347 * Set the font to the default font (sans serif, 16 point). 348 */ 349 public static void setFont() { setFont(DEFAULT_FONT); } 350 351 /** 352 * Set the font to the given value. 353 * @param f the font to make text 354 */ 355 public static void setFont(Font f) { font = f; } 356 357 358 /* *********************************************************************** 359 * Drawing geometric shapes. 360 *************************************************************************/ 361 362 /** 363 * Draw a line from (x0, y0) to (x1, y1). 364 * @param x0 the x-coordinate of the starting point 365 * @param y0 the y-coordinate of the starting point 366 * @param x1 the x-coordinate of the destination point 367 * @param y1 the y-coordinate of the destination point 368 */ 369 public static void line(double x0, double y0, double x1, double y1) { 370 offscreen.draw(new Line2D.Double(scaleX(x0), scaleY(y0), scaleX(x1), scaleY(y1))); 371 draw(); 372 } 373 374 /** 375 * Draw one pixel at (x, y). 376 * @param x the x-coordinate of the pixel 377 * @param y the y-coordinate of the pixel 378 */ 379 private static void pixel(double x, double y) { 380 offscreen.fillRect((int) Math.round(scaleX(x)), (int) Math.round(scaleY(y)), 1, 1); 381 } 382 383 /** 384 * Draw a point at (x, y). 385 * @param x the x-coordinate of the point 386 * @param y the y-coordinate of the point 387 */ 388 public static void point(double x, double y) { 389 double xs = scaleX(x); 390 double ys = scaleY(y); 391 double r = penRadius; 392 float scaledPenRadius = (float) (r * DEFAULT_SIZE); 393 394 // double ws = factorX(2*r); 395 // double hs = factorY(2*r); 396 // if (ws <= 1 && hs <= 1) pixel(x, y); 397 if (scaledPenRadius <= 1) pixel(x, y); 398 else offscreen.fill(new Ellipse2D.Double(xs - scaledPenRadius/2, ys - scaledPenRadius/2, 399 scaledPenRadius, scaledPenRadius)); 400 draw(); 401 } 402 403 /** 404 * Draw a circle of radius r, centered on (x, y). 405 * @param x the x-coordinate of the center of the circle 406 * @param y the y-coordinate of the center of the circle 407 * @param r the radius of the circle 408 * @throws java.lang.RuntimeException if the radius of the circle is negative 409 */ 410 public static void circle(double x, double y, double r) { 411 if (r < 0) throw new RuntimeException("circle radius can't be negative"); 412 double xs = scaleX(x); 413 double ys = scaleY(y); 414 double ws = factorX(2*r); 415 double hs = factorY(2*r); 416 if (ws <= 1 && hs <= 1) pixel(x, y); 417 else offscreen.draw(new Ellipse2D.Double(xs - ws/2, ys - hs/2, ws, hs)); 418 draw(); 419 } 420 421 /** 422 * Draw filled circle of radius r, centered on (x, y). 423 * @param x the x-coordinate of the center of the circle 424 * @param y the y-coordinate of the center of the circle 425 * @param r the radius of the circle 426 * @throws java.lang.RuntimeException if the radius of the circle is negative 427 */ 428 public static void filledCircle(double x, double y, double r) { 429 if (r < 0) throw new RuntimeException("circle radius can't be negative"); 430 double xs = scaleX(x); 431 double ys = scaleY(y); 432 double ws = factorX(2*r); 433 double hs = factorY(2*r); 434 if (ws <= 1 && hs <= 1) pixel(x, y); 435 else offscreen.fill(new Ellipse2D.Double(xs - ws/2, ys - hs/2, ws, hs)); 436 draw(); 437 } 438 439 440 /** 441 * Draw an ellipse with given semimajor and semiminor axes, centered on (x, y). 442 * @param x the x-coordinate of the center of the ellipse 443 * @param y the y-coordinate of the center of the ellipse 444 * @param semiMajorAxis is the semimajor axis of the ellipse 445 * @param semiMinorAxis is the semiminor axis of the ellipse 446 * @throws java.lang.RuntimeException if either of the axes are negative 447 */ 448 public static void ellipse(double x, double y, double semiMajorAxis, double semiMinorAxis) { 449 if (semiMajorAxis < 0) throw new RuntimeException("ellipse semimajor axis can't be negative"); 450 if (semiMinorAxis < 0) throw new RuntimeException("ellipse semiminor axis can't be negative"); 451 double xs = scaleX(x); 452 double ys = scaleY(y); 453 double ws = factorX(2*semiMajorAxis); 454 double hs = factorY(2*semiMinorAxis); 455 if (ws <= 1 && hs <= 1) pixel(x, y); 456 else offscreen.draw(new Ellipse2D.Double(xs - ws/2, ys - hs/2, ws, hs)); 457 draw(); 458 } 459 460 /** 461 * Draw an ellipse with given semimajor and semiminor axes, centered on (x, y). 462 * @param x the x-coordinate of the center of the ellipse 463 * @param y the y-coordinate of the center of the ellipse 464 * @param semiMajorAxis is the semimajor axis of the ellipse 465 * @param semiMinorAxis is the semiminor axis of the ellipse 466 * @throws java.lang.RuntimeException if either of the axes are negative 467 */ 468 public static void filledEllipse(double x, double y, double semiMajorAxis, double semiMinorAxis) { 469 if (semiMajorAxis < 0) throw new RuntimeException("ellipse semimajor axis can't be negative"); 470 if (semiMinorAxis < 0) throw new RuntimeException("ellipse semiminor axis can't be negative"); 471 double xs = scaleX(x); 472 double ys = scaleY(y); 473 double ws = factorX(2*semiMajorAxis); 474 double hs = factorY(2*semiMinorAxis); 475 if (ws <= 1 && hs <= 1) pixel(x, y); 476 else offscreen.fill(new Ellipse2D.Double(xs - ws/2, ys - hs/2, ws, hs)); 477 draw(); 478 } 479 480 481 /** 482 * Draw an arc of radius r, centered on (x, y), from angle1 to angle2 (in degrees). 483 * @param x the x-coordinate of the center of the circle 484 * @param y the y-coordinate of the center of the circle 485 * @param r the radius of the circle 486 * @param angle1 the starting angle. 0 would mean an arc beginning at 3 o'clock. 487 * @param angle2 the angle at the end of the arc. For example, if 488 * you want a 90 degree arc, then angle2 should be angle1 + 90. 489 * @throws java.lang.RuntimeException if the radius of the circle is negative 490 */ 491 public static void arc(double x, double y, double r, double angle1, double angle2) { 492 if (r < 0) throw new RuntimeException("arc radius can't be negative"); 493 while (angle2 < angle1) angle2 += 360; 494 double xs = scaleX(x); 495 double ys = scaleY(y); 496 double ws = factorX(2*r); 497 double hs = factorY(2*r); 498 if (ws <= 1 && hs <= 1) pixel(x, y); 499 else offscreen.draw(new Arc2D.Double(xs - ws/2, ys - hs/2, ws, hs, angle1, angle2 - angle1, Arc2D.OPEN)); 500 draw(); 501 } 502 503 /** 504 * Draw a square of side length 2r, centered on (x, y). 505 * @param x the x-coordinate of the center of the square 506 * @param y the y-coordinate of the center of the square 507 * @param r radius is half the length of any side of the square 508 * @throws java.lang.RuntimeException if r is negative 509 */ 510 public static void square(double x, double y, double r) { 511 if (r < 0) throw new RuntimeException("square side length can't be negative"); 512 double xs = scaleX(x); 513 double ys = scaleY(y); 514 double ws = factorX(2*r); 515 double hs = factorY(2*r); 516 if (ws <= 1 && hs <= 1) pixel(x, y); 517 else offscreen.draw(new Rectangle2D.Double(xs - ws/2, ys - hs/2, ws, hs)); 518 draw(); 519 } 520 521 /** 522 * Draw a filled square of side length 2r, centered on (x, y). 523 * @param x the x-coordinate of the center of the square 524 * @param y the y-coordinate of the center of the square 525 * @param r radius is half the length of any side of the square 526 * @throws java.lang.RuntimeException if r is negative 527 */ 528 public static void filledSquare(double x, double y, double r) { 529 if (r < 0) throw new RuntimeException("square side length can't be negative"); 530 double xs = scaleX(x); 531 double ys = scaleY(y); 532 double ws = factorX(2*r); 533 double hs = factorY(2*r); 534 if (ws <= 1 && hs <= 1) pixel(x, y); 535 else offscreen.fill(new Rectangle2D.Double(xs - ws/2, ys - hs/2, ws, hs)); 536 draw(); 537 } 538 539 540 /** 541 * Draw a rectangle of given half width and half height, centered on (x, y). 542 * @param x the x-coordinate of the center of the rectangle 543 * @param y the y-coordinate of the center of the rectangle 544 * @param halfWidth is half the width of the rectangle 545 * @param halfHeight is half the height of the rectangle 546 * @throws java.lang.RuntimeException if halfWidth or halfHeight is negative 547 */ 548 public static void rectangle(double x, double y, double halfWidth, double halfHeight) { 549 if (halfWidth < 0) throw new RuntimeException("half width can't be negative"); 550 if (halfHeight < 0) throw new RuntimeException("half height can't be negative"); 551 double xs = scaleX(x); 552 double ys = scaleY(y); 553 double ws = factorX(2*halfWidth); 554 double hs = factorY(2*halfHeight); 555 if (ws <= 1 && hs <= 1) pixel(x, y); 556 else offscreen.draw(new Rectangle2D.Double(xs - ws/2, ys - hs/2, ws, hs)); 557 draw(); 558 } 559 560 /** 561 * Draw a filled rectangle of given half width and half height, centered on (x, y). 562 * @param x the x-coordinate of the center of the rectangle 563 * @param y the y-coordinate of the center of the rectangle 564 * @param halfWidth is half the width of the rectangle 565 * @param halfHeight is half the height of the rectangle 566 * @throws java.lang.RuntimeException if halfWidth or halfHeight is negative 567 */ 568 public static void filledRectangle(double x, double y, double halfWidth, double halfHeight) { 569 if (halfWidth < 0) throw new RuntimeException("half width can't be negative"); 570 if (halfHeight < 0) throw new RuntimeException("half height can't be negative"); 571 double xs = scaleX(x); 572 double ys = scaleY(y); 573 double ws = factorX(2*halfWidth); 574 double hs = factorY(2*halfHeight); 575 if (ws <= 1 && hs <= 1) pixel(x, y); 576 else offscreen.fill(new Rectangle2D.Double(xs - ws/2, ys - hs/2, ws, hs)); 577 draw(); 578 } 579 580 581 /** 582 * Draw a polygon with the given (x[i], y[i]) coordinates. 583 * @param x an array of all the x-coordindates of the polygon 584 * @param y an array of all the y-coordindates of the polygon 585 */ 586 public static void polygon(double[] x, double[] y) { 587 int N = x.length; 588 GeneralPath path = new GeneralPath(); 589 path.moveTo((float) scaleX(x[0]), (float) scaleY(y[0])); 590 for (int i = 0; i < N; i++) 591 path.lineTo((float) scaleX(x[i]), (float) scaleY(y[i])); 592 path.closePath(); 593 offscreen.draw(path); 594 draw(); 595 } 596 597 /** 598 * Draw a filled polygon with the given (x[i], y[i]) coordinates. 599 * @param x an array of all the x-coordindates of the polygon 600 * @param y an array of all the y-coordindates of the polygon 601 */ 602 public static void filledPolygon(double[] x, double[] y) { 603 int N = x.length; 604 GeneralPath path = new GeneralPath(); 605 path.moveTo((float) scaleX(x[0]), (float) scaleY(y[0])); 606 for (int i = 0; i < N; i++) 607 path.lineTo((float) scaleX(x[i]), (float) scaleY(y[i])); 608 path.closePath(); 609 offscreen.fill(path); 610 draw(); 611 } 612 613 614 615 /* *********************************************************************** 616 * Drawing images. 617 *************************************************************************/ 618 619 // get an image from the given filename 620 private static Image getImage(String filename) { 621 622 // to read from file 623 ImageIcon icon = new ImageIcon(filename); 624 625 // try to read from URL 626 if ((icon == null) || (icon.getImageLoadStatus() != MediaTracker.COMPLETE)) { 627 try { 628 URL url = new java.net.URI(filename).toURL(); 629 icon = new ImageIcon(url); 630 } catch (Exception e) { /* not a url */ } 631 } 632 633 // in case file is inside a .jar 634 if ((icon == null) || (icon.getImageLoadStatus() != MediaTracker.COMPLETE)) { 635 URL url = StdDraw.class.getResource(filename); 636 if (url == null) throw new RuntimeException("image " + filename + " not found"); 637 icon = new ImageIcon(url); 638 } 639 640 return icon.getImage(); 641 } 642 643 /** 644 * Draw picture (gif, jpg, or png) centered on (x, y). 645 * @param x the center x-coordinate of the image 646 * @param y the center y-coordinate of the image 647 * @param s the name of the image/picture, e.g., "ball.gif" 648 * @throws java.lang.RuntimeException if the image is corrupt 649 */ 650 public static void picture(double x, double y, String s) { 651 Image image = getImage(s); 652 double xs = scaleX(x); 653 double ys = scaleY(y); 654 int ws = image.getWidth(null); 655 int hs = image.getHeight(null); 656 if (ws < 0 || hs < 0) throw new RuntimeException("image " + s + " is corrupt"); 657 658 offscreen.drawImage(image, (int) Math.round(xs - ws/2.0), (int) Math.round(ys - hs/2.0), null); 659 draw(); 660 } 661 662 /** 663 * Draw picture (gif, jpg, or png) centered on (x, y), 664 * rotated given number of degrees 665 * @param x the center x-coordinate of the image 666 * @param y the center y-coordinate of the image 667 * @param s the name of the image/picture, e.g., "ball.gif" 668 * @param degrees is the number of degrees to rotate counterclockwise 669 * @throws java.lang.RuntimeException if the image is corrupt 670 */ 671 public static void picture(double x, double y, String s, double degrees) { 672 Image image = getImage(s); 673 double xs = scaleX(x); 674 double ys = scaleY(y); 675 int ws = image.getWidth(null); 676 int hs = image.getHeight(null); 677 if (ws < 0 || hs < 0) throw new RuntimeException("image " + s + " is corrupt"); 678 679 offscreen.rotate(Math.toRadians(-degrees), xs, ys); 680 offscreen.drawImage(image, (int) Math.round(xs - ws/2.0), (int) Math.round(ys - hs/2.0), null); 681 offscreen.rotate(Math.toRadians(+degrees), xs, ys); 682 683 draw(); 684 } 685 686 /** 687 * Draw picture (gif, jpg, or png) centered on (x, y), rescaled to w-by-h. 688 * @param x the center x coordinate of the image 689 * @param y the center y coordinate of the image 690 * @param s the name of the image/picture, e.g., "ball.gif" 691 * @param w the width of the image 692 * @param h the height of the image 693 * @throws java.lang.RuntimeException if the width height are negative 694 * @throws java.lang.RuntimeException if the image is corrupt 695 */ 696 public static void picture(double x, double y, String s, double w, double h) { 697 Image image = getImage(s); 698 double xs = scaleX(x); 699 double ys = scaleY(y); 700 if (w < 0) throw new RuntimeException("width is negative: " + w); 701 if (h < 0) throw new RuntimeException("height is negative: " + h); 702 double ws = factorX(w); 703 double hs = factorY(h); 704 if (ws < 0 || hs < 0) throw new RuntimeException("image " + s + " is corrupt"); 705 if (ws <= 1 && hs <= 1) pixel(x, y); 706 else { 707 offscreen.drawImage(image, (int) Math.round(xs - ws/2.0), 708 (int) Math.round(ys - hs/2.0), 709 (int) Math.round(ws), 710 (int) Math.round(hs), null); 711 } 712 draw(); 713 } 714 715 716 /** 717 * Draw picture (gif, jpg, or png) centered on (x, y), rotated 718 * given number of degrees, rescaled to w-by-h. 719 * @param x the center x-coordinate of the image 720 * @param y the center y-coordinate of the image 721 * @param s the name of the image/picture, e.g., "ball.gif" 722 * @param w the width of the image 723 * @param h the height of the image 724 * @param degrees is the number of degrees to rotate counterclockwise 725 * @throws java.lang.RuntimeException if the image is corrupt 726 */ 727 public static void picture(double x, double y, String s, double w, double h, double degrees) { 728 Image image = getImage(s); 729 double xs = scaleX(x); 730 double ys = scaleY(y); 731 double ws = factorX(w); 732 double hs = factorY(h); 733 if (ws < 0 || hs < 0) throw new RuntimeException("image " + s + " is corrupt"); 734 if (ws <= 1 && hs <= 1) pixel(x, y); 735 736 offscreen.rotate(Math.toRadians(-degrees), xs, ys); 737 offscreen.drawImage(image, (int) Math.round(xs - ws/2.0), 738 (int) Math.round(ys - hs/2.0), 739 (int) Math.round(ws), 740 (int) Math.round(hs), null); 741 offscreen.rotate(Math.toRadians(+degrees), xs, ys); 742 743 draw(); 744 } 745 746 747 /* *********************************************************************** 748 * Drawing text. 749 *************************************************************************/ 750 751 /** 752 * Write the given text string in the current font, centered on (x, y). 753 * @param x the center x-coordinate of the text 754 * @param y the center y-coordinate of the text 755 * @param s the text 756 */ 757 public static void text(double x, double y, String s) { 758 offscreen.setFont(font); 759 FontMetrics metrics = offscreen.getFontMetrics(); 760 double xs = scaleX(x); 761 double ys = scaleY(y); 762 int ws = metrics.stringWidth(s); 763 int hs = metrics.getDescent(); 764 offscreen.drawString(s, (float) (xs - ws/2.0), (float) (ys + hs)); 765 draw(); 766 } 767 768 /** 769 * Write the given text string in the current font, centered on (x, y) and 770 * rotated by the specified number of degrees 771 * @param x the center x-coordinate of the text 772 * @param y the center y-coordinate of the text 773 * @param s the text 774 * @param degrees is the number of degrees to rotate counterclockwise 775 */ 776 public static void text(double x, double y, String s, double degrees) { 777 double xs = scaleX(x); 778 double ys = scaleY(y); 779 offscreen.rotate(Math.toRadians(-degrees), xs, ys); 780 text(x, y, s); 781 offscreen.rotate(Math.toRadians(+degrees), xs, ys); 782 } 783 784 785 /** 786 * Write the given text string in the current font, left-aligned at (x, y). 787 * @param x the x-coordinate of the text 788 * @param y the y-coordinate of the text 789 * @param s the text 790 */ 791 public static void textLeft(double x, double y, String s) { 792 offscreen.setFont(font); 793 FontMetrics metrics = offscreen.getFontMetrics(); 794 double xs = scaleX(x); 795 double ys = scaleY(y); 796 int hs = metrics.getDescent(); 797 offscreen.drawString(s, (float) (xs), (float) (ys + hs)); 798 draw(); 799 } 800 801 /** 802 * Write the given text string in the current font, right-aligned at (x, y). 803 * @param x the x-coordinate of the text 804 * @param y the y-coordinate of the text 805 * @param s the text 806 */ 807 public static void textRight(double x, double y, String s) { 808 offscreen.setFont(font); 809 FontMetrics metrics = offscreen.getFontMetrics(); 810 double xs = scaleX(x); 811 double ys = scaleY(y); 812 int ws = metrics.stringWidth(s); 813 int hs = metrics.getDescent(); 814 offscreen.drawString(s, (float) (xs - ws), (float) (ys + hs)); 815 draw(); 816 } 817 818 819 820 /** 821 * Display on screen, pause for t milliseconds, and turn on 822 * <em>animation mode</em>: subsequent calls to 823 * drawing methods such as {@code line()}, {@code circle()}, and {@code square()} 824 * will not be displayed on screen until the next call to {@code show()}. 825 * This is useful for producing animations (clear the screen, draw a bunch of shapes, 826 * display on screen for a fixed amount of time, and repeat). It also speeds up 827 * drawing a huge number of shapes (call {@code show(0)} to defer drawing 828 * on screen, draw the shapes, and call {@code show(0)} to display them all 829 * on screen at once). 830 * @param t number of milliseconds 831 */ 832 public static void show(int t) { 833 defer = false; 834 draw(); 835 try { Thread.sleep(t); } 836 catch (InterruptedException e) { System.out.println("Error sleeping"); } 837 defer = true; 838 } 839 840 /** 841 * Display on-screen and turn off animation mode: 842 * subsequent calls to 843 * drawing methods such as {@code line()}, {@code circle()}, and {@code square()} 844 * will be displayed on screen when called. This is the default. 845 */ 846 public static void show() { 847 defer = false; 848 draw(); 849 } 850 851 // draw onscreen if defer is false 852 private static void draw() { 853 if (defer) return; 854 onscreen.drawImage(offscreenImage, 0, 0, null); 855 frame.repaint(); 856 } 857 858 859 /* *********************************************************************** 860 * Save drawing to a file. 861 *************************************************************************/ 862 863 /** 864 * Save onscreen image to file - suffix must be png, jpg, or gif. 865 * @param filename the name of the file with one of the required suffixes 866 */ 867 public static void save(String filename) { 868 File file = new File(filename); 869 String suffix = filename.substring(filename.lastIndexOf('.') + 1); 870 871 // png files 872 if (suffix.toLowerCase().equals("png")) { 873 try { ImageIO.write(onscreenImage, suffix, file); } 874 catch (IOException e) { e.printStackTrace(); } 875 } 876 877 // need to change from ARGB to RGB for jpeg 878 // reference: http://archives.java.sun.com/cgi-bin/wa?A2=ind0404&L=java2d-interest&D=0&P=2727 879 else if (suffix.toLowerCase().equals("jpg")) { 880 WritableRaster raster = onscreenImage.getRaster(); 881 WritableRaster newRaster; 882 newRaster = raster.createWritableChild(0, 0, width, height, 0, 0, new int[] {0, 1, 2}); 883 DirectColorModel cm = (DirectColorModel) onscreenImage.getColorModel(); 884 DirectColorModel newCM = new DirectColorModel(cm.getPixelSize(), 885 cm.getRedMask(), 886 cm.getGreenMask(), 887 cm.getBlueMask()); 888 BufferedImage rgbBuffer = new BufferedImage(newCM, newRaster, false, null); 889 try { ImageIO.write(rgbBuffer, suffix, file); } 890 catch (IOException e) { e.printStackTrace(); } 891 } 892 893 else { 894 System.out.println("Invalid image file type: " + suffix); 895 } 896 } 897 898 899 /** 900 * This method cannot be called directly. 901 */ 902 public void actionPerformed(ActionEvent e) { 903 FileDialog chooser = new FileDialog(StdDraw.frame, "Use a .png or .jpg extension", FileDialog.SAVE); 904 chooser.setVisible(true); 905 String filename = chooser.getFile(); 906 if (filename != null) { 907 StdDraw.save(chooser.getDirectory() + File.separator + chooser.getFile()); 908 } 909 } 910 911 912 /* *********************************************************************** 913 * Mouse interactions. 914 *************************************************************************/ 915 916 /** 917 * Is the mouse being pressed? 918 * @return true or false 919 */ 920 public static boolean mousePressed() { 921 synchronized (mouseLock) { 922 return mousePressed; 923 } 924 } 925 926 /** 927 * What is the x-coordinate of the mouse? 928 * @return the value of the x-coordinate of the mouse 929 */ 930 public static double mouseX() { 931 synchronized (mouseLock) { 932 return mouseX; 933 } 934 } 935 936 /** 937 * What is the y-coordinate of the mouse? 938 * @return the value of the y-coordinate of the mouse 939 */ 940 public static double mouseY() { 941 synchronized (mouseLock) { 942 return mouseY; 943 } 944 } 945 946 947 /** 948 * This method cannot be called directly. 949 */ 950 public void mouseClicked(MouseEvent e) { } 951 952 /** 953 * This method cannot be called directly. 954 */ 955 public void mouseEntered(MouseEvent e) { } 956 957 /** 958 * This method cannot be called directly. 959 */ 960 public void mouseExited(MouseEvent e) { } 961 962 /** 963 * This method cannot be called directly. 964 */ 965 public void mousePressed(MouseEvent e) { 966 synchronized (mouseLock) { 967 mouseX = StdDraw.userX(e.getX()); 968 mouseY = StdDraw.userY(e.getY()); 969 mousePressed = true; 970 } 971 } 972 973 /** 974 * This method cannot be called directly. 975 */ 976 public void mouseReleased(MouseEvent e) { 977 synchronized (mouseLock) { 978 mousePressed = false; 979 } 980 } 981 982 /** 983 * This method cannot be called directly. 984 */ 985 public void mouseDragged(MouseEvent e) { 986 synchronized (mouseLock) { 987 mouseX = StdDraw.userX(e.getX()); 988 mouseY = StdDraw.userY(e.getY()); 989 } 990 } 991 992 /** 993 * This method cannot be called directly. 994 */ 995 public void mouseMoved(MouseEvent e) { 996 synchronized (mouseLock) { 997 mouseX = StdDraw.userX(e.getX()); 998 mouseY = StdDraw.userY(e.getY()); 999 } 1000 } 1001 1002 1003 /* *********************************************************************** 1004 * Keyboard interactions. 1005 *************************************************************************/ 1006 1007 /** 1008 * Has the user typed a key? 1009 * @return true if the user has typed a key, false otherwise 1010 */ 1011 public static boolean hasNextKeyTyped() { 1012 synchronized (keyLock) { 1013 return !keysTyped.isEmpty(); 1014 } 1015 } 1016 1017 /** 1018 * What is the next key that was typed by the user? This method returns 1019 * a Unicode character corresponding to the key typed (such as 'a' or 'A'). 1020 * It cannot identify action keys (such as F1 1021 * and arrow keys) or modifier keys (such as control). 1022 * @return the next Unicode key typed 1023 */ 1024 public static char nextKeyTyped() { 1025 synchronized (keyLock) { 1026 return keysTyped.removeLast(); 1027 } 1028 } 1029 1030 /** 1031 * Is the keycode currently being pressed? This method takes as an argument 1032 * the keycode (corresponding to a physical key). It can handle action keys 1033 * (such as F1 and arrow keys) and modifier keys (such as shift and control). 1034 * See <a href = "http://download.oracle.com/javase/6/docs/api/java/awt/event/KeyEvent.html">KeyEvent.java</a> 1035 * for a description of key codes. 1036 * @return true if keycode is currently being pressed, false otherwise 1037 */ 1038 public static boolean isKeyPressed(int keycode) { 1039 synchronized (keyLock) { 1040 return keysDown.contains(keycode); 1041 } 1042 } 1043 1044 1045 /** 1046 * This method cannot be called directly. 1047 */ 1048 public void keyTyped(KeyEvent e) { 1049 synchronized (keyLock) { 1050 keysTyped.addFirst(e.getKeyChar()); 1051 } 1052 } 1053 1054 /** 1055 * This method cannot be called directly. 1056 */ 1057 public void keyPressed(KeyEvent e) { 1058 synchronized (keyLock) { 1059 keysDown.add(e.getKeyCode()); 1060 } 1061 } 1062 1063 /** 1064 * This method cannot be called directly. 1065 */ 1066 public void keyReleased(KeyEvent e) { 1067 synchronized (keyLock) { 1068 keysDown.remove(e.getKeyCode()); 1069 } 1070 } 1071 1072 1073 1074 1075 /** 1076 * Test client. 1077 */ 1078 public static void main(String[] args) { 1079 StdDraw.square(.2, .8, .1); 1080 StdDraw.filledSquare(.8, .8, .2); 1081 StdDraw.circle(.8, .2, .2); 1082 1083 StdDraw.setPenColor(StdDraw.BOOK_RED); 1084 StdDraw.setPenRadius(.02); 1085 StdDraw.arc(.8, .2, .1, 200, 45); 1086 1087 // draw a blue diamond 1088 StdDraw.setPenRadius(); 1089 StdDraw.setPenColor(StdDraw.BOOK_BLUE); 1090 double[] x = { .1, .2, .3, .2 }; 1091 double[] y = { .2, .3, .2, .1 }; 1092 StdDraw.filledPolygon(x, y); 1093 1094 // text 1095 StdDraw.setPenColor(StdDraw.BLACK); 1096 StdDraw.text(0.2, 0.5, "black text"); 1097 StdDraw.setPenColor(StdDraw.WHITE); 1098 StdDraw.text(0.8, 0.8, "white text"); 1099 } 1100 1101}