Java Drawing in 2D
Download
Report
Transcript Java Drawing in 2D
Java Drawing in 2D
Animations with Timer
Jan SmrcinaReferences:
Slides with help from
Sun’s Java Tutorials
Rick Snodgrass’ Slides on Basic and Advanced Swing
http://courses.coreservlets.com/Course-Materials/pdf/java5/12-Java-2D.pdf
Drawing Basics
A simple two-dimensional coordinate system exists for
each graphics context or drawing surface such as a JPanel
Points on the coordinate system represent single pixels
(no world coordinates exist)
Top left corner of the area is coordinate <0, 0>
A drawing surface has a width and height
example: JPanel has getWidth() and
getHeight()methods
Anything drawn outside of that area is not visible
JComponent
To begin drawing we first need a class which extends
JComponent. For this we can use JPanel.
Once we subclass JPanel we can override the
paintComponent() method to specify what we
want the panel to paint when repainting.
When painting we paint to a Graphics context
(which is given to us as an argument to
paintComponent). This will be supplied when the
method is called but this means that…
paintComponent(Graphics g)
paintComponent() is the method which is called
when repainting a Component.
It should never be called explicitly, but instead
repaint() should be invoked which will then call
paintComponent()on the approriate Components.
When we override paintComponent()the first line
should be super.paintComponent(g)which will
clear the panel for drawing.
Graphics vs. Graphics2D
We are passed a Graphics object to
paintComponent() which we paint to. We can always
cast the object passed to a Graphics2D which has much
more functionality.
Why didn’t Java just pass Graphics2D in?
Legacy support
Cast:
public void paintComponent(Graphics g) {
// Clear the Component so we can draw to a fresh
canvas
super.paintComponent(g);
// Cast g to a Graphics2D object
Graphics2D g2 = (Graphics2D)g; }
Now for some Drawing…
Now that we have our Graphics2D object we can
draw things to the graphics context which will show up
on our panel:
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g;
g2.drawString("Draw a string to the context...", 20, 20);
}
Note: When drawing strings the coordinates refer to the
BOTTOM left corner.
Pulling It Together
Now that we have a JPanel with an overridden
paintComponent()we can add this JPanel to a
JFrame and display it to our user.
But drawing is about more than just strings, and Java
gives us lots of additional drawing features.
Note: A common pitfall is to try to call
paintComponent() explicitly. Make sure never to
do this, always use the repaint() function!
Using Java Geometry
Classes within java.awt.geom:
AffineTransform, Arc2D, Arc2D.Double, Arc2D.Float, Area,
CubicCurve2D, CubicCurve2D.Double, CubicCurve2D.Float,
Dimension2D, Ellipse2D,Ellipse2D.Double,
Ellipse2D.Float, FlatteningPathIterator, GeneralPath,
Line2D, Line2D.Double, Line2D.Float, Path2D,
Path2D.Double, Path2D.Float, Point2D, Point2D.Double,
Point2D.Float, QuadCurve2D, QuadCurve2D.Double,
QuadCurve2D.Float, Rectangle2D, Rectangle2D.Double,
Rectangle2D.Float, RectangularShape, RoundRectangle2D,
RoundRectangle2D.Double, RoundRectangle2D.Float
What are these useful for? Let’s look at a Code example!
Using Java Geometry (cont.)
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g;
//
xcoord, ycoord, width, height
Rectangle2D body = new Rectangle2D.Double(30.0, 70.0, 200.0, 50.0);
g2.draw(body);
}
Note: For all other shapes the coordinates refer to the TOP left corner.
Note: Rectangle2D.Double means that Double is an inner class
contained in Rectangle2D.
The difference between g2.draw() and g2.fill() is that draw will
draw an outline of the object while fill will fill in the object.
Until now we have been drawing using a basic penstroke (just a plain black
line). Now that we know how to draw shapes we can start messing with the
pen.
Pen Styles
Let’s look at some Pen Styles that we can set:
// Basic functionality
// Set the Pen color or pattern (Let’s focus on color)
g2d.setPaint(fillColorOrPattern);
// Set the Stroke style
g2d.setStroke(penThicknessOrPattern);
// Set Alpha (transparency)
g2d.setComposite(someAlphaComposite);
g2d.setFont(someFont);
// More advanced functionality
// Translate to a different point
g2d.translate(...);
// Rotate around our origin
g2d.rotate(...);
// Scale everything we draw
g2d.scale(...);
// Shear what we draw
g2d.shear(...);
Color
To begin modifying the pen we first need to understand
color. The Color class in Java allows us to define and
manage the color in which shapes are drawn.
Colors are defined by their RGB (Red, Green, Blue) values
(and possibly an alpha value). Any color can be expressed
this way (i.e. Conifer is defined as (140, 225, 65)):
The way to tell Java to use a particular color when drawing
is by invoking g2.setPaint(Color c) or
g2.setColor(Color c) . These two are equivalent.
Color (cont.)
There are some predefined colors in Java which can be
accessed through the Color class (i.e. Color.BLUE is
blue). Additionally we can define our own colors through
the Color constructor:
Color conifer = new Color(140, 225, 65);
The values that go into the constructor are 1 byte large
(means that the min is 0 and the max is 255). This allows
Java to store a color into 4 bytes (the size of an int)
including its alpha value (transparency, more on that later).
Strokes
g2.setStroke(…) allows us to create different
strokes and create effects (such as a dotted line).
// 30 pxl line, 10 pxl gap, 10 pxl line, 10 pxl gap
float[] dashPattern = {30.0f , 10.0f , 10.0f, 10.0f};
// float width, int cap, int join, float miterlimit, float[] dash,
// float dash_phase
g2d.setStroke(new BasicStroke(8, BasicStroke.CAP_BUTT,
BasicStroke.JOIN_MITER, 10, dashPattern, 0));
g2d.draw(getCircle());
Note: Assume getCircle() returns a geometric circle.
What is a cap or join?
Cap and Join
JOIN_MITER
– Extend outside edges of lines until they meet
This is the default
JOIN_BEVEL
– Connect outside corners of outlines with straight line
JOIN_ROUND
– Round off corner with a circle that has diameter equal to the pen width
CAP_SQUARE
– Make a square cap that extends past the end point by half
the pen width
This is the default
CAP_BUTT
– Cut off segment exactly at end point
Use this one for dashed lines.
CAP_ROUND
– Make a circle centered on the end point. Use a diameter
equal to the pen width.
What does this all mean!? Let’s look at a graphical example…
Cap and Join (cont.)
Alpha (Transparency)
Concept:
We want to assign transparency (alpha) values to drawing operations
so that the underlying graphics partially shows through when you draw shapes
or images.
Execution:
Create an AlphaComposite object using
AlphaComposite.getInstance (Singleton anyone?) with a mixing rule.
There are 12 built-in mixing rules but we only care about
AlphaComposite.SRC_OVER. See the AlphaComposite API for more
details.
Alpha values range from 0.0f to 1.0f, completely transparent and completely
opaque respectively.
Finally pass the AlphaCoposite object to g2.setComposite(…) so that
it will be used in our rendering.
Let’s see an example…
Alpha (Transparency, cont.)
Code:
public void paintComponent(Graphics g)
{
Composite originalComposite = g2d.getComposite();
g2d.setPaint(Color.BLUE);
g2d.fill(blueSquare);
AlphaComposite alphaComposite =
AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f);
g2d.setComposite(alphaComposite);
g2d.setPaint(Color.RED);
g2d.fill(redSquare);
g2d.setComposite(originalComposite);
}
Note: assume blueSquare and redSquare exist with set
positions and sizes
Fonts
A Font object is constructed with 3 arguments to indicate the
logical font names such as "SansSerif"
style such as Font.PLAIN and Font.BOLD
font size
Once we have constructed a font we can tell Java to use it using
g2.setFont(Font f).
Example:
Font aFont = new Font("SansSerif", Font.BOLD, 16);
g2.setFont(aFont);
Note: Avoid font names like "AvantGuard" or "Book Antiqua" since they may not be
installed
Instead, use logical font names mapped to fonts actually installed. On windows,
SansSerif is Arial. Examples:
"SansSerif"
"Serif"
"Monospaced"
"Dialog"
"DialogInput"
Fonts (cont.)
Let’s say we want to use a cool font but we aren’t sure if it’s
installed. We can ask the environment for all of the installed
Fonts (this is slow so only do it once, i.e. not in
paintComponent()). We can then safely use the font if we
know it is present.
Example:
GraphicsEnvironment env =
GraphicsEnvironment.getLocalGraphicsEnvironment();
String[] fontNames = env.getAvailableFontFamilyNames();
System.out.println("Available Fonts:");
for(int i = 0; i < fontNames.length; i++)
System.out.println(" " + fontNames[i]);
What happens if we try to use a Font that isn’t installed?
Java will use one of the default fonts
Advanced Functionality
Most of the advanced functionalities shown earlier
(translate, rotate, shear, etc.) are beyond the
scope of this class. We will discuss
affineTransformations so that you can rotate
your images in your program (if you need to).
Before we can talk about rotating images we need to
know how to instantiate and draw them!
Images and BufferedImage
The BufferedImage subclass describes an Image with an accessible
buffer of image data.
BufferedImages are what we will use to draw images to the screen.
Once we have an image, painting it to our canvas is easy:
public void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g;
// Image img, int x, int y, ImageObserver observer
g2.drawImage(image, 100, 100, null);
}
Note: Assume image is defined. There are many overloaded definitions
of drawImage, you can find them all in the API
Now we know how to paint an image, but we don’t have an image!
Drawing to a BufferedImage
Let’s say we are writing a JPaint program which will
allow us to paint images, much like MS Paint, and then
save them to the disk. How could we do this?
We create a blank BufferedImage, paint to it, and
then write it to the disk.
Example:
// Construct a blank BufferedImage (width, height, type of image)
BufferedImage bufferedImage = new BufferedImage(100, 100, BufferedImage.TYPE_INT_RGB);
// Create a Graphics object for this image
Graphics2D g2dImg = bufferedImage.createGraphics();
// Paint whatever we want to it (use g2dImg as you would g2 in paintComponent)
g2dImg.drawString(“String on my image”, 100, 100);
Now we want to write our BufferedImage to disk. How can we achieve this?
ImageIO
Java provides a library for reading and writing Images from and
to the hard disk so that we don’t have to deal with coding it
(becomes very complex when dealing with multiple types such as
PNG and JPEG).
There are methods for reading and writing images to a file.
Reading an image from the disk:
BufferedImage img = ImageIO.read(new File(“myimg.png”));
Writing an image to disk:
try
{
// Assume image is from our previous slide
ImageIO.write(image, "png", outputfile);
}
catch (IOException e)
{ ... }
Animation
At its simplest, animation is the time-based alteration of
graphical objects through different states, locations, sizes and
orientations.
Most animation consists of cycling through a pre-made set of
bitmaps at a certain rate.
In general a rate of 24 images (frames) per second is used in
film, but many video games do well over this.
Note that most displays only support around 60 refreshes per
second.
Advanced Swing
F2-24
Animation: Timing
Since code execution time may vary (even on the same
machine), animations should take into account how much
time has passed from the last refresh to calculate the new
values of an animation.
When doing animation – note that
System.currentTimeMillis() usually has a
resolution of 16 milliseconds.
System.nanoTime()is more precise. (But not to the
precision of one nanosecond!)
Advanced Swing
F2-25
Animation: Simple Timer Example
Here we set a timer to wake up every 10 milliseconds,
update a count, and request a repaint.
timer = new javax.swing.Timer(10, new ActionListener() {
public void actionPerformed(ActionEvent evt) {
count++; // count should be volatile!
repaint();
if (count == 100) {
timer.stop();
} }});
// The timer is started when animation is started
// The code in paintComponent() uses the count to calculate
// how far along the animation is.
public void paintComponent(Graphics g) {
...
double percentage = (count / (double) 100);
int h1 = (int) (unitHeight * percentage); }
Animation: Double Buffering
Double buffering is the mechanism of using a
second bitmap (or buffer) which receives all of
the updates to a window during an update.
Once all of the objects of a window have been
drawn, then the bitmap is copied to the
primary bitmap seen on the screen.
This prevents the flashing from appearing to
the user that occurs when multiple items are
being drawn to the screen.
Animation: Double Buffering In
Swing
Swing now (as of Java 6) provides true double
buffering. Previously, application
programmers had to implement double
buffering themselves.
The no-arg constructor of JPanel returns a
JPanel with a double buffer (and a flow layout)
by default.
"...uses additional memory space to achieve fast,
flicker-free updates."
Animation: Double Buffering In
Swing
Each box drawn
Backing Bitmap
(not visible to user)
Window Bitmap
(visible to user)
Drawn
1st
Drawn
2nd
Drawn
3rd
Drawn
4th
in order might be
detectable by user.
The entire backing
bitmap is copied at
once to the visible
bitmap, avoiding any
flicker.
Animation: Coalescence Of
Requests
Swing will coalesce (or merge) a number of
repaint requests into one request if requests
occur faster than they can be executed.
Caveat to Animated GIF’s
Using animated GIF’s in your projects is okay, but there is a
better way to animate things.
Problems with animated GIF’s:
The timings between frames are set in stone and cannot be
controlled by the programmer.
During the first animation of the GIF it will appear that your
image is not loaded since the GIF loads each frame as it is
displayed (looks ugly, but can be avoided using image
loaders)
Programmers also can’t control which frame to display or
start/stop on since the GIF just keeps animating.
Instead of Animated GIF’s, most 2D games will use what is
called a SpriteSheet and then animate using a Timer or
Thread.
Sprite Sheets
A sprite sheet is a depiction of various
sprites arranged in one image, detailing
the exact frames of animation for each
character or object by way of layout.
The programmer can then control how
fast frames switch and which frame to
display. Additionally the whole sheet is
loaded at once as a single image and
won’t cause a loading glitch.
In Java we can use the BufferedImage
method:
getSubimage(int x, int y, int
w, int h)
Returns a subimage defined by a
specified rectangular region.
Basic Affine Transformations
An affine transformation is a transformation which is a combination of single
transformations such as translation or rotation or reflection on an axis
What does this mean for us?
It means we can rotate our images with relative ease!
More complex affine transformations won’t be discussed here but there are
tutorials widely available on the all-powerful internet.
Here we finally get to see a functionality only available in Graphics2D:
drawImage(Image img, AffineTransform xform,
ImageObserver obs)
Description: Renders an image, applying a transform from image space
into user space before drawing.
Note: When rotating, remember that you are rotating around the your origin,
so if you don’t set up your transformation correctly it will rotate your image
wrong!
Let’s see an example!
Basic Affine Transformations (cont.)
public void paintComponent(Graphics g)
{
Graphics2D g2d = (Graphics2D)g;
AffineTransform at = new AffineTransform();
// rotate 45 degrees around image center
at.rotate(45.0 * Math.PI / 180.0, image.getWidth() / 2.0,
image.getHeight() / 2.0);
//draw the image using the AffineTransform
g2d.drawImage(image, at, null);
}
Assume image is defined.
Why do we need to specify the second and third arguments to rotate?
This specifies our image center as the point that we are rotating around, otherwise we rotate around 0,0 which
isn’t what we intended!
Wrap-up
Questions, Comments, Concerns, etc?
Sprite Sheet Example (if time permits)