Skip to content
This repository has been archived by the owner on Jul 15, 2019. It is now read-only.

Add PNG encoder/decoder #6

Merged
merged 9 commits into from
Apr 20, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Binary file modified examples/export.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified examples/export_lightness.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified examples/export_sepia.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
60 changes: 60 additions & 0 deletions examples/testDecoder.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
var Decoder = require('../lib/png/decoder');
var Encoder = require('../lib/png/encoder');
// //var decoder = new Decoder(content, 0);
// //var result = decoder.decode();
//
// //console.log(result);
//
// //result.copy(image._image.data, 0, 0, image._image.data.length);
// console.log('done');
//
// //image._image.data = result;
// image.writeImage(__dirname + '/test_out.png', function (err) {
// if (err) throw err;
// console.log('Done!')
// });
//});
//


// Copyright 2014-2015, Yahoo! Inc.
// Copyrights licensed under the Mit License. See the accompanying LICENSE file for terms.

var PNGImage = require('../index'),
fs = require('fs');

var blob = fs.readFileSync(__dirname + '/a1.png');

// Load the image from a blob
var image = PNGImage.loadImage(blob, function (err, image) {

var newImage;

if (err) {
throw err;
}

//var decoder = new Decoder(blob, 0);
//var result = decoder.decode();
image = PNGImage.loadImageSync(blob);

////result.copy(image._image.data, 0, 0, result.length);
//for(var i = 0; i < image._image.data.length; i++) {
// if (image._image.data[i] != result[i]) {
// console.log('different');
// }
//}


// Export it
image.writeImage(__dirname + '/export2.png', function (err) {
if (err) throw err;

image.writeImageSync(__dirname + '/a3.png');
//var encoder = new Encoder(image.getWidth(), image.getHeight(), image._image.data, 0);
//
//fs.writeFileSync(__dirname + '/a2.png', encoder.encode());

console.log('Done');
});
});
37 changes: 37 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ var fs = require('fs'),
filters = require('./lib/filters'),
streamBuffers = require("stream-buffers");

var Decoder = require('./lib/png/decoder');
var Encoder = require('./lib/png/encoder');

/**
* PNGjs-image class
*
Expand Down Expand Up @@ -143,6 +146,30 @@ PNGImage.loadImage = function (blob, fn) {
return resultImage;
};


/**
* Loads an image synchronously from a blob
*
* @static
* @method loadImageSync
* @param {Buffer} blob
* @return {PNGImage}
*/
PNGImage.loadImageSync = function (blob) {
var decoder = new Decoder(blob, 0);
var result = decoder.decode();
var headerChunk = decoder.getHeaderChunk();
var width = headerChunk.getWidth();
var height = headerChunk.getHeight();

var image = new PNG({
width: width,
height: height
});
result.copy(image.data, 0, 0, result.length);
return new PNGImage(image);
};

/**
* Log method that can be overwritten to modify the logging behavior
*
Expand Down Expand Up @@ -350,6 +377,16 @@ PNGImage.prototype = {
}.bind(this));
},

writeImageSync: function (filename) {
fs.writeFileSync(filename, this.toBlobSync());
},

toBlobSync: function () {
var encoder = new Encoder(this.getWidth(), this.getHeight(), this.getBlob(), 0);

return encoder.encode();
},

/**
* Writes the image to a buffer
*
Expand Down
25 changes: 14 additions & 11 deletions lib/pixel.js
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ module.exports = {
* @return {int}
*/
getRed: function (idx) {
return this._getValue(idx << 2, 0);
return this._getValue(idx, 0);
},

/**
Expand All @@ -172,7 +172,7 @@ module.exports = {
* @param {number} [opacity] Opacity of value set
*/
setRed: function (idx, value, opacity) {
this._setValue(idx << 2, 0, value, opacity);
this._setValue(idx, 0, value, opacity);
},


Expand All @@ -184,7 +184,7 @@ module.exports = {
* @return {int}
*/
getGreen: function (idx) {
return this._getValue(idx << 2, 1);
return this._getValue(idx, 1);
},

/**
Expand All @@ -196,7 +196,7 @@ module.exports = {
* @param {number} [opacity] Opacity of value set
*/
setGreen: function (idx, value, opacity) {
this._setValue(idx << 2, 1, value, opacity);
this._setValue(idx, 1, value, opacity);
},


Expand All @@ -208,7 +208,7 @@ module.exports = {
* @return {int}
*/
getBlue: function (idx) {
return this._getValue(idx << 2, 2);
return this._getValue(idx, 2);
},

/**
Expand All @@ -220,7 +220,7 @@ module.exports = {
* @param {number} [opacity] Opacity of value set
*/
setBlue: function (idx, value, opacity) {
this._setValue(idx << 2, 2, value, opacity);
this._setValue(idx, 2, value, opacity);
},


Expand All @@ -232,7 +232,7 @@ module.exports = {
* @return {int}
*/
getAlpha: function (idx) {
return this._getValue(idx << 2, 3);
return this._getValue(idx, 3);
},

/**
Expand All @@ -244,7 +244,7 @@ module.exports = {
* @param {number} [opacity] Opacity of value set
*/
setAlpha: function (idx, value, opacity) {
this._setValue(idx << 2, 3, value, opacity);
this._setValue(idx, 3, value, opacity);
},


Expand All @@ -258,7 +258,8 @@ module.exports = {
* @private
*/
_getValue: function (offset, colorOffset) {
return this._image.data[offset + colorOffset];
var localOffset = offset << 2;
return this._image.data[localOffset + colorOffset];
},

/**
Expand All @@ -272,7 +273,9 @@ module.exports = {
* @private
*/
_setValue: function (offset, colorOffset, value, opacity) {
var previousValue = this._getValue(offset, colorOffset);
this._image.data[offset + colorOffset] = this._calculateColorValue(previousValue, value, opacity);
var previousValue = this._getValue(offset, colorOffset),
localOffset = offset << 2;

this._image.data[localOffset + colorOffset] = this._calculateColorValue(previousValue, value, opacity);
}
};
134 changes: 134 additions & 0 deletions lib/png/chunk.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
var utils = require('./utils');
var path = require('path');

var Chunk = function (type, chunks) {
this._type = type;
this._chunks = chunks;

utils.loadModule(path.join(__dirname, 'chunkUtils.js'), this);
Chunk.applyChunkType(this.getType(), this);
};

/**
* Defines chunk to be known/unknown
*
* @method isUnknown
* @return {boolean}
*/
Chunk.prototype.isUnknown = function () {
return true;
};

Chunk.prototype.getType = function () {
return utils.getChunkTypeById(this._type);
};

Chunk.prototype.getTypeId = function () {
return this._type;
};


Chunk.prototype._isUpperCase = function (value) {
return !(value & 0x20); // 0x20 = 32 dec -> Lowercase has bit 32 set
};


// Critical chunks are necessary for successful display of the contents of the datastream, for example the image header chunk (IHDR). A decoder trying to extract the image, upon encountering an unknown chunk type in which the ancillary bit is 0, shall indicate to the user that the image contains information it cannot safely interpret.
// Ancillary chunks are not strictly necessary in order to meaningfully display the contents of the datastream, for example the time chunk (tIME). A decoder encountering an unknown chunk type in which the ancillary bit is 1 can safely ignore the chunk and proceed to display the image.
Chunk.prototype.isCritical = function () {
return this._isUpperCase(this._type[0]);
};

Chunk.prototype.isAncillary = function () {
return !this.isCritical();
};


// A public chunk is one that is defined in this International Standard or is registered in the list of PNG special-purpose public chunk types maintained by the Registration Authority (see 4.9 Extension and registration). Applications can also define private (unregistered) chunk types for their own purposes. The names of private chunks have a lowercase second letter, while public chunks will always be assigned names with uppercase second letters. Decoders do not need to test the private-chunk property bit, since it has no functional significance; it is simply an administrative convenience to ensure that public and private chunk names will not conflict. See clause 14: Editors and extensions and 12.10.2: Use of private chunks.
Chunk.prototype.isPublic = function () {
return this._isUpperCase(this._type[1]);
};

Chunk.prototype.isPrivate = function () {
return !this.isPublic();
};


// This property bit is not of interest to pure decoders, but it is needed by PNG editors. This bit defines the proper handling of unrecognized chunks in a datastream that is being modified. Rules for PNG editors are discussed further in 14.2: Behaviour of PNG editors.
Chunk.prototype.isSafe = function () {
return !this.isUnsafe();
};

Chunk.prototype.isUnsafe = function () {
return this._isUpperCase(this._type[3]);
};


/**
* Makes sure that all required information is available before decoding
*
* @method preParse
* @param {Buffer} data Chunk data
* @param {int} offset Offset in chunk data
* @param {int} length Length of chunk data
* @param {boolean} strict Should decoding be strict?
*/
Chunk.prototype.preDecode = function (data, offset, length, strict) {

};

/**
* Decoding of chunk data
*
* @method parse
* @param {Buffer} data Chunk data
* @param {int} offset Offset in chunk data
* @param {int} length Length of chunk data
* @param {boolean} strict Should decoding be strict?
*/
Chunk.prototype.decode = function (data, offset, length, strict) {

};

/**
* Validates all decoded data
*
* @method postParse
* @param {Buffer} data Chunk data
* @param {int} offset Offset in chunk data
* @param {int} length Length of chunk data
* @param {boolean} strict Should decoding be strict?
*/
Chunk.prototype.postDecode = function (data, offset, length, strict) {

};


Chunk._registry = {};
Chunk.addChunkType = function (type, module) {
this._registry[type] = module;
};
Chunk.getChunkType = function (type) {
return this._registry[type];
};
Chunk.applyChunkType = function (type, obj) {
var methods = this.getChunkType(type);

if (methods) {
utils.copyModule(methods, obj);
} else {
throw new Error('Unknown chunk-type ' + type);
}
};
Chunk.initDefaultChunkTypes = function () {
var chunks = ['bKGD', 'cHRM', 'gAMA', 'hIST', 'iCCP', 'IDAT', 'IEND', 'IHDR', 'iTXt', 'pHYs', 'PLTE', 'sBIT', 'sPLT', 'sRGB', 'tEXt', 'tIME', 'tRNS', 'zTXt'];

chunks.forEach(function (chunkType) {
this.addChunkType(chunkType, require(path.join(__dirname, 'chunks', chunkType)));
}.bind(this));
};


Chunk.initDefaultChunkTypes();

module.exports = Chunk;
42 changes: 42 additions & 0 deletions lib/png/chunkUtils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
module.exports = {

getChunks: function () {
return this._chunks;
},

clearChunksByType: function (type) {
if (this.hasChunksByType(type)) {
this.getChunks()[type] = [];
}
},

getChunksByType: function (type, required) {
var chunks = this.getChunks();

if (chunks[type] && chunks[type].length > 0) {
return chunks[type];
} else if (required) {
throw new Error('Could not retrieve header chunk.');
} else {
return null;
}
},

hasChunksByType: function (type) {
return (this.getChunksByType(type, false) !== null);
},

getFirstChunk: function (type, required) {
var chunks = this.getChunksByType(type, required);

if (!required && chunks == null) {
return null;
} else {
return chunks[0];
}
},

getHeaderChunk: function () {
return this.getFirstChunk('IHDR', true);
}
};