├── LICENSE ├── README.md ├── frames ├── pet0.gif ├── pet1.gif ├── pet2.gif ├── pet3.gif ├── pet4.gif ├── pet5.gif ├── pet6.gif ├── pet7.gif ├── pet8.gif └── pet9.gif ├── gifencoder ├── GIFEncoder.js ├── LICENSE ├── LZWEncoder.js ├── NeuQuant.js ├── TypedNeuQuant.js └── index.js ├── index.js └── manifest.json /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # petpet 2 | 3 | A powercord plugin to create pet gifs 4 | 5 | Inspired by 6 | 7 | ## Example 8 | 9 | 10 | | Input | Output | 11 | |---|---| 12 | | ![Input](https://cdn.discordapp.com/attachments/769715970502557727/857766059394596894/97a3135fe0eaae28526bb80e1bb8fb86.png) | ![Output](https://media.discordapp.net/attachments/769715970502557727/857765131320819712/petpet.gif) | 13 | 14 | ## Usage 15 | 16 | `[p]` is your powercord command prefix, `.` by default 17 | 18 | ```sh 19 | [p]petpet [FILE_NAME - default pet.gif] [DELAY - default 20)] [RESOLUTION - default 128] 20 | ``` 21 | -------------------------------------------------------------------------------- /frames/pet0.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VenPlugs/petpet/f591cdc5b809c4d52c2666519571484517ac5c8d/frames/pet0.gif -------------------------------------------------------------------------------- /frames/pet1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VenPlugs/petpet/f591cdc5b809c4d52c2666519571484517ac5c8d/frames/pet1.gif -------------------------------------------------------------------------------- /frames/pet2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VenPlugs/petpet/f591cdc5b809c4d52c2666519571484517ac5c8d/frames/pet2.gif -------------------------------------------------------------------------------- /frames/pet3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VenPlugs/petpet/f591cdc5b809c4d52c2666519571484517ac5c8d/frames/pet3.gif -------------------------------------------------------------------------------- /frames/pet4.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VenPlugs/petpet/f591cdc5b809c4d52c2666519571484517ac5c8d/frames/pet4.gif -------------------------------------------------------------------------------- /frames/pet5.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VenPlugs/petpet/f591cdc5b809c4d52c2666519571484517ac5c8d/frames/pet5.gif -------------------------------------------------------------------------------- /frames/pet6.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VenPlugs/petpet/f591cdc5b809c4d52c2666519571484517ac5c8d/frames/pet6.gif -------------------------------------------------------------------------------- /frames/pet7.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VenPlugs/petpet/f591cdc5b809c4d52c2666519571484517ac5c8d/frames/pet7.gif -------------------------------------------------------------------------------- /frames/pet8.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VenPlugs/petpet/f591cdc5b809c4d52c2666519571484517ac5c8d/frames/pet8.gif -------------------------------------------------------------------------------- /frames/pet9.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VenPlugs/petpet/f591cdc5b809c4d52c2666519571484517ac5c8d/frames/pet9.gif -------------------------------------------------------------------------------- /gifencoder/GIFEncoder.js: -------------------------------------------------------------------------------- 1 | /* 2 | GIFEncoder.js 3 | 4 | Authors 5 | Kevin Weiner (original Java version - kweiner@fmsware.com) 6 | Thibault Imbert (AS3 version - bytearray.org) 7 | Johan Nordberg (JS version - code@johan-nordberg.com) 8 | Eugene Ware (node.js streaming version - eugene@noblesmaurai.com) 9 | */ 10 | 11 | var stream = require("stream"); 12 | var NeuQuant = require("./TypedNeuQuant.js"); 13 | var LZWEncoder = require("./LZWEncoder.js"); 14 | 15 | function ByteArray() { 16 | this.data = []; 17 | } 18 | 19 | ByteArray.prototype.getData = function () { 20 | return Buffer.from(this.data); 21 | }; 22 | 23 | ByteArray.prototype.writeByte = function (val) { 24 | this.data.push(val); 25 | }; 26 | 27 | ByteArray.prototype.writeUTFBytes = function (string) { 28 | for (var l = string.length, i = 0; i < l; i++) this.writeByte(string.charCodeAt(i)); 29 | }; 30 | 31 | ByteArray.prototype.writeBytes = function (array, offset, length) { 32 | for (var l = length || array.length, i = offset || 0; i < l; i++) this.writeByte(array[i]); 33 | }; 34 | 35 | function GIFEncoder(width, height) { 36 | // image size 37 | this.width = ~~width; 38 | this.height = ~~height; 39 | 40 | // transparent color if given 41 | this.transparent = null; 42 | 43 | // transparent index in color table 44 | this.transIndex = 0; 45 | 46 | // -1 = no repeat, 0 = forever. anything else is repeat count 47 | this.repeat = -1; 48 | 49 | // frame delay (hundredths) 50 | this.delay = 0; 51 | 52 | this.image = null; // current frame 53 | this.pixels = null; // BGR byte array from frame 54 | this.indexedPixels = null; // converted frame indexed to palette 55 | this.colorDepth = null; // number of bit planes 56 | this.colorTab = null; // RGB palette 57 | this.usedEntry = new Array(); // active palette entries 58 | this.palSize = 7; // color table size (bits-1) 59 | this.dispose = -1; // disposal code (-1 = use default) 60 | this.firstFrame = true; 61 | this.sample = 10; // default sample interval for quantizer 62 | 63 | this.started = false; // started encoding 64 | 65 | this.readStreams = []; 66 | 67 | this.out = new ByteArray(); 68 | } 69 | 70 | GIFEncoder.prototype.createReadStream = function (rs) { 71 | if (!rs) { 72 | rs = new stream.Readable(); 73 | rs._read = function () {}; 74 | } 75 | this.readStreams.push(rs); 76 | return rs; 77 | }; 78 | 79 | GIFEncoder.prototype.createWriteStream = function (options) { 80 | var self = this; 81 | if (options) { 82 | Object.keys(options).forEach(function (option) { 83 | var fn = "set" + option[0].toUpperCase() + option.substr(1); 84 | if (~["setDelay", "setFrameRate", "setDispose", "setRepeat", "setTransparent", "setQuality"].indexOf(fn)) { 85 | self[fn].call(self, options[option]); 86 | } 87 | }); 88 | } 89 | 90 | var ws = new stream.Duplex({ objectMode: true }); 91 | ws._read = function () {}; 92 | this.createReadStream(ws); 93 | 94 | var self = this; 95 | ws._write = function (data, enc, next) { 96 | if (!self.started) self.start(); 97 | self.addFrame(data); 98 | next(); 99 | }; 100 | var end = ws.end; 101 | ws.end = function () { 102 | end.apply(ws, [].slice.call(arguments)); 103 | self.finish(); 104 | }; 105 | return ws; 106 | }; 107 | 108 | GIFEncoder.prototype.emit = function () { 109 | var self = this; 110 | if (this.readStreams.length === 0) return; 111 | if (this.out.data.length) { 112 | this.readStreams.forEach(function (rs) { 113 | rs.push(Buffer.from(self.out.data)); 114 | }); 115 | this.out.data = []; 116 | } 117 | }; 118 | 119 | GIFEncoder.prototype.end = function () { 120 | if (this.readStreams.length === null) return; 121 | this.emit(); 122 | this.readStreams.forEach(function (rs) { 123 | rs.push(null); 124 | }); 125 | this.readStreams = []; 126 | }; 127 | 128 | /* 129 | Sets the delay time between each frame, or changes it for subsequent frames 130 | (applies to the next frame added) 131 | */ 132 | GIFEncoder.prototype.setDelay = function (milliseconds) { 133 | this.delay = Math.round(milliseconds / 10); 134 | }; 135 | 136 | /* 137 | Sets frame rate in frames per second. 138 | */ 139 | GIFEncoder.prototype.setFrameRate = function (fps) { 140 | this.delay = Math.round(100 / fps); 141 | }; 142 | 143 | /* 144 | Sets the GIF frame disposal code for the last added frame and any 145 | subsequent frames. 146 | 147 | Default is 0 if no transparent color has been set, otherwise 2. 148 | */ 149 | GIFEncoder.prototype.setDispose = function (disposalCode) { 150 | if (disposalCode >= 0) this.dispose = disposalCode; 151 | }; 152 | 153 | /* 154 | Sets the number of times the set of GIF frames should be played. 155 | 156 | -1 = play once 157 | 0 = repeat indefinitely 158 | 159 | Default is -1 160 | 161 | Must be invoked before the first image is added 162 | */ 163 | 164 | GIFEncoder.prototype.setRepeat = function (repeat) { 165 | this.repeat = repeat; 166 | }; 167 | 168 | /* 169 | Sets the transparent color for the last added frame and any subsequent 170 | frames. Since all colors are subject to modification in the quantization 171 | process, the color in the final palette for each frame closest to the given 172 | color becomes the transparent color for that frame. May be set to null to 173 | indicate no transparent color. 174 | */ 175 | GIFEncoder.prototype.setTransparent = function (color) { 176 | this.transparent = color; 177 | }; 178 | 179 | /* 180 | Adds next GIF frame. The frame is not written immediately, but is 181 | actually deferred until the next frame is received so that timing 182 | data can be inserted. Invoking finish() flushes all frames. 183 | */ 184 | GIFEncoder.prototype.addFrame = function (imageData) { 185 | // HTML Canvas 2D Context Passed In 186 | if (imageData && imageData.getImageData) { 187 | this.image = imageData.getImageData(0, 0, this.width, this.height).data; 188 | } else { 189 | this.image = imageData; 190 | } 191 | 192 | this.getImagePixels(); // convert to correct format if necessary 193 | this.analyzePixels(); // build color table & map pixels 194 | 195 | if (this.firstFrame) { 196 | this.writeLSD(); // logical screen descriptior 197 | this.writePalette(); // global color table 198 | if (this.repeat >= 0) { 199 | // use NS app extension to indicate reps 200 | this.writeNetscapeExt(); 201 | } 202 | } 203 | 204 | this.writeGraphicCtrlExt(); // write graphic control extension 205 | this.writeImageDesc(); // image descriptor 206 | if (!this.firstFrame) this.writePalette(); // local color table 207 | this.writePixels(); // encode and write pixel data 208 | 209 | this.firstFrame = false; 210 | this.emit(); 211 | }; 212 | 213 | /* 214 | Adds final trailer to the GIF stream, if you don't call the finish method 215 | the GIF stream will not be valid. 216 | */ 217 | GIFEncoder.prototype.finish = function () { 218 | this.out.writeByte(0x3b); // gif trailer 219 | this.end(); 220 | }; 221 | 222 | /* 223 | Sets quality of color quantization (conversion of images to the maximum 256 224 | colors allowed by the GIF specification). Lower values (minimum = 1) 225 | produce better colors, but slow processing significantly. 10 is the 226 | default, and produces good color mapping at reasonable speeds. Values 227 | greater than 20 do not yield significant improvements in speed. 228 | */ 229 | GIFEncoder.prototype.setQuality = function (quality) { 230 | if (quality < 1) quality = 1; 231 | this.sample = quality; 232 | }; 233 | 234 | /* 235 | Writes GIF file header 236 | */ 237 | GIFEncoder.prototype.start = function () { 238 | this.out.writeUTFBytes("GIF89a"); 239 | this.started = true; 240 | this.emit(); 241 | }; 242 | 243 | /* 244 | Analyzes current frame colors and creates color map. 245 | */ 246 | GIFEncoder.prototype.analyzePixels = function () { 247 | var len = this.pixels.length; 248 | var nPix = len / 3; 249 | 250 | this.indexedPixels = new Uint8Array(nPix); 251 | 252 | var imgq = new NeuQuant(this.pixels, this.sample); 253 | imgq.buildColormap(); // create reduced palette 254 | this.colorTab = imgq.getColormap(); 255 | 256 | // map image pixels to new palette 257 | var k = 0; 258 | for (var j = 0; j < nPix; j++) { 259 | var index = imgq.lookupRGB(this.pixels[k++] & 0xff, this.pixels[k++] & 0xff, this.pixels[k++] & 0xff); 260 | this.usedEntry[index] = true; 261 | this.indexedPixels[j] = index; 262 | } 263 | 264 | this.pixels = null; 265 | this.colorDepth = 8; 266 | this.palSize = 7; 267 | 268 | // get closest match to transparent color if specified 269 | if (this.transparent !== null) { 270 | this.transIndex = this.findClosest(this.transparent); 271 | 272 | // ensure that pixels with full transparency in the RGBA image are using the selected transparent color index in the indexed image. 273 | for (var pixelIndex = 0; pixelIndex < nPix; pixelIndex++) { 274 | if (this.image[pixelIndex * 4 + 3] == 0) { 275 | this.indexedPixels[pixelIndex] = this.transIndex; 276 | } 277 | } 278 | } 279 | }; 280 | 281 | /* 282 | Returns index of palette color closest to c 283 | */ 284 | GIFEncoder.prototype.findClosest = function (c) { 285 | if (this.colorTab === null) return -1; 286 | 287 | var r = (c & 0xff0000) >> 16; 288 | var g = (c & 0x00ff00) >> 8; 289 | var b = c & 0x0000ff; 290 | var minpos = 0; 291 | var dmin = 256 * 256 * 256; 292 | var len = this.colorTab.length; 293 | 294 | for (var i = 0; i < len; ) { 295 | var index = i / 3; 296 | var dr = r - (this.colorTab[i++] & 0xff); 297 | var dg = g - (this.colorTab[i++] & 0xff); 298 | var db = b - (this.colorTab[i++] & 0xff); 299 | var d = dr * dr + dg * dg + db * db; 300 | if (this.usedEntry[index] && d < dmin) { 301 | dmin = d; 302 | minpos = index; 303 | } 304 | } 305 | 306 | return minpos; 307 | }; 308 | 309 | /* 310 | Extracts image pixels into byte array pixels 311 | (removes alphachannel from canvas imagedata) 312 | */ 313 | GIFEncoder.prototype.getImagePixels = function () { 314 | var w = this.width; 315 | var h = this.height; 316 | this.pixels = new Uint8Array(w * h * 3); 317 | 318 | var data = this.image; 319 | var count = 0; 320 | 321 | for (var i = 0; i < h; i++) { 322 | for (var j = 0; j < w; j++) { 323 | var b = i * w * 4 + j * 4; 324 | this.pixels[count++] = data[b]; 325 | this.pixels[count++] = data[b + 1]; 326 | this.pixels[count++] = data[b + 2]; 327 | } 328 | } 329 | }; 330 | 331 | /* 332 | Writes Graphic Control Extension 333 | */ 334 | GIFEncoder.prototype.writeGraphicCtrlExt = function () { 335 | this.out.writeByte(0x21); // extension introducer 336 | this.out.writeByte(0xf9); // GCE label 337 | this.out.writeByte(4); // data block size 338 | 339 | var transp, disp; 340 | if (this.transparent === null) { 341 | transp = 0; 342 | disp = 0; // dispose = no action 343 | } else { 344 | transp = 1; 345 | disp = 2; // force clear if using transparent color 346 | } 347 | 348 | if (this.dispose >= 0) { 349 | disp = this.dispose & 7; // user override 350 | } 351 | disp <<= 2; 352 | 353 | // packed fields 354 | this.out.writeByte( 355 | 0 | // 1:3 reserved 356 | disp | // 4:6 disposal 357 | 0 | // 7 user input - 0 = none 358 | transp // 8 transparency flag 359 | ); 360 | 361 | this.writeShort(this.delay); // delay x 1/100 sec 362 | this.out.writeByte(this.transIndex); // transparent color index 363 | this.out.writeByte(0); // block terminator 364 | }; 365 | 366 | /* 367 | Writes Image Descriptor 368 | */ 369 | GIFEncoder.prototype.writeImageDesc = function () { 370 | this.out.writeByte(0x2c); // image separator 371 | this.writeShort(0); // image position x,y = 0,0 372 | this.writeShort(0); 373 | this.writeShort(this.width); // image size 374 | this.writeShort(this.height); 375 | 376 | // packed fields 377 | if (this.firstFrame) { 378 | // no LCT - GCT is used for first (or only) frame 379 | this.out.writeByte(0); 380 | } else { 381 | // specify normal LCT 382 | this.out.writeByte( 383 | 0x80 | // 1 local color table 1=yes 384 | 0 | // 2 interlace - 0=no 385 | 0 | // 3 sorted - 0=no 386 | 0 | // 4-5 reserved 387 | this.palSize // 6-8 size of color table 388 | ); 389 | } 390 | }; 391 | 392 | /* 393 | Writes Logical Screen Descriptor 394 | */ 395 | GIFEncoder.prototype.writeLSD = function () { 396 | // logical screen size 397 | this.writeShort(this.width); 398 | this.writeShort(this.height); 399 | 400 | // packed fields 401 | this.out.writeByte( 402 | 0x80 | // 1 : global color table flag = 1 (gct used) 403 | 0x70 | // 2-4 : color resolution = 7 404 | 0x00 | // 5 : gct sort flag = 0 405 | this.palSize // 6-8 : gct size 406 | ); 407 | 408 | this.out.writeByte(0); // background color index 409 | this.out.writeByte(0); // pixel aspect ratio - assume 1:1 410 | }; 411 | 412 | /* 413 | Writes Netscape application extension to define repeat count. 414 | */ 415 | GIFEncoder.prototype.writeNetscapeExt = function () { 416 | this.out.writeByte(0x21); // extension introducer 417 | this.out.writeByte(0xff); // app extension label 418 | this.out.writeByte(11); // block size 419 | this.out.writeUTFBytes("NETSCAPE2.0"); // app id + auth code 420 | this.out.writeByte(3); // sub-block size 421 | this.out.writeByte(1); // loop sub-block id 422 | this.writeShort(this.repeat); // loop count (extra iterations, 0=repeat forever) 423 | this.out.writeByte(0); // block terminator 424 | }; 425 | 426 | /* 427 | Writes color table 428 | */ 429 | GIFEncoder.prototype.writePalette = function () { 430 | this.out.writeBytes(this.colorTab); 431 | var n = 3 * 256 - this.colorTab.length; 432 | for (var i = 0; i < n; i++) this.out.writeByte(0); 433 | }; 434 | 435 | GIFEncoder.prototype.writeShort = function (pValue) { 436 | this.out.writeByte(pValue & 0xff); 437 | this.out.writeByte((pValue >> 8) & 0xff); 438 | }; 439 | 440 | /* 441 | Encodes and writes pixel data 442 | */ 443 | GIFEncoder.prototype.writePixels = function () { 444 | var enc = new LZWEncoder(this.width, this.height, this.indexedPixels, this.colorDepth); 445 | enc.encode(this.out); 446 | }; 447 | 448 | module.exports = GIFEncoder; 449 | -------------------------------------------------------------------------------- /gifencoder/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013, Eugene Ware 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 2. Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | 3. Neither the name of Eugene Ware nor the names of its contributors 13 | may be used to endorse or promote products derived from this software 14 | without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY EUGENE WARE ''AS IS'' AND ANY 17 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL EUGENE WARE BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /gifencoder/LZWEncoder.js: -------------------------------------------------------------------------------- 1 | /* 2 | LZWEncoder.js 3 | 4 | Authors 5 | Kevin Weiner (original Java version - kweiner@fmsware.com) 6 | Thibault Imbert (AS3 version - bytearray.org) 7 | Johan Nordberg (JS version - code@johan-nordberg.com) 8 | 9 | Acknowledgements 10 | GIFCOMPR.C - GIF Image compression routines 11 | Lempel-Ziv compression based on 'compress'. GIF modifications by 12 | David Rowley (mgardi@watdcsu.waterloo.edu) 13 | GIF Image compression - modified 'compress' 14 | Based on: compress.c - File compression ala IEEE Computer, June 1984. 15 | By Authors: Spencer W. Thomas (decvax!harpo!utah-cs!utah-gr!thomas) 16 | Jim McKie (decvax!mcvax!jim) 17 | Steve Davies (decvax!vax135!petsd!peora!srd) 18 | Ken Turkowski (decvax!decwrl!turtlevax!ken) 19 | James A. Woods (decvax!ihnp4!ames!jaw) 20 | Joe Orost (decvax!vax135!petsd!joe) 21 | */ 22 | 23 | var EOF = -1; 24 | var BITS = 12; 25 | var HSIZE = 5003; // 80% occupancy 26 | var masks = [0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 27 | 0x003F, 0x007F, 0x00FF, 0x01FF, 0x03FF, 0x07FF, 28 | 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF]; 29 | 30 | function LZWEncoder(width, height, pixels, colorDepth) { 31 | var initCodeSize = Math.max(2, colorDepth); 32 | 33 | var accum = new Uint8Array(256); 34 | var htab = new Int32Array(HSIZE); 35 | var codetab = new Int32Array(HSIZE); 36 | 37 | var cur_accum, cur_bits = 0; 38 | var a_count; 39 | var free_ent = 0; // first unused entry 40 | var maxcode; 41 | 42 | // block compression parameters -- after all codes are used up, 43 | // and compression rate changes, start over. 44 | var clear_flg = false; 45 | 46 | // Algorithm: use open addressing double hashing (no chaining) on the 47 | // prefix code / next character combination. We do a variant of Knuth's 48 | // algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime 49 | // secondary probe. Here, the modular division first probe is gives way 50 | // to a faster exclusive-or manipulation. Also do block compression with 51 | // an adaptive reset, whereby the code table is cleared when the compression 52 | // ratio decreases, but after the table fills. The variable-length output 53 | // codes are re-sized at this point, and a special CLEAR code is generated 54 | // for the decompressor. Late addition: construct the table according to 55 | // file size for noticeable speed improvement on small files. Please direct 56 | // questions about this implementation to ames!jaw. 57 | var g_init_bits, ClearCode, EOFCode; 58 | 59 | // Add a character to the end of the current packet, and if it is 254 60 | // characters, flush the packet to disk. 61 | function char_out(c, outs) { 62 | accum[a_count++] = c; 63 | if (a_count >= 254) flush_char(outs); 64 | } 65 | 66 | // Clear out the hash table 67 | // table clear for block compress 68 | function cl_block(outs) { 69 | cl_hash(HSIZE); 70 | free_ent = ClearCode + 2; 71 | clear_flg = true; 72 | output(ClearCode, outs); 73 | } 74 | 75 | // Reset code table 76 | function cl_hash(hsize) { 77 | for (var i = 0; i < hsize; ++i) htab[i] = -1; 78 | } 79 | 80 | function compress(init_bits, outs) { 81 | var fcode, c, i, ent, disp, hsize_reg, hshift; 82 | 83 | // Set up the globals: g_init_bits - initial number of bits 84 | g_init_bits = init_bits; 85 | 86 | // Set up the necessary values 87 | clear_flg = false; 88 | n_bits = g_init_bits; 89 | maxcode = MAXCODE(n_bits); 90 | 91 | ClearCode = 1 << (init_bits - 1); 92 | EOFCode = ClearCode + 1; 93 | free_ent = ClearCode + 2; 94 | 95 | a_count = 0; // clear packet 96 | 97 | ent = nextPixel(); 98 | 99 | hshift = 0; 100 | for (fcode = HSIZE; fcode < 65536; fcode *= 2) ++hshift; 101 | hshift = 8 - hshift; // set hash code range bound 102 | hsize_reg = HSIZE; 103 | cl_hash(hsize_reg); // clear hash table 104 | 105 | output(ClearCode, outs); 106 | 107 | outer_loop: while ((c = nextPixel()) != EOF) { 108 | fcode = (c << BITS) + ent; 109 | i = (c << hshift) ^ ent; // xor hashing 110 | if (htab[i] === fcode) { 111 | ent = codetab[i]; 112 | continue; 113 | } else if (htab[i] >= 0) { // non-empty slot 114 | disp = hsize_reg - i; // secondary hash (after G. Knott) 115 | if (i === 0) disp = 1; 116 | do { 117 | if ((i -= disp) < 0) i += hsize_reg; 118 | if (htab[i] === fcode) { 119 | ent = codetab[i]; 120 | continue outer_loop; 121 | } 122 | } while (htab[i] >= 0); 123 | } 124 | output(ent, outs); 125 | ent = c; 126 | if (free_ent < 1 << BITS) { 127 | codetab[i] = free_ent++; // code -> hashtable 128 | htab[i] = fcode; 129 | } else { 130 | cl_block(outs); 131 | } 132 | } 133 | 134 | // Put out the final code. 135 | output(ent, outs); 136 | output(EOFCode, outs); 137 | } 138 | 139 | function encode(outs) { 140 | outs.writeByte(initCodeSize); // write "initial code size" byte 141 | remaining = width * height; // reset navigation variables 142 | curPixel = 0; 143 | compress(initCodeSize + 1, outs); // compress and write the pixel data 144 | outs.writeByte(0); // write block terminator 145 | } 146 | 147 | // Flush the packet to disk, and reset the accumulator 148 | function flush_char(outs) { 149 | if (a_count > 0) { 150 | outs.writeByte(a_count); 151 | outs.writeBytes(accum, 0, a_count); 152 | a_count = 0; 153 | } 154 | } 155 | 156 | function MAXCODE(n_bits) { 157 | return (1 << n_bits) - 1; 158 | } 159 | 160 | // Return the next pixel from the image 161 | function nextPixel() { 162 | if (remaining === 0) return EOF; 163 | --remaining; 164 | var pix = pixels[curPixel++]; 165 | return pix & 0xff; 166 | } 167 | 168 | function output(code, outs) { 169 | cur_accum &= masks[cur_bits]; 170 | 171 | if (cur_bits > 0) cur_accum |= (code << cur_bits); 172 | else cur_accum = code; 173 | 174 | cur_bits += n_bits; 175 | 176 | while (cur_bits >= 8) { 177 | char_out((cur_accum & 0xff), outs); 178 | cur_accum >>= 8; 179 | cur_bits -= 8; 180 | } 181 | 182 | // If the next entry is going to be too big for the code size, 183 | // then increase it, if possible. 184 | if (free_ent > maxcode || clear_flg) { 185 | if (clear_flg) { 186 | maxcode = MAXCODE(n_bits = g_init_bits); 187 | clear_flg = false; 188 | } else { 189 | ++n_bits; 190 | if (n_bits == BITS) maxcode = 1 << BITS; 191 | else maxcode = MAXCODE(n_bits); 192 | } 193 | } 194 | 195 | if (code == EOFCode) { 196 | // At EOF, write the rest of the buffer. 197 | while (cur_bits > 0) { 198 | char_out((cur_accum & 0xff), outs); 199 | cur_accum >>= 8; 200 | cur_bits -= 8; 201 | } 202 | flush_char(outs); 203 | } 204 | } 205 | 206 | this.encode = encode; 207 | } 208 | 209 | module.exports = LZWEncoder; 210 | -------------------------------------------------------------------------------- /gifencoder/NeuQuant.js: -------------------------------------------------------------------------------- 1 | /* NeuQuant Neural-Net Quantization Algorithm 2 | * ------------------------------------------ 3 | * 4 | * Copyright (c) 1994 Anthony Dekker 5 | * 6 | * NEUQUANT Neural-Net quantization algorithm by Anthony Dekker, 1994. 7 | * See "Kohonen neural networks for optimal colour quantization" 8 | * in "Network: Computation in Neural Systems" Vol. 5 (1994) pp 351-367. 9 | * for a discussion of the algorithm. 10 | * See also http://members.ozemail.com.au/~dekker/NEUQUANT.HTML 11 | * 12 | * Any party obtaining a copy of these files from the author, directly or 13 | * indirectly, is granted, free of charge, a full and unrestricted irrevocable, 14 | * world-wide, paid up, royalty-free, nonexclusive right and license to deal 15 | * in this software and documentation files (the "Software"), including without 16 | * limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 17 | * and/or sell copies of the Software, and to permit persons who receive 18 | * copies from any such party to do so, with the only requirement being 19 | * that this copyright notice remain intact. 20 | * 21 | * (JavaScript port 2012 by Johan Nordberg) 22 | */ 23 | 24 | function toInt(v) { 25 | return ~~v; 26 | } 27 | 28 | var ncycles = 100; // number of learning cycles 29 | var netsize = 256; // number of colors used 30 | var maxnetpos = netsize - 1; 31 | 32 | // defs for freq and bias 33 | var netbiasshift = 4; // bias for colour values 34 | var intbiasshift = 16; // bias for fractions 35 | var intbias = (1 << intbiasshift); 36 | var gammashift = 10; 37 | var gamma = (1 << gammashift); 38 | var betashift = 10; 39 | var beta = (intbias >> betashift); /* beta = 1/1024 */ 40 | var betagamma = (intbias << (gammashift - betashift)); 41 | 42 | // defs for decreasing radius factor 43 | var initrad = (netsize >> 3); // for 256 cols, radius starts 44 | var radiusbiasshift = 6; // at 32.0 biased by 6 bits 45 | var radiusbias = (1 << radiusbiasshift); 46 | var initradius = (initrad * radiusbias); //and decreases by a 47 | var radiusdec = 30; // factor of 1/30 each cycle 48 | 49 | // defs for decreasing alpha factor 50 | var alphabiasshift = 10; // alpha starts at 1.0 51 | var initalpha = (1 << alphabiasshift); 52 | var alphadec; // biased by 10 bits 53 | 54 | /* radbias and alpharadbias used for radpower calculation */ 55 | var radbiasshift = 8; 56 | var radbias = (1 << radbiasshift); 57 | var alpharadbshift = (alphabiasshift + radbiasshift); 58 | var alpharadbias = (1 << alpharadbshift); 59 | 60 | // four primes near 500 - assume no image has a length so large that it is 61 | // divisible by all four primes 62 | var prime1 = 499; 63 | var prime2 = 491; 64 | var prime3 = 487; 65 | var prime4 = 503; 66 | var minpicturebytes = (3 * prime4); 67 | 68 | /* 69 | Constructor: NeuQuant 70 | 71 | Arguments: 72 | 73 | pixels - array of pixels in RGB format 74 | samplefac - sampling factor 1 to 30 where lower is better quality 75 | 76 | > 77 | > pixels = [r, g, b, r, g, b, r, g, b, ..] 78 | > 79 | */ 80 | function NeuQuant(pixels, samplefac) { 81 | var network; // int[netsize][4] 82 | var netindex; // for network lookup - really 256 83 | 84 | // bias and freq arrays for learning 85 | var bias; 86 | var freq; 87 | var radpower; 88 | 89 | /* 90 | Private Method: init 91 | 92 | sets up arrays 93 | */ 94 | function init() { 95 | network = []; 96 | netindex = []; 97 | bias = []; 98 | freq = []; 99 | radpower = []; 100 | 101 | var i, v; 102 | for (i = 0; i < netsize; i++) { 103 | v = (i << (netbiasshift + 8)) / netsize; 104 | network[i] = [v, v, v]; 105 | freq[i] = intbias / netsize; 106 | bias[i] = 0; 107 | } 108 | } 109 | 110 | /* 111 | Private Method: unbiasnet 112 | 113 | unbiases network to give byte values 0..255 and record position i to prepare for sort 114 | */ 115 | function unbiasnet() { 116 | for (var i = 0; i < netsize; i++) { 117 | network[i][0] >>= netbiasshift; 118 | network[i][1] >>= netbiasshift; 119 | network[i][2] >>= netbiasshift; 120 | network[i][3] = i; // record color number 121 | } 122 | } 123 | 124 | /* 125 | Private Method: altersingle 126 | 127 | moves neuron *i* towards biased (b,g,r) by factor *alpha* 128 | */ 129 | function altersingle(alpha, i, b, g, r) { 130 | network[i][0] -= (alpha * (network[i][0] - b)) / initalpha; 131 | network[i][1] -= (alpha * (network[i][1] - g)) / initalpha; 132 | network[i][2] -= (alpha * (network[i][2] - r)) / initalpha; 133 | } 134 | 135 | /* 136 | Private Method: alterneigh 137 | 138 | moves neurons in *radius* around index *i* towards biased (b,g,r) by factor *alpha* 139 | */ 140 | function alterneigh(radius, i, b, g, r) { 141 | var lo = Math.abs(i - radius); 142 | var hi = Math.min(i + radius, netsize); 143 | 144 | var j = i + 1; 145 | var k = i - 1; 146 | var m = 1; 147 | 148 | var p, a; 149 | while ((j < hi) || (k > lo)) { 150 | a = radpower[m++]; 151 | 152 | if (j < hi) { 153 | p = network[j++]; 154 | p[0] -= (a * (p[0] - b)) / alpharadbias; 155 | p[1] -= (a * (p[1] - g)) / alpharadbias; 156 | p[2] -= (a * (p[2] - r)) / alpharadbias; 157 | } 158 | 159 | if (k > lo) { 160 | p = network[k--]; 161 | p[0] -= (a * (p[0] - b)) / alpharadbias; 162 | p[1] -= (a * (p[1] - g)) / alpharadbias; 163 | p[2] -= (a * (p[2] - r)) / alpharadbias; 164 | } 165 | } 166 | } 167 | 168 | /* 169 | Private Method: contest 170 | 171 | searches for biased BGR values 172 | */ 173 | function contest(b, g, r) { 174 | /* 175 | finds closest neuron (min dist) and updates freq 176 | finds best neuron (min dist-bias) and returns position 177 | for frequently chosen neurons, freq[i] is high and bias[i] is negative 178 | bias[i] = gamma * ((1 / netsize) - freq[i]) 179 | */ 180 | 181 | var bestd = ~(1 << 31); 182 | var bestbiasd = bestd; 183 | var bestpos = -1; 184 | var bestbiaspos = bestpos; 185 | 186 | var i, n, dist, biasdist, betafreq; 187 | for (i = 0; i < netsize; i++) { 188 | n = network[i]; 189 | 190 | dist = Math.abs(n[0] - b) + Math.abs(n[1] - g) + Math.abs(n[2] - r); 191 | if (dist < bestd) { 192 | bestd = dist; 193 | bestpos = i; 194 | } 195 | 196 | biasdist = dist - ((bias[i]) >> (intbiasshift - netbiasshift)); 197 | if (biasdist < bestbiasd) { 198 | bestbiasd = biasdist; 199 | bestbiaspos = i; 200 | } 201 | 202 | betafreq = (freq[i] >> betashift); 203 | freq[i] -= betafreq; 204 | bias[i] += (betafreq << gammashift); 205 | } 206 | 207 | freq[bestpos] += beta; 208 | bias[bestpos] -= betagamma; 209 | 210 | return bestbiaspos; 211 | } 212 | 213 | /* 214 | Private Method: inxbuild 215 | 216 | sorts network and builds netindex[0..255] 217 | */ 218 | function inxbuild() { 219 | var i, j, p, q, smallpos, smallval, previouscol = 0, startpos = 0; 220 | for (i = 0; i < netsize; i++) { 221 | p = network[i]; 222 | smallpos = i; 223 | smallval = p[1]; // index on g 224 | // find smallest in i..netsize-1 225 | for (j = i + 1; j < netsize; j++) { 226 | q = network[j]; 227 | if (q[1] < smallval) { // index on g 228 | smallpos = j; 229 | smallval = q[1]; // index on g 230 | } 231 | } 232 | q = network[smallpos]; 233 | // swap p (i) and q (smallpos) entries 234 | if (i != smallpos) { 235 | j = q[0]; q[0] = p[0]; p[0] = j; 236 | j = q[1]; q[1] = p[1]; p[1] = j; 237 | j = q[2]; q[2] = p[2]; p[2] = j; 238 | j = q[3]; q[3] = p[3]; p[3] = j; 239 | } 240 | // smallval entry is now in position i 241 | 242 | if (smallval != previouscol) { 243 | netindex[previouscol] = (startpos + i) >> 1; 244 | for (j = previouscol + 1; j < smallval; j++) 245 | netindex[j] = i; 246 | previouscol = smallval; 247 | startpos = i; 248 | } 249 | } 250 | netindex[previouscol] = (startpos + maxnetpos) >> 1; 251 | for (j = previouscol + 1; j < 256; j++) 252 | netindex[j] = maxnetpos; // really 256 253 | } 254 | 255 | /* 256 | Private Method: inxsearch 257 | 258 | searches for BGR values 0..255 and returns a color index 259 | */ 260 | function inxsearch(b, g, r) { 261 | var a, p, dist; 262 | 263 | var bestd = 1000; // biggest possible dist is 256*3 264 | var best = -1; 265 | 266 | var i = netindex[g]; // index on g 267 | var j = i - 1; // start at netindex[g] and work outwards 268 | 269 | while ((i < netsize) || (j >= 0)) { 270 | if (i < netsize) { 271 | p = network[i]; 272 | dist = p[1] - g; // inx key 273 | if (dist >= bestd) i = netsize; // stop iter 274 | else { 275 | i++; 276 | if (dist < 0) dist = -dist; 277 | a = p[0] - b; if (a < 0) a = -a; 278 | dist += a; 279 | if (dist < bestd) { 280 | a = p[2] - r; if (a < 0) a = -a; 281 | dist += a; 282 | if (dist < bestd) { 283 | bestd = dist; 284 | best = p[3]; 285 | } 286 | } 287 | } 288 | } 289 | if (j >= 0) { 290 | p = network[j]; 291 | dist = g - p[1]; // inx key - reverse dif 292 | if (dist >= bestd) j = -1; // stop iter 293 | else { 294 | j--; 295 | if (dist < 0) dist = -dist; 296 | a = p[0] - b; if (a < 0) a = -a; 297 | dist += a; 298 | if (dist < bestd) { 299 | a = p[2] - r; if (a < 0) a = -a; 300 | dist += a; 301 | if (dist < bestd) { 302 | bestd = dist; 303 | best = p[3]; 304 | } 305 | } 306 | } 307 | } 308 | } 309 | 310 | return best; 311 | } 312 | 313 | /* 314 | Private Method: learn 315 | 316 | "Main Learning Loop" 317 | */ 318 | function learn() { 319 | var i; 320 | 321 | var lengthcount = pixels.length; 322 | var alphadec = toInt(30 + ((samplefac - 1) / 3)); 323 | var samplepixels = toInt(lengthcount / (3 * samplefac)); 324 | var delta = toInt(samplepixels / ncycles); 325 | var alpha = initalpha; 326 | var radius = initradius; 327 | 328 | var rad = radius >> radiusbiasshift; 329 | 330 | if (rad <= 1) rad = 0; 331 | for (i = 0; i < rad; i++) 332 | radpower[i] = toInt(alpha * (((rad * rad - i * i) * radbias) / (rad * rad))); 333 | 334 | var step; 335 | if (lengthcount < minpicturebytes) { 336 | samplefac = 1; 337 | step = 3; 338 | } else if ((lengthcount % prime1) !== 0) { 339 | step = 3 * prime1; 340 | } else if ((lengthcount % prime2) !== 0) { 341 | step = 3 * prime2; 342 | } else if ((lengthcount % prime3) !== 0) { 343 | step = 3 * prime3; 344 | } else { 345 | step = 3 * prime4; 346 | } 347 | 348 | var b, g, r, j; 349 | var pix = 0; // current pixel 350 | 351 | i = 0; 352 | while (i < samplepixels) { 353 | b = (pixels[pix] & 0xff) << netbiasshift; 354 | g = (pixels[pix + 1] & 0xff) << netbiasshift; 355 | r = (pixels[pix + 2] & 0xff) << netbiasshift; 356 | 357 | j = contest(b, g, r); 358 | 359 | altersingle(alpha, j, b, g, r); 360 | if (rad !== 0) alterneigh(rad, j, b, g, r); // alter neighbours 361 | 362 | pix += step; 363 | if (pix >= lengthcount) pix -= lengthcount; 364 | 365 | i++; 366 | 367 | if (delta === 0) delta = 1; 368 | if (i % delta === 0) { 369 | alpha -= alpha / alphadec; 370 | radius -= radius / radiusdec; 371 | rad = radius >> radiusbiasshift; 372 | 373 | if (rad <= 1) rad = 0; 374 | for (j = 0; j < rad; j++) 375 | radpower[j] = toInt(alpha * (((rad * rad - j * j) * radbias) / (rad * rad))); 376 | } 377 | } 378 | } 379 | 380 | /* 381 | Method: buildColormap 382 | 383 | 1. initializes network 384 | 2. trains it 385 | 3. removes misconceptions 386 | 4. builds colorindex 387 | */ 388 | function buildColormap() { 389 | init(); 390 | learn(); 391 | unbiasnet(); 392 | inxbuild(); 393 | } 394 | this.buildColormap = buildColormap; 395 | 396 | /* 397 | Method: getColormap 398 | 399 | builds colormap from the index 400 | 401 | returns array in the format: 402 | 403 | > 404 | > [r, g, b, r, g, b, r, g, b, ..] 405 | > 406 | */ 407 | function getColormap() { 408 | var map = []; 409 | var index = []; 410 | 411 | for (var i = 0; i < netsize; i++) 412 | index[network[i][3]] = i; 413 | 414 | var k = 0; 415 | for (var l = 0; l < netsize; l++) { 416 | var j = index[l]; 417 | map[k++] = (network[j][0]); 418 | map[k++] = (network[j][1]); 419 | map[k++] = (network[j][2]); 420 | } 421 | return map; 422 | } 423 | this.getColormap = getColormap; 424 | 425 | /* 426 | Method: lookupRGB 427 | 428 | looks for the closest *r*, *g*, *b* color in the map and 429 | returns its index 430 | */ 431 | this.lookupRGB = inxsearch; 432 | } 433 | 434 | module.exports = NeuQuant; 435 | -------------------------------------------------------------------------------- /gifencoder/TypedNeuQuant.js: -------------------------------------------------------------------------------- 1 | /* NeuQuant Neural-Net Quantization Algorithm 2 | * ------------------------------------------ 3 | * 4 | * Copyright (c) 1994 Anthony Dekker 5 | * 6 | * NEUQUANT Neural-Net quantization algorithm by Anthony Dekker, 1994. 7 | * See "Kohonen neural networks for optimal colour quantization" 8 | * in "Network: Computation in Neural Systems" Vol. 5 (1994) pp 351-367. 9 | * for a discussion of the algorithm. 10 | * See also http://members.ozemail.com.au/~dekker/NEUQUANT.HTML 11 | * 12 | * Any party obtaining a copy of these files from the author, directly or 13 | * indirectly, is granted, free of charge, a full and unrestricted irrevocable, 14 | * world-wide, paid up, royalty-free, nonexclusive right and license to deal 15 | * in this software and documentation files (the "Software"), including without 16 | * limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 17 | * and/or sell copies of the Software, and to permit persons who receive 18 | * copies from any such party to do so, with the only requirement being 19 | * that this copyright notice remain intact. 20 | * 21 | * (JavaScript port 2012 by Johan Nordberg) 22 | */ 23 | 24 | var ncycles = 100; // number of learning cycles 25 | var netsize = 256; // number of colors used 26 | var maxnetpos = netsize - 1; 27 | 28 | // defs for freq and bias 29 | var netbiasshift = 4; // bias for colour values 30 | var intbiasshift = 16; // bias for fractions 31 | var intbias = (1 << intbiasshift); 32 | var gammashift = 10; 33 | var gamma = (1 << gammashift); 34 | var betashift = 10; 35 | var beta = (intbias >> betashift); /* beta = 1/1024 */ 36 | var betagamma = (intbias << (gammashift - betashift)); 37 | 38 | // defs for decreasing radius factor 39 | var initrad = (netsize >> 3); // for 256 cols, radius starts 40 | var radiusbiasshift = 6; // at 32.0 biased by 6 bits 41 | var radiusbias = (1 << radiusbiasshift); 42 | var initradius = (initrad * radiusbias); //and decreases by a 43 | var radiusdec = 30; // factor of 1/30 each cycle 44 | 45 | // defs for decreasing alpha factor 46 | var alphabiasshift = 10; // alpha starts at 1.0 47 | var initalpha = (1 << alphabiasshift); 48 | var alphadec; // biased by 10 bits 49 | 50 | /* radbias and alpharadbias used for radpower calculation */ 51 | var radbiasshift = 8; 52 | var radbias = (1 << radbiasshift); 53 | var alpharadbshift = (alphabiasshift + radbiasshift); 54 | var alpharadbias = (1 << alpharadbshift); 55 | 56 | // four primes near 500 - assume no image has a length so large that it is 57 | // divisible by all four primes 58 | var prime1 = 499; 59 | var prime2 = 491; 60 | var prime3 = 487; 61 | var prime4 = 503; 62 | var minpicturebytes = (3 * prime4); 63 | 64 | /* 65 | Constructor: NeuQuant 66 | 67 | Arguments: 68 | 69 | pixels - array of pixels in RGB format 70 | samplefac - sampling factor 1 to 30 where lower is better quality 71 | 72 | > 73 | > pixels = [r, g, b, r, g, b, r, g, b, ..] 74 | > 75 | */ 76 | function NeuQuant(pixels, samplefac) { 77 | var network; // int[netsize][4] 78 | var netindex; // for network lookup - really 256 79 | 80 | // bias and freq arrays for learning 81 | var bias; 82 | var freq; 83 | var radpower; 84 | 85 | /* 86 | Private Method: init 87 | 88 | sets up arrays 89 | */ 90 | function init() { 91 | network = []; 92 | netindex = new Int32Array(256); 93 | bias = new Int32Array(netsize); 94 | freq = new Int32Array(netsize); 95 | radpower = new Int32Array(netsize >> 3); 96 | 97 | var i, v; 98 | for (i = 0; i < netsize; i++) { 99 | v = (i << (netbiasshift + 8)) / netsize; 100 | network[i] = new Float64Array([v, v, v, 0]); 101 | //network[i] = [v, v, v, 0] 102 | freq[i] = intbias / netsize; 103 | bias[i] = 0; 104 | } 105 | } 106 | 107 | /* 108 | Private Method: unbiasnet 109 | 110 | unbiases network to give byte values 0..255 and record position i to prepare for sort 111 | */ 112 | function unbiasnet() { 113 | for (var i = 0; i < netsize; i++) { 114 | network[i][0] >>= netbiasshift; 115 | network[i][1] >>= netbiasshift; 116 | network[i][2] >>= netbiasshift; 117 | network[i][3] = i; // record color number 118 | } 119 | } 120 | 121 | /* 122 | Private Method: altersingle 123 | 124 | moves neuron *i* towards biased (b,g,r) by factor *alpha* 125 | */ 126 | function altersingle(alpha, i, b, g, r) { 127 | network[i][0] -= (alpha * (network[i][0] - b)) / initalpha; 128 | network[i][1] -= (alpha * (network[i][1] - g)) / initalpha; 129 | network[i][2] -= (alpha * (network[i][2] - r)) / initalpha; 130 | } 131 | 132 | /* 133 | Private Method: alterneigh 134 | 135 | moves neurons in *radius* around index *i* towards biased (b,g,r) by factor *alpha* 136 | */ 137 | function alterneigh(radius, i, b, g, r) { 138 | var lo = Math.abs(i - radius); 139 | var hi = Math.min(i + radius, netsize); 140 | 141 | var j = i + 1; 142 | var k = i - 1; 143 | var m = 1; 144 | 145 | var p, a; 146 | while ((j < hi) || (k > lo)) { 147 | a = radpower[m++]; 148 | 149 | if (j < hi) { 150 | p = network[j++]; 151 | p[0] -= (a * (p[0] - b)) / alpharadbias; 152 | p[1] -= (a * (p[1] - g)) / alpharadbias; 153 | p[2] -= (a * (p[2] - r)) / alpharadbias; 154 | } 155 | 156 | if (k > lo) { 157 | p = network[k--]; 158 | p[0] -= (a * (p[0] - b)) / alpharadbias; 159 | p[1] -= (a * (p[1] - g)) / alpharadbias; 160 | p[2] -= (a * (p[2] - r)) / alpharadbias; 161 | } 162 | } 163 | } 164 | 165 | /* 166 | Private Method: contest 167 | 168 | searches for biased BGR values 169 | */ 170 | function contest(b, g, r) { 171 | /* 172 | finds closest neuron (min dist) and updates freq 173 | finds best neuron (min dist-bias) and returns position 174 | for frequently chosen neurons, freq[i] is high and bias[i] is negative 175 | bias[i] = gamma * ((1 / netsize) - freq[i]) 176 | */ 177 | 178 | var bestd = ~(1 << 31); 179 | var bestbiasd = bestd; 180 | var bestpos = -1; 181 | var bestbiaspos = bestpos; 182 | 183 | var i, n, dist, biasdist, betafreq; 184 | for (i = 0; i < netsize; i++) { 185 | n = network[i]; 186 | 187 | dist = Math.abs(n[0] - b) + Math.abs(n[1] - g) + Math.abs(n[2] - r); 188 | if (dist < bestd) { 189 | bestd = dist; 190 | bestpos = i; 191 | } 192 | 193 | biasdist = dist - ((bias[i]) >> (intbiasshift - netbiasshift)); 194 | if (biasdist < bestbiasd) { 195 | bestbiasd = biasdist; 196 | bestbiaspos = i; 197 | } 198 | 199 | betafreq = (freq[i] >> betashift); 200 | freq[i] -= betafreq; 201 | bias[i] += (betafreq << gammashift); 202 | } 203 | 204 | freq[bestpos] += beta; 205 | bias[bestpos] -= betagamma; 206 | 207 | return bestbiaspos; 208 | } 209 | 210 | /* 211 | Private Method: inxbuild 212 | 213 | sorts network and builds netindex[0..255] 214 | */ 215 | function inxbuild() { 216 | var i, j, p, q, smallpos, smallval, previouscol = 0, startpos = 0; 217 | for (i = 0; i < netsize; i++) { 218 | p = network[i]; 219 | smallpos = i; 220 | smallval = p[1]; // index on g 221 | // find smallest in i..netsize-1 222 | for (j = i + 1; j < netsize; j++) { 223 | q = network[j]; 224 | if (q[1] < smallval) { // index on g 225 | smallpos = j; 226 | smallval = q[1]; // index on g 227 | } 228 | } 229 | q = network[smallpos]; 230 | // swap p (i) and q (smallpos) entries 231 | if (i != smallpos) { 232 | j = q[0]; q[0] = p[0]; p[0] = j; 233 | j = q[1]; q[1] = p[1]; p[1] = j; 234 | j = q[2]; q[2] = p[2]; p[2] = j; 235 | j = q[3]; q[3] = p[3]; p[3] = j; 236 | } 237 | // smallval entry is now in position i 238 | 239 | if (smallval != previouscol) { 240 | netindex[previouscol] = (startpos + i) >> 1; 241 | for (j = previouscol + 1; j < smallval; j++) 242 | netindex[j] = i; 243 | previouscol = smallval; 244 | startpos = i; 245 | } 246 | } 247 | netindex[previouscol] = (startpos + maxnetpos) >> 1; 248 | for (j = previouscol + 1; j < 256; j++) 249 | netindex[j] = maxnetpos; // really 256 250 | } 251 | 252 | /* 253 | Private Method: inxsearch 254 | 255 | searches for BGR values 0..255 and returns a color index 256 | */ 257 | function inxsearch(b, g, r) { 258 | var a, p, dist; 259 | 260 | var bestd = 1000; // biggest possible dist is 256*3 261 | var best = -1; 262 | 263 | var i = netindex[g]; // index on g 264 | var j = i - 1; // start at netindex[g] and work outwards 265 | 266 | while ((i < netsize) || (j >= 0)) { 267 | if (i < netsize) { 268 | p = network[i]; 269 | dist = p[1] - g; // inx key 270 | if (dist >= bestd) i = netsize; // stop iter 271 | else { 272 | i++; 273 | if (dist < 0) dist = -dist; 274 | a = p[0] - b; if (a < 0) a = -a; 275 | dist += a; 276 | if (dist < bestd) { 277 | a = p[2] - r; if (a < 0) a = -a; 278 | dist += a; 279 | if (dist < bestd) { 280 | bestd = dist; 281 | best = p[3]; 282 | } 283 | } 284 | } 285 | } 286 | if (j >= 0) { 287 | p = network[j]; 288 | dist = g - p[1]; // inx key - reverse dif 289 | if (dist >= bestd) j = -1; // stop iter 290 | else { 291 | j--; 292 | if (dist < 0) dist = -dist; 293 | a = p[0] - b; if (a < 0) a = -a; 294 | dist += a; 295 | if (dist < bestd) { 296 | a = p[2] - r; if (a < 0) a = -a; 297 | dist += a; 298 | if (dist < bestd) { 299 | bestd = dist; 300 | best = p[3]; 301 | } 302 | } 303 | } 304 | } 305 | } 306 | 307 | return best; 308 | } 309 | 310 | /* 311 | Private Method: learn 312 | 313 | "Main Learning Loop" 314 | */ 315 | function learn() { 316 | var i; 317 | 318 | var lengthcount = pixels.length; 319 | var alphadec = 30 + ((samplefac - 1) / 3); 320 | var samplepixels = lengthcount / (3 * samplefac); 321 | var delta = ~~(samplepixels / ncycles); 322 | var alpha = initalpha; 323 | var radius = initradius; 324 | 325 | var rad = radius >> radiusbiasshift; 326 | 327 | if (rad <= 1) rad = 0; 328 | for (i = 0; i < rad; i++) 329 | radpower[i] = alpha * (((rad * rad - i * i) * radbias) / (rad * rad)); 330 | 331 | var step; 332 | if (lengthcount < minpicturebytes) { 333 | samplefac = 1; 334 | step = 3; 335 | } else if ((lengthcount % prime1) !== 0) { 336 | step = 3 * prime1; 337 | } else if ((lengthcount % prime2) !== 0) { 338 | step = 3 * prime2; 339 | } else if ((lengthcount % prime3) !== 0) { 340 | step = 3 * prime3; 341 | } else { 342 | step = 3 * prime4; 343 | } 344 | 345 | var b, g, r, j; 346 | var pix = 0; // current pixel 347 | 348 | i = 0; 349 | while (i < samplepixels) { 350 | b = (pixels[pix] & 0xff) << netbiasshift; 351 | g = (pixels[pix + 1] & 0xff) << netbiasshift; 352 | r = (pixels[pix + 2] & 0xff) << netbiasshift; 353 | 354 | j = contest(b, g, r); 355 | 356 | altersingle(alpha, j, b, g, r); 357 | if (rad !== 0) alterneigh(rad, j, b, g, r); // alter neighbours 358 | 359 | pix += step; 360 | if (pix >= lengthcount) pix -= lengthcount; 361 | 362 | i++; 363 | 364 | if (delta === 0) delta = 1; 365 | if (i % delta === 0) { 366 | alpha -= alpha / alphadec; 367 | radius -= radius / radiusdec; 368 | rad = radius >> radiusbiasshift; 369 | 370 | if (rad <= 1) rad = 0; 371 | for (j = 0; j < rad; j++) 372 | radpower[j] = alpha * (((rad * rad - j * j) * radbias) / (rad * rad)); 373 | } 374 | } 375 | } 376 | 377 | /* 378 | Method: buildColormap 379 | 380 | 1. initializes network 381 | 2. trains it 382 | 3. removes misconceptions 383 | 4. builds colorindex 384 | */ 385 | function buildColormap() { 386 | init(); 387 | learn(); 388 | unbiasnet(); 389 | inxbuild(); 390 | } 391 | this.buildColormap = buildColormap; 392 | 393 | /* 394 | Method: getColormap 395 | 396 | builds colormap from the index 397 | 398 | returns array in the format: 399 | 400 | > 401 | > [r, g, b, r, g, b, r, g, b, ..] 402 | > 403 | */ 404 | function getColormap() { 405 | var map = []; 406 | var index = []; 407 | 408 | for (var i = 0; i < netsize; i++) 409 | index[network[i][3]] = i; 410 | 411 | var k = 0; 412 | for (var l = 0; l < netsize; l++) { 413 | var j = index[l]; 414 | map[k++] = (network[j][0]); 415 | map[k++] = (network[j][1]); 416 | map[k++] = (network[j][2]); 417 | } 418 | return map; 419 | } 420 | this.getColormap = getColormap; 421 | 422 | /* 423 | Method: lookupRGB 424 | 425 | looks for the closest *r*, *g*, *b* color in the map and 426 | returns its index 427 | */ 428 | this.lookupRGB = inxsearch; 429 | } 430 | 431 | module.exports = NeuQuant; 432 | -------------------------------------------------------------------------------- /gifencoder/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require("./GIFEncoder"); 2 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /* petpet, a Powercord Plugin to create petting gifs 2 | * Copyright 2021 Vendicated 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | 15 | const { Plugin } = require("powercord/entities"); 16 | const { channels, getModule } = require("powercord/webpack"); 17 | const readFile = require("util").promisify(require("fs").readFile); 18 | const { join } = require("path"); 19 | 20 | const GifEncoder = require("./gifencoder"); 21 | 22 | const defaults = { 23 | resolution: 128, 24 | delay: 20 25 | }; 26 | 27 | const emoteRegex = /?/; 28 | 29 | module.exports = class PetPet extends Plugin { 30 | async startPlugin() { 31 | const { promptToUpload } = await getModule(["promptToUpload"]); 32 | const { getUser } = await getModule(["getUser"]); 33 | const { getChannel } = await getModule(["getChannel", "hasChannel"]) 34 | 35 | const res = result => ({ result }); 36 | 37 | powercord.api.commands.registerCommand({ 38 | command: "petpet", 39 | aliases: ["patpat"], 40 | description: "Generate a pet gif", 41 | usage: " [FILE_NAME - default pet.gif] [DELAY - default 20)] [RESOLUTION - default 128]", 42 | executor: async ([url, fileName, delay, resolution]) => { 43 | if (!url) return res(`give me an image or user to pet dummy`); 44 | 45 | if (!url.startsWith("http")) { 46 | const emoteMatch = emoteRegex.exec(url); 47 | if (emoteMatch) { 48 | url = `https://cdn.discordapp.com/emojis/${emoteMatch[3]}.${emoteMatch[1] ? "gif" : "png"}`; 49 | } else { 50 | const id = url.match(/\d{17,19}/)?.[0]; 51 | if (!id) return res("Please specify either a valid url (must start with http(s)), a user or an emote"); 52 | const user = await getUser(id).catch(() => void 0); 53 | if (!user) return res("That user doesn't exist"); 54 | url = user.getAvatarURL(); 55 | if (!url) return res("That user doesn't have an avatar"); 56 | } 57 | } 58 | 59 | const av = await this.loadImage(url, false).catch(() => void 0); 60 | if (!av) return res("Something went wrong while loading that image"); 61 | 62 | const options = {}; 63 | if (delay !== undefined) { 64 | if (typeof (delay = this.parseInt(delay, "delay")) === "string") return res(delay); 65 | options.delay = delay; 66 | } 67 | if (resolution !== undefined) { 68 | if (typeof (resolution = this.parseInt(resolution, "resolution")) === "string") return res(resolution); 69 | options.resolution = resolution; 70 | } 71 | 72 | let buf; 73 | try { 74 | buf = await this.petpet(av, options); 75 | } catch (error) { 76 | this.error(error); 77 | return res("Sorry, something went wrong. Check the console for more info"); 78 | } 79 | 80 | let name; 81 | if (fileName) { 82 | if (!fileName.endsWith(".gif")) fileName += ".gif"; 83 | name = fileName; 84 | } else { 85 | name = "petpet.gif"; 86 | } 87 | const file = new File([buf], name, { type: "image/gif" }); 88 | promptToUpload([file], getChannel(channels.getChannelId()), 0); 89 | } 90 | }); 91 | } 92 | 93 | pluginWillUnload() { 94 | powercord.api.commands.unregisterCommand("petpet"); 95 | } 96 | 97 | parseInt(str, type) { 98 | const n = parseInt(str); 99 | if (isNaN(n)) return `${type} must be a number, received \`${str}\``; 100 | if (n <= 0) return `${type} must be bigger than 0, received \`${str}\``; 101 | return n; 102 | } 103 | 104 | loadImage(src, local) { 105 | return new Promise(async (resolve, reject) => { 106 | const img = new Image(); 107 | img.onload = () => { 108 | if (local) URL.revokeObjectURL(src); 109 | resolve(img); 110 | }; 111 | img.onerror = reject; 112 | if (local) { 113 | try { 114 | const buf = await readFile(src); 115 | const blob = new Blob([buf], { type: "image/gif" }); 116 | src = URL.createObjectURL(blob); 117 | } catch (err) { 118 | reject(err); 119 | } 120 | } else img.crossOrigin = "Anonymous"; 121 | img.src = src; 122 | }); 123 | } 124 | 125 | // Based on https://github.com/aDu/pet-pet-gif, licensed under ISC 126 | async petpet(avatar, options) { 127 | if (!this.frames) 128 | this.frames = await Promise.all( 129 | Array(10) 130 | .fill(null) 131 | .map((_, i) => { 132 | const filename = join(__dirname, "frames", `pet${i}.gif`); 133 | return this.loadImage(filename, true); 134 | }) 135 | ); 136 | 137 | const FRAMES = this.frames.length; 138 | 139 | options = { ...defaults, ...options }; 140 | const encoder = new GifEncoder(options.resolution, options.resolution); 141 | 142 | encoder.start(); 143 | encoder.setRepeat(0); 144 | encoder.setDelay(options.delay); 145 | encoder.setTransparent(); 146 | 147 | const canvas = document.createElement("canvas"); 148 | canvas.width = canvas.height = options.resolution; 149 | const ctx = canvas.getContext("2d"); 150 | 151 | for (let i = 0; i < FRAMES; i++) { 152 | ctx.clearRect(0, 0, canvas.width, canvas.height); 153 | 154 | const j = i < FRAMES / 2 ? i : FRAMES - i; 155 | 156 | const width = 0.8 + j * 0.02; 157 | const height = 0.8 - j * 0.05; 158 | const offsetX = (1 - width) * 0.5 + 0.1; 159 | const offsetY = 1 - height - 0.08; 160 | 161 | ctx.drawImage(avatar, options.resolution * offsetX, options.resolution * offsetY, options.resolution * width, options.resolution * height); 162 | ctx.drawImage(this.frames[i], 0, 0, options.resolution, options.resolution); 163 | 164 | encoder.addFrame(ctx); 165 | } 166 | 167 | encoder.finish(); 168 | return encoder.out.getData(); 169 | } 170 | }; 171 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "petpet", 3 | "version": "1.0.5", 4 | "description": "Generate pet gifs", 5 | "author": "Vendicated", 6 | "license": "Apache-2.0" 7 | } 8 | --------------------------------------------------------------------------------