/************************************************************************* * Compilation: javac StdDraw.java * Execution: java StdDraw * Dependencies: EpsDocument.java EpsException.java EpsGraphics2D.java * * Standard graphics library. * * For documentation, see http://www.cs.princeton.edu/introcs/15inout * * Todo * ---- * - Add support for CubicCurve2D or QudarticCurve2D or Arc2D * - Add support for gradient fill, etc. * * Remarks * ------- * - don't use AffineTransform for rescaling since it inverts * images and strings * - careful using setFont in inner loop within an animation - * it can cause flicker * *************************************************************************/ import java.io.*; import java.net.*; import java.awt.*; import java.awt.geom.*; import java.awt.event.*; import java.awt.image.*; import javax.swing.*; import javax.imageio.ImageIO; public class StdDraw implements ActionListener { // pre-defined colors public final static Color BLACK = Color.BLACK; public final static Color BLUE = Color.BLUE; public final static Color CYAN = Color.CYAN; public final static Color DARK_GRAY = Color.DARK_GRAY; public final static Color GRAY = Color.GRAY; public final static Color GREEN = Color.GREEN; public final static Color LIGHT_GRAY = Color.LIGHT_GRAY; public final static Color MAGENTA = Color.MAGENTA; public final static Color ORANGE = Color.ORANGE; public final static Color PINK = Color.PINK; public final static Color RED = Color.RED; public final static Color WHITE = Color.WHITE; public final static Color YELLOW = Color.YELLOW; // default colors public final static Color DEFAULT_PEN_COLOR = BLACK; public final static Color DEFAULT_CLEAR_COLOR = WHITE; // current pen color private static Color penColor; // default canvas size is SIZE-by-SIZE public final static int SIZE = 512; private static int width = SIZE; private static int height = SIZE; // default pen radius private final static double DEFAULT_PEN_RADIUS = 0.002; // current pen radius private static double penRadius; // show we draw immediately or wait until next show? private static boolean defer = false; // boundary of drawing canvas private final static double BORDER = 0.05; // 5 % border private final static double DEFAULT_XMIN = 0.0; private final static double DEFAULT_XMAX = 1.0; private final static double DEFAULT_YMIN = 0.0; private final static double DEFAULT_YMAX = 1.0; private static double xmin, ymin, xmax, ymax; // font private final static Font DEFAULT_FONT = new Font("Serif", Font.PLAIN, 16); private static Font font; // double buffered graphics private static BufferedImage offscreenImage, onscreenImage; private static Graphics2D offscreen, onscreen; private static EpsGraphics2D eps; // singleton for callbacks // solely used to avoid generating extra .class files from nested classes private static StdDraw std = new StdDraw(); // not instantiable private StdDraw() { } // the frame for drawing to the screen private static JFrame frame = new JFrame(); // static initializer static { init(); } public static void setCanvasSize(int w, int h) { width = w; height = h; init(); } // init private static void init() { offscreenImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); onscreenImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); offscreen = (Graphics2D) offscreenImage.getGraphics(); onscreen = (Graphics2D) onscreenImage.getGraphics(); eps = new EpsGraphics2D(); setXscale(); setYscale(); offscreen.setColor(DEFAULT_CLEAR_COLOR); offscreen.fillRect(0, 0, width, height); eps.setColor(DEFAULT_CLEAR_COLOR); eps.fillRect(0, 0, width, height); setPenColor(); setPenRadius(); setFont(); clear(); // add antialiasing RenderingHints hints = new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); hints.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); offscreen.addRenderingHints(hints); // frame stuff ImageIcon icon = new ImageIcon(onscreenImage); frame.setContentPane(new JLabel(icon)); frame.setResizable(false); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // closes all windows // frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); // closes only current window frame.setTitle("Standard Draw"); frame.setJMenuBar(createMenuBar()); frame.pack(); frame.setVisible(true); } // create the menu bar public static JMenuBar createMenuBar() { JMenuBar menuBar = new JMenuBar(); JMenu menu = new JMenu("File"); menuBar.add(menu); JMenuItem menuItem1 = new JMenuItem(" Save... "); menuItem1.addActionListener(std); menuItem1.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); menu.add(menuItem1); return menuBar; } // change the user coordinate system public static void setXscale() { setXscale(DEFAULT_XMIN, DEFAULT_XMAX); } public static void setYscale() { setYscale(DEFAULT_YMIN, DEFAULT_YMAX); } public static void setXscale(double min, double max) { double size = max - min; xmin = min - BORDER * size; xmax = max + BORDER * size; } public static void setYscale(double min, double max) { double size = max - min; ymin = min - BORDER * size; ymax = max + BORDER * size; } // helper functions that scale from user coordinates to screen coordinates private static double scaleX(double x) { return width * (x - xmin) / (xmax - xmin); } private static double scaleY(double y) { return height * (ymax - y) / (ymax - ymin); } private static double factorX(double w) { return w * width / Math.abs(xmax - xmin); } private static double factorY(double h) { return h * height / Math.abs(ymax - ymin); } // clear the screen with given color public static void clear() { clear(DEFAULT_CLEAR_COLOR); } public static void clear(Color color) { offscreen.setColor(color); offscreen.fillRect(0, 0, width, height); offscreen.setColor(penColor); eps.setColor(color); eps.fillRect(0, 0, width, height); eps.setColor(penColor); show(); } // set the pen size public static void setPenRadius() { setPenRadius(DEFAULT_PEN_RADIUS); } public static void setPenRadius(double r) { penRadius = r * SIZE; BasicStroke stroke = new BasicStroke((float) penRadius); offscreen.setStroke(stroke); eps.setStroke(stroke); } // set the pen color public static void setPenColor() { setPenColor(DEFAULT_PEN_COLOR); } public static void setPenColor(Color color) { penColor = color; offscreen.setColor(penColor); eps.setColor(penColor); } // write the given string in the current font public static void setFont() { setFont(DEFAULT_FONT); } public static void setFont(Font f) { font = f; } // draw a line from (x0, y0) to (x1, y1) public static void line(double x0, double y0, double x1, double y1) { offscreen.draw(new Line2D.Double(scaleX(x0), scaleY(y0), scaleX(x1), scaleY(y1))); eps.draw(new Line2D.Double(scaleX(x0), scaleY(y0), scaleX(x1), scaleY(y1))); show(); } // draw one pixel at (x, y) private static void pixel(double x, double y) { offscreen.fillRect((int) Math.round(scaleX(x)), (int) Math.round(scaleY(y)), 1, 1); eps.fillRect((int) Math.round(scaleX(x)), (int) Math.round(scaleY(y)), 1, 1); } // draw point at (x, y) public static void point(double x, double y) { double xs = scaleX(x); double ys = scaleY(y); double r = penRadius; //double ws = factorX(2*r); //double hs = factorY(2*r); //if (ws <= 1 && hs <= 1) pixel(x, y); if (r <= 1) pixel(x, y); else offscreen.fill(new Ellipse2D.Double(xs - r/2, ys - r/2, r, r)); eps.fill(new Ellipse2D.Double(xs - r/2, ys - r/2, r, r)); show(); } // draw circle of radius r, centered on (x, y); degenerate to pixel if small public static void circle(double x, double y, double r) { double xs = scaleX(x); double ys = scaleY(y); double ws = factorX(2*r); double hs = factorY(2*r); if (ws <= 1 && hs <= 1) pixel(x, y); else offscreen.draw(new Ellipse2D.Double(xs - ws/2, ys - hs/2, ws, hs)); eps.draw(new Ellipse2D.Double(xs - ws/2, ys - hs/2, ws, hs)); show(); } // draw filled circle of radius r, centered on (x, y); degenerate to pixel if small public static void filledCircle(double x, double y, double r) { double xs = scaleX(x); double ys = scaleY(y); double ws = factorX(2*r); double hs = factorY(2*r); if (ws <= 1 && hs <= 1) pixel(x, y); else offscreen.fill(new Ellipse2D.Double(xs - ws/2, ys - hs/2, ws, hs)); eps.fill(new Ellipse2D.Double(xs - ws/2, ys - hs/2, ws, hs)); show(); } // draw squared of side length 2r, centered on (x, y); degenerate to pixel if small public static void square(double x, double y, double r) { // screen coordinates double xs = scaleX(x); double ys = scaleY(y); double ws = factorX(2*r); double hs = factorY(2*r); if (ws <= 1 && hs <= 1) pixel(x, y); else offscreen.draw(new Rectangle2D.Double(xs - ws/2, ys - hs/2, ws, hs)); eps.draw(new Rectangle2D.Double(xs - ws/2, ys - hs/2, ws, hs)); show(); } // draw squared of side length 2r, centered on (x, y); degenerate to pixel if small public static void filledSquare(double x, double y, double r) { // screen coordinates double xs = scaleX(x); double ys = scaleY(y); double ws = factorX(2*r); double hs = factorY(2*r); if (ws <= 1 && hs <= 1) pixel(x, y); else offscreen.fill(new Rectangle2D.Double(xs - ws/2, ys - hs/2, ws, hs)); eps.fill(new Rectangle2D.Double(xs - ws/2, ys - hs/2, ws, hs)); show(); } // draw a polygon with the given (x[i], y[i]) coordinates public static void polygon(double[] x, double[] y) { int N = x.length; GeneralPath path = new GeneralPath(); path.moveTo((float) scaleX(x[0]), (float) scaleY(y[0])); for (int i = 0; i < N; i++) path.lineTo((float) scaleX(x[i]), (float) scaleY(y[i])); path.closePath(); offscreen.draw(path); eps.draw(path); show(); } // draw a filled polygon with the given (x[i], y[i]) coordinates public static void filledPolygon(double[] x, double[] y) { int N = x.length; GeneralPath path = new GeneralPath(); path.moveTo((float) scaleX(x[0]), (float) scaleY(y[0])); for (int i = 0; i < N; i++) path.lineTo((float) scaleX(x[i]), (float) scaleY(y[i])); path.closePath(); offscreen.fill(path); eps.fill(path); show(); } // get an image from the given filename private static Image getImage(String filename) { // to read from file ImageIcon icon = new ImageIcon(filename); // try to read from URL if ((icon == null) || (icon.getImageLoadStatus() != MediaTracker.COMPLETE)) { try { URL url = new URL(filename); icon = new ImageIcon(url); } catch(Exception e) { } } // in case file is inside a .jar if ((icon == null) || (icon.getImageLoadStatus() != MediaTracker.COMPLETE)) { URL url = StdDraw.class.getResource(filename); if (url == null) throw new RuntimeException("image " + filename + " not found"); icon = new ImageIcon(url); } return icon.getImage(); } // draw picture (gif, jpg, or png) centered on (x, y) public static void picture(double x, double y, String s) { Image image = getImage(s); double xs = scaleX(x); double ys = scaleY(y); int ws = image.getWidth(null); int hs = image.getHeight(null); offscreen.drawImage(image, (int) (xs - ws/2.0), (int) (ys - hs/2.0), null); eps.drawImage(image, (int) (xs - ws/2.0), (int) (ys - hs/2.0), null); show(); } // write the given text string in the current font, center on (x, y) public static void text(double x, double y, String s) { offscreen.setFont(font); eps.setFont(font); FontMetrics metrics = offscreen.getFontMetrics(); double xs = scaleX(x); double ys = scaleY(y); int ws = metrics.stringWidth(s); int hs = metrics.getDescent(); offscreen.drawString(s, (float) (xs - ws/2.0), (float) (ys + hs)); eps.drawString(s, (float) (xs - ws/2.0), (float) (ys + hs)); show(); } // display on screen and pause for t miliseconds public static void show(int t) { defer = true; onscreen.drawImage(offscreenImage, 0, 0, null); frame.repaint(); try { Thread.currentThread().sleep(t); } catch (InterruptedException e) { } } // view on-screen, creating new frame if necessary public static void show() { if (!defer) onscreen.drawImage(offscreenImage, 0, 0, null); if (!defer) frame.repaint(); } // save to file - suffix can be png, jpg, or gif public static void save(String filename) { File file = new File(filename); String suffix = filename.substring(filename.lastIndexOf('.') + 1).toLowerCase(); if (suffix.equals("eps")) { PrintWriter out = null; try { out = new PrintWriter(new FileOutputStream(filename), true); } catch(IOException ioe) { ioe.printStackTrace(); } out.println(eps); out.close(); } else if (suffix.equals("png") || suffix.equals("jpeg") || suffix.equals("jpg")) { try { ImageIO.write(offscreenImage, suffix, file); } catch (IOException e) { e.printStackTrace(); } } else { System.out.println("Illegal file extension: " + filename); } } // open a save dialog when the user selects "Save As" from the menu public void actionPerformed(ActionEvent e) { FileDialog chooser = new FileDialog(StdDraw.frame, "Use a .png, .jpg, or .eps extension", FileDialog.SAVE); chooser.setVisible(true); String filename = chooser.getFile(); if (filename != null) { StdDraw.save(chooser.getDirectory() + File.separator + chooser.getFile()); } } // test client public static void main(String args[]) { StdDraw.square(.2, .8, .1); StdDraw.filledSquare(.8, .8, .2); StdDraw.circle(.8, .2, .2); // draw a blue diamond StdDraw.setPenColor(StdDraw.BLUE); double[] x = { .1, .2, .3, .2 }; double[] y = { .2, .3, .2, .1 }; StdDraw.filledPolygon(x, y); // text StdDraw.text(0.8, 0.2, "centered"); } }