Images and Animation    latest revision Aug 27 / 2001  Peter Komisar
 

reference:, 'Just Java' Peter van der Linden, The Java Developer's Handbook, Heller&Roberts


Images

"A picture paints a thousand words", so the saying goes.

Images are an intrinsic part of the information processed by computers whether in the local
memory and storage context of a PC or in the sense of  a PC being an extension of the larger
domain known as the Internet. Images hold pictures in memory. Aside from retrieving images
over the Internet, pictures can be scanned into a computer and then stored as files.

HTML and the Internet have established wide spread support for two common image file
types, the gif and jpeg file formats. Java also has supports these file formats for processing
images.

The Gif file format

GIF files, an abreviation for Graphics Interchange Format were developed by CompuServe
and use 8-bits to store the color information for a single pixel (picture element). Gif files use
a compression system based of the Lempel-Ziv-Welch algorithm patented by Unisys. Gifs
are a low quality picture format which would be headed for extinction except they are well
suited for simple drawings, like backgrounds and cartoon images that do not require highly
granular color descriptions. Also their small size make them economical to store in memory.

The JPEG File Format

The JPEG is a file format was developed by the Joint Photographic Experts Group and is
an accepted ISO/ITU standard. The JPEG format ( pronounced jay-peg ) is capable of
photographic quality. At the same time it provides a very high compression capability. The
format allows a controlled trade off between photo quality and storage capacity where
image quality can be specified as a percentage. At it's highest quality, JPEG files store
color information at 24 bits per pixel which is a perfect impedence match for java which
stores red green blue values in 24 bits, one byte per color.

JPEG uses mathematical algorithms called discrete cosine transforms to provide what is
called lossy compression. Lossy compression gets it's name from the fact that information
is lost when very high compression ratios are used, in the order of 100:1 or higher. The
format is very effective as relatively high compression ratios of 10 or 20 to one will often
not show any visible loss of information. The method the format uses involves divided the
image into a grid of small pixel blocks which reduced by half repeatedly until the desired
ratio is obtained.

The motion picture industry use a variation of the JPEG file called MPEG for releasing
motion pictures to digital video formats.

Get the Image and Draw it to Screen

There are really only two things that need to be done to work with images in a Java program.
First, the image has to be brought into the application. This entails streaming the bytes that
describe the image from the storage file and into the java program. Inside the program the
byte collection will be represented by an Image object. Once this has happened the image
can be drawn to screen using a call on the Graphics object from inside the paint method.
 

The getImage( ) method

The first step of getting an image into a java program is accomplished using one of many
getImage( ) methods defined by various classes in the Java hierarchy. For instance,
TexturePaint and ImageIcon define no-args versions of getImage( ). The abstract Toolkit
class, AppletContext and Applet all define getImage( ) methods that take a URL object.
The Toolkit class also defines a getImage( ) method that takes a String representing a
file and Applet has a getImage( ) method that takes a URL with a String object describing
path. Applet uses the URLs to get images partly because that is the standard way files
are located on the internet and also to get around the security restriction posed by the
applet sandbox which prohibits accessing files on a client's machine. Note an applet has
access to the machine from which it originates, so if the applet is created to run on a client
locally, the file system will be available for it's use.

The following code shows Toolkit's getImage( ) method being used. I found this image on
the web at a print sales shop. I have put the image in an Cezanne html page so you can
access it and save it to the local directory that you are running this code in.

Code Sample

import java.awt.*;
import javax.swing.*;

// portrait class gets and draws an image

public class Portrait extends JFrame {
       Image i;
            Portrait( String s ){
               super(s);
               Toolkit tools=Toolkit.getDefaultToolkit( );
                i=tools.getImage("cezanne.jpg");
               setBackground(Color.darkGray);
               setSize(400,300);
               setVisible(true);
               }
        // overridden paint method draws image
        public void paint(Graphics g){
          g.drawImage( i, 85, 70, this);
          }

        static public void main(String a[]) {
          new Portrait("  Apples and Oranges               by Cezanne" );
          }
  }

Often you will see the getImage and getDefaultToolkit done in a single step as
in the following example.

Example      i = Toolkit.getDefaultToolkit( ).getImage("cezanne.jpg");
 

Toolkit

The way computers render graphics to screen is a very low level process that will be
done uniquely on each platform, whether Windows or Linux on an IBM machine or
on a Mac running one of the Apple operating systems. Java takes an object-oriented
approach and describes an abstract Toolkit class. Then a version of this class is created
for each different platform Java is running on. The concrete toolkit implementations
communicate with their native platform peers. The documentation describes the Toolkit
calls as being the glue that joins the platform independant java classes to the native
classes represented in the java.awt.peer package. As we have seen, we can get a handle
to the concrete toolkit implementation via Toolkit's static method getDefaultToolkit.

Using getImage( ) in Applets

Images are loaded into applets by referencing uniform resource locators or urls. Applets
are usually restricted from reading and writing to the local file system for security purposes
(except on the machine from which they originate where they are considered  local and
trusted). On the other hand, applets are free, as are browsers, to access images on the
Internet so their getImage methods are defined taking a url. If your applet has the
SecurityManager class' permission to read and write to the local file system you can use
the Applet class getImage( ) method to read that file locally but as a rule this is not allowed.
Following are the getImage methods defined in Applet.

Applet's getImage( ) methods

Image  getImage(URL url)                           // takes a url and returns an Image object
Image  getImage(URL url, String name)  // takes a url to a directory where name is located
 

URL class

The uniform resource locator or url is one of the component parts of the http protocol that
the world wide web uses to communicate. Java encapsulates the url for it's own use in the
java class, URL located in the net package. Applets are able to load urls, both from the
String literals describing the url and from a java URL object representing a url. A link that
might be as a reference for urls is located at: http://www.ncsa.uiuc.edu/demoweb/url-primer.html
Following are examples of URL objects being created passing in standard urls as String
literals.

Example 1      URL u1 = new URL("http://www.mars.com / inhabitants / marsians.jpg");

Example 2       URL u2 = new URL("file:///c: / jupiter / moon / europa / "ocean.jpg");
 
 

The following examples show the two forms of the Applet's getImage( ) method being
used. The first takes a single url. The second takes a URL to a base directory where
an image file is located and referenced by the second String argument to the method.

Examples     Image j = getImage("http://Venus/Clouds.jpg");
                       Image k = getImage("http://Venus/ ", "Clouds.jpg");
 

getDocumentBase( ) & getCodeBase( )

Applet also defines two useful methods that can reference the location of the java applet
code and the location of the html code that contains the tag for the java applet. They are
handy because you may not know exactly what these paths are. getDocumentBase( )
returns the URL of the directory containing the html document (which is referencing an applet).
getCodeBase( ) returns the URL representing the directory where the applet class file resides.
Here is their short definitions straight from the JDK documentation.
 

Applet's getCodeBase and getDocumentBase( )  methods

 URL  getCodeBase( )       // returns the code base for the applet class file as a URL
 URL getDocumentBase( ) // returns the URL naming the directory of the document
                                                       // containing the applet the applet is embedded.

Example        Image cat = getImage( getDocumentBase( ), "Cat.jpg" );
 

A Caution  // maybe this has been remedied but we keep in to be sure all works with older jre's.

A weakness in the design of the imaging model in conjunction with how components are built
using native system calls, requires special treatment when using the methods getImage( ) and
createImage( ). It boils down to a component has to be 'realized', ( Some say, 'casting a shadow
to screen') before an image can be added to it. For applet the quasi-rule is to declare your
image in the applet name scope and then assign a value  via getImage( ) ininit( ) as is
shown in the example below. The same caution goes for using the createImage( ) method.

Example

public class A extends Applet{
   Image i;  //declare the Image reference outside init( )
   public void init( ){
   i = getImage( getDocumentBase( ), "Cat.jpg");
  }
}
 
  A Few AppletContext methods // to load an HTML page in a browser from an applet


 // An applet can cause an HTML page to be loaded into the browser using the following
    this.getAppletContext( ).showDocument(URL); 

  // to put a line into the browser status line 
  this.getAppletContext( ).showStatus( "Puts the message in the browser status line" );
.



 

Drawing an Image

Once you have brought an image into a java program you need to go about drawing it.
This is done with one of java's variations of the drawImage( ) method.

The Graphics class' four variations of drawImage( )

public boolean drawImage(Image img, int x, int y, ImageObserver observer);
// image drawn to original size

public boolean drawImage(Image img, int x, int y, int width, int height, ImageObserver obsvr);
// scales image to fit specified width and height

public boolean drawImage(Image img, int x, int y, Color bgcolor, ImageObserver observer);
// sets background color and draws with, no scaling

public boolean drawImage
(Image img, int x, int y,int width, int height,Color bgcolor, ImageObserver observer);
// sets background color and scales image
 
 
 Another Code Sample // showing getting and drawing images


 // <applet code=UrlEx.class height=400 width=500 ></applet>

 import java.awt.*; 
 import java.applet.*; 
 import java.net.*; 

 public class UrlEx extends Applet {
  Image image,image2;
  URL u1;

 public void init( ){
  image2=getImage(getCodeBase( ),"OttawaR.gif");
  // put your own .gif into this line
 try{
  u1=new URL("http://mars.sgi.com/mgs/msss/camera/images/"
  + "3_10_99_global/moc2_86_msss_icon.jpg");     // Marsian Globes
   }catch(Exception e) {System.exit(0);}
   image=getImage(u1);
  }
 public void paint(Graphics g){
   g.drawImage(image,15,15,this);
   g.drawImage(image2,15,220,400,180,this); 
 // Note this version scales the gif (See below)
   }
 }


 

ImageObserver

A call to getImage( ) returns immediately (after receiving a reference to the image file). At
some point a thread is started which reads (downloads) the file from the specified Internet
server's hard drive. The incoming image is an observable event. The Component class
(and by inheritance, it's descendants) implement the ImageObserver interface, referenced
by 'this' in the above example's drawImage( ) method. ImageObserver is similar in design
to EventListener interfaces, only it is specialized for 'watching' images as they are
downloaded.

imageUpdate( )

Component also has an imageUpdate( ) method which schedules a call to repaint when
more of an image is available to be drawn. The method is affected by two system properties.

1) awt.image.incrementaldraw
2) awt.image.redrawrate

incrementaldraw  has a boolean value which, when true, indicates an image is available
for painting while it is downloading, and when false, indicates it can be drawn only when
the image is fully loaded. redrawrate specifies the minimun time between image repaints
and is 100 milliseconds by default.

// 'Just Java' offers instructions on how to change these properties on pages 572 and 573

MediaTracker

MediaTracker is like ImageObserver but with the extended capability of tracking several
images simultaneously. To use the class, call the tracker's addImage( ) on each image you
wish to track. Each image can be assigned a unique ID which controls the order in which the
images are called. (Unique subsets can also be established that can be waited on independently.)
waitforID(n) suspends the thread until the complete image is loaded. isErrorAny( ) and
isErrorID(i) reports if all or a subset of the images loaded correctly.

MediaTracker Class Constructor
 
MediaTracker
(Component comp)
Creates a media tracker to track images for a given component.

 Example from P. Linden's 'Just Java' example p.575
 

public void init( ) {
   MediaTracker t=new MediaTracker(this); 
  // what caution is the next line ignoring?
   Image i = getImage( getDocumentBase( ) , "spots.gif" ); 
  // note text typo's missing round braces and a comma
   t.addImage( i , 1 );
   try{ t.waitForID(1); }
   catch(InterruptedException ie) {return;}
   // image is now in memory ready to draw
  }

A new MediaTracker is created for the container component. An Image object is being
created out of the stream of data coming from the gif file 'spots'. The image is added to a list
of images, via addImage( ) to the MediaTracker instance, and a waitForID(1) is being
called on it so the draw to screen will only happen when the whole image is in memory.


Overriding update( )

One of the main enemies of processing images, especially successive images in the process
of animation is flicker. One of the first lines of defence to prevent flicker is to override update
whose default definition repaints the background
 
 

 /* Here is the default definition of update( )   */

 public void update(Graphics g){
    g.setColor(getBackground( )); 
    g.fillRect( 0, 0, width, height);
    g.setColor(getForeground( ));
    paint(g);
 }



 /* The overridden version reduces flicker by not repainting the 
      background, useful if the whole component is renewed when 
      paint is called                                                                                */

 public void update(Graphics g){
 paint(g);
 } 

 Code from 'Just Java' P.v.d.Linden ,p.576  just for our discussion
 

//<applet  code=pin.class   width=600 height=350>  </applet>

import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;

public class pin extends java.applet.Applet {
    Image spirit, rolls;
    int new_x=550;
    int new_y=100;

    public void init () {
        spirit = getImage(getDocumentBase( ), "spirit.jpg");
        rolls = getImage(getDocumentBase( ), "rolls.jpg");

        addMouseMotionListener( new MouseMotionListener ( ) {
         public void mouseDragged(MouseEvent e){
             System.out.println("what a drag");
             new_x=e.getX( );
             new_y=e.getY( );
             repaint( );
         }
         public void mouseMoved(MouseEvent e){}
    } );
    }

    public void paint (Graphics g) {
        g.setColor(Color.gray);
        g.fillRect(0,0,getSize().width, getSize().height );
        g.drawImage (rolls,5, 5, this);
        g.drawImage (spirit, new_x-25, new_y-25, this);
    }

    public void update(Graphics g) {          // reduces but doesn't
        paint(g);                                                 // eliminate flicker
    }
 }

In the above class, each mouse move calls update where the screen is repainted, including the
background resulting in a flash. An improvement is obtained by not redoing the screen down to
the background and just calling paint( ) in update. Note if paint doesn't redo the background,
pieces of old images will remain and collect. This can be a useful effect. Overriding update( ) is
an excellent technique to use when the new image being added is the same size as the old one,
the new completely covering the old.
 

Double Buffering

Double buffering is offscreen imaging, doing the slow process of drawing to a graphics area
in memory first, then, when ready for presentation, painting the image to screen. Image and
Graphics objects are declared for offscreen use. An offscreen image is created to the size
required then an off screen, graphics context is obtained for it. paint( ) is used to first paint
to the off screen graphics context which is then drawn to the applets graphic's context g, on
screen with drawImage( ).

 Code from 'Just Java' P.v.d.Linden ,p.579 just for our discussion
 
 //<applet  code=pin2.class   width=600 height=350>  </applet>

 // same as pin, but uses double buffered output.
 // PvdL. 

 import java.awt.*;
 import java.awt.event.*;
 import java.awt.image.*;

 public class pin2 extends java.applet.Applet {
    Image spirit, rolls;
    Image myOffScreenImage;  // here's the offscreen image handle
    Graphics myOffScreenGraphics; // here's the offscreen graphics ref
    int new_x=550;
    int new_y=100;

    public void init ( ) {
        spirit = getImage(getDocumentBase(), "spirit.jpg");
        rolls = getImage(getDocumentBase(), "rolls.jpg");
        myOffScreenImage= createImage(getSize().width, getSize().height );
        myOffScreenGraphics = myOffScreenImage.getGraphics( );

 // in init(), a blank off-screen image is created and a graphics 
 // context is obtained  for it 

        addMouseMotionListener( new MouseMotionListener () {
           public void mouseDragged(MouseEvent e){
               System.out.println("what a drag");
               new_x=e.getX();
               new_y=e.getY();
               repaint();
   // incidently, here's a well placed call to repaint in the action method
           }
           public void mouseMoved(MouseEvent e){}
       } );
    }
   // paint( ) does it's usual thing
    public void paint (Graphics g) {
        g.setColor(Color.gray);
        g.fillRect(0,0,getSize().width, getSize().height );
        g.drawImage (rolls,5, 5, this);
        g.drawImage (spirit, new_x-25, new_y-25, this);
    }
 // here's the trick, in update, paint is called to work regularly  but  using
 // the second, off-screen graphics context to paint to the off-screen image 
 //  then, using the on screen, there is a quick call to drawImage 

    public void update(Graphics g) {
       paint(myOffScreenGraphics);    // draws on the db
       g.drawImage(myOffScreenImage,0,0, this); // draws the db onto applet
    }
 }

A Short Note on Animation

The basic engine for a java animation is housed in an infinite loop placed in a thread's run
method. In the loop a call to sleep regulates the rate at which the animation will show frames.
A call to repaint( ) ensures each new frame is painted. The last element in the loop is the
incrementation of the index which specifies the next frame or image to be displayed, normally
stored in an array of images. This counter effects the index of the images which are drawn
(via drawImage) in paint( ). The techniques mentioned in this note, i.e.double buffering and
overriding update( ), can be used to improve the smoothness of the animation. In addition
clipping can restrict the area of the screen which is updated if only a small part of the screen
has been changed.
 
/*  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;  // not atomic and may be interrupted
      }
}
public void paint(Graphics g){                   // paint is overridden to draw the images
     g.drawImage(images[index], 0,0,this);
     }
}

One thing that has to be guarded against is an index operation that is not atomic. This is
a process that can be potentially broken half way through it's execution by the preemptive
process in multithreading. A way to protect against it is to put the index incrementation in
a synchronized method or making sure the final step of the increment is naturally atomic.
 
 
Solution 1

 The JVM spec states, 32 bit writes (i.e. index = temp) are atomic.
 // but not 64 bit writes so this  for a long value this wouldn't be atomic
 Therefore the following change to the code  fixes the problem.

                                 int temp = index;
                                 temp = ++ temp % max ;
                                 index = temp;      // this is an atomic, uninterruptable line,
Solution 2

 Create a synchronized method called from run( ) which takes care of incrementing 
 the index.  In run( ), a method like the following would be called.

 synchronized void IndexUp( ){
 index = ++ index % max:
 }
 // something to consider, synchronizing code has a performance penalty which
//  may or may not be significant 

 The act of synchronizing makes sure the method returns without being interrupted.
.


 
           Note: Intro Java, final test coverage stops here! 


To complete the discussion of Graphics, you may wish to check the next supplemental note for
a more detailed discussion of Animation and Image Processing. Image Processing classes
provide means for composing images from arrays of pixels and decomposing images into arrays
of  pixels (like PixelGrabber and MemoryImageSource)

Java also provide filtering classes for changing the relative values of a set of pixels allowing for
easy the application of filters to images. Examples are CropImageFilter and RGBImageFilter.