@engine-room/after-effects-mcp 0.5.2 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/README.md +126 -185
  2. package/bin/server.js +12555 -2477
  3. package/package.json +8 -8
  4. package/panel/CSXS/manifest.xml +44 -4
  5. package/panel/META-INF/signatures.xml +182 -0
  6. package/panel/client/csinterface.js +32 -0
  7. package/panel/client/main.js +522 -632
  8. package/panel/client/panelhash.js +79 -0
  9. package/panel/client/status.html +27 -0
  10. package/panel/client/status.js +112 -0
  11. package/panel/jsx/bundle.jsx +8957 -2514
  12. package/panel/mimetype +1 -0
  13. package/panel/node_modules/ws/LICENSE +20 -0
  14. package/panel/node_modules/ws/README.md +548 -0
  15. package/panel/node_modules/ws/browser.js +8 -0
  16. package/panel/node_modules/ws/index.js +22 -0
  17. package/panel/node_modules/ws/lib/buffer-util.js +131 -0
  18. package/panel/node_modules/ws/lib/constants.js +19 -0
  19. package/panel/node_modules/ws/lib/event-target.js +292 -0
  20. package/panel/node_modules/ws/lib/extension.js +203 -0
  21. package/panel/node_modules/ws/lib/limiter.js +55 -0
  22. package/panel/node_modules/ws/lib/permessage-deflate.js +530 -0
  23. package/panel/node_modules/ws/lib/receiver.js +743 -0
  24. package/panel/node_modules/ws/lib/sender.js +607 -0
  25. package/panel/node_modules/ws/lib/stream.js +161 -0
  26. package/panel/node_modules/ws/lib/subprotocol.js +62 -0
  27. package/panel/node_modules/ws/lib/validation.js +152 -0
  28. package/panel/node_modules/ws/lib/websocket-server.js +562 -0
  29. package/panel/node_modules/ws/lib/websocket.js +1421 -0
  30. package/panel/node_modules/ws/package.json +74 -0
  31. package/panel/node_modules/ws/wrapper.mjs +21 -0
  32. package/panel/package.json +2 -2
  33. package/panel/client/contactsheet.js +0 -347
  34. package/panel/client/framecache.js +0 -98
  35. package/panel/client/framereader.js +0 -248
  36. package/panel/client/mogrt.js +0 -328
  37. package/panel/client/pngcodec.js +0 -524
@@ -1,524 +0,0 @@
1
- // pngcodec.js — the panel's PNG normaliser.
2
- //
3
- // After Effects writes screenshot frames at the project's colour depth, so a
4
- // 16-bit project produces 16-bit-per-channel PNGs. Plenty of decoders — the ones
5
- // on the far side of an MCP image content block included — reject those outright
6
- // with "Could not process image", so a perfectly good render arrives useless.
7
- // This converts to 8 bits per channel before the panel base64-encodes, and
8
- // passes anything already 8-bit through byte-for-byte untouched.
9
- //
10
- // It also does the two other things that need the pixels rather than the file:
11
- //
12
- // * reports a frame whose every pixel is fully transparent, because "this
13
- // frame is empty" is the useful reading of a ~5KB PNG that decoders choke
14
- // on, and it usually means the caller is looking at the wrong time or a
15
- // disabled layer;
16
- // * hands back the decoded samples so the caller can hash them. Two different
17
- // screenshot requests that produce byte-identical pixels are how a stale
18
- // render buffer shows itself (see framecache.js).
19
- //
20
- // Cost, since it is paid on every screenshot and not only on 16-bit ones: an
21
- // already-8-bit frame is still inflated and un-filtered, because that is the
22
- // only way to know whether it is empty and the only hash that cannot be thrown
23
- // off by an encoder writing the same picture two ways. That is one pass over
24
- // width*height*channels bytes — negligible at the downsample the guides ask
25
- // for, and small next to a render measured in seconds even at full 4K. If it
26
- // ever stops being negligible, note that an empty frame compresses to a few KB,
27
- // so a size threshold could skip the decode and fall back to the IDAT hash.
28
- //
29
- // A CommonJS module on purpose: `client/main.js` requires it inside CEP's mixed
30
- // context, and `tests/unit/png-codec.mjs` requires the same file under plain
31
- // Node. There is no After Effects on a CI runner and this is real image code, so
32
- // it has to be exercisable without one. Node builtins only — adding a runtime
33
- // dependency here would mean shipping another directory into the CEP extension,
34
- // which is exactly the problem `ws` already causes.
35
-
36
- "use strict";
37
-
38
- var zlib = require("zlib");
39
-
40
- var SIGNATURE = [137, 80, 78, 71, 13, 10, 26, 10];
41
-
42
- // Deflate level: the output is a diagnostic image that has to fit in an agent's
43
- // context, so size matters — but a 4K frame is 33MB of samples and level 9 costs
44
- // seconds on top of a render that already took seconds. 6 is within a few
45
- // percent of 9 here and several times faster.
46
- var DEFLATE_LEVEL = 6;
47
-
48
- function channelsFor(colorType) {
49
- if (colorType === 0) return 1; // greyscale
50
- if (colorType === 2) return 3; // truecolour
51
- if (colorType === 3) return 1; // indexed
52
- if (colorType === 4) return 2; // greyscale + alpha
53
- if (colorType === 6) return 4; // truecolour + alpha
54
- return 0;
55
- }
56
-
57
- function alphaIndexFor(colorType) {
58
- if (colorType === 4) return 1;
59
- if (colorType === 6) return 3;
60
- return -1;
61
- }
62
-
63
- // ---------- CRC-32, as specified in the PNG spec ----------
64
- var CRC_TABLE = (function () {
65
- var table = new Int32Array(256);
66
- for (var n = 0; n < 256; n++) {
67
- var c = n;
68
- for (var k = 0; k < 8; k++) c = (c & 1) ? (0xedb88320 ^ (c >>> 1)) : (c >>> 1);
69
- table[n] = c;
70
- }
71
- return table;
72
- })();
73
-
74
- function crc32(bytes) {
75
- var c = 0xffffffff;
76
- for (var i = 0; i < bytes.length; i++) c = CRC_TABLE[(c ^ bytes[i]) & 0xff] ^ (c >>> 8);
77
- return (c ^ 0xffffffff) >>> 0;
78
- }
79
-
80
- // ---------- Structural completeness ----------
81
- /**
82
- * Is this byte range a whole PNG file yet?
83
- *
84
- * Answered by walking the chunk table alone — no inflate, no pixels — because
85
- * the caller is asking about a file After Effects may still be writing, and the
86
- * question has to be cheap enough to ask repeatedly.
87
- *
88
- * It exists because "the file stopped growing" was never the same statement as
89
- * "the file is finished". `saveFrameToPng` returns before the bytes are on disk,
90
- * and the panel used to accept a frame the moment two `stat` calls 30ms apart
91
- * reported the same size. A writer that pauses for 30ms — which on a heavy comp
92
- * it routinely does — was read mid-flight and shipped, and the agent got
93
- * `truncated PNG: chunk IDAT runs past the end of the file` for a render that
94
- * was merely still happening. Issue #45.
95
- *
96
- * `growable` matters as much as `complete`: it separates "not finished yet,
97
- * keep waiting" from "no number of further bytes can make this a PNG, stop
98
- * now". A truncated chunk is the first; a wrong signature or a missing IHDR is
99
- * the second, and waiting out a 120s render budget for one of those only turns
100
- * a precise diagnosis into a timeout.
101
- *
102
- * Returns { complete, growable, reason, bytes, lastChunk, trailingBytes }.
103
- */
104
- function inspectPngStructure(buf) {
105
- var n = buf.length;
106
- var prefix = n < SIGNATURE.length ? n : SIGNATURE.length;
107
- for (var i = 0; i < prefix; i++) {
108
- if (buf[i] !== SIGNATURE[i]) {
109
- return {
110
- complete: false,
111
- growable: false,
112
- bytes: n,
113
- reason: "not a PNG: signature mismatch at byte " + i,
114
- };
115
- }
116
- }
117
- if (n < SIGNATURE.length) {
118
- return {
119
- complete: false,
120
- growable: true,
121
- bytes: n,
122
- reason: "only " + n + " of the 8 signature bytes are on disk",
123
- };
124
- }
125
-
126
- var pos = SIGNATURE.length;
127
- var first = true;
128
- while (true) {
129
- if (pos + 8 > n) {
130
- return {
131
- complete: false,
132
- growable: true,
133
- bytes: n,
134
- reason: "the chunk header at byte " + pos + " is incomplete",
135
- };
136
- }
137
- var len = buf.readUInt32BE(pos);
138
- var type = buf.toString("ascii", pos + 4, pos + 8);
139
- if (!/^[A-Za-z]{4}$/.test(type)) {
140
- return {
141
- complete: false,
142
- growable: false,
143
- bytes: n,
144
- reason: "the chunk type at byte " + pos + " is not four letters",
145
- };
146
- }
147
- // The format caps a chunk at 2^31-1. A larger figure is garbage, not a
148
- // chunk still arriving, and calling it growable would spend the whole
149
- // render budget waiting for a file that is already wrong.
150
- if (len > 0x7fffffff) {
151
- return {
152
- complete: false,
153
- growable: false,
154
- bytes: n,
155
- lastChunk: type,
156
- reason: "chunk " + type + " declares " + len + " bytes, past the format's limit",
157
- };
158
- }
159
- if (first && type !== "IHDR") {
160
- return {
161
- complete: false,
162
- growable: false,
163
- bytes: n,
164
- lastChunk: type,
165
- reason: "malformed PNG: the first chunk is " + type + ", not IHDR",
166
- };
167
- }
168
- first = false;
169
- var end = pos + 8 + len + 4;
170
- if (end > n) {
171
- return {
172
- complete: false,
173
- growable: true,
174
- bytes: n,
175
- lastChunk: type,
176
- reason: "chunk " + type + " declares " + len + " bytes and " +
177
- Math.max(0, n - pos - 8) + " of them are on disk",
178
- };
179
- }
180
- if (type === "IEND") {
181
- return {
182
- complete: true,
183
- growable: false,
184
- bytes: n,
185
- lastChunk: "IEND",
186
- reason: null,
187
- trailingBytes: n - end,
188
- };
189
- }
190
- pos = end;
191
- }
192
- }
193
-
194
- // ---------- Reading ----------
195
- function readChunks(buf) {
196
- if (buf.length < 8) throw new Error("not a PNG: only " + buf.length + " bytes");
197
- for (var i = 0; i < 8; i++) {
198
- if (buf[i] !== SIGNATURE[i]) throw new Error("not a PNG: signature mismatch at byte " + i);
199
- }
200
- var chunks = [];
201
- var pos = 8;
202
- while (pos + 8 <= buf.length) {
203
- var len = buf.readUInt32BE(pos);
204
- var type = buf.toString("ascii", pos + 4, pos + 8);
205
- var dataStart = pos + 8;
206
- var dataEnd = dataStart + len;
207
- if (dataEnd + 4 > buf.length) {
208
- throw new Error("truncated PNG: chunk " + type + " runs past the end of the file");
209
- }
210
- chunks.push({ type: type, data: buf.subarray(dataStart, dataEnd) });
211
- pos = dataEnd + 4;
212
- if (type === "IEND") return chunks;
213
- }
214
- throw new Error("truncated PNG: no IEND chunk");
215
- }
216
-
217
- function parseHeader(chunks) {
218
- if (!chunks.length || chunks[0].type !== "IHDR") {
219
- throw new Error("malformed PNG: IHDR is not the first chunk");
220
- }
221
- var d = chunks[0].data;
222
- if (d.length !== 13) throw new Error("malformed PNG: IHDR is " + d.length + " bytes, expected 13");
223
- var h = {
224
- width: d.readUInt32BE(0),
225
- height: d.readUInt32BE(4),
226
- bitDepth: d[8],
227
- colorType: d[9],
228
- compression: d[10],
229
- filter: d[11],
230
- interlace: d[12],
231
- };
232
- if (h.width === 0 || h.height === 0) {
233
- throw new Error("malformed PNG: " + h.width + "x" + h.height);
234
- }
235
- return h;
236
- }
237
-
238
- // ---------- Filtering ----------
239
- function paeth(a, b, c) {
240
- var p = a + b - c;
241
- var pa = p > a ? p - a : a - p;
242
- var pb = p > b ? p - b : b - p;
243
- var pc = p > c ? p - c : c - p;
244
- if (pa <= pb && pa <= pc) return a;
245
- if (pb <= pc) return b;
246
- return c;
247
- }
248
-
249
- // Reverse the per-scanline filters into a flat sample buffer. `raw` is the
250
- // inflated IDAT stream: one filter-type byte followed by rowBytes of data, per
251
- // row. Every filter reads back from bytes already reconstructed, so this has to
252
- // run in order and read out of `out` rather than out of `raw`.
253
- function unfilter(raw, width, height, bitDepth, channels) {
254
- var bpp = Math.ceil((bitDepth * channels) / 8);
255
- var rowBytes = Math.ceil((bitDepth * channels * width) / 8);
256
- var expected = (rowBytes + 1) * height;
257
- if (raw.length < expected) {
258
- throw new Error("PNG pixel data is " + raw.length + " bytes, expected " + expected);
259
- }
260
- var out = Buffer.alloc(rowBytes * height);
261
- var pos = 0;
262
- for (var y = 0; y < height; y++) {
263
- var filter = raw[pos];
264
- pos += 1;
265
- var o = y * rowBytes;
266
- var up = o - rowBytes;
267
- for (var x = 0; x < rowBytes; x++) {
268
- var cur = raw[pos + x];
269
- var a = x >= bpp ? out[o + x - bpp] : 0;
270
- var b = y > 0 ? out[up + x] : 0;
271
- var c = (x >= bpp && y > 0) ? out[up + x - bpp] : 0;
272
- var v;
273
- if (filter === 0) v = cur;
274
- else if (filter === 1) v = cur + a;
275
- else if (filter === 2) v = cur + b;
276
- else if (filter === 3) v = cur + ((a + b) >> 1);
277
- else if (filter === 4) v = cur + paeth(a, b, c);
278
- else throw new Error("unknown PNG row filter " + filter + " on row " + y);
279
- out[o + x] = v & 0xff;
280
- }
281
- pos += rowBytes;
282
- }
283
- return { data: out, rowBytes: rowBytes, bpp: bpp };
284
- }
285
-
286
- // Re-apply a filter on the way out. Paeth on every row rather than picking the
287
- // cheapest of the five per row: one pass instead of five, and on rendered frames
288
- // it is within a few percent of the best choice.
289
- function filterRowsPaeth(data, height, rowBytes, bpp) {
290
- var out = Buffer.alloc((rowBytes + 1) * height);
291
- var pos = 0;
292
- for (var y = 0; y < height; y++) {
293
- out[pos] = 4;
294
- pos += 1;
295
- var o = y * rowBytes;
296
- var up = o - rowBytes;
297
- for (var x = 0; x < rowBytes; x++) {
298
- var a = x >= bpp ? data[o + x - bpp] : 0;
299
- var b = y > 0 ? data[up + x] : 0;
300
- var c = (x >= bpp && y > 0) ? data[up + x - bpp] : 0;
301
- out[pos + x] = (data[o + x] - paeth(a, b, c)) & 0xff;
302
- }
303
- pos += rowBytes;
304
- }
305
- return out;
306
- }
307
-
308
- // ---------- Writing ----------
309
- function chunk(type, data) {
310
- var out = Buffer.alloc(data.length + 12);
311
- out.writeUInt32BE(data.length, 0);
312
- out.write(type, 4, 4, "ascii");
313
- data.copy(out, 8);
314
- out.writeUInt32BE(crc32(out.subarray(4, 8 + data.length)), 8 + data.length);
315
- return out;
316
- }
317
-
318
- /**
319
- * Emit an 8-bit PNG from flat samples. Ancillary chunks from the source are
320
- * deliberately not carried over: the ones that would matter (tRNS, sBIT, bKGD)
321
- * hold bit-depth-dependent values that would be wrong at 8 bits, and the rest
322
- * describe colour intent that no decoder of a diagnostic screenshot acts on.
323
- */
324
- function encodePng8(width, height, colorType, data8) {
325
- var channels = channelsFor(colorType);
326
- if (channels === 0) throw new Error("cannot encode PNG colour type " + colorType);
327
- var rowBytes = width * channels;
328
- if (data8.length < rowBytes * height) {
329
- throw new Error("sample buffer is " + data8.length + " bytes, expected " + rowBytes * height);
330
- }
331
- var ihdr = Buffer.alloc(13);
332
- ihdr.writeUInt32BE(width, 0);
333
- ihdr.writeUInt32BE(height, 4);
334
- ihdr[8] = 8; // bit depth
335
- ihdr[9] = colorType;
336
- ihdr[10] = 0; // deflate
337
- ihdr[11] = 0; // adaptive filtering
338
- ihdr[12] = 0; // no interlace
339
- var idat = zlib.deflateSync(filterRowsPaeth(data8, height, rowBytes, channels), {
340
- level: DEFLATE_LEVEL,
341
- });
342
- return Buffer.concat([
343
- Buffer.from(SIGNATURE),
344
- chunk("IHDR", ihdr),
345
- chunk("IDAT", idat),
346
- chunk("IEND", Buffer.alloc(0)),
347
- ]);
348
- }
349
-
350
- // 16-bit samples are big-endian pairs, and the high byte *is* the 8-bit value:
351
- // promoting 8-bit to 16-bit multiplies by 257 (0x7f -> 0x7f7f), so taking the
352
- // high byte is exact for anything that started life at 8 bits and off by at most
353
- // 1/255 for anything that did not. Rounding through /257 would cost a divide per
354
- // sample and change nothing anybody looking at a screenshot could see.
355
- function narrow16to8(data16, sampleCount) {
356
- var out = Buffer.alloc(sampleCount);
357
- for (var i = 0; i < sampleCount; i++) out[i] = data16[i * 2];
358
- return out;
359
- }
360
-
361
- function everyPixelTransparent(data8, channels, alphaIndex, pixelCount) {
362
- for (var i = 0; i < pixelCount; i++) {
363
- if (data8[i * channels + alphaIndex] !== 0) return false;
364
- }
365
- return true;
366
- }
367
-
368
- /**
369
- * Normalise one PNG for delivery to an agent.
370
- *
371
- * Returns:
372
- * buffer the PNG to send, or null when `empty` — an empty frame is
373
- * reported, never shipped, so there is nothing to hand out
374
- * width/height from IHDR, so they can never disagree with the pixels
375
- * bitDepth of the *source*, before any conversion
376
- * converted true when the bytes were re-encoded from 16-bit
377
- * channels samples per pixel, for a caller that composites hashInput
378
- * decoded true when the pixels were actually interpreted
379
- * empty true when every pixel is fully transparent
380
- * hashInput bytes to hash for the stale-render check
381
- * hashBasis "pixels" when hashInput is decoded samples, "idat" when it is
382
- * the compressed stream (metadata-free either way, so an
383
- * embedded timestamp can never make two identical frames differ)
384
- * passthrough why the pixels were not inspected, or null
385
- *
386
- * Throws on anything that is not a readable PNG. The caller is expected to treat
387
- * that as "could not normalise", not as "the screenshot failed" — the render did
388
- * happen, and shipping it unconverted with a warning beats discarding it.
389
- */
390
- function normalizePng(buf) {
391
- if (!Buffer.isBuffer(buf)) throw new Error("normalizePng expects a Buffer");
392
- var chunks = readChunks(buf);
393
- var h = parseHeader(chunks);
394
-
395
- var idatParts = [];
396
- for (var i = 0; i < chunks.length; i++) {
397
- if (chunks[i].type === "IDAT") idatParts.push(chunks[i].data);
398
- }
399
- if (!idatParts.length) throw new Error("malformed PNG: no IDAT chunk");
400
- var idat = Buffer.concat(idatParts);
401
-
402
- var channels = channelsFor(h.colorType);
403
- var result = {
404
- buffer: buf,
405
- width: h.width,
406
- height: h.height,
407
- bitDepth: h.bitDepth,
408
- colorType: h.colorType,
409
- // Samples per pixel, so a caller compositing `hashInput` into a contact
410
- // sheet does not have to re-derive it from the colour type.
411
- channels: channels,
412
- converted: false,
413
- decoded: false,
414
- empty: false,
415
- hashInput: idat,
416
- hashBasis: "idat",
417
- passthrough: null,
418
- };
419
-
420
- if (h.compression !== 0 || h.filter !== 0) {
421
- throw new Error("unsupported PNG: compression " + h.compression + ", filter method " + h.filter);
422
- }
423
- if (channels === 0) throw new Error("unsupported PNG colour type " + h.colorType);
424
-
425
- // Adam7 interlacing, palettes and sub-byte samples all need machinery that
426
- // would be written blind for a case saveFrameToPng does not produce. Pass the
427
- // file through untouched and record why the pixels were not read, rather than
428
- // guessing at them — the stale check still works off the IDAT bytes.
429
- if (h.interlace !== 0) {
430
- result.passthrough = "interlaced";
431
- return result;
432
- }
433
- if (h.colorType === 3) {
434
- result.passthrough = "indexed colour";
435
- return result;
436
- }
437
- if (h.bitDepth !== 8 && h.bitDepth !== 16) {
438
- result.passthrough = h.bitDepth + "-bit samples";
439
- return result;
440
- }
441
-
442
- var un = unfilter(zlib.inflateSync(idat), h.width, h.height, h.bitDepth, channels);
443
- var pixels = h.bitDepth === 16
444
- ? narrow16to8(un.data, h.width * h.height * channels)
445
- : un.data;
446
-
447
- result.decoded = true;
448
- result.hashInput = pixels;
449
- result.hashBasis = "pixels";
450
-
451
- var alphaIndex = alphaIndexFor(h.colorType);
452
- if (alphaIndex >= 0 && everyPixelTransparent(pixels, channels, alphaIndex, h.width * h.height)) {
453
- // Nothing to send, so nothing is encoded — and `buffer: null` makes it
454
- // impossible for a caller to ship the frame by forgetting to check `empty`.
455
- result.empty = true;
456
- result.buffer = null;
457
- return result;
458
- }
459
-
460
- if (h.bitDepth === 16) {
461
- result.buffer = encodePng8(h.width, h.height, h.colorType, pixels);
462
- result.converted = true;
463
- }
464
- return result;
465
- }
466
-
467
- /**
468
- * Decode a PNG to flat 8-bit samples.
469
- *
470
- * `normalizePng` deliberately passes the awkward encodings through untouched,
471
- * because a screenshot it cannot read is still a screenshot the client might.
472
- * A caller that is going to *resample* the pixels has no such fallback — half a
473
- * decode is not a smaller picture, it is a wrong one — so this throws on
474
- * anything it cannot interpret exactly.
475
- *
476
- * Returns { width, height, colorType, channels, bitDepth, pixels }, where
477
- * `pixels` is width*height*channels bytes and `bitDepth` is the source's.
478
- */
479
- function decodePng8(buf) {
480
- if (!Buffer.isBuffer(buf)) throw new Error("decodePng8 expects a Buffer");
481
- var chunks = readChunks(buf);
482
- var h = parseHeader(chunks);
483
- var channels = channelsFor(h.colorType);
484
-
485
- if (h.compression !== 0 || h.filter !== 0) {
486
- throw new Error("unsupported PNG: compression " + h.compression + ", filter method " + h.filter);
487
- }
488
- if (channels === 0) throw new Error("unsupported PNG colour type " + h.colorType);
489
- if (h.interlace !== 0) throw new Error("unsupported PNG: interlaced");
490
- if (h.colorType === 3) throw new Error("unsupported PNG: indexed colour");
491
- if (h.bitDepth !== 8 && h.bitDepth !== 16) throw new Error("unsupported PNG: " + h.bitDepth + "-bit samples");
492
-
493
- var idatParts = [];
494
- for (var i = 0; i < chunks.length; i++) {
495
- if (chunks[i].type === "IDAT") idatParts.push(chunks[i].data);
496
- }
497
- if (!idatParts.length) throw new Error("malformed PNG: no IDAT chunk");
498
-
499
- var un = unfilter(zlib.inflateSync(Buffer.concat(idatParts)), h.width, h.height, h.bitDepth, channels);
500
- var pixels = h.bitDepth === 16 ? narrow16to8(un.data, h.width * h.height * channels) : un.data;
501
-
502
- return {
503
- width: h.width,
504
- height: h.height,
505
- colorType: h.colorType,
506
- channels: channels,
507
- bitDepth: h.bitDepth,
508
- pixels: pixels,
509
- };
510
- }
511
-
512
- module.exports = {
513
- normalizePng: normalizePng,
514
- // For main.js, which has to know whether the file After Effects is writing is
515
- // finished before it reads it. It lives here because the chunk walk and the
516
- // signature it walks from are already defined once, above.
517
- inspectPngStructure: inspectPngStructure,
518
- // For mogrt.js, which resamples a rendered frame into a template's thumbnail
519
- // and needs the same decoder, encoder and CRC rather than a second copy of
520
- // each. The zip format's CRC-32 is the one PNG uses, bit for bit.
521
- decodePng8: decodePng8,
522
- encodePng8: encodePng8,
523
- crc32: crc32,
524
- };