mirror of
https://github.com/1disk/edp445.git
synced 2024-08-14 22:47:02 +00:00
Changed alot of things.
This commit is contained in:
parent
a5a0523e5a
commit
3513d5390c
2016 changed files with 336930 additions and 9 deletions
562
node_modules/@skyra/gifenc/dist/lib/GifEncoder.js
generated
vendored
Normal file
562
node_modules/@skyra/gifenc/dist/lib/GifEncoder.js
generated
vendored
Normal file
|
|
@ -0,0 +1,562 @@
|
|||
"use strict";
|
||||
/**
|
||||
* GifEncoder
|
||||
*
|
||||
* Authors
|
||||
* - Kevin Weiner (original Java version - kweiner@fmsware.com)
|
||||
* - Thibault Imbert (AS3 version - bytearray.org)
|
||||
* - Johan Nordberg (JS version - code@johan-nordberg.com)
|
||||
* - Eugene Ware (node.js streaming version - eugene@noblesmaurai.com)
|
||||
* - Antonio Román (TS version - kyradiscord@gmail.com)
|
||||
*/
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.GifEncoder = void 0;
|
||||
const stream_1 = require("stream");
|
||||
const util_1 = require("util");
|
||||
const ByteBuffer_1 = require("./ByteBuffer");
|
||||
const LZWEncoder_1 = require("./LZWEncoder");
|
||||
const NeuQuant_1 = require("./NeuQuant");
|
||||
const NOP = () => {
|
||||
// no-op
|
||||
};
|
||||
const GIF_HEADER = new TextEncoder().encode('GIF89a');
|
||||
const NETSCAPE_HEADER = new Uint8Array([0x4e, 0x45, 0x54, 0x53, 0x43, 0x41, 0x50, 0x45, 0x32, 0x2e, 0x30]); // NETSCAPE2.0
|
||||
/**
|
||||
* The color table size (bits - 1).
|
||||
*/
|
||||
const PALETTE_SIZE = 7;
|
||||
class GifEncoder {
|
||||
/**
|
||||
* Constructs the GIF encoder.
|
||||
* @param width An integer representing the GIF image's width, between `1` and `65536`.
|
||||
* @param height An integer representing the GIF image's height, between `1` and `65536`.
|
||||
*/
|
||||
constructor(width, height) {
|
||||
/**
|
||||
* The GIF image's width, between `1` and `65536`.
|
||||
*/
|
||||
Object.defineProperty(this, "width", {
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
writable: true,
|
||||
value: void 0
|
||||
});
|
||||
/**
|
||||
* The GIF image's height, between `1` and `65536`.
|
||||
*/
|
||||
Object.defineProperty(this, "height", {
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
writable: true,
|
||||
value: void 0
|
||||
});
|
||||
/**
|
||||
* The transparent color, `null` if no transparent color is given.
|
||||
*/
|
||||
Object.defineProperty(this, "transparent", {
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
writable: true,
|
||||
value: null
|
||||
});
|
||||
/**
|
||||
* The transparent index in the color table.
|
||||
*/
|
||||
Object.defineProperty(this, "transparentIndex", {
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
writable: true,
|
||||
value: 0
|
||||
});
|
||||
/**
|
||||
* Number between `-1` and `65536`, `-1` indicating no repeat (GIF89a specification), otherwise repeating `repeat`
|
||||
* times with the exception of `0`, which repeats indefinitely.
|
||||
*/
|
||||
Object.defineProperty(this, "repeat", {
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
writable: true,
|
||||
value: -1
|
||||
});
|
||||
/**
|
||||
* Frame delay in hundredths of a second (1 = 10ms).
|
||||
*/
|
||||
Object.defineProperty(this, "delay", {
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
writable: true,
|
||||
value: 0
|
||||
});
|
||||
/**
|
||||
* The current frame.
|
||||
*/
|
||||
Object.defineProperty(this, "image", {
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
writable: true,
|
||||
value: null
|
||||
});
|
||||
/**
|
||||
* The BGR byte array from the current frame.
|
||||
*/
|
||||
Object.defineProperty(this, "pixels", {
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
writable: true,
|
||||
value: null
|
||||
});
|
||||
/**
|
||||
* The converted frame indexed to the palette.
|
||||
*/
|
||||
Object.defineProperty(this, "indexedPixels", {
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
writable: true,
|
||||
value: null
|
||||
});
|
||||
/**
|
||||
* The number of bit planes.
|
||||
*/
|
||||
Object.defineProperty(this, "colorDepth", {
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
writable: true,
|
||||
value: null
|
||||
});
|
||||
/**
|
||||
* The RGB palette.
|
||||
*/
|
||||
Object.defineProperty(this, "colorPalette", {
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
writable: true,
|
||||
value: null
|
||||
});
|
||||
/**
|
||||
* The active palette entries.
|
||||
*/
|
||||
Object.defineProperty(this, "usedEntry", {
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
writable: true,
|
||||
value: []
|
||||
});
|
||||
/**
|
||||
* The disposal code (`-1` = determine defaults).
|
||||
*/
|
||||
Object.defineProperty(this, "disposalMode", {
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
writable: true,
|
||||
value: -1
|
||||
});
|
||||
/**
|
||||
* Whether or not this is the first frame.
|
||||
*/
|
||||
Object.defineProperty(this, "firstFrame", {
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
writable: true,
|
||||
value: true
|
||||
});
|
||||
/**
|
||||
* The sample interval for the quantifier.
|
||||
*/
|
||||
Object.defineProperty(this, "sample", {
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
writable: true,
|
||||
value: 10
|
||||
});
|
||||
/**
|
||||
* Whether or not we started encoding.
|
||||
*/
|
||||
Object.defineProperty(this, "started", {
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
writable: true,
|
||||
value: false
|
||||
});
|
||||
/**
|
||||
* The readable streams.
|
||||
*/
|
||||
Object.defineProperty(this, "readableStreams", {
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
writable: true,
|
||||
value: []
|
||||
});
|
||||
/**
|
||||
* The output buffer.
|
||||
*/
|
||||
Object.defineProperty(this, "byteBuffer", {
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
writable: true,
|
||||
value: new ByteBuffer_1.ByteBuffer()
|
||||
});
|
||||
this.width = ~~width;
|
||||
this.height = ~~height;
|
||||
}
|
||||
createReadStream(readable) {
|
||||
if (!readable) {
|
||||
readable = new stream_1.Readable();
|
||||
readable._read = NOP;
|
||||
}
|
||||
this.readableStreams.push(readable);
|
||||
return readable;
|
||||
}
|
||||
/**
|
||||
* Creates a write stream.
|
||||
* @param options The options for the write stream.
|
||||
* @returns A {@link Duplex}.
|
||||
* @example
|
||||
* ```typescript
|
||||
* const { GifEncoder } = require('@skyra/gifenc');
|
||||
* const encoder = new GifEncoder(400, 200);
|
||||
*
|
||||
* pngStreamGenerator() // A user-defined `Readable`.
|
||||
* .pipe(encoder.createWriteStream({ repeat: -1, delay: 500, quality: 10 }))
|
||||
* .pipe(fs.createWriteStream('runningKitten.gif'));
|
||||
* ```
|
||||
*/
|
||||
createWriteStream(options) {
|
||||
if (options) {
|
||||
if (options.delay !== undefined)
|
||||
this.setDelay(options.delay);
|
||||
if (options.framerate !== undefined)
|
||||
this.setFramerate(options.framerate);
|
||||
if (options.dispose !== undefined)
|
||||
this.setDispose(options.dispose);
|
||||
if (options.repeat !== undefined)
|
||||
this.setRepeat(options.repeat);
|
||||
if (options.transparent !== undefined)
|
||||
this.setTransparent(options.transparent);
|
||||
if (options.quality !== undefined)
|
||||
this.setQuality(options.quality);
|
||||
}
|
||||
const duplex = new stream_1.Duplex({ objectMode: true });
|
||||
duplex._read = NOP;
|
||||
this.createReadStream(duplex);
|
||||
duplex._write = (data, _enc, next) => {
|
||||
if (!this.started)
|
||||
this.start();
|
||||
this.addFrame(data);
|
||||
next();
|
||||
};
|
||||
const end = duplex.end.bind(duplex);
|
||||
// @ts-expect-error This is a Node 17 issue and it should not break using the library
|
||||
duplex.end = (...args) => {
|
||||
end(...args);
|
||||
this.finish();
|
||||
};
|
||||
return duplex;
|
||||
}
|
||||
/**
|
||||
* Sets the delay time between each frame, or changes it for subsequent frames (applies to the next frame added).
|
||||
* @param delay The delay between frames, in milliseconds. Must be a number between `655360` and `10`.
|
||||
*/
|
||||
setDelay(delay) {
|
||||
this.delay = Math.round(delay / 10);
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Sets frame rate in frames per second.
|
||||
* @param fps The amount of frames per second, maximum is `100` frames per second.
|
||||
*/
|
||||
setFramerate(fps) {
|
||||
this.delay = Math.round(100 / fps);
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Sets the GIF frame disposal code for the last added frame and any subsequent frames.
|
||||
*
|
||||
* Defaults to one of the following values:
|
||||
* - `0` : If `transparent` is set
|
||||
* - `2` : Otherwise
|
||||
*
|
||||
* @param disposalCode The disposal code.
|
||||
* @see {@link DisposalCode}
|
||||
*/
|
||||
setDispose(disposalCode) {
|
||||
if (disposalCode >= 0)
|
||||
this.disposalMode = disposalCode;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Sets the number of times the set of GIF frames should be played.
|
||||
* @param repeat The number of times between `-1` and `65536` to repeat the GIF, with two special cases:
|
||||
* - `-1` (**default**): play once
|
||||
* - `0`: repeat indefinitely
|
||||
*
|
||||
* @note This method has no effect after the first image was added.
|
||||
*/
|
||||
setRepeat(repeat) {
|
||||
this.repeat = repeat;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Sets the transparent color for the last added frame and any subsequent frames. Since all colors are subject to
|
||||
* modification in the quantization process, the color in the final palette for each frame closest to the given
|
||||
* color becomes the transparent color for that frame. May be set to null to indicate no transparent color.
|
||||
* @param color The color to be set in transparent pixels.
|
||||
*/
|
||||
setTransparent(color) {
|
||||
this.transparent = color;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Sets the quality of color quantization (conversion of images to the maximum 256 colors allowed by the GIF
|
||||
* specification). Lower values (`minimum` = 1) produce better colors, but slow processing significantly. `10` is
|
||||
* the default, and produces good color mapping at reasonable speeds. Values greater than 20 do not yield
|
||||
* significant improvements in speed.
|
||||
* @param quality A number between `1` and `30`.
|
||||
*/
|
||||
setQuality(quality) {
|
||||
if (quality < 1)
|
||||
quality = 1;
|
||||
this.sample = quality;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Adds the next GIF frame. The frame is not written immediately, but is actually deferred until the next frame is
|
||||
* received so that timing data can be inserted. Calling {@link GifEncoder.finish} will flush all frames.
|
||||
* @param imageData The image data to add into the next frame.
|
||||
*/
|
||||
addFrame(imageData) {
|
||||
if (util_1.types.isUint8ClampedArray(imageData)) {
|
||||
this.image = imageData;
|
||||
}
|
||||
else {
|
||||
this.image = imageData.getImageData(0, 0, this.width, this.height).data;
|
||||
}
|
||||
this.getImagePixels(); // convert to correct format if necessary
|
||||
this.analyzePixels(); // build color table & map pixels
|
||||
if (this.firstFrame) {
|
||||
this.writeLogicalScreenDescriptor(); // logical screen descriptor
|
||||
this.writePalette(); // global color table
|
||||
if (this.repeat >= 0) {
|
||||
// use NS app extension to indicate reps
|
||||
this.writeNetscapeExtension();
|
||||
}
|
||||
}
|
||||
this.writeGraphicControlExtension(); // write graphic control extension
|
||||
this.writeImageDescriptor(); // image descriptor
|
||||
if (!this.firstFrame)
|
||||
this.writePalette(); // local color table
|
||||
this.writePixels(); // encode and write pixel data
|
||||
this.firstFrame = false;
|
||||
this.emit();
|
||||
}
|
||||
/**
|
||||
* Adds final trailer to the GIF stream, if you don't call the finish method the GIF stream will not be valid.
|
||||
*/
|
||||
finish() {
|
||||
this.byteBuffer.writeByte(0x3b); // gif trailer
|
||||
this.end();
|
||||
}
|
||||
/**
|
||||
* Writes the GIF file header
|
||||
*/
|
||||
start() {
|
||||
this.byteBuffer.writeBytes(GIF_HEADER);
|
||||
this.started = true;
|
||||
this.emit();
|
||||
}
|
||||
end() {
|
||||
if (this.readableStreams.length === 0)
|
||||
return;
|
||||
this.emit();
|
||||
for (const stream of this.readableStreams) {
|
||||
stream.push(null);
|
||||
}
|
||||
this.readableStreams = [];
|
||||
}
|
||||
emit() {
|
||||
if (this.readableStreams.length === 0 || this.byteBuffer.length === 0)
|
||||
return;
|
||||
const data = this.byteBuffer.toArray();
|
||||
for (const stream of this.readableStreams) {
|
||||
stream.push(Buffer.from(data));
|
||||
}
|
||||
this.byteBuffer.reset();
|
||||
}
|
||||
/**
|
||||
* Analyzes current frame colors and creates a color map.
|
||||
*/
|
||||
analyzePixels() {
|
||||
const pixels = this.pixels;
|
||||
const pixelByteCount = pixels.length;
|
||||
const pixelCount = pixelByteCount / 3;
|
||||
this.indexedPixels = new Uint8Array(pixelCount);
|
||||
const quantifier = new NeuQuant_1.NeuQuant(pixels, this.sample);
|
||||
this.colorPalette = quantifier.getColorMap();
|
||||
// Map image pixels to new palette:
|
||||
let k = 0;
|
||||
for (let j = 0; j < pixelCount; j++) {
|
||||
const r = pixels[k++] & 0xff;
|
||||
const g = pixels[k++] & 0xff;
|
||||
const b = pixels[k++] & 0xff;
|
||||
const index = quantifier.lookupRGB(r, g, b);
|
||||
this.usedEntry[index] = true;
|
||||
this.indexedPixels[j] = index;
|
||||
}
|
||||
this.pixels = null;
|
||||
this.colorDepth = 8;
|
||||
// Get closest match to transparent color if specified:
|
||||
if (this.transparent === null)
|
||||
return;
|
||||
this.transparentIndex = this.findClosest(this.transparent);
|
||||
// Ensure that pixels with full transparency in the RGBA image are using
|
||||
// the selected transparent color index in the indexed image.
|
||||
for (let pixelIndex = 0; pixelIndex < pixelCount; pixelIndex++) {
|
||||
if (this.image[pixelIndex * 4 + 3] === 0) {
|
||||
this.indexedPixels[pixelIndex] = this.transparentIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Returns index of palette color closest to c.
|
||||
* @param color The color to compare.
|
||||
*/
|
||||
findClosest(color) {
|
||||
if (this.colorPalette === null)
|
||||
return -1;
|
||||
const r = (color & 0xff0000) >> 16;
|
||||
const g = (color & 0x00ff00) >> 8;
|
||||
const b = color & 0x0000ff;
|
||||
let minimumIndex = 0;
|
||||
let distanceMinimum = 256 * 256 * 256;
|
||||
const len = this.colorPalette.length;
|
||||
for (let i = 0; i < len;) {
|
||||
const index = i / 3;
|
||||
const dr = r - (this.colorPalette[i++] & 0xff);
|
||||
const dg = g - (this.colorPalette[i++] & 0xff);
|
||||
const db = b - (this.colorPalette[i++] & 0xff);
|
||||
const d = dr * dr + dg * dg + db * db;
|
||||
if (this.usedEntry[index] && d < distanceMinimum) {
|
||||
distanceMinimum = d;
|
||||
minimumIndex = index;
|
||||
}
|
||||
}
|
||||
return minimumIndex;
|
||||
}
|
||||
/**
|
||||
* Updates {@link GifEncoder.pixels} by creating an RGB-formatted {@link Uint8Array} from the RGBA-formatted data.
|
||||
*/
|
||||
getImagePixels() {
|
||||
const w = this.width;
|
||||
const h = this.height;
|
||||
this.pixels = new Uint8Array(w * h * 3);
|
||||
const data = this.image;
|
||||
for (let i = 0, count = 0; i < h; i++) {
|
||||
for (let j = 0; j < w; j++) {
|
||||
const b = i * w * 4 + j * 4;
|
||||
this.pixels[count++] = data[b];
|
||||
this.pixels[count++] = data[b + 1];
|
||||
this.pixels[count++] = data[b + 2];
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Writes the GCE (Graphic Control Extension).
|
||||
*/
|
||||
writeGraphicControlExtension() {
|
||||
this.byteBuffer.writeByte(0x21); // extension introducer
|
||||
this.byteBuffer.writeByte(0xf9); // GCE label
|
||||
this.byteBuffer.writeByte(4); // data block size
|
||||
let transparency;
|
||||
let dispose;
|
||||
if (this.transparent === null) {
|
||||
transparency = 0;
|
||||
dispose = 0; // dispose = no action
|
||||
}
|
||||
else {
|
||||
transparency = 1;
|
||||
dispose = 2; // force clear if using transparent color
|
||||
}
|
||||
if (this.disposalMode >= 0) {
|
||||
dispose = this.disposalMode & 7; // user override
|
||||
}
|
||||
dispose <<= 2;
|
||||
// Write GCP's packed fields
|
||||
const fields = 0 | // XXX0_0000 : Reserved
|
||||
dispose | // 000X_XX00 : Disposal Method
|
||||
0 | // 0000_00X0 : User Input Flag
|
||||
transparency; // 0000_000X : Transparent Color Flag
|
||||
this.byteBuffer.writeByte(fields);
|
||||
this.writeShort(this.delay); // delay x 1 / 100 sec
|
||||
this.byteBuffer.writeByte(this.transparentIndex); // transparent color index
|
||||
this.byteBuffer.writeByte(0); // block terminator
|
||||
}
|
||||
/**
|
||||
* Writes the ID (Image Descriptor).
|
||||
*/
|
||||
writeImageDescriptor() {
|
||||
this.byteBuffer.writeByte(0x2c); // Image Descriptor block identifier
|
||||
this.writeShort(0); // Image Left Position
|
||||
this.writeShort(0); // Image Top Position
|
||||
this.writeShort(this.width); // Image Width
|
||||
this.writeShort(this.height); // Image Height
|
||||
// Write the LCT (Local Color Table):
|
||||
const fields = this.firstFrame
|
||||
? 0 // The first frame uses the GCT (Global Color Table)
|
||||
: 128 | // X000_0000 : Local Color Table Flag = 1
|
||||
0 | // 0X00_0000 : Interlace Flag = 0
|
||||
0 | // 00X0_0000 : Sort Flag = 0
|
||||
0 | // 000X_X000 : Reserved
|
||||
PALETTE_SIZE; // 0000_0XXX : Size of Local Color Table
|
||||
this.byteBuffer.writeByte(fields);
|
||||
}
|
||||
/**
|
||||
* Writes the LSD (Logical Screen Descriptor)
|
||||
*/
|
||||
writeLogicalScreenDescriptor() {
|
||||
// logical screen size
|
||||
this.writeShort(this.width);
|
||||
this.writeShort(this.height);
|
||||
// Write the GCT (Global Color Table):
|
||||
const fields = 128 | // X000_0000 : GCT (Global Color Table) flag = 1
|
||||
112 | // 0XXX_0000 : Color Resolution = 7
|
||||
0 | // 0000_X000 : GCT sort flag = 0
|
||||
0 | // 0000_0X00 : Reserved
|
||||
PALETTE_SIZE; // 0000_00XX : GCT (Global Color Table) size
|
||||
this.byteBuffer.writeByte(fields);
|
||||
this.byteBuffer.writeByte(0x000000); // background color index
|
||||
this.byteBuffer.writeByte(0); // pixel aspect ratio - assume 1:1
|
||||
}
|
||||
/**
|
||||
* Writes the Netscape application extension to define repeat count.
|
||||
*/
|
||||
writeNetscapeExtension() {
|
||||
// Reference: http://www.vurdalakov.net/misc/gif/netscape-looping-application-extension
|
||||
this.byteBuffer.writeByte(0x21); // Extension
|
||||
this.byteBuffer.writeByte(0xff); // Application Extension
|
||||
this.byteBuffer.writeByte(0x0b); // Block Size
|
||||
this.byteBuffer.writeBytes(NETSCAPE_HEADER); // Application Identifier + Application Authentication Code
|
||||
this.byteBuffer.writeByte(0x03); // Sub-block data size
|
||||
this.byteBuffer.writeByte(0x01); // Sub-block ID
|
||||
this.writeShort(this.repeat); // Loop Count (up to 2 bytes, `0` = repeat forever)
|
||||
this.byteBuffer.writeByte(0); // Block Terminator
|
||||
}
|
||||
/**
|
||||
* Writes the color table palette.
|
||||
*/
|
||||
writePalette() {
|
||||
this.byteBuffer.writeBytes(this.colorPalette);
|
||||
this.byteBuffer.writeTimes(0, 3 * 256 - this.colorPalette.length);
|
||||
}
|
||||
writeShort(pValue) {
|
||||
this.byteBuffer.writeByte(pValue & 0xff);
|
||||
this.byteBuffer.writeByte((pValue >> 8) & 0xff);
|
||||
}
|
||||
/**
|
||||
* Encodes and writes pixel data into {@link GifEncoder.byteBuffer}.
|
||||
*/
|
||||
writePixels() {
|
||||
const enc = new LZWEncoder_1.LZWEncoder(this.width, this.height, this.indexedPixels, this.colorDepth);
|
||||
enc.encode(this.byteBuffer);
|
||||
}
|
||||
}
|
||||
exports.GifEncoder = GifEncoder;
|
||||
//# sourceMappingURL=GifEncoder.js.map
|
||||
Loading…
Add table
Add a link
Reference in a new issue