package ij.plugin;
import ij.*;
import ij.io.*;
import ij.gui.*;
import ij.process.*;
import ij.plugin.*;
import java.io.*;
import java.awt.*;
import java.awt.image.*;
import java.awt.Color;
import java.awt.Point;
import java.io.OutputStream;
import java.io.IOException;
import javax.imageio.ImageIO;


/**
 * This plugin encodes a GIF file consisting of one or more frames.
 *
 * <pre>
 * Extensively Modified for ImagePlus
 * Extended to handle 8 bit Images with more complex Color lookup tables with transparency index
 *
 * Ryan Raz March 2002
 * raz@rraz.ca
 * Version 1.01
 ** Extensively Modified for ImagePlus
 * Extended to handle 8 bit Images with more complex Color lookup tables with transparency index
 *
 * Ryan Raz March 2002
 * ryan@rraz.ca
 * Version 1.01 Please report any bugs
 *
 * Credits for the base conversion codes
 * No copyright asserted on the source code of this class.	May be used
 * for any purpose, however, refer to the Unisys LZW patent for restrictions
 * on use of the associated LZWEncoder class.  Please forward any corrections
 * to kweiner@fmsware.com.
 * </pre>
 *
 * @author Kevin Weiner, FM Software
 * @version 1.0 December 2000
 *
 */

/** Writes a stack as an animated Gif */
public class GifWriter implements PlugIn {
	static int transparentIndex = Prefs.getTransparentIndex();
   
	public void run(String path) {
		ImagePlus imp = IJ.getImage();
		if (path.equals("")) {
			SaveDialog sd = new SaveDialog("Save as Gif", imp.getTitle(), ".gif");
			if (sd.getFileName()==null) return;
			path = sd.getDirectory()+sd.getFileName();
		}

		ImageStack stack = imp.getStack();
		Overlay overlay = imp.getOverlay();
		int nSlices = stack.getSize();
		
		if (nSlices==1) { // save using ImageIO
			if (overlay!=null)
				imp = imp.flatten();
			try {
				writeImage(imp, path, transparentIndex);
			} catch (Exception e) {
				String msg = e.getMessage();
				if (msg==null || msg.equals(""))
					msg = ""+e;
				IJ.error("GifWriter", "An error occured writing the file.\n \n" + msg);
			}
			return;
		}

		GifEncoder ge = new GifEncoder();
		double fps = imp.getCalibration().fps;
		if (fps==0.0) fps = Animator.getFrameRate();
		if (fps<=0.2) fps = 0.2;
		if (fps>60.0) fps = 60.0;
		ge.setDelay((int)((1.0/fps)*1000.0));
		if (transparentIndex!=-1) {
			ge.transparent = true;
			ge.transIndex = transparentIndex;
		}
		ge.start(path);
		
		ImagePlus tmp = new ImagePlus();
		for (int i=1; i<=nSlices; i++) {
			IJ.showStatus("writing: "+i+"/"+nSlices);
			IJ.showProgress((double)i/nSlices);
			tmp.setProcessor(stack.getProcessor(i));
			if (overlay!=null) {
				Overlay overlay2 = overlay.duplicate();
				overlay2.crop(i, i);
				if (overlay2.size()>0) {
					tmp.setOverlay(overlay2);
					tmp = tmp.flatten();
					new ImageConverter(tmp).convertRGBtoIndexedColor(256);
				}
			}
			try {
				ge.addFrame(tmp);
			} catch(Exception e)  {
				IJ.showMessage("Save as Gif", ""+e);
				return;
			}

		}
		ge.finish();
		IJ.showStatus("");
		IJ.showProgress(1.0);
	}
	
	private void writeImage(ImagePlus imp, String path, int transparentIndex) throws Exception {
		if (transparentIndex>=0 && transparentIndex<=255)
			writeImageWithTransparency(imp, path, transparentIndex);
		else
			ImageIO.write(imp.getBufferedImage(), "gif", new File(path));
	}
	
	private void writeImageWithTransparency(ImagePlus imp, String path, int transparentIndex) throws Exception {
		int width = imp.getWidth();
		int	 height = imp.getHeight();
		ImageProcessor ip = imp.getProcessor();
		IndexColorModel cm = (IndexColorModel)ip.getColorModel();
		int size = cm.getMapSize();
		//IJ.log("write: "+size+" "+transparentIndex);
		byte[] reds = new byte[256];
		byte[] greens = new byte[256];
		byte[] blues = new byte[256];	
		cm.getReds(reds); 
		cm.getGreens(greens); 
		cm.getBlues(blues);
		cm = new IndexColorModel(8, size, reds, greens, blues, transparentIndex);
		WritableRaster wr = cm.createCompatibleWritableRaster(width, height);
		DataBufferByte db = (DataBufferByte)wr.getDataBuffer();
		byte[] biPixels = db.getData();
		System.arraycopy(ip.getPixels(), 0, biPixels, 0, biPixels.length);
		BufferedImage bi = new BufferedImage(cm, wr, false, null);
		ImageIO.write(bi, "gif", new File(path));
	}

}


class GifEncoder {
   int width;				  // image size
   int height;
   boolean transparent;	 // transparent color if given
   int transIndex;			  // transparent index in color table
   int repeat = 0;			 // repeat forever
   protected int delay = 50;			 // frame delay (hundredths)
   boolean started = false;	  // ready to output frames
   OutputStream out;
   ImagePlus image;		  // current frame
   byte[] pixels;			  // BGR byte array from frame
   byte[] indexedPixels;	  // converted frame indexed to palette
   int colorDepth;			  // number of bit planes
   byte[] colorTab;			  // RGB palette
   int lctSize = 7;			  // local color table size (bits-1)
   int dispose = 0;			 // disposal code (-1 = use default)
   boolean closeStream = false;	 // close stream when finished
   boolean firstFrame = true;
   boolean sizeSet = false;	  // if false, get size from first frame
   int sample = 2;			 // default sample interval for quantizer distance should be small for small icons
   byte[] gct = null;		//Global color table
   boolean GCTextracted = false; // Set if global color table extracted from rgb image 
   boolean GCTloadedExternal = false; // Set if global color table loaded directly from external image 
   int	  GCTred =	0;	 //Transparent Color
   int	GCTgrn = 0;	   // green
   int	 GCTbl	=  0;	// blue
   int	 GCTcindex = 0;	 //index into color table
   boolean GCTsetTransparent = false; //If true then Color table transparency index is set
   boolean GCToverideIndex = false; //If true Transparent index is set to index with closest colors
   boolean GCToverideColor = false; //if true Color at Transparent index is set to GCTred, GCTgrn GCTbl
   
   /**
	* Adds next GIF frame.	The frame is not written immediately, but is
	* actually deferred until the next frame is received so that timing
	* data can be inserted.	 Invoking <code>finish()</code> flushes all
	* frames.  If <code>setSize</code> was not invoked, the size of the
	* first image is used for all subsequent frames.
	*
	* @param im	 containing frame to write.
	* @return true if successful.
	*/
   public boolean addFrame(ImagePlus image) {
	  if ((image == null) || !started) return false;
	  boolean ok = true;
	  try {
		 if (firstFrame) {
			if (!sizeSet) // use first frame's size
			   setSize(image.getWidth(), image.getHeight());
			 writeLSD();
			if (repeat>=0) writeNetscapeExt();		// use NS app extension to indicate reps
			firstFrame = false;
		 }
		int bitDepth = image.getBitDepth();
		// If  indexed byte image then format does not need changing
		int k;
		Process8bitCLT(image);
		writeGraphicCtrlExt();		   // write graphic control extension
		writeImageDesc();			   // image descriptor
		writePalette();				   // local color table
		writePixels();				   // encode and write pixel data
	  } catch (IOException e) { ok = false; }
	  return ok;
  }
  
 /* 
*	Get Options because options box has been checked
	
	Some of the code being set
		setTransparent(Color.black);
		Dispose = 0;   
			setDelay(500);	 //	 time per frame in milliseconds
			gctused = false; // Set to true to use Global color table
		GCTextracted = false; // Set if global color table extracted from rgb image 
				GCTloadedExternal = false; // Set if global color table loaded directly from external image 
				GCTextracted = false; // Set if global color table extracted from rgb image 
		GCTred =  0;   //Transparent Color
		GCTgrn = 0;	   // green
		GCTbl  =  0;   // blue
		GCTcindex = 0;	//index into color table
		autotransparent = false; // Set True if transparency index coming from image 8 bit only
		GCTsetTransparent = true; //If true then Color table transparency index is set
		GCToverideIndex = false; //If true Transparent index is set to index with closest colors
		GCToverideColor = false; //if true Color at Transparent index is set to GCTred, GCTgrn GCTbl
   
*/

  
/********************************************************
*	 Gets Color lookup Table from 8 bit ImagePlus
*/
void Process8bitCLT(ImagePlus image){
	   colorDepth = 8;
		//setTransparent(false);
		ImageProcessor ip = image.getProcessor();
		ip = ip.convertToByte(true);	   
		ColorModel cm = ip.getColorModel();
		indexedPixels = (byte[])(ip.getPixels());
		IndexColorModel m = (IndexColorModel)cm;
		int mapSize = m.getMapSize();
		if (transIndex>=mapSize) {
			setTransparent(false);
			transIndex = 0;
		}
		int k;
		colorTab = new byte[mapSize*3];
		for (int i = 0; i < mapSize; i++) {
			k=i*3;
			colorTab[k] = (byte)m.getRed(i);
			colorTab[k+1] = (byte)m.getGreen(i);
			colorTab[k+2] = (byte)m.getBlue(i);
		}
		m.finalize();
  
 }	   

   /**
	* Flushes any pending data and closes output file.
	* If writing to an OutputStream, the stream is not
	* closed.
	*/
   public boolean finish() {
	  if (!started) return false;
	  boolean ok = true;
	  started = false;
	  try {
		 out.write(0x3b);  // gif trailer
		 out.flush();
		 if (closeStream)
			out.close();
	  } catch (IOException e) { ok = false; }

	  // reset for subsequent use
	  GCTextracted = false; // Set if global color table extracted from rgb image 
	  GCTloadedExternal = false; // Set if global color table loaded directly from external image 
	  transIndex = 0;
	  transparent = false;	  
	  gct = null;		//Global color table
	  out = null;
	  image = null;
	  pixels = null;
	  indexedPixels = null;
	  colorTab = null;
	  closeStream = false;
	  firstFrame = true;

	  return ok;
   }
   
   /**
	* Sets the delay time between each frame, or changes it
	* for subsequent frames (applies to last frame added).
	*
	* @param ms int delay time in milliseconds
	*/
   public void setDelay(int ms) {
	  delay = Math.round(ms / 10.0f);
   }


   /**
	* Sets the GIF frame disposal code for the last added frame
	* and any subsequent frames.  Default is 0 if no transparent
	* color has been set, otherwise 2.
	* @param code int disposal code.
	*/
   public void setDispose(int code) {
	  if (code >= 0)
		 dispose = code;
   }


   /**
	* Sets frame rate in frames per second.	 Equivalent to
	* <code>setDelay(1000/fps)</code>.
	*
	* @param fps float frame rate (frames per second)
	*/
   public void setFrameRate(float fps) {
	  if (fps != 0f) {
		 delay = Math.round(100f/fps);
	  }
   }


   /**
	* Sets quality of color quantization (conversion of images
	* to the maximum 256 colors allowed by the GIF specification).
	* Lower values (minimum = 1) produce better colors, but slow
	* processing significantly.	 10 is the default, and produces
	* good color mapping at reasonable speeds.	Values greater
	* than 20 do not yield significant improvements in speed.
	*
	* @param quality int greater than 0.
	* @return
	*/
   public void setQuality(int quality) {
	  if (quality < 1) quality = 1;
	  sample = quality;
   }
   
   /**
	* Sets the number of times the set of GIF frames
	* should be played.	 Default is 1; 0 means play
	* indefinitely.	 Must be invoked before the first
	* image is added.
	*
	* @param iter int number of iterations.
	* @return
	*/
   public void setRepeat(int iter) {
	  if (iter >= 0)
		 repeat = iter;
   }


   /**
	* Sets the GIF frame size.	The default size is the
	* size of the first frame added if this method is
	* not invoked.
	*
	* @param w int frame width.
	* @param h int frame width.
	*/
   public void setSize(int w, int h) {
	  if (started && !firstFrame) return;
	  width = w;
	  height = h;
	  if (width < 1) width = 320;
	  if (height < 1) height = 240;
	  sizeSet = true;
   }


   /**
	* Sets the transparent color for the last added frame
	* and any subsequent frames.
	* Since all colors are subject to modification
	* in the quantization process, the color in the final
	* palette for each frame closest to the given color
	* becomes the transparent color for that frame.
	* May be set to null to indicate no transparent color.
	*
	* @param c Color to be treated as transparent on display.
	*/
   public void setTransparent(boolean c) {
	  transparent = c;
   }


   /**
	* Initiates GIF file creation on the given stream.	The stream
	* is not closed automatically.
	*
	* @param os OutputStream on which GIF images are written.
	* @return false if initial write failed.
	*/
   public boolean start(OutputStream os) {
	  if (os == null) return false;
	  boolean ok = true;
	  closeStream = false;
	  out = os;
	  try {
		 writeString("GIF89a");		   // header
	  } catch (IOException e) { ok = false; }
	  return started = ok;
   }


   /**
	* Initiates writing of a GIF file with the specified name.
	*
	* @param file String containing output file name.
	* @return false if open or initial write failed.
	*/
   public boolean start(String file) {
	  boolean ok = true;
	  try {
		 out = new BufferedOutputStream(new FileOutputStream(file));
		 ok = start(out);
		 closeStream = true;
	  } catch (IOException e) { ok = false; }
	  return started = ok;
   }
/**
	Sets Net sample size depending on image size
	
**/
   public void OverRideQuality(int npixs){
		if(npixs>100000) sample = 10;
		else sample = npixs/10000;
		if(sample < 1) sample = 1;

	}
	
   /**
	* Writes Graphic Control Extension
	*/
   protected void writeGraphicCtrlExt() throws IOException {
	  out.write(0x21);		   // extension introducer
	  out.write(0xf9);		   // GCE label
	  out.write(4);			   // data block size
	  int transp, disp;
	  if (!transparent) {
		 transp = 0;
		 disp = 0;			   // dispose = no action
	  } else {
		 transp = 1;
		 disp = 2;			   // force clear if using transparent color  
	  }
	  if (dispose >= 0)
		 disp = dispose & 7;   // user override
	  disp <<= 2;

	  // packed fields
	  out.write(  0 |		   // 1:3 reserved
			   disp |		   // 4:6 disposal
			   0 |			   // 7	  user input - 0 = none
			   transp);		   // 8	  transparency flag

	  writeShort(delay);	   // delay x 1/100 sec
	  out.write(transIndex);   // transparent color index
	  out.write(0);			   // block terminator
  }


   /**
	* Writes Image Descriptor
	*/
   protected void writeImageDesc() throws IOException {
	  out.write(0x2c);		   // image separator
	  writeShort(0);		   // image position x,y = 0,0
	  writeShort(0);
	  writeShort(width);	   // image size
	  writeShort(height);
	  // packed fields
	  out.write(0x80 |		   // 1 local color table  1=yes
			0 |			   // 2 interlace - 0=no
			0 |			   // 3 sorted - 0=no
			0 |			   // 4-5 reserved
			lctSize);	// size of local color table
   }


   /**
	* Writes Logical Screen Descriptor with global color table
	*/
   protected void writeLSDgct() throws IOException {
	  // logical screen size
	  writeShort(width);
	  writeShort(height);
	  // packed fields
	  out.write((0x80 |		  // 1	 : global color table flag = 0 (nn
			   0x70 |		  // 2-4 : color resolution = 7
			   0x00 |		  // 5	 : gct sort flag = 0
			   lctSize));		 // 6-8 : gct size = 0

	  out.write(0);			  // background color index
	  out.write(0);			  // pixel aspect ratio - assume 1:1
   }
   
 /**
	* Writes Logical Screen Descriptor without global color table
	*/
   protected void writeLSD() throws IOException {
	  // logical screen size
	  writeShort(width);
	  writeShort(height);
	  // packed fields
	  out.write((0x00 |		  // 1	 : global color table flag = 0 (none)
			   0x70 |		  // 2-4 : color resolution = 7
			   0x00 |		  // 5	 : gct sort flag = 0
			   0x00));		  // 6-8 : gct size = 0

	  out.write(0);			  // background color index
	  out.write(0);			  // pixel aspect ratio - assume 1:1
   }


   /**
	* Writes Netscape application extension to define
	* repeat count.
	*/
   protected void writeNetscapeExt() throws IOException {
	  out.write(0x21);		 // extension introducer
	  out.write(0xff);		 // app extension label
	  out.write(11);		 // block size
	  writeString("NETSCAPE"+"2.0");	// app id + auth code
	  out.write(3);			 // sub-block size
	  out.write(1);			 // loop sub-block id
	  writeShort(repeat);	 // loop count (extra iterations, 0=repeat forever)
	  out.write(0);			 // block terminator
   }


   /**
	* Writes color table
	*/
   protected void writePalette() throws IOException {
	  out.write(colorTab, 0, colorTab.length);
	  int n = (3 * 256) - colorTab.length;
	  for (int i = 0; i < n; i++)
		 out.write(0);
   }


   /**
	* Encodes and writes pixel data
	*/
   protected void writePixels() throws IOException {
	  LZWEncoder encoder = new LZWEncoder(width, height, indexedPixels, colorDepth);
	  encoder.encode(out);
   }


   /**
	*	 Write 16-bit value to output stream, LSB first
	*/
   protected void writeShort(int value) throws IOException {
	  out.write(value & 0xff);
	  out.write((value >> 8) & 0xff);
   }


   /**
	* Writes string to output stream
	*/
   protected void writeString(String s) throws IOException {
	  for (int i = 0; i < s.length(); i++)
		 out.write((byte) s.charAt(i));
   }
}


//==============================================================================
//	Adapted from Jef Poskanzer's Java port by way of J. M. G. Elliott.
//	K Weiner 12/00


class LZWEncoder {

  private static final int EOF = -1;

  private int	  imgW, imgH;
  private byte[]  pixAry;
  private int	  initCodeSize;
  private int	  remaining;
  private int	  curPixel;


  // GIFCOMPR.C		  - GIF Image compression routines
  //
  // Lempel-Ziv compression based on 'compress'.  GIF modifications by
  // David Rowley (mgardi@watdcsu.waterloo.edu)

  // General DEFINEs

  static final int BITS = 12;

  static final int HSIZE = 5003;	   // 80% occupancy

  // GIF Image compression - modified 'compress'
  //
  // Based on: compress.c - File compression ala IEEE Computer, June 1984.
  //
  // By Authors:  Spencer W. Thomas		 (decvax!harpo!utah-cs!utah-gr!thomas)
  //			  Jim McKie				 (decvax!mcvax!jim)
  //			  Steve Davies			 (decvax!vax135!petsd!peora!srd)
  //			  Ken Turkowski			 (decvax!decwrl!turtlevax!ken)
  //			  James A. Woods		 (decvax!ihnp4!ames!jaw)
  //			  Joe Orost				 (decvax!vax135!petsd!joe)

  int n_bits;						  // number of bits/code
  int maxbits = BITS;				  // user settable max # bits/code
  int maxcode;						  // maximum code, given n_bits
  int maxmaxcode = 1 << BITS; // should NEVER generate this code

  int[] htab = new int[HSIZE];
  int[] codetab = new int[HSIZE];

  int hsize = HSIZE;				  // for dynamic table sizing

  int free_ent = 0;					  // first unused entry

  // block compression parameters -- after all codes are used up,
  // and compression rate changes, start over.
  boolean clear_flg = false;

  // Algorithm:	 use open addressing double hashing (no chaining) on the
  // prefix code / next character combination.	We do a variant of Knuth's
  // algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime
  // secondary probe.  Here, the modular division first probe is gives way
  // to a faster exclusive-or manipulation.	 Also do block compression with
  // an adaptive reset, whereby the code table is cleared when the compression
  // ratio decreases, but after the table fills.  The variable-length output
  // codes are re-sized at this point, and a special CLEAR code is generated
  // for the decompressor.	Late addition:	construct the table according to
  // file size for noticeable speed improvement on small files.	 Please direct
  // questions about this implementation to ames!jaw.

  int g_init_bits;

  int ClearCode;
  int EOFCode;

  // output
  //
  // Output the given code.
  // Inputs:
  //	  code:	  A n_bits-bit integer.	 If == -1, then EOF.  This assumes
  //			  that n_bits =< wordsize - 1.
  // Outputs:
  //	  Outputs code to the file.
  // Assumptions:
  //	  Chars are 8 bits long.
  // Algorithm:
  //	  Maintain a BITS character long buffer (so that 8 codes will
  // fit in it exactly).  Use the VAX insv instruction to insert each
  // code in turn.	When the buffer fills up empty it and start over.

  int cur_accum = 0;
  int cur_bits = 0;

  int masks[] = { 0x0000, 0x0001, 0x0003, 0x0007, 0x000F,
			  0x001F, 0x003F, 0x007F, 0x00FF,
			  0x01FF, 0x03FF, 0x07FF, 0x0FFF,
			  0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF };

  // Number of characters so far in this 'packet'
  int a_count;

  // Define the storage for the packet accumulator
  byte[] accum = new byte[256];


  //----------------------------------------------------------------------------
  LZWEncoder(int width, int height, byte[] pixels, int color_depth) {
   imgW = width;
   imgH = height;
   pixAry = pixels;
   initCodeSize = Math.max(2, color_depth);
  }


  // Add a character to the end of the current packet, and if it is 254
  // characters, flush the packet to disk.
  void char_out( byte c, OutputStream outs ) throws IOException
	 {
	 accum[a_count++] = c;
	 if ( a_count >= 254 )
		flush_char( outs );
	 }


  // Clear out the hash table

  // table clear for block compress
  void cl_block( OutputStream outs ) throws IOException
	 {
	 cl_hash( hsize );
	 free_ent = ClearCode + 2;
	 clear_flg = true;

	 output( ClearCode, outs );
	 }


  // reset code table
  void cl_hash( int hsize )
	 {
	 for ( int i = 0; i < hsize; ++i )
		htab[i] = -1;
	 }


  void compress( int init_bits, OutputStream outs ) throws IOException
	 {
	 int fcode;
	 int i /* = 0 */;
	 int c;
	 int ent;
	 int disp;
	 int hsize_reg;
	 int hshift;

	 // Set up the globals:	 g_init_bits - initial number of bits
	 g_init_bits = init_bits;

	 // Set up the necessary values
	 clear_flg = false;
	 n_bits = g_init_bits;
	 maxcode = MAXCODE( n_bits );

	 ClearCode = 1 << ( init_bits - 1 );
	 EOFCode = ClearCode + 1;
	 free_ent = ClearCode + 2;

	 a_count = 0;  // clear packet

	 ent = nextPixel();

	 hshift = 0;
	 for ( fcode = hsize; fcode < 65536; fcode *= 2 )
		++hshift;
	 hshift = 8 - hshift;		  // set hash code range bound

	 hsize_reg = hsize;
	 cl_hash( hsize_reg );		  // clear hash table

	 output( ClearCode, outs );

	 outer_loop:
	 while ( (c = nextPixel()) != EOF )
		{
		fcode = ( c << maxbits ) + ent;
		i = ( c << hshift ) ^ ent;	 // xor hashing

		if ( htab[i] == fcode )
		   {
		   ent = codetab[i];
		   continue;
		   }
		else if ( htab[i] >= 0 )	 // non-empty slot
		   {
		   disp = hsize_reg - i;  // secondary hash (after G. Knott)
		   if ( i == 0 )
			  disp = 1;
		   do
			  {
			  if ( (i -= disp) < 0 )
				 i += hsize_reg;

			  if ( htab[i] == fcode )
				 {
				 ent = codetab[i];
				 continue outer_loop;
				 }
			  }
		   while ( htab[i] >= 0 );
		   }
		output( ent, outs );
		ent = c;
		if ( free_ent < maxmaxcode )
		   {
		   codetab[i] = free_ent++;	 // code -> hashtable
		   htab[i] = fcode;
		   }
		else
		   cl_block( outs );
		}
	 // Put out the final code.
	 output( ent, outs );
	 output( EOFCode, outs );
	 }


  //----------------------------------------------------------------------------
  void encode(OutputStream os) throws IOException
  {
   os.write(initCodeSize);		   // write "initial code size" byte

   remaining = imgW * imgH;		   // reset navigation variables
   curPixel = 0;

   compress(initCodeSize + 1, os); // compress and write the pixel data

   os.write(0);					   // write block terminator
  }


  // Flush the packet to disk, and reset the accumulator
  void flush_char( OutputStream outs ) throws IOException
	 {
	 if ( a_count > 0 )
		{
		outs.write( a_count );
		outs.write( accum, 0, a_count );
		a_count = 0;
		}
	 }


  final int MAXCODE( int n_bits )
	 {
	 return ( 1 << n_bits ) - 1;
	 }


  //----------------------------------------------------------------------------
  // Return the next pixel from the image
  //----------------------------------------------------------------------------
  private int nextPixel()
  {
   if (remaining == 0)
	 return EOF;

   --remaining;

   byte pix = pixAry[curPixel++];

   return pix & 0xff;
  }


  void output( int code, OutputStream outs ) throws IOException
	 {
	 cur_accum &= masks[cur_bits];

	 if ( cur_bits > 0 )
		cur_accum |= ( code << cur_bits );
	 else
		cur_accum = code;

	 cur_bits += n_bits;

	 while ( cur_bits >= 8 )
		{
		char_out( (byte) ( cur_accum & 0xff ), outs );
		cur_accum >>= 8;
		cur_bits -= 8;
		}

	 // If the next entry is going to be too big for the code size,
	 // then increase it, if possible.
	if ( free_ent > maxcode || clear_flg )
		{
		if ( clear_flg )
		   {
		   maxcode = MAXCODE(n_bits = g_init_bits);
		   clear_flg = false;
		   }
		else
		   {
		   ++n_bits;
		   if ( n_bits == maxbits )
			  maxcode = maxmaxcode;
		   else
			  maxcode = MAXCODE(n_bits);
		   }
		}

	 if ( code == EOFCode )
		{
		// At EOF, write the rest of the buffer.
		while ( cur_bits > 0 )
		   {
		   char_out( (byte) ( cur_accum & 0xff ), outs );
		   cur_accum >>= 8;
		   cur_bits -= 8;
		   }

		flush_char( outs );
		}
	 }
}

