references: Animation, 'Java
1.2 Developer's Handbook, Heller&Roberts
Graphics 'Just Java' Peter v.d.Linden
JDK1.2.2 API documentation
Animation is showing several images or frames
in
quick succession. The human eye sees a series of
slightly different frames as movement, a quality known as persistence.
Changing the entire frame is
called frame animation, while changing
a small area is called sprite animation.
The principle enemy
of animation quality (aside from bandwidth restrictions on downloads)
is flicker. Flicker can be reduced
or eliminated by using the programming techniques such as 1)
clipping, 2) double buffering.
and in
the case of Java, 3) overridding update.
Sprites are shapes that move across the screen, often tracking
the mouse. The general problems associated with sprites is repairing
of an area of the screen from which
the sprite has been moved and the reduction of flicker. Graphics animation
can be achieved by using
Graphics drawing methods in conjunction with loops, and changes
to the (x, y) drawing coordinates.
A variation on this is to draw an image to screen and have it's (x,
y) coordinates be changed.
Conventional animation, the kind we see at the theatre, requires a
series of images of the same rectangular
dimensions to be passed over a screen at a fixed rate. Something over
24 frames per second appears
continuous to the human eye. In this situation, where the whole screen
is changed as each image is shown,
Java affords the optimization of overriding update. (The flicker producing
process of resetting the
background color can be eliminated.)
Clipping Rectangles
Clipping is a standard graphics technique to restrict the amount of
graphics area being updated so
unchanged areas of the screen do not have to be repainted. It is used
extensively in animation. Graphics
class' clipRect( ) method is declared,
public void clipRect( int x, int y, int width, int height );
Because clipRect( ) only works by reductions, the method setClip(
int x, int y, int width, int height )
was added to work on areas of unrestricted size.
Adjusting the Clip Region
clipRect( ) limits or 'clips' the area being repaired to that
specified by the clip rectangle. All that needs
to be repaired is the union of the old and new sprite locations, described
by the smallest rectangle that
contains both the old and new sprite positions. When clipping buffering
to an offscreen location should
be used to eliminates flash.
The algorithm occurs in three steps.
1) set the clip region
2) draw the background image
3) drawing the sprite on the new location.
Setting the clip region
Imagine the following table represents an area
of a screen that is being effected by a sprite animation.
The text Sprite
position, (representing an image), is
to be repositioned from the lower-left panel to
the upper-right.The rectangular union of the
two panels is the area that will need to be repainted. Here
the rectangular union is represented by the bounds
of the table itself.
|------ rectangular union width -------|
A | Sprite position |
Sprite position | B |
Java's Rectangle class has a union( ) method which can simplify this process.
example
If R1 and R2 are Rectangle objects, their union is described
by a rectangle returned by
the union method
Rectangle U = R1.union(R2);
Rectangle class has methods such as getX( ),
getY( ), getWidth( ) & getHeight( ) which can be used
to provide parameters for the clipRect method.
int height | The height of the Rectangle. |
int width | The width of the Rectangle. |
int x | The x coordinate of the Rectangle. |
int y | The y coordinate of the Rectangle. |
Constructors
Rectangle
(int width, int height) |
Constructs a new Rectangle whose top-left corner is at (0, 0) in the coordinate space, and whose width and height are specified by the arguments of the same name. // 1 of 6 variations |
Methods ( 6 of 37 methods)
void add (int newx, int newy) | Adds a point, specified by the integer arguments newx and newy, to this Rectangle. // one of three |
boolean contains (int x, int y) | Checks whether or not this Rectangle contains the
point at the specified
location (x, y). // one of four |
Rectangle2D createIntersection
(Rectangle2D r) |
Returns a new Rectangle2D object representing the
intersection of this
Rectangle with the specified Rectangle2D. |
Rectangle2D createUnion
(Rectangle2D r) |
Returns a new Rectangle2D object representing the
union of this Rectangle
with the specified Rectangle2D. |
boolean intersects (Rectangle r) | Determines whether or not this Rectangle and the specified Rectangle intersect. |
void translate (int x, int y) | Translates this Rectangle the indicated distance,
to the right along the
x coordinate axis, and downward along the y coordinate axis. |
union (Rectangle r) | Computes the union of this Rectangle with the specified Rectangle. |
public class zapplet extends java.applet.Applet{ |
public void init( ) { . . . } |
public void start( ) {. . .} |
public void stop( ) {. . .} |
public void paint(Graphics g){ . . .} |
} |
Framework and methods a Threaded
Applet used in Animation
public class tv_applet extends
java.applet.Applet
implements Runnable{ |
applet implements the Runnable interface |
Thread t ; | declare a Thread class reference |
public void run( ){ // thread action } | Runnable's method requiring implementation |
public void init( ) { . . . . } | same function as in conventional applet |
public void start( ) {
t = new Thread(this); t.start( ); } |
A new thread is started with this, the containing
applet, serving as the target. The target implements Runnable by a body for run( ). Thread's start calls run |
public void stop( ){
t.interrupt( ); t=null; } |
Thread's stop( ) is no longer to be used inside applets stop( ). Instead interrupt and assign to null or just assign to null |
public void paint(Graphics g) {
. . . }
} |
same role except adapted to do animations |
The most common form of animation uses arrays of images. An integer
variable is used to indicate the
index of the next frame to be displayed. The animation thread executes
in an infinite loop in which it
1) sleeps, 2) increments the index and 3)
calls repaint( ).
import java.awt.*;
import java.applet.Applet;
public class Bogus extends Applet implements Runnable{
Image images[];
int index;
Thread animator;
public void init( ){
images = initImages( );
// this method provides the images it builds and isn't listed here
index=0;
// if coming from file MediaTracker would be used
animator = new Thread(this);
}
/* The animation thread
executes in an infinite loop in which it
1) sleeps, 2) increments the index and 3) calls repaint( ).
*/
public void run( ){
while(true){
try{
Thread.sleep(100 );
}catch(InterruptedException ex){ }
repaint( );
index = ++ index % images.length;
}
}
public void paint(Graphics g){
// paint is overridden to draw the images
g.drawImage(images[index], 0,0,this);
}
}
Design Problems in the typical animation example
1) When the browser is away from the page the animator thread should
be paused so it doesn't
waste CPU cycles. Code should also be prepared
to cause the animation to resume.
2) The applet should offer the user the option to start or stop the animation.
3) update( ) should be overridden to reduce flicker. // double buffering could be used as well
4) ++Index is vulnerable to interrupts. The thread could be pre-empted
after the index was incremented
but before the modulo operator
was called resulting in an out-of-bounds exception.
public void start( ){
synchronized(this){
// here's the synchronized block you don't see that often
suspendRequest = false;
notify( );
// wait( ), notify( ) and notifyAll( ) are defined in Object
}
// notify( ) tells the thread to run when ready
}
public void run( ){
while(true){
if(suspendRequest){
sychronized(this){
try{
wait( );
}
catch(InterruptedException x) { }
}
}
try
{Thread.sleep(100);
}catch(InterruptedException e) { }
index = ++ index % images.length;
repaint( );
}
}
public void itemStateChanged(ItemEvent ev){ // the method does
the same job as start and stop
if (theCheckbox.getState( ) = = true)
suspendRequest
= true;
else {
synchronized(this)
{
suspendRequest
= false;
notify( );
}
}
}
/* the revision to start( ) makes sure the animation remains
off if checked off and the page is left and
then returned to */
public void start( ) {
if (theCheckbox.getState( ) =
= false){
synchronized(this){
suspendRequest = false;
notify( );
}
}
}
To review, update( ) by default is:
public void update(Graphics g) {
g.setColor(getBackground( ));
g.fillRect(0,0,width,height);
g.setColor(getForeground( ));
paint(g);
}
// overridden to just painting the
foreground, update( ) becomes
public void update(Graphics g){
paint(g);
}
Two means can be used to solve this problem, the problem being, if at
index max - 1, index is
incremented to max but not modulo operated on, an array index
out-of-bounds exception will occur.
Looking at the code, index = ++ index % max; the line is
not atomic.In assembly programming, an
atomic operation is one which can't be interrupted. In the above line
several assembly-like statements
occur between getting the value, incrementing the value then reassigning
the new value to index. There
are many places where interrupts could occur.
Solution 1
The JVM spec states, 32 bit writes (i.e. index = temp) are atomic. //not
64's so not true for long
Therefore,
int temp = index;
temp = ++ temp % max ;
index = temp;
// this is an atomic, uninterruptable line,
fixes the problem.
Solution 2
Create a synchronized method called from run( ) which takes care of
incrementing the index.
So in run( ), a method like the following would be called.
// something to consider,
synchronizing code has a performance penalty which may or may not be significant
synchronized void IndexUp( ){
index = ++ index % max:
}
The act of synchronizing makes sure the method returns without being
interrupted.
Image Processing
Transparency
| alpha bits 31 to 23 | red bits 23 to 15 | green bis 15 to 7 | blue bits 7 to 0 |
Alpha set to 0 is totally transparent of invisible.
Alpha set to 255 is a solid color. Values between
vary in translucence or
opacity.
ImageConsumer
The abstract class ImageConsumer converts
an image into an array of pixels. PixelGrabber extends
ImageConsumer.
PixelGrabber
1) Instantiate PixelGrabber taking an image (or ImageProducer), dimensions, and an int array as arguments.
PixelGrabber pg = new PixelGrabber( lots of arguments );
2) call grabPixel( ) to fill the int array with pixels from the image
pg.grabPixels( );
3) check the status to see if bits were grabbed successfully
if (pg.status( ) & ImageObserver.ALLBITS)
!=0)
// all bits grabbed successfully
PixelGrabber constuctors
public PixelGrabber( Images img, int x, int y int w, int h, int[]
pix, int off, int scansize );
public PixelGrabber( ImageProducer img, int x, int y int w, int
h, int[] pix, int off, int scansize );
where 1)
img is the image to retrieve pixels from
2) x & y are upper left hand corner coordinates
3) w & h are the width and height of the rectangle of pixels to retrieve
4) pix is the array of integers used to hold the RGB values
5) off is the offset into the array of where to store the first pixel
6) scansize number of pixels associated with each array row
ImageProducer
MemoryImageSource is a concrete extension ofthe
abstract ImageProducer class.
MemoryImageSource
MemoryImageSource serves the reverse function of PixelGrabber,
reading an array of pixels
and returning an image. An instance of MemoryImageSource is created.
The instance is supplied
to Component.createImage( ) which returns an Image object.
Simplest of six constructors is,
public MemoryImageSource(int w, int h, int pix[], int off, int scan)
// a w x h MemoryImageSource starting from the pix[off] element in the
array of pixels, with scan
pixels in each line // other constructors add Hashtables and ColorModels
RubberBanding // really more of a graphics than animation technique
Rubber-band lines are outlines of regions on the screen created by clicking
and dragging the mouse.
(mousePressed( ) creates the first point and mouseDragged( ) and mouseReleased(
) a second point which
defines the diagonal corner of a rectangular region. (the user sees
the rectangle stretching and retracting
with the mouse movement hence the rubber analogy.)
Summary of the logic
1) The x and y coordinates of of the mouse
pressed set the anchor corner of the rectangle
2) Dragging, new x and y's are created, call
to repaint( ) during dragging represent tracking
3) In paint( ) the whole background is redrawn
and the final rectangle is drawn.
4) Override update( ) to just paint( ) to
reduce flicker. // Look at code if time in the lab
Why you should do your drawing in paint and not in action handlers
1) Easy maintenance and debugging as all drawing operations are centrally
located in paint( )
2) All screen state data is located in one spot so the screen is rendered
accurately and completely
3) Painting outside paint( ) won't neccessarily be repaired in
case of screen damage from an overlapping window.
setXORMode( ) vs setPaintMode( )
In regular paint mode pixel values are set independently of previous
pixel colors. In XOR drawing the
new color is computed by XORing the previous pixel with the current
pixel. setXORMode( ) sets
XOR drawing, and takes an instance of Color as an argument (the XOR
color of the graphics object)
This color mixed with the current color of a draw plus the color being
overwritten are XOR'd together
to create the new pixel.
new pixel = old pixel ^ current color ^ XOR color
There are many interesting relationships between the pixels after repeated
drawings but the important
point is drawing twice is the same as not
drawing at all. If a graphics object is in XOR mode, a box
drawn with g.drawRect(x,y,w,h)
and then undrawn by the same call.
Disadvantages
1) Imprecision using drawRect
over
a photo may cause a visibly obscure multicolored box to appear.
Also using XOR to erase may leave visible traces
on screen.
2) Unpredictable if a pixels data
refer to color tables rather than a true color model
3) Programming overhead is
associated with adding logic to the paint method
PixelGrabber
public PixelGrabber(Image img,
int x,
int y,
int w,
int h,
int[] pix,
int off,
int scansize)
Create a PixelGrabber object to grab the (x,
y, w, h) rectangular section of pixels from the
specified image into the given array. The
pixels are stored into the array in the default RGB
ColorModel. The RGB data for pixel (i, j)
where (i, j) is inside the rectangle (x, y, w, h) is
stored in the array at pix[(j - y) * scansize
+ (i - x) + off].
Parameters:
img - the image
to retrieve pixels from
x - the x coordinate
of the upper left corner of the rectangle of pixels to retrieve from
the image, relative
to the default (unscaled) size of the image
y - the y coordinate
of the upper left corner of the rectangle of pixels to retrieve from
the image
w - the width
of the rectangle of pixels to retrieve
h - the height
of the rectangle of pixels to retrieve
pix - the array
of integers which are to be used to hold the RGB pixels retrieved from
the image
off - the offset
into the array of where to store the first pixel
scansize - the
distance from one row of pixels to the next in the array
MemoryImageSource
public MemoryImageSource(int w,
int h,
int[] pix,
int off,
int scan)
Constructs an ImageProducer object which uses
an array of integers in the default RGB
ColorModel to produce data for an Image object.