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}