├── 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 | |  |  |
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 = /(a)?:?(\w{2,32}):(\d{17,19})>?/;
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 |
--------------------------------------------------------------------------------