UNPKG

pngjs-image

Version:

JavaScript-based PNG image encoder, decoder, and manipulator

456 lines (391 loc) 11.1 kB
// Copyright 2015 Yahoo! Inc. // Copyrights licensed under the Mit License. See the accompanying LICENSE file for terms. /** * @class BufferedStream * @module PNG * @submodule PNGCore * @param {Buffer} [data] Data buffer * @param {int|boolean} [offset=0] Offset within the data (With offset = false, they data will be taken as-is) * @param {int} [length=data.length-offset] Length of the data * @constructor */ var BufferedStream = function (data, offset, length) { this.readOffset = 0; this.writeOffset = 0; this.readCounter = 0; this.writeCounter = 0; if (data) { if (offset === false) { this._data = data; this.writeOffset = data.length; } else { this._data = new Buffer((length || (data.length - (offset || 0))) * 2); this.writeBuffer(data, offset, length); } } else { if (length === 0) { this._data = new Buffer(); } else { this._data = new Buffer(length || 1024 * 500); } } }; Object.defineProperty(BufferedStream.prototype, 'length', { enumerable: false, configurable: false, get: function () { return this.writeOffset - this.readOffset; }, set: function (length) { if (this.readOffset + length > this.writeOffset) { throw new Error('Length beyond the write pointer is not allowed.'); } this.writeOffset = this.readOffset + length; } }); /** * Returns the number of bytes left before resizing * * @method getSpaceLeft * @return {int} */ BufferedStream.prototype.getSpaceLeft = function () { return this._data.length - this.writeOffset; }; /** * Checks if a read goes beyond the write pointer, reaching out of bounds * * @method _readCheck * @param {int} size Size of the data that is pending to be read from the stream * @private */ BufferedStream.prototype._readCheck = function (size) { if (this.readOffset + size > this.writeOffset) { throw new Error('Reading out of bounds.'); } }; /** * Skips a number of bytes * * @param {int} count Number of bytes to skip */ BufferedStream.prototype.skip = function (count) { this.readOffset += count; this.readCounter += count; }; /** * @method readUInt8 * @param {boolean} [noAssert=false] * @return {int} */ /** * @method peekUInt8 * @param {boolean} [noAssert=false] * @return {int} */ [ ['UInt16LE', 2], ['UInt16BE', 2], ['UInt32LE', 4], ['UInt32BE', 4], ['Int8', 1], ['Int16LE', 2], ['Int16BE', 2], ['Int32LE', 4], ['Int32BE', 4], ['FloatLE', 4], ['FloatBE', 4], ['DoubleLE', 8], ['DoubleBE', 8] ].forEach(function (fnInfo) { ['read', 'peek'].forEach(function (prefix) { var relayedMethodName = 'read' + fnInfo[0], methodName = prefix + fnInfo[0], peeking = (prefix === 'peek'), size = fnInfo[1]; BufferedStream.prototype[methodName] = function (noAssert) { var value; this._readCheck(size); value = this._data[relayedMethodName](this.readOffset, noAssert); if (!peeking) { this.readOffset += size; this.readCounter += size; } return value; } }); }); BufferedStream.prototype.peekUInt8 = function () { return this._data[this.readOffset]; }; BufferedStream.prototype.readUInt8 = function () { var result = this._data[this.readOffset]; this.readOffset++; this.readCounter++; return result; }; BufferedStream.prototype.writeUInt8 = function (value) { this._writeCheck(1); this._data[this.writeOffset] = value & 0xff; this.writeOffset++; this.writeCounter++; }; BufferedStream.prototype.peekUInt16BE = function () { return (this._data[this.readOffset] << 8) | this._data[this.readOffset + 1] }; BufferedStream.prototype.readUInt16BE = function () { var result = (this._data[this.readOffset] << 8) | this._data[this.readOffset + 1]; this.readOffset += 2; this.readCounter += 2; return result; }; BufferedStream.prototype.writeUInt16BE = function (value) { this._writeCheck(2); this._data[this.writeOffset] = (value >>> 8) & 0xff; this._data[this.writeOffset + 1] = value & 0xff; this.writeOffset += 2; this.writeCounter += 2; }; //BufferedStream.prototype.peekUInt32BE = function () { // return (this._data[this.readOffset] * 0x1000000) + // ((this._data[this.readOffset + 1] << 16) | // (this._data[this.readOffset + 2] << 8) | // this._data[this.readOffset + 3]); //}; // //BufferedStream.prototype.readUInt32BE = function () { // var result = this.peekUInt32BE(); // this.readOffset += 4; // this.readCounter += 4; // return result; //}; BufferedStream.prototype.writeUInt32BE = function (value) { this._writeCheck(4); this._data[this.writeOffset] = (value >>> 24) & 0xff; this._data[this.writeOffset + 1] = (value >>> 16) & 0xff; this._data[this.writeOffset + 2] = (value >>> 8) & 0xff; this._data[this.writeOffset + 3] = value & 0xff; this.writeOffset += 4; this.writeCounter += 4; }; /** * Reads a string from the stream without moving the read pointer * * @method peekString * @param {int} size Number of bytes to read from stream * @param {string} [encoding='utf8'] Encoding of string * @return {string} */ BufferedStream.prototype.peekString = function (size, encoding) { this._readCheck(size); return this._data.toString(encoding || 'utf8', this.readOffset, this.readOffset + size); }; /** * Reads a string from the stream * * @method readString * @param {int} size Number of bytes to read from stream * @param {string} [encoding='utf8'] Encoding of string * @return {string} */ BufferedStream.prototype.readString = function (size, encoding) { var result = this.peekString(size, encoding); this.readOffset += size; this.readCounter += size; return result; }; /** * Reads data from the stream into a buffer without moving the read pointer * * @method peekBuffer * @param {int} size Number of bytes that should be read from the stream * @returns {Buffer} */ BufferedStream.prototype.peekBuffer = function (size) { var buffer; this._readCheck(size); buffer = new Buffer(size); this._data.copy(buffer, 0, this.readOffset, this.readOffset + size); return buffer; }; /** * Reads data from the stream into a buffer * * @method readBuffer * @param {int} size Number of bytes that should be read from the stream * @return {Buffer} */ BufferedStream.prototype.readBuffer = function (size) { var buffer = this.peekBuffer(size); this.readOffset += size; this.readCounter += size; return buffer; }; /** * Reads data from the stream into a buffer * * @method peekBufferedStream * @param {BufferedStream} stream Stream to write to * @param {int} size Number of bytes that should be read */ BufferedStream.prototype.peekBufferedStream = function (stream, size) { stream.writeBufferedStream(this, size); }; /** * Reads data from the stream into a buffer * * @method readBufferedStream * @param {BufferedStream} stream Stream to write to * @param {int} size Number of bytes that should be read */ BufferedStream.prototype.readBufferedStream = function (stream, size) { this.peekBufferedStream(stream, size); this.readOffset += size; this.readCounter += size; }; /** * Checks if a write needs a re-size * * @method _writeCheck * @param {int} size Size that is pending to be written to the stream * @private */ BufferedStream.prototype._writeCheck = function (size) { if (this._data.length < this.writeOffset + size) { this._resize(); } }; /** * Re-sizes the internal buffer, copying all existing data into it * * @method _resize * @private */ BufferedStream.prototype._resize = function () { var buffer = new Buffer(this._data.length * 2); this._data.copy(buffer, 0, 0, this.writeOffset); this._data = buffer; }; /** * @method writeUInt8 * @param {boolean} [noAssert=false] */ [ ['writeUInt16LE', 2], ['writeUInt32LE', 4], ['writeInt8', 1], ['writeInt16LE', 2], ['writeInt16BE', 2], ['writeInt32LE', 4], ['writeInt32BE', 4], ['writeFloatLE', 4], ['writeFloatBE', 4], ['writeDoubleLE', 8], ['writeDoubleBE', 8] ].forEach(function (fnInfo) { BufferedStream.prototype[fnInfo[0]] = function (value, noAssert) { this._writeCheck(fnInfo[1]); this._data[fnInfo[0]](value, this.writeOffset, noAssert); this.writeOffset += fnInfo[1]; this.writeCounter += fnInfo[1]; } }); /** * Writes a string to the stream * * @method writeASCIIString * @param {string} text Text to write to the stream */ BufferedStream.prototype.writeASCIIString = function (text) { this._writeCheck(text.length); for(var i = 0, len = text.length; i < len; i++) { this._data.writeUInt8(text.charCodeAt(i) & 0xff, this.writeOffset + i); } this.writeOffset += text.length; this.writeCounter += text.length; }; /** * Writes a string to the stream * * @method writeString * @param {string} text Text to write to the stream * @param {string} [encoding='utf8'] Encoding of string */ BufferedStream.prototype.writeString = function (text, encoding) { this.writeBuffer(new Buffer(text, encoding || 'utf8')); }; /** * Writes a buffer to the stream * * @method writeBuffer * @param {Buffer} buffer Data buffer * @param {int} [offset=0] Offset within buffer * @param {int} [length=buffer.length-offset] length Length of buffer */ BufferedStream.prototype.writeBuffer = function (buffer, offset, length) { var localOffset = offset || 0, localLength = length || (buffer.length - localOffset); this._writeCheck(localLength); buffer.copy(this._data, this.writeOffset, localOffset, localOffset + localLength); this.writeOffset += localLength; this.writeCounter += localLength; }; /** * Writes a buffered stream into the stream * * @param {BufferedStream} stream * @param {int} [length=stream.length] Length of buffered data */ BufferedStream.prototype.writeBufferedStream = function (stream, length) { var len = (length || stream.length); this._writeCheck(len); stream._data.copy(this._data, this.writeOffset, stream.readOffset, stream.readOffset + len); this.writeOffset += len; this.writeCounter += len; }; /** * Clones the stream with the current internal state * * @method clone * @return {BufferedStream} Cloned stream */ BufferedStream.prototype.clone = function () { return this.slice(); }; /** * Slices the stream * * @method slice * @param {int} [start=0] Start of stream * @param {int} [end=this.length] End of stream * @return {BufferedStream} Sliced stream */ BufferedStream.prototype.slice = function (start, end) { var stream, localStart = start || 0, localEnd = end || this._data.length; if (localStart > localEnd) { throw new Error('End cannot be smaller than start.'); } stream = new BufferedStream(this._data, false); stream.writeOffset = this.readOffset + localEnd; stream.readOffset = this.readOffset + localStart; stream.readCounter = this.readCounter; stream.writeCounter = this.writeCounter; return stream; }; /** * Converts contents to a buffer without moving the read pointer * * @method toBuffer * @param {boolean} [noCopy=false] * @return {Buffer} */ BufferedStream.prototype.toBuffer = function (noCopy) { if (noCopy) { return this._data; } else { return this.peekBuffer(this.length); } }; module.exports = BufferedStream;