/****************************************************************************************/
/**  J.Anders     *   Germany                 *          TU-Chemnitz-Zwickau            */
/****************************************************************************************/
/** Fakultaet fuer Informatik                 *       Professur "Rechnernetze und       */
/**                                           *          verteilte Systeme"             */
/****************************************************************************************/
/**   Telefon: +49 0371 531 1360              *         Fax: +49 371 531 1628           */
/****************************************************************************************/
/*											*/
/* This file contains the whole source code for	a FLI/FLC file player in JAVA.		*/
/* Parameters:										*/
/*			FILENAME: URL of the FLI/FLC file (relative or absolut)		*/
/*			DELAY (optional): delay (in ms) between 2 frames		*/
/*					  during animation (default: 200)		*/
/*											*/
/* The FLI/FLC player works in 3 steps:							*/
/*			1. Loading and parsing of the FLI/FLC file;			*/
/*			2. Reconstruction of the single frames;				*/
/*			3. Playing of the animation in a thread;			*/
/*											*/
/****************************************************************************************/

/*+++++++++++++++++++++++++++ library modules ++++++++++++++++++++++++++++++++++++++++++*/

import java.io.*;			
import java.net.*;
import java.applet.Applet;
import java.awt.*;
import java.awt.image.IndexColorModel;
import java.awt.image.ColorModel;
import java.awt.image.ImageProducer;
import java.awt.image.ImageConsumer;

/*++++++++++++++++++++++++++ auxiliary classes containing data ++++++++++++++++++++++++++*/

class Err {	// contains a possible error message;
	public static String Msg = null;
}

class Pixel_List { 			      // to build a list of frame data
	public Pixel_List next = null;        // (one element per frame)
	public byte color_values[];
	public IndexColorModel curr_Model;    // the current color model

	Pixel_List (Pixel_List predecessor, IndexColorModel m, int width, int height) {
		color_values = new byte[width * height];
		if (m != null) { 	// new color model ?
			curr_Model = m;	// adopt
		}
		if (predecessor != null) {

// If a predecessor frame is available its data are copied. That's because in general the
// FLI/FLC file contains a sequence of differences in respect of the predecessor frame.

			System.arraycopy(predecessor.color_values, 0, color_values, 0, color_values.length);

// If no new color model is specified the color model of the predecessor frame is used:

			if (m == null) curr_Model = predecessor.curr_Model;
			predecessor.next = this;
		}
	}
		
}

/*++++++++++++++++++++++++++++++++++++++ classes +++++++++++++++++++++++++++++++++++++++*/

/* The class "Flic_Player" implements the applet. It produces an object "scan" of the	*/
/* class "Scanner" and passes the FLC/FLI data to it. After that the variables:		*/
/*		scan.frame_counter							*/
/*		scan.width								*/
/*		scan.height								*/
/* are set according to the FLI/FLI file data and the variable "scan.anchor" points to	*/
/* a list of frame data.								*/

public class Flic_Player extends Applet implements Runnable {
	private int curr_nr = 0;	// current frame (during animation)
	private Scanner scan;		// THE scanner
   	private Thread AnimatorThread;  // the animation runs as thread
	
	boolean painted = false;	// "true", if the frame "curr_nr" is painted
	private int delay = 200;	// delay between 2 frames (in ms)
	private Image Images[];		// the sequence of images
	private boolean io = true;	// "false" in case of errors

	public void init () {		// overwrites the method "init()" of Applet
		String filename = null;	// THE Name of the FLI/FLC file
		String s_delay = null;	// delay (as string)
		URL source_url;		// THE source (as URL)
		Pixel_List pl;	        // auxiliary variable to trace the list of images
		DataInputStream in_stream = null; // THE input stream

		try {			// obtain parameters
			filename = getParameter("FILENAME");
			s_delay = getParameter("DELAY");
			if (s_delay != null) {
            			try {
                			delay = Integer.parseInt(s_delay);
            			} catch (NumberFormatException e) { 
					Err.Msg = s_delay + " is not a number";
					io = false;
				}
			}
		}
		catch (NullPointerException e) {
			Err.Msg = "no parameter \"FILENAME\"";
			io = false;
                }
		if (filename == null) {
			Err.Msg = "no parameter \"FILENAME\"";
			io = false;
		}
		if (io) {
			try {	// construct URL and open the input stream:
				source_url = new URL(getCodeBase(), filename);
				in_stream = new DataInputStream(source_url.openStream());
			}
			catch (MalformedURLException e) {
				Err.Msg = "MalformedURLException";
				io = false;
			}
			catch (IOException e) {
				Err.Msg = "Cannot open: " + getCodeBase() + "/" + filename;
				io = false;
			}
		}
		if (io) {
			scan = new Scanner(in_stream); // produce the scanner and pass
			io = scan.scan_flic();		  // the input stream to it
			if (io)  {
				resize(scan.width, scan.height); // but now possible because of
				Images = new Image[scan.frame_counter]; // "frame_counter"
				pl = scan.anchor; 		// trace the list
				for (int i = 0; i < scan.frame_counter && io; i++) {
					if (pl == null) { // bad "frame_counter" ?
						Err.Msg = "error in tracing the list";
						io = false;
					}
					/*+++ produce one image per frame +++*/
					Images[i] = createImage( new myProducer(pl.color_values,
					 	scan.width, scan.height, pl.curr_Model));
					pl = pl.next;		// continue
				}
			}
		}
	}

	/*+++ The animation must be performed as thread because the   +++*/
	/*+++ web browser expects that the "start()" method returns   +++*/
	/*+++ after finite time. An infinite animation loop would     +++*/
	/*+++ violate against this principle.			      +++*/

	public void run() { 
   		long starttime = System.currentTimeMillis();  // notice the start time
   		while (Thread.currentThread() == AnimatorThread) { // Is it me ?
        		repaint(); // sets the flag for "update()" --> "paint()"
			painted = false;
        		try {  // "sleep()" throws the "InterruptedException"
           			starttime += delay; // compute the destination time
           			// if destination time isn't reached --> sleep
           			Thread.sleep(Math.max(0,starttime - System.currentTimeMillis()));
        		}
        		catch (InterruptedException e) { // must be; otherwise it leads to an error
           			break;   // because "sleep()" throws the "InterruptedException"
			}

			/* "curr_nr" is only incremented if the Web browser    */
			/* had the opportunity to paint the current image.     */
			/* ("repaint()" sets only a flag: "Please repaint      */
			/* occasionally!")				       */

			if (painted) curr_nr++;
		}
	}

	/*+++ It is guaranteed that "start()" is called after "init()".    +++*/
	/*+++ That means the image sequence list (anchor) already exists.  +++*/
	/*+++ The "start()" method leaves behind only a runnable thread	   +++*/
	/*+++ and returns.						   +++*/

	public void start() { // overwrites the "start()" method of the Applet
		if (!io) return;
        	if (AnimatorThread == null) { // make sure there is no animation
           		AnimatorThread = new Thread(this); // produce the thread
           		AnimatorThread.start();      // pass the thread to the scheduler
        	}
	}

	/*+++ The "update()" method is called at the first opportunity	    +++*/
	/*+++ after "repaint()" has set the "Please repaint!" - Flag.	    +++*/

   	public void update(Graphics g) {  // overwrites the "update()" - method of Applet
		// Put frame (curr_nr % scan.frame_counter) to foreground
        	g.drawImage(Images[curr_nr % scan.frame_counter], 0, 0, this);
		painted = true; // "is painted" --> see "run()"
	}

	// "stop()" overwrites the "stop()" method of the Applet

	public void stop() { // is called if the page is unloaded
		AnimatorThread = null; // IMPORTANT: otherwise the animation continues in background !!!
	}

	/*+++ "paint()" is actually called after "update()" has cleared the 	 +++*/
	/*+++ screen. Because "update()" is overwritten "paint()" is only called +++*/
	/*+++ at the beginning and in case of exposure events. At this 		 +++*/
	/*+++ opportunity a possible error message is displayed.		 +++*/

	public void paint(Graphics g) { // overwrites the "paint()" method of the Applet
		if (Err.Msg != null) {
			g.setFont(new Font(g.getFont().getName(), Font.BOLD, 10));
			g.drawString(Err.Msg, 10, 100);
			return;
		}
		g.drawImage(Images[curr_nr % scan.frame_counter], 0, 0, this);
	}
}

/*+++ The class "Scanner" provides the methods suitable to parse the FLI/FLC	+++*/
/*+++ coded data. As a result of calling "scan_flic" the following (public)	+++*/
/*+++ variables are set accordingly to the values in FLI/FLC file:		+++*/
/*+++										+++*/
/*+++			frame_counter    number of frames			+++*/
/*+++			width		 width of one frame			+++*/
/*+++			height		 height of one frame			+++*/
/*+++										+++*/
/*+++ Furthermore the variable "anchor" points to the beginning of a list of	+++*/
/*+++ image data. Each image is stored as a sequence of pixel indexes in	+++*/
/*+++ left-right/top-down manner. The indexes refer to an index color model 	+++*/
/*+++ which is stored in this list, too.					+++*/

class Scanner {

	/*+++ FLI/FLC constants:		+++*/

	final private int FLI_MAGIC = 0xAF11;
	final private int HEADER_LENGTH = 26 + 102;
	final private int FRAME_HEADER_LENGTH = 16;
	final private short M_FLI = (short) 0xAF11;
	final private short M_FLC = (short) 0xAF12;
	final private short FLI_COLOR_256 = (short) 4;
	final private short FLI_COLOR = (short) 11;
	final private short FLI_LC    = (short) 12;
	final private short FLI_WORD_LC = (short) 7;
	final private short FLI_BLACK = (short) 13;
	final private short FLI_BRUN  = (short) 15;
	final private short FLI_COPY  = (short) 16;
	final private short FRAME_ID  = (short) 0xf1fa;

	/*+++ public variables (will be set reasonably): +++*/

	public Pixel_List anchor = null;
	public int width, height, frame_counter = 0;

	/*+++ private variables: +++*/

	private boolean io = true;		// "false" if syntax error
	private byte buffer[];			// portion of source data
	private DataInputStream In_str;		// THE input stream
	private Pixel_List last = null;		// end of the list of image data
	private IndexColorModel myModel;	// "null" if no new color model specified 
	private	byte r[] = new byte[256];	// sequence of red values in index color model
	private	byte g[] = new byte[256];	// sequence of green values in index color model
	private	byte b[] = new byte[256];	// sequence of blue values in index color model

	Scanner(DataInputStream f_In_str) {
		In_str = f_In_str;		// notice the input stream
	}

	/* The method "scan_fli()" reads the content of the FLI/FLC-header.   */
	/* It sets the variables "width" and "height".			      */
	/* The return value is true if no parsing error occurs.		      */

	public boolean scan_flic() {
		int length;	// length of file
		short magic, size, speed;

		// read the whole header (fix length) to the"buffer":

		buffer = new byte[HEADER_LENGTH];
		try {
			ReadContent(In_str, buffer, HEADER_LENGTH);
		}
		catch (IOException e) {
			Err.Msg = "read error 1";
			return false;
		}

		// extract content:

		length 	=         to_int(buffer, 4,  0);
		magic   = (short) to_int(buffer, 2,  4);
		size    = (short) to_int(buffer, 2,  6);
		width   = (short) to_int(buffer, 2,  8);
		height  = (short) to_int(buffer, 2, 10);
		speed   = (short) to_int(buffer, 2, 16);
		switch (magic) { // OK ?
			case M_FLI: break;
			case M_FLC: break;
			default: Err.Msg = "Number " + magic + "unknown";
				 return false;
		}
		length -= HEADER_LENGTH; // remaining length

		// The frame count isn't specified in the header. That's why
		// the end of file must be computed by means of file length.
		// Such the frame count is computed implicitely.

		while (length > 0 && io) {
			length -= scan_frame();

			// The "frames_counter" cannot be incremented here
			// because a frame not necessarily contains a an image
			// but only a color model.
		}
		frame_counter--; // erase the loop frame
		return io;
	}

	/* The method "scan_frame()" reads data of one frame. A frame 	    */
	/* contains a number of chunks. Such a chunk contains either	    */
	/* pixel data (FLI_BRUN), pixel difference data (FLI_LC in FLI or   */
	/* FLI_WORD_LC in FLC) or a color map (FLI_COLOR in FLI or	    */
	/* FLI_COLOR_256 in FLC). FLI_COPY, FLI_BLACK and FLI_PREVIEW data  */
	/* are ignored and I hope they will never occur.		    */
	/* The return value is the amount of data in bytes. The method 	    */
	/* changes possibly the value of "io".				    */

	private int scan_frame() {
		int frame_size;
		short fh_id, chunks;

		// read the whole frame header (fix length) to the"buffer":

		buffer = new byte[FRAME_HEADER_LENGTH];
		try {
			ReadContent(In_str, buffer, FRAME_HEADER_LENGTH);
		}
		catch (IOException e) {
			Err.Msg = "read error 2";
			io = false; return 0;
		}

		// extract data:

		frame_size =      to_int(buffer, 4,  0);
		fh_id   = (short) to_int(buffer, 2,  4); // tag
		chunks  = (short) to_int(buffer, 2,  6); // count of chunks

		// First the color model is set to "null". If an FLI_COLOR(_256)
		// appears "myModel" is changed accordingly.

		myModel = null;

		if (fh_id != FRAME_ID) { // check !
			Err.Msg = "FLI/FLC synchronization error";
			io = false; return 0;
		} 
		for (int ch = 0; ch < chunks; ch++) { // read the chunk data
			scan_chunk();
		}
		return frame_size;
	}

	/* The method "scan_chunk()" reads one chunk. Depending on the chunk  */
	/* type further methods are called.				      */

	private void scan_chunk() {
		int ch_size;
		short ch_type;

		// read the chunk header (fix length) to the "buffer":

		buffer = new byte[6];
		try {
			ReadContent(In_str, buffer, 6);
		}
		catch (IOException e) {
			Err.Msg = "read error 3";
			io = false; return;
		}

		// extract data:

		ch_size = 	  to_int(buffer, 4, 0);
		ch_type = (short) to_int(buffer, 2, 4);

		// read the remaining data to the "buffer":

		buffer = new byte[ch_size - 6];
		try {	
			ReadContent(In_str, buffer, ch_size - 6);
		}
		catch (IOException e) {
			Err.Msg = "read error 4";
			io = false; return;
		}
		switch (ch_type) { // dectect chunk type
			case FLI_COLOR: scan_FLI_COLOR(2);break;
			case FLI_COLOR_256: scan_FLI_COLOR(0);break;
			case FLI_LC: scan_FLI_LC();  break;
			case FLI_WORD_LC: scan_FLI_WORD_LC();  break;
			case FLI_BRUN: scan_FLI_BRUN(); break;
			case FLI_BLACK:break;
			case FLI_COPY: break;
			default: Err.Msg = "chunk type " + ch_type + " unknown";
				io = false; return;
		}
	}

	/* The method "scan_FLI_BRUN()" reads FLI_BRUN data. These data des-	*/
	/* cribe an image pixel by pixel. The data are possibly subdivided into */
	/* packets. In the case of a sequence of equal pixel values only the	*/
	/* pixels value and the length of the sequence is given.		*/
	/* It is assumed that the FLI_BRUN data are given first. Otherwise the	*/
	/* player will fail.							*/

	private void scan_FLI_BRUN() {
		int idx = 0;	// offset in data buffer
		int ppx;	// offset in frame
		int pkts;	// count of packets in a line
		int xchpx;	// current pixel value
		int pixel;	// pixel counter
		int line;	// current line
		byte f;		// to remember a pixel value

		short line_count = (short) height;

		if (frame_counter > 0) { // see above !
			Err.Msg = "FLI_BRUN although frame_counter > 0"; io = false; return;
		}

		if (myModel == null) { // no color model ???
			Err.Msg = "No color model for the first frame ?"; io = false; return;
		}

		// produce the first element in the list of frames:

		last = anchor = new Pixel_List(null, myModel, width, height);
		frame_counter++;
		idx = 0;
			
		for (line = 0; line < line_count; line++) {
			pkts = (0xff & buffer[idx++]);
			ppx = line * width;
			for (int pkt_nr = 0; pkt_nr < pkts; pkt_nr++) {
				xchpx  = (int) buffer[idx++];
				if (xchpx >= 0) { // set the following byte "xchpx" times
					f = (byte) (0xff & buffer[idx++]);
					for (int k = 0; k < xchpx; k++) {
						anchor.color_values[ppx++] = f; 
					}
				}
				else { // the following "xchpx" bytes are pixel values
					xchpx = -xchpx;
					for (pixel = 0; pixel < xchpx; pixel++) {
						anchor.color_values[ppx++ ] =
							(byte)(0xff & buffer[idx++]);
					}
				}
			}
		}
	}

	/* The method "scan_FLI_LC()" recognizes FLI_LC data which occur in FLI */
	/* files. They describe the differences in respect of the predecessor	*/
	/* frame. After a possible skip of (unchanged) data at the beginning	*/
	/* follow the data of "line_count" lines. The data of every line are	*/
	/* possibly subdivided into packets with (possibly) some unchanged	*/
	/* pixels between them.							*/
	/* It is assumed that FLI_LC data never occur first. Otherwise the 	*/
	/* player will fail.							*/

	private void scan_FLI_LC() {
		int idx = 0;	// offset in buffer
		int ppx;	// offset in frame
		int pkts;	// count of data packets in a line
		int xchpx;	// current value
		int pixel;	// pixel counter
		int line;	// current line
		short line_count; // count of lines to change (after a possbile skipping)
		byte f;		// ro remember a pixel value

		idx = 0;
		if (frame_counter == 0) { // see above !
			Err.Msg = "FLI_LC although frame_counter == 0"; io = false; return;
		}

		// produce the next element in the list of frames:
		// (The constructor copies the data of the predecessor
		//  frame "last")

		last.next = new Pixel_List(last, myModel, width, height);
		frame_counter++;
		
		last = last.next; // update

		line      =          to_int(buffer, 2, idx); idx += 2;
		line_count = (short) to_int(buffer, 2, idx); idx += 2;
			
		for (; line < line_count; line++) {
			pkts = (0xff & buffer[idx++]);
			ppx = line * width;
			for (int pkt_nr = 0; pkt_nr < pkts; pkt_nr++) {
				ppx += (0xff & buffer[idx++]); // skip pixels
				xchpx = (int) buffer[idx++];
				if (xchpx < 0) { // set the following value "xchpx" times
					xchpx = -xchpx;
					f = (byte) (0xff & buffer[idx++]);
					for (int k = 0; k < xchpx; k++) {
						last.color_values[ppx++] = f;
					}
				}
				else {	// the following "xchpx" bytes are pixel values
					for (pixel = 0; pixel < xchpx; pixel++) {
						last.color_values[ppx++ ] =
							(byte)(0xff & buffer[idx++]);
					}
				}
			}
		}
	}
	
	/* The method "scan_FLI_WORD_LC()" recognizes FLI_WORD_LC data which	*/
	/* occur in FLC files. These are pixel difference values in respect of	*/
	/* the predecessor frame. The pixel values of "line_count" consecutive  */
	/* lines are specified followed by a (possible) skip of (unchanged) 	*/
	/* lines. The pixel values are given in double bytes. In the case of an	*/
	/* odd number of pixels per line the remaining pixel is given in a	*/
	/* special manner.							*/
	/* It is assumed that FLI_WORD_LC data never occur first. Otherwise the	*/
	/* player will fail.							*/

	private void scan_FLI_WORD_LC() {
		int idx = 0;	// offset in buffer
		int ppx;	// offset in frame
		int xchpx;	// current value
		int pixel;	// pixel counter
		int line;	// line counter
		int yoff;	// the real current line
		short line_count; // count of lines to change
		short pkts;	// count of packets per line
		byte f1, f2; 	// ro remember 2 pixel values
		boolean last_pix_flag = false; // is a last single pixel stored ?
		byte last_pixel = 0; // value of a last single pixel

		idx = 0;
		if (frame_counter == 0) { // see above!
			Err.Msg = "FLI_LC_WORD although frame_counter == 0"; io = false; return;
		}

		// produce the next element in the list of frames:
		// (The constructor copies the data of the predecessor
		//  frame "last")

		last.next = new Pixel_List(last, myModel, width, height);
		frame_counter++;
	
		last = last.next;	// update
		line_count = (short) to_int(buffer, 2, idx); idx += 2;
		yoff = 0;
		for (line = 0; line < line_count; line++) {
			pkts   =  (short) to_int(buffer, 2, idx); idx += 2;
			while ((pkts & 0x8000) != 0) {
				if ((pkts & 0x4000) != 0) { // no packet counter but ...
					yoff -=(int) pkts; // ...count of lines to skip
				}
				else {			   // ...value of a last single pixel
					last_pix_flag = true; // notice!
					last_pixel = (byte) (pkts & 0xff);
				}
				// the packet count follows
				pkts   =  (short) to_int(buffer, 2, idx); idx += 2;
			}

			ppx = yoff * width;
			for (int pkt_nr = 0; pkt_nr < pkts; pkt_nr++) {
				ppx +=  (0xff & buffer[idx++]); // skip pixels
				xchpx = (int) buffer[idx++];
				if (xchpx < 0) { // set the following 2 bytes "xchpx" times
					xchpx = -xchpx;
					f1 = (byte) (0xff & buffer[idx++]);
					f2 = (byte) (0xff & buffer[idx++]);
					for (int k = 0; k < xchpx; k++) {
						last.color_values[ppx++] = f1;
						last.color_values[ppx++] = f2;
					}
				}
				else {	// "xchpx" double bytes follow
					for (pixel = 0; pixel < xchpx; pixel++) {
						last.color_values[ppx++ ] =
							(byte)(0xff & buffer[idx++]);
						last.color_values[ppx++ ] =
							(byte)(0xff & buffer[idx++]);
					}
				}
			}
			if (last_pix_flag) { // is there a value of a last single pixel ?
				last.color_values[(yoff + 1) * width - 1] = last_pixel;
				last_pix_flag = false;
			}
			yoff++; // count the lines
		}
	}

	/* The method "scan_FLI_COLOR(int shift)" recognizes FLI_COLOR data as  */
	/* well as FLI_COLOR_256 data. The latter occur in FLC files, the other */
	/* in FLI files. The data describe a color map with 256 color values	*/
	/* maximal. The difference is: The RGB values in FLI_COLOR_256 are 8 	*/
	/* bit values; the RGB values in FLI_COLOR are 6 bit values. That means */
	/* the FLI_COLOR data must be "shift"-ed 2 bytes to the left. The	*/
	/* FLI_COLOR(_256) data are (eventually) subdivided into packets and are*/
	/* (possibly) differences in respect of the predecessor color map. 	*/
	/* Therefore a skipping is possible. Since the arrays "r", "g" and "b"  */
	/* are private global variables it is guaranteed that the new values	*/
	/* are always built in recpect to the existing one.			*/
	/* The method produces a new color model "myModel".			*/

	private void scan_FLI_COLOR(int shift) {
		short pkts;		// count of packets
		int skip;		// count of values to skip
		int count;		// count of values in a packet
		int idx = 0;		// offset in buffer
		int table_idx = 0;	// index in color map

		pkts = (short) to_int(buffer, 2, 0); idx += 2;
		for (int pkt_nr = 0; pkt_nr < pkts; pkt_nr++) {
			skip = 0xff & (buffer[idx++]);
			count= 0xff & (buffer[idx++]);
			table_idx += skip;
			if (count == 0) count = 256;	// zero means 256 !!!
			for (int j = 0; j < count; j++) { // read RGB values
				r[table_idx  ] = (byte) ((0xff & buffer[idx++]) << shift);
				g[table_idx  ] = (byte) ((0xff & buffer[idx++]) << shift);
				b[table_idx++] = (byte) ((0xff & buffer[idx++]) << shift);
			}
		}

		// After reading the RGB values a new index color model is produced:

		myModel = new IndexColorModel(8, table_idx, r, g, b);
	}

	/* The method "ReadContent (DataInputStream Stream, byte field[], int size)"  */
	/* reads from "Stream" into the "field" until "size" bytes are read or a read */
	/* error occurs.							      */

	private void ReadContent (DataInputStream Stream, byte field[], int size)
		throws IOException {
		int bytes_read = 0;
		while (bytes_read < size) {
			bytes_read += Stream.read(field, bytes_read, size - bytes_read);
		}
	}

	/* Because JAVA has no pointers and no unsigned types a special method	    */
	/*   		"int to_int(byte b[], int length, int off)"		    */
	/* is necessary which reads "length" Bytes at position "off" from "b" and   */
	/* translates it to a "length" byte integer value.			    */

	private int to_int(byte b[], int length, int off) {
		int r_val = 0;
		for (int i = 0; i < length; i++) {
			r_val <<= 8;
			r_val |= b[length - 1 - i + off] & 0xff;
		}
		return r_val;
	}
}

/*+++ JAVA offers 2 possibilities to create an image:				+++*/
/*+++										+++*/
/*+++			1. createImage(int width, int height);			+++*/
/*+++			2. createImage(ImageProducer producer);			+++*/		
/*+++										+++*/
/*+++ The first method isn't suitable here because it requires to draw the	+++*/
/*+++ pixel values by means of a sequence of "draw...()" methods into the image.+++*/
/*+++ Since all the pixel values and the index color model are known the images +++*/
/*+++ are produced according to the second method by means of objects of the	+++*/
/*+++ class "myProducer" which implements the interface "ImageProducer".	+++*/
/*+++ An image producer is an object which supplies the pixel values on demand  +++*/
/*+++ of an image consumer. The pixel values are delivered in recpect of a 	+++*/
/*+++ certain color model which must be known to the image consumer, too.	+++*/
/*+++ Therefore the image producer must also supply the color model on demand.  +++*/

class myProducer implements ImageProducer {
	private byte Pix_Data[];	// THE pixel values
	private int width, height;	// THE dimensionens
	ColorModel  Model;		// THE color model

	// The constructor only assures that the image producer
	// notices the pixel values, the dimension and the
	// color model.

	myProducer(byte pixels[], int w, int h, ColorModel cm) {
		Pix_Data = pixels; // Notice that this is a kind of 
				   // "pointer assignement"! Is is
				   // possible because the constructor of
				   // "Pixel_List" copies the pixel values.
		width = w; height = h; Model = cm;
	}

	// Before the first use the image consumer registers itself
	// to the image producer.
	// At this opportunity he is informed of the dimension and
	// the color model.

	public void addConsumer(ImageConsumer  ic) {
		ic.setDimensions(width, height);
		ic.setColorModel(Model);
	}

	public boolean isConsumer(ImageConsumer  ic) {
		return true; // dummy
	}

	public void removeConsumer(ImageConsumer  ic) {
		// dummy
	}

	// The following 2 methods are almost identically.
	// They are called by the image consumer to get the
	// pixel data:

	public void requestTopDownLeftRightResend(ImageConsumer  ic) {

		// Tell the image consumer that the pixel data
		// will be supplied from left to right und from
		// top to bottom:

		ic.setHints(ImageConsumer.TOPDOWNLEFTRIGHT);

		// Send all the pixel data:

		ic.setPixels(0, 0, width, height, Model, Pix_Data, 0, width);

		// Tell the image consumer that no further data follow:

		ic.imageComplete(ImageConsumer.STATICIMAGEDONE);
	}

	// The method "startProduction(ImageConsumer ic)" is called by
	// the image consumer if it produces the image.
	// For security it is informed of the color model
	// and the dimensions (I don't know if this is really nenecessary).

	public void startProduction(ImageConsumer ic)  {
		ic.setDimensions(width, height);
		ic.setColorModel(Model);
		requestTopDownLeftRightResend(ic);
	}
}
