You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
211 lines
5.0 KiB
211 lines
5.0 KiB
var startTime = new Date().getTime();
|
|
|
|
var fs = require("fs");
|
|
var pngjs = require("pngjs").PNG;
|
|
|
|
/**
|
|
*
|
|
*/
|
|
var Pixels = function() {};
|
|
|
|
/**
|
|
*
|
|
*/
|
|
Pixels.prototype = {
|
|
source: null,
|
|
confirm: null,
|
|
target: null,
|
|
result: {},
|
|
};
|
|
|
|
/**
|
|
* Heavy lifting done here.
|
|
*/
|
|
Pixels.prototype.repalettize = function() {
|
|
// Indices is count from 0 to LxW, used to reference pixels in the tgtPalette, which are 8? bits wide.
|
|
var indices = [];
|
|
for (var y = 0; y < P.target.height; y++) {
|
|
for (var x = 0; x < P.target.width; x++) {
|
|
indices.push((P.target.width * y + x) << 2);
|
|
}
|
|
}
|
|
|
|
var len = indices.length;
|
|
P.result.asBuffer = new Buffer(P.target.height * P.target.width * 4);
|
|
P.result.asArray = [];
|
|
|
|
var i, ii;
|
|
|
|
while (len) {
|
|
i = Math.floor(Math.random() * len);
|
|
ii = indices[i];
|
|
|
|
// Find RGB in source, no need for alpha channel.
|
|
matchIndex = P.findMatch(
|
|
zeropad(P.target.asBuffer[ii], 3) +
|
|
zeropad(P.target.asBuffer[ii + 1], 3) +
|
|
zeropad(P.target.asBuffer[ii + 2], 3)
|
|
);
|
|
|
|
matchRgb = P.source.asArray[matchIndex];
|
|
|
|
P.result.asArray.push(matchRgb);
|
|
|
|
P.result.asBuffer[ii] = matchRgb.substr(0, 3) * 1;
|
|
P.result.asBuffer[ii + 1] = matchRgb.substr(3, 3) * 1;
|
|
P.result.asBuffer[ii + 2] = matchRgb.substr(6, 3) * 1;
|
|
P.result.asBuffer[ii + 3] = 255;
|
|
|
|
indices.splice(i, 1);
|
|
P.source.asArray.splice(matchIndex, 1);
|
|
len = indices.length;
|
|
}
|
|
|
|
var resultImg = new pngjs({
|
|
filterType: 4
|
|
});
|
|
|
|
resultImg.data = P.result.asBuffer;
|
|
resultImg.width = P.target.width;
|
|
resultImg.height = P.target.height;
|
|
resultImg.pack().pipe(fs.createWriteStream('result.png'));
|
|
|
|
var endTime = new Date().getTime();
|
|
console.log((endTime - startTime) / 1000 + " seconds, confirming");
|
|
|
|
P.doConfirm(P.confirm.asArray, P.result.asArray) ?
|
|
console.log('OK - Source array and result array match.') :
|
|
console.log('ERROR! Source array and result array do not match!');
|
|
};
|
|
|
|
/**
|
|
* Slightly modified binary search tree.
|
|
*/
|
|
Pixels.prototype.findMatch = function(rgb0) {
|
|
var start = 0;
|
|
var end = P.source.asArray.length;
|
|
var mid;
|
|
|
|
while (start + 1 < end) {
|
|
mid = Math.floor((end - start) / 2 + start);
|
|
if (P.source.asArray[mid] < rgb0) {
|
|
start = mid;
|
|
}
|
|
else {
|
|
end = mid;
|
|
}
|
|
}
|
|
|
|
return start;
|
|
};
|
|
|
|
/**
|
|
*
|
|
*/
|
|
Pixels.prototype.doConfirm = function(arr1, arr2) {
|
|
var len1 = arr1.length;
|
|
var len2 = arr2.length;
|
|
|
|
if (len1 !== len2) {
|
|
return false;
|
|
}
|
|
|
|
arr1.sort();
|
|
arr2.sort();
|
|
|
|
for (var i = 0; i < len1; i++) {
|
|
if (arr1[i] !== arr2[i]) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
/**
|
|
* Reads an image from a path, generates required information, and passes information to callback.
|
|
*/
|
|
Pixels.prototype.read = function(path0, callback0) {
|
|
var width = 0;
|
|
var height = 0;
|
|
var asBuffer = null;
|
|
var asArray = null;
|
|
|
|
fs.createReadStream(path0).pipe(new pngjs({ filterType: 4 }))
|
|
.on('metadata', function(meta0) {
|
|
width = meta0.width;
|
|
height = meta0.height;
|
|
})
|
|
.on('parsed', function(buffer0) {
|
|
var x, y, i;
|
|
var arr = [];
|
|
for (y = 0; y < height; y++) {
|
|
for (x = 0; x < width; x++) {
|
|
i = y * width + x << 2;
|
|
|
|
arr.push(zeropad(buffer0[i], 3) + zeropad(buffer0[i + 1], 3) + zeropad(buffer0[i + 2], 3));
|
|
}
|
|
}
|
|
|
|
callback0({
|
|
width: width,
|
|
height: height,
|
|
asBuffer: buffer0,
|
|
asArray: arr,
|
|
});
|
|
});
|
|
};
|
|
|
|
/**
|
|
* sprintf implementation to ensure 9-digit pixels for sorting.
|
|
*/
|
|
var zeropad = function(str0, len0) {
|
|
str0 = str0.toString();
|
|
while (str0.length < len0) {
|
|
str0 = "0" + str0;
|
|
}
|
|
return str0;
|
|
};
|
|
|
|
/**
|
|
* Ansynchronous file reads will execute and call this function. After they're all finished, the processing can begin.
|
|
*/
|
|
var filesRead = 0;
|
|
var thenContinue = function(data0) {
|
|
filesRead++;
|
|
|
|
if (filesRead === 3) {
|
|
P.source.asArray.sort();
|
|
P.repalettize();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Information for the source image, where the pixels are taken from.
|
|
*/
|
|
var thenSaveSource = function(obj0) {
|
|
P.source = obj0;
|
|
thenContinue();
|
|
}
|
|
|
|
/**
|
|
* A copy of the source data used after processing to ensure source pixels match result pixels.
|
|
*/
|
|
var thenSaveConfirm = function(obj0) {
|
|
P.confirm = obj0;
|
|
thenContinue();
|
|
}
|
|
|
|
/**
|
|
* Information for the target images, which the pixels are matched to.
|
|
*/
|
|
var thenSaveTarget = function(obj0) {
|
|
P.target = obj0;
|
|
thenContinue();
|
|
}
|
|
|
|
//===== Entry point
|
|
var P = new Pixels();
|
|
P.read('scream.png', thenSaveSource);
|
|
P.read('scream.png', thenSaveConfirm);
|
|
P.read('starry.png', thenSaveTarget);
|
|
|