/******************************************************************************
*
Compilation: javac StdDraw.java
*
Execution: java StdDraw
*
Dependencies: none
*
*
Standard drawing library. This class provides a basic capability for
*
creating drawings with your programs. It uses a simple graphics model
that
*
allows you to create drawings consisting of points, lines, and curves
* in a
window on your computer and to save the drawings to a file.
*
* Todo
* ----
*
- Add support for gradient fill,
etc.
*
- Fix setCanvasSize() so that it
can only be called once.
*
- On some systems, drawing a line
(or other shape) that extends way
*
beyond canvas (e.g., to infinity) dimensions does not get drawn.
*
* Remarks
*
-------
*
- don't use AffineTransform for
rescaling since it inverts
*
images and strings
*
******************************************************************************/
package
turtlehilbert;
import
java.awt.BasicStroke;
import java.awt.Color;
import
java.awt.FileDialog;
import
java.awt.Font;
import
java.awt.FontMetrics;
import
java.awt.Graphics2D;
import
java.awt.Image;
import
java.awt.MediaTracker;
import
java.awt.RenderingHints;
import
java.awt.Toolkit;
import
java.awt.event.ActionEvent;
import
java.awt.event.ActionListener;
import
java.awt.event.MouseEvent;
import
java.awt.event.MouseListener;
import
java.awt.event.MouseMotionListener;
import
java.awt.event.KeyEvent;
import
java.awt.event.KeyListener;
import
java.awt.geom.Arc2D;
import
java.awt.geom.Ellipse2D;
import
java.awt.geom.GeneralPath;
import
java.awt.geom.Line2D;
import
java.awt.geom.Rectangle2D;
import
java.awt.image.BufferedImage;
import
java.awt.image.DirectColorModel;
import
java.awt.image.WritableRaster;
import
java.io.File;
import
java.io.IOException;
import
java.net.MalformedURLException;
import
java.net.URL;
import
java.util.LinkedList;
import
java.util.TreeSet;
import
java.util.NoSuchElementException;
import
javax.imageio.ImageIO;
import
javax.swing.ImageIcon;
import
javax.swing.JFrame;
import
javax.swing.JLabel;
import
javax.swing.JMenu;
import
javax.swing.JMenuBar;
import
javax.swing.JMenuItem;
import
javax.swing.KeyStroke;
/**
* The
{@code StdDraw} class provides a basic capability for
*
creating drawings with your programs. It uses a simple graphics model
that
*
allows you to create drawings consisting of points, lines, squares,
*
circles, and other geometric shapes in a window on your computer and
* to
save the drawings to a file. Standard drawing also includes
*
facilities for text, color, pictures, and animation, along with
* user
interaction via the keyboard and mouse.
*
<p>
*
<b>Getting started.</b>
* To
use standard drawing, you must have {@code StdDraw.class} in your
* Java
classpath. If you used our autoinstaller, you should be all set.
*
Otherwise, download
* <a
href =
"http://introcs.cs.princeton.edu/java/stdlib/StdDraw.java">StdDraw.java</a>
* and
put a copy in your working directory.
* <p>
* Now,
type the following short program into your editor:
*
<pre>
*
public class TestStdDraw {
*
public static void main(String[] args) {
*
StdDraw.setPenRadius(0.05);
*
StdDraw.setPenColor(StdDraw.BLUE);
*
StdDraw.point(0.5, 0.5);
*
StdDraw.setPenColor(StdDraw.MAGENTA);
*
StdDraw.line(0.2, 0.2, 0.8, 0.2);
*
}
* }
*
</pre>
* If
you compile and execute the program, you should see a window
*
appear with a thick magenta line and a blue point.
* This
program illustrates the two main types of methods in standard
*
drawing—methods that draw geometric shapes and methods that
*
control drawing parameters.
* The
methods {@code StdDraw.line()} and {@code StdDraw.point()}
* draw
lines and points; the methods {@code StdDraw.setPenRadius()}
* and
{@code StdDraw.setPenColor()} control the line thickness and color.
*
<p>
*
<b>Points and lines.</b>
* You
can draw points and line segments with the following methods:
*
<ul>
*
<li> {@link #point(double x, double y)}
*
<li> {@link #line(double x1, double y1, double x2, double y2)}
*
</ul>
*
<p>
* The
<em>x</em>- and <em>y</em>-coordinates must be in the
drawing area
*
(between 0 and 1 and by default) or the points and lines will not be
visible.
*
<p>
*
<b>Squares, circles, rectangles, and ellipses.</b>
* You
can draw squares, circles, rectangles, and ellipses using
* the
following methods:
*
<ul>
*
<li> {@link #circle(double x, double y, double radius)}
*
<li> {@link #ellipse(double x, double y, double semiMajorAxis,
double semiMinorAxis)}
*
<li> {@link #square(double x, double y, double radius)}
*
<li> {@link #rectangle(double x, double y, double halfWidth,
double halfHeight)}
*
</ul>
*
<p>
* All
of these methods take as arguments the location and size of the shape.
* The
location is always specified by the <em>x</em>- and
<em>y</em>-coordinates
* of
its <em>center</em>.
* The
size of a circle is specified by its radius and the size of an ellipse is
*
specified by the lengths of its semi-major and semi-minor axes.
* The
size of a square or rectangle is specified by its half-width or half-height.
* The
convention for drawing squares and rectangles is parallel to those for
*
drawing circles and ellipses, but may be unexpected to the uninitiated.
*
<p>
* The
methods above trace outlines of the given shapes. The following methods
* draw
filled versions:
*
<ul>
*
<li> {@link #filledCircle(double x, double y, double radius)}
*
<li> {@link #filledEllipse(double x, double y, double
semiMajorAxis, double semiMinorAxis)}
*
<li> {@link #filledSquare(double x, double y, double radius)}
*
<li> {@link #filledRectangle(double x, double y, double halfWidth,
double halfHeight)}
*
</ul>
*
<p>
*
<b>Circular arcs.</b>
* You
can draw circular arcs with the following method:
*
<ul>
*
<li> {@link #arc(double x, double y, double radius, double angle1,
double angle2)}
*
</ul>
*
<p>
* The
arc is from the circle centered at (<em>x</em>,
<em>y</em>) of the specified radius.
* The
arc extends from angle1 to angle2. By convention, the angles are
*
<em>polar</em> (counterclockwise angle from the
<em>x</em>-axis)
* and
represented in degrees. For example, {@code StdDraw.arc(0.0, 0.0, 1.0, 0, 90)}
* draws
the arc of the unit circle from 3 o'clock (0 degrees) to 12 o'clock (90
degrees).
*
<p>
*
<b>Polygons.</b>
* You
can draw polygons with the following methods:
*
<ul>
*
<li> {@link #polygon(double[] x, double[] y)}
*
<li> {@link #filledPolygon(double[] x, double[] y)}
*
</ul>
*
<p>
* The
points in the polygon are ({@code x[i]}, {@code y[i]}).
* For
example, the following code fragment draws a filled diamond
* with
vertices (0.1, 0.2), (0.2, 0.3), (0.3, 0.2), and (0.2, 0.1):
*
<pre>
*
double[] x = { 0.1, 0.2, 0.3, 0.2 };
*
double[] y = { 0.2, 0.3, 0.2, 0.1 };
*
StdDraw.filledPolygon(x, y);
*
</pre>
*
<p>
*
<b>Pen size.</b>
* The
pen is circular, so that when you set the pen radius to <em>r</em>
* and
draw a point, you get a circle of radius <em>r</em>. Also, lines
are
* of
thickness 2<em>r</em> and have rounded ends. The default pen radius
* is
0.005 and is not affected by coordinate scaling. This default pen
*
radius is about 1/200 the width of the default canvas, so that if
* you
draw 100 points equally spaced along a horizontal or vertical line,
* you
will be able to see individual circles, but if you draw 200 such
*
points, the result will look like a line.
* <ul>
* <li> {@link #setPenRadius(double radius)}
* </ul>
*
<p>
* For
example, {@code StdDraw.setPenRadius(0.025)} makes
* the
thickness of the lines and the size of the points to be five times
* the
0.005 default.
* To
draw points with the minimum possible radius (one pixel on typical
*
displays), set the pen radius to 0.0.
*
<p>
*
<b>Pen color.</b>
* All
geometric shapes (such as points, lines, and circles) are drawn using
* the
current pen color. By default, it is black.
* You
can change the pen color with the following methods:
*
<ul>
*
<li> {@link #setPenColor(int red, int green, int blue)}
*
<li> {@link #setPenColor(Color color)}
*
</ul>
*
<p>
* The
first method allows you to specify colors using the RGB color system.
* This
<a href = "http://johndyer.name/lab/colorpicker/">color
picker</a>
* is a
convenient way to find a desired color.
* The
second method allows you to specify colors using the
*
{@link Color} data type that is discussed in Chapter 3. Until then,
* you
can use this method with one of these predefined colors in standard drawing:
*
{@link #BLACK}, {@link #BLUE}, {@link #CYAN}, {@link #DARK_GRAY}, {@link
#GRAY},
*
{@link #GREEN}, {@link #LIGHT_GRAY}, {@link #MAGENTA}, {@link #ORANGE},
*
{@link #PINK}, {@link #RED}, {@link #WHITE}, and {@link #YELLOW}.
* For
example, {@code StdDraw.setPenColor(StdDraw.MAGENTA)} sets the
* pen
color to magenta.
*
<p>
*
<b>Canvas size.</b>
* By
default, all drawing takes places in a 512-by-512 canvas.
* The
canvas does not include the window title or window border.
* You
can change the size of the canvas with the following method:
*
<ul>
*
<li> {@link #setCanvasSize(int width, int height)}
*
</ul>
*
<p>
* This
sets the canvas size to be
<em>width</em>-by-<em>height</em> pixels.
* It
also erases the current drawing and resets the coordinate system,
* pen
radius, pen color, and font back to their default values.
* Ordinarly,
this method is called once, at the very beginning of a program.
* For
example, {@code StdDraw.setCanvasSize(800, 800)}
* sets
the canvas size to be 800-by-800 pixels.
*
<p>
*
<b>Canvas scale and coordinate system.</b>
* By
default, all drawing takes places in the unit square, with (0, 0) at
* lower
left and (1, 1) at upper right. You can change the default
*
coordinate system with the following methods:
*
<ul>
*
<li> {@link #setXscale(double xmin, double xmax)}
*
<li> {@link #setYscale(double ymin, double ymax)}
*
<li> {@link #setScale(double min, double max)}
*
</ul>
*
<p>
* The
arguments are the coordinates of the minimum and maximum
*
<em>x</em>- or <em>y</em>-coordinates that will
appear in the canvas.
* For
example, if you wish to use the default
coordinate system but
* leave
a small margin, you can call {@code StdDraw.setScale(-.05, 1.05)}.
*
<p>
* These
methods change the coordinate system for subsequent drawing
*
commands; they do not affect previous drawings.
* These
methods do not change the canvas size; so, if the <em>x</em>-
* and
<em>y</em>-scales are different, squares will become rectangles
* and
circles will become ellipsoidal.
*
<p>
*
<b>Text.</b>
* You
can use the following methods to annotate your drawings with text:
*
<ul>
*
<li> {@link #text(double x, double y, String text)}
*
<li> {@link #text(double x, double y, String text, double
degrees)}
*
<li> {@link #textLeft(double x, double y, String text)}
*
<li> {@link #textRight(double x, double y, String text)}
*
</ul>
*
<p>
* The
first two methods write the specified text in the current font,
*
centered at (<em>x</em>, <em>y</em>).
* The
second method allows you to rotate the text.
* The
last two methods either left- or right-align the text at
(<em>x</em>, <em>y</em>).
*
<p>
* The
default font is a Sans Serif font with point size 16.
* You
can use the following method to change the font:
* <ul>
* <li> {@link #setFont(Font font)}
*
</ul>
*
<p>
* You
use the {@link Font} data type to specify the font. This allows you to
*
choose the face, size, and style of the font. For example, the following
* code
fragment sets the font to Arial Bold, 60 point.
*
<pre>
* Font
font = new Font("Arial", Font.BOLD, 60);
*
StdDraw.setFont(font);
*
StdDraw.text(0.5, 0.5, "Hello, World");
*
</pre>
*
<p>
*
<b>Images.</b>
* You
can use the following methods to add images to your drawings:
*
<ul>
*
<li> {@link #picture(double x, double y, String filename)}
*
<li> {@link #picture(double x, double y, String filename, double
degrees)}
*
<li> {@link #picture(double x, double y, String filename, double
scaledWidth, double scaledHeight)}
*
<li> {@link #picture(double x, double y, String filename, double
scaledWidth, double scaledHeight, double degrees)}
*
</ul>
*
<p>
* These
methods draw the specified image, centered at (<em>x</em>,
<em>y</em>).
* The
supported image formats are JPEG, PNG, and GIF.
* The
image will display at its native size, independent of the coordinate system.
*
Optionally, you can rotate the image a specified number of degrees
counterclockwise
* or
rescale it to fit snugly inside a width-by-height bounding box.
*
<p>
* <b>Saving
to a file.</b>
* You
save your image to a file using the <em>File ? Save</em> menu
option.
* You
can also save a file programatically using the following method:
*
<ul>
*
<li> {@link #save(String filename)}
*
</ul>
*
<p>
* The
supported image formats are JPEG and PNG. The filename must have either the
*
extension .jpg or .png.
* We
recommend using PNG for drawing that consist solely of geometric shapes and
JPEG
* for
drawings that contains pictures.
*
<p>
*
<b>Clearing the canvas.</b>
* To
clear the entire drawing canvas, you can use the following methods:
*
<ul>
*
<li> {@link #clear()}
*
<li> {@link #clear(Color color)}
*
</ul>
*
<p>
* The
first method clears the canvas to white; the second method
*
allows you to specify a color of your choice. For example,
*
{@code StdDraw.clear(StdDraw.LIGHT_GRAY)} clears the canvas to a shade
* of
gray.
*
<p>
*
<b>Computer animations and double buffering.</b>
*
Double buffering is one of the most powerful features of standard
drawing,
*
enabling computer animations.
* The
following methods control the way in which objects are drawn:
*
<ul>
*
<li> {@link #enableDoubleBuffering()}
*
<li> {@link #disableDoubleBuffering()}
* <li> {@link #show()}
* <li> {@link #pause(int t)}
*
</ul>
*
<p>
* By
default, double buffering is disabled, which means that as soon as you
* call
a drawing
*
method—such as {@code point()} or {@code line()}—the
* results
appear on the screen.
*
<p>
* When
double buffering is enabled by calling {@link #enableDoubleBuffering()},
* all
drawing takes place on the <em>offscreen canvas</em>. The offscreen
canvas
* is
not displayed. Only when you call
*
{@link #show()} does your drawing get copied from the offscreen canvas
to
* the
onscreen canvas, where it is displayed in the standard drawing window. You
* can
think of double buffering as collecting all of the lines, points, shapes,
* and
text that you tell it to draw, and then drawing them all
*
<em>simultaneously</em>, upon request.
*
<p>
* The
most important use of double buffering is to produce computer
*
animations, creating the illusion of motion by rapidly
*
displaying static drawings. To produce an animation, repeat
* the
following four steps:
*
<ul>
*
<li> Clear the offscreen canvas.
*
<li> Draw objects on the offscreen canvas.
*
<li> Copy the offscreen canvas to the onscreen canvas.
*
<li> Wait for a short while.
*
</ul>
*
<p>
* The
{@link #clear()}, {@link #show()}, and {@link #pause(int t)} methods
*
support the first, third, and fourth of these steps, respectively.
*
<p>
* For
example, this code fragment animates two balls moving in a circle.
*
<pre>
* StdDraw.setScale(-2,
+2);
*
StdDraw.enableDoubleBuffering();
*
* for
(double t = 0.0; true; t += 0.02) {
*
double x = Math.sin(t);
*
double y = Math.cos(t);
*
StdDraw.clear();
*
StdDraw.filledCircle(x, y, 0.05);
*
StdDraw.filledCircle(-x, -y, 0.05);
*
StdDraw.show();
*
StdDraw.pause(20);
* }
*
</pre>
*
<p>
*
<b>Keyboard and mouse inputs.</b>
*
Standard drawing has very basic support for keyboard and mouse input.
* It is
much less powerful than most user interface libraries provide, but also much
simpler.
* You
can use the following methods to intercept mouse events:
* <ul>
* <li> {@link #mousePressed()}
* <li> {@link #mouseX()}
* <li> {@link #mouseY()}
* </ul>
*
<p>
* The
first method tells you whether a mouse button is currently being pressed.
* The
last two methods tells you the <em>x</em>- and
<em>y</em>-coordinates of the mouse's
*
current position, using the same coordinate system as the canvas (the
unit square, by default).
* You
should use these methods in an animation loop that waits a short while before
trying
* to
poll the mouse for its current state.
* You
can use the following methods to intercept keyboard events:
*
<ul>
*
<li> {@link #hasNextKeyTyped()}
*
<li> {@link #nextKeyTyped()}
*
<li> {@link #isKeyPressed(int keycode)}
*
</ul>
*
<p>
* If
the user types lots of keys, they will be saved in a list until you process
them.
* The
first method tells you whether the user has typed a key (that your program has
* not
yet processed).
* The
second method returns the next key that the user typed (that your program has
* not
yet processed) and removes it from the list of saved keystrokes.
* The
third method tells you whether a key is currently being pressed.
*
<p>
*
<b>Accessing control parameters.</b>
* You
can use the following methods to access the current pen color, pen radius,
* and
font:
*
<ul>
*
<li> {@link #getPenColor()}
* <li> {@link #getPenRadius()}
* <li> {@link #getFont()}
*
</ul>
*
<p>
* These
methods are useful when you want to temporarily change a
*
control parameter and reset it back to its original value.
*
<p>
*
<b>Corner cases.</b>
* To
avoid clutter, the API doesn't explicitly refer to arguments that are
* null,
infinity, or NaN.
*
<ul>
*
<li> Any method that is passed a {@code null} argument will throw
an
*
{@link IllegalArgumentException}.
*
<li> Except as noted in the APIs, drawing an object outside (or
partly outside)
*
the canvas is permitted—however, only the part of the object that
*
appears inside the canvas will be visible.
*
<li> Except as noted in the APIs, all methods accept {@link
Double#NaN},
*
{@link Double#POSITIVE_INFINITY}, and {@link Double#NEGATIVE_INFINITY}
*
as arugments. An object drawn with an <em>x</em>- or
<em>y</em>-coordinate
*
that is NaN will behave as if it is outside the canvas, and will not be
visible.
*
</ul>
*
<p>
*
<b>Performance tricks.</b>
*
Standard drawing is capable of drawing large amounts of data.
* Here
are a few tricks and tips:
*
<ul>
*
<li> Use <em>double buffering</em> for static drawing
with a large
*
number of objects.
*
That is, call {@link #enableDoubleBuffering()} before
*
the sequence of drawing commands and call {@link #show()} afterwards.
*
Incrementally displaying a complex drawing while it is being
*
created can be intolerably inefficient on many computer systems.
*
<li> When drawing computer animations, call {@code show()}
*
only once per frame, not after drawing each individual object.
*
<li> If you call {@code picture()} multiple times with the same
filename,
*
Java will cache the image, so you do not incur the cost of reading
*
from a file each time.
*
</ul>
*
<p>
*
<b>Known bugs and issues.</b>
*
<ul>
*
<li> The {@code picture()} methods may not draw the portion of the
image that is
*
inside the canvas if the center point (<em>x</em>,
<em>y</em>) is outside the
*
canvas.
*
This bug appears only on some systems.
*
<li> Some methods may not draw the portion of the geometric object
that is inside the
*
canvas if the <em>x</em>- or
<em>y</em>-coordinates are infinite.
*
This bug appears only on some systems.
*
</ul>
*
<p>
*
<b>Reference.</b>
* For
additional documentation,
* see
<a href="http://introcs.cs.princeton.edu/15inout">Section
1.5</a> of
*
<em>Computer Science: An Interdisciplinary Approach</em>
* by
Robert Sedgewick and Kevin Wayne.
*
*
@author Robert Sedgewick
*
@author Kevin Wayne
*/
public
final class StdDraw implements ActionListener, MouseListener,
MouseMotionListener, KeyListener {
/**
*
The color black.
*/
public static final Color BLACK =
Color.BLACK;
/**
*
The color blue.
*/
public static final Color BLUE =
Color.BLUE;
/**
*
The color cyan.
*/
public static final Color CYAN =
Color.CYAN;
/**
*
The color dark gray.
*/
public static final Color DARK_GRAY =
Color.DARK_GRAY;
/**
*
The color gray.
*/
public static final Color GRAY =
Color.GRAY;
/**
*
The color green.
*/
public static final Color GREEN = Color.GREEN;
/**
*
The color light gray.
*/
public static final Color LIGHT_GRAY =
Color.LIGHT_GRAY;
/**
*
The color magenta.
*/
public static final Color MAGENTA =
Color.MAGENTA;
/**
*
The color orange.
*/
public static final Color ORANGE =
Color.ORANGE;
/**
*
The color pink.
*/
public static final Color PINK =
Color.PINK;
/**
*
The color red.
*/
public static final Color RED = Color.RED;
/**
*
The color white.
*/
public static final Color WHITE =
Color.WHITE;
/**
*
The color yellow.
*/
public static final Color YELLOW =
Color.YELLOW;
/**
* Shade of blue used in
<em>Introduction to Programming in Java</em>.
* It is Pantone 300U. The RGB values are
approximately (9, 90, 166).
*/
public static final Color BOOK_BLUE = new
Color(9, 90, 166);
/**
* Shade of light blue used in
<em>Introduction to Programming in Java</em>.
* The RGB values are approximately (103,
198, 243).
*/
public static final Color BOOK_LIGHT_BLUE =
new Color(103, 198, 243);
/**
* Shade of red used in
<em>Algorithms, 4th edition</em>.
* It is Pantone 1805U. The RGB values are
approximately (150, 35, 31).
*/
public static final Color BOOK_RED = new
Color(150, 35, 31);
// default colors
private static final Color
DEFAULT_PEN_COLOR = BLACK;
private static final Color
DEFAULT_CLEAR_COLOR = WHITE;
// current pen color
private static Color penColor;
// default canvas size is
DEFAULT_SIZE-by-DEFAULT_SIZE
private static final int DEFAULT_SIZE =
512;
private static int width = DEFAULT_SIZE;
private static int height = DEFAULT_SIZE;
// default pen radius
private static final 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, 0% border
// private static final double BORDER =
0.05;
private static final double BORDER = 0.00;
private static final double DEFAULT_XMIN =
0.0;
private static final double DEFAULT_XMAX =
1.0;
private static final double DEFAULT_YMIN =
0.0;
private static final double DEFAULT_YMAX =
1.0;
private static double xmin, ymin, xmax,
ymax;
// for synchronization
private static Object mouseLock = new
Object();
private static Object keyLock = new
Object();
// default font
private static final Font DEFAULT_FONT =
new Font("SansSerif", Font.PLAIN, 16);
// current font
private static Font font;
// double buffered graphics
private static BufferedImage
offscreenImage, onscreenImage;
private static Graphics2D offscreen,
onscreen;
// singleton for callbacks: avoids
generation of extra .class files
private static StdDraw std = new StdDraw();
// the frame for drawing to the screen
private static JFrame frame;
// mouse state
private static boolean mousePressed =
false;
private static double mouseX = 0;
private static double mouseY = 0;
// queue of typed key characters
private static LinkedList<Character>
keysTyped = new LinkedList<Character>();
// set of key codes currently pressed down
private static TreeSet<Integer>
keysDown = new TreeSet<Integer>();
// time in milliseconds (from
currentTimeMillis()) when we can draw again
// used to control the frame rate
private static long nextDraw = -1;
// singleton pattern: client can't
instantiate
private StdDraw() { }
// static initializer
static {
init();
}
/**
* Sets the canvas (drawing area) to be
512-by-512 pixels.
* This also erases the current drawing and
resets the coordinate system,
* pen radius, pen color, and font back to
their default values.
* Ordinarly, this method is called once,
at the very beginning
* of a program.
*/
public static void setCanvasSize() {
setCanvasSize(DEFAULT_SIZE,
DEFAULT_SIZE);
}
/**
* Sets the canvas (drawing area) to be
<em>width</em>-by-<em>height</em> pixels.
* This also erases the current drawing and
resets the coordinate system,
* pen radius, pen color, and font back to
their default values.
* Ordinarly, this method is called once,
at the very beginning
* of a program.
*
* @param
canvasWidth the width as a number of pixels
* @param
canvasHeight the height as a number of pixels
* @throws IllegalArgumentException unless
both {@code canvasWidth} and
*
{@code canvasHeight} are positive
*/
public static void setCanvasSize(int
canvasWidth, int canvasHeight) {
if (canvasWidth <= 0 || canvasHeight
<= 0)
throw new
IllegalArgumentException("width and height must be positive");
width = canvasWidth;
height = canvasHeight;
init();
}
// init
private static void init() {
if (frame != null)
frame.setVisible(false);
frame = new JFrame();
offscreenImage = new
BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
onscreenImage = new BufferedImage(width, height,
BufferedImage.TYPE_INT_ARGB);
offscreen =
offscreenImage.createGraphics();
onscreen = onscreenImage.createGraphics();
setXscale();
setYscale();
offscreen.setColor(DEFAULT_CLEAR_COLOR);
offscreen.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);
JLabel draw = new JLabel(icon);
draw.addMouseListener(std);
draw.addMouseMotionListener(std);
frame.setContentPane(draw);
frame.addKeyListener(std); // JLabel cannot get keyboard focus
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.requestFocusInWindow();
frame.setVisible(true);
}
// create the menu bar (changed to private)
private 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;
}
/***************************************************************************
*
User and screen coordinate systems.
***************************************************************************/
/**
* Sets the <em>x</em>-scale to
be the default (between 0.0 and 1.0).
*/
public static void setXscale() {
setXscale(DEFAULT_XMIN, DEFAULT_XMAX);
}
/**
* Sets the <em>y</em>-scale to
be the default (between 0.0 and 1.0).
*/
public static void setYscale() {
setYscale(DEFAULT_YMIN, DEFAULT_YMAX);
}
/**
* Sets the <em>x</em>-scale
and <em>y</em>-scale to be the default
* (between 0.0 and 1.0).
*/
public static void setScale() {
setXscale();
setYscale();
}
/**
* Sets the <em>x</em>-scale to
the specified range.
*
* @param
min the minimum value of the <em>x</em>-scale
* @param
max the maximum value of the <em>x</em>-scale
* @throws IllegalArgumentException if
{@code (max == min)}
*/
public static void setXscale(double min,
double max) {
double size = max - min;
if (size == 0.0) throw new
IllegalArgumentException("the min and max are the same");
synchronized (mouseLock) {
xmin = min - BORDER * size;
xmax = max + BORDER * size;
}
}
/**
* Sets the <em>y</em>-scale to
the specified range.
*
* @param
min the minimum value of the <em>y</em>-scale
* @param
max the maximum value of the <em>y</em>-scale
* @throws IllegalArgumentException if
{@code (max == min)}
*/
public static void setYscale(double min,
double max) {
double size = max - min;
if (size == 0.0) throw new
IllegalArgumentException("the min and max are the same");
synchronized (mouseLock) {
ymin = min - BORDER * size;
ymax = max + BORDER * size;
}
}
/**
* Sets both the
<em>x</em>-scale and <em>y</em>-scale to the (same)
specified range.
*
* @param
min the minimum value of the <em>x</em>- and
<em>y</em>-scales
* @param
max the maximum value of the <em>x</em>- and
<em>y</em>-scales
* @throws IllegalArgumentException if
{@code (max == min)}
*/
public static void setScale(double min,
double max) {
double size = max - min;
if (size == 0.0) throw new
IllegalArgumentException("the min and max are the same");
synchronized (mouseLock) {
xmin = min - BORDER * size;
xmax = max + BORDER * size;
ymin = min - BORDER * size;
ymax = max + BORDER * size;
}
}
// helper functions that scale from user
coordinates to screen coordinates and back
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);
}
private static double userX(double x) { return xmin + x * (xmax -
xmin) / width; }
private static double userY(double y) { return ymax - y * (ymax -
ymin) / height; }
/**
* Clears the screen to the default color
(white).
*/
public static void clear() {
clear(DEFAULT_CLEAR_COLOR);
}
/**
* Clears the screen to the specified
color.
*
*
@param color the color to make the background
*/
public static void clear(Color color) {
offscreen.setColor(color);
offscreen.fillRect(0, 0, width,
height);
offscreen.setColor(penColor);
draw();
}
/**
* Returns the current pen radius.
*
* @return the current value of the pen
radius
*/
public static double getPenRadius() {
return penRadius;
}
/**
* Sets the pen size to the default size
(0.002).
* The pen is circular, so that lines have
rounded ends, and when you set the
* pen radius and draw a point, you get a
circle of the specified radius.
* The pen radius is not affected by
coordinate scaling.
*/
public static void setPenRadius() {
setPenRadius(DEFAULT_PEN_RADIUS);
}
/**
* Sets the radius of the pen to the
specified size.
* The pen is circular, so that lines have
rounded ends, and when you set the
* pen radius and draw a point, you get a
circle of the specified radius.
* The pen radius is not affected by
coordinate scaling.
*
* @param
radius the radius of the pen
* @throws IllegalArgumentException if
{@code radius} is negative
*/
public static void setPenRadius(double
radius) {
if (!(radius >= 0)) throw new
IllegalArgumentException("pen radius must be nonnegative");
penRadius = radius;
float scaledPenRadius = (float) (radius
* DEFAULT_SIZE);
BasicStroke stroke = new
BasicStroke(scaledPenRadius, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);
// BasicStroke stroke = new
BasicStroke(scaledPenRadius);
offscreen.setStroke(stroke);
}
/**
* Returns the current pen color.
*
* @return the current pen color
*/
public static Color getPenColor() {
return penColor;
}
/**
* Set the pen color to the default color
(black).
*/
public static void setPenColor() {
setPenColor(DEFAULT_PEN_COLOR);
}
/**
* Sets the pen color to the specified
color.
* <p>
* The predefined pen colors are
* {@code StdDraw.BLACK}, {@code
StdDraw.BLUE}, {@code StdDraw.CYAN},
* {@code StdDraw.DARK_GRAY}, {@code
StdDraw.GRAY}, {@code StdDraw.GREEN},
* {@code StdDraw.LIGHT_GRAY}, {@code
StdDraw.MAGENTA}, {@code StdDraw.ORANGE},
* {@code StdDraw.PINK}, {@code
StdDraw.RED}, {@code StdDraw.WHITE}, and
* {@code StdDraw.YELLOW}.
*
* @param color the color to make the pen
*/
public static void setPenColor(Color color)
{
if (color == null) throw new
IllegalArgumentException();
penColor = color;
offscreen.setColor(penColor);
}
/**
* Sets the pen color to the specified RGB
color.
*
* @param
red the amount of red (between 0 and 255)
* @param
green the amount of green (between 0 and 255)
* @param
blue the amount of blue (between 0 and 255)
*
@throws IllegalArgumentException if {@code red}, {@code green},
*
or {@code blue} is outside its prescribed range
*/
public static void setPenColor(int red, int
green, int blue) {
if (red < 0 || red >= 256) throw new IllegalArgumentException("amount
of red must be between 0 and 255");
if (green < 0 || green >= 256)
throw new IllegalArgumentException("amount of green must be between 0 and
255");
if (blue < 0 || blue >= 256) throw new
IllegalArgumentException("amount of blue must be between 0 and 255");
setPenColor(new Color(red, green,
blue));
}
/**
* Returns the current font.
*
* @return the current font
*/
public static Font getFont() {
return font;
}
/**
* Sets the font to the default font (sans
serif, 16 point).
*/
public static void setFont() {
setFont(DEFAULT_FONT);
}
/**
* Sets the font to the specified value.
*
* @param font the font
*/
public static void setFont(Font font) {
if (font == null) throw new
IllegalArgumentException();
StdDraw.font = font;
}
/***************************************************************************
*
Drawing geometric shapes.
***************************************************************************/
/**
* Draws a line segment between
(<em>x</em><sub>0</sub>,
<em>y</em><sub>0</sub>) and
*
(<em>x</em><sub>1</sub>,
<em>y</em><sub>1</sub>).
*
* @param
x0 the <em>x</em>-coordinate of one endpoint
* @param
y0 the <em>y</em>-coordinate of one endpoint
* @param
x1 the <em>x</em>-coordinate of the other endpoint
* @param
y1 the <em>y</em>-coordinate of the other endpoint
*/
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)));
draw();
}
/**
* Draws one pixel at
(<em>x</em>, <em>y</em>).
* This method is private because pixels
depend on the display.
* To achieve the same effect, set the pen
radius to 0 and call {@code point()}.
*
* @param
x the <em>x</em>-coordinate of the pixel
* @param
y the <em>y</em>-coordinate of the pixel
*/
private static void pixel(double x, double
y) {
offscreen.fillRect((int)
Math.round(scaleX(x)), (int) Math.round(scaleY(y)), 1, 1);
}
/**
* Draws a point centered at
(<em>x</em>, <em>y</em>).
* The point is a filled circle whose
radius is equal to the pen radius.
* To draw a single-pixel point, first set
the pen radius to 0.
*
* @param x the
<em>x</em>-coordinate of the point
* @param y the
<em>y</em>-coordinate of the point
*/
public static void point(double x, double
y) {
double xs = scaleX(x);
double ys = scaleY(y);
double r = penRadius;
float scaledPenRadius = (float) (r *
DEFAULT_SIZE);
// double ws = factorX(2*r);
// double hs = factorY(2*r);
// if (ws <= 1 && hs <=
1) pixel(x, y);
if (scaledPenRadius <= 1) pixel(x, y);
else offscreen.fill(new
Ellipse2D.Double(xs - scaledPenRadius/2, ys - scaledPenRadius/2,
scaledPenRadius, scaledPenRadius));
draw();
}
/**
*
Draws a circle of the specified radius, centered at (<em>x</em>,
<em>y</em>).
*
* @param
x the <em>x</em>-coordinate of the center of the circle
* @param
y the <em>y</em>-coordinate of the center of the circle
* @param
radius the radius of the circle
* @throws IllegalArgumentException if
{@code radius} is negative
*/
public static void circle(double x, double
y, double radius) {
if (!(radius >= 0)) throw new
IllegalArgumentException("radius must be nonnegative");
double xs = scaleX(x);
double ys = scaleY(y);
double ws = factorX(2*radius);
double hs = factorY(2*radius);
if (ws <= 1 && hs <= 1)
pixel(x, y);
else offscreen.draw(new Ellipse2D.Double(xs
- ws/2, ys - hs/2, ws, hs));
draw();
}
/**
* Draws a filled circle of the specified
radius, centered at (<em>x</em>, <em>y</em>).
*
* @param
x the <em>x</em>-coordinate of the center of the circle
* @param
y the <em>y</em>-coordinate of the center of the circle
* @param
radius the radius of the circle
* @throws IllegalArgumentException if
{@code radius} is negative
*/
public static void filledCircle(double x,
double y, double radius) {
if (!(radius >= 0)) throw new
IllegalArgumentException("radius must be nonnegative");
double xs = scaleX(x);
double ys = scaleY(y);
double ws = factorX(2*radius);
double hs = factorY(2*radius);
if (ws <= 1 && hs <= 1)
pixel(x, y);
else offscreen.fill(new
Ellipse2D.Double(xs - ws/2, ys - hs/2, ws, hs));
draw();
}
/**
* Draws an ellipse with the specified
semimajor and semiminor axes,
* centered at (<em>x</em>,
<em>y</em>).
*
* @param
x the <em>x</em>-coordinate of the center of the ellipse
* @param
y the <em>y</em>-coordinate of the center of the ellipse
* @param
semiMajorAxis is the semimajor axis of the ellipse
* @param
semiMinorAxis is the semiminor axis of the ellipse
* @throws IllegalArgumentException if
either {@code semiMajorAxis}
*
or {@code semiMinorAxis} is negative
*/
public static void ellipse(double x, double
y, double semiMajorAxis, double semiMinorAxis) {
if (!(semiMajorAxis >= 0)) throw new
IllegalArgumentException("ellipse semimajor axis must be
nonnegative");
if (!(semiMinorAxis >= 0)) throw new
IllegalArgumentException("ellipse semiminor axis must be
nonnegative");
double xs = scaleX(x);
double ys = scaleY(y);
double ws = factorX(2*semiMajorAxis);
double hs = factorY(2*semiMinorAxis);
if (ws <= 1 && hs <= 1)
pixel(x, y);
else offscreen.draw(new
Ellipse2D.Double(xs - ws/2, ys - hs/2, ws, hs));
draw();
}
/**
* Draws an ellipse with the specified
semimajor and semiminor axes,
* centered at (<em>x</em>,
<em>y</em>).
*
* @param
x the <em>x</em>-coordinate of the center of the ellipse
* @param
y the <em>y</em>-coordinate of the center of the ellipse
* @param
semiMajorAxis is the semimajor axis of the ellipse
* @param
semiMinorAxis is the semiminor axis of the ellipse
* @throws IllegalArgumentException if
either {@code semiMajorAxis}
*
or {@code semiMinorAxis} is negative
*/
public static void filledEllipse(double x,
double y, double semiMajorAxis, double semiMinorAxis) {
if (!(semiMajorAxis >= 0)) throw new
IllegalArgumentException("ellipse semimajor axis must be
nonnegative");
if (!(semiMinorAxis >= 0)) throw new
IllegalArgumentException("ellipse semiminor axis must be
nonnegative");
double xs = scaleX(x);
double ys = scaleY(y);
double ws = factorX(2*semiMajorAxis);
double hs = factorY(2*semiMinorAxis);
if (ws <= 1 && hs <= 1)
pixel(x, y);
else offscreen.fill(new
Ellipse2D.Double(xs - ws/2, ys - hs/2, ws, hs));
draw();
}
/**
* Draws a circular arc of the specified
radius,
* centered at (<em>x</em>,
<em>y</em>), from angle1 to angle2 (in degrees).
*
* @param
x the <em>x</em>-coordinate of the center of the circle
* @param
y the <em>y</em>-coordinate of the center of the circle
* @param
radius the radius of the circle
* @param
angle1 the starting angle. 0 would mean an arc beginning at 3 o'clock.
* @param
angle2 the angle at the end of the arc. For example, if
*
you want a 90 degree arc, then angle2 should be angle1 + 90.
* @throws IllegalArgumentException if
{@code radius} is negative
*/
public static void arc(double x, double y,
double radius, double angle1, double angle2) {
if (radius < 0) throw new
IllegalArgumentException("arc radius must be nonnegative");
while (angle2 < angle1) angle2 +=
360;
double xs = scaleX(x);
double ys = scaleY(y);
double ws = factorX(2*radius);
double hs = factorY(2*radius);
if (ws <= 1 && hs <= 1)
pixel(x, y);
else offscreen.draw(new Arc2D.Double(xs
- ws/2, ys - hs/2, ws, hs, angle1, angle2 - angle1, Arc2D.OPEN));
draw();
}
/**
* Draws a square of side length 2r,
centered at (<em>x</em>, <em>y</em>).
*
* @param
x the <em>x</em>-coordinate of the center of the square
* @param
y the <em>y</em>-coordinate of the center of the square
* @param
halfLength one half the length of any side of the square
* @throws IllegalArgumentException if
{@code halfLength} is negative
*/
public static void square(double x, double
y, double halfLength) {
if (!(halfLength >= 0)) throw new
IllegalArgumentException("half length must be nonnegative");
double xs = scaleX(x);
double ys = scaleY(y);
double ws = factorX(2*halfLength);
double hs = factorY(2*halfLength);
if (ws <= 1 && hs <= 1)
pixel(x, y);
else offscreen.draw(new
Rectangle2D.Double(xs - ws/2, ys - hs/2, ws, hs));
draw();
}
/**
* Draws a filled square of the specified
size, centered at (<em>x</em>, <em>y</em>).
*
* @param
x the <em>x</em>-coordinate of the center of the square
* @param
y the <em>y</em>-coordinate of the center of the square
* @param
halfLength one half the length of any side of the square
* @throws IllegalArgumentException if
{@code halfLength} is negative
*/
public static void filledSquare(double x,
double y, double halfLength) {
if (!(halfLength >= 0)) throw new
IllegalArgumentException("half length must be nonnegative");
double xs = scaleX(x);
double ys = scaleY(y);
double ws = factorX(2*halfLength);
double hs = factorY(2*halfLength);
if (ws <= 1 && hs <= 1)
pixel(x, y);
else offscreen.fill(new
Rectangle2D.Double(xs - ws/2, ys - hs/2, ws, hs));
draw();
}
/**
* Draws a rectangle of the specified size,
centered at (<em>x</em>, <em>y</em>).
*
* @param
x the <em>x</em>-coordinate of the center of the rectangle
* @param
y the <em>y</em>-coordinate of the center of the rectangle
* @param
halfWidth one half the width of the rectangle
* @param
halfHeight one half the height of the rectangle
* @throws IllegalArgumentException if
either {@code halfWidth} or {@code halfHeight} is negative
*/
public static void rectangle(double x,
double y, double halfWidth, double halfHeight) {
if (!(halfWidth >= 0)) throw new
IllegalArgumentException("half width must be nonnegative");
if (!(halfHeight >= 0)) throw new
IllegalArgumentException("half height must be nonnegative");
double xs = scaleX(x);
double ys = scaleY(y);
double ws = factorX(2*halfWidth);
double hs = factorY(2*halfHeight);
if (ws <= 1 && hs <= 1)
pixel(x, y);
else offscreen.draw(new
Rectangle2D.Double(xs - ws/2, ys - hs/2, ws, hs));
draw();
}
/**
* Draws a filled rectangle of the specified
size, centered at (<em>x</em>, <em>y</em>).
*
* @param
x the <em>x</em>-coordinate of the center of the rectangle
* @param
y the <em>y</em>-coordinate of the center of the rectangle
* @param
halfWidth one half the width of the rectangle
* @param
halfHeight one half the height of the rectangle
* @throws IllegalArgumentException if
either {@code halfWidth} or {@code halfHeight} is negative
*/
public static void filledRectangle(double
x, double y, double halfWidth, double halfHeight) {
if (!(halfWidth >= 0)) throw new
IllegalArgumentException("half width must be nonnegative");
if (!(halfHeight >= 0)) throw new
IllegalArgumentException("half height must be nonnegative");
double xs = scaleX(x);
double ys = scaleY(y);
double ws = factorX(2*halfWidth);
double hs = factorY(2*halfHeight);
if (ws <= 1 && hs <= 1)
pixel(x, y);
else offscreen.fill(new
Rectangle2D.Double(xs - ws/2, ys - hs/2, ws, hs));
draw();
}
/**
* Draws a polygon with the vertices
*
(<em>x</em><sub>0</sub>,
<em>y</em><sub>0</sub>),
*
(<em>x</em><sub>1</sub>,
<em>y</em><sub>1</sub>), ...,
*
(<em>x</em><sub><em>n</em>–1</sub>,
<em>y</em><sub><em>n</em>–1</sub>).
*
* @param
x an array of all the <em>x</em>-coordinates of the polygon
* @param
y an array of all the <em>y</em>-coordinates of the polygon
* @throws IllegalArgumentException unless
{@code x[]} and {@code y[]}
*
are of the same length
*/
public static void polygon(double[] x,
double[] y) {
if (x == null) throw new
IllegalArgumentException();
if (y == null) throw new
IllegalArgumentException();
int n1 = x.length;
int n2 = y.length;
if (n1 != n2) throw new
IllegalArgumentException("arrays must be of the same length");
int n = n1;
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);
draw();
}
/**
* Draws a polygon with the vertices
*
(<em>x</em><sub>0</sub>,
<em>y</em><sub>0</sub>),
*
(<em>x</em><sub>1</sub>,
<em>y</em><sub>1</sub>), ...,
*
(<em>x</em><sub><em>n</em>–1</sub>,
<em>y</em><sub><em>n</em>–1</sub>).
*
* @param
x an array of all the <em>x</em>-coordinates of the polygon
* @param
y an array of all the <em>y</em>-coordinates of the polygon
* @throws IllegalArgumentException unless
{@code x[]} and {@code y[]}
*
are of the same length
*/
public static void filledPolygon(double[]
x, double[] y) {
if (x == null) throw new
IllegalArgumentException();
if (y == null) throw new
IllegalArgumentException();
int n1 = x.length;
int n2 = y.length;
if (n1 != n2) throw new
IllegalArgumentException("arrays must be of the same length");
int n = n1;
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);
draw();
}
/***************************************************************************
*
Drawing images.
***************************************************************************/
// get an image from the given filename
private static Image getImage(String
filename) {
if (filename == null) throw new
IllegalArgumentException();
// 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 (MalformedURLException e) {
/* not a url */
}
}
// in case file is inside a .jar
(classpath relative to StdDraw)
if ((icon == null) ||
(icon.getImageLoadStatus() != MediaTracker.COMPLETE)) {
URL url =
StdDraw.class.getResource(filename);
if (url != null)
icon = new ImageIcon(url);
}
// in case file is inside a .jar
(classpath relative to root of jar)
if ((icon == null) ||
(icon.getImageLoadStatus() != MediaTracker.COMPLETE)) {
URL url =
StdDraw.class.getResource("/" + filename);
if (url == null) throw new
IllegalArgumentException("image " + filename + " not
found");
icon = new ImageIcon(url);
}
return icon.getImage();
}
/***************************************************************************
* [Summer 2016] Should we update to use
ImageIO instead of ImageIcon()?
* Seems to have some issues
loading images on some systems
* and slows things down on other
systems.
*
especially if you don't
call ImageIO.setUseCache(false)
* One advantage is that it returns
a BufferedImage.
***************************************************************************/
/*
private static BufferedImage
getImage(String filename) {
if (filename == null) throw new
IllegalArgumentException();
// from a file or URL
try {
URL url = new URL(filename);
BufferedImage image = ImageIO.read(url);
return image;
}
catch (IOException e) {
// ignore
}
// in case file is inside a .jar
(classpath relative to StdDraw)
try {
URL url =
StdDraw.class.getResource(filename);
BufferedImage image =
ImageIO.read(url);
return image;
}
catch (IOException e) {
// ignore
}
// in case file is inside a .jar
(classpath relative to root of jar)
try {
URL url =
StdDraw.class.getResource("/" + filename);
BufferedImage image =
ImageIO.read(url);
return image;
}
catch (IOException e) {
// ignore
}
throw new
IllegalArgumentException("image " + filename + " not
found");
}
*/
/**
* Draws the specified image centered at
(<em>x</em>, <em>y</em>).
* The supported image formats are JPEG,
PNG, and GIF.
* As an optimization, the picture is
cached, so there is no performance
* penalty for redrawing the same image
multiple times (e.g., in an animation).
* However, if you change the picture file
after drawing it, subsequent
* calls will draw the original picture.
*
* @param
x the center <em>x</em>-coordinate of the image
* @param
y the center <em>y</em>-coordinate of the image
* @param
filename the name of the image/picture, e.g., "ball.gif"
* @throws IllegalArgumentException if the
image filename is invalid
*/
public static void picture(double x, double
y, String filename) {
// BufferedImage image =
getImage(filename);
Image image = getImage(filename);
double xs = scaleX(x);
double ys = scaleY(y);
// int ws = image.getWidth(); // can call only if image is a
BufferedImage
// int hs = image.getHeight();
int ws = image.getWidth(null);
int hs = image.getHeight(null);
if (ws < 0 || hs < 0) throw new
IllegalArgumentException("image " + filename + " is
corrupt");
offscreen.drawImage(image, (int)
Math.round(xs - ws/2.0), (int) Math.round(ys - hs/2.0), null);
draw();
}
/**
* Draws the specified image centered at
(<em>x</em>, <em>y</em>),
* rotated given number of degrees.
* The supported image formats are JPEG,
PNG, and GIF.
*
* @param
x the center <em>x</em>-coordinate of the image
* @param
y the center <em>y</em>-coordinate of the image
* @param
filename the name of the image/picture, e.g., "ball.gif"
* @param
degrees is the number of degrees to rotate counterclockwise
* @throws IllegalArgumentException if the
image filename is invalid
*/
public static void picture(double x, double
y, String filename, double degrees) {
// BufferedImage image =
getImage(filename);
Image image = getImage(filename);
double xs = scaleX(x);
double ys = scaleY(y);
// int ws = image.getWidth(); // can call only if image is a
BufferedImage
// int hs = image.getHeight();
int ws = image.getWidth(null);
int hs = image.getHeight(null);
if (ws < 0 || hs < 0) throw new
IllegalArgumentException("image " + filename + " is
corrupt");
offscreen.rotate(Math.toRadians(-degrees), xs, ys);
offscreen.drawImage(image, (int)
Math.round(xs - ws/2.0), (int) Math.round(ys - hs/2.0), null);
offscreen.rotate(Math.toRadians(+degrees), xs, ys);
draw();
}
/**
* Draws the specified image centered at
(<em>x</em>, <em>y</em>),
* rescaled to the specified bounding box.
* The supported image formats are JPEG, PNG,
and GIF.
*
* @param
x the center <em>x</em>-coordinate of the image
* @param
y the center <em>y</em>-coordinate of the image
* @param
filename the name of the image/picture, e.g., "ball.gif"
* @param
scaledWidth the width of the scaled image (in screen coordinates)
* @param
scaledHeight the height of the scaled image (in screen coordinates)
* @throws IllegalArgumentException if
either {@code scaledWidth}
*
or {@code scaledHeight} is negative
* @throws IllegalArgumentException if the
image filename is invalid
*/
public static void picture(double x, double
y, String filename, double scaledWidth, double scaledHeight) {
Image image = getImage(filename);
if (scaledWidth < 0) throw new
IllegalArgumentException("width is
negative: " + scaledWidth);
if (scaledHeight < 0) throw new
IllegalArgumentException("height is negative: " + scaledHeight);
double xs = scaleX(x);
double ys = scaleY(y);
double ws = factorX(scaledWidth);
double hs = factorY(scaledHeight);
if (ws < 0 || hs < 0) throw new
IllegalArgumentException("image " + filename + " is
corrupt");
if (ws <= 1 && hs <= 1)
pixel(x, y);
else {
offscreen.drawImage(image, (int)
Math.round(xs - ws/2.0),
(int)
Math.round(ys - hs/2.0),
(int)
Math.round(ws),
(int)
Math.round(hs), null);
}
draw();
}
/**
* Draws the specified image centered at
(<em>x</em>, <em>y</em>), rotated
* given number of degrees, and rescaled to
the specified bounding box.
* The supported image formats are JPEG, PNG,
and GIF.
*
* @param
x the center <em>x</em>-coordinate of the image
* @param
y the center <em>y</em>-coordinate of the image
* @param
filename the name of the image/picture, e.g., "ball.gif"
* @param
scaledWidth the width of the scaled image (in screen coordinates)
* @param
scaledHeight the height of the scaled image (in screen coordinates)
* @param
degrees is the number of degrees to rotate counterclockwise
* @throws IllegalArgumentException if
either {@code scaledWidth}
*
or {@code scaledHeight} is negative
* @throws IllegalArgumentException if the
image filename is invalid
*/
public static void picture(double x, double
y, String filename, double scaledWidth, double scaledHeight, double degrees) {
if (scaledWidth < 0) throw new
IllegalArgumentException("width is negative: " + scaledWidth);
if (scaledHeight < 0) throw new
IllegalArgumentException("height is negative: " + scaledHeight);
Image image = getImage(filename);
double xs = scaleX(x);
double ys = scaleY(y);
double ws = factorX(scaledWidth);
double hs = factorY(scaledHeight);
if (ws < 0 || hs < 0) throw new
IllegalArgumentException("image " + filename + " is
corrupt");
if (ws <= 1 && hs <= 1)
pixel(x, y);
offscreen.rotate(Math.toRadians(-degrees), xs, ys);
offscreen.drawImage(image, (int)
Math.round(xs - ws/2.0),
(int)
Math.round(ys - hs/2.0),
(int)
Math.round(ws),
(int)
Math.round(hs), null);
offscreen.rotate(Math.toRadians(+degrees), xs, ys);
draw();
}
/***************************************************************************
*
Drawing text.
***************************************************************************/
/**
* Write the given text string in the
current font, centered at (<em>x</em>, <em>y</em>).
*
* @param
x the center <em>x</em>-coordinate of the text
* @param
y the center <em>y</em>-coordinate of the text
* @param
text the text to write
*/
public static void text(double x, double y,
String text) {
if (text == null) throw new
IllegalArgumentException();
offscreen.setFont(font);
FontMetrics metrics =
offscreen.getFontMetrics();
double xs = scaleX(x);
double ys = scaleY(y);
int ws = metrics.stringWidth(text);
int hs = metrics.getDescent();
offscreen.drawString(text, (float) (xs
- ws/2.0), (float) (ys + hs));
draw();
}
/**
* Write the given text string in the
current font, centered at (<em>x</em>, <em>y</em>) and
* rotated by the specified number of
degrees.
* @param
x the center <em>x</em>-coordinate of the text
* @param
y the center <em>y</em>-coordinate of the text
* @param
text the text to write
* @param
degrees is the number of degrees to rotate counterclockwise
*/
public static void text(double x, double y,
String text, double degrees) {
if (text == null) throw new
IllegalArgumentException();
double xs = scaleX(x);
double ys = scaleY(y);
offscreen.rotate(Math.toRadians(-degrees),
xs, ys);
text(x, y, text);
offscreen.rotate(Math.toRadians(+degrees), xs, ys);
}
/**
* Write the given text string in the
current font, left-aligned at (<em>x</em>, <em>y</em>).
* @param
x the <em>x</em>-coordinate of the text
* @param
y the <em>y</em>-coordinate of the text
* @param
text the text
*/
public static void textLeft(double x,
double y, String text) {
if (text == null) throw new IllegalArgumentException();
offscreen.setFont(font);
FontMetrics metrics =
offscreen.getFontMetrics();
double xs = scaleX(x);
double ys = scaleY(y);
int hs = metrics.getDescent();
offscreen.drawString(text, (float) xs,
(float) (ys + hs));
draw();
}
/**
* Write the given text string in the
current font, right-aligned at (<em>x</em>,
<em>y</em>).
*
* @param
x the <em>x</em>-coordinate of the text
* @param
y the <em>y</em>-coordinate of the text
* @param
text the text to write
*/
public static void textRight(double x,
double y, String text) {
if (text == null) throw new
IllegalArgumentException();
offscreen.setFont(font);
FontMetrics metrics =
offscreen.getFontMetrics();
double xs = scaleX(x);
double ys = scaleY(y);
int ws = metrics.stringWidth(text);
int hs = metrics.getDescent();
offscreen.drawString(text, (float) (xs
- ws), (float) (ys + hs));
draw();
}
/**
* Copies the offscreen buffer to the
onscreen buffer, pauses for t milliseconds
* and enables double buffering.
* @param t number of milliseconds
* @deprecated replaced by {@link
#enableDoubleBuffering()}, {@link #show()}, and {@link #pause(int t)}
*/
@Deprecated
public static void show(int t) {
// sleep until the next time we're
allowed to draw
long millis =
System.currentTimeMillis();
if (millis < nextDraw) {
try {
Thread.sleep(nextDraw -
millis);
}
catch (InterruptedException e) {
System.out.println("Error
sleeping");
}
millis = nextDraw;
}
show();
enableDoubleBuffering();
// when are we allowed to draw again
nextDraw = millis + t;
}
/**
* Pause for t milliseconds. This method is
intended to support computer animations.
* @param t number of milliseconds
*/
public static void pause(int t) {
// sleep until the next time we're
allowed to draw
long millis =
System.currentTimeMillis();
if (millis < nextDraw) {
try {
Thread.sleep(nextDraw -
millis);
}
catch (InterruptedException e) {
System.out.println("Error
sleeping");
}
millis = nextDraw;
}
// when are we allowed to draw again
nextDraw = millis + t;
}
/**
* Copies offscreen buffer to onscreen
buffer. There is no reason to call
* this method unless double buffering is
enabled.
*/
public static void show() {
onscreen.drawImage(offscreenImage, 0,
0, null);
frame.repaint();
}
// draw onscreen if defer is false
private static void draw() {
if (!defer) show();
}
/**
* Enable double buffering. All subsequent
calls to
* drawing methods such as {@code line()},
{@code circle()},
* and {@code square()} will be deffered
until the next call
* to show(). Useful for animations.
*/
public static void enableDoubleBuffering()
{
defer = true;
}
/**
* Disable double buffering. All subsequent
calls to
* drawing methods such as {@code line()},
{@code circle()},
* and {@code square()} will be displayed
on screen when called.
* This is the default.
*/
public static void disableDoubleBuffering()
{
defer = false;
}
/***************************************************************************
*
Save drawing to a file.
***************************************************************************/
/**
* Saves the drawing to using the specified
filename.
* The supported image formats are JPEG and
PNG;
* the filename suffix must be {@code .jpg}
or {@code .png}.
*
* @param
filename the name of the file with one of the required suffixes
*/
public static void save(String filename) {
if (filename == null) throw new
IllegalArgumentException();
File file = new File(filename);
String suffix =
filename.substring(filename.lastIndexOf('.') + 1);
// png files
if
("png".equalsIgnoreCase(suffix)) {
try {
ImageIO.write(onscreenImage,
suffix, file);
}
catch (IOException e) {
e.printStackTrace();
}
}
// need to change from ARGB to RGB for
JPEG
// reference:
http://archives.java.sun.com/cgi-bin/wa?A2=ind0404&L=java2d-interest&D=0&P=2727
else if
("jpg".equalsIgnoreCase(suffix)) {
WritableRaster raster =
onscreenImage.getRaster();
WritableRaster newRaster;
newRaster =
raster.createWritableChild(0, 0, width, height, 0, 0, new int[] {0, 1, 2});
DirectColorModel cm =
(DirectColorModel) onscreenImage.getColorModel();
DirectColorModel newCM = new
DirectColorModel(cm.getPixelSize(),
cm.getRedMask(),
cm.getGreenMask(),
cm.getBlueMask());
BufferedImage rgbBuffer = new
BufferedImage(newCM, newRaster, false,
null);
try {
ImageIO.write(rgbBuffer,
suffix, file);
}
catch (IOException e) {
e.printStackTrace();
}
}
else {
System.out.println("Invalid
image file type: " + suffix);
}
}
/**
* This method cannot be called directly.
*/
@Override
public void actionPerformed(ActionEvent e)
{
FileDialog chooser = new
FileDialog(StdDraw.frame, "Use a .png or .jpg extension",
FileDialog.SAVE);
chooser.setVisible(true);
String filename = chooser.getFile();
if (filename != null) {
StdDraw.save(chooser.getDirectory()
+ File.separator + chooser.getFile());
}
}
/***************************************************************************
*
Mouse interactions.
***************************************************************************/
/**
* Returns true if the mouse is being
pressed.
*
* @return {@code true} if the mouse is
being pressed; {@code false} otherwise
*/
public static boolean mousePressed() {
synchronized (mouseLock) {
return mousePressed;
}
}
/**
* Returns the
<em>x</em>-coordinate of the mouse.
*
* @return the
<em>x</em>-coordinate of the mouse
*/
public static double mouseX() {
synchronized (mouseLock) {
return mouseX;
}
}
/**
* Returns the
<em>y</em>-coordinate of the mouse.
*
* @return
<em>y</em>-coordinate of the mouse
*/
public static double mouseY() {
synchronized (mouseLock) {
return mouseY;
}
}
/**
* This method cannot be called directly.
*/
@Override
public void mouseClicked(MouseEvent e) {
// this body is intentionally left
empty
}
/**
* This method cannot be called directly.
*/
@Override
public void mouseEntered(MouseEvent e) {
// this body is intentionally left
empty
}
/**
* This method cannot be called directly.
*/
@Override
public void mouseExited(MouseEvent e) {
// this body is intentionally left
empty
}
/**
* This method cannot be called directly.
*/
@Override
public void mousePressed(MouseEvent e) {
synchronized (mouseLock) {
mouseX = StdDraw.userX(e.getX());
mouseY = StdDraw.userY(e.getY());
mousePressed = true;
}
}
/**
* This method cannot be called directly.
*/
@Override
public void mouseReleased(MouseEvent e) {
synchronized (mouseLock) {
mousePressed = false;
}
}
/**
* This method cannot be called directly.
*/
@Override
public void mouseDragged(MouseEvent e) {
synchronized (mouseLock) {
mouseX = StdDraw.userX(e.getX());
mouseY = StdDraw.userY(e.getY());
}
}
/**
* This method cannot be called directly.
*/
@Override
public void mouseMoved(MouseEvent e) {
synchronized (mouseLock) {
mouseX = StdDraw.userX(e.getX());
mouseY = StdDraw.userY(e.getY());
}
}
/***************************************************************************
*
Keyboard interactions.
***************************************************************************/
/**
* Returns true if the user has typed a key
(that has not yet been processed).
*
* @return {@code true} if the user has
typed a key (that has not yet been processed
*
by {@link #nextKeyTyped()}; {@code false} otherwise
*/
public static boolean hasNextKeyTyped() {
synchronized (keyLock) {
return !keysTyped.isEmpty();
}
}
/**
* Returns the next key that was typed by
the user (that your program has not already processed).
* This method should be preceded by a call
to {@link #hasNextKeyTyped()} to ensure
* that there is a next key to process.
* This method returns a Unicode character
corresponding to the key
* typed (such as {@code 'a'} or {@code
'A'}).
* It cannot identify action keys (such as
F1 and arrow keys)
* or modifier keys (such as control).
*
* @return the next key typed by the user
(that your program has not already processed).
* @throws NoSuchElementException if there
is no remaining key
*/
public static char nextKeyTyped() {
synchronized (keyLock) {
if (keysTyped.isEmpty()) {
throw new
NoSuchElementException("your program has already processed all
keystrokes");
}
return keysTyped.removeLast();
}
}
/**
* Returns true if the given key is being pressed.
* <p>
* This method takes the keycode
(corresponding to a physical key)
* as
an argument. It can handle action keys
* (such as F1 and arrow keys) and modifier
keys (such as shift and control).
* See {@link KeyEvent} for a description
of key codes.
*
* @param
keycode the key to check if it is being pressed
* @return {@code true} if {@code keycode}
is currently being pressed;
*
{@code false} otherwise
*/
public static boolean isKeyPressed(int
keycode) {
synchronized (keyLock) {
return keysDown.contains(keycode);
}
}
/**
* This method cannot be called directly.
*/
@Override
public void keyTyped(KeyEvent e) {
synchronized (keyLock) {
keysTyped.addFirst(e.getKeyChar());
}
}
/**
* This method cannot be called directly.
*/
@Override
public void keyPressed(KeyEvent e) {
synchronized (keyLock) {
keysDown.add(e.getKeyCode());
}
}
/**
* This method cannot be called directly.
*/
@Override
public void keyReleased(KeyEvent e) {
synchronized (keyLock) {
keysDown.remove(e.getKeyCode());
}
}
/**
* Test client.
*
* @param args the command-line arguments
*/
public static void main(String[] args) {
StdDraw.square(.2, .8, .1);
StdDraw.filledSquare(.8, .8, .2);
StdDraw.circle(.8, .2, .2);
StdDraw.setPenColor(StdDraw.BOOK_RED);
StdDraw.setPenRadius(.02);
StdDraw.arc(.8, .2, .1, 200, 45);
// draw a blue diamond
StdDraw.setPenRadius();
StdDraw.setPenColor(StdDraw.BOOK_BLUE);
double[] x = { .1, .2, .3, .2 };
double[] y = { .2, .3, .2, .1 };
StdDraw.filledPolygon(x, y);
// text
StdDraw.setPenColor(StdDraw.BLACK);
StdDraw.text(0.2, 0.5, "black
text");
StdDraw.setPenColor(StdDraw.WHITE);
StdDraw.text(0.8, 0.8, "white text");
}
}