mirror of
https://github.com/piskelapp/piskel.git
synced 2023-08-10 21:12:52 +03:00
Now using webworker to compute current colors
This commit is contained in:
parent
30cdb6d335
commit
e11355193b
@ -33,7 +33,7 @@
|
|||||||
"karma": "0.12.17",
|
"karma": "0.12.17",
|
||||||
"karma-chrome-launcher": "^0.1.4",
|
"karma-chrome-launcher": "^0.1.4",
|
||||||
"karma-phantomjs-launcher": "^0.1.4",
|
"karma-phantomjs-launcher": "^0.1.4",
|
||||||
"karma-jasmine": "^0.2.0",
|
"karma-jasmine": "^0.3.5",
|
||||||
"nodewebkit": "~0.10.1"
|
"nodewebkit": "~0.10.1"
|
||||||
},
|
},
|
||||||
"window": {
|
"window": {
|
||||||
|
52
src/js/model/frame/AsyncCachedFrameProcessor.js
Normal file
52
src/js/model/frame/AsyncCachedFrameProcessor.js
Normal 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);
|
||||||
|
}
|
||||||
|
})();
|
@ -17,6 +17,7 @@
|
|||||||
this.cacheResetInterval = cacheResetInterval || DEFAULT_CLEAR_INTERVAL;
|
this.cacheResetInterval = cacheResetInterval || DEFAULT_CLEAR_INTERVAL;
|
||||||
this.frameProcessor = DEFAULT_FRAME_PROCESSOR;
|
this.frameProcessor = DEFAULT_FRAME_PROCESSOR;
|
||||||
this.outputCloner = DEFAULT_OUTPUT_CLONER;
|
this.outputCloner = DEFAULT_OUTPUT_CLONER;
|
||||||
|
this.defaultNamespace = DEFAULT_NAMESPACE;
|
||||||
|
|
||||||
window.setInterval(this.clear.bind(this), this.cacheResetInterval);
|
window.setInterval(this.clear.bind(this), this.cacheResetInterval);
|
||||||
};
|
};
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
this.cache = {};
|
this.cache = {};
|
||||||
this.currentColors = [];
|
this.currentColors = [];
|
||||||
|
|
||||||
this.cachedFrameProcessor = new pskl.model.frame.CachedFrameProcessor();
|
this.cachedFrameProcessor = new pskl.model.frame.AsyncCachedFrameProcessor();
|
||||||
this.cachedFrameProcessor.setFrameProcessor(this.getFrameColors_.bind(this));
|
this.cachedFrameProcessor.setFrameProcessor(this.getFrameColors_.bind(this));
|
||||||
|
|
||||||
this.colorSorter = new pskl.service.color.ColorSorter();
|
this.colorSorter = new pskl.service.color.ColorSorter();
|
||||||
@ -44,20 +44,37 @@
|
|||||||
var colors = this.cache[historyIndex];
|
var colors = this.cache[historyIndex];
|
||||||
if (colors) {
|
if (colors) {
|
||||||
this.setCurrentColors(colors);
|
this.setCurrentColors(colors);
|
||||||
|
} else {
|
||||||
|
this.updateCurrentColors_();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ns.CurrentColorsService.prototype.updateCurrentColors_ = function () {
|
ns.CurrentColorsService.prototype.updateCurrentColors_ = function () {
|
||||||
var layers = this.piskelController.getLayers();
|
var layers = this.piskelController.getLayers();
|
||||||
var frames = layers.map(function (l) {return l.getFrames();}).reduce(function (p, n) {return p.concat(n);});
|
var frames = layers.map(function (l) {return l.getFrames();}).reduce(function (p, n) {return p.concat(n);});
|
||||||
var colors = {};
|
|
||||||
|
|
||||||
frames.forEach(function (f) {
|
this.currentJob = new pskl.utils.Job({
|
||||||
var frameColors = this.cachedFrameProcessor.get(f);
|
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) {
|
Object.keys(frameColors).slice(0, Constants.MAX_CURRENT_COLORS_DISPLAYED).forEach(function (color) {
|
||||||
colors[color] = true;
|
colors[color] = true;
|
||||||
});
|
});
|
||||||
}.bind(this));
|
},
|
||||||
|
onComplete : this.updateCurrentColorsReady_.bind(this)
|
||||||
|
});
|
||||||
|
|
||||||
|
this.currentJob.start();
|
||||||
|
};
|
||||||
|
|
||||||
|
ns.CurrentColorsService.prototype.updateCurrentColorsReady_ = function (args) {
|
||||||
|
var colors = args.colors;
|
||||||
|
|
||||||
// Remove transparent color from used colors
|
// Remove transparent color from used colors
|
||||||
delete colors[Constants.TRANSPARENT_COLOR];
|
delete colors[Constants.TRANSPARENT_COLOR];
|
||||||
@ -69,30 +86,13 @@
|
|||||||
this.setCurrentColors(currentColors);
|
this.setCurrentColors(currentColors);
|
||||||
};
|
};
|
||||||
|
|
||||||
ns.CurrentColorsService.prototype.getFrameColors_ = function (frame) {
|
ns.CurrentColorsService.prototype.getFrameColors_ = function (frame, processorCallback) {
|
||||||
var frameColors = {};
|
var frameColorsWorker = new pskl.worker.framecolors.FrameColors(frame,
|
||||||
frame.forEachPixel(function (color, x, y) {
|
function (event) {processorCallback(event.data.colors);},
|
||||||
var hexColor = this.toHexString_(color);
|
function () {},
|
||||||
frameColors[hexColor] = true;
|
function (event) {processorCallback({});}
|
||||||
}.bind(this));
|
);
|
||||||
return frameColors;
|
|
||||||
};
|
|
||||||
|
|
||||||
ns.CurrentColorsService.prototype.toHexString_ = function (color) {
|
frameColorsWorker.process();
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
})();
|
})();
|
@ -14,7 +14,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
ns.PaletteImageReader.prototype.onImageLoaded_ = function (image) {
|
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.onWorkerSuccess_.bind(this),
|
||||||
this.onWorkerStep_.bind(this),
|
this.onWorkerStep_.bind(this),
|
||||||
this.onWorkerError_.bind(this));
|
this.onWorkerError_.bind(this));
|
||||||
|
29
src/js/utils/Job.js
Normal file
29
src/js/utils/Job.js
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})();
|
32
src/js/worker/framecolors/FrameColors.js
Normal file
32
src/js/worker/framecolors/FrameColors.js
Normal 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();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})();
|
66
src/js/worker/framecolors/FrameColorsWorker.js
Normal file
66
src/js/worker/framecolors/FrameColorsWorker.js
Normal 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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
})();
|
@ -8,7 +8,7 @@
|
|||||||
this.onSuccess = onSuccess;
|
this.onSuccess = onSuccess;
|
||||||
this.onError = onError;
|
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);
|
this.worker.onmessage = this.onWorkerMessage.bind(this);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
(function () {
|
(function () {
|
||||||
var ns = $.namespace('pskl.worker');
|
var ns = $.namespace('pskl.worker.hash');
|
||||||
|
|
||||||
ns.HashBuilder = function () {
|
ns.HashWorker = function () {
|
||||||
var hashCode = function(str) {
|
var hashCode = function(str) {
|
||||||
var hash = 0;
|
var hash = 0;
|
||||||
if (str.length !== 0) {
|
if (str.length !== 0) {
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
"js/utils/FileUtilsDesktop.js",
|
"js/utils/FileUtilsDesktop.js",
|
||||||
"js/utils/FrameTransform.js",
|
"js/utils/FrameTransform.js",
|
||||||
"js/utils/FrameUtils.js",
|
"js/utils/FrameUtils.js",
|
||||||
|
"js/utils/Job.js",
|
||||||
"js/utils/LayerUtils.js",
|
"js/utils/LayerUtils.js",
|
||||||
"js/utils/ImageResizer.js",
|
"js/utils/ImageResizer.js",
|
||||||
"js/utils/PixelUtils.js",
|
"js/utils/PixelUtils.js",
|
||||||
@ -58,6 +59,7 @@
|
|||||||
"js/model/Layer.js",
|
"js/model/Layer.js",
|
||||||
"js/model/piskel/Descriptor.js",
|
"js/model/piskel/Descriptor.js",
|
||||||
"js/model/frame/CachedFrameProcessor.js",
|
"js/model/frame/CachedFrameProcessor.js",
|
||||||
|
"js/model/frame/AsyncCachedFrameProcessor.js",
|
||||||
"js/model/Palette.js",
|
"js/model/Palette.js",
|
||||||
"js/model/Piskel.js",
|
"js/model/Piskel.js",
|
||||||
|
|
||||||
@ -188,6 +190,8 @@
|
|||||||
"js/devtools/init.js",
|
"js/devtools/init.js",
|
||||||
|
|
||||||
// Workers
|
// Workers
|
||||||
|
"js/worker/framecolors/FrameColorsWorker.js",
|
||||||
|
"js/worker/framecolors/FrameColors.js",
|
||||||
"js/worker/hash/HashWorker.js",
|
"js/worker/hash/HashWorker.js",
|
||||||
"js/worker/hash/Hash.js",
|
"js/worker/hash/Hash.js",
|
||||||
"js/worker/imageprocessor/ImageProcessorWorker.js",
|
"js/worker/imageprocessor/ImageProcessorWorker.js",
|
||||||
@ -195,6 +199,7 @@
|
|||||||
|
|
||||||
// Application controller and initialization
|
// Application controller and initialization
|
||||||
"js/app.js",
|
"js/app.js",
|
||||||
|
|
||||||
// Bonus features !!
|
// Bonus features !!
|
||||||
"js/snippets.js"
|
"js/snippets.js"
|
||||||
];
|
];
|
73
test/js/utils/JobTest.js
Normal file
73
test/js/utils/JobTest.js
Normal 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]);
|
||||||
|
});
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
});
|
Loading…
x
Reference in New Issue
Block a user