UNPKG

pngjs-image

Version:

JavaScript-based PNG image encoder, decoder, and manipulator

488 lines (430 loc) 10.2 kB
// Copyright 2015 Yahoo! Inc. // Copyrights licensed under the Mit License. See the accompanying LICENSE file for terms. var utils = require('./utils/utils'); var path = require('path'); /** * @class Chunk * @module PNG * @submodule PNGCore * @extends chunkUtils * @param {string} type Chunk-type for loading the right chunk * @param {object} chunks Dictionary of available chunks * @constructor */ var Chunk = function (type, chunks) { this._chunks = chunks; // Import chunk Chunk.applyChunkType(type, this); }; /** * Gets the chunk-type as string * * Note: * Identifier for chunk that is the string of the chunk-type. * * @method getType * @return {string|null} */ Chunk.prototype.getType = function () { return null; }; /** * Gets the sequence * * Note: * This defines the sequence the chunk will have when all chunks are written to the blob. * Lowest sequence numbers will be written first. * * Range: * * 0 - Header * * 500 - Data * * 1000 - End * * @method getSequence * @return {int} */ Chunk.prototype.getSequence = function () { return 750; }; /** * Is value an upper-case ASCII character? * * @method _isUpperCase * @param {int} value * @return {boolean} * @private */ Chunk.prototype._isUpperCase = function (value) { return !(value & 0x20); // 0x20 = 32 dec -> Lowercase has bit 32 set }; /** * Is the chunk a critical chunk that cannot be ignored? * * @method isCritical * @return {boolean} */ Chunk.prototype.isCritical = function () { return this._isUpperCase(this.getType().charCodeAt(0)); }; /** * Is the chunk an ancillary chunk that can be ignored when unknown? * * @method isAncillary * @return {boolean} */ Chunk.prototype.isAncillary = function () { return !this.isCritical(); }; /** * Is the chunk a public chunk? * * @method isPublic * @return {boolean} */ Chunk.prototype.isPublic = function () { return this._isUpperCase(this.getType().charCodeAt(1)); }; /** * Is the chunk a private chunk? * * @method isPrivate * @return {boolean} */ Chunk.prototype.isPrivate = function () { return !this.isPublic(); }; /** * Is the data safe to copy? * * @method isSafe * @return {boolean} */ Chunk.prototype.isSafe = function () { return !this.isUnsafe(); }; /** * Is the data safe to copy? * * @method isUnsafe * @return {boolean} */ Chunk.prototype.isUnsafe = function () { return this._isUpperCase(this.getType().charCodeAt(3)); }; /** * Parsing of chunk data * * Phase 1 * * Note: * Use this methods to parse data for each chunk. * * @method parse * @param {BufferedStream} stream Data stream * @param {int} length Length of chunk data * @param {boolean} strict Should parsing be strict? * @param {object} options Decoding options */ Chunk.prototype.parse = function (stream, length, strict, options) { throw new Error('Unimplemented method "parse".'); }; /** * Decoding of chunk data before scaling * * Phase 2 * * Note: * Use this method when you have to do some preliminary * modifications to the image or data like decompression, * applying of changes before the image is scaled, etc. * * @method decode * @param {Buffer} image * @param {boolean} strict Should parsing be strict? * @param {object} options Decoding options * @return {Buffer} */ Chunk.prototype.decode = function (image, strict, options) { // Do nothing by default return image; }; /** * Re-working of image after scaling * * Phase 3 * * Note: * Use this method to add modifications to the scaled image. * * @method postDecode * @param {Buffer} image * @param {boolean} strict Should parsing be strict? * @param {object} options Decoding options * @return {Buffer} */ Chunk.prototype.postDecode = function (image, strict, options) { // Do nothing by default return image; }; /** * Finalizing image - applying changes to completed image * * Phase 4 * * @method finalizeDecode * @param {Buffer} image * @param {boolean} strict Should parsing be strict? * @param {object} options Decoding options * @return {Buffer} */ Chunk.prototype.finalizeDecode = function (image, strict, options) { // Do nothing by default return image; }; /** * Decodes chunk-data to an external data-object * * Phase 5 * * Note: * Use this method to export data to the data-object. * * @static * @method decodeData * @param {object} decodeData Data-object that will be used to export values * @param {boolean} strict Should parsing be strict? * @param {object} options Decoding options */ Chunk.prototype.decodeData = function (decodeData, strict, options) { // Do nothing by default }; /** * Encodes chunk-data from an external data-object * * Phase 1 * * Note: * Use this method to import data from the data-object. * * @static * @method encodeData * @param {object} options Encoding options * @return {Chunk[]} List of chunks to encode */ Chunk.prototype.encodeData = function (options) { // Do nothing by default return []; }; /** * Before encoding of chunk data * * Phase 2 * * Note: * Use this method to gather image-data before scaling. * * @method preEncode * @param {Buffer} image * @param {object} options Encoding options * @return {Buffer} */ Chunk.prototype.preEncode = function (image, options) { // Do nothing by default return image; }; /** * Encoding of chunk data * * Phase 3 * * Note: * Use this method to add data to the image after scaling. * * @method encode * @param {Buffer} image * @param {object} options Encoding options * @return {Buffer} */ Chunk.prototype.encode = function (image, options) { // Do nothing by default return image; }; /** * Composing of image data * * Phase 4 * * Note: * Use this method to compose each chunks data. * * @method compose * @param {BufferedStream} stream Data stream * @param {object} options Encoding options */ Chunk.prototype.compose = function (stream, options) { throw new Error('Unimplemented method "compose".'); }; /** * Registry * * @static * @type {object} * @private */ Chunk._registry = {}; /** * Modifies the data-object with the contents of the decoded chunks * * @static * @method decodeTypeData * @param {string} type Chunk-type * @param {object} decodeData Object that will holds all the data from the decoded chunks * @param {object} chunks Dictionary of already decoded chunks * @param {boolean} strict Should parsing be strict? * @param {object} [options] */ Chunk.decodeTypeData = function (type, decodeData, chunks, strict, options) { var methods = this.getChunkType(type); if (!methods) { throw new Error('Unknown chunk-type ' + type); } if (methods.decodeData) { methods._staticChunks = chunks; methods.decodeData(decodeData, strict, options); } }; /** * Determines a list of chunks to encode of the same type from the data-object * * @static * @method encodeTypeData * @param {string} type Chunk-type * @param {Buffer} image Image data * @param {object} options Encoding options * @param {object} chunks Dictionary of already determined chunks * @return {Chunk[]} List of chunks to encode */ Chunk.encodeTypeData = function (type, image, options, chunks) { var methods = this.getChunkType(type); if (!methods) { throw new Error('Unknown chunk-type ' + type); } if (methods.encodeData) { methods._staticChunks = chunks; return methods.encodeData(image, options); } else { return []; } }; /** * Adds a new chunk-type to the registry * * @static * @method addChunkType * @param {string} type Name of the chunk * @param {object} module List of methods specific for the chunk-type */ Chunk.addChunkType = function (type, module) { // Add utils utils.loadModule(path.join(__dirname, 'utils', 'chunkUtils.js'), module); // This is needed for static access!!! module.getType = function () { return type; }; this._registry[type] = module; }; /** * Gets a specific chunk-type module, listing all chunk-type specific methods * * @static * @method getChunkType * @param {string} type Name of the chunk * @return {object} Chunk module */ Chunk.getChunkType = function (type) { return this._registry[type]; }; /** * Gets a list of registered chunk types * * @static * @method getChunkTypes * @return {string[]} */ Chunk.getChunkTypes = function () { var result = [], i; for (i in this._registry) { if (this._registry.hasOwnProperty(i)) { result.push(i); } } return result; }; /** * Applies the chunk-module on an object * * @static * @method applyChunkType * @param {string} type Name of the chunk * @param {object} obj Object the module to apply to */ Chunk.applyChunkType = function (type, obj) { var methods = this.getChunkType(type), unknownType = false; // Use default chunk for unknown types if (!methods) { methods = this.getChunkType('zzZz'); unknownType = type; type = 'zzZz'; } if (methods) { utils.copyModule(methods, obj); // Remember the original type if it is an unknown chunk-type if (unknownType) { obj.setInternalType(unknownType); } // This is needed for dynamic access, specifically for the default chunk!!! obj.getType = function () { return type; }; } else { throw new Error('Unknown chunk-type ' + type); } }; /** * Initializes all official chunk types * * @static * @method initDefaultChunkTypes */ Chunk.initDefaultChunkTypes = function () { var chunks = [ 'bKGD', 'cHRM', 'gAMA', 'hIST', 'iCCP', 'IDAT', 'IEND', 'IHDR', // 'iTXt' 'pHYs', 'PLTE', 'sRGB', 'tEXt', 'tIME', 'tRNS', 'zTXt', // 'sBIT', 'sPLT' 'oFFs', 'sCAL', 'sTER' // 'pCAL' ], module; chunks.forEach(function (chunkType) { module = require(path.join(__dirname, 'chunks', chunkType)); this.addChunkType(chunkType, module); }.bind(this)); }; /** * Initializes all known custom chunk types * * @static * @method initCustomChunkTypes */ Chunk.initCustomChunkTypes = function () { var chunks = ['stRT', 'jsOn', 'zzZz'], module; chunks.forEach(function (chunkType) { module = require(path.join(__dirname, 'custom', chunkType)); this.addChunkType(chunkType, module); }.bind(this)); }; // Initialize Chunk.initDefaultChunkTypes(); Chunk.initCustomChunkTypes(); module.exports = Chunk;