V1_commit_RGC
This commit is contained in:
508
SuiviREForamteur/node_modules/fast-png/lib/PngDecoder.js
generated
vendored
Normal file
508
SuiviREForamteur/node_modules/fast-png/lib/PngDecoder.js
generated
vendored
Normal file
@@ -0,0 +1,508 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const iobuffer_1 = require("iobuffer");
|
||||
const pako_1 = require("pako");
|
||||
const crc_1 = require("./helpers/crc");
|
||||
const decodeInterlaceAdam7_1 = require("./helpers/decodeInterlaceAdam7");
|
||||
const decodeInterlaceNull_1 = require("./helpers/decodeInterlaceNull");
|
||||
const signature_1 = require("./helpers/signature");
|
||||
const text_1 = require("./helpers/text");
|
||||
const internalTypes_1 = require("./internalTypes");
|
||||
class PngDecoder extends iobuffer_1.IOBuffer {
|
||||
_checkCrc;
|
||||
_inflator;
|
||||
_png;
|
||||
_apng;
|
||||
_end;
|
||||
_hasPalette;
|
||||
_palette;
|
||||
_hasTransparency;
|
||||
_transparency;
|
||||
_compressionMethod;
|
||||
_filterMethod;
|
||||
_interlaceMethod;
|
||||
_colorType;
|
||||
_isAnimated;
|
||||
_numberOfFrames;
|
||||
_numberOfPlays;
|
||||
_frames;
|
||||
_writingDataChunks;
|
||||
constructor(data, options = {}) {
|
||||
super(data);
|
||||
const { checkCrc = false } = options;
|
||||
this._checkCrc = checkCrc;
|
||||
this._inflator = new pako_1.Inflate();
|
||||
this._png = {
|
||||
width: -1,
|
||||
height: -1,
|
||||
channels: -1,
|
||||
data: new Uint8Array(0),
|
||||
depth: 1,
|
||||
text: {},
|
||||
};
|
||||
this._apng = {
|
||||
width: -1,
|
||||
height: -1,
|
||||
channels: -1,
|
||||
depth: 1,
|
||||
numberOfFrames: 1,
|
||||
numberOfPlays: 0,
|
||||
text: {},
|
||||
frames: [],
|
||||
};
|
||||
this._end = false;
|
||||
this._hasPalette = false;
|
||||
this._palette = [];
|
||||
this._hasTransparency = false;
|
||||
this._transparency = new Uint16Array(0);
|
||||
this._compressionMethod = internalTypes_1.CompressionMethod.UNKNOWN;
|
||||
this._filterMethod = internalTypes_1.FilterMethod.UNKNOWN;
|
||||
this._interlaceMethod = internalTypes_1.InterlaceMethod.UNKNOWN;
|
||||
this._colorType = internalTypes_1.ColorType.UNKNOWN;
|
||||
this._isAnimated = false;
|
||||
this._numberOfFrames = 1;
|
||||
this._numberOfPlays = 0;
|
||||
this._frames = [];
|
||||
this._writingDataChunks = false;
|
||||
// PNG is always big endian
|
||||
// https://www.w3.org/TR/PNG/#7Integers-and-byte-order
|
||||
this.setBigEndian();
|
||||
}
|
||||
decode() {
|
||||
(0, signature_1.checkSignature)(this);
|
||||
while (!this._end) {
|
||||
const length = this.readUint32();
|
||||
const type = this.readChars(4);
|
||||
this.decodeChunk(length, type);
|
||||
}
|
||||
this.decodeImage();
|
||||
return this._png;
|
||||
}
|
||||
decodeApng() {
|
||||
(0, signature_1.checkSignature)(this);
|
||||
while (!this._end) {
|
||||
const length = this.readUint32();
|
||||
const type = this.readChars(4);
|
||||
this.decodeApngChunk(length, type);
|
||||
}
|
||||
this.decodeApngImage();
|
||||
return this._apng;
|
||||
}
|
||||
// https://www.w3.org/TR/PNG/#5Chunk-layout
|
||||
decodeChunk(length, type) {
|
||||
const offset = this.offset;
|
||||
switch (type) {
|
||||
// 11.2 Critical chunks
|
||||
case 'IHDR': // 11.2.2 IHDR Image header
|
||||
this.decodeIHDR();
|
||||
break;
|
||||
case 'PLTE': // 11.2.3 PLTE Palette
|
||||
this.decodePLTE(length);
|
||||
break;
|
||||
case 'IDAT': // 11.2.4 IDAT Image data
|
||||
this.decodeIDAT(length);
|
||||
break;
|
||||
case 'IEND': // 11.2.5 IEND Image trailer
|
||||
this._end = true;
|
||||
break;
|
||||
// 11.3 Ancillary chunks
|
||||
case 'tRNS': // 11.3.2.1 tRNS Transparency
|
||||
this.decodetRNS(length);
|
||||
break;
|
||||
case 'iCCP': // 11.3.3.3 iCCP Embedded ICC profile
|
||||
this.decodeiCCP(length);
|
||||
break;
|
||||
case text_1.textChunkName: // 11.3.4.3 tEXt Textual data
|
||||
(0, text_1.decodetEXt)(this._png.text, this, length);
|
||||
break;
|
||||
case 'pHYs': // 11.3.5.3 pHYs Physical pixel dimensions
|
||||
this.decodepHYs();
|
||||
break;
|
||||
default:
|
||||
this.skip(length);
|
||||
break;
|
||||
}
|
||||
if (this.offset - offset !== length) {
|
||||
throw new Error(`Length mismatch while decoding chunk ${type}`);
|
||||
}
|
||||
if (this._checkCrc) {
|
||||
(0, crc_1.checkCrc)(this, length + 4, type);
|
||||
}
|
||||
else {
|
||||
this.skip(4);
|
||||
}
|
||||
}
|
||||
decodeApngChunk(length, type) {
|
||||
const offset = this.offset;
|
||||
if (type !== 'fdAT' && type !== 'IDAT' && this._writingDataChunks) {
|
||||
this.pushDataToFrame();
|
||||
}
|
||||
switch (type) {
|
||||
case 'acTL':
|
||||
this.decodeACTL();
|
||||
break;
|
||||
case 'fcTL':
|
||||
this.decodeFCTL();
|
||||
break;
|
||||
case 'fdAT':
|
||||
this.decodeFDAT(length);
|
||||
break;
|
||||
default:
|
||||
this.decodeChunk(length, type);
|
||||
this.offset = offset + length;
|
||||
break;
|
||||
}
|
||||
if (this.offset - offset !== length) {
|
||||
throw new Error(`Length mismatch while decoding chunk ${type}`);
|
||||
}
|
||||
if (this._checkCrc) {
|
||||
(0, crc_1.checkCrc)(this, length + 4, type);
|
||||
}
|
||||
else {
|
||||
this.skip(4);
|
||||
}
|
||||
}
|
||||
// https://www.w3.org/TR/PNG/#11IHDR
|
||||
decodeIHDR() {
|
||||
const image = this._png;
|
||||
image.width = this.readUint32();
|
||||
image.height = this.readUint32();
|
||||
image.depth = checkBitDepth(this.readUint8());
|
||||
const colorType = this.readUint8();
|
||||
this._colorType = colorType;
|
||||
let channels;
|
||||
switch (colorType) {
|
||||
case internalTypes_1.ColorType.GREYSCALE:
|
||||
channels = 1;
|
||||
break;
|
||||
case internalTypes_1.ColorType.TRUECOLOUR:
|
||||
channels = 3;
|
||||
break;
|
||||
case internalTypes_1.ColorType.INDEXED_COLOUR:
|
||||
channels = 1;
|
||||
break;
|
||||
case internalTypes_1.ColorType.GREYSCALE_ALPHA:
|
||||
channels = 2;
|
||||
break;
|
||||
case internalTypes_1.ColorType.TRUECOLOUR_ALPHA:
|
||||
channels = 4;
|
||||
break;
|
||||
// Kept for exhaustiveness.
|
||||
// eslint-disable-next-line unicorn/no-useless-switch-case
|
||||
case internalTypes_1.ColorType.UNKNOWN:
|
||||
default:
|
||||
throw new Error(`Unknown color type: ${colorType}`);
|
||||
}
|
||||
this._png.channels = channels;
|
||||
this._compressionMethod = this.readUint8();
|
||||
if (this._compressionMethod !== internalTypes_1.CompressionMethod.DEFLATE) {
|
||||
throw new Error(`Unsupported compression method: ${this._compressionMethod}`);
|
||||
}
|
||||
this._filterMethod = this.readUint8();
|
||||
this._interlaceMethod = this.readUint8();
|
||||
}
|
||||
decodeACTL() {
|
||||
this._numberOfFrames = this.readUint32();
|
||||
this._numberOfPlays = this.readUint32();
|
||||
this._isAnimated = true;
|
||||
}
|
||||
decodeFCTL() {
|
||||
const image = {
|
||||
sequenceNumber: this.readUint32(),
|
||||
width: this.readUint32(),
|
||||
height: this.readUint32(),
|
||||
xOffset: this.readUint32(),
|
||||
yOffset: this.readUint32(),
|
||||
delayNumber: this.readUint16(),
|
||||
delayDenominator: this.readUint16(),
|
||||
disposeOp: this.readUint8(),
|
||||
blendOp: this.readUint8(),
|
||||
data: new Uint8Array(0),
|
||||
};
|
||||
this._frames.push(image);
|
||||
}
|
||||
// https://www.w3.org/TR/PNG/#11PLTE
|
||||
decodePLTE(length) {
|
||||
if (length % 3 !== 0) {
|
||||
throw new RangeError(`PLTE field length must be a multiple of 3. Got ${length}`);
|
||||
}
|
||||
const l = length / 3;
|
||||
this._hasPalette = true;
|
||||
const palette = [];
|
||||
this._palette = palette;
|
||||
for (let i = 0; i < l; i++) {
|
||||
palette.push([this.readUint8(), this.readUint8(), this.readUint8()]);
|
||||
}
|
||||
}
|
||||
// https://www.w3.org/TR/PNG/#11IDAT
|
||||
decodeIDAT(length) {
|
||||
this._writingDataChunks = true;
|
||||
const dataLength = length;
|
||||
const dataOffset = this.offset + this.byteOffset;
|
||||
this._inflator.push(new Uint8Array(this.buffer, dataOffset, dataLength));
|
||||
if (this._inflator.err) {
|
||||
throw new Error(`Error while decompressing the data: ${this._inflator.err}`);
|
||||
}
|
||||
this.skip(length);
|
||||
}
|
||||
decodeFDAT(length) {
|
||||
this._writingDataChunks = true;
|
||||
let dataLength = length;
|
||||
let dataOffset = this.offset + this.byteOffset;
|
||||
dataOffset += 4;
|
||||
dataLength -= 4;
|
||||
this._inflator.push(new Uint8Array(this.buffer, dataOffset, dataLength));
|
||||
if (this._inflator.err) {
|
||||
throw new Error(`Error while decompressing the data: ${this._inflator.err}`);
|
||||
}
|
||||
this.skip(length);
|
||||
}
|
||||
// https://www.w3.org/TR/PNG/#11tRNS
|
||||
decodetRNS(length) {
|
||||
switch (this._colorType) {
|
||||
case internalTypes_1.ColorType.GREYSCALE:
|
||||
case internalTypes_1.ColorType.TRUECOLOUR: {
|
||||
if (length % 2 !== 0) {
|
||||
throw new RangeError(`tRNS chunk length must be a multiple of 2. Got ${length}`);
|
||||
}
|
||||
if (length / 2 > this._png.width * this._png.height) {
|
||||
throw new Error(`tRNS chunk contains more alpha values than there are pixels (${length / 2} vs ${this._png.width * this._png.height})`);
|
||||
}
|
||||
this._hasTransparency = true;
|
||||
this._transparency = new Uint16Array(length / 2);
|
||||
for (let i = 0; i < length / 2; i++) {
|
||||
this._transparency[i] = this.readUint16();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case internalTypes_1.ColorType.INDEXED_COLOUR: {
|
||||
if (length > this._palette.length) {
|
||||
throw new Error(`tRNS chunk contains more alpha values than there are palette colors (${length} vs ${this._palette.length})`);
|
||||
}
|
||||
let i = 0;
|
||||
for (; i < length; i++) {
|
||||
const alpha = this.readByte();
|
||||
this._palette[i].push(alpha);
|
||||
}
|
||||
for (; i < this._palette.length; i++) {
|
||||
this._palette[i].push(255);
|
||||
}
|
||||
break;
|
||||
}
|
||||
// Kept for exhaustiveness.
|
||||
/* eslint-disable unicorn/no-useless-switch-case */
|
||||
case internalTypes_1.ColorType.UNKNOWN:
|
||||
case internalTypes_1.ColorType.GREYSCALE_ALPHA:
|
||||
case internalTypes_1.ColorType.TRUECOLOUR_ALPHA:
|
||||
default: {
|
||||
throw new Error(`tRNS chunk is not supported for color type ${this._colorType}`);
|
||||
}
|
||||
/* eslint-enable unicorn/no-useless-switch-case */
|
||||
}
|
||||
}
|
||||
// https://www.w3.org/TR/PNG/#11iCCP
|
||||
decodeiCCP(length) {
|
||||
const name = (0, text_1.readKeyword)(this);
|
||||
const compressionMethod = this.readUint8();
|
||||
if (compressionMethod !== internalTypes_1.CompressionMethod.DEFLATE) {
|
||||
throw new Error(`Unsupported iCCP compression method: ${compressionMethod}`);
|
||||
}
|
||||
const compressedProfile = this.readBytes(length - name.length - 2);
|
||||
this._png.iccEmbeddedProfile = {
|
||||
name,
|
||||
profile: (0, pako_1.inflate)(compressedProfile),
|
||||
};
|
||||
}
|
||||
// https://www.w3.org/TR/PNG/#11pHYs
|
||||
decodepHYs() {
|
||||
const ppuX = this.readUint32();
|
||||
const ppuY = this.readUint32();
|
||||
const unitSpecifier = this.readByte();
|
||||
this._png.resolution = { x: ppuX, y: ppuY, unit: unitSpecifier };
|
||||
}
|
||||
decodeApngImage() {
|
||||
this._apng.width = this._png.width;
|
||||
this._apng.height = this._png.height;
|
||||
this._apng.channels = this._png.channels;
|
||||
this._apng.depth = this._png.depth;
|
||||
this._apng.numberOfFrames = this._numberOfFrames;
|
||||
this._apng.numberOfPlays = this._numberOfPlays;
|
||||
this._apng.text = this._png.text;
|
||||
this._apng.resolution = this._png.resolution;
|
||||
for (let i = 0; i < this._numberOfFrames; i++) {
|
||||
const newFrame = {
|
||||
sequenceNumber: this._frames[i].sequenceNumber,
|
||||
delayNumber: this._frames[i].delayNumber,
|
||||
delayDenominator: this._frames[i].delayDenominator,
|
||||
data: this._apng.depth === 8
|
||||
? new Uint8Array(this._apng.width * this._apng.height * this._apng.channels)
|
||||
: new Uint16Array(this._apng.width * this._apng.height * this._apng.channels),
|
||||
};
|
||||
const frame = this._frames.at(i);
|
||||
if (frame) {
|
||||
frame.data = (0, decodeInterlaceNull_1.decodeInterlaceNull)({
|
||||
data: frame.data,
|
||||
width: frame.width,
|
||||
height: frame.height,
|
||||
channels: this._apng.channels,
|
||||
depth: this._apng.depth,
|
||||
});
|
||||
if (this._hasPalette) {
|
||||
this._apng.palette = this._palette;
|
||||
}
|
||||
if (this._hasTransparency) {
|
||||
this._apng.transparency = this._transparency;
|
||||
}
|
||||
if (i === 0 ||
|
||||
(frame.xOffset === 0 &&
|
||||
frame.yOffset === 0 &&
|
||||
frame.width === this._png.width &&
|
||||
frame.height === this._png.height)) {
|
||||
newFrame.data = frame.data;
|
||||
}
|
||||
else {
|
||||
const prevFrame = this._apng.frames.at(i - 1);
|
||||
this.disposeFrame(frame, prevFrame, newFrame);
|
||||
this.addFrameDataToCanvas(newFrame, frame);
|
||||
}
|
||||
this._apng.frames.push(newFrame);
|
||||
}
|
||||
}
|
||||
return this._apng;
|
||||
}
|
||||
disposeFrame(frame, prevFrame, imageFrame) {
|
||||
switch (frame.disposeOp) {
|
||||
case internalTypes_1.DisposeOpType.NONE:
|
||||
break;
|
||||
case internalTypes_1.DisposeOpType.BACKGROUND:
|
||||
for (let row = 0; row < this._png.height; row++) {
|
||||
for (let col = 0; col < this._png.width; col++) {
|
||||
const index = (row * frame.width + col) * this._png.channels;
|
||||
for (let channel = 0; channel < this._png.channels; channel++) {
|
||||
imageFrame.data[index + channel] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case internalTypes_1.DisposeOpType.PREVIOUS:
|
||||
imageFrame.data.set(prevFrame.data);
|
||||
break;
|
||||
default:
|
||||
throw new Error('Unknown disposeOp');
|
||||
}
|
||||
}
|
||||
addFrameDataToCanvas(imageFrame, frame) {
|
||||
const maxValue = 1 << this._png.depth;
|
||||
const calculatePixelIndices = (row, col) => {
|
||||
const index = ((row + frame.yOffset) * this._png.width + frame.xOffset + col) *
|
||||
this._png.channels;
|
||||
const frameIndex = (row * frame.width + col) * this._png.channels;
|
||||
return { index, frameIndex };
|
||||
};
|
||||
switch (frame.blendOp) {
|
||||
case internalTypes_1.BlendOpType.SOURCE:
|
||||
for (let row = 0; row < frame.height; row++) {
|
||||
for (let col = 0; col < frame.width; col++) {
|
||||
const { index, frameIndex } = calculatePixelIndices(row, col);
|
||||
for (let channel = 0; channel < this._png.channels; channel++) {
|
||||
imageFrame.data[index + channel] =
|
||||
frame.data[frameIndex + channel];
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
// https://www.w3.org/TR/png-3/#13Alpha-channel-processing
|
||||
case internalTypes_1.BlendOpType.OVER:
|
||||
for (let row = 0; row < frame.height; row++) {
|
||||
for (let col = 0; col < frame.width; col++) {
|
||||
const { index, frameIndex } = calculatePixelIndices(row, col);
|
||||
for (let channel = 0; channel < this._png.channels; channel++) {
|
||||
const sourceAlpha = frame.data[frameIndex + this._png.channels - 1] / maxValue;
|
||||
const foregroundValue = channel % (this._png.channels - 1) === 0
|
||||
? 1
|
||||
: frame.data[frameIndex + channel];
|
||||
const value = Math.floor(sourceAlpha * foregroundValue +
|
||||
(1 - sourceAlpha) * imageFrame.data[index + channel]);
|
||||
imageFrame.data[index + channel] += value;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new Error('Unknown blendOp');
|
||||
}
|
||||
}
|
||||
decodeImage() {
|
||||
if (this._inflator.err) {
|
||||
throw new Error(`Error while decompressing the data: ${this._inflator.err}`);
|
||||
}
|
||||
const data = this._isAnimated
|
||||
? (this._frames?.at(0)).data
|
||||
: this._inflator.result;
|
||||
if (this._filterMethod !== internalTypes_1.FilterMethod.ADAPTIVE) {
|
||||
throw new Error(`Filter method ${this._filterMethod} not supported`);
|
||||
}
|
||||
if (this._interlaceMethod === internalTypes_1.InterlaceMethod.NO_INTERLACE) {
|
||||
this._png.data = (0, decodeInterlaceNull_1.decodeInterlaceNull)({
|
||||
data: data,
|
||||
width: this._png.width,
|
||||
height: this._png.height,
|
||||
channels: this._png.channels,
|
||||
depth: this._png.depth,
|
||||
});
|
||||
}
|
||||
else if (this._interlaceMethod === internalTypes_1.InterlaceMethod.ADAM7) {
|
||||
this._png.data = (0, decodeInterlaceAdam7_1.decodeInterlaceAdam7)({
|
||||
data: data,
|
||||
width: this._png.width,
|
||||
height: this._png.height,
|
||||
channels: this._png.channels,
|
||||
depth: this._png.depth,
|
||||
});
|
||||
}
|
||||
else {
|
||||
throw new Error(`Interlace method ${this._interlaceMethod} not supported`);
|
||||
}
|
||||
if (this._hasPalette) {
|
||||
this._png.palette = this._palette;
|
||||
}
|
||||
if (this._hasTransparency) {
|
||||
this._png.transparency = this._transparency;
|
||||
}
|
||||
}
|
||||
pushDataToFrame() {
|
||||
const result = this._inflator.result;
|
||||
const lastFrame = this._frames.at(-1);
|
||||
if (lastFrame) {
|
||||
lastFrame.data = result;
|
||||
}
|
||||
else {
|
||||
this._frames.push({
|
||||
sequenceNumber: 0,
|
||||
width: this._png.width,
|
||||
height: this._png.height,
|
||||
xOffset: 0,
|
||||
yOffset: 0,
|
||||
delayNumber: 0,
|
||||
delayDenominator: 0,
|
||||
disposeOp: internalTypes_1.DisposeOpType.NONE,
|
||||
blendOp: internalTypes_1.BlendOpType.SOURCE,
|
||||
data: result,
|
||||
});
|
||||
}
|
||||
this._inflator = new pako_1.Inflate();
|
||||
this._writingDataChunks = false;
|
||||
}
|
||||
}
|
||||
exports.default = PngDecoder;
|
||||
function checkBitDepth(value) {
|
||||
if (value !== 1 &&
|
||||
value !== 2 &&
|
||||
value !== 4 &&
|
||||
value !== 8 &&
|
||||
value !== 16) {
|
||||
throw new Error(`invalid bit depth: ${value}`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
//# sourceMappingURL=PngDecoder.js.map
|
||||
Reference in New Issue
Block a user