Java applet Tutorial

Showing and manipulating pics via MemoryImageSource

1.01 Introduction

Welcome to my first Java tutorial. This one deals with a very simple zoomer. We load a picture into memory and zoom in and out until the user aborts the execution. For output we use a very simple and un-advanced method, only good enough for demonstration purposes. Have fun...  (((you can get the complete source here)))

1.1 Create Applet and Thread

First we need to create a new Class, inherited  from java.applet.Applet (which is inherited from java.awt.panel), with the interface Runnable (not absolutely necessary for the applet, but for the thread). An applet needs (in contrast to an application) no  main() method, because the init() and start() methods manage the execution of the applet. And we have to import some classes for loading and processing the pictures.

import java.awt.*
import java.awt.image.*

public class bild extends java.applet.Applet implements Runnable
{
  // applet source code here
}

We also need some variables for zooming and some for saving the picture in memory.
 

int width=216; // width of picture
int height=288; // height of picture
int zoomfaktor=3; // zoom level
int zstart=1<<zoomfaktor;  // zoom start value
int zstop=zstart*3; // zoom stop value
boolean quit=false; // if quit==true execution of applet stops
Image zoomimage; // contains the picture
Image display; // buffer for output
int zoomimageAr[]=new int [width*height]; // array for saving the zoomed picture
int displayAr[]=new int [width*height]; // array for output
MemoryImageSource mic; // keeps order of picture and array
Thread hThread=null; // the thread

                                                
The thread is created by calling the method start() which is done the first time the browser shows the applet or if the execution is continued. The method stop() aborts the execution (e.g. browser is closed), the thread is stopped.

public void start(){
  if (hThread==null){
    hThread = new Thread(this);
    hThread.start();
  }
}

public void stop(){
  if (hThread != null){
    hThread.stop();
    hThread=null;
  }
}

1.2. Initialization

A constructor is not necessary, because we initialize the thread in init() , which is always called when the browser loads the applet. But we have to use a MediaTracker to supervise the loading of the resources (pictures, sounds, ...), because we don't know when they are completely loaded. The method getCodeBase() returns the path to the applet, so there is no need to use absolute paths. By using getimage (URL, string) we could load and process gif and jpeg pictures. To get the color information of every single pixel we copy the data with a PixelGrabber from the Image variable to our (previously defined) array. The array has to be defined as array of int  because every pixel uses 4 x 8 bit for color information, the highest byte is the alpha value (for loaded pictures, this value is always 0xFF, opaque). The other three bytes represent the color values for red, green and blue. With the help of our MemoryImageSource object we later (after processing) re-convert the array back to Image.
 
 
 
public void init()
{
  MediaTracker tracker = new MediaTracker(this);
// create MediaTracker
  showStatus("Loading Image...");  // status bar shows "Loading..."
  zoomimage = getImage(getCodeBase(),"razor.jpg"); // load the picture
  tracker.addImage(zoomimage,0);  // adds the picture to 
// the MediaTracker
  try
    {
    tracker.waitForID(0); // wait for the picture to load
  }
  catch (Exception e)
  {
    showStatus("Error!!!!!!");
  }
// catch "loading error" exception
  resize(width, height);
  makear(); 
// convert Image to array
   mic=new MemoryImageSource (width, height, displayAr, 0, height);
  showStatus(" ");
}
public void makear(){
  PixelGrabber Grabber=new PixelGrabber(zoomimage.getSource(), 0, 0, width, height, zoomimageAr, 0, width);
  try{
    Grabber.grabPixels();
  }
  catch (Exception e){
    return;
  }
}

 

1.3 The Calculation

The method run() is called by the thread and contains the actual code. Not much to do here, the new picture is calculated 
by ZoomIn (int) and displayed with repaint(). Then we stop the thread for 30 ms, to slow down the demo. The next Tutorial will have automatic time measurement to get better control of the speed (on different pc's the same execution speed). One problem of Java is that it manages the memory usage on it's own and some methods and objects are quite generous with memory.
The GarbageCollector does his work in the breaks. But according to my experience he doesn't use this breaks very wisely, that's why the applet performance gets worse and worse after some time of running and you get the feeling that it formats your HDD.
To prevent that memory gets to low, we call gc() by ourselves every time we had a complete zoom cycle.
ZoomIn(int) does the actual calculation of the next image, but optimization is currently very low. The method is synchronized as paint() is to prevent the simultaneous output and calculation of the same picture (distorts the displayed picture)
To display the calculated image (saved in displayAr) on the screen we copy the data from the array to the  Image object by using  createImage (MemoryImageSource).
Here is a flow chart which shows the way of the image till output :
 
 
getImage PixelGrabber Calculation Memory-
razor.jpg --------> zoomimage -----------> zommimageAr ----------> displayAr --- Image-
    | Source
  to screen <----------- display <---------- mic <-- 
drawImage createImage  

public void run(){ 
  int zaehler=1;
  int n= zstart;

  while (!quit){
    if (n==zstop) zaehler =-1;
    if (n==zstart) zaehler =1;
    n+=zaehler;
    ZoomIn (n); 
    repaint();
    try{
      Thread.sleep(30);
    } 
    catch (Exception e){}
    if (n==zstart) System.gc();
  }
}

public synchronized void ZoomIn (int n){
  int x=0;
  int y=0; 
  int xbuf=0;
  int ybuf=0;
  int i2buf = 0;

  int mult216[]=new int [height];                                  // to save one mutliplication
  for (int i=0;i<height;i++) mult216[i]=i*width;
  xbuf=((n-zstart)*11)<<(zoomfaktor);
  ybuf=((n-zstart)*11)<<(zoomfaktor);

  for(int i2=0;i2<=height-1;i2++){
    x = mult216[(((i2<<zoomfaktor)+xbuf))/n];
    i2buf=mult216[i2];

    for(int i1=0;i1<=width-1;i1++){ 
      y =(((i1<<(zoomfaktor))+ybuf))/n;
      displayAr[i2buf+i1]=zoomimageAr[x+y];}}

  display = createImage(mic);
}

1.4 Output to screen

To get our new picture on screen as quick as possible, we overwrite the paint(Graphics) and update(Graphics) methods. 
paint(Graphics) is obvious (because we need to have our own customized output), but the update(Graphics) method is important, because normally Java would first clear the screen and then repaint it which is very slow and will result in heavy flicker. Besides we can overwrite the whole picture thus no deleting/clearing is necessary

public synchronized void paint(Graphics g)
{
  g.drawImage(display,0,0,this);

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

1.5 The Rest

The remaining methods take care of the mouse action :

public boolean mouseEnter(Event e, int x, int y){
  showStatus("Erster Test von Boris");
  return true;
}

public boolean mouseExit(Event e, int x, int y){
  showStatus(" ");
  return true;
}
public boolean mouseDown(Event e,int x, int y){
  if (!quit){
    quit=true;
    showStatus("Halt");
  }
  return true;
}

1.6 Epilogue und Preview

That's all ! Our funky Zoomer is done. The zooming algorithm isn't very good (quick and dirty) as the output method MemoryImageSource is. In the flowchart you can see how many steps you need to produce one picture and that many steps are very time and performance consuming which results in the low speed. But for simple effects (like this one) this method is OK.
In the next tutorial I will use another method (for output) and I will show how to use the good old 8 bit pallet.
As usual I'm thankful for any kind of suggestions, ideas, comments and code improvements because my Java skills are far from 
perfect.

©©©© 18.Juli.00 Tobias von Loesch
©©©© 08.Aug.00 Oldskool