· 6 years ago · Mar 21, 2020, 09:42 AM
1/******************************************************************************
2 * Compilation: javac StdDraw.java
3 * Execution: java StdDraw
4 * Dependencies: none
5 *
6 * Standard drawing library. This class provides a basic capability for
7 * creating drawings with your programs. It uses a simple graphics model that
8 * allows you to create drawings consisting of points, lines, and curves
9 * in a window on your computer and to save the drawings to a file.
10 *
11 * Todo
12 * ----
13 * - Add support for gradient fill, etc.
14 * - Fix setCanvasSize() so that it can only be called once.
15 * - On some systems, drawing a line (or other shape) that extends way
16 * beyond canvas (e.g., to infinity) dimensions does not get drawn.
17 *
18 * Remarks
19 * -------
20 * - don't use AffineTransform for rescaling since it inverts
21 * images and strings
22 *
23 ******************************************************************************/
24
25import javax.imageio.ImageIO;
26import javax.swing.*;
27import java.awt.*;
28import java.awt.event.*;
29import java.awt.geom.*;
30import java.awt.image.BufferedImage;
31import java.awt.image.DirectColorModel;
32import java.awt.image.WritableRaster;
33import java.io.File;
34import java.io.IOException;
35import java.net.MalformedURLException;
36import java.net.URL;
37import java.util.LinkedList;
38import java.util.NoSuchElementException;
39import java.util.TreeSet;
40
41/**
42 * The {@code StdDraw} class provides a basic capability for
43 * creating drawings with your programs. It uses a simple graphics model that
44 * allows you to create drawings consisting of points, lines, squares,
45 * circles, and other geometric shapes in a window on your computer and
46 * to save the drawings to a file. Standard drawing also includes
47 * facilities for text, color, pictures, and animation, along with
48 * user interaction via the keyboard and mouse.
49 * <p>
50 * <b>Getting started.</b>
51 * To use this class, you must have {@code StdDraw.class} in your
52 * Java classpath. If you used our autoinstaller, you should be all set.
53 * Otherwise, either download
54 * <a href = "https://introcs.cs.princeton.edu/java/code/stdlib.jar">stdlib.jar</a>
55 * and add to your Java classpath or download
56 * <a href = "https://introcs.cs.princeton.edu/java/stdlib/StdDraw.java">StdDraw.java</a>
57 * and put a copy in your working directory.
58 * <p>
59 * Now, type the following short program into your editor:
60 * <pre>
61 * public class TestStdDraw {
62 * public static void main(String[] args) {
63 * StdDraw.setPenRadius(0.05);
64 * StdDraw.setPenColor(StdDraw.BLUE);
65 * StdDraw.point(0.5, 0.5);
66 * StdDraw.setPenColor(StdDraw.MAGENTA);
67 * StdDraw.line(0.2, 0.2, 0.8, 0.2);
68 * }
69 * }
70 * </pre>
71 * If you compile and execute the program, you should see a window
72 * appear with a thick magenta line and a blue point.
73 * This program illustrates the two main types of methods in standard
74 * drawing—methods that draw geometric shapes and methods that
75 * control drawing parameters.
76 * The methods {@code StdDraw.line()} and {@code StdDraw.point()}
77 * draw lines and points; the methods {@code StdDraw.setPenRadius()}
78 * and {@code StdDraw.setPenColor()} control the line thickness and color.
79 * <p>
80 * <b>Points and lines.</b>
81 * You can draw points and line segments with the following methods:
82 * <ul>
83 * <li> {@link #point(double x, double y)}
84 * <li> {@link #line(double x1, double y1, double x2, double y2)}
85 * </ul>
86 * <p>
87 * The <em>x</em>- and <em>y</em>-coordinates must be in the drawing area
88 * (between 0 and 1 and by default) or the points and lines will not be visible.
89 * <p>
90 * <b>Squares, circles, rectangles, and ellipses.</b>
91 * You can draw squares, circles, rectangles, and ellipses using
92 * the following methods:
93 * <ul>
94 * <li> {@link #circle(double x, double y, double radius)}
95 * <li> {@link #ellipse(double x, double y, double semiMajorAxis, double semiMinorAxis)}
96 * <li> {@link #square(double x, double y, double radius)}
97 * <li> {@link #rectangle(double x, double y, double halfWidth, double halfHeight)}
98 * </ul>
99 * <p>
100 * All of these methods take as arguments the location and size of the shape.
101 * The location is always specified by the <em>x</em>- and <em>y</em>-coordinates
102 * of its <em>center</em>.
103 * The size of a circle is specified by its radius and the size of an ellipse is
104 * specified by the lengths of its semi-major and semi-minor axes.
105 * The size of a square or rectangle is specified by its half-width or half-height.
106 * The convention for drawing squares and rectangles is parallel to those for
107 * drawing circles and ellipses, but may be unexpected to the uninitiated.
108 * <p>
109 * The methods above trace outlines of the given shapes. The following methods
110 * draw filled versions:
111 * <ul>
112 * <li> {@link #filledCircle(double x, double y, double radius)}
113 * <li> {@link #filledEllipse(double x, double y, double semiMajorAxis, double semiMinorAxis)}
114 * <li> {@link #filledSquare(double x, double y, double radius)}
115 * <li> {@link #filledRectangle(double x, double y, double halfWidth, double halfHeight)}
116 * </ul>
117 * <p>
118 * <b>Circular arcs.</b>
119 * You can draw circular arcs with the following method:
120 * <ul>
121 * <li> {@link #arc(double x, double y, double radius, double angle1, double angle2)}
122 * </ul>
123 * <p>
124 * The arc is from the circle centered at (<em>x</em>, <em>y</em>) of the specified radius.
125 * The arc extends from angle1 to angle2. By convention, the angles are
126 * <em>polar</em> (counterclockwise angle from the <em>x</em>-axis)
127 * and represented in degrees. For example, {@code StdDraw.arc(0.0, 0.0, 1.0, 0, 90)}
128 * draws the arc of the unit circle from 3 o'clock (0 degrees) to 12 o'clock (90 degrees).
129 * <p>
130 * <b>Polygons.</b>
131 * You can draw polygons with the following methods:
132 * <ul>
133 * <li> {@link #polygon(double[] x, double[] y)}
134 * <li> {@link #filledPolygon(double[] x, double[] y)}
135 * </ul>
136 * <p>
137 * The points in the polygon are ({@code x[i]}, {@code y[i]}).
138 * For example, the following code fragment draws a filled diamond
139 * with vertices (0.1, 0.2), (0.2, 0.3), (0.3, 0.2), and (0.2, 0.1):
140 * <pre>
141 * double[] x = { 0.1, 0.2, 0.3, 0.2 };
142 * double[] y = { 0.2, 0.3, 0.2, 0.1 };
143 * StdDraw.filledPolygon(x, y);
144 * </pre>
145 * <p>
146 * <b>Pen size.</b>
147 * The pen is circular, so that when you set the pen radius to <em>r</em>
148 * and draw a point, you get a circle of radius <em>r</em>. Also, lines are
149 * of thickness 2<em>r</em> and have rounded ends. The default pen radius
150 * is 0.005 and is not affected by coordinate scaling. This default pen
151 * radius is about 1/200 the width of the default canvas, so that if
152 * you draw 100 points equally spaced along a horizontal or vertical line,
153 * you will be able to see individual circles, but if you draw 200 such
154 * points, the result will look like a line.
155 * <ul>
156 * <li> {@link #setPenRadius(double radius)}
157 * </ul>
158 * <p>
159 * For example, {@code StdDraw.setPenRadius(0.025)} makes
160 * the thickness of the lines and the size of the points to be five times
161 * the 0.005 default.
162 * To draw points with the minimum possible radius (one pixel on typical
163 * displays), set the pen radius to 0.0.
164 * <p>
165 * <b>Pen color.</b>
166 * All geometric shapes (such as points, lines, and circles) are drawn using
167 * the current pen color. By default, it is black.
168 * You can change the pen color with the following methods:
169 * <ul>
170 * <li> {@link #setPenColor(int red, int green, int blue)}
171 * <li> {@link #setPenColor(Color color)}
172 * </ul>
173 * <p>
174 * The first method allows you to specify colors using the RGB color system.
175 * This <a href = "http://johndyer.name/lab/colorpicker/">color picker</a>
176 * is a convenient way to find a desired color.
177 * The second method allows you to specify colors using the
178 * {@link Color} data type that is discussed in Chapter 3. Until then,
179 * you can use this method with one of these predefined colors in standard drawing:
180 * {@link #BLACK}, {@link #BLUE}, {@link #CYAN}, {@link #DARK_GRAY}, {@link #GRAY},
181 * {@link #GREEN}, {@link #LIGHT_GRAY}, {@link #MAGENTA}, {@link #ORANGE},
182 * {@link #PINK}, {@link #RED}, {@link #WHITE}, {@link #YELLOW},
183 * {@link #BOOK_BLUE}, {@link #BOOK_LIGHT_BLUE}, {@link #BOOK_RED}, and
184 * {@link #PRINCETON_ORANGE}.
185 * For example, {@code StdDraw.setPenColor(StdDraw.MAGENTA)} sets the
186 * pen color to magenta.
187 * <p>
188 * <b>Canvas size.</b>
189 * By default, all drawing takes places in a 512-by-512 canvas.
190 * The canvas does not include the window title or window border.
191 * You can change the size of the canvas with the following method:
192 * <ul>
193 * <li> {@link #setCanvasSize(int width, int height)}
194 * </ul>
195 * <p>
196 * This sets the canvas size to be <em>width</em>-by-<em>height</em> pixels.
197 * It also erases the current drawing and resets the coordinate system,
198 * pen radius, pen color, and font back to their default values.
199 * Ordinarly, this method is called once, at the very beginning of a program.
200 * For example, {@code StdDraw.setCanvasSize(800, 800)}
201 * sets the canvas size to be 800-by-800 pixels.
202 * <p>
203 * <b>Canvas scale and coordinate system.</b>
204 * By default, all drawing takes places in the unit square, with (0, 0) at
205 * lower left and (1, 1) at upper right. You can change the default
206 * coordinate system with the following methods:
207 * <ul>
208 * <li> {@link #setXscale(double xmin, double xmax)}
209 * <li> {@link #setYscale(double ymin, double ymax)}
210 * <li> {@link #setScale(double min, double max)}
211 * </ul>
212 * <p>
213 * The arguments are the coordinates of the minimum and maximum
214 * <em>x</em>- or <em>y</em>-coordinates that will appear in the canvas.
215 * For example, if you wish to use the default coordinate system but
216 * leave a small margin, you can call {@code StdDraw.setScale(-.05, 1.05)}.
217 * <p>
218 * These methods change the coordinate system for subsequent drawing
219 * commands; they do not affect previous drawings.
220 * These methods do not change the canvas size; so, if the <em>x</em>-
221 * and <em>y</em>-scales are different, squares will become rectangles
222 * and circles will become ellipsoidal.
223 * <p>
224 * <b>Text.</b>
225 * You can use the following methods to annotate your drawings with text:
226 * <ul>
227 * <li> {@link #text(double x, double y, String text)}
228 * <li> {@link #text(double x, double y, String text, double degrees)}
229 * <li> {@link #textLeft(double x, double y, String text)}
230 * <li> {@link #textRight(double x, double y, String text)}
231 * </ul>
232 * <p>
233 * The first two methods write the specified text in the current font,
234 * centered at (<em>x</em>, <em>y</em>).
235 * The second method allows you to rotate the text.
236 * The last two methods either left- or right-align the text at (<em>x</em>, <em>y</em>).
237 * <p>
238 * The default font is a Sans Serif font with point size 16.
239 * You can use the following method to change the font:
240 * <ul>
241 * <li> {@link #setFont(Font font)}
242 * </ul>
243 * <p>
244 * You use the {@link Font} data type to specify the font. This allows you to
245 * choose the face, size, and style of the font. For example, the following
246 * code fragment sets the font to Arial Bold, 60 point.
247 * <pre>
248 * Font font = new Font("Arial", Font.BOLD, 60);
249 * StdDraw.setFont(font);
250 * StdDraw.text(0.5, 0.5, "Hello, World");
251 * </pre>
252 * <p>
253 * <b>Images.</b>
254 * You can use the following methods to add images to your drawings:
255 * <ul>
256 * <li> {@link #picture(double x, double y, String filename)}
257 * <li> {@link #picture(double x, double y, String filename, double degrees)}
258 * <li> {@link #picture(double x, double y, String filename, double scaledWidth, double scaledHeight)}
259 * <li> {@link #picture(double x, double y, String filename, double scaledWidth, double scaledHeight, double degrees)}
260 * </ul>
261 * <p>
262 * These methods draw the specified image, centered at (<em>x</em>, <em>y</em>).
263 * The supported image formats are JPEG, PNG, and GIF.
264 * The image will display at its native size, independent of the coordinate system.
265 * Optionally, you can rotate the image a specified number of degrees counterclockwise
266 * or rescale it to fit snugly inside a width-by-height bounding box.
267 * <p>
268 * <b>Saving to a file.</b>
269 * You save your image to a file using the <em>File → Save</em> menu option.
270 * You can also save a file programatically using the following method:
271 * <ul>
272 * <li> {@link #save(String filename)}
273 * </ul>
274 * <p>
275 * The supported image formats are JPEG and PNG. The filename must have either the
276 * extension .jpg or .png.
277 * We recommend using PNG for drawing that consist solely of geometric shapes and JPEG
278 * for drawings that contains pictures.
279 * <p>
280 * <b>Clearing the canvas.</b>
281 * To clear the entire drawing canvas, you can use the following methods:
282 * <ul>
283 * <li> {@link #clear()}
284 * <li> {@link #clear(Color color)}
285 * </ul>
286 * <p>
287 * The first method clears the canvas to white; the second method
288 * allows you to specify a color of your choice. For example,
289 * {@code StdDraw.clear(StdDraw.LIGHT_GRAY)} clears the canvas to a shade
290 * of gray.
291 * <p>
292 * <b>Computer animations and double buffering.</b>
293 * Double buffering is one of the most powerful features of standard drawing,
294 * enabling computer animations.
295 * The following methods control the way in which objects are drawn:
296 * <ul>
297 * <li> {@link #enableDoubleBuffering()}
298 * <li> {@link #disableDoubleBuffering()}
299 * <li> {@link #show()}
300 * <li> {@link #pause(int t)}
301 * </ul>
302 * <p>
303 * By default, double buffering is disabled, which means that as soon as you
304 * call a drawing
305 * method—such as {@code point()} or {@code line()}—the
306 * results appear on the screen.
307 * <p>
308 * When double buffering is enabled by calling {@link #enableDoubleBuffering()},
309 * all drawing takes place on the <em>offscreen canvas</em>. The offscreen canvas
310 * is not displayed. Only when you call
311 * {@link #show()} does your drawing get copied from the offscreen canvas to
312 * the onscreen canvas, where it is displayed in the standard drawing window. You
313 * can think of double buffering as collecting all of the lines, points, shapes,
314 * and text that you tell it to draw, and then drawing them all
315 * <em>simultaneously</em>, upon request.
316 * <p>
317 * The most important use of double buffering is to produce computer
318 * animations, creating the illusion of motion by rapidly
319 * displaying static drawings. To produce an animation, repeat
320 * the following four steps:
321 * <ul>
322 * <li> Clear the offscreen canvas.
323 * <li> Draw objects on the offscreen canvas.
324 * <li> Copy the offscreen canvas to the onscreen canvas.
325 * <li> Wait for a short while.
326 * </ul>
327 * <p>
328 * The {@link #clear()}, {@link #show()}, and {@link #pause(int t)} methods
329 * support the first, third, and fourth of these steps, respectively.
330 * <p>
331 * For example, this code fragment animates two balls moving in a circle.
332 * <pre>
333 * StdDraw.setScale(-2, +2);
334 * StdDraw.enableDoubleBuffering();
335 *
336 * for (double t = 0.0; true; t += 0.02) {
337 * double x = Math.sin(t);
338 * double y = Math.cos(t);
339 * StdDraw.clear();
340 * StdDraw.filledCircle(x, y, 0.05);
341 * StdDraw.filledCircle(-x, -y, 0.05);
342 * StdDraw.show();
343 * StdDraw.pause(20);
344 * }
345 * </pre>
346 * <p>
347 * <b>Keyboard and mouse inputs.</b>
348 * Standard drawing has very basic support for keyboard and mouse input.
349 * It is much less powerful than most user interface libraries provide, but also much simpler.
350 * You can use the following methods to intercept mouse events:
351 * <ul>
352 * <li> {@link #isMousePressed()}
353 * <li> {@link #mouseX()}
354 * <li> {@link #mouseY()}
355 * </ul>
356 * <p>
357 * The first method tells you whether a mouse button is currently being pressed.
358 * The last two methods tells you the <em>x</em>- and <em>y</em>-coordinates of the mouse's
359 * current position, using the same coordinate system as the canvas (the unit square, by default).
360 * You should use these methods in an animation loop that waits a short while before trying
361 * to poll the mouse for its current state.
362 * You can use the following methods to intercept keyboard events:
363 * <ul>
364 * <li> {@link #hasNextKeyTyped()}
365 * <li> {@link #nextKeyTyped()}
366 * <li> {@link #isKeyPressed(int keycode)}
367 * </ul>
368 * <p>
369 * If the user types lots of keys, they will be saved in a list until you process them.
370 * The first method tells you whether the user has typed a key (that your program has
371 * not yet processed).
372 * The second method returns the next key that the user typed (that your program has
373 * not yet processed) and removes it from the list of saved keystrokes.
374 * The third method tells you whether a key is currently being pressed.
375 * <p>
376 * <b>Accessing control parameters.</b>
377 * You can use the following methods to access the current pen color, pen radius,
378 * and font:
379 * <ul>
380 * <li> {@link #getPenColor()}
381 * <li> {@link #getPenRadius()}
382 * <li> {@link #getFont()}
383 * </ul>
384 * <p>
385 * These methods are useful when you want to temporarily change a
386 * control parameter and reset it back to its original value.
387 * <p>
388 * <b>Corner cases.</b>
389 * To avoid clutter, the API doesn't explicitly refer to arguments that are
390 * null, infinity, or NaN.
391 * <ul>
392 * <li> Any method that is passed a {@code null} argument will throw an
393 * {@link IllegalArgumentException}.
394 * <li> Except as noted in the APIs, drawing an object outside (or partly outside)
395 * the canvas is permitted—however, only the part of the object that
396 * appears inside the canvas will be visible.
397 * <li> Except as noted in the APIs, all methods accept {@link Double#NaN},
398 * {@link Double#POSITIVE_INFINITY}, and {@link Double#NEGATIVE_INFINITY}
399 * as arugments. An object drawn with an <em>x</em>- or <em>y</em>-coordinate
400 * that is NaN will behave as if it is outside the canvas, and will not be visible.
401 * <li> Due to floating-point issues, an object drawn with an <em>x</em>- or
402 * <em>y</em>-coordinate that is way outside the canvas (such as the line segment
403 * from (0.5, –∞) to (0.5, ∞) may not be visible even in the
404 * part of the canvas where it should be.
405 * </ul>
406 * <p>
407 * <b>Performance tricks.</b>
408 * Standard drawing is capable of drawing large amounts of data.
409 * Here are a few tricks and tips:
410 * <ul>
411 * <li> Use <em>double buffering</em> for static drawing with a large
412 * number of objects.
413 * That is, call {@link #enableDoubleBuffering()} before
414 * the sequence of drawing commands and call {@link #show()} afterwards.
415 * Incrementally displaying a complex drawing while it is being
416 * created can be intolerably inefficient on many computer systems.
417 * <li> When drawing computer animations, call {@code show()}
418 * only once per frame, not after drawing each individual object.
419 * <li> If you call {@code picture()} multiple times with the same filename,
420 * Java will cache the image, so you do not incur the cost of reading
421 * from a file each time.
422 * </ul>
423 * <p>
424 * <b>Known bugs and issues.</b>
425 * <ul>
426 * <li> The {@code picture()} methods may not draw the portion of the image that is
427 * inside the canvas if the center point (<em>x</em>, <em>y</em>) is outside the
428 * canvas.
429 * This bug appears only on some systems.
430 * <li> Some methods may not draw the portion of the geometric object that is inside the
431 * canvas if the <em>x</em>- or <em>y</em>-coordinates are infinite.
432 * This bug appears only on some systems.
433 * </ul>
434 * <p>
435 * <b>Reference.</b>
436 * For additional documentation,
437 * see <a href="https://introcs.cs.princeton.edu/15inout">Section 1.5</a> of
438 * <em>Computer Science: An Interdisciplinary Approach</em>
439 * by Robert Sedgewick and Kevin Wayne.
440 *
441 * @author Robert Sedgewick
442 * @author Kevin Wayne
443 */
444public final class StdDraw implements ActionListener, MouseListener, MouseMotionListener, KeyListener {
445
446 /**
447 * The color black.
448 */
449 public static final Color BLACK = Color.BLACK;
450
451 /**
452 * The color blue.
453 */
454 public static final Color BLUE = Color.BLUE;
455
456 /**
457 * The color cyan.
458 */
459 public static final Color CYAN = Color.CYAN;
460
461 /**
462 * The color dark gray.
463 */
464 public static final Color DARK_GRAY = Color.DARK_GRAY;
465
466 /**
467 * The color gray.
468 */
469 public static final Color GRAY = Color.GRAY;
470
471 /**
472 * The color green.
473 */
474 public static final Color GREEN = Color.GREEN;
475
476 /**
477 * The color light gray.
478 */
479 public static final Color LIGHT_GRAY = Color.LIGHT_GRAY;
480
481 /**
482 * The color magenta.
483 */
484 public static final Color MAGENTA = Color.MAGENTA;
485
486 /**
487 * The color orange.
488 */
489 public static final Color ORANGE = Color.ORANGE;
490
491 /**
492 * The color pink.
493 */
494 public static final Color PINK = Color.PINK;
495
496 /**
497 * The color red.
498 */
499 public static final Color RED = Color.RED;
500
501 /**
502 * The color white.
503 */
504 public static final Color WHITE = Color.WHITE;
505
506 /**
507 * The color yellow.
508 */
509 public static final Color YELLOW = Color.YELLOW;
510
511 /**
512 * Shade of blue used in <em>Introduction to Programming in Java</em>.
513 * It is Pantone 300U. The RGB values are approximately (9, 90, 166).
514 */
515 public static final Color BOOK_BLUE = new Color(9, 90, 166);
516
517 /**
518 * Shade of light blue used in <em>Introduction to Programming in Java</em>.
519 * The RGB values are approximately (103, 198, 243).
520 */
521 public static final Color BOOK_LIGHT_BLUE = new Color(103, 198, 243);
522
523 /**
524 * Shade of red used in <em>Algorithms, 4th edition</em>.
525 * It is Pantone 1805U. The RGB values are approximately (150, 35, 31).
526 */
527 public static final Color BOOK_RED = new Color(150, 35, 31);
528
529 /**
530 * Shade of orange used in Princeton University's identity.
531 * It is PMS 158. The RGB values are approximately (245, 128, 37).
532 */
533 public static final Color PRINCETON_ORANGE = new Color(245, 128, 37);
534
535 // default colors
536 private static final Color DEFAULT_PEN_COLOR = BLACK;
537 private static final Color DEFAULT_CLEAR_COLOR = WHITE;
538
539 // current pen color
540 private static Color penColor;
541
542 // default canvas size is DEFAULT_SIZE-by-DEFAULT_SIZE
543 private static final int DEFAULT_SIZE = 512;
544 private static int width = DEFAULT_SIZE;
545 private static int height = DEFAULT_SIZE;
546
547 // default pen radius
548 private static final double DEFAULT_PEN_RADIUS = 0.002;
549
550 // current pen radius
551 private static double penRadius;
552
553 // show we draw immediately or wait until next show?
554 private static boolean defer = false;
555
556 // boundary of drawing canvas, 0% border
557 // private static final double BORDER = 0.05;
558 private static final double BORDER = 0.00;
559 private static final double DEFAULT_XMIN = 0.0;
560 private static final double DEFAULT_XMAX = 1.0;
561 private static final double DEFAULT_YMIN = 0.0;
562 private static final double DEFAULT_YMAX = 1.0;
563 private static double xmin, ymin, xmax, ymax;
564
565 // for synchronization
566 private static Object mouseLock = new Object();
567 private static Object keyLock = new Object();
568
569 // default font
570 private static final Font DEFAULT_FONT = new Font("SansSerif", Font.PLAIN, 16);
571
572 // current font
573 private static Font font;
574
575 // double buffered graphics
576 private static BufferedImage offscreenImage, onscreenImage;
577 private static Graphics2D offscreen, onscreen;
578
579 // singleton for callbacks: avoids generation of extra .class files
580 private static StdDraw std = new StdDraw();
581
582 // the frame for drawing to the screen
583 private static JFrame frame;
584
585 // mouse state
586 private static boolean isMousePressed = false;
587 private static double mouseX = 0;
588 private static double mouseY = 0;
589
590 // queue of typed key characters
591 private static LinkedList<Character> keysTyped = new LinkedList<Character>();
592
593 // set of key codes currently pressed down
594 private static TreeSet<Integer> keysDown = new TreeSet<Integer>();
595
596 // singleton pattern: client can't instantiate
597 private StdDraw() { }
598
599
600 // static initializer
601 static {
602 init();
603 }
604
605 /**
606 * Sets the canvas (drawing area) to be 512-by-512 pixels.
607 * This also erases the current drawing and resets the coordinate system,
608 * pen radius, pen color, and font back to their default values.
609 * Ordinarly, this method is called once, at the very beginning
610 * of a program.
611 */
612 public static void setCanvasSize() {
613 setCanvasSize(DEFAULT_SIZE, DEFAULT_SIZE);
614 }
615
616 /**
617 * Sets the canvas (drawing area) to be <em>width</em>-by-<em>height</em> pixels.
618 * This also erases the current drawing and resets the coordinate system,
619 * pen radius, pen color, and font back to their default values.
620 * Ordinarly, this method is called once, at the very beginning
621 * of a program.
622 *
623 * @param canvasWidth the width as a number of pixels
624 * @param canvasHeight the height as a number of pixels
625 * @throws IllegalArgumentException unless both {@code canvasWidth} and
626 * {@code canvasHeight} are positive
627 */
628 public static void setCanvasSize(int canvasWidth, int canvasHeight) {
629 if (canvasWidth <= 0 || canvasHeight <= 0)
630 throw new IllegalArgumentException("width and height must be positive");
631 width = canvasWidth;
632 height = canvasHeight;
633 init();
634 }
635
636 // init
637 private static void init() {
638 if (frame != null) frame.setVisible(false);
639 frame = new JFrame();
640 offscreenImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
641 onscreenImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
642 offscreen = offscreenImage.createGraphics();
643 onscreen = onscreenImage.createGraphics();
644 setXscale();
645 setYscale();
646 offscreen.setColor(DEFAULT_CLEAR_COLOR);
647 offscreen.fillRect(0, 0, width, height);
648 setPenColor();
649 setPenRadius();
650 setFont();
651 clear();
652
653 // add antialiasing
654 RenderingHints hints = new RenderingHints(RenderingHints.KEY_ANTIALIASING,
655 RenderingHints.VALUE_ANTIALIAS_ON);
656 hints.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
657 offscreen.addRenderingHints(hints);
658
659 // frame stuff
660 ImageIcon icon = new ImageIcon(onscreenImage);
661 JLabel draw = new JLabel(icon);
662
663 draw.addMouseListener(std);
664 draw.addMouseMotionListener(std);
665
666 frame.setContentPane(draw);
667 frame.addKeyListener(std); // JLabel cannot get keyboard focus
668 frame.setResizable(false);
669 frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // closes all windows
670 // frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); // closes only current window
671 frame.setTitle("Standard Draw");
672 frame.setJMenuBar(createMenuBar());
673 frame.pack();
674 frame.requestFocusInWindow();
675 frame.setVisible(true);
676 }
677
678 // create the menu bar (changed to private)
679 private static JMenuBar createMenuBar() {
680 JMenuBar menuBar = new JMenuBar();
681 JMenu menu = new JMenu("File");
682 menuBar.add(menu);
683 JMenuItem menuItem1 = new JMenuItem(" Save... ");
684 menuItem1.addActionListener(std);
685 menuItem1.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S,
686 Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
687 menu.add(menuItem1);
688 return menuBar;
689 }
690
691
692 /***************************************************************************
693 * User and screen coordinate systems.
694 ***************************************************************************/
695
696 /**
697 * Sets the <em>x</em>-scale to be the default (between 0.0 and 1.0).
698 */
699 public static void setXscale() {
700 setXscale(DEFAULT_XMIN, DEFAULT_XMAX);
701 }
702
703 /**
704 * Sets the <em>y</em>-scale to be the default (between 0.0 and 1.0).
705 */
706 public static void setYscale() {
707 setYscale(DEFAULT_YMIN, DEFAULT_YMAX);
708 }
709
710 /**
711 * Sets the <em>x</em>-scale and <em>y</em>-scale to be the default
712 * (between 0.0 and 1.0).
713 */
714 public static void setScale() {
715 setXscale();
716 setYscale();
717 }
718
719 /**
720 * Sets the <em>x</em>-scale to the specified range.
721 *
722 * @param min the minimum value of the <em>x</em>-scale
723 * @param max the maximum value of the <em>x</em>-scale
724 * @throws IllegalArgumentException if {@code (max == min)}
725 */
726 public static void setXscale(double min, double max) {
727 double size = max - min;
728 if (size == 0.0) throw new IllegalArgumentException("the min and max are the same");
729 synchronized (mouseLock) {
730 xmin = min - BORDER * size;
731 xmax = max + BORDER * size;
732 }
733 }
734
735 /**
736 * Sets the <em>y</em>-scale to the specified range.
737 *
738 * @param min the minimum value of the <em>y</em>-scale
739 * @param max the maximum value of the <em>y</em>-scale
740 * @throws IllegalArgumentException if {@code (max == min)}
741 */
742 public static void setYscale(double min, double max) {
743 double size = max - min;
744 if (size == 0.0) throw new IllegalArgumentException("the min and max are the same");
745 synchronized (mouseLock) {
746 ymin = min - BORDER * size;
747 ymax = max + BORDER * size;
748 }
749 }
750
751 /**
752 * Sets both the <em>x</em>-scale and <em>y</em>-scale to the (same) specified range.
753 *
754 * @param min the minimum value of the <em>x</em>- and <em>y</em>-scales
755 * @param max the maximum value of the <em>x</em>- and <em>y</em>-scales
756 * @throws IllegalArgumentException if {@code (max == min)}
757 */
758 public static void setScale(double min, double max) {
759 double size = max - min;
760 if (size == 0.0) throw new IllegalArgumentException("the min and max are the same");
761 synchronized (mouseLock) {
762 xmin = min - BORDER * size;
763 xmax = max + BORDER * size;
764 ymin = min - BORDER * size;
765 ymax = max + BORDER * size;
766 }
767 }
768
769 // helper functions that scale from user coordinates to screen coordinates and back
770 private static double scaleX(double x) { return width * (x - xmin) / (xmax - xmin); }
771 private static double scaleY(double y) { return height * (ymax - y) / (ymax - ymin); }
772 private static double factorX(double w) { return w * width / Math.abs(xmax - xmin); }
773 private static double factorY(double h) { return h * height / Math.abs(ymax - ymin); }
774 private static double userX(double x) { return xmin + x * (xmax - xmin) / width; }
775 private static double userY(double y) { return ymax - y * (ymax - ymin) / height; }
776
777
778 /**
779 * Clears the screen to the default color (white).
780 */
781 public static void clear() {
782 clear(DEFAULT_CLEAR_COLOR);
783 }
784
785 /**
786 * Clears the screen to the specified color.
787 *
788 * @param color the color to make the background
789 */
790 public static void clear(Color color) {
791 offscreen.setColor(color);
792 offscreen.fillRect(0, 0, width, height);
793 offscreen.setColor(penColor);
794 draw();
795 }
796
797 /**
798 * Returns the current pen radius.
799 *
800 * @return the current value of the pen radius
801 */
802 public static double getPenRadius() {
803 return penRadius;
804 }
805
806 /**
807 * Sets the pen size to the default size (0.002).
808 * The pen is circular, so that lines have rounded ends, and when you set the
809 * pen radius and draw a point, you get a circle of the specified radius.
810 * The pen radius is not affected by coordinate scaling.
811 */
812 public static void setPenRadius() {
813 setPenRadius(DEFAULT_PEN_RADIUS);
814 }
815
816 /**
817 * Sets the radius of the pen to the specified size.
818 * The pen is circular, so that lines have rounded ends, and when you set the
819 * pen radius and draw a point, you get a circle of the specified radius.
820 * The pen radius is not affected by coordinate scaling.
821 *
822 * @param radius the radius of the pen
823 * @throws IllegalArgumentException if {@code radius} is negative
824 */
825 public static void setPenRadius(double radius) {
826 if (!(radius >= 0)) throw new IllegalArgumentException("pen radius must be nonnegative");
827 penRadius = radius;
828 float scaledPenRadius = (float) (radius * DEFAULT_SIZE);
829 BasicStroke stroke = new BasicStroke(scaledPenRadius, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);
830 // BasicStroke stroke = new BasicStroke(scaledPenRadius);
831 offscreen.setStroke(stroke);
832 }
833
834 /**
835 * Returns the current pen color.
836 *
837 * @return the current pen color
838 */
839 public static Color getPenColor() {
840 return penColor;
841 }
842
843 /**
844 * Set the pen color to the default color (black).
845 */
846 public static void setPenColor() {
847 setPenColor(DEFAULT_PEN_COLOR);
848 }
849
850 /**
851 * Sets the pen color to the specified color.
852 * <p>
853 * The predefined pen colors are
854 * {@code StdDraw.BLACK}, {@code StdDraw.BLUE}, {@code StdDraw.CYAN},
855 * {@code StdDraw.DARK_GRAY}, {@code StdDraw.GRAY}, {@code StdDraw.GREEN},
856 * {@code StdDraw.LIGHT_GRAY}, {@code StdDraw.MAGENTA}, {@code StdDraw.ORANGE},
857 * {@code StdDraw.PINK}, {@code StdDraw.RED}, {@code StdDraw.WHITE}, and
858 * {@code StdDraw.YELLOW}.
859 *
860 * @param color the color to make the pen
861 */
862 public static void setPenColor(Color color) {
863 if (color == null) throw new IllegalArgumentException();
864 penColor = color;
865 offscreen.setColor(penColor);
866 }
867
868 /**
869 * Sets the pen color to the specified RGB color.
870 *
871 * @param red the amount of red (between 0 and 255)
872 * @param green the amount of green (between 0 and 255)
873 * @param blue the amount of blue (between 0 and 255)
874 * @throws IllegalArgumentException if {@code red}, {@code green},
875 * or {@code blue} is outside its prescribed range
876 */
877 public static void setPenColor(int red, int green, int blue) {
878 if (red < 0 || red >= 256) throw new IllegalArgumentException("amount of red must be between 0 and 255");
879 if (green < 0 || green >= 256) throw new IllegalArgumentException("amount of green must be between 0 and 255");
880 if (blue < 0 || blue >= 256) throw new IllegalArgumentException("amount of blue must be between 0 and 255");
881 setPenColor(new Color(red, green, blue));
882 }
883
884 /**
885 * Returns the current font.
886 *
887 * @return the current font
888 */
889 public static Font getFont() {
890 return font;
891 }
892
893 /**
894 * Sets the font to the default font (sans serif, 16 point).
895 */
896 public static void setFont() {
897 setFont(DEFAULT_FONT);
898 }
899
900 /**
901 * Sets the font to the specified value.
902 *
903 * @param font the font
904 */
905 public static void setFont(Font font) {
906 if (font == null) throw new IllegalArgumentException();
907 StdDraw.font = font;
908 }
909
910
911 /***************************************************************************
912 * Drawing geometric shapes.
913 ***************************************************************************/
914
915 /**
916 * Draws a line segment between (<em>x</em><sub>0</sub>, <em>y</em><sub>0</sub>) and
917 * (<em>x</em><sub>1</sub>, <em>y</em><sub>1</sub>).
918 *
919 * @param x0 the <em>x</em>-coordinate of one endpoint
920 * @param y0 the <em>y</em>-coordinate of one endpoint
921 * @param x1 the <em>x</em>-coordinate of the other endpoint
922 * @param y1 the <em>y</em>-coordinate of the other endpoint
923 */
924 public static void line(double x0, double y0, double x1, double y1) {
925 offscreen.draw(new Line2D.Double(scaleX(x0), scaleY(y0), scaleX(x1), scaleY(y1)));
926 draw();
927 }
928
929 /**
930 * Draws one pixel at (<em>x</em>, <em>y</em>).
931 * This method is private because pixels depend on the display.
932 * To achieve the same effect, set the pen radius to 0 and call {@code point()}.
933 *
934 * @param x the <em>x</em>-coordinate of the pixel
935 * @param y the <em>y</em>-coordinate of the pixel
936 */
937 private static void pixel(double x, double y) {
938 offscreen.fillRect((int) Math.round(scaleX(x)), (int) Math.round(scaleY(y)), 1, 1);
939 }
940
941 /**
942 * Draws a point centered at (<em>x</em>, <em>y</em>).
943 * The point is a filled circle whose radius is equal to the pen radius.
944 * To draw a single-pixel point, first set the pen radius to 0.
945 *
946 * @param x the <em>x</em>-coordinate of the point
947 * @param y the <em>y</em>-coordinate of the point
948 */
949 public static void point(double x, double y) {
950 double xs = scaleX(x);
951 double ys = scaleY(y);
952 double r = penRadius;
953 float scaledPenRadius = (float) (r * DEFAULT_SIZE);
954
955 // double ws = factorX(2*r);
956 // double hs = factorY(2*r);
957 // if (ws <= 1 && hs <= 1) pixel(x, y);
958 if (scaledPenRadius <= 1) pixel(x, y);
959 else offscreen.fill(new Ellipse2D.Double(xs - scaledPenRadius/2, ys - scaledPenRadius/2,
960 scaledPenRadius, scaledPenRadius));
961 draw();
962 }
963
964 /**
965 * Draws a circle of the specified radius, centered at (<em>x</em>, <em>y</em>).
966 *
967 * @param x the <em>x</em>-coordinate of the center of the circle
968 * @param y the <em>y</em>-coordinate of the center of the circle
969 * @param radius the radius of the circle
970 * @throws IllegalArgumentException if {@code radius} is negative
971 */
972 public static void circle(double x, double y, double radius) {
973 if (!(radius >= 0)) throw new IllegalArgumentException("radius must be nonnegative");
974 double xs = scaleX(x);
975 double ys = scaleY(y);
976 double ws = factorX(2*radius);
977 double hs = factorY(2*radius);
978 if (ws <= 1 && hs <= 1) pixel(x, y);
979 else offscreen.draw(new Ellipse2D.Double(xs - ws/2, ys - hs/2, ws, hs));
980 draw();
981 }
982
983 /**
984 * Draws a filled circle of the specified radius, centered at (<em>x</em>, <em>y</em>).
985 *
986 * @param x the <em>x</em>-coordinate of the center of the circle
987 * @param y the <em>y</em>-coordinate of the center of the circle
988 * @param radius the radius of the circle
989 * @throws IllegalArgumentException if {@code radius} is negative
990 */
991 public static void filledCircle(double x, double y, double radius) {
992 if (!(radius >= 0)) throw new IllegalArgumentException("radius must be nonnegative");
993 double xs = scaleX(x);
994 double ys = scaleY(y);
995 double ws = factorX(2*radius);
996 double hs = factorY(2*radius);
997 if (ws <= 1 && hs <= 1) pixel(x, y);
998 else offscreen.fill(new Ellipse2D.Double(xs - ws/2, ys - hs/2, ws, hs));
999 draw();
1000 }
1001
1002
1003 /**
1004 * Draws an ellipse with the specified semimajor and semiminor axes,
1005 * centered at (<em>x</em>, <em>y</em>).
1006 *
1007 * @param x the <em>x</em>-coordinate of the center of the ellipse
1008 * @param y the <em>y</em>-coordinate of the center of the ellipse
1009 * @param semiMajorAxis is the semimajor axis of the ellipse
1010 * @param semiMinorAxis is the semiminor axis of the ellipse
1011 * @throws IllegalArgumentException if either {@code semiMajorAxis}
1012 * or {@code semiMinorAxis} is negative
1013 */
1014 public static void ellipse(double x, double y, double semiMajorAxis, double semiMinorAxis) {
1015 if (!(semiMajorAxis >= 0)) throw new IllegalArgumentException("ellipse semimajor axis must be nonnegative");
1016 if (!(semiMinorAxis >= 0)) throw new IllegalArgumentException("ellipse semiminor axis must be nonnegative");
1017 double xs = scaleX(x);
1018 double ys = scaleY(y);
1019 double ws = factorX(2*semiMajorAxis);
1020 double hs = factorY(2*semiMinorAxis);
1021 if (ws <= 1 && hs <= 1) pixel(x, y);
1022 else offscreen.draw(new Ellipse2D.Double(xs - ws/2, ys - hs/2, ws, hs));
1023 draw();
1024 }
1025
1026 /**
1027 * Draws an ellipse with the specified semimajor and semiminor axes,
1028 * centered at (<em>x</em>, <em>y</em>).
1029 *
1030 * @param x the <em>x</em>-coordinate of the center of the ellipse
1031 * @param y the <em>y</em>-coordinate of the center of the ellipse
1032 * @param semiMajorAxis is the semimajor axis of the ellipse
1033 * @param semiMinorAxis is the semiminor axis of the ellipse
1034 * @throws IllegalArgumentException if either {@code semiMajorAxis}
1035 * or {@code semiMinorAxis} is negative
1036 */
1037 public static void filledEllipse(double x, double y, double semiMajorAxis, double semiMinorAxis) {
1038 if (!(semiMajorAxis >= 0)) throw new IllegalArgumentException("ellipse semimajor axis must be nonnegative");
1039 if (!(semiMinorAxis >= 0)) throw new IllegalArgumentException("ellipse semiminor axis must be nonnegative");
1040 double xs = scaleX(x);
1041 double ys = scaleY(y);
1042 double ws = factorX(2*semiMajorAxis);
1043 double hs = factorY(2*semiMinorAxis);
1044 if (ws <= 1 && hs <= 1) pixel(x, y);
1045 else offscreen.fill(new Ellipse2D.Double(xs - ws/2, ys - hs/2, ws, hs));
1046 draw();
1047 }
1048
1049
1050 /**
1051 * Draws a circular arc of the specified radius,
1052 * centered at (<em>x</em>, <em>y</em>), from angle1 to angle2 (in degrees).
1053 *
1054 * @param x the <em>x</em>-coordinate of the center of the circle
1055 * @param y the <em>y</em>-coordinate of the center of the circle
1056 * @param radius the radius of the circle
1057 * @param angle1 the starting angle. 0 would mean an arc beginning at 3 o'clock.
1058 * @param angle2 the angle at the end of the arc. For example, if
1059 * you want a 90 degree arc, then angle2 should be angle1 + 90.
1060 * @throws IllegalArgumentException if {@code radius} is negative
1061 */
1062 public static void arc(double x, double y, double radius, double angle1, double angle2) {
1063 if (radius < 0) throw new IllegalArgumentException("arc radius must be nonnegative");
1064 while (angle2 < angle1) angle2 += 360;
1065 double xs = scaleX(x);
1066 double ys = scaleY(y);
1067 double ws = factorX(2*radius);
1068 double hs = factorY(2*radius);
1069 if (ws <= 1 && hs <= 1) pixel(x, y);
1070 else offscreen.draw(new Arc2D.Double(xs - ws/2, ys - hs/2, ws, hs, angle1, angle2 - angle1, Arc2D.OPEN));
1071 draw();
1072 }
1073
1074 /**
1075 * Draws a square of side length 2r, centered at (<em>x</em>, <em>y</em>).
1076 *
1077 * @param x the <em>x</em>-coordinate of the center of the square
1078 * @param y the <em>y</em>-coordinate of the center of the square
1079 * @param halfLength one half the length of any side of the square
1080 * @throws IllegalArgumentException if {@code halfLength} is negative
1081 */
1082 public static void square(double x, double y, double halfLength) {
1083 if (!(halfLength >= 0)) throw new IllegalArgumentException("half length must be nonnegative");
1084 double xs = scaleX(x);
1085 double ys = scaleY(y);
1086 double ws = factorX(2*halfLength);
1087 double hs = factorY(2*halfLength);
1088 if (ws <= 1 && hs <= 1) pixel(x, y);
1089 else offscreen.draw(new Rectangle2D.Double(xs - ws/2, ys - hs/2, ws, hs));
1090 draw();
1091 }
1092
1093 /**
1094 * Draws a filled square of the specified size, centered at (<em>x</em>, <em>y</em>).
1095 *
1096 * @param x the <em>x</em>-coordinate of the center of the square
1097 * @param y the <em>y</em>-coordinate of the center of the square
1098 * @param halfLength one half the length of any side of the square
1099 * @throws IllegalArgumentException if {@code halfLength} is negative
1100 */
1101 public static void filledSquare(double x, double y, double halfLength) {
1102 if (!(halfLength >= 0)) throw new IllegalArgumentException("half length must be nonnegative");
1103 double xs = scaleX(x);
1104 double ys = scaleY(y);
1105 double ws = factorX(2*halfLength);
1106 double hs = factorY(2*halfLength);
1107 if (ws <= 1 && hs <= 1) pixel(x, y);
1108 else offscreen.fill(new Rectangle2D.Double(xs - ws/2, ys - hs/2, ws, hs));
1109 draw();
1110 }
1111
1112
1113 /**
1114 * Draws a rectangle of the specified size, centered at (<em>x</em>, <em>y</em>).
1115 *
1116 * @param x the <em>x</em>-coordinate of the center of the rectangle
1117 * @param y the <em>y</em>-coordinate of the center of the rectangle
1118 * @param halfWidth one half the width of the rectangle
1119 * @param halfHeight one half the height of the rectangle
1120 * @throws IllegalArgumentException if either {@code halfWidth} or {@code halfHeight} is negative
1121 */
1122 public static void rectangle(double x, double y, double halfWidth, double halfHeight) {
1123 if (!(halfWidth >= 0)) throw new IllegalArgumentException("half width must be nonnegative");
1124 if (!(halfHeight >= 0)) throw new IllegalArgumentException("half height must be nonnegative");
1125 double xs = scaleX(x);
1126 double ys = scaleY(y);
1127 double ws = factorX(2*halfWidth);
1128 double hs = factorY(2*halfHeight);
1129 if (ws <= 1 && hs <= 1) pixel(x, y);
1130 else offscreen.draw(new Rectangle2D.Double(xs - ws/2, ys - hs/2, ws, hs));
1131 draw();
1132 }
1133
1134 /**
1135 * Draws a filled rectangle of the specified size, centered at (<em>x</em>, <em>y</em>).
1136 *
1137 * @param x the <em>x</em>-coordinate of the center of the rectangle
1138 * @param y the <em>y</em>-coordinate of the center of the rectangle
1139 * @param halfWidth one half the width of the rectangle
1140 * @param halfHeight one half the height of the rectangle
1141 * @throws IllegalArgumentException if either {@code halfWidth} or {@code halfHeight} is negative
1142 */
1143 public static void filledRectangle(double x, double y, double halfWidth, double halfHeight) {
1144 if (!(halfWidth >= 0)) throw new IllegalArgumentException("half width must be nonnegative");
1145 if (!(halfHeight >= 0)) throw new IllegalArgumentException("half height must be nonnegative");
1146 double xs = scaleX(x);
1147 double ys = scaleY(y);
1148 double ws = factorX(2*halfWidth);
1149 double hs = factorY(2*halfHeight);
1150 if (ws <= 1 && hs <= 1) pixel(x, y);
1151 else offscreen.fill(new Rectangle2D.Double(xs - ws/2, ys - hs/2, ws, hs));
1152 draw();
1153 }
1154
1155
1156 /**
1157 * Draws a polygon with the vertices
1158 * (<em>x</em><sub>0</sub>, <em>y</em><sub>0</sub>),
1159 * (<em>x</em><sub>1</sub>, <em>y</em><sub>1</sub>), ...,
1160 * (<em>x</em><sub><em>n</em>–1</sub>, <em>y</em><sub><em>n</em>–1</sub>).
1161 *
1162 * @param x an array of all the <em>x</em>-coordinates of the polygon
1163 * @param y an array of all the <em>y</em>-coordinates of the polygon
1164 * @throws IllegalArgumentException unless {@code x[]} and {@code y[]}
1165 * are of the same length
1166 */
1167 public static void polygon(double[] x, double[] y) {
1168 if (x == null) throw new IllegalArgumentException("x-coordinate array is null");
1169 if (y == null) throw new IllegalArgumentException("y-coordinate array is null");
1170 int n1 = x.length;
1171 int n2 = y.length;
1172 if (n1 != n2) throw new IllegalArgumentException("arrays must be of the same length");
1173 int n = n1;
1174 if (n == 0) return;
1175
1176 GeneralPath path = new GeneralPath();
1177 path.moveTo((float) scaleX(x[0]), (float) scaleY(y[0]));
1178 for (int i = 0; i < n; i++)
1179 path.lineTo((float) scaleX(x[i]), (float) scaleY(y[i]));
1180 path.closePath();
1181 offscreen.draw(path);
1182 draw();
1183 }
1184
1185 /**
1186 * Draws a polygon with the vertices
1187 * (<em>x</em><sub>0</sub>, <em>y</em><sub>0</sub>),
1188 * (<em>x</em><sub>1</sub>, <em>y</em><sub>1</sub>), ...,
1189 * (<em>x</em><sub><em>n</em>–1</sub>, <em>y</em><sub><em>n</em>–1</sub>).
1190 *
1191 * @param x an array of all the <em>x</em>-coordinates of the polygon
1192 * @param y an array of all the <em>y</em>-coordinates of the polygon
1193 * @throws IllegalArgumentException unless {@code x[]} and {@code y[]}
1194 * are of the same length
1195 */
1196 public static void filledPolygon(double[] x, double[] y) {
1197 if (x == null) throw new IllegalArgumentException("x-coordinate array is null");
1198 if (y == null) throw new IllegalArgumentException("y-coordinate array is null");
1199 int n1 = x.length;
1200 int n2 = y.length;
1201 if (n1 != n2) throw new IllegalArgumentException("arrays must be of the same length");
1202 int n = n1;
1203 if (n == 0) return;
1204
1205 GeneralPath path = new GeneralPath();
1206 path.moveTo((float) scaleX(x[0]), (float) scaleY(y[0]));
1207 for (int i = 0; i < n; i++)
1208 path.lineTo((float) scaleX(x[i]), (float) scaleY(y[i]));
1209 path.closePath();
1210 offscreen.fill(path);
1211 draw();
1212 }
1213
1214
1215 /***************************************************************************
1216 * Drawing images.
1217 ***************************************************************************/
1218 // get an image from the given filename
1219 private static Image getImage(String filename) {
1220 if (filename == null) throw new IllegalArgumentException();
1221
1222 // to read from file
1223 ImageIcon icon = new ImageIcon(filename);
1224
1225 // try to read from URL
1226 if ((icon == null) || (icon.getImageLoadStatus() != MediaTracker.COMPLETE)) {
1227 try {
1228 URL url = new URL(filename);
1229 icon = new ImageIcon(url);
1230 }
1231 catch (MalformedURLException e) {
1232 /* not a url */
1233 }
1234 }
1235
1236 // in case file is inside a .jar (classpath relative to StdDraw)
1237 if ((icon == null) || (icon.getImageLoadStatus() != MediaTracker.COMPLETE)) {
1238 URL url = StdDraw.class.getResource(filename);
1239 if (url != null)
1240 icon = new ImageIcon(url);
1241 }
1242
1243 // in case file is inside a .jar (classpath relative to root of jar)
1244 if ((icon == null) || (icon.getImageLoadStatus() != MediaTracker.COMPLETE)) {
1245 URL url = StdDraw.class.getResource("/" + filename);
1246 if (url == null) throw new IllegalArgumentException("image " + filename + " not found");
1247 icon = new ImageIcon(url);
1248 }
1249
1250 return icon.getImage();
1251 }
1252
1253 /***************************************************************************
1254 * [Summer 2016] Should we update to use ImageIO instead of ImageIcon()?
1255 * Seems to have some issues loading images on some systems
1256 * and slows things down on other systems.
1257 * especially if you don't call ImageIO.setUseCache(false)
1258 * One advantage is that it returns a BufferedImage.
1259 ***************************************************************************/
1260/*
1261 private static BufferedImage getImage(String filename) {
1262 if (filename == null) throw new IllegalArgumentException();
1263
1264 // from a file or URL
1265 try {
1266 URL url = new URL(filename);
1267 BufferedImage image = ImageIO.read(url);
1268 return image;
1269 }
1270 catch (IOException e) {
1271 // ignore
1272 }
1273
1274 // in case file is inside a .jar (classpath relative to StdDraw)
1275 try {
1276 URL url = StdDraw.class.getResource(filename);
1277 BufferedImage image = ImageIO.read(url);
1278 return image;
1279 }
1280 catch (IOException e) {
1281 // ignore
1282 }
1283
1284 // in case file is inside a .jar (classpath relative to root of jar)
1285 try {
1286 URL url = StdDraw.class.getResource("/" + filename);
1287 BufferedImage image = ImageIO.read(url);
1288 return image;
1289 }
1290 catch (IOException e) {
1291 // ignore
1292 }
1293 throw new IllegalArgumentException("image " + filename + " not found");
1294 }
1295*/
1296 /**
1297 * Draws the specified image centered at (<em>x</em>, <em>y</em>).
1298 * The supported image formats are JPEG, PNG, and GIF.
1299 * As an optimization, the picture is cached, so there is no performance
1300 * penalty for redrawing the same image multiple times (e.g., in an animation).
1301 * However, if you change the picture file after drawing it, subsequent
1302 * calls will draw the original picture.
1303 *
1304 * @param x the center <em>x</em>-coordinate of the image
1305 * @param y the center <em>y</em>-coordinate of the image
1306 * @param filename the name of the image/picture, e.g., "ball.gif"
1307 * @throws IllegalArgumentException if the image filename is invalid
1308 */
1309 public static void picture(double x, double y, String filename) {
1310 // BufferedImage image = getImage(filename);
1311 Image image = getImage(filename);
1312 double xs = scaleX(x);
1313 double ys = scaleY(y);
1314 // int ws = image.getWidth(); // can call only if image is a BufferedImage
1315 // int hs = image.getHeight();
1316 int ws = image.getWidth(null);
1317 int hs = image.getHeight(null);
1318 if (ws < 0 || hs < 0) throw new IllegalArgumentException("image " + filename + " is corrupt");
1319
1320 offscreen.drawImage(image, (int) Math.round(xs - ws/2.0), (int) Math.round(ys - hs/2.0), null);
1321 draw();
1322 }
1323
1324 /**
1325 * Draws the specified image centered at (<em>x</em>, <em>y</em>),
1326 * rotated given number of degrees.
1327 * The supported image formats are JPEG, PNG, and GIF.
1328 *
1329 * @param x the center <em>x</em>-coordinate of the image
1330 * @param y the center <em>y</em>-coordinate of the image
1331 * @param filename the name of the image/picture, e.g., "ball.gif"
1332 * @param degrees is the number of degrees to rotate counterclockwise
1333 * @throws IllegalArgumentException if the image filename is invalid
1334 */
1335 public static void picture(double x, double y, String filename, double degrees) {
1336 // BufferedImage image = getImage(filename);
1337 Image image = getImage(filename);
1338 double xs = scaleX(x);
1339 double ys = scaleY(y);
1340 // int ws = image.getWidth(); // can call only if image is a BufferedImage
1341 // int hs = image.getHeight();
1342 int ws = image.getWidth(null);
1343 int hs = image.getHeight(null);
1344 if (ws < 0 || hs < 0) throw new IllegalArgumentException("image " + filename + " is corrupt");
1345
1346 offscreen.rotate(Math.toRadians(-degrees), xs, ys);
1347 offscreen.drawImage(image, (int) Math.round(xs - ws/2.0), (int) Math.round(ys - hs/2.0), null);
1348 offscreen.rotate(Math.toRadians(+degrees), xs, ys);
1349
1350 draw();
1351 }
1352
1353 /**
1354 * Draws the specified image centered at (<em>x</em>, <em>y</em>),
1355 * rescaled to the specified bounding box.
1356 * The supported image formats are JPEG, PNG, and GIF.
1357 *
1358 * @param x the center <em>x</em>-coordinate of the image
1359 * @param y the center <em>y</em>-coordinate of the image
1360 * @param filename the name of the image/picture, e.g., "ball.gif"
1361 * @param scaledWidth the width of the scaled image (in screen coordinates)
1362 * @param scaledHeight the height of the scaled image (in screen coordinates)
1363 * @throws IllegalArgumentException if either {@code scaledWidth}
1364 * or {@code scaledHeight} is negative
1365 * @throws IllegalArgumentException if the image filename is invalid
1366 */
1367 public static void picture(double x, double y, String filename, double scaledWidth, double scaledHeight) {
1368 Image image = getImage(filename);
1369 if (scaledWidth < 0) throw new IllegalArgumentException("width is negative: " + scaledWidth);
1370 if (scaledHeight < 0) throw new IllegalArgumentException("height is negative: " + scaledHeight);
1371 double xs = scaleX(x);
1372 double ys = scaleY(y);
1373 double ws = factorX(scaledWidth);
1374 double hs = factorY(scaledHeight);
1375 if (ws < 0 || hs < 0) throw new IllegalArgumentException("image " + filename + " is corrupt");
1376 if (ws <= 1 && hs <= 1) pixel(x, y);
1377 else {
1378 offscreen.drawImage(image, (int) Math.round(xs - ws/2.0),
1379 (int) Math.round(ys - hs/2.0),
1380 (int) Math.round(ws),
1381 (int) Math.round(hs), null);
1382 }
1383 draw();
1384 }
1385
1386
1387 /**
1388 * Draws the specified image centered at (<em>x</em>, <em>y</em>), rotated
1389 * given number of degrees, and rescaled to the specified bounding box.
1390 * The supported image formats are JPEG, PNG, and GIF.
1391 *
1392 * @param x the center <em>x</em>-coordinate of the image
1393 * @param y the center <em>y</em>-coordinate of the image
1394 * @param filename the name of the image/picture, e.g., "ball.gif"
1395 * @param scaledWidth the width of the scaled image (in screen coordinates)
1396 * @param scaledHeight the height of the scaled image (in screen coordinates)
1397 * @param degrees is the number of degrees to rotate counterclockwise
1398 * @throws IllegalArgumentException if either {@code scaledWidth}
1399 * or {@code scaledHeight} is negative
1400 * @throws IllegalArgumentException if the image filename is invalid
1401 */
1402 public static void picture(double x, double y, String filename, double scaledWidth, double scaledHeight, double degrees) {
1403 if (scaledWidth < 0) throw new IllegalArgumentException("width is negative: " + scaledWidth);
1404 if (scaledHeight < 0) throw new IllegalArgumentException("height is negative: " + scaledHeight);
1405 Image image = getImage(filename);
1406 double xs = scaleX(x);
1407 double ys = scaleY(y);
1408 double ws = factorX(scaledWidth);
1409 double hs = factorY(scaledHeight);
1410 if (ws < 0 || hs < 0) throw new IllegalArgumentException("image " + filename + " is corrupt");
1411 if (ws <= 1 && hs <= 1) pixel(x, y);
1412
1413 offscreen.rotate(Math.toRadians(-degrees), xs, ys);
1414 offscreen.drawImage(image, (int) Math.round(xs - ws/2.0),
1415 (int) Math.round(ys - hs/2.0),
1416 (int) Math.round(ws),
1417 (int) Math.round(hs), null);
1418 offscreen.rotate(Math.toRadians(+degrees), xs, ys);
1419
1420 draw();
1421 }
1422
1423 /***************************************************************************
1424 * Drawing text.
1425 ***************************************************************************/
1426
1427 /**
1428 * Write the given text string in the current font, centered at (<em>x</em>, <em>y</em>).
1429 *
1430 * @param x the center <em>x</em>-coordinate of the text
1431 * @param y the center <em>y</em>-coordinate of the text
1432 * @param text the text to write
1433 */
1434 public static void text(double x, double y, String text) {
1435 if (text == null) throw new IllegalArgumentException();
1436 offscreen.setFont(font);
1437 FontMetrics metrics = offscreen.getFontMetrics();
1438 double xs = scaleX(x);
1439 double ys = scaleY(y);
1440 int ws = metrics.stringWidth(text);
1441 int hs = metrics.getDescent();
1442 offscreen.drawString(text, (float) (xs - ws/2.0), (float) (ys + hs));
1443 draw();
1444 }
1445
1446 /**
1447 * Write the given text string in the current font, centered at (<em>x</em>, <em>y</em>) and
1448 * rotated by the specified number of degrees.
1449 * @param x the center <em>x</em>-coordinate of the text
1450 * @param y the center <em>y</em>-coordinate of the text
1451 * @param text the text to write
1452 * @param degrees is the number of degrees to rotate counterclockwise
1453 */
1454 public static void text(double x, double y, String text, double degrees) {
1455 if (text == null) throw new IllegalArgumentException();
1456 double xs = scaleX(x);
1457 double ys = scaleY(y);
1458 offscreen.rotate(Math.toRadians(-degrees), xs, ys);
1459 text(x, y, text);
1460 offscreen.rotate(Math.toRadians(+degrees), xs, ys);
1461 }
1462
1463
1464 /**
1465 * Write the given text string in the current font, left-aligned at (<em>x</em>, <em>y</em>).
1466 * @param x the <em>x</em>-coordinate of the text
1467 * @param y the <em>y</em>-coordinate of the text
1468 * @param text the text
1469 */
1470 public static void textLeft(double x, double y, String text) {
1471 if (text == null) throw new IllegalArgumentException();
1472 offscreen.setFont(font);
1473 FontMetrics metrics = offscreen.getFontMetrics();
1474 double xs = scaleX(x);
1475 double ys = scaleY(y);
1476 int hs = metrics.getDescent();
1477 offscreen.drawString(text, (float) xs, (float) (ys + hs));
1478 draw();
1479 }
1480
1481 /**
1482 * Write the given text string in the current font, right-aligned at (<em>x</em>, <em>y</em>).
1483 *
1484 * @param x the <em>x</em>-coordinate of the text
1485 * @param y the <em>y</em>-coordinate of the text
1486 * @param text the text to write
1487 */
1488 public static void textRight(double x, double y, String text) {
1489 if (text == null) throw new IllegalArgumentException();
1490 offscreen.setFont(font);
1491 FontMetrics metrics = offscreen.getFontMetrics();
1492 double xs = scaleX(x);
1493 double ys = scaleY(y);
1494 int ws = metrics.stringWidth(text);
1495 int hs = metrics.getDescent();
1496 offscreen.drawString(text, (float) (xs - ws), (float) (ys + hs));
1497 draw();
1498 }
1499
1500
1501
1502 /**
1503 * Copies the offscreen buffer to the onscreen buffer, pauses for t milliseconds
1504 * and enables double buffering.
1505 * @param t number of milliseconds
1506 * @deprecated replaced by {@link #enableDoubleBuffering()}, {@link #show()}, and {@link #pause(int t)}
1507 */
1508 @Deprecated
1509 public static void show(int t) {
1510 show();
1511 pause(t);
1512 enableDoubleBuffering();
1513 }
1514
1515 /**
1516 * Pause for t milliseconds. This method is intended to support computer animations.
1517 * @param t number of milliseconds
1518 */
1519 public static void pause(int t) {
1520 try {
1521 Thread.sleep(t);
1522 }
1523 catch (InterruptedException e) {
1524 System.out.println("Error sleeping");
1525 }
1526 }
1527
1528 /**
1529 * Copies offscreen buffer to onscreen buffer. There is no reason to call
1530 * this method unless double buffering is enabled.
1531 */
1532 public static void show() {
1533 onscreen.drawImage(offscreenImage, 0, 0, null);
1534 frame.repaint();
1535 }
1536
1537 // draw onscreen if defer is false
1538 private static void draw() {
1539 if (!defer) show();
1540 }
1541
1542 /**
1543 * Enable double buffering. All subsequent calls to
1544 * drawing methods such as {@code line()}, {@code circle()},
1545 * and {@code square()} will be deffered until the next call
1546 * to show(). Useful for animations.
1547 */
1548 public static void enableDoubleBuffering() {
1549 defer = true;
1550 }
1551
1552 /**
1553 * Disable double buffering. All subsequent calls to
1554 * drawing methods such as {@code line()}, {@code circle()},
1555 * and {@code square()} will be displayed on screen when called.
1556 * This is the default.
1557 */
1558 public static void disableDoubleBuffering() {
1559 defer = false;
1560 }
1561
1562
1563 /***************************************************************************
1564 * Save drawing to a file.
1565 ***************************************************************************/
1566
1567 /**
1568 * Saves the drawing to using the specified filename.
1569 * The supported image formats are JPEG and PNG;
1570 * the filename suffix must be {@code .jpg} or {@code .png}.
1571 *
1572 * @param filename the name of the file with one of the required suffixes
1573 */
1574 public static void save(String filename) {
1575 if (filename == null) throw new IllegalArgumentException();
1576 File file = new File(filename);
1577 String suffix = filename.substring(filename.lastIndexOf('.') + 1);
1578
1579 // png files
1580 if ("png".equalsIgnoreCase(suffix)) {
1581 try {
1582 ImageIO.write(onscreenImage, suffix, file);
1583 }
1584 catch (IOException e) {
1585 e.printStackTrace();
1586 }
1587 }
1588
1589 // need to change from ARGB to RGB for JPEG
1590 // reference: http://archives.java.sun.com/cgi-bin/wa?A2=ind0404&L=java2d-interest&D=0&P=2727
1591 else if ("jpg".equalsIgnoreCase(suffix)) {
1592 WritableRaster raster = onscreenImage.getRaster();
1593 WritableRaster newRaster;
1594 newRaster = raster.createWritableChild(0, 0, width, height, 0, 0, new int[] {0, 1, 2});
1595 DirectColorModel cm = (DirectColorModel) onscreenImage.getColorModel();
1596 DirectColorModel newCM = new DirectColorModel(cm.getPixelSize(),
1597 cm.getRedMask(),
1598 cm.getGreenMask(),
1599 cm.getBlueMask());
1600 BufferedImage rgbBuffer = new BufferedImage(newCM, newRaster, false, null);
1601 try {
1602 ImageIO.write(rgbBuffer, suffix, file);
1603 }
1604 catch (IOException e) {
1605 e.printStackTrace();
1606 }
1607 }
1608
1609 else {
1610 System.out.println("Invalid image file type: " + suffix);
1611 }
1612 }
1613
1614
1615 /**
1616 * This method cannot be called directly.
1617 */
1618 @Override
1619 public void actionPerformed(ActionEvent e) {
1620 FileDialog chooser = new FileDialog(StdDraw.frame, "Use a .png or .jpg extension", FileDialog.SAVE);
1621 chooser.setVisible(true);
1622 String filename = chooser.getFile();
1623 if (filename != null) {
1624 StdDraw.save(chooser.getDirectory() + File.separator + chooser.getFile());
1625 }
1626 }
1627
1628
1629 /***************************************************************************
1630 * Mouse interactions.
1631 ***************************************************************************/
1632
1633 /**
1634 * Returns true if the mouse is being pressed.
1635 *
1636 * @return {@code true} if the mouse is being pressed; {@code false} otherwise
1637 */
1638 public static boolean isMousePressed() {
1639 synchronized (mouseLock) {
1640 return isMousePressed;
1641 }
1642 }
1643
1644 /**
1645 * Returns true if the mouse is being pressed.
1646 *
1647 * @return {@code true} if the mouse is being pressed; {@code false} otherwise
1648 * @deprecated replaced by {@link #isMousePressed()}
1649 */
1650 @Deprecated
1651 public static boolean mousePressed() {
1652 synchronized (mouseLock) {
1653 return isMousePressed;
1654 }
1655 }
1656
1657 /**
1658 * Returns the <em>x</em>-coordinate of the mouse.
1659 *
1660 * @return the <em>x</em>-coordinate of the mouse
1661 */
1662 public static double mouseX() {
1663 synchronized (mouseLock) {
1664 return mouseX;
1665 }
1666 }
1667
1668 /**
1669 * Returns the <em>y</em>-coordinate of the mouse.
1670 *
1671 * @return <em>y</em>-coordinate of the mouse
1672 */
1673 public static double mouseY() {
1674 synchronized (mouseLock) {
1675 return mouseY;
1676 }
1677 }
1678
1679
1680 /**
1681 * This method cannot be called directly.
1682 */
1683 @Override
1684 public void mouseClicked(MouseEvent e) {
1685 // this body is intentionally left empty
1686 }
1687
1688 /**
1689 * This method cannot be called directly.
1690 */
1691 @Override
1692 public void mouseEntered(MouseEvent e) {
1693 // this body is intentionally left empty
1694 }
1695
1696 /**
1697 * This method cannot be called directly.
1698 */
1699 @Override
1700 public void mouseExited(MouseEvent e) {
1701 // this body is intentionally left empty
1702 }
1703
1704 /**
1705 * This method cannot be called directly.
1706 */
1707 @Override
1708 public void mousePressed(MouseEvent e) {
1709 synchronized (mouseLock) {
1710 mouseX = StdDraw.userX(e.getX());
1711 mouseY = StdDraw.userY(e.getY());
1712 isMousePressed = true;
1713 }
1714 }
1715
1716 /**
1717 * This method cannot be called directly.
1718 */
1719 @Override
1720 public void mouseReleased(MouseEvent e) {
1721 synchronized (mouseLock) {
1722 isMousePressed = false;
1723 }
1724 }
1725
1726 /**
1727 * This method cannot be called directly.
1728 */
1729 @Override
1730 public void mouseDragged(MouseEvent e) {
1731 synchronized (mouseLock) {
1732 mouseX = StdDraw.userX(e.getX());
1733 mouseY = StdDraw.userY(e.getY());
1734 }
1735 }
1736
1737 /**
1738 * This method cannot be called directly.
1739 */
1740 @Override
1741 public void mouseMoved(MouseEvent e) {
1742 synchronized (mouseLock) {
1743 mouseX = StdDraw.userX(e.getX());
1744 mouseY = StdDraw.userY(e.getY());
1745 }
1746 }
1747
1748
1749 /***************************************************************************
1750 * Keyboard interactions.
1751 ***************************************************************************/
1752
1753 /**
1754 * Returns true if the user has typed a key (that has not yet been processed).
1755 *
1756 * @return {@code true} if the user has typed a key (that has not yet been processed
1757 * by {@link #nextKeyTyped()}; {@code false} otherwise
1758 */
1759 public static boolean hasNextKeyTyped() {
1760 synchronized (keyLock) {
1761 return !keysTyped.isEmpty();
1762 }
1763 }
1764
1765 /**
1766 * Returns the next key that was typed by the user (that your program has not already processed).
1767 * This method should be preceded by a call to {@link #hasNextKeyTyped()} to ensure
1768 * that there is a next key to process.
1769 * This method returns a Unicode character corresponding to the key
1770 * typed (such as {@code 'a'} or {@code 'A'}).
1771 * It cannot identify action keys (such as F1 and arrow keys)
1772 * or modifier keys (such as control).
1773 *
1774 * @return the next key typed by the user (that your program has not already processed).
1775 * @throws NoSuchElementException if there is no remaining key
1776 */
1777 public static char nextKeyTyped() {
1778 synchronized (keyLock) {
1779 if (keysTyped.isEmpty()) {
1780 throw new NoSuchElementException("your program has already processed all keystrokes");
1781 }
1782 return keysTyped.remove(keysTyped.size() - 1);
1783 // return keysTyped.removeLast();
1784 }
1785 }
1786
1787 /**
1788 * Returns true if the given key is being pressed.
1789 * <p>
1790 * This method takes the keycode (corresponding to a physical key)
1791 * as an argument. It can handle action keys
1792 * (such as F1 and arrow keys) and modifier keys (such as shift and control).
1793 * See {@link KeyEvent} for a description of key codes.
1794 *
1795 * @param keycode the key to check if it is being pressed
1796 * @return {@code true} if {@code keycode} is currently being pressed;
1797 * {@code false} otherwise
1798 */
1799 public static boolean isKeyPressed(int keycode) {
1800 synchronized (keyLock) {
1801 return keysDown.contains(keycode);
1802 }
1803 }
1804
1805
1806 /**
1807 * This method cannot be called directly.
1808 */
1809 @Override
1810 public void keyTyped(KeyEvent e) {
1811 synchronized (keyLock) {
1812 keysTyped.addFirst(e.getKeyChar());
1813 }
1814 }
1815
1816 /**
1817 * This method cannot be called directly.
1818 */
1819 @Override
1820 public void keyPressed(KeyEvent e) {
1821 synchronized (keyLock) {
1822 keysDown.add(e.getKeyCode());
1823 }
1824 }
1825
1826 /**
1827 * This method cannot be called directly.
1828 */
1829 @Override
1830 public void keyReleased(KeyEvent e) {
1831 synchronized (keyLock) {
1832 keysDown.remove(e.getKeyCode());
1833 }
1834 }
1835
1836
1837
1838
1839 /**
1840 * Test client.
1841 *
1842 * @param args the command-line arguments
1843 */
1844 public static void main(String[] args) {
1845 StdDraw.square(0.2, 0.8, 0.1);
1846 StdDraw.filledSquare(0.8, 0.8, 0.2);
1847 StdDraw.circle(0.8, 0.2, 0.2);
1848
1849 StdDraw.setPenColor(StdDraw.BOOK_RED);
1850 StdDraw.setPenRadius(0.02);
1851 StdDraw.arc(0.8, 0.2, 0.1, 200, 45);
1852
1853 // draw a blue diamond
1854 StdDraw.setPenRadius();
1855 StdDraw.setPenColor(StdDraw.BOOK_BLUE);
1856 double[] x = { 0.1, 0.2, 0.3, 0.2 };
1857 double[] y = { 0.2, 0.3, 0.2, 0.1 };
1858 StdDraw.filledPolygon(x, y);
1859
1860 // text
1861 StdDraw.setPenColor(StdDraw.BLACK);
1862 StdDraw.text(0.2, 0.5, "black text");
1863 StdDraw.setPenColor(StdDraw.WHITE);
1864 StdDraw.text(0.8, 0.8, "white text");
1865 }
1866
1867}