yahoo/pngjs-image

View on GitHub
lib/png/decoder.js

Summary

Maintainability
C
1 day
Test Coverage
// 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;