Greasy Animation

Animation tools for Sketchful.io

目前為 2020-08-04 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name Greasy Animation
  3. // @namespace https://greasyfork.org/users/281093
  4. // @match https://sketchful.io/
  5. // @grant none
  6. // @version 0.3
  7. // @author Bell
  8. // @license MIT
  9. // @copyright 2020, Bell (https://openuserjs.org/users/Bell)
  10. // @description Animation tools for Sketchful.io
  11. // ==/UserScript==
  12. /* jshint esversion: 6 */
  13.  
  14. // Libraries
  15. LZWEncoder = function() {
  16.  
  17. var exports = {};
  18. var EOF = -1;
  19. var imgW;
  20. var imgH;
  21. var pixAry;
  22. var initCodeSize;
  23. var remaining;
  24. var curPixel;
  25.  
  26. // GIFCOMPR.C - GIF Image compression routines
  27. // Lempel-Ziv compression based on 'compress'. GIF modifications by
  28. // David Rowley (mgardi@watdcsu.waterloo.edu)
  29. // General DEFINEs
  30.  
  31. var BITS = 12;
  32. var HSIZE = 5003; // 80% occupancy
  33.  
  34. // GIF Image compression - modified 'compress'
  35. // Based on: compress.c - File compression ala IEEE Computer, June 1984.
  36. // By Authors: Spencer W. Thomas (decvax!harpo!utah-cs!utah-gr!thomas)
  37. // Jim McKie (decvax!mcvax!jim)
  38. // Steve Davies (decvax!vax135!petsd!peora!srd)
  39. // Ken Turkowski (decvax!decwrl!turtlevax!ken)
  40. // James A. Woods (decvax!ihnp4!ames!jaw)
  41. // Joe Orost (decvax!vax135!petsd!joe)
  42.  
  43. var n_bits; // number of bits/code
  44. var maxbits = BITS; // user settable max # bits/code
  45. var maxcode; // maximum code, given n_bits
  46. var maxmaxcode = 1 << BITS; // should NEVER generate this code
  47. var htab = [];
  48. var codetab = [];
  49. var hsize = HSIZE; // for dynamic table sizing
  50. var free_ent = 0; // first unused entry
  51.  
  52. // block compression parameters -- after all codes are used up,
  53. // and compression rate changes, start over.
  54.  
  55. var clear_flg = false;
  56.  
  57. // Algorithm: use open addressing double hashing (no chaining) on the
  58. // prefix code / next character combination. We do a variant of Knuth's
  59. // algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime
  60. // secondary probe. Here, the modular division first probe is gives way
  61. // to a faster exclusive-or manipulation. Also do block compression with
  62. // an adaptive reset, whereby the code table is cleared when the compression
  63. // ratio decreases, but after the table fills. The variable-length output
  64. // codes are re-sized at this point, and a special CLEAR code is generated
  65. // for the decompressor. Late addition: construct the table according to
  66. // file size for noticeable speed improvement on small files. Please direct
  67. // questions about this implementation to ames!jaw.
  68.  
  69. var g_init_bits;
  70. var ClearCode;
  71. var EOFCode;
  72.  
  73. // output
  74. // Output the given code.
  75. // Inputs:
  76. // code: A n_bits-bit integer. If == -1, then EOF. This assumes
  77. // that n_bits =< wordsize - 1.
  78. // Outputs:
  79. // Outputs code to the file.
  80. // Assumptions:
  81. // Chars are 8 bits long.
  82. // Algorithm:
  83. // Maintain a BITS character long buffer (so that 8 codes will
  84. // fit in it exactly). Use the VAX insv instruction to insert each
  85. // code in turn. When the buffer fills up empty it and start over.
  86.  
  87. var cur_accum = 0;
  88. var cur_bits = 0;
  89. var masks = [0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF, 0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF];
  90.  
  91. // Number of characters so far in this 'packet'
  92. var a_count;
  93.  
  94. // Define the storage for the packet accumulator
  95. var accum = [];
  96.  
  97. var LZWEncoder = exports.LZWEncoder = function LZWEncoder(width, height, pixels, color_depth) {
  98. imgW = width;
  99. imgH = height;
  100. pixAry = pixels;
  101. initCodeSize = Math.max(2, color_depth);
  102. };
  103.  
  104. // Add a character to the end of the current packet, and if it is 254
  105. // characters, flush the packet to disk.
  106. var char_out = function char_out(c, outs) {
  107. accum[a_count++] = c;
  108. if (a_count >= 254) flush_char(outs);
  109. };
  110.  
  111. // Clear out the hash table
  112. // table clear for block compress
  113.  
  114. var cl_block = function cl_block(outs) {
  115. cl_hash(hsize);
  116. free_ent = ClearCode + 2;
  117. clear_flg = true;
  118. output(ClearCode, outs);
  119. };
  120.  
  121. // reset code table
  122. var cl_hash = function cl_hash(hsize) {
  123. for (var i = 0; i < hsize; ++i) htab[i] = -1;
  124. };
  125.  
  126. var compress = exports.compress = function compress(init_bits, outs) {
  127.  
  128. var fcode;
  129. var i; /* = 0 */
  130. var c;
  131. var ent;
  132. var disp;
  133. var hsize_reg;
  134. var hshift;
  135.  
  136. // Set up the globals: g_init_bits - initial number of bits
  137. g_init_bits = init_bits;
  138.  
  139. // Set up the necessary values
  140. clear_flg = false;
  141. n_bits = g_init_bits;
  142. maxcode = MAXCODE(n_bits);
  143.  
  144. ClearCode = 1 << (init_bits - 1);
  145. EOFCode = ClearCode + 1;
  146. free_ent = ClearCode + 2;
  147.  
  148. a_count = 0; // clear packet
  149.  
  150. ent = nextPixel();
  151.  
  152. hshift = 0;
  153. for (fcode = hsize; fcode < 65536; fcode *= 2)
  154. ++hshift;
  155. hshift = 8 - hshift; // set hash code range bound
  156.  
  157. hsize_reg = hsize;
  158. cl_hash(hsize_reg); // clear hash table
  159.  
  160. output(ClearCode, outs);
  161.  
  162. outer_loop: while ((c = nextPixel()) != EOF) {
  163. fcode = (c << maxbits) + ent;
  164. i = (c << hshift) ^ ent; // xor hashing
  165.  
  166. if (htab[i] == fcode) {
  167. ent = codetab[i];
  168. continue;
  169. }
  170.  
  171. else if (htab[i] >= 0) { // non-empty slot
  172.  
  173. disp = hsize_reg - i; // secondary hash (after G. Knott)
  174. if (i === 0) disp = 1;
  175.  
  176. do {
  177. if ((i -= disp) < 0)
  178. i += hsize_reg;
  179.  
  180. if (htab[i] == fcode) {
  181. ent = codetab[i];
  182. continue outer_loop;
  183. }
  184. } while (htab[i] >= 0);
  185. }
  186.  
  187. output(ent, outs);
  188. ent = c;
  189. if (free_ent < maxmaxcode) {
  190. codetab[i] = free_ent++; // code -> hashtable
  191. htab[i] = fcode;
  192. }
  193. else cl_block(outs);
  194. }
  195.  
  196. // Put out the final code.
  197. output(ent, outs);
  198. output(EOFCode, outs);
  199. };
  200.  
  201. // ----------------------------------------------------------------------------
  202. var encode = exports.encode = function encode(os) {
  203. os.writeByte(initCodeSize); // write "initial code size" byte
  204. remaining = imgW * imgH; // reset navigation variables
  205. curPixel = 0;
  206. compress(initCodeSize + 1, os); // compress and write the pixel data
  207. os.writeByte(0); // write block terminator
  208. };
  209.  
  210. // Flush the packet to disk, and reset the accumulator
  211. var flush_char = function flush_char(outs) {
  212. if (a_count > 0) {
  213. outs.writeByte(a_count);
  214. outs.writeBytes(accum, 0, a_count);
  215. a_count = 0;
  216. }
  217. };
  218.  
  219. var MAXCODE = function MAXCODE(n_bits) {
  220. return (1 << n_bits) - 1;
  221. };
  222.  
  223. // ----------------------------------------------------------------------------
  224. // Return the next pixel from the image
  225. // ----------------------------------------------------------------------------
  226.  
  227. var nextPixel = function nextPixel() {
  228. if (remaining === 0) return EOF;
  229. --remaining;
  230. var pix = pixAry[curPixel++];
  231. return pix & 0xff;
  232. };
  233.  
  234. var output = function output(code, outs) {
  235.  
  236. cur_accum &= masks[cur_bits];
  237.  
  238. if (cur_bits > 0) cur_accum |= (code << cur_bits);
  239. else cur_accum = code;
  240.  
  241. cur_bits += n_bits;
  242.  
  243. while (cur_bits >= 8) {
  244. char_out((cur_accum & 0xff), outs);
  245. cur_accum >>= 8;
  246. cur_bits -= 8;
  247. }
  248.  
  249. // If the next entry is going to be too big for the code size,
  250. // then increase it, if possible.
  251.  
  252. if (free_ent > maxcode || clear_flg) {
  253.  
  254. if (clear_flg) {
  255.  
  256. maxcode = MAXCODE(n_bits = g_init_bits);
  257. clear_flg = false;
  258.  
  259. } else {
  260.  
  261. ++n_bits;
  262. if (n_bits == maxbits) maxcode = maxmaxcode;
  263. else maxcode = MAXCODE(n_bits);
  264. }
  265. }
  266.  
  267. if (code == EOFCode) {
  268.  
  269. // At EOF, write the rest of the buffer.
  270. while (cur_bits > 0) {
  271. char_out((cur_accum & 0xff), outs);
  272. cur_accum >>= 8;
  273. cur_bits -= 8;
  274. }
  275.  
  276. flush_char(outs);
  277. }
  278. };
  279.  
  280. LZWEncoder.apply(this, arguments);
  281. return exports;
  282. };
  283.  
  284. ;/*
  285. * NeuQuant Neural-Net Quantization Algorithm
  286. * ------------------------------------------
  287. *
  288. * Copyright (c) 1994 Anthony Dekker
  289. *
  290. * NEUQUANT Neural-Net quantization algorithm by Anthony Dekker, 1994. See
  291. * "Kohonen neural networks for optimal colour quantization" in "Network:
  292. * Computation in Neural Systems" Vol. 5 (1994) pp 351-367. for a discussion of
  293. * the algorithm.
  294. *
  295. * Any party obtaining a copy of these files from the author, directly or
  296. * indirectly, is granted, free of charge, a full and unrestricted irrevocable,
  297. * world-wide, paid up, royalty-free, nonexclusive right and license to deal in
  298. * this software and documentation files (the "Software"), including without
  299. * limitation the rights to use, copy, modify, merge, publish, distribute,
  300. * sublicense, and/or sell copies of the Software, and to permit persons who
  301. * receive copies from any such party to do so, with the only requirement being
  302. * that this copyright notice remain intact.
  303. */
  304.  
  305. /*
  306. * This class handles Neural-Net quantization algorithm
  307. * @author Kevin Weiner (original Java version - kweiner@fmsware.com)
  308. * @author Thibault Imbert (AS3 version - bytearray.org)
  309. * @author Kevin Kwok (JavaScript version - https://github.com/antimatter15/jsgif)
  310. * @version 0.1 AS3 implementation
  311. */
  312.  
  313. NeuQuant = function() {
  314.  
  315. var exports = {};
  316. var netsize = 256; /* number of colours used */
  317.  
  318. /* four primes near 500 - assume no image has a length so large */
  319. /* that it is divisible by all four primes */
  320.  
  321. var prime1 = 499;
  322. var prime2 = 491;
  323. var prime3 = 487;
  324. var prime4 = 503;
  325. var minpicturebytes = (3 * prime4); /* minimum size for input image */
  326.  
  327. /*
  328. * Program Skeleton ---------------- [select samplefac in range 1..30] [read
  329. * image from input file] pic = (unsigned char*) malloc(3*width*height);
  330. * initnet(pic,3*width*height,samplefac); learn(); unbiasnet(); [write output
  331. * image header, using writecolourmap(f)] inxbuild(); write output image using
  332. * inxsearch(b,g,r)
  333. */
  334.  
  335. /*
  336. * Network Definitions -------------------
  337. */
  338.  
  339. var maxnetpos = (netsize - 1);
  340. var netbiasshift = 4; /* bias for colour values */
  341. var ncycles = 100; /* no. of learning cycles */
  342.  
  343. /* defs for freq and bias */
  344. var intbiasshift = 16; /* bias for fractions */
  345. var intbias = (1 << intbiasshift);
  346. var gammashift = 10; /* gamma = 1024 */
  347. var gamma = (1 << gammashift);
  348. var betashift = 10;
  349. var beta = (intbias >> betashift); /* beta = 1/1024 */
  350. var betagamma = (intbias << (gammashift - betashift));
  351.  
  352. /* defs for decreasing radius factor */
  353. var initrad = (netsize >> 3); /* for 256 cols, radius starts */
  354. var radiusbiasshift = 6; /* at 32.0 biased by 6 bits */
  355. var radiusbias = (1 << radiusbiasshift);
  356. var initradius = (initrad * radiusbias); /* and decreases by a */
  357. var radiusdec = 30; /* factor of 1/30 each cycle */
  358.  
  359. /* defs for decreasing alpha factor */
  360. var alphabiasshift = 10; /* alpha starts at 1.0 */
  361. var initalpha = (1 << alphabiasshift);
  362. var alphadec; /* biased by 10 bits */
  363.  
  364. /* radbias and alpharadbias used for radpower calculation */
  365. var radbiasshift = 8;
  366. var radbias = (1 << radbiasshift);
  367. var alpharadbshift = (alphabiasshift + radbiasshift);
  368. var alpharadbias = (1 << alpharadbshift);
  369.  
  370. /*
  371. * Types and Global Variables --------------------------
  372. */
  373.  
  374. var thepicture; /* the input image itself */
  375. var lengthcount; /* lengthcount = H*W*3 */
  376. var samplefac; /* sampling factor 1..30 */
  377.  
  378. // typedef int pixel[4]; /* BGRc */
  379. var network; /* the network itself - [netsize][4] */
  380. var netindex = [];
  381.  
  382. /* for network lookup - really 256 */
  383. var bias = [];
  384.  
  385. /* bias and freq arrays for learning */
  386. var freq = [];
  387. var radpower = [];
  388.  
  389. var NeuQuant = exports.NeuQuant = function NeuQuant(thepic, len, sample) {
  390.  
  391. var i;
  392. var p;
  393.  
  394. thepicture = thepic;
  395. lengthcount = len;
  396. samplefac = sample;
  397.  
  398. network = new Array(netsize);
  399.  
  400. for (i = 0; i < netsize; i++) {
  401.  
  402. network[i] = new Array(4);
  403. p = network[i];
  404. p[0] = p[1] = p[2] = (i << (netbiasshift + 8)) / netsize;
  405. freq[i] = intbias / netsize; /* 1/netsize */
  406. bias[i] = 0;
  407. }
  408. };
  409.  
  410. var colorMap = function colorMap() {
  411.  
  412. var map = [];
  413. var index = new Array(netsize);
  414.  
  415. for (var i = 0; i < netsize; i++)
  416. index[network[i][3]] = i;
  417.  
  418. var k = 0;
  419. for (var l = 0; l < netsize; l++) {
  420. var j = index[l];
  421. map[k++] = (network[j][0]);
  422. map[k++] = (network[j][1]);
  423. map[k++] = (network[j][2]);
  424. }
  425.  
  426. return map;
  427. };
  428.  
  429. /*
  430. * Insertion sort of network and building of netindex[0..255] (to do after
  431. * unbias)
  432. * -------------------------------------------------------------------------------
  433. */
  434.  
  435. var inxbuild = function inxbuild() {
  436.  
  437. var i;
  438. var j;
  439. var smallpos;
  440. var smallval;
  441. var p;
  442. var q;
  443. var previouscol;
  444. var startpos;
  445.  
  446. previouscol = 0;
  447. startpos = 0;
  448. for (i = 0; i < netsize; i++) {
  449.  
  450. p = network[i];
  451. smallpos = i;
  452. smallval = p[1]; /* index on g */
  453.  
  454. /* find smallest in i..netsize-1 */
  455. for (j = i + 1; j < netsize; j++) {
  456.  
  457. q = network[j];
  458. if (q[1] < smallval) { /* index on g */
  459. smallpos = j;
  460. smallval = q[1]; /* index on g */
  461. }
  462. }
  463. q = network[smallpos];
  464.  
  465. /* swap p (i) and q (smallpos) entries */
  466. if (i != smallpos) {
  467. j = q[0];
  468. q[0] = p[0];
  469. p[0] = j;
  470. j = q[1];
  471. q[1] = p[1];
  472. p[1] = j;
  473. j = q[2];
  474. q[2] = p[2];
  475. p[2] = j;
  476. j = q[3];
  477. q[3] = p[3];
  478. p[3] = j;
  479. }
  480.  
  481. /* smallval entry is now in position i */
  482.  
  483. if (smallval != previouscol) {
  484.  
  485. netindex[previouscol] = (startpos + i) >> 1;
  486.  
  487. for (j = previouscol + 1; j < smallval; j++) netindex[j] = i;
  488.  
  489. previouscol = smallval;
  490. startpos = i;
  491. }
  492. }
  493.  
  494. netindex[previouscol] = (startpos + maxnetpos) >> 1;
  495. for (j = previouscol + 1; j < 256; j++) netindex[j] = maxnetpos; /* really 256 */
  496. };
  497.  
  498. /*
  499. * Main Learning Loop ------------------
  500. */
  501.  
  502. var learn = function learn() {
  503.  
  504. var i;
  505. var j;
  506. var b;
  507. var g;
  508. var r;
  509. var radius;
  510. var rad;
  511. var alpha;
  512. var step;
  513. var delta;
  514. var samplepixels;
  515. var p;
  516. var pix;
  517. var lim;
  518.  
  519. if (lengthcount < minpicturebytes) samplefac = 1;
  520.  
  521. alphadec = 30 + ((samplefac - 1) / 3);
  522. p = thepicture;
  523. pix = 0;
  524. lim = lengthcount;
  525. samplepixels = lengthcount / (3 * samplefac);
  526. delta = (samplepixels / ncycles) | 0;
  527. alpha = initalpha;
  528. radius = initradius;
  529.  
  530. rad = radius >> radiusbiasshift;
  531. if (rad <= 1) rad = 0;
  532.  
  533. for (i = 0; i < rad; i++) radpower[i] = alpha * (((rad * rad - i * i) * radbias) / (rad * rad));
  534.  
  535. if (lengthcount < minpicturebytes) step = 3;
  536.  
  537. else if ((lengthcount % prime1) !== 0) step = 3 * prime1;
  538.  
  539. else {
  540.  
  541. if ((lengthcount % prime2) !== 0) step = 3 * prime2;
  542. else {
  543. if ((lengthcount % prime3) !== 0) step = 3 * prime3;
  544. else step = 3 * prime4;
  545. }
  546. }
  547.  
  548. i = 0;
  549. while (i < samplepixels) {
  550.  
  551. b = (p[pix + 0] & 0xff) << netbiasshift;
  552. g = (p[pix + 1] & 0xff) << netbiasshift;
  553. r = (p[pix + 2] & 0xff) << netbiasshift;
  554. j = contest(b, g, r);
  555.  
  556. altersingle(alpha, j, b, g, r);
  557. if (rad !== 0) alterneigh(rad, j, b, g, r); /* alter neighbours */
  558.  
  559. pix += step;
  560. if (pix >= lim) pix -= lengthcount;
  561.  
  562. i++;
  563.  
  564. if (delta === 0) delta = 1;
  565.  
  566. if (i % delta === 0) {
  567. alpha -= alpha / alphadec;
  568. radius -= radius / radiusdec;
  569. rad = radius >> radiusbiasshift;
  570.  
  571. if (rad <= 1) rad = 0;
  572.  
  573. for (j = 0; j < rad; j++) radpower[j] = alpha * (((rad * rad - j * j) * radbias) / (rad * rad));
  574. }
  575. }
  576. };
  577.  
  578. /*
  579. ** Search for BGR values 0..255 (after net is unbiased) and return colour
  580. * index
  581. * ----------------------------------------------------------------------------
  582. */
  583.  
  584. var map = exports.map = function map(b, g, r) {
  585.  
  586. var i;
  587. var j;
  588. var dist;
  589. var a;
  590. var bestd;
  591. var p;
  592. var best;
  593.  
  594. bestd = 1000; /* biggest possible dist is 256*3 */
  595. best = -1;
  596. i = netindex[g]; /* index on g */
  597. j = i - 1; /* start at netindex[g] and work outwards */
  598.  
  599. while ((i < netsize) || (j >= 0)) {
  600.  
  601. if (i < netsize) {
  602. p = network[i];
  603. dist = p[1] - g; /* inx key */
  604.  
  605. if (dist >= bestd) i = netsize; /* stop iter */
  606.  
  607. else {
  608.  
  609. i++;
  610. if (dist < 0) dist = -dist;
  611. a = p[0] - b;
  612. if (a < 0) a = -a;
  613. dist += a;
  614.  
  615. if (dist < bestd) {
  616. a = p[2] - r;
  617. if (a < 0) a = -a;
  618. dist += a;
  619.  
  620. if (dist < bestd) {
  621. bestd = dist;
  622. best = p[3];
  623. }
  624. }
  625. }
  626. }
  627.  
  628. if (j >= 0) {
  629.  
  630. p = network[j];
  631. dist = g - p[1]; /* inx key - reverse dif */
  632.  
  633. if (dist >= bestd) j = -1; /* stop iter */
  634.  
  635. else {
  636.  
  637. j--;
  638. if (dist < 0) dist = -dist;
  639. a = p[0] - b;
  640. if (a < 0) a = -a;
  641. dist += a;
  642.  
  643. if (dist < bestd) {
  644. a = p[2] - r;
  645. if (a < 0) a = -a;
  646. dist += a;
  647. if (dist < bestd) {
  648. bestd = dist;
  649. best = p[3];
  650. }
  651. }
  652. }
  653. }
  654. }
  655.  
  656. return (best);
  657. };
  658.  
  659. var process = exports.process = function process() {
  660. learn();
  661. unbiasnet();
  662. inxbuild();
  663. return colorMap();
  664. };
  665.  
  666. /*
  667. * Unbias network to give byte values 0..255 and record position i to prepare
  668. * for sort
  669. * -----------------------------------------------------------------------------------
  670. */
  671.  
  672. var unbiasnet = function unbiasnet() {
  673.  
  674. var i;
  675. var j;
  676.  
  677. for (i = 0; i < netsize; i++) {
  678. network[i][0] >>= netbiasshift;
  679. network[i][1] >>= netbiasshift;
  680. network[i][2] >>= netbiasshift;
  681. network[i][3] = i; /* record colour no */
  682. }
  683. };
  684.  
  685. /*
  686. * Move adjacent neurons by precomputed alpha*(1-((i-j)^2/[r]^2)) in
  687. * radpower[|i-j|]
  688. * ---------------------------------------------------------------------------------
  689. */
  690.  
  691. var alterneigh = function alterneigh(rad, i, b, g, r) {
  692.  
  693. var j;
  694. var k;
  695. var lo;
  696. var hi;
  697. var a;
  698. var m;
  699. var p;
  700.  
  701. lo = i - rad;
  702. if (lo < -1) lo = -1;
  703.  
  704. hi = i + rad;
  705. if (hi > netsize) hi = netsize;
  706.  
  707. j = i + 1;
  708. k = i - 1;
  709. m = 1;
  710.  
  711. while ((j < hi) || (k > lo)) {
  712. a = radpower[m++];
  713.  
  714. if (j < hi) {
  715. p = network[j++];
  716.  
  717. try {
  718. p[0] -= (a * (p[0] - b)) / alpharadbias;
  719. p[1] -= (a * (p[1] - g)) / alpharadbias;
  720. p[2] -= (a * (p[2] - r)) / alpharadbias;
  721. } catch (e) {} // prevents 1.3 miscompilation
  722. }
  723.  
  724. if (k > lo) {
  725. p = network[k--];
  726.  
  727. try {
  728. p[0] -= (a * (p[0] - b)) / alpharadbias;
  729. p[1] -= (a * (p[1] - g)) / alpharadbias;
  730. p[2] -= (a * (p[2] - r)) / alpharadbias;
  731. } catch (e) {}
  732. }
  733. }
  734. };
  735.  
  736. /*
  737. * Move neuron i towards biased (b,g,r) by factor alpha
  738. * ----------------------------------------------------
  739. */
  740.  
  741. var altersingle = function altersingle(alpha, i, b, g, r) {
  742.  
  743. /* alter hit neuron */
  744. var n = network[i];
  745. n[0] -= (alpha * (n[0] - b)) / initalpha;
  746. n[1] -= (alpha * (n[1] - g)) / initalpha;
  747. n[2] -= (alpha * (n[2] - r)) / initalpha;
  748. };
  749.  
  750. /*
  751. * Search for biased BGR values ----------------------------
  752. */
  753.  
  754. var contest = function contest(b, g, r) {
  755.  
  756. /* finds closest neuron (min dist) and updates freq */
  757. /* finds best neuron (min dist-bias) and returns position */
  758. /* for frequently chosen neurons, freq[i] is high and bias[i] is negative */
  759. /* bias[i] = gamma*((1/netsize)-freq[i]) */
  760.  
  761. var i;
  762. var dist;
  763. var a;
  764. var biasdist;
  765. var betafreq;
  766. var bestpos;
  767. var bestbiaspos;
  768. var bestd;
  769. var bestbiasd;
  770. var n;
  771.  
  772. bestd = ~ (1 << 31);
  773. bestbiasd = bestd;
  774. bestpos = -1;
  775. bestbiaspos = bestpos;
  776.  
  777. for (i = 0; i < netsize; i++) {
  778. n = network[i];
  779. dist = n[0] - b;
  780. if (dist < 0) dist = -dist;
  781. a = n[1] - g;
  782. if (a < 0) a = -a;
  783. dist += a;
  784. a = n[2] - r;
  785. if (a < 0) a = -a;
  786. dist += a;
  787.  
  788. if (dist < bestd) {
  789. bestd = dist;
  790. bestpos = i;
  791. }
  792.  
  793. biasdist = dist - ((bias[i]) >> (intbiasshift - netbiasshift));
  794.  
  795. if (biasdist < bestbiasd) {
  796. bestbiasd = biasdist;
  797. bestbiaspos = i;
  798. }
  799.  
  800. betafreq = (freq[i] >> betashift);
  801. freq[i] -= betafreq;
  802. bias[i] += (betafreq << gammashift);
  803. }
  804.  
  805. freq[bestpos] += beta;
  806. bias[bestpos] -= betagamma;
  807. return (bestbiaspos);
  808. };
  809.  
  810. NeuQuant.apply(this, arguments);
  811. return exports;
  812. };
  813.  
  814. ;/**
  815. * This class lets you encode animated GIF files
  816. * Base class : http://www.java2s.com/Code/Java/2D-Graphics-GUI/AnimatedGifEncoder.htm
  817. * @author Kevin Weiner (original Java version - kweiner@fmsware.com)
  818. * @author Thibault Imbert (AS3 version - bytearray.org)
  819. * @author Kevin Kwok (JavaScript version - https://github.com/antimatter15/jsgif)
  820. * @version 0.1 AS3 implementation
  821. */
  822.  
  823. GIFEncoder = function() {
  824.  
  825. for (var i = 0, chr = {}; i < 256; i++)
  826. chr[i] = String.fromCharCode(i);
  827.  
  828. function ByteArray() {
  829. this.bin = [];
  830. }
  831.  
  832. ByteArray.prototype.getData = function() {
  833. for (var v = '', l = this.bin.length, i = 0; i < l; i++)
  834. v += chr[this.bin[i]];
  835. return v;
  836. };
  837.  
  838. ByteArray.prototype.writeByte = function(val) {
  839. this.bin.push(val);
  840. };
  841.  
  842. ByteArray.prototype.writeUTFBytes = function(string) {
  843. for (var l = string.length, i = 0; i < l; i++)
  844. this.writeByte(string.charCodeAt(i));
  845. };
  846.  
  847. ByteArray.prototype.writeBytes = function(array, offset, length) {
  848. for (var l = length || array.length, i = offset || 0; i < l; i++)
  849. this.writeByte(array[i]);
  850. };
  851.  
  852. var exports = {};
  853. var width; // image size
  854. var height;
  855. var transparent = null; // transparent color if given
  856. var transIndex; // transparent index in color table
  857. var repeat = -1; // no repeat
  858. var delay = 0; // frame delay (hundredths)
  859. var started = false; // ready to output frames
  860. var out;
  861. var image; // current frame
  862. var pixels; // BGR byte array from frame
  863. var indexedPixels; // converted frame indexed to palette
  864. var colorDepth; // number of bit planes
  865. var colorTab; // RGB palette
  866. var usedEntry = []; // active palette entries
  867. var palSize = 7; // color table size (bits-1)
  868. var dispose = -1; // disposal code (-1 = use default)
  869. var closeStream = false; // close stream when finished
  870. var firstFrame = true;
  871. var sizeSet = false; // if false, get size from first frame
  872. var sample = 10; // default sample interval for quantizer
  873. var comment = "Generated by jsgif (https://github.com/antimatter15/jsgif/)"; // default comment for generated gif
  874.  
  875. /**
  876. * Sets the delay time between each frame, or changes it for subsequent frames
  877. * (applies to last frame added)
  878. * int delay time in milliseconds
  879. * @param ms
  880. */
  881.  
  882. var setDelay = exports.setDelay = function setDelay(ms) {
  883. delay = Math.round(ms / 10);
  884. };
  885.  
  886. /**
  887. * Sets the GIF frame disposal code for the last added frame and any
  888. *
  889. * subsequent frames. Default is 0 if no transparent color has been set,
  890. * otherwise 2.
  891. * @param code
  892. * int disposal code.
  893. */
  894.  
  895. var setDispose = exports.setDispose = function setDispose(code) {
  896. if (code >= 0) dispose = code;
  897. };
  898.  
  899. /**
  900. * Sets the number of times the set of GIF frames should be played. Default is
  901. * 1; 0 means play indefinitely. Must be invoked before the first image is
  902. * added.
  903. *
  904. * @param iter
  905. * int number of iterations.
  906. * @return
  907. */
  908.  
  909. var setRepeat = exports.setRepeat = function setRepeat(iter) {
  910. if (iter >= 0) repeat = iter;
  911. };
  912.  
  913. /**
  914. * Sets the transparent color for the last added frame and any subsequent
  915. * frames. Since all colors are subject to modification in the quantization
  916. * process, the color in the final palette for each frame closest to the given
  917. * color becomes the transparent color for that frame. May be set to null to
  918. * indicate no transparent color.
  919. * @param
  920. * Color to be treated as transparent on display.
  921. */
  922.  
  923. var setTransparent = exports.setTransparent = function setTransparent(c) {
  924. transparent = c;
  925. };
  926.  
  927.  
  928. /**
  929. * Sets the comment for the block comment
  930. * @param
  931. * string to be insterted as comment
  932. */
  933.  
  934. var setComment = exports.setComment = function setComment(c) {
  935. comment = c;
  936. };
  937.  
  938.  
  939.  
  940. /**
  941. * The addFrame method takes an incoming BitmapData object to create each frames
  942. * @param
  943. * BitmapData object to be treated as a GIF's frame
  944. */
  945.  
  946. var addFrame = exports.addFrame = function addFrame(im, is_imageData) {
  947.  
  948. if ((im === null) || !started || out === null) {
  949. throw new Error("Please call start method before calling addFrame");
  950. }
  951.  
  952. var ok = true;
  953.  
  954. try {
  955. if (!is_imageData) {
  956. image = im.getImageData(0, 0, im.canvas.width, im.canvas.height).data;
  957. if (!sizeSet) setSize(im.canvas.width, im.canvas.height);
  958. } else {
  959. if(im instanceof ImageData) {
  960. image = im.data;
  961. if(!sizeset || width!=im.width || height!=im.height) {
  962. setSize(im.width,im.height);
  963. } else {
  964. }
  965. } else if(im instanceof Uint8ClampedArray) {
  966. if(im.length==(width*height*4)) {
  967. image=im;
  968. } else {
  969. console.log("Please set the correct size: ImageData length mismatch");
  970. ok=false;
  971. }
  972. } else {
  973. console.log("Please provide correct input");
  974. ok=false;
  975. }
  976. }
  977. getImagePixels(); // convert to correct format if necessary
  978. analyzePixels(); // build color table & map pixels
  979.  
  980. if (firstFrame) {
  981. writeLSD(); // logical screen descriptior
  982. writePalette(); // global color table
  983. if (repeat >= 0) {
  984. // use NS app extension to indicate reps
  985. writeNetscapeExt();
  986. }
  987. }
  988.  
  989. writeGraphicCtrlExt(); // write graphic control extension
  990. if (comment !== '') {
  991. writeCommentExt(); // write comment extension
  992. }
  993. writeImageDesc(); // image descriptor
  994. if (!firstFrame) writePalette(); // local color table
  995. writePixels(); // encode and write pixel data
  996. firstFrame = false;
  997. } catch (e) {
  998. ok = false;
  999. }
  1000.  
  1001. return ok;
  1002. };
  1003. /**
  1004. * @description: Downloads the encoded gif with the given name
  1005. * No need of any conversion from the stream data (out) to base64
  1006. * Solves the issue of large file sizes when there are more frames
  1007. * and does not involve in creation of any temporary data in the process
  1008. * so no wastage of memory, and speeds up the process of downloading
  1009. * to just calling this function.
  1010. * @parameter {String} filename filename used for downloading the gif
  1011. */
  1012. var download = exports.download = function download(filename) {
  1013. if(out===null || closeStream==false) {
  1014. console.log("Please call start method and add frames and call finish method before calling download");
  1015. } else {
  1016. filename= filename !== undefined ? ( filename.endsWith(".gif")? filename: filename+".gif" ): "download.gif";
  1017. var templink = document.createElement("a");
  1018. templink.download=filename;
  1019. templink.href= URL.createObjectURL(new Blob([new Uint8Array(out.bin)], {type : "image/gif" } ));
  1020. templink.click();
  1021. }
  1022. }
  1023.  
  1024. /**
  1025. * Adds final trailer to the GIF stream, if you don't call the finish method
  1026. * the GIF stream will not be valid.
  1027. */
  1028.  
  1029. var finish = exports.finish = function finish() {
  1030.  
  1031. if (!started) return false;
  1032.  
  1033. var ok = true;
  1034. started = false;
  1035.  
  1036. try {
  1037. out.writeByte(0x3b); // gif trailer
  1038. closeStream=true;
  1039. } catch (e) {
  1040. ok = false;
  1041. }
  1042.  
  1043. return ok;
  1044. };
  1045.  
  1046. /**
  1047. * Resets some members so that a new stream can be started.
  1048. * This method is actually called by the start method
  1049. */
  1050.  
  1051. var reset = function reset() {
  1052.  
  1053. // reset for subsequent use
  1054. transIndex = 0;
  1055. image = null;
  1056. pixels = null;
  1057. indexedPixels = null;
  1058. colorTab = null;
  1059. closeStream = false;
  1060. firstFrame = true;
  1061. };
  1062.  
  1063. /**
  1064. * * Sets frame rate in frames per second. Equivalent to
  1065. * <code>setDelay(1000/fps)</code>.
  1066. * @param fps
  1067. * float frame rate (frames per second)
  1068. */
  1069.  
  1070. var setFrameRate = exports.setFrameRate = function setFrameRate(fps) {
  1071. if (fps != 0xf) delay = Math.round(100 / fps);
  1072. };
  1073.  
  1074. /**
  1075. * Sets quality of color quantization (conversion of images to the maximum 256
  1076. * colors allowed by the GIF specification). Lower values (minimum = 1)
  1077. * produce better colors, but slow processing significantly. 10 is the
  1078. * default, and produces good color mapping at reasonable speeds. Values
  1079. * greater than 20 do not yield significant improvements in speed.
  1080. * @param quality
  1081. * int greater than 0.
  1082. * @return
  1083. */
  1084.  
  1085. var setQuality = exports.setQuality = function setQuality(quality) {
  1086. if (quality < 1) quality = 1;
  1087. sample = quality;
  1088. };
  1089.  
  1090. /**
  1091. * Sets the GIF frame size. The default size is the size of the first frame
  1092. * added if this method is not invoked.
  1093. * @param w
  1094. * int frame width.
  1095. * @param h
  1096. * int frame width.
  1097. */
  1098.  
  1099. var setSize = exports.setSize = function setSize(w, h) {
  1100.  
  1101. if (started && !firstFrame) return;
  1102. width = w;
  1103. height = h;
  1104. if (width < 1) width = 320;
  1105. if (height < 1) height = 240;
  1106. sizeSet = true;
  1107. };
  1108.  
  1109. /**
  1110. * Initiates GIF file creation on the given stream.
  1111. * @param os
  1112. * OutputStream on which GIF images are written.
  1113. * @return false if initial write failed.
  1114. */
  1115.  
  1116. var start = exports.start = function start() {
  1117.  
  1118. reset();
  1119. var ok = true;
  1120. closeStream = false;
  1121. out = new ByteArray();
  1122. try {
  1123. out.writeUTFBytes("GIF89a"); // header
  1124. } catch (e) {
  1125. ok = false;
  1126. }
  1127.  
  1128. return started = ok;
  1129. };
  1130.  
  1131. var cont = exports.cont = function cont() {
  1132.  
  1133. reset();
  1134. var ok = true;
  1135. closeStream = false;
  1136. out = new ByteArray();
  1137.  
  1138. return started = ok;
  1139. };
  1140.  
  1141. /**
  1142. * Analyzes image colors and creates color map.
  1143. */
  1144.  
  1145. var analyzePixels = function analyzePixels() {
  1146.  
  1147. var len = pixels.length;
  1148. var nPix = len / 3;
  1149. indexedPixels = [];
  1150. var nq = new NeuQuant(pixels, len, sample);
  1151.  
  1152. // initialize quantizer
  1153. colorTab = nq.process(); // create reduced palette
  1154.  
  1155. // map image pixels to new palette
  1156. var k = 0;
  1157. for (var j = 0; j < nPix; j++) {
  1158. var index = nq.map(pixels[k++] & 0xff, pixels[k++] & 0xff, pixels[k++] & 0xff);
  1159. usedEntry[index] = true;
  1160. indexedPixels[j] = index;
  1161. }
  1162.  
  1163. pixels = null;
  1164. colorDepth = 8;
  1165. palSize = 7;
  1166.  
  1167. // get closest match to transparent color if specified
  1168. if (transparent !== null) {
  1169. transIndex = findClosest(transparent);
  1170. }
  1171. };
  1172.  
  1173. /**
  1174. * Returns index of palette color closest to c
  1175. */
  1176.  
  1177. var findClosest = function findClosest(c) {
  1178.  
  1179. if (colorTab === null) return -1;
  1180. var r = (c & 0xFF0000) >> 16;
  1181. var g = (c & 0x00FF00) >> 8;
  1182. var b = (c & 0x0000FF);
  1183. var minpos = 0;
  1184. var dmin = 256 * 256 * 256;
  1185. var len = colorTab.length;
  1186.  
  1187. for (var i = 0; i < len;) {
  1188. var dr = r - (colorTab[i++] & 0xff);
  1189. var dg = g - (colorTab[i++] & 0xff);
  1190. var db = b - (colorTab[i] & 0xff);
  1191. var d = dr * dr + dg * dg + db * db;
  1192. var index = i / 3;
  1193. if (usedEntry[index] && (d < dmin)) {
  1194. dmin = d;
  1195. minpos = index;
  1196. }
  1197. i++;
  1198. }
  1199. return minpos;
  1200. };
  1201.  
  1202. /**
  1203. * Extracts image pixels into byte array "pixels
  1204. */
  1205.  
  1206. var getImagePixels = function getImagePixels() {
  1207. var w = width;
  1208. var h = height;
  1209. pixels = [];
  1210. var data = image;
  1211. var count = 0;
  1212.  
  1213. for (var i = 0; i < h; i++) {
  1214.  
  1215. for (var j = 0; j < w; j++) {
  1216.  
  1217. var b = (i * w * 4) + j * 4;
  1218. pixels[count++] = data[b];
  1219. pixels[count++] = data[b + 1];
  1220. pixels[count++] = data[b + 2];
  1221.  
  1222. }
  1223.  
  1224. }
  1225. };
  1226.  
  1227. /**
  1228. * Writes Graphic Control Extension
  1229. */
  1230.  
  1231. var writeGraphicCtrlExt = function writeGraphicCtrlExt() {
  1232. out.writeByte(0x21); // extension introducer
  1233. out.writeByte(0xf9); // GCE label
  1234. out.writeByte(4); // data block size
  1235. var transp;
  1236. var disp;
  1237. if (transparent === null) {
  1238. transp = 0;
  1239. disp = 0; // dispose = no action
  1240. } else {
  1241. transp = 1;
  1242. disp = 2; // force clear if using transparent color
  1243. }
  1244. if (dispose >= 0) {
  1245. disp = dispose & 7; // user override
  1246. }
  1247. disp <<= 2;
  1248. // packed fields
  1249. out.writeByte(0 | // 1:3 reserved
  1250. disp | // 4:6 disposal
  1251. 0 | // 7 user input - 0 = none
  1252. transp); // 8 transparency flag
  1253.  
  1254. WriteShort(delay); // delay x 1/100 sec
  1255. out.writeByte(transIndex); // transparent color index
  1256. out.writeByte(0); // block terminator
  1257. };
  1258.  
  1259. /**
  1260. * Writes Comment Extention
  1261. */
  1262.  
  1263. var writeCommentExt = function writeCommentExt() {
  1264. out.writeByte(0x21); // extension introducer
  1265. out.writeByte(0xfe); // comment label
  1266. out.writeByte(comment.length); // Block Size (s)
  1267. out.writeUTFBytes(comment);
  1268. out.writeByte(0); // block terminator
  1269. };
  1270.  
  1271.  
  1272. /**
  1273. * Writes Image Descriptor
  1274. */
  1275.  
  1276. var writeImageDesc = function writeImageDesc() {
  1277.  
  1278. out.writeByte(0x2c); // image separator
  1279. WriteShort(0); // image position x,y = 0,0
  1280. WriteShort(0);
  1281. WriteShort(width); // image size
  1282. WriteShort(height);
  1283.  
  1284. // packed fields
  1285. if (firstFrame) {
  1286. // no LCT - GCT is used for first (or only) frame
  1287. out.writeByte(0);
  1288. } else {
  1289. // specify normal LCT
  1290. out.writeByte(0x80 | // 1 local color table 1=yes
  1291. 0 | // 2 interlace - 0=no
  1292. 0 | // 3 sorted - 0=no
  1293. 0 | // 4-5 reserved
  1294. palSize); // 6-8 size of color table
  1295. }
  1296. };
  1297.  
  1298. /**
  1299. * Writes Logical Screen Descriptor
  1300. */
  1301.  
  1302. var writeLSD = function writeLSD() {
  1303.  
  1304. // logical screen size
  1305. WriteShort(width);
  1306. WriteShort(height);
  1307. // packed fields
  1308. out.writeByte((0x80 | // 1 : global color table flag = 1 (gct used)
  1309. 0x70 | // 2-4 : color resolution = 7
  1310. 0x00 | // 5 : gct sort flag = 0
  1311. palSize)); // 6-8 : gct size
  1312.  
  1313. out.writeByte(0); // background color index
  1314. out.writeByte(0); // pixel aspect ratio - assume 1:1
  1315. };
  1316.  
  1317. /**
  1318. * Writes Netscape application extension to define repeat count.
  1319. */
  1320.  
  1321. var writeNetscapeExt = function writeNetscapeExt() {
  1322. out.writeByte(0x21); // extension introducer
  1323. out.writeByte(0xff); // app extension label
  1324. out.writeByte(11); // block size
  1325. out.writeUTFBytes("NETSCAPE" + "2.0"); // app id + auth code
  1326. out.writeByte(3); // sub-block size
  1327. out.writeByte(1); // loop sub-block id
  1328. WriteShort(repeat); // loop count (extra iterations, 0=repeat forever)
  1329. out.writeByte(0); // block terminator
  1330. };
  1331.  
  1332. /**
  1333. * Writes color table
  1334. */
  1335.  
  1336. var writePalette = function writePalette() {
  1337. out.writeBytes(colorTab);
  1338. var n = (3 * 256) - colorTab.length;
  1339. for (var i = 0; i < n; i++) out.writeByte(0);
  1340. };
  1341.  
  1342. var WriteShort = function WriteShort(pValue) {
  1343. out.writeByte(pValue & 0xFF);
  1344. out.writeByte((pValue >> 8) & 0xFF);
  1345. };
  1346.  
  1347. /**
  1348. * Encodes and writes pixel data
  1349. */
  1350.  
  1351. var writePixels = function writePixels() {
  1352. var myencoder = new LZWEncoder(width, height, indexedPixels, colorDepth);
  1353. myencoder.encode(out);
  1354. };
  1355.  
  1356. /**
  1357. * Retrieves the GIF stream
  1358. */
  1359.  
  1360. var stream = exports.stream = function stream() {
  1361. return out;
  1362. };
  1363.  
  1364. var setProperties = exports.setProperties = function setProperties(has_start, is_first) {
  1365. started = has_start;
  1366. firstFrame = is_first;
  1367. };
  1368.  
  1369. return exports;
  1370.  
  1371. };
  1372.  
  1373. // garbage code below
  1374. const styleRules = [
  1375. '#layerContainer::-webkit-scrollbar { width: 5px; height: 5px; overflow: hidden}',
  1376. '#layerContainer::-webkit-scrollbar-track { background: none }',
  1377. '#layerContainer::-webkit-scrollbar-thumb { background: #F5BC09; border-radius: 5px }',
  1378. ];
  1379.  
  1380. const sheet = window.document.styleSheets[window.document.styleSheets.length - 1];
  1381.  
  1382. const outerContainer = document.createElement('div');
  1383. const canvasContainer = document.querySelector('#gameCanvas');
  1384. const canvasInner = document.querySelector("#gameCanvasInner");
  1385. const containerStyle = `white-space: nowrap;
  1386. overflow: auto;
  1387. justify-content:center;
  1388. margin-top: 10px;
  1389. max-width: 76%;
  1390. height: 124px;
  1391. background: rgb(0 0 0 / 30%);
  1392. padding: 10px;
  1393. overflow-y: hidden;
  1394. border-radius: 10px;
  1395. margin-bottom: 5px;
  1396. margin-left: 5vw;
  1397. width: 100%`;
  1398.  
  1399. const canvas = document.querySelector('#canvas');
  1400. const ctx = canvas.getContext('2d');
  1401. const encoder = new GIFEncoder();
  1402.  
  1403. const contextLayers = [];
  1404.  
  1405. (() => {
  1406. addLayerContainer();
  1407. styleRules.forEach((rule) => sheet.insertRule(rule));
  1408. const gameModeObserver = new MutationObserver(checkRoomType);
  1409. gameModeObserver.observe(document.querySelector('.game'),
  1410. { attributes: true });
  1411. gameModeObserver.observe(canvas, { attributes: true });
  1412. })();
  1413.  
  1414. function checkRoomType() {
  1415. outerContainer.style.display = isFreeDraw() ? 'flex' : 'none';
  1416. }
  1417.  
  1418. function addLayer() {
  1419. resetActiveLayer();
  1420. const imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);
  1421. saveLayer(imgData);
  1422. const canvasLayer = createLayer();
  1423. const layerCtx = canvasLayer.getContext('2d');
  1424. layerCtx.putImageData(imgData, 0, 0);
  1425. makeTransparent(layerCtx);
  1426. const previousLayer = canvasInner.querySelector("#canvasLayer");
  1427. if (previousLayer) previousLayer.remove();
  1428. canvas.parentElement.insertBefore(canvasLayer, canvas);
  1429. }
  1430.  
  1431. function render(name, delay) {
  1432. if (!contextLayers.length) return;
  1433. encoder.setRepeat(0);
  1434. encoder.setDelay(delay);
  1435. encoder.start();
  1436. contextLayers.forEach(layer => {
  1437. encoder.addFrame(layer);
  1438. });
  1439. encoder.finish();
  1440. encoder.download(name + ".gif");
  1441. }
  1442.  
  1443. function copyCtx(imgData) {
  1444. const tempCanvas = document.createElement('canvas');
  1445. tempCanvas.width = canvas.width;
  1446. tempCanvas.height = canvas.height;
  1447. const tempCtx = tempCanvas.getContext('2d');
  1448. tempCtx.putImageData(imgData, 0, 0);
  1449. return tempCtx;
  1450. }
  1451. function saveLayer(data) {
  1452. contextLayers.push(copyCtx(data));
  1453. const container = document.querySelector("#layerContainer");
  1454. // const div = document.createElement("div");
  1455. const img = document.createElement("img");
  1456. img.style.width = "133px";
  1457. img.style.cursor = "pointer";
  1458. img.style.marginRight = "5px";
  1459. img.src = canvas.toDataURL();
  1460. container.append(img);
  1461. }
  1462.  
  1463. function setActiveLayer(e) {
  1464. const img = e.target;
  1465. if (img.tagName !== "IMG") return;
  1466. resetActiveLayer();
  1467. const canvasLayerCtx = document.querySelector("#canvasLayer").getContext("2d");
  1468. const previousImg = img.previousSibling;
  1469. if (previousImg) {
  1470. canvasLayerCtx.drawImage(previousImg, 0, 0);
  1471. makeTransparent(canvasLayerCtx);
  1472. } else {
  1473. canvasLayerCtx.clearRect(0, 0, canvas.width, canvas.height);
  1474. }
  1475. img.id = "activeLayer";
  1476. img.style.border = "3px solid red";
  1477. ctx.drawImage(img, 0, 0);
  1478. }
  1479.  
  1480. function resetActiveLayer() {
  1481. const layer = document.querySelector("#activeLayer");
  1482. if (!layer) return;
  1483. layer.id = "";
  1484. layer.style.border = "";
  1485. }
  1486.  
  1487. function createLayer() {
  1488. const canvasLayer = document.createElement('canvas');
  1489. canvasLayer.style.width = '100%';
  1490. canvasLayer.style.position = 'absolute';
  1491. canvasLayer.style.pointerEvents = 'none';
  1492. canvasLayer.style.imageRendering = 'pixelated';
  1493. canvasLayer.style.filter = 'opacity(0.5)';
  1494. canvasLayer.width = canvas.width;
  1495. canvasLayer.height = canvas.height;
  1496. canvasLayer.id = "canvasLayer";
  1497. return canvasLayer;
  1498. }
  1499.  
  1500. function downloadGif(data, name) {
  1501. let a = document.createElement("a");
  1502. a.download = name + ".gif";
  1503. a.href = data;
  1504. a.click();
  1505. }
  1506.  
  1507. function addButton(text, clickFunction, element, type) {
  1508. const button = document.createElement("div");
  1509. button.setAttribute("class", `btn btn-sm btn-${type}`);
  1510. button.setAttribute("style", "height: fit-content; margin-top: 10px; margin-left: 10px;");
  1511. button.textContent = text;
  1512. button.onclick = clickFunction;
  1513. element.append(button);
  1514. return button;
  1515. }
  1516.  
  1517. function getInterval() {
  1518. let interval = parseInt(document.querySelector("#gifInterval").value);
  1519. if (isNaN(interval) || interval < 0 || interval > 5000) interval = 100;
  1520. return interval;
  1521. }
  1522.  
  1523. function renderGif() {
  1524. const interval = getInterval();
  1525. const name = "sketchful-gif-" + Date.now();
  1526. render(name, interval);
  1527. console.log("rendered " + name);
  1528. }
  1529.  
  1530. function removeLayer() {
  1531. const activeLayer = document.querySelector('#activeLayer');
  1532. const layerContainer = document.querySelector('#layerContainer');
  1533. if (!activeLayer) return;
  1534. const index = Array.prototype.indexOf.call(layerContainer.children, activeLayer);
  1535. contextLayers.splice(index, 1);
  1536. activeLayer.remove();
  1537. }
  1538.  
  1539. function addLayerContainer() {
  1540. const game = document.querySelector("body > div.game");
  1541. const container = document.createElement("div");
  1542. outerContainer.style.display = "flex";
  1543. outerContainer.style.flexDirection = "row";
  1544. container.addEventListener('wheel', (e) => {
  1545. if (e.deltaY > 0) container.scrollLeft += 100;
  1546. else container.scrollLeft -= 100;
  1547. e.preventDefault();
  1548. });
  1549. container.addEventListener('pointerdown', setActiveLayer, true);
  1550. container.id = "layerContainer";
  1551. container.setAttribute("style", containerStyle);
  1552. container.setAttribute("ondragstart", "return false");
  1553. outerContainer.append(container);
  1554. const buttonContainer = document.createElement("div");
  1555. buttonContainer.style.width = "15%";
  1556. outerContainer.append(buttonContainer);
  1557. addButton("Save Gif", renderGif, buttonContainer, "warning");
  1558. addButton("NOnion", toggleOnion, buttonContainer, "warning");
  1559. addButton("Save Layer", addLayer, buttonContainer, "info");
  1560. addButton("Delete Layer", removeLayer, buttonContainer, "danger");
  1561. const textInput = document.createElement("input");
  1562. textInput.style.width = "100px";
  1563. textInput.placeholder = "Interval (ms)";
  1564. textInput.style.marginTop = "10px";
  1565. textInput.style.marginLeft = "10px";
  1566. textInput.id = "gifInterval";
  1567. setInputFilter(textInput, (value) => {
  1568. return /^\d*\.?\d*$/.test(value);
  1569. });
  1570. buttonContainer.append(textInput);
  1571. addButton("Play", playAnimation, buttonContainer, "success").style.marginTop = "";
  1572. game.append(outerContainer);
  1573. }
  1574.  
  1575. function toggleOnion(e) {
  1576. const button = e.target;
  1577. const canvasLayer = document.querySelector("#canvasLayer");
  1578. if (!canvasLayer) return;
  1579. if (button.textContent === "NOnion") {
  1580. canvasLayer.style.display = "none";
  1581. button.textContent = "Onion";
  1582. } else {
  1583. canvasLayer.style.display = "";
  1584. button.textContent = "NOnion";
  1585. }
  1586. }
  1587.  
  1588. let animating = null;
  1589. function playAnimation(e) {
  1590. const playButton = e.target;
  1591. if (playButton.textContent === "Stop") {
  1592. playButton.classList.toggle("btn-success");
  1593. playButton.classList.toggle("btn-danger");
  1594. playButton.textContent = "Play";
  1595. if (animating) clearInterval(animating);
  1596. const preview = document.querySelector("#gifPreview");
  1597. if (preview) preview.remove();
  1598. return;
  1599. }
  1600. const canvasCover = document.querySelector("#canvasCover");
  1601. const layerContainer = document.querySelector("#layerContainer");
  1602. const img = document.createElement('img');
  1603. img.style.position = "absolute";
  1604. img.style.zIndex = "1";
  1605. img.style.width = "100%";
  1606. img.style.imageRendering = "pixelated";
  1607. img.id = "gifPreview";
  1608. canvasCover.parentElement.insertBefore(img, canvasCover);
  1609. let frame = layerContainer.firstChild;
  1610. if (!frame) return;
  1611. const interval = getInterval();
  1612. playButton.classList.toggle("btn-success");
  1613. playButton.classList.toggle("btn-danger");
  1614. playButton.textContent = "Stop";
  1615. animating = setInterval(() => {
  1616. img.src = frame.src;
  1617. frame = frame.nextSibling || layerContainer.firstChild;
  1618. }, interval);
  1619. }
  1620.  
  1621. function isFreeDraw() {
  1622. return (
  1623. document.querySelector("#canvas").style.display !== 'none' &&
  1624. document.querySelector('#gameClock').style.display === 'none' &&
  1625. document.querySelector('#gameSettings').style.display === 'none'
  1626. );
  1627. }
  1628.  
  1629. function setInputFilter(textbox, inputFilter) {
  1630. ["input", "keydown", "keyup", "mousedown", "mouseup", "select", "contextmenu", "drop"].forEach(function(event) {
  1631. textbox.addEventListener(event, function() {
  1632. if (inputFilter(this.value)) {
  1633. this.oldValue = this.value;
  1634. this.oldSelectionStart = this.selectionStart;
  1635. this.oldSelectionEnd = this.selectionEnd;
  1636. } else if (this.hasOwnProperty("oldValue")) {
  1637. this.value = this.oldValue;
  1638. this.setSelectionRange(this.oldSelectionStart, this.oldSelectionEnd);
  1639. } else {
  1640. this.value = "";
  1641. }
  1642. });
  1643. });
  1644. }
  1645.  
  1646. function makeTransparent(context) {
  1647. const imgData = context.getImageData(0, 0, canvas.width, canvas.height);
  1648. const data = imgData.data;
  1649.  
  1650. for(let i = 0; i < data.length; i += 4) {
  1651. const [r, g, b] = data.slice(i, i + 3);
  1652. if (r == 255 && g == 255 && b == 255) {
  1653. data[i + 3] = 0;
  1654. }
  1655. }
  1656. context.putImageData(imgData, 0, 0);
  1657. }