utils.js 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  1. /**
  2. * @copyright Shachaf Ben-Kiki and the Converse.js contributors
  3. * @description
  4. * Started as a fork of Shachaf Ben-Kiki's jsgif library
  5. * https://github.com/shachaf/jsgif
  6. * @license MIT License
  7. */
  8. function bitsToNum (ba) {
  9. return ba.reduce(function (s, n) {
  10. return s * 2 + n;
  11. }, 0);
  12. }
  13. function byteToBitArr (bite) {
  14. const a = [];
  15. for (let i = 7; i >= 0; i--) {
  16. a.push( !! (bite & (1 << i)));
  17. }
  18. return a;
  19. }
  20. function lzwDecode (minCodeSize, data) {
  21. // TODO: Now that the GIF parser is a bit different, maybe this should get an array of bytes instead of a String?
  22. let pos = 0; // Maybe this streaming thing should be merged with the Stream?
  23. function readCode (size) {
  24. let code = 0;
  25. for (let i = 0; i < size; i++) {
  26. if (data.charCodeAt(pos >> 3) & (1 << (pos & 7))) {
  27. code |= 1 << i;
  28. }
  29. pos++;
  30. }
  31. return code;
  32. }
  33. const output = [];
  34. const clearCode = 1 << minCodeSize;
  35. const eoiCode = clearCode + 1;
  36. let codeSize = minCodeSize + 1;
  37. let dict = [];
  38. const clear = function () {
  39. dict = [];
  40. codeSize = minCodeSize + 1;
  41. for (let i = 0; i < clearCode; i++) {
  42. dict[i] = [i];
  43. }
  44. dict[clearCode] = [];
  45. dict[eoiCode] = null;
  46. };
  47. let code;
  48. let last;
  49. while (true) { // eslint-disable-line no-constant-condition
  50. last = code;
  51. code = readCode(codeSize);
  52. if (code === clearCode) {
  53. clear();
  54. continue;
  55. }
  56. if (code === eoiCode) break;
  57. if (code < dict.length) {
  58. if (last !== clearCode) {
  59. dict.push(dict[last].concat(dict[code][0]));
  60. }
  61. }
  62. else {
  63. if (code !== dict.length) throw new Error('Invalid LZW code.');
  64. dict.push(dict[last].concat(dict[last][0]));
  65. }
  66. output.push.apply(output, dict[code]);
  67. if (dict.length === (1 << codeSize) && codeSize < 12) {
  68. // If we're at the last code and codeSize is 12, the next code will be a clearCode, and it'll be 12 bits long.
  69. codeSize++;
  70. }
  71. }
  72. // I don't know if this is technically an error, but some GIFs do it.
  73. //if (Math.ceil(pos / 8) !== data.length) throw new Error('Extraneous LZW bytes.');
  74. return output;
  75. }
  76. function readSubBlocks (st) {
  77. let size, data;
  78. data = '';
  79. do {
  80. size = st.readByte();
  81. data += st.read(size);
  82. } while (size !== 0);
  83. return data;
  84. }
  85. /**
  86. * Parses GIF image color table information
  87. * @param { Stream } st
  88. * @param { Number } entries
  89. */
  90. function parseCT (st, entries) { // Each entry is 3 bytes, for RGB.
  91. const ct = [];
  92. for (let i = 0; i < entries; i++) {
  93. ct.push(st.readBytes(3));
  94. }
  95. return ct;
  96. }
  97. /**
  98. * Parses GIF image information
  99. * @param { Stream } st
  100. * @param { ByteStream } img
  101. * @param { Function } [callback]
  102. */
  103. function parseImg (st, img, callback) {
  104. function deinterlace (pixels, width) {
  105. // Of course this defeats the purpose of interlacing. And it's *probably*
  106. // the least efficient way it's ever been implemented. But nevertheless...
  107. const newPixels = new Array(pixels.length);
  108. const rows = pixels.length / width;
  109. function cpRow (toRow, fromRow) {
  110. const fromPixels = pixels.slice(fromRow * width, (fromRow + 1) * width);
  111. newPixels.splice.apply(newPixels, [toRow * width, width].concat(fromPixels));
  112. }
  113. // See appendix E.
  114. const offsets = [0, 4, 2, 1];
  115. const steps = [8, 8, 4, 2];
  116. let fromRow = 0;
  117. for (let pass = 0; pass < 4; pass++) {
  118. for (let toRow = offsets[pass]; toRow < rows; toRow += steps[pass]) {
  119. cpRow(toRow, fromRow)
  120. fromRow++;
  121. }
  122. }
  123. return newPixels;
  124. }
  125. img.leftPos = st.readUnsigned();
  126. img.topPos = st.readUnsigned();
  127. img.width = st.readUnsigned();
  128. img.height = st.readUnsigned();
  129. const bits = byteToBitArr(st.readByte());
  130. img.lctFlag = bits.shift();
  131. img.interlaced = bits.shift();
  132. img.sorted = bits.shift();
  133. img.reserved = bits.splice(0, 2);
  134. img.lctSize = bitsToNum(bits.splice(0, 3));
  135. if (img.lctFlag) {
  136. img.lct = parseCT(st, 1 << (img.lctSize + 1));
  137. }
  138. img.lzwMinCodeSize = st.readByte();
  139. const lzwData = readSubBlocks(st);
  140. img.pixels = lzwDecode(img.lzwMinCodeSize, lzwData);
  141. if (img.interlaced) { // Move
  142. img.pixels = deinterlace(img.pixels, img.width);
  143. }
  144. callback?.(img);
  145. }
  146. /**
  147. * Parses GIF header information
  148. * @param { Stream } st
  149. * @param { Function } [callback]
  150. */
  151. function parseHeader (st, callback) {
  152. const hdr = {};
  153. hdr.sig = st.read(3);
  154. hdr.ver = st.read(3);
  155. if (hdr.sig !== 'GIF') {
  156. throw new Error('Not a GIF file.');
  157. }
  158. hdr.width = st.readUnsigned();
  159. hdr.height = st.readUnsigned();
  160. const bits = byteToBitArr(st.readByte());
  161. hdr.gctFlag = bits.shift();
  162. hdr.colorRes = bitsToNum(bits.splice(0, 3));
  163. hdr.sorted = bits.shift();
  164. hdr.gctSize = bitsToNum(bits.splice(0, 3));
  165. hdr.bgColor = st.readByte();
  166. hdr.pixelAspectRatio = st.readByte(); // if not 0, aspectRatio = (pixelAspectRatio + 15) / 64
  167. if (hdr.gctFlag) {
  168. hdr.gct = parseCT(st, 1 << (hdr.gctSize + 1));
  169. }
  170. callback?.(hdr);
  171. }
  172. function parseExt (st, block, handler) {
  173. function parseGCExt (block) {
  174. st.readByte(); // blocksize, always 4
  175. const bits = byteToBitArr(st.readByte());
  176. block.reserved = bits.splice(0, 3); // Reserved; should be 000.
  177. block.disposalMethod = bitsToNum(bits.splice(0, 3));
  178. block.userInput = bits.shift();
  179. block.transparencyGiven = bits.shift();
  180. block.delayTime = st.readUnsigned();
  181. block.transparencyIndex = st.readByte();
  182. block.terminator = st.readByte();
  183. handler?.gce(block);
  184. }
  185. function parseComExt (block) {
  186. block.comment = readSubBlocks(st);
  187. handler.com && handler.com(block);
  188. }
  189. function parsePTExt (block) {
  190. // No one *ever* uses this. If you use it, deal with parsing it yourself.
  191. st.readByte(); // blocksize, always 12
  192. block.ptHeader = st.readBytes(12);
  193. block.ptData = readSubBlocks(st);
  194. handler.pte && handler.pte(block);
  195. }
  196. function parseAppExt (block) {
  197. function parseNetscapeExt (block) {
  198. st.readByte(); // blocksize, always 3
  199. block.unknown = st.readByte(); // ??? Always 1? What is this?
  200. block.iterations = st.readUnsigned();
  201. block.terminator = st.readByte();
  202. handler.app && handler.app.NETSCAPE && handler.app.NETSCAPE(block);
  203. }
  204. function parseUnknownAppExt (block) {
  205. block.appData = readSubBlocks(st);
  206. // FIXME: This won't work if a handler wants to match on any identifier.
  207. handler.app && handler.app[block.identifier] && handler.app[block.identifier](block);
  208. }
  209. st.readByte(); // blocksize, always 11
  210. block.identifier = st.read(8);
  211. block.authCode = st.read(3);
  212. switch (block.identifier) {
  213. case 'NETSCAPE':
  214. parseNetscapeExt(block);
  215. break;
  216. default:
  217. parseUnknownAppExt(block);
  218. break;
  219. }
  220. }
  221. function parseUnknownExt (block) {
  222. block.data = readSubBlocks(st);
  223. handler.unknown && handler.unknown(block);
  224. }
  225. block.label = st.readByte();
  226. switch (block.label) {
  227. case 0xF9:
  228. block.extType = 'gce';
  229. parseGCExt(block);
  230. break;
  231. case 0xFE:
  232. block.extType = 'com';
  233. parseComExt(block);
  234. break;
  235. case 0x01:
  236. block.extType = 'pte';
  237. parsePTExt(block);
  238. break;
  239. case 0xFF:
  240. block.extType = 'app';
  241. parseAppExt(block);
  242. break;
  243. default:
  244. block.extType = 'unknown';
  245. parseUnknownExt(block);
  246. break;
  247. }
  248. }
  249. /**
  250. * @param { Stream } st
  251. * @param { GIFParserHandlers } handler
  252. */
  253. function parseBlock (st, handler) {
  254. const block = {}
  255. block.sentinel = st.readByte();
  256. switch (String.fromCharCode(block.sentinel)) { // For ease of matching
  257. case '!':
  258. block.type = 'ext';
  259. parseExt(st, block, handler);
  260. break;
  261. case ',':
  262. block.type = 'img';
  263. parseImg(st, block, handler?.img);
  264. break;
  265. case ';':
  266. block.type = 'eof';
  267. handler?.eof(block);
  268. break;
  269. default:
  270. throw new Error('Unknown block: 0x' + block.sentinel.toString(16)); // TODO: Pad this with a 0.
  271. }
  272. if (block.type !== 'eof') setTimeout(() => parseBlock(st, handler), 0);
  273. }
  274. /**
  275. * Takes a Stream and parses it for GIF data, calling the relevant handler
  276. * methods on the passed in `handler` object.
  277. * @param { Stream } st
  278. * @param { GIFParserHandlers } handler
  279. */
  280. export function parseGIF (st, handler={}) {
  281. parseHeader(st, handler?.hdr);
  282. setTimeout(() => parseBlock(st, handler), 0);
  283. }