import javax.swing.*; import java.awt.*; public class DrawRectangle extends JPanel { public void paintComponent(Graphics g) { super.paintComponent(g); // always include this g.drawLine(100, 100, 100, 200); g.drawLine(100, 200, 300, 200); g.drawLine(300, 200, 300, 100); g.drawLine(300, 100, 100, 100); } public static void main(String[] args) { DrawRectangle drawRectangle = new DrawRectangle(); JFrame frame = new JFrame("Draw Rectangle"); frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); frame.getContentPane().add(drawRectangle); frame.setSize(400, 300); frame.setVisible(true); } }ANCHOR How it works
The units are given in pixels.
This is a left-handed coordinate system. ADDANCHOR j2d ANCHOR Java2D
A quadratic curve has the form
where
Quadratic curves may be drawn elegantly by a subdivision process
The curve is broken into two halves, each of which is itself a quadratic curve whose control points are obtained by averaging the original control points. ANCHOR Instantiation
Quadratic curves may be instantiated using either
QuadCurve2D.Double quad = new QuadCurve2D.Double(double x0, double y0, double x1, double y1, double x2, double y2);
or
QuadCurve2D.Double quad = new QuadCurve2D.Double(); quad.setCurve(Point2D p0, Point2D p1, Point2D p2);ANCHOR Cubic curves
A cubic curve has the form
where
Cubic curves may be instantiated using either
CubicCurve2D.Double quad = new CubicCurve2D.Double(double x0, double y0, double x1, double y1, double x2, double y2, double x3, double y3);
or
CubicCurve2D.Double quad = new CubicCurve2D.Double(); quad.setCurve(Point2D p0, Point2D p1, Point2D p2, Point2D p3);ANCHOR Rectangular shapes
Rectangular shapes, including rectangles and ellipses, are generally defined by stating the upper left corner of the smallest rectangle containing the shape along with its width and height.
Rectangle2D.Double rectangle = new Rectangle2D.Double(double ulx, double uly, double width, double height);
Ellipse2D.Double ellipse = new Ellipse2D.Double(double ulx, double uly, double width, double height);ANCHOR Arcs
Arc2D.Double arc = new Arc2D.Double(double ulx, double uly, double width, double height, double angleStart, double angleEnd, int closure);
The class GeneralPath lets us build paths like we might in PostScript.
GeneralPath path = new GeneralPath(); path.moveTo(50, 50); path.lineTo(150, 150); path.quadTo(200, 200, 250, 150); path.curveTo(250, 250, 150, 250, 150, 200); path.closePath(); g.draw(path); |
Constructive area geometry operations area available through the Area class.
Area area = new Area(new Rectangle(50, 25, 100, 125)); Area addArea = new Area(new Ellipse2D.Float(100, 100, 75, 75)); Area intersectArea = new Area(new Ellipse2D.Float(50, 50, 125, 125)); area.add(addArea); area.intersect(intersectArea); g.setPaint(Color.blue); g.fill(area); g.setPaint(Color.black); g.setStroke(new BasicStroke(4f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 3f)); g.draw(area);ANCHOR
ADDANCHOR iterator ANCHOR Playing back a path
Any Shape object can produce a PathIterator, a description of the outline of the Shape in terms of line segments and quadratic and cubic curves.
Here is how Java2D draws a circle:
PathIteratedCircle.java ADDANCHOR state ANCHOR Graphic2D's internal state
A Graphics2D object uses some adjustable parameters when rendering a Shape. These include
Font font = new Font("sanserif", Font.BOLD, 80); g.setFont(font); g.drawString("graphics", 50, 100);
A FontMetrics object allows us to measure the size of a String.
FontMetrics fm = g.getFontMetrics(); Rectangle2D rect = fm.getStringBounds("graphics", g);
This produces the smallest rectangle containing the String were it to be drawn at the point (0, 0). ADDANCHOR transforms ANCHOR Affine transforms
Affine transforms of the plane have the form
A Graphics2D maintains an internal affine transform to convert the coordinates of a Shape into pixels on the computer screen using an AffineTransform object.
A Shape is defined in a coordinate system called user space. Pixels live in a coordinate system called device space.
The AffineTransform is a map from user space to device space. ANCHOR Creating AffineTransforms
AffineTransforms are ubiquitous in Java2D and there are consequently many ways to instantiate them.
AffineTransform transform = new AffineTransform();
gives an object representing the identity transform. Instructions such as
transform.translate(100, 200); transform.scale(2, -1); transform.shear(1, 0);
modify transform by composing it, on the right, with the indicated transform. ANCHOR Using AffineTransforms
There are two ways to use AffineTransforms. First, one may modify a Graphics2D's internal transform. Here is an example along with the result:
AffineTransform at = new AffineTransform(); at.translate(150, 200); at.rotate(-Math.PI/3); at.scale(2, 0.5); g.transform(at); Ellipse2D.Float circle = new Ellipse2D.Float(-50, -50, 100, 100); g.draw(circle); |
Alternatively, we may transform shapes.
AffineTransform at = new AffineTransform(); at.translate(150, 200); at.rotate(-Math.PI/3); at.scale(2, 0.5); Ellipse2D.Float circle = new Ellipse2D.Float(-50, -50, 100, 100); Shape shape = at.createTransformedShape(circle); g.draw(shape); |
I generally transform Shapes rather than alter the Graphics2D's underlying AffineTransform.
This is to avoid the problem we've just seen with non-uniform scaling.
Also, the thickness of lines and the size of points drawn are most conveniently expressed in terms of pixels.
Finally, when we work with events, some important information will be given to us in pixels. ANCHOR Inverses
The inverse of an AffineTransform may be found like this:
AffineTransform inverse; try { inverse = transform.createInverse(); } catch(NoninvertibleTransformException ex) { do something here if transform is not invertible }ADDANCHOR graph ANCHOR Graphing a function
Let's look at an example. In a 301 by 301 pixel frame, we will draw the graph of the function y = x^{3} - x in the viewing rectangle along with a 1 by 1 grid.
It would be most convenient if user space agreed with our mathematical coordinate system. We will therefore set up an AffineTransform that converts mathematical coordinates into the coordinates of the JPanel.
AffineTransform transform = new AffineTransform(); transform.translate(150, 150); transform.scale(1, -1); transform.scale(75, 50);
The origin of the coordinate system is moved to the center of the JPanel, the vertical coordinate is scaled so that it increases as we move upward and finally, both coordinates are scaled so that the viewing rectangle will fill up the JPanel. ANCHOR Constructing the grid
g.setPaint(Color.lightGray); GeneralPath path = new GeneralPath(); for (int i = -2; i <= 2; i++) { path.moveTo(i, -3); path.lineTo(i, 3); } for (int i = -3; i <= 3; i++) { path.moveTo(-2, i); path.lineTo(2, i); } g.draw(transform.createTransformedShape(path));ANCHOR Constructing the graph
int steps = 50; float dx = 4.0f/steps; g.setPaint(Color.black); path = new GeneralPath(); path.moveTo(-2, valueAt(-2)); for (float x = -2+dx; x <= 2; x += dx) path.lineTo(x, valueAt(x)); g.draw(transform.createTransformedShape(path));
Here the method valueAt(double x) returns the value of the function evaluated at x. ADDANCHOR stroke ANCHOR Stroking lines
Here are some examples illustrating how a Graphics2D's stroke attribute may be modified.
BasicStroke stroke = new BasicStroke(50); g.setStroke(stroke); g.draw(new Line2D.Float(50, 50, 250, 150)); |
This example explains why a non-uniform scaling of a Graphics2D's AffineTransform produces a curve of varying thickness: the outline of the stroked curve is transformed. ANCHOR
BasicStroke stroke = new BasicStroke(25f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER); |
BasicStroke stroke = new BasicStroke(25f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_MITER); |
BasicStroke stroke = new BasicStroke(25f, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER); |
BasicStroke stroke = new BasicStroke(5f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 1f, new float[] {10, 5, 5, 5}, 2); |
A clipping Shape may be given so that anything outside the Shape is not visible.
GeneralPath path = new GeneralPath(); for (int i = 0; i <= 300; i+=20) { path.moveTo(i, 0); path.lineTo(i, 300); path.moveTo(0, i); path.lineTo(300, i); } Ellipse2D.Double circle = new Ellipse2D.Double(-1, -1, 2, 2); AffineTransform transform = new AffineTransform(); transform.translate(150, 150); transform.rotate(Math.PI/6); transform.scale(75, -100); Shape clippingShape = transform.createTransformedShape(circle); Shape originalClippingShape = g.getClip(); g.setClip(clippingShape); g.setPaint(Color.lightGray); g.draw(path); g.setClip(originalClippingShape); g.setPaint(Color.black); g.draw(clippingShape);ADDANCHOR composite ANCHOR Compositing
Earlier, we said that Java paint is opaque. However, this behavior can be modified through compositing rules, a sample of which is demonstrated here. The set of Porter-Duff compositing rules is implemented by java.awt.AlphaComposite.
Rectangle2D.Double rectangle = new Rectangle2D.Double(50, 50, 150, 100); g.setPaint(Color.blue); g.fill(rectangle); g.setPaint(Color.black); g.draw(rectangle); Composite composite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.8f); g.setComposite(composite); rectangle = new Rectangle2D.Double(90, 75, 125, 150); g.setPaint(Color.red); g.fill(rectangle); g.setPaint(Color.black); g.draw(rectangle);ANCHOR
Alternatively, we can define transparent colors by specifying an alpha value.
Rectangle2D.Double rectangle = new Rectangle2D.Double(50, 50, 150, 100); g.setPaint(new Color(0f, 0f, 1f)); g.fill(rectangle); g.setPaint(Color.black); g.draw(rectangle); rectangle = new Rectangle2D.Double(90, 75, 125, 150); g.setPaint(new Color(1f, 0f, 0f, 0.8f)); g.fill(rectangle); g.setPaint(Color.black); g.draw(rectangle);ADDANCHOR hints ANCHOR Rendering hints
Rendering hints help us control some of the details in the rendering process. Generally speaking, these involve a compromise between quality and speed.
For us, one of the most useful is anti-aliasing. In a black and white image, anti-aliasing allows some pixels to be colored in shades of gray to capture the fact that, say, a line passes nearby. This produces images that appear smoother.
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
Compare the following images: on the left, anti-aliasing is turned off while on the right, it is turned on.
LASTANCHOR Stroke control
By default, the endpoints of line segments are adjusted to the center of the nearest pixel. This can produce some undesirable effects in mathematical illustrations.
To defeat this behavior, use
g.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
as shown on the right below.