text: /**
   * This class lets you encode animated GIF files
   * Base class :  http://www.java2s.com/Code/Java/2D-Graphics-GUI/AnimatedGifEncoder.htm
   * @author Kevin Weiner (original Java version - kweiner@fmsware.com)
   * @author Thibault Imbert (AS3 version - bytearray.org)
   * @author Kevin Kwok (JavaScript version - https://github.com/antimatter15/jsgif)
   * @version 0.1 AS3 implementation
   */
  
  GIFEncoder = function () {
  
    for (var i = 0, chr = {}; i < 256; i++)
      chr[i] = String.fromCharCode(i);
  
    function ByteArray() {
      this.bin = [];
    }
  
    ByteArray.prototype.getData = function () {
      for (var v = '', l = this.bin.length, i = 0; i < l; i++)
        v += chr[this.bin[i]];
      return v;
    };
  
    ByteArray.prototype.writeByte = function (val) {
      this.bin.push(val);
    };
  
    ByteArray.prototype.writeUTFBytes = function (string) {
      for (var l = string.length, i = 0; i < l; i++)
        this.writeByte(string.charCodeAt(i));
    };
  
    ByteArray.prototype.writeBytes = function (array, offset, length) {
      for (var l = length || array.length, i = offset || 0; i < l; i++)
        this.writeByte(array[i]);
    };
  
    var exports = {};
    var width; // image size
    var height;
    var transparent = null; // transparent color if given
    var transIndex; // transparent index in color table
    var repeat = -1; // no repeat
    var delay = 0; // frame delay (hundredths)
    var started = false; // ready to output frames
    var out;
    var image; // current frame
    var pixels; // BGR byte array from frame
    var indexedPixels; // converted frame indexed to palette
    var colorDepth; // number of bit planes
    var colorTab; // RGB palette
    var usedEntry = []; // active palette entries
    var palSize = 7; // color table size (bits-1)
    var dispose = -1; // disposal code (-1 = use default)
    var closeStream = false; // close stream when finished
    var firstFrame = true;
    var sizeSet = false; // if false, get size from first frame
    var sample = 10; // default sample interval for quantizer
    var comment = "Generated by jsgif (https://github.com/antimatter15/jsgif/)"; // default comment for generated gif
  
  	/**
  	 * Sets the delay time between each frame, or changes it for subsequent frames
  	 * (applies to last frame added)
  	 * int delay time in milliseconds
  	 * @param ms
  	 */
  
    var setDelay = exports.setDelay = function setDelay(ms) {
      delay = Math.round(ms / 10);
    };
  
  	/**
  	 * 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.
  	 */
  
    var setDispose = exports.setDispose = function setDispose(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
  	 */
  
    var setRepeat = exports.setRepeat = function setRepeat(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
  	 * Color to be treated as transparent on display.
  	 */
  
    var setTransparent = exports.setTransparent = function setTransparent(c) {
      transparent = c;
    };
  
  
  	/**
  	 * Sets the comment for the block comment
  	 * @param
  	 * string to be insterted as comment
  	 */
  
    var setComment = exports.setComment = function setComment(c) {
      comment = c;
    };
  
  
  
  	/**
  	 * The addFrame method takes an incoming BitmapData object to create each frames
  	 * @param
  	 * BitmapData object to be treated as a GIF's frame
  	 */
  
    var addFrame = exports.addFrame = function addFrame(im, is_imageData) {
  
      if ((im === null) || !started || out === null) {
        throw new Error("Please call start method before calling addFrame");
      }
  
      var ok = true;
  
      try {
        if (!is_imageData) {
          image = im.getImageData(0, 0, im.canvas.width, im.canvas.height).data;
          if (!sizeSet) setSize(im.canvas.width, im.canvas.height);
        } else {
          if (im instanceof ImageData) {
            image = im.data;
            if (!sizeSet || width != im.width || height != im.height) {
              setSize(im.width, im.height);
            } else {
  
            }
          } else if (im instanceof Uint8ClampedArray) {
            if (im.length == (width * height * 4)) {
              image = im;
            } else {
              console.log("Please set the correct size: ImageData length mismatch");
              ok = false;
            }
          } else {
            console.log("Please provide correct input");
            ok = false;
          }
        }
        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
        if (comment !== '') {
          writeCommentExt(); // write comment extension
        }
        writeImageDesc(); // image descriptor
        if (!firstFrame) writePalette(); // local color table
        writePixels(); // encode and write pixel data
        firstFrame = false;
      } catch (e) {
        ok = false;
      }
  
      return ok;
    };
  
  	/**
  	* @description: Downloads the encoded gif with the given name
  	* No need of any conversion from the stream data (out) to base64
  	* Solves the issue of large file sizes when there are more frames
  	* and does not involve in creation of any temporary data in the process
  	* so no wastage of memory, and speeds up the process of downloading
  	* to just calling this function.
  	* @parameter {String} filename filename used for downloading the gif
  	*/
  
    var download = exports.download = function download(filename) {
      if (out === null || closeStream == false) {
        console.log("Please call start method and add frames and call finish method before calling download");
      } else {
        filename = filename !== undefined ? (filename.endsWith(".gif") ? filename : filename + ".gif") : "download.gif";
        var templink = document.createElement("a");
        templink.download = filename;
        templink.href = URL.createObjectURL(new Blob([new Uint8Array(out.bin)], { type: "image/gif" }));
        templink.click();
      }
    }
  
  	/**
  	 * Adds final trailer to the GIF stream, if you don't call the finish method
  	 * the GIF stream will not be valid.
  	 */
  
    var finish = exports.finish = function finish() {
  
      if (!started) return false;
  
      var ok = true;
      started = false;
  
      try {
        out.writeByte(0x3b); // gif trailer
        closeStream = true;
      } catch (e) {
        ok = false;
      }
  
      return ok;
    };
  
  	/**
  	 * Resets some members so that a new stream can be started.
  	 * This method is actually called by the start method
  	 */
  
    var reset = function reset() {
  
      // reset for subsequent use
      transIndex = 0;
      image = null;
      pixels = null;
      indexedPixels = null;
      colorTab = null;
      closeStream = false;
      firstFrame = true;
    };
  
  	/**
  	 * * Sets frame rate in frames per second. Equivalent to
  	 * <code>setDelay(1000/fps)</code>.
  	 * @param fps
  	 * float frame rate (frames per second)
  	 */
  
    var setFrameRate = exports.setFrameRate = function setFrameRate(fps) {
      if (fps != 0xf) delay = Math.round(100 / 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
  	 */
  
    var setQuality = exports.setQuality = function setQuality(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.
  	 */
  
    var setSize = exports.setSize = function setSize(w, 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.
  	 * @param os
  	 * OutputStream on which GIF images are written.
  	 * @return false if initial write failed.
  	 */
  
    var start = exports.start = function start() {
  
      reset();
      var ok = true;
      closeStream = false;
      out = new ByteArray();
      try {
        out.writeUTFBytes("GIF89a"); // header
      } catch (e) {
        ok = false;
      }
  
      return started = ok;
    };
  
    var cont = exports.cont = function cont() {
  
      reset();
      var ok = true;
      closeStream = false;
      out = new ByteArray();
  
      return started = ok;
    };
  
  	/**
  	 * Analyzes image colors and creates color map.
  	 */
  
    var analyzePixels = function analyzePixels() {
  
      var len = pixels.length;
      var nPix = len / 3;
      indexedPixels = [];
      var nq = new NeuQuant(pixels, len, sample);
  
      // initialize quantizer
      colorTab = nq.process(); // create reduced palette
  
      // map image pixels to new palette
      var k = 0;
      for (var j = 0; j < nPix; j++) {
        var index = nq.map(pixels[k++] & 0xff, pixels[k++] & 0xff, pixels[k++] & 0xff);
        usedEntry[index] = true;
        indexedPixels[j] = 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
  	 */
  
    var findClosest = function findClosest(c) {
  
      if (colorTab === null) return -1;
      var r = (c & 0xFF0000) >> 16;
      var g = (c & 0x00FF00) >> 8;
      var b = (c & 0x0000FF);
      var minpos = 0;
      var dmin = 256 * 256 * 256;
      var len = colorTab.length;
  
      for (var i = 0; i < len;) {
        var dr = r - (colorTab[i++] & 0xff);
        var dg = g - (colorTab[i++] & 0xff);
        var db = b - (colorTab[i] & 0xff);
        var d = dr * dr + dg * dg + db * db;
        var index = i / 3;
        if (usedEntry[index] && (d < dmin)) {
          dmin = d;
          minpos = index;
        }
        i++;
      }
      return minpos;
    };
  
  	/**
  	 * Extracts image pixels into byte array "pixels
  	 */
  
    var getImagePixels = function getImagePixels() {
      var w = width;
      var h = height;
      pixels = [];
      var data = image;
      var count = 0;
  
      for (var i = 0; i < h; i++) {
        for (var j = 0; j < w; j++) {
          var b = (i * w * 4) + j * 4;
          // 因为 GIF 没有 alpha 通道，无法表示透明度，因此需要设定一个阈值，低于这个阈值的透明度舍弃为白色，否则渲染为黑色
          // 清理透明度的阈值，最高 255，越高会导致最终结果的颜色越浅
          if (data[b + 3] < 100) {
            pixels[count++] = 255;
            pixels[count++] = 255;
            pixels[count++] = 255;
            continue;
          }
          pixels[count++] = data[b];
          pixels[count++] = data[b + 1];
          pixels[count++] = data[b + 2];
        }
      }
    };
  
  	/**
  	 * Writes Graphic Control Extension
  	 */
  
    var writeGraphicCtrlExt = function writeGraphicCtrlExt() {
      out.writeByte(0x21); // extension introducer
      out.writeByte(0xf9); // GCE label
      out.writeByte(4); // data block size
      var transp;
      var 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.writeByte(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.writeByte(transIndex); // transparent color index
      out.writeByte(0); // block terminator
    };
  
  	/**
  	 * Writes Comment Extention
  	 */
  
    var writeCommentExt = function writeCommentExt() {
      out.writeByte(0x21); // extension introducer
      out.writeByte(0xfe); // comment label
      out.writeByte(comment.length); // Block Size (s)
      out.writeUTFBytes(comment);
      out.writeByte(0); // block terminator
    };
  
  
  	/**
  	 * Writes Image Descriptor
  	 */
  
    var writeImageDesc = function writeImageDesc() {
  
      out.writeByte(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.writeByte(0);
      } else {
        // specify normal LCT
        out.writeByte(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
  	 */
  
    var writeLSD = function writeLSD() {
  
      // logical screen size
      WriteShort(width);
      WriteShort(height);
      // packed fields
      out.writeByte((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.writeByte(0); // background color index
      out.writeByte(0); // pixel aspect ratio - assume 1:1
    };
  
  	/**
  	 * Writes Netscape application extension to define repeat count.
  	 */
  
    var writeNetscapeExt = function writeNetscapeExt() {
      out.writeByte(0x21); // extension introducer
      out.writeByte(0xff); // app extension label
      out.writeByte(11); // block size
      out.writeUTFBytes("NETSCAPE" + "2.0"); // app id + auth code
      out.writeByte(3); // sub-block size
      out.writeByte(1); // loop sub-block id
      WriteShort(repeat); // loop count (extra iterations, 0=repeat forever)
      out.writeByte(0); // block terminator
    };
  
  	/**
  	 * Writes color table
  	 */
  
    var writePalette = function writePalette() {
      out.writeBytes(colorTab);
      var n = (3 * 256) - colorTab.length;
      for (var i = 0; i < n; i++) out.writeByte(0);
    };
  
    var WriteShort = function WriteShort(pValue) {
      out.writeByte(pValue & 0xFF);
      out.writeByte((pValue >> 8) & 0xFF);
    };
  
  	/**
  	 * Encodes and writes pixel data
  	 */
  
    var writePixels = function writePixels() {
      var myencoder = new LZWEncoder(width, height, indexedPixels, colorDepth);
      myencoder.encode(out);
    };
  
  	/**
  	 * Retrieves the GIF stream
  	 */
  
    var stream = exports.stream = function stream() {
      return out;
    };
  
    var setProperties = exports.setProperties = function setProperties(has_start, is_first) {
      started = has_start;
      firstFrame = is_first;
    };
  
    return exports;
  
  };
  