UNPKG

image-in-browser

Version:

Package for encoding / decoding images, transforming images, applying filters, drawing primitives on images on the client side (no need for server Node.js)

375 lines 14.2 kB
import { NeuralQuantizer } from '../image/neural-quantizer.js'; import { OutputBuffer } from '../common/output-buffer.js'; import { StringUtils } from '../common/string-utils.js'; import { QuantizerType } from '../image/quantizer-type.js'; import { OctreeQuantizer } from '../image/octree-quantizer.js'; import { Filter } from '../filter/filter.js'; import { LibError } from '../error/lib-error.js'; import { DitherKernel } from '../filter/dither-kernel.js'; import { BinaryQuantizer } from '../image/binary-quantizer.js'; export class GifEncoder { get supportsAnimation() { return this._supportsAnimation; } constructor(opt) { var _a, _b, _c, _d, _e; this._curAccum = 0; this._curBits = 0; this._nBits = 0; this._initBits = 0; this._eofCode = 0; this._maxCode = 0; this._clearCode = 0; this._freeEnt = 0; this._clearFlag = false; this._blockSize = 0; this._supportsAnimation = true; this._delay = (_a = opt === null || opt === void 0 ? void 0 : opt.delay) !== null && _a !== void 0 ? _a : 80; this._repeat = (_b = opt === null || opt === void 0 ? void 0 : opt.repeat) !== null && _b !== void 0 ? _b : 0; this._numColors = 256; this._quantizerType = QuantizerType.neural; this._samplingFactor = (_c = opt === null || opt === void 0 ? void 0 : opt.samplingFactor) !== null && _c !== void 0 ? _c : 10; this._dither = (_d = opt === null || opt === void 0 ? void 0 : opt.dither) !== null && _d !== void 0 ? _d : DitherKernel.floydSteinberg; this._ditherSerpentine = (_e = opt === null || opt === void 0 ? void 0 : opt.ditherSerpentine) !== null && _e !== void 0 ? _e : false; this._encodedFrames = 0; } addImage(image, width, height) { if (!image.hasPalette) { throw new LibError('GIF can only encode palette images.'); } const palette = image.palette; const numColors = palette.numColors; const out = this._outputBuffer; out.writeByte(GifEncoder._imageDescRecordType); out.writeUint16(0); out.writeUint16(0); out.writeUint16(width); out.writeUint16(height); const paletteBytes = palette.toUint8Array(); out.writeByte(0x87); const numChannels = palette.numChannels; if (numChannels === 3) { out.writeBytes(paletteBytes); } else if (numChannels === 4) { for (let i = 0, pi = 0; i < numColors; ++i, pi += 4) { out.writeByte(paletteBytes[pi]); out.writeByte(paletteBytes[pi + 1]); out.writeByte(paletteBytes[pi + 2]); } } else if (numChannels === 1 || numChannels === 2) { for (let i = 0, pi = 0; i < numColors; ++i, pi += numChannels) { const g = paletteBytes[pi]; out.writeByte(g); out.writeByte(g); out.writeByte(g); } } for (let i = numColors; i < 256; ++i) { out.writeByte(0); out.writeByte(0); out.writeByte(0); } this.encodeLZW(image); } encodeLZW(image) { this._curAccum = 0; this._curBits = 0; this._blockSize = 0; this._block = new Uint8Array(256); const initCodeSize = 8; this._outputBuffer.writeByte(initCodeSize); const hTab = new Int32Array(GifEncoder._hSize); const codeTab = new Int32Array(GifEncoder._hSize); const pIter = image[Symbol.iterator](); let pIterRes = pIter.next(); this._initBits = initCodeSize + 1; this._nBits = this._initBits; this._maxCode = (1 << this._nBits) - 1; this._clearCode = 1 << (this._initBits - 1); this._eofCode = this._clearCode + 1; this._clearFlag = false; this._freeEnt = this._clearCode + 2; let pFinished = false; const nextPixel = () => { if (pFinished) { return GifEncoder._eof; } const r = Math.trunc(pIterRes.value.index); if (((pIterRes = pIter.next()), pIterRes.done)) { pFinished = true; } return r; }; let ent = nextPixel(); let hShift = 0; for (let fCode = GifEncoder._hSize; fCode < 65536; fCode *= 2) { hShift++; } hShift = 8 - hShift; const hSizeReg = GifEncoder._hSize; for (let i = 0; i < hSizeReg; ++i) { hTab[i] = -1; } this.output(this._clearCode); let outerLoop = true; while (outerLoop) { outerLoop = false; let c = nextPixel(); while (c !== GifEncoder._eof) { const fcode = (c << GifEncoder._bits) + ent; let i = (c << hShift) ^ ent; if (hTab[i] === fcode) { ent = codeTab[i]; c = nextPixel(); continue; } else if (hTab[i] >= 0) { let disp = hSizeReg - i; if (i === 0) { disp = 1; } do { if ((i -= disp) < 0) { i += hSizeReg; } if (hTab[i] === fcode) { ent = codeTab[i]; outerLoop = true; break; } } while (hTab[i] >= 0); if (outerLoop) { break; } } this.output(ent); ent = c; if (this._freeEnt < 1 << GifEncoder._bits) { codeTab[i] = this._freeEnt++; hTab[i] = fcode; } else { for (let i = 0; i < GifEncoder._hSize; ++i) { hTab[i] = -1; } this._freeEnt = this._clearCode + 2; this._clearFlag = true; this.output(this._clearCode); } c = nextPixel(); } } this.output(ent); this.output(this._eofCode); this._outputBuffer.writeByte(0); } output(code) { this._curAccum &= GifEncoder._masks[this._curBits]; if (this._curBits > 0) { this._curAccum |= code << this._curBits; } else { this._curAccum = code; } this._curBits += this._nBits; while (this._curBits >= 8) { this.addToBlock(this._curAccum & 0xff); this._curAccum >>>= 8; this._curBits -= 8; } if (this._freeEnt > this._maxCode || this._clearFlag) { if (this._clearFlag) { this._nBits = this._initBits; this._maxCode = (1 << this._nBits) - 1; this._clearFlag = false; } else { ++this._nBits; if (this._nBits === GifEncoder._bits) { this._maxCode = 1 << GifEncoder._bits; } else { this._maxCode = (1 << this._nBits) - 1; } } } if (code === this._eofCode) { while (this._curBits > 0) { this.addToBlock(this._curAccum & 0xff); this._curAccum >>>= 8; this._curBits -= 8; } this.writeBlock(); } } writeBlock() { if (this._blockSize > 0) { this._outputBuffer.writeByte(this._blockSize); this._outputBuffer.writeBytes(this._block, this._blockSize); this._blockSize = 0; } } addToBlock(c) { this._block[this._blockSize++] = c; if (this._blockSize >= 254) { this.writeBlock(); } } writeApplicationExt() { this._outputBuffer.writeByte(GifEncoder._extensionRecordType); this._outputBuffer.writeByte(GifEncoder._applicationExt); this._outputBuffer.writeByte(11); const appCodeUnits = StringUtils.getCodePoints('NETSCAPE2.0'); this._outputBuffer.writeBytes(appCodeUnits); this._outputBuffer.writeBytes(new Uint8Array([0x03, 0x01])); this._outputBuffer.writeUint16(this._repeat); this._outputBuffer.writeByte(0); } writeGraphicsCtrlExt(image) { var _a; this._outputBuffer.writeByte(GifEncoder._extensionRecordType); this._outputBuffer.writeByte(GifEncoder._graphicControlExt); this._outputBuffer.writeByte(4); let transparentIndex = 0; let hasTransparency = 0; const palette = image.palette; const nc = palette.numChannels; const pa = nc - 1; if (nc === 4 || nc === 2) { const p = palette.toUint8Array(); const l = palette.numColors; for (let i = 0, pi = pa; i < l; ++i, pi += nc) { const a = p[pi]; if (a === 0) { hasTransparency = 1; transparentIndex = i; break; } } } const dispose = 2; const fields = 0 | (dispose << 2) | 0 | hasTransparency; this._outputBuffer.writeByte(fields); this._outputBuffer.writeUint16((_a = this._lastImageDuration) !== null && _a !== void 0 ? _a : this._delay); this._outputBuffer.writeByte(transparentIndex); this._outputBuffer.writeByte(0); } writeHeader(width, height) { const idCodeUnits = StringUtils.getCodePoints(GifEncoder._gif89Id); this._outputBuffer.writeBytes(idCodeUnits); this._outputBuffer.writeUint16(width); this._outputBuffer.writeUint16(height); this._outputBuffer.writeByte(0); this._outputBuffer.writeByte(0); this._outputBuffer.writeByte(0); } finish() { let bytes = undefined; if (this._outputBuffer === undefined) { return bytes; } if (this._encodedFrames === 0) { this.writeHeader(this._width, this._height); this.writeApplicationExt(); } this.writeGraphicsCtrlExt(this._lastImage); this.addImage(this._lastImage, this._width, this._height); this._outputBuffer.writeByte(GifEncoder._terminateRecordType); this._lastImage = undefined; this._lastColorMap = undefined; this._encodedFrames = 0; bytes = this._outputBuffer.getBytes(); this._outputBuffer = undefined; return bytes; } addFrame(image, duration) { if (this._outputBuffer === undefined) { this._outputBuffer = new OutputBuffer(); if (!image.hasPalette) { if (this._quantizerType === QuantizerType.neural) { this._lastColorMap = new NeuralQuantizer(image, this._numColors, this._samplingFactor); } else if (this._quantizerType === QuantizerType.octree) { this._lastColorMap = new OctreeQuantizer(image, this._numColors); } else { this._lastColorMap = new BinaryQuantizer(); } this._lastImage = Filter.ditherImage({ image: image, quantizer: this._lastColorMap, kernel: this._dither, serpentine: this._ditherSerpentine, }); } else { this._lastImage = image; } this._lastImageDuration = duration; this._width = image.width; this._height = image.height; return; } if (this._encodedFrames === 0) { this.writeHeader(this._width, this._height); this.writeApplicationExt(); } this.writeGraphicsCtrlExt(this._lastImage); this.addImage(this._lastImage, this._width, this._height); this._encodedFrames++; if (!image.hasPalette) { if (this._quantizerType === QuantizerType.neural) { this._lastColorMap = new NeuralQuantizer(image, this._numColors, this._samplingFactor); } else if (this._quantizerType === QuantizerType.octree) { this._lastColorMap = new OctreeQuantizer(image, this._numColors); } else { this._lastColorMap = new BinaryQuantizer(); } this._lastImage = Filter.ditherImage({ image: image, quantizer: this._lastColorMap, kernel: this._dither, serpentine: this._ditherSerpentine, }); } else { this._lastImage = image; } this._lastImageDuration = duration; } encode(opt) { var _a; const image = opt.image; const singleFrame = (_a = opt.singleFrame) !== null && _a !== void 0 ? _a : false; if (!image.hasAnimation || singleFrame) { this.addFrame(image); return this.finish(); } this._repeat = image.loopCount; for (const f of image.frames) { this.addFrame(f, Math.trunc(f.frameDuration / 10)); } return this.finish(); } } GifEncoder._gif89Id = 'GIF89a'; GifEncoder._imageDescRecordType = 0x2c; GifEncoder._extensionRecordType = 0x21; GifEncoder._terminateRecordType = 0x3b; GifEncoder._applicationExt = 0xff; GifEncoder._graphicControlExt = 0xf9; GifEncoder._eof = -1; GifEncoder._bits = 12; GifEncoder._hSize = 5003; GifEncoder._masks = [ 0x0000, 0x0001, 0x0003, 0x0007, 0x000f, 0x001f, 0x003f, 0x007f, 0x00ff, 0x01ff, 0x03ff, 0x07ff, 0x0fff, 0x1fff, 0x3fff, 0x7fff, 0xffff, ]; //# sourceMappingURL=gif-encoder.js.map