Now using webworker to compute current colors

This commit is contained in:
Julian Descottes 2015-04-09 14:01:02 +02:00 committed by juliandescottes
parent 30cdb6d335
commit e11355193b
12 changed files with 295 additions and 37 deletions

View file

@ -33,7 +33,7 @@
"karma": "0.12.17",
"karma-chrome-launcher": "^0.1.4",
"karma-phantomjs-launcher": "^0.1.4",
"karma-jasmine": "^0.2.0",
"karma-jasmine": "^0.3.5",
"nodewebkit": "~0.10.1"
},
"window": {

View file

@ -0,0 +1,52 @@
(function () {
var ns = $.namespace('pskl.model.frame');
ns.AsyncCachedFrameProcessor = function (cacheResetInterval) {
ns.CachedFrameProcessor.call(this, cacheResetInterval);
};
pskl.utils.inherit(ns.AsyncCachedFrameProcessor, ns.CachedFrameProcessor);
/**
* Retrieve the processed frame from the cache, in the (optional) namespace
* If the first level cache is empty, attempt to clone it from 2nd level cache. If second level cache is empty process the frame.
* @param {pskl.model.Frame} frame
* @param {String} namespace
* @return {Object} the processed frame
*/
ns.AsyncCachedFrameProcessor.prototype.get = function (frame, callback, namespace) {
var processedFrame = null;
namespace = namespace || this.defaultNamespace;
if (!this.cache_[namespace]) {
this.cache_[namespace] = {};
}
var cache = this.cache_[namespace];
var firstCacheKey = frame.getHash();
if (cache[firstCacheKey]) {
processedFrame = cache[firstCacheKey];
} else {
var framePixels = JSON.stringify(frame.getPixels());
var secondCacheKey = pskl.utils.hashCode(framePixels);
if (cache[secondCacheKey]) {
processedFrame = this.outputCloner(cache[secondCacheKey], frame);
cache[firstCacheKey] = processedFrame;
} else {
this.frameProcessor(frame, this.onFrameProcessorComplete.bind(this, callback, cache, firstCacheKey, secondCacheKey));
}
}
if (processedFrame) {
callback(processedFrame);
}
};
ns.AsyncCachedFrameProcessor.prototype.onFrameProcessorComplete = function (callback, cache, firstCacheKey, secondCacheKey, processedFrame) {
cache[secondCacheKey] = processedFrame;
cache[firstCacheKey] = processedFrame;
callback(processedFrame);
}
})();

View file

@ -17,6 +17,7 @@
this.cacheResetInterval = cacheResetInterval || DEFAULT_CLEAR_INTERVAL;
this.frameProcessor = DEFAULT_FRAME_PROCESSOR;
this.outputCloner = DEFAULT_OUTPUT_CLONER;
this.defaultNamespace = DEFAULT_NAMESPACE;
window.setInterval(this.clear.bind(this), this.cacheResetInterval);
};

View file

@ -7,7 +7,7 @@
this.cache = {};
this.currentColors = [];
this.cachedFrameProcessor = new pskl.model.frame.CachedFrameProcessor();
this.cachedFrameProcessor = new pskl.model.frame.AsyncCachedFrameProcessor();
this.cachedFrameProcessor.setFrameProcessor(this.getFrameColors_.bind(this));
this.colorSorter = new pskl.service.color.ColorSorter();
@ -44,20 +44,37 @@
var colors = this.cache[historyIndex];
if (colors) {
this.setCurrentColors(colors);
} else {
this.updateCurrentColors_();
}
};
ns.CurrentColorsService.prototype.updateCurrentColors_ = function () {
var layers = this.piskelController.getLayers();
var frames = layers.map(function (l) {return l.getFrames();}).reduce(function (p, n) {return p.concat(n);});
var colors = {};
frames.forEach(function (f) {
var frameColors = this.cachedFrameProcessor.get(f);
Object.keys(frameColors).slice(0, Constants.MAX_CURRENT_COLORS_DISPLAYED).forEach(function (color) {
colors[color] = true;
});
}.bind(this));
this.currentJob = new pskl.utils.Job({
items : frames,
args : {
colors : {}
},
process : function (frame, callback) {
return this.cachedFrameProcessor.get(frame, callback);
}.bind(this),
onProcessEnd : function (frameColors) {
var colors = this.args.colors;
Object.keys(frameColors).slice(0, Constants.MAX_CURRENT_COLORS_DISPLAYED).forEach(function (color) {
colors[color] = true;
});
},
onComplete : this.updateCurrentColorsReady_.bind(this)
});
this.currentJob.start();
};
ns.CurrentColorsService.prototype.updateCurrentColorsReady_ = function (args) {
var colors = args.colors;
// Remove transparent color from used colors
delete colors[Constants.TRANSPARENT_COLOR];
@ -69,30 +86,13 @@
this.setCurrentColors(currentColors);
};
ns.CurrentColorsService.prototype.getFrameColors_ = function (frame) {
var frameColors = {};
frame.forEachPixel(function (color, x, y) {
var hexColor = this.toHexString_(color);
frameColors[hexColor] = true;
}.bind(this));
return frameColors;
};
ns.CurrentColorsService.prototype.getFrameColors_ = function (frame, processorCallback) {
var frameColorsWorker = new pskl.worker.framecolors.FrameColors(frame,
function (event) {processorCallback(event.data.colors);},
function () {},
function (event) {processorCallback({});}
);
ns.CurrentColorsService.prototype.toHexString_ = function (color) {
if (color === Constants.TRANSPARENT_COLOR) {
return color;
} else {
color = color.replace(/\s/g, '');
var hexRe = (/^#([a-f0-9]{3}){1,2}$/i);
var rgbRe = (/^rgb\((\d{1,3}),(\d{1,3}),(\d{1,3})\)$/i);
if (hexRe.test(color)) {
return color.toUpperCase();
} else if (rgbRe.test(color)) {
var exec = rgbRe.exec(color);
return pskl.utils.rgbToHex(exec[1] * 1, exec[2] * 1, exec[3] * 1);
} else {
console.error('Could not convert color to hex : ', color);
}
}
frameColorsWorker.process();
};
})();

View file

@ -14,7 +14,7 @@
};
ns.PaletteImageReader.prototype.onImageLoaded_ = function (image) {
var imageProcessor = new pskl.worker.ImageProcessor(image,
var imageProcessor = new pskl.worker.imageprocessor.ImageProcessor(image,
this.onWorkerSuccess_.bind(this),
this.onWorkerStep_.bind(this),
this.onWorkerError_.bind(this));

29
src/js/utils/Job.js Normal file
View file

@ -0,0 +1,29 @@
(function () {
var ns = $.namespace('pskl.utils');
ns.Job = function (cfg) {
this.args = cfg.args;
this.items = cfg.items;
this.process = cfg.process;
this.onProcessEnd = cfg.onProcessEnd;
this.onComplete = cfg.onComplete;
this.completed_ = 0;
};
ns.Job.prototype.start = function () {
this.items.forEach(function (item, index) {
this.process(item, this.processCallback.bind(this, index));
}.bind(this))
};
ns.Job.prototype.processCallback = function (index, args) {
this.completed_++;
this.onProcessEnd(args, index);
if (this.completed_ === this.items.length) {
this.onComplete(this.args);
}
}
})();

View file

@ -0,0 +1,32 @@
(function () {
var ns = $.namespace('pskl.worker.framecolors');
ns.FrameColors = function (frame, onSuccess, onStep, onError) {
this.serializedFrame = JSON.stringify(frame.pixels);
this.onStep = onStep;
this.onSuccess = onSuccess;
this.onError = onError;
this.worker = pskl.utils.WorkerUtils.createWorker(ns.FrameColorsWorker, 'frame-colors');
this.worker.onmessage = this.onWorkerMessage.bind(this);
};
ns.FrameColors.prototype.process = function () {
this.worker.postMessage({
serializedFrame : this.serializedFrame
});
};
ns.FrameColors.prototype.onWorkerMessage = function (event) {
if (event.data.type === 'STEP') {
this.onStep(event);
} else if (event.data.type === 'SUCCESS') {
this.onSuccess(event);
this.worker.terminate();
} else if (event.data.type === 'ERROR') {
this.onError(event);
this.worker.terminate();
}
};
})();

View file

@ -0,0 +1,66 @@
(function () {
var ns = $.namespace('pskl.worker.framecolors');
if (Constants.TRANSPARENT_COLOR !== 'rgba(0, 0, 0, 0)') {
throw 'Constants.TRANSPARENT_COLOR, please update FrameColorsWorker';
}
ns.FrameColorsWorker = function () {
var TRANSPARENT_COLOR = 'rgba(0, 0, 0, 0)';
var toHexString_ = function(color) {
if (color === TRANSPARENT_COLOR) {
return color;
} else {
color = color.replace(/\s/g, '');
var hexRe = (/^#([a-f0-9]{3}){1,2}$/i);
var rgbRe = (/^rgb\((\d{1,3}),(\d{1,3}),(\d{1,3})\)$/i);
if (hexRe.test(color)) {
return color.toUpperCase();
} else if (rgbRe.test(color)) {
var exec = rgbRe.exec(color);
return rgbToHex(exec[1] * 1, exec[2] * 1, exec[3] * 1);
}
}
};
var rgbToHex = function (r, g, b) {
return "#" + componentToHex(r) + componentToHex(g) + componentToHex(b);
};
var componentToHex = function (c) {
var hex = c.toString(16);
return hex.length == 1 ? "0" + hex : hex;
};
var getFrameColors = function (frame) {
var frameColors = {};
for (var x = 0 ; x < frame.length ; x ++) {
for (var y = 0 ; y < frame[x].length ; y++) {
var color = frame[x][y];
var hexColor = toHexString_(color);
frameColors[hexColor] = true;
}
}
return frameColors;
};
this.onmessage = function(event) {
try {
var data = event.data;
var frame = JSON.parse(data.serializedFrame);
var colors = getFrameColors(frame);
this.postMessage({
type : 'SUCCESS',
colors : colors
});
} catch (e) {
this.postMessage({
type : 'ERROR',
message : e.message
});
}
};
};
})();

View file

@ -8,7 +8,7 @@
this.onSuccess = onSuccess;
this.onError = onError;
this.worker = pskl.utils.WorkerUtils.createWorker(ns.HashWorker, 'hash-builder');
this.worker = pskl.utils.WorkerUtils.createWorker(ns.HashWorker, 'hash');
this.worker.onmessage = this.onWorkerMessage.bind(this);
};

View file

@ -1,7 +1,7 @@
(function () {
var ns = $.namespace('pskl.worker');
var ns = $.namespace('pskl.worker.hash');
ns.HashBuilder = function () {
ns.HashWorker = function () {
var hashCode = function(str) {
var hash = 0;
if (str.length !== 0) {

View file

@ -24,6 +24,7 @@
"js/utils/FileUtilsDesktop.js",
"js/utils/FrameTransform.js",
"js/utils/FrameUtils.js",
"js/utils/Job.js",
"js/utils/LayerUtils.js",
"js/utils/ImageResizer.js",
"js/utils/PixelUtils.js",
@ -58,6 +59,7 @@
"js/model/Layer.js",
"js/model/piskel/Descriptor.js",
"js/model/frame/CachedFrameProcessor.js",
"js/model/frame/AsyncCachedFrameProcessor.js",
"js/model/Palette.js",
"js/model/Piskel.js",
@ -188,6 +190,8 @@
"js/devtools/init.js",
// Workers
"js/worker/framecolors/FrameColorsWorker.js",
"js/worker/framecolors/FrameColors.js",
"js/worker/hash/HashWorker.js",
"js/worker/hash/Hash.js",
"js/worker/imageprocessor/ImageProcessorWorker.js",
@ -195,6 +199,7 @@
// Application controller and initialization
"js/app.js",
// Bonus features !!
"js/snippets.js"
];

73
test/js/utils/JobTest.js Normal file
View file

@ -0,0 +1,73 @@
describe("Job for // async", function() {
beforeEach(function() {});
afterEach(function() {});
it("completes synchronous job", function() {
// when
var isComplete = false;
var result = null;
// then
var job = new pskl.utils.Job({
items : [0,1,2,3,4],
args : {
store : []
},
process : function (item, callback) {
callback(item+5)
},
onProcessEnd : function (value, index) {
this.args.store[index] = value;
},
onComplete : function (args) {
isComplete = true;
result = args.store;
}
});
job.start();
// verify
expect(isComplete).toBe(true);
expect(result).toEqual([5,6,7,8,9]);
});
describe("async", function () {
// when
var isComplete = false;
var result = null;
beforeEach(function(done) {
// then
var job = new pskl.utils.Job({
items : [0,1,2,3,4],
args : {
store : []
},
process : function (item, callback) {
setTimeout(function (item, callback) {
callback(item+5);
}.bind(this, item, callback), 100 - (item * 20));
},
onProcessEnd : function (value, index) {
console.log('Processed ', index);
this.args.store[index] = value;
},
onComplete : function (args) {
isComplete = true;
result = args.store;
done();
}
});
job.start();
});
it("completes asynchronous job", function() {
// verify
expect(isComplete).toBe(true);
expect(result).toEqual([5,6,7,8,9]);
});
})
});