gif-encoder

GIFEncoder from jsgif

此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.cn-greasyfork.org/scripts/428487/1028787/gif-encoder.js

  1. /**
  2. * This class lets you encode animated GIF files
  3. * Base class : http://www.java2s.com/Code/Java/2D-Graphics-GUI/AnimatedGifEncoder.htm
  4. * @author Kevin Weiner (original Java version - kweiner@fmsware.com)
  5. * @author Thibault Imbert (AS3 version - bytearray.org)
  6. * @author Kevin Kwok (JavaScript version - https://github.com/antimatter15/jsgif)
  7. * @version 0.1 AS3 implementation
  8. */
  9.  
  10. GIFEncoder = function() {
  11.  
  12. for (var i = 0, chr = {}; i < 256; i++)
  13. chr[i] = String.fromCharCode(i);
  14.  
  15. function ByteArray() {
  16. this.bin = [];
  17. }
  18.  
  19. ByteArray.prototype.getData = function() {
  20. for (var v = '', l = this.bin.length, i = 0; i < l; i++)
  21. v += chr[this.bin[i]];
  22. return v;
  23. };
  24.  
  25. ByteArray.prototype.writeByte = function(val) {
  26. this.bin.push(val);
  27. };
  28.  
  29. ByteArray.prototype.writeUTFBytes = function(string) {
  30. for (var l = string.length, i = 0; i < l; i++)
  31. this.writeByte(string.charCodeAt(i));
  32. };
  33.  
  34. ByteArray.prototype.writeBytes = function(array, offset, length) {
  35. for (var l = length || array.length, i = offset || 0; i < l; i++)
  36. this.writeByte(array[i]);
  37. };
  38.  
  39. var exports = {};
  40. var width; // image size
  41. var height;
  42. var transparent = null; // transparent color if given
  43. var transIndex; // transparent index in color table
  44. var repeat = -1; // no repeat
  45. var delay = 0; // frame delay (hundredths)
  46. var started = false; // ready to output frames
  47. var out;
  48. var image; // current frame
  49. var pixels; // BGR byte array from frame
  50. var indexedPixels; // converted frame indexed to palette
  51. var colorDepth; // number of bit planes
  52. var colorTab; // RGB palette
  53. var usedEntry = []; // active palette entries
  54. var palSize = 7; // color table size (bits-1)
  55. var dispose = -1; // disposal code (-1 = use default)
  56. var closeStream = false; // close stream when finished
  57. var firstFrame = true;
  58. var sizeSet = false; // if false, get size from first frame
  59. var sample = 10; // default sample interval for quantizer
  60. var comment = "Generated by jsgif (https://github.com/antimatter15/jsgif/)"; // default comment for generated gif
  61.  
  62. /**
  63. * Sets the delay time between each frame, or changes it for subsequent frames
  64. * (applies to last frame added)
  65. * int delay time in milliseconds
  66. * @param ms
  67. */
  68.  
  69. var setDelay = exports.setDelay = function setDelay(ms) {
  70. delay = Math.round(ms / 10);
  71. };
  72.  
  73. /**
  74. * Sets the GIF frame disposal code for the last added frame and any
  75. *
  76. * subsequent frames. Default is 0 if no transparent color has been set,
  77. * otherwise 2.
  78. * @param code
  79. * int disposal code.
  80. */
  81.  
  82. var setDispose = exports.setDispose = function setDispose(code) {
  83. if (code >= 0) dispose = code;
  84. };
  85.  
  86. /**
  87. * Sets the number of times the set of GIF frames should be played. Default is
  88. * 1; 0 means play indefinitely. Must be invoked before the first image is
  89. * added.
  90. *
  91. * @param iter
  92. * int number of iterations.
  93. * @return
  94. */
  95.  
  96. var setRepeat = exports.setRepeat = function setRepeat(iter) {
  97. if (iter >= 0) repeat = iter;
  98. };
  99.  
  100. /**
  101. * Sets the transparent color for the last added frame and any subsequent
  102. * frames. Since all colors are subject to modification in the quantization
  103. * process, the color in the final palette for each frame closest to the given
  104. * color becomes the transparent color for that frame. May be set to null to
  105. * indicate no transparent color.
  106. * @param
  107. * Color to be treated as transparent on display.
  108. */
  109.  
  110. var setTransparent = exports.setTransparent = function setTransparent(c) {
  111. transparent = c;
  112. };
  113.  
  114.  
  115. /**
  116. * Sets the comment for the block comment
  117. * @param
  118. * string to be insterted as comment
  119. */
  120.  
  121. var setComment = exports.setComment = function setComment(c) {
  122. comment = c;
  123. };
  124.  
  125.  
  126.  
  127. /**
  128. * The addFrame method takes an incoming BitmapData object to create each frames
  129. * @param
  130. * BitmapData object to be treated as a GIF's frame
  131. */
  132.  
  133. var addFrame = exports.addFrame = function addFrame(im, is_imageData) {
  134.  
  135. if ((im === null) || !started || out === null) {
  136. throw new Error("Please call start method before calling addFrame");
  137. }
  138.  
  139. var ok = true;
  140.  
  141. try {
  142. if (!is_imageData) {
  143. image = im.getImageData(0, 0, im.canvas.width, im.canvas.height).data;
  144. if (!sizeSet) setSize(im.canvas.width, im.canvas.height);
  145. } else {
  146. if(im instanceof ImageData) {
  147. image = im.data;
  148. if(!sizeset || width!=im.width || height!=im.height) {
  149. setSize(im.width,im.height);
  150. } else {
  151. }
  152. } else if(im instanceof Uint8ClampedArray) {
  153. if(im.length==(width*height*4)) {
  154. image=im;
  155. } else {
  156. console.log("Please set the correct size: ImageData length mismatch");
  157. ok=false;
  158. }
  159. } else {
  160. console.log("Please provide correct input");
  161. ok=false;
  162. }
  163. }
  164. getImagePixels(); // convert to correct format if necessary
  165. analyzePixels(); // build color table & map pixels
  166.  
  167. if (firstFrame) {
  168. writeLSD(); // logical screen descriptior
  169. writePalette(); // global color table
  170. if (repeat >= 0) {
  171. // use NS app extension to indicate reps
  172. writeNetscapeExt();
  173. }
  174. }
  175.  
  176. writeGraphicCtrlExt(); // write graphic control extension
  177. if (comment !== '') {
  178. writeCommentExt(); // write comment extension
  179. }
  180. writeImageDesc(); // image descriptor
  181. if (!firstFrame) writePalette(); // local color table
  182. writePixels(); // encode and write pixel data
  183. firstFrame = false;
  184. } catch (e) {
  185. ok = false;
  186. }
  187.  
  188. return ok;
  189. };
  190. /**
  191. * @description: Downloads the encoded gif with the given name
  192. * No need of any conversion from the stream data (out) to base64
  193. * Solves the issue of large file sizes when there are more frames
  194. * and does not involve in creation of any temporary data in the process
  195. * so no wastage of memory, and speeds up the process of downloading
  196. * to just calling this function.
  197. * @parameter {String} filename filename used for downloading the gif
  198. */
  199. var download = exports.download = function download(filename) {
  200. if(out===null || closeStream==false) {
  201. console.log("Please call start method and add frames and call finish method before calling download");
  202. } else {
  203. filename= filename !== undefined ? ( filename.endsWith(".gif")? filename: filename+".gif" ): "download.gif";
  204. var templink = document.createElement("a");
  205. templink.download=filename;
  206. templink.href= URL.createObjectURL(new Blob([new Uint8Array(out.bin)], {type : "image/gif" } ));
  207. templink.click();
  208. }
  209. }
  210.  
  211. /**
  212. * Adds final trailer to the GIF stream, if you don't call the finish method
  213. * the GIF stream will not be valid.
  214. */
  215.  
  216. var finish = exports.finish = function finish() {
  217.  
  218. if (!started) return false;
  219.  
  220. var ok = true;
  221. started = false;
  222.  
  223. try {
  224. out.writeByte(0x3b); // gif trailer
  225. closeStream=true;
  226. } catch (e) {
  227. ok = false;
  228. }
  229.  
  230. return ok;
  231. };
  232.  
  233. /**
  234. * Resets some members so that a new stream can be started.
  235. * This method is actually called by the start method
  236. */
  237.  
  238. var reset = function reset() {
  239.  
  240. // reset for subsequent use
  241. transIndex = 0;
  242. image = null;
  243. pixels = null;
  244. indexedPixels = null;
  245. colorTab = null;
  246. closeStream = false;
  247. firstFrame = true;
  248. };
  249.  
  250. /**
  251. * * Sets frame rate in frames per second. Equivalent to
  252. * <code>setDelay(1000/fps)</code>.
  253. * @param fps
  254. * float frame rate (frames per second)
  255. */
  256.  
  257. var setFrameRate = exports.setFrameRate = function setFrameRate(fps) {
  258. if (fps != 0xf) delay = Math.round(100 / fps);
  259. };
  260.  
  261. /**
  262. * Sets quality of color quantization (conversion of images to the maximum 256
  263. * colors allowed by the GIF specification). Lower values (minimum = 1)
  264. * produce better colors, but slow processing significantly. 10 is the
  265. * default, and produces good color mapping at reasonable speeds. Values
  266. * greater than 20 do not yield significant improvements in speed.
  267. * @param quality
  268. * int greater than 0.
  269. * @return
  270. */
  271.  
  272. var setQuality = exports.setQuality = function setQuality(quality) {
  273. if (quality < 1) quality = 1;
  274. sample = quality;
  275. };
  276.  
  277. /**
  278. * Sets the GIF frame size. The default size is the size of the first frame
  279. * added if this method is not invoked.
  280. * @param w
  281. * int frame width.
  282. * @param h
  283. * int frame width.
  284. */
  285.  
  286. var setSize = exports.setSize = function setSize(w, h) {
  287.  
  288. if (started && !firstFrame) return;
  289. width = w;
  290. height = h;
  291. if (width < 1) width = 320;
  292. if (height < 1) height = 240;
  293. sizeSet = true;
  294. };
  295.  
  296. /**
  297. * Initiates GIF file creation on the given stream.
  298. * @param os
  299. * OutputStream on which GIF images are written.
  300. * @return false if initial write failed.
  301. */
  302.  
  303. var start = exports.start = function start() {
  304.  
  305. reset();
  306. var ok = true;
  307. closeStream = false;
  308. out = new ByteArray();
  309. try {
  310. out.writeUTFBytes("GIF89a"); // header
  311. } catch (e) {
  312. ok = false;
  313. }
  314.  
  315. return started = ok;
  316. };
  317.  
  318. var cont = exports.cont = function cont() {
  319.  
  320. reset();
  321. var ok = true;
  322. closeStream = false;
  323. out = new ByteArray();
  324.  
  325. return started = ok;
  326. };
  327.  
  328. /**
  329. * Analyzes image colors and creates color map.
  330. */
  331.  
  332. var analyzePixels = function analyzePixels() {
  333.  
  334. var len = pixels.length;
  335. var nPix = len / 3;
  336. indexedPixels = [];
  337. var nq = new NeuQuant(pixels, len, sample);
  338.  
  339. // initialize quantizer
  340. colorTab = nq.process(); // create reduced palette
  341.  
  342. // map image pixels to new palette
  343. var k = 0;
  344. for (var j = 0; j < nPix; j++) {
  345. var index = nq.map(pixels[k++] & 0xff, pixels[k++] & 0xff, pixels[k++] & 0xff);
  346. usedEntry[index] = true;
  347. indexedPixels[j] = index;
  348. }
  349.  
  350. pixels = null;
  351. colorDepth = 8;
  352. palSize = 7;
  353.  
  354. // get closest match to transparent color if specified
  355. if (transparent !== null) {
  356. transIndex = findClosest(transparent);
  357. }
  358. };
  359.  
  360. /**
  361. * Returns index of palette color closest to c
  362. */
  363.  
  364. var findClosest = function findClosest(c) {
  365.  
  366. if (colorTab === null) return -1;
  367. var r = (c & 0xFF0000) >> 16;
  368. var g = (c & 0x00FF00) >> 8;
  369. var b = (c & 0x0000FF);
  370. var minpos = 0;
  371. var dmin = 256 * 256 * 256;
  372. var len = colorTab.length;
  373.  
  374. for (var i = 0; i < len;) {
  375. var dr = r - (colorTab[i++] & 0xff);
  376. var dg = g - (colorTab[i++] & 0xff);
  377. var db = b - (colorTab[i] & 0xff);
  378. var d = dr * dr + dg * dg + db * db;
  379. var index = i / 3;
  380. if (usedEntry[index] && (d < dmin)) {
  381. dmin = d;
  382. minpos = index;
  383. }
  384. i++;
  385. }
  386. return minpos;
  387. };
  388.  
  389. /**
  390. * Extracts image pixels into byte array "pixels
  391. */
  392.  
  393. var getImagePixels = function getImagePixels() {
  394. var w = width;
  395. var h = height;
  396. pixels = [];
  397. var data = image;
  398. var count = 0;
  399.  
  400. for (var i = 0; i < h; i++) {
  401.  
  402. for (var j = 0; j < w; j++) {
  403.  
  404. var b = (i * w * 4) + j * 4;
  405. pixels[count++] = data[b];
  406. pixels[count++] = data[b + 1];
  407. pixels[count++] = data[b + 2];
  408.  
  409. }
  410.  
  411. }
  412. };
  413.  
  414. /**
  415. * Writes Graphic Control Extension
  416. */
  417.  
  418. var writeGraphicCtrlExt = function writeGraphicCtrlExt() {
  419. out.writeByte(0x21); // extension introducer
  420. out.writeByte(0xf9); // GCE label
  421. out.writeByte(4); // data block size
  422. var transp;
  423. var disp;
  424. if (transparent === null) {
  425. transp = 0;
  426. disp = 0; // dispose = no action
  427. } else {
  428. transp = 1;
  429. disp = 2; // force clear if using transparent color
  430. }
  431. if (dispose >= 0) {
  432. disp = dispose & 7; // user override
  433. }
  434. disp <<= 2;
  435. // packed fields
  436. out.writeByte(0 | // 1:3 reserved
  437. disp | // 4:6 disposal
  438. 0 | // 7 user input - 0 = none
  439. transp); // 8 transparency flag
  440.  
  441. WriteShort(delay); // delay x 1/100 sec
  442. out.writeByte(transIndex); // transparent color index
  443. out.writeByte(0); // block terminator
  444. };
  445.  
  446. /**
  447. * Writes Comment Extention
  448. */
  449.  
  450. var writeCommentExt = function writeCommentExt() {
  451. out.writeByte(0x21); // extension introducer
  452. out.writeByte(0xfe); // comment label
  453. out.writeByte(comment.length); // Block Size (s)
  454. out.writeUTFBytes(comment);
  455. out.writeByte(0); // block terminator
  456. };
  457.  
  458.  
  459. /**
  460. * Writes Image Descriptor
  461. */
  462.  
  463. var writeImageDesc = function writeImageDesc() {
  464.  
  465. out.writeByte(0x2c); // image separator
  466. WriteShort(0); // image position x,y = 0,0
  467. WriteShort(0);
  468. WriteShort(width); // image size
  469. WriteShort(height);
  470.  
  471. // packed fields
  472. if (firstFrame) {
  473. // no LCT - GCT is used for first (or only) frame
  474. out.writeByte(0);
  475. } else {
  476. // specify normal LCT
  477. out.writeByte(0x80 | // 1 local color table 1=yes
  478. 0 | // 2 interlace - 0=no
  479. 0 | // 3 sorted - 0=no
  480. 0 | // 4-5 reserved
  481. palSize); // 6-8 size of color table
  482. }
  483. };
  484.  
  485. /**
  486. * Writes Logical Screen Descriptor
  487. */
  488.  
  489. var writeLSD = function writeLSD() {
  490.  
  491. // logical screen size
  492. WriteShort(width);
  493. WriteShort(height);
  494. // packed fields
  495. out.writeByte((0x80 | // 1 : global color table flag = 1 (gct used)
  496. 0x70 | // 2-4 : color resolution = 7
  497. 0x00 | // 5 : gct sort flag = 0
  498. palSize)); // 6-8 : gct size
  499.  
  500. out.writeByte(0); // background color index
  501. out.writeByte(0); // pixel aspect ratio - assume 1:1
  502. };
  503.  
  504. /**
  505. * Writes Netscape application extension to define repeat count.
  506. */
  507.  
  508. var writeNetscapeExt = function writeNetscapeExt() {
  509. out.writeByte(0x21); // extension introducer
  510. out.writeByte(0xff); // app extension label
  511. out.writeByte(11); // block size
  512. out.writeUTFBytes("NETSCAPE" + "2.0"); // app id + auth code
  513. out.writeByte(3); // sub-block size
  514. out.writeByte(1); // loop sub-block id
  515. WriteShort(repeat); // loop count (extra iterations, 0=repeat forever)
  516. out.writeByte(0); // block terminator
  517. };
  518.  
  519. /**
  520. * Writes color table
  521. */
  522.  
  523. var writePalette = function writePalette() {
  524. out.writeBytes(colorTab);
  525. var n = (3 * 256) - colorTab.length;
  526. for (var i = 0; i < n; i++) out.writeByte(0);
  527. };
  528.  
  529. var WriteShort = function WriteShort(pValue) {
  530. out.writeByte(pValue & 0xFF);
  531. out.writeByte((pValue >> 8) & 0xFF);
  532. };
  533.  
  534. /**
  535. * Encodes and writes pixel data
  536. */
  537.  
  538. var writePixels = function writePixels() {
  539. var myencoder = new LZWEncoder(width, height, indexedPixels, colorDepth);
  540. myencoder.encode(out);
  541. };
  542.  
  543. /**
  544. * Retrieves the GIF stream
  545. */
  546.  
  547. var stream = exports.stream = function stream() {
  548. return out;
  549. };
  550.  
  551. var setProperties = exports.setProperties = function setProperties(has_start, is_first) {
  552. started = has_start;
  553. firstFrame = is_first;
  554. };
  555.  
  556. return exports;
  557.  
  558. };