yahoo/pngjs-image

View on GitHub
lib/png/encoder.js

Summary

Maintainability
A
2 hrs
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 Encoder
 * @module PNG
 * @submodule PNGCore
 * @extends chunkUtils
 * @constructor
 */
var Encoder = 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}
 */
Encoder.prototype.getChunkData = function () {
    return this._chunkData;
};


/**
 * Encodes the supplied data
 *
 * @method encode
 * @param {Buffer} image Image data
 * @param {int} width Width of image
 * @param {int} height Height of image
 * @param {object} [options] Optimization options
 */
Encoder.prototype.encode = function (image, width, height, options) {

    options = options || {};

    var i, len,
        stream,
        buffer = image,
        signature = constants.signature;

    this._chunks = {};
    this._chunkData = options;

    // Use optimization to figure out what format to safe it in
    this._optimize(buffer, width, height, options);

    // Run through all chunk-types (not chunks) to gather chunks
    // Phase 1 - Forward
    this.applyWithSortedChunks(function (chunk) {

        // Figure out which chunks we need for the image
        var chunks = Chunk.encodeTypeData(chunk.getType(), image, options, this._chunks);

        // Add all returned chunks
        chunks.forEach(function (chunk) {
            this.addChunk(chunk);
        }.bind(this));

    }.bind(this), false);

    // Run through all chunks before scaling
    // Phase 2 - Backward
    this.applyWithSortedChunks(function (chunk) {
        buffer = chunk.preEncode(buffer, options);
    }, true, true);


    // Run through all chunks after scaling
    // Phase 3 - Backward
    this.applyWithSortedChunks(function (chunk) {
        buffer = chunk.encode(buffer, options);
    }, true, true);


    // Write signature
    stream = new BufferedStream();
    for(i = 0, len = signature.length; i < len; i++) {
        stream.writeUInt8(signature[i], i);
    }

    // Write all chunks
    // Phase 4 - Forward - Write order
    this.applyWithSortedChunks(function (chunk) {
        this._encodeChunk(stream, chunk, options);
    }.bind(this), true, false);

    return stream.toBuffer(false);
};

/**
 * Encodes one chunk to the data stream
 *
 * @method _encodeChunk
 * @param {BufferedStream} stream Data stream
 * @param {Chunk} chunk Chunk that should be serialized to the stream
 * @param {object} options Encoding options
 * @private
 */
Encoder.prototype._encodeChunk = function (stream, chunk, options) {

    var crc,
        buffer,
        crcStartOffset, lengthOffset,
        chunkLength;

    // Write empty length for now
    lengthOffset = stream.writeOffset;
    stream.writeUInt32BE(0);

    // Write type but remember the start of it for CRC
    crcStartOffset = stream.writeOffset;
    if (chunk.getInternalType) { // Use this for unknown chunks
        stream.writeASCIIString(chunk.getInternalType());
    } else {
        stream.writeASCIIString(chunk.getType());
    }

    // Get the chunk-data and how much was written to the stream
    stream.writeCounter = 0;
    chunk.compose(stream, options);
    chunkLength = stream.writeCounter;

    // Calculate and write CRC
    buffer = stream.toBuffer(true);
    crc = new CRC();
    crc.write(buffer, crcStartOffset, chunkLength + 4); // Add chunk-type
    stream.writeInt32BE(crc.getValue());

    // Overwrite length
    buffer.writeUInt32BE(chunkLength, lengthOffset);
};

/**
 *
 * @method _optimize
 * @param {Buffer} image Image data
 * @param {int} width Width of image
 * @param {int} height Height of image
 * @param {object} options Encoding options
 * @private
 */
Encoder.prototype._optimize = function (image, width, height, options) {

    // Setup header data
    options.header = options.header || {};
    options.header.width = width;
    options.header.height = height;

    // Default configuration
    options.header.bitDepth = 8;
    options.header.colorType = 6; // True-color
    options.header.compression = 0; // Deflate
    options.header.filter = 0; // Default

    options.header.interlace = options.interlace || 0; // Default - non-streaming
};

module.exports = Encoder;