/*
 * Copyright (c) 1996 by Jan Andersson, Torpa Konsult AB.
 *
 * Permission to use, copy, and distribute this software for
 * NON-COMMERCIAL purposes and without fee is hereby granted
 * provided that this copyright notice appears in all copies.
 *
 */

import java.awt.Color;
import java.awt.image.ImageConsumer;
import java.awt.image.ImageProducer;
import java.awt.image.ColorModel;
import java.awt.image.IndexColorModel;
import java.util.Hashtable;
import java.io.InputStream;
import java.io.StringBufferInputStream;

/**
 * This class is an implementation of the ImageProducer interface that
 * uses a X11 bitmap (xbm) array, X11 pixmap (xpm) array or an input
 * stream to a xbm/xpm file to produce pixel values for an Image.
 *
 * Here is a few examples: which creates an image from a xbm-file: "foo.xbm".
 * The forground is set to blue and the Component background is used.
 * <pre>
 *     // create image from inline xbm data using transparent color. 
 *     // I.e no background specied.
 *     static private int[] arrow = {
 *        0x3f, 0x0f, 0x0f, 0x1f, 0x39, 0x71, 0xe0, 0xc0
 *     };
 *     Image image = createImage(
 *          new XImageSource(8, 8, arrow, Color.black, nil, true));
 *
 *     // create image from xpm-file "foo.xpm"
 *     FileInputStream is = new FileInputStream("foo.xpm");
 *     Image image = createImage(new XImageSource(is));
 * 
 * </pre>
 *
 * @see ImageProducer
 *
 * @version	1.1 96/02/20
 * @author 	Jan Andersson, Torpa Konsult AB. (janne@torpa.se)
 */
public class XImageSource implements ImageProducer {
   int width;
   int height;
   byte pixels[];
   
   ColorModel colorModel;
   private boolean error = false;
   private ImageConsumer theConsumer;
   
   /**
    * Constructs an ImageProducer object from an X11 style bitmap (xbm)
    * to produce data for an Image object.
    * @param w width
    * @param h height
    * @param bits bitmap in xbm format
    * @param fg foreground color
    * @param bg background color (if trans is false)
    * @param trans transparent if true
    */
   public XImageSource(int w, int h, int[] bits,
			 Color fg, Color bg, boolean trans) {
      xbmInitialize(w, h, bits, fg, bg, trans);
   }

   /**
    * Constructs an ImageProducer object from an X11 style bitmap (xbm)
    * input stream to produce data for an Image object.
    * @param is input stream to xbm data
    * @param fg foreground color
    * @param bg background color (if trans is false)
    * @param trans transparent if true
    */
   public XImageSource(InputStream is,
		       Color fg, Color bg, boolean trans) {
      // Use XbmParser to parse input stream
      XbmParser parser = new XbmParser(is);
      if (parser.parse()) 
	 xbmInitialize(parser.getWidth(), parser.getHeight(),
		    parser.getBitmap(), fg, bg, trans);
      else
	 error = true;
   }

   /**
    * Constructs an ImageProducer object from an X11 style pixmap (xpm)
    * consisting of an array of strings.
    * @param data string array in xpm format.
    */
   public XImageSource(String lines[], int nblines) {

      // create string buffer from data array
      StringBuffer strBuf = new StringBuffer(1024);
      for (int i=0; i<nblines; i++){
		strBuf.append(lines[i] + "\n");
	  }
      
      // create an input stream from the string buffer
      StringBufferInputStream is =
	 new StringBufferInputStream(strBuf.toString());

      // Use XpmParser to parse input stream
      XpmParser parser = new XpmParser(is);
      if (parser.parse()) 
	 xpmInitialize(parser.getWidth(), parser.getHeight(),
		       parser.getPixmap(), parser.getColorTable());
      else
	 error = true;
   }
   /**
    * Constructs an ImageProducer object from an X11 style pixmap (xpm)
    * input stream to produce data for an Image object.
    * @param is input stream to xpm data
    */
   public XImageSource(InputStream is) {
      // Use XpmParser to parse input stream
      XpmParser parser = new XpmParser(is);
      if (parser.parse()) 
	 xpmInitialize(parser.getWidth(), parser.getHeight(),
		       parser.getPixmap(), parser.getColorTable());
      else
	 error = true;
   }
   
   /**
    * Initialize pixels from the xbm bitmap, using the provided colors
    * as foreground/background
    */
   private void xbmInitialize(int w, int h, int[] bits,
			      Color fg, Color bg,
			      boolean trans) {
      width = w;
      height = h;

      // create color map
      byte colormap[] = new byte[6];
      int transIndex = -1;
      // color component 0 is for background
      if (trans) {
	 // color component 0: transparent
	 transIndex = 0;
	 colormap[0] = 0;
	 colormap[1] = 0;
	 colormap[2] = 0;
      }
      else {
	 // color component 0: background
	 colormap[0] = (byte) bg.getRed();
	 colormap[1] = (byte) bg.getGreen();
	 colormap[2] = (byte) bg.getBlue();
      }
      // color component 1: foreground
      colormap[3] = (byte) fg.getRed();
      colormap[4] = (byte) fg.getGreen();
      colormap[5] = (byte) fg.getBlue();

      // create color model
      colorModel = new IndexColorModel(8, 2, colormap, 0, false, transIndex);
       
      // allocate pixels
      pixels = new byte[width*height];
      
      // loop for xbm bitmap and create pixels
      int index = 0;
      int byteIndex = 0;
      int jMax = width/8;
      try {
	 for (int i=0; i<height; i++) {
	    for (int j=0; j<jMax; j++) {
	       byte bitByte = (byte) bits[byteIndex];
	       for (int k=0; k<8; k++) {
		  int pixel = bitByte & (1 << k);
		  pixels[index] = (pixel != 0) ? (byte) 1 : (byte) 0;
		  index++;
	       }
	       byteIndex++;
	    }
	 }
      }
      catch (Exception e) {
	 error = true;		// error
      }
   }

   /**
    * Initialize pixels from the xpm pixmap.
    */
   private void xpmInitialize(int w, int h, byte[] pixels,
			      Color[] colorTable) {
      width = w;
      height = h;
      this.pixels = pixels;

      // create color map
      int nColors = colorTable.length;
      int cmSize = 3 * nColors;
      byte colormap[] = new byte[cmSize];

      // init color map
      int transIndex = -1;
      for (int i=0; i<nColors; i++) {
	 if (colorTable[i] == null) {
	    // transparent color
	    transIndex = i;
	 }
	 else {
	    colormap[3*i] = (byte) colorTable[i].getRed();
	    colormap[3*i+1] = (byte) colorTable[i].getGreen();
	    colormap[3*i+2] = (byte) colorTable[i].getBlue();
	 }
      }
      
      // create color model
      colorModel = new IndexColorModel(8, nColors, colormap,
				       0, false, transIndex);
   }

   /**
    * Adds an ImageConsumer to the list of consumers interested in
    * data for this image.
    * @see ImageConsumer
    */
   public synchronized void addConsumer(ImageConsumer ic) {
      theConsumer = ic;
      produce();
      theConsumer = null;
   }
   
   /**
    * Determine if an ImageConsumer is on the list of consumers currently
    * interested in data for this image.
    * @return true if the ImageConsumer is on the list; false otherwise
    * @see ImageConsumer
    */
   public synchronized boolean isConsumer(ImageConsumer ic) {
      return (ic == theConsumer);
   }

   /**
    * Remove an ImageConsumer from the list of consumers interested in
    * data for this image.
    * @see ImageConsumer
    */
   public synchronized void removeConsumer(ImageConsumer ic) {
      if (theConsumer == ic) {
	 theConsumer = null;
      }
   }
   
   /**
    * Adds an ImageConsumer to the list of consumers interested in
    * data for this image, and immediately start delivery of the
    * image data through the ImageConsumer interface.
    * @see ImageConsumer
    */
   public void startProduction(ImageConsumer ic) {
      addConsumer(ic);
   }

   /**
    * Requests that a given ImageConsumer have the image data delivered
    * one more time in top-down, left-right order.
    * @see ImageConsumer
    */
   public void requestTopDownLeftRightResend(ImageConsumer ic) {
      // Not needed.  The data is always in TDLR format.
   }

   /*
    * Produce Image data.
    * I.e set the data for the Image, using the ImageConsumer interface.
    */
   private void produce() {
      if (theConsumer != null) {
	 theConsumer.setDimensions(width, height);
      }
      if (theConsumer != null) {
	 theConsumer.setProperties(new Hashtable());
      }
      if (theConsumer != null) {
	 theConsumer.setColorModel(colorModel);
      }
      if (theConsumer != null) {
	 theConsumer.setHints(ImageConsumer.TOPDOWNLEFTRIGHT |
			      ImageConsumer.COMPLETESCANLINES |
			      ImageConsumer.SINGLEPASS |
			      ImageConsumer.SINGLEFRAME);
      }
      if (theConsumer != null) {
	 theConsumer.setPixels(0, 0, width, height,
			       colorModel,
			       pixels, 0, width);
      }
      if (theConsumer != null) {
	 if (!error) {
	    theConsumer.imageComplete(ImageConsumer.STATICIMAGEDONE);
	 }
	 else {
	    theConsumer.imageComplete(ImageConsumer.IMAGEERROR);
	 }
      }
   }
}

