UNPKG

pngjs-image

Version:

JavaScript-based PNG image encoder, decoder, and manipulator

205 lines (161 loc) 4.88 kB
// Copyright 2015 Yahoo! Inc. // Copyrights licensed under the Mit License. See the accompanying LICENSE file for terms. var Chunk = require('./chunk'); var CRC = require('./processor/crc'); var BufferedStream = require('./utils/bufferedStream'); var utils = require('./utils/utils'); var constants = require('./utils/constants'); var path = require('path'); /** * @class Decoder * @module PNG * @submodule PNGCore * @extends chunkUtils * @constructor */ var Decoder = function () { this._chunks = {}; this._chunkData = {}; utils.loadModule(path.join(__dirname, 'utils', 'chunkUtils.js'), this); }; /** * Gets the data supplied by each chunk * * @method getChunkData * @return {object} */ Decoder.prototype.getChunkData = function () { return this._chunkData; }; /** * Decodes the supplied data * * @method decode * @param {Buffer} buffer Image data * @param {object} [options] Decoding options * @param {boolean} [options.strict=false] Strict decoding */ Decoder.prototype.decode = function (buffer, options) { options = options || {}; var i, len, chunk, image = null, signature = constants.signature, streamSignature, strict = !!options.strict, stream = new BufferedStream(buffer, false); this._chunks = {}; this._chunkData = {}; // Check signature streamSignature = stream.readBuffer(signature.length); for (i = 0, len = signature.length; i < len; i++) { if (streamSignature[i] !== signature[i]) { // Always throw error (even with strict=false) - need to be sure it is a PNG throw new Error('Invalid signature for a PNG image.'); } } // Load all chunks until end is reached // Phase 1 - Forward do { chunk = this._parseChunk(stream, strict, options); } while (chunk.getType() !== 'IEND'); // Run through all chunks before scaling // Phase 2 - Forward this.applyWithSortedChunks(function (chunk) { try { image = chunk.decode(image, strict, options); } catch (err) { // Ignore non-critical errors - not required for image reading // If strict is requested, then re-throw if (strict || chunk.isCritical()) { throw err; } } }, true); // Run through all chunks after scaling // Phase 3 - Forward this.applyWithSortedChunks(function (chunk) { try { image = chunk.postDecode(image, strict, options); } catch (err) { // Ignore non-critical errors - not required for image reading // If strict is requested, then re-throw if (strict || chunk.isCritical()) { throw err; } } }, true); // Run through all chunks after image is complete // Phase 4 - Forward this.applyWithSortedChunks(function (chunk) { try { image = chunk.finalizeDecode(image, strict, options); } catch (err) { // Ignore non-critical errors - not required for image reading // If strict is requested, then re-throw if (strict || chunk.isCritical()) { throw err; } } }, true); // Run through all chunks-types (not chunks) to gather chunk-data // Phase 5 - Forward this.applyWithSortedChunks(function (chunk) { Chunk.decodeTypeData(chunk.getType(), this._chunkData, this._chunks, strict, options); }.bind(this), false); return image; }; /** * Reads the next chunk in the stream * * @method _parseChunk * @param {BufferedStream} stream Data stream * @param {boolean} strict Should decoding be strict? * @param {object} options Decoding options * @return {Chunk} Chunk read * @private */ Decoder.prototype._parseChunk = function (stream, strict, options) { var chunkLength, chunkType, chunkCrc, calculatedCrc, crc, chunk, chunkOffset; // Read chunk-length chunkLength = stream.readUInt32BE(); if (chunkLength + 8 > stream.length) { throw new Error('Chunk length is greater than the input data.'); } chunkOffset = stream.readOffset + chunkLength + 8; // 8 for type and CRC value // Load chunk for crc calculation crc = new CRC(); crc.write(stream.toBuffer(true), stream.readOffset, chunkLength + 4); // Including chunk-type // Create chunk that is suggested by the data chunkType = stream.readString(4, 'ascii'); chunk = new Chunk(chunkType, this._chunks); try { // Parsing of chunk data chunk.parse(stream, chunkLength, strict, options); // Load crc and compare with calculated one chunkCrc = stream.readInt32BE(); calculatedCrc = crc.getValue(); if (strict && (chunkCrc !== calculatedCrc)) { //throw new Error('CRC error.'); //TODO: Something is not working; overflow? } // Add to parsed chunks this.addChunk(chunk); } catch (err) { // Ignore non-critical errors - not required for image reading // If strict is requested, then re-throw if (strict || chunk.isCritical()) { throw err; } } finally { // Make sure the chunk did not read over bounds - resetting of reader offset stream.readOffset = chunkOffset; } return chunk; }; module.exports = Decoder;