package com.mortals.xhx.base.login.service;

import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

/**  
 * Class AnimatedGifEncoder - Encodes a GIF file consisting of one or  
 * more frames.  
 * <pre>  
 * Example:  
 *    AnimatedGifEncoder e = new AnimatedGifEncoder();  
 *    e.start(outputFileName);  
 *    e.setDelay(1000);   // 1 frame per sec  
 *    e.addFrame(image1);  
 *    e.addFrame(image2);  
 *    e.finish();  
 * </pre>  
 * 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.  
 *  
 * @author Kevin Weiner, FM Software  
 * @version 1.03 November 2003  
 *  
 */  
  
public class AnimatedGifEncoder {  
  
 protected int width; // image size  
 protected int height;  
 protected Color transparent = null; // transparent color if given  
 protected int transIndex; // transparent index in color table  
 protected int repeat = -1; // no repeat  
 protected int delay = 0; // frame delay (hundredths)  
 protected boolean started = false; // ready to output frames  
 protected OutputStream out;  
 protected BufferedImage image; // current frame  
 protected byte[] pixels; // BGR byte array from frame  
 protected byte[] indexedPixels; // converted frame indexed to palette  
 protected int colorDepth; // number of bit planes  
 protected byte[] colorTab; // RGB palette  
 protected boolean[] usedEntry = new boolean[256]; // active palette entries  
 protected int palSize = 7; // color table size (bits-1)  
 protected int dispose = -1; // disposal code (-1 = use default)  
 protected boolean closeStream = false; // close stream when finished  
 protected boolean firstFrame = true;  
 protected boolean sizeSet = false; // if false, get size from first frame  
 protected int sample = 10; // default sample interval for quantizer  
  
 /**  
  * 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 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 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(Color c) {  
  transparent = c;  
 }  
   
 /**  
  * 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 BufferedImage containing frame to write.  
  * @return true if successful.  
  */  
 public boolean addFrame(BufferedImage im) {  
  if ((im == null) || !started) {  
   return false;  
  }  
  boolean ok = true;  
  try {  
   if (!sizeSet) {  
    // use first frame's size  
    setSize(im.getWidth(), im.getHeight());  
   }  
   image = im;  
   getImagePixels(); // convert to correct format if necessary  
   analyzePixels(); // build color table & map pixels  
   if (firstFrame) {  
    writeLSD(); // logical screen descriptior  
    writePalette(); // global color table  
    if (repeat >= 0) {  
     // use NS app extension to indicate reps  
     writeNetscapeExt();  
    }  
   }  
   writeGraphicCtrlExt(); // write graphic control extension  
   writeImageDesc(); // image descriptor  
   if (!firstFrame) {  
    writePalette(); // local color table  
   }  
   writePixels(); // encode and write pixel data  
   firstFrame = false;  
  } catch (IOException e) {  
   ok = false;  
  }  
  
  return ok;  
 }  
   
 /**  
  * 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  
  transIndex = 0;  
  out = null;  
  image = null;  
  pixels = null;  
  indexedPixels = null;  
  colorTab = null;  
  closeStream = false;  
  firstFrame = true;  
  
  return ok;  
 }  
   
 /**  
  * 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 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;  
 }  
   
 /**  
  * 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;  
 }  
   
 /**  
  * Analyzes image colors and creates color map.  
  */  
 protected void analyzePixels() {  
  int len = pixels.length;  
  int nPix = len / 3;  
  indexedPixels = new byte[nPix];  
  NeuQuant nq = new NeuQuant(pixels, len, sample);  
  // initialize quantizer  
  colorTab = nq.process(); // create reduced palette  
  // convert map from BGR to RGB  
  for (int i = 0; i < colorTab.length; i += 3) {  
   byte temp = colorTab[i];  
   colorTab[i] = colorTab[i + 2];  
   colorTab[i + 2] = temp;  
   usedEntry[i / 3] = false;  
  }  
  // map image pixels to new palette  
  int k = 0;  
  for (int i = 0; i < nPix; i++) {  
   int index =  
    nq.map(pixels[k++] & 0xff,  
        pixels[k++] & 0xff,  
        pixels[k++] & 0xff);  
   usedEntry[index] = true;  
   indexedPixels[i] = (byte) index;  
  }  
  pixels = null;  
  colorDepth = 8;  
  palSize = 7;  
  // get closest match to transparent color if specified  
  if (transparent != null) {  
   transIndex = findClosest(transparent);  
  }  
 }  
   
 /**  
  * Returns index of palette color closest to c  
  *  
  */  
 protected int findClosest(Color c) {  
  if (colorTab == null) return -1;  
  int r = c.getRed();  
  int g = c.getGreen();  
  int b = c.getBlue();  
  int minpos = 0;  
  int dmin = 256 * 256 * 256;  
  int len = colorTab.length;  
  for (int i = 0; i < len;) {  
   int dr = r - (colorTab[i++] & 0xff);  
   int dg = g - (colorTab[i++] & 0xff);  
   int db = b - (colorTab[i] & 0xff);  
   int d = dr * dr + dg * dg + db * db;  
   int index = i / 3;  
   if (usedEntry[index] && (d < dmin)) {  
    dmin = d;  
    minpos = index;  
   }  
   i++;  
  }  
  return minpos;  
 }  
   
 /**  
  * Extracts image pixels into byte array "pixels"  
  */  
 protected void getImagePixels() {  
  int w = image.getWidth();  
  int h = image.getHeight();  
  int type = image.getType();  
  if ((w != width)  
   || (h != height)  
   || (type != BufferedImage.TYPE_3BYTE_BGR)) {  
   // create new image with right size/format  
   BufferedImage temp =  
    new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);  
   Graphics2D g = temp.createGraphics();  
   g.drawImage(image, 0, 0, null);  
   image = temp;  
  }  
  pixels = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();  
 }  
   
 /**  
  * 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 == null) {  
   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  
  if (firstFrame) {  
   // no LCT  - GCT is used for first (or only) frame  
   out.write(0);  
  } else {  
   // specify normal LCT  
   out.write(0x80 | // 1 local color table  1=yes  
       0 | // 2 interlace - 0=no  
       0 | // 3 sorted - 0=no  
       0 | // 4-5 reserved  
       palSize); // 6-8 size of color table  
  }  
 }  
   
 /**  
  * Writes Logical Screen Descriptor  
  */  
 protected void writeLSD() throws IOException {  
  // logical screen size  
  writeShort(width);  
  writeShort(height);  
  // packed fields  
  out.write((0x80 | // 1   : global color table flag = 1 (gct used)  
       0x70 | // 2-4 : color resolution = 7  
       0x00 | // 5   : gct sort flag = 0  
      palSize)); // 6-8 : gct size  
  
  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));  
  }  
 }  
}  
