yahoo/pngjs-image

View on GitHub
lib/png/processor/parser.js

Summary

Maintainability
A
3 hrs
Test Coverage
// Copyright 2015 Yahoo! Inc.
// Copyrights licensed under the Mit License. See the accompanying LICENSE file for terms.

var Interlace = require('./interlace');
var BufferedStream = require('../utils/bufferedStream');

/**
 * @class Scanline Parser
 * @module PNG
 * @submodule PNGCore
 * @param {Chunk} headerChunk Header chunk of data stream
 * @param {object} [options] Options for the compressor
 * @constructor
 */
var Parser = function (headerChunk, options) {
    this._headerChunk = headerChunk;
    this._options = options || {};
};

/**
 * Gets the options
 *
 * @method getOptions
 * @return {object}
 */
Parser.prototype.getOptions = function () {
    return this._options;
};


/**
 * Gets the header chunk
 *
 * @method getHeaderChunk
 * @return {Chunk}
 */
Parser.prototype.getHeaderChunk = function () {
    return this._headerChunk;
};


/**
 * Encodes an image
 *
 * @method encoder
 * @param {Buffer} image
 * @return {Buffer}
 */
Parser.prototype.encoder = function (image) {
    return image;
    //TODO: Writing is currently only 8-bit
};


/**
 * Determines the scanline-parser factory according to header data
 *
 * @method _determineParserFactory
 * @return {object}
 * @private
 */
Parser.prototype._determineParserFactory = function () {

    var headerChunk = this.getHeaderChunk(),
        result = null;

    switch (headerChunk.getBitDepth()) {
        case 1:
            result = '_parse1bit';
            break;

        case 2:
            result = '_parse2bit';
            break;

        case 4:
            result = '_parse4bit';
            break;

        case 8:
            result = '_parse8bit';
            break;

        case 16:
            result = '_parse16bit';
            break;
    }

    return result;
};

/**
 * Decodes an image
 *
 * @method decoder
 * @param {Buffer} image
 * @return {Buffer} 16-bit image
 */
Parser.prototype.decode = function (image) {

    var headerChunk = this.getHeaderChunk(),
        interlace = new Interlace(headerChunk),

        imageStream, outputStream,

        bitDepth = headerChunk.getBitDepth(),
        internalImageSize = headerChunk.getImageSizeInBytes() * 2, // 16-bit
        parser;

    imageStream = new BufferedStream(image, false);
    outputStream = new BufferedStream(null, null, internalImageSize);

    parser = this._determineParserFactory();

    interlace.processPasses(function (width, height, scanLineLength) {

        var i,
            paddingAt,
            length,
            parserFn;

        length = scanLineLength * height;

        paddingAt = headerChunk.scanLineWithWidthPaddingAt(width);
        parserFn = this[parser](paddingAt);

        if (bitDepth === 16) {
            for(i = 0; i < length; i += 2) {
                parserFn(imageStream.readUInt16BE(), outputStream);
            }
        } else {
            for(i = 0; i < length; i += 1) {
                parserFn(imageStream.readUInt8(), outputStream);
            }
        }

    }.bind(this));

    return outputStream.toBuffer();
};

/**
 * Parses a 1-bit scanline stream
 *
 * @method _parse1bit
 * @param {number} paddingAt Defines the position of padding within each scanline
 * @return {function}
 * @private
 */
Parser.prototype._parse1bit = function (paddingAt) {

    var byteCounter = 0;

    return function (value, output) {

        output.writeUInt16BE((value >> 7) & 1); byteCounter++;
        if (!paddingAt || byteCounter < paddingAt) {
            output.writeUInt16BE((value >> 6) & 1);
            byteCounter++;
        }
        if (!paddingAt || byteCounter < paddingAt) {
            output.writeUInt16BE((value >> 5) & 1);
            byteCounter++;
        }
        if (!paddingAt || byteCounter < paddingAt) {
            output.writeUInt16BE((value >> 4) & 1);
            byteCounter++;
        }
        if (!paddingAt || byteCounter < paddingAt) {
            output.writeUInt16BE((value >> 3) & 1);
            byteCounter++;
        }
        if (!paddingAt || byteCounter < paddingAt) {
            output.writeUInt16BE((value >> 2) & 1);
            byteCounter++;
        }
        if (!paddingAt || byteCounter < paddingAt) {
            output.writeUInt16BE((value >> 1) & 1);
            byteCounter++;
        }
        if (!paddingAt || byteCounter < paddingAt) {
            output.writeUInt16BE(value & 1);
            byteCounter++;
        }

        // Make sure that padding is removed
        if (paddingAt && byteCounter >= paddingAt) {
            byteCounter = 0;
        }
    };
};

/**
 * Parses a 2-bit scanline stream
 *
 * @method _parse2bit
 * @param {number} paddingAt Defines the position of padding within each scanline
 * @return {function}
 * @private
 */
Parser.prototype._parse2bit = function (paddingAt) {

    var byteCounter = 0;

    return function (value, output) {

        output.writeUInt16BE((value >> 6) & 3); byteCounter++;
        if (!paddingAt || byteCounter < paddingAt) {
            output.writeUInt16BE((value >> 4) & 3);
            byteCounter++;
        }
        if (!paddingAt || byteCounter < paddingAt) {
            output.writeUInt16BE((value >> 2) & 3);
            byteCounter++;
        }
        if (!paddingAt || byteCounter < paddingAt) {
            output.writeUInt16BE(value & 3);
            byteCounter++;
        }

        // Make sure that padding is removed
        if (paddingAt && byteCounter >= paddingAt) {
            byteCounter = 0;
        }
    };
};

/**
 * Parses a 4-bit scanline stream
 *
 * @method _parse4bit
 * @param {number} paddingAt Defines the position of padding within each scanline
 * @return {function}
 * @private
 */
Parser.prototype._parse4bit = function (paddingAt) {

    var byteCounter = 0;

    return function (value, output) {

        output.writeUInt16BE((value >> 4) & 15); byteCounter++;
        if (!paddingAt || byteCounter < paddingAt) {
            output.writeUInt16BE(value & 15);
            byteCounter++;
        }

        // Make sure that padding is removed
        if (paddingAt && byteCounter >= paddingAt) {
            byteCounter = 0;
        }
    };
};

/**
 * Parses a 8-bit scanline stream
 *
 * @method _parse8bit
 * @return {function}
 * @private
 */
Parser.prototype._parse8bit = function () {
    return function (value, output) {
        output.writeUInt16BE(value);
    };
};

/**
 * Parses a 16-bit scanline stream
 *
 * @method _parse16bit
 * @return {function}
 * @private
 */
Parser.prototype._parse16bit = function () {
    return function (value, output) {
        output.writeUInt16BE(value);
    };
};

module.exports = Parser;