Java Drawing in 2D Animations With Timer
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: 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);
g2d.draw(getCircle());
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.
AlphaComposite alphaComposite =
AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f);
g2d.setComposite(alphaComposite);
g2d.setPaint(Color.RED);
g2d.fill(redSquare);
g2d.setComposite(originalComposite);
}
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]);
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).
Drawn Drawn
3rd 4th
Window Bitmap
(visible to 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);
}