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
|
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.*;
public class UrlEx extends Applet {
public void init( ){
|
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){
/* 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){
|
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.*;
public class pin extends java.applet.Applet {
public void init () {
addMouseMotionListener(
new MouseMotionListener ( ) {
public void paint (Graphics g) {
public void update(Graphics g) {
// reduces but doesn't
|
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.
import java.awt.*;
public class pin2 extends java.applet.Applet {
public void init ( ) {
// in init(), a blank off-screen
image is created and a graphics
addMouseMotionListener(
new MouseMotionListener () {
public void update(Graphics g) {
|
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( ){
|
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.
int temp = index;
Create a synchronized method called from run( )
which takes care of incrementing
synchronized void IndexUp( ){
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.