Upload
york
View
38
Download
2
Tags:
Embed Size (px)
DESCRIPTION
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. - PowerPoint PPT Presentation
Citation preview
Jan SmrcinaReferences:Slides with help from Sun’s Java TutorialsRick Snodgrass’ Slides on Basic and Advanced Swinghttp://courses.coreservlets.com/Course-Materials/pdf/java5/12-Java-2D.pdf
Java Drawing in 2DAnimations with Timer
Drawing BasicsA 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
JComponentTo 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. Graphics2DWe 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 supportCast: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 TogetherNow 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 GeometryClasses 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, heightRectangle2D 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 StylesLet’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 styleg2d.setStroke(penThicknessOrPattern);// Set Alpha (transparency)g2d.setComposite(someAlphaComposite); g2d.setFont(someFont);
// More advanced functionality// Translate to a different pointg2d.translate(...); // Rotate around our origing2d.rotate(...);// Scale everything we drawg2d.scale(...);// Shear what we drawg2d.shear(...);// Use a custom transformationg2d.setTransform(someAffineTransform);
ColorTo 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).
Strokesg2.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 gapfloat[] dashPattern = {30.0f , 10.0f , 10.0f, 10.0f};
// float width, int cap, int join, float miterlimit, float[] dash,// float dash_phaseg2d.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 meetThis 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 usingg2.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 installedInstead, 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 FunctionalityMost 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 BufferedImageThe 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 observerg2.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 BufferedImageLet’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 imageGraphics2D 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?
ImageIOJava 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 slideImageIO.write(image, "png", outputfile);}catch (IOException e){ ... }
AnimationAt 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: TimingSince 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 ExampleHere 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 BufferingDouble 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 SwingSwing 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
drawnin order might be detectable by user.
The entire backing bitmap is copied at once to the visible bitmap, avoiding any flicker.
Drawn1st
Drawn2nd
Drawn3rd
Drawn4th
Backing Bitmap(not visible to
user)
Window Bitmap
(visible to user)
Animation: Coalescence Of RequestsSwing 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’sUsing 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 SheetsA 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-upQuestions, Comments, Concerns, etc?Sprite Sheet Example (if time permits)